Subclassing BaseSolver and FluidSolver
======================================
Subclassing FluidSolver
-----------------------
You may want to subclass FluidSolver to have a class with the same equations
as FluidSolver, but a different set of default assumptions. The default
assumptions are a tuple *default_assumptions* that may be overridden::
class DryFluidSolver(FluidSolver):
default_assumptions = ('no liquid water', 'no ice', 'low water vapor',
'ideal gas', 'bolton', 'constant g',
'constant Lv', 'hydrostatic', 'constant Cp')
# also set a new solution dictionary so it will not be inherited
# from FluidSolver
_solutions = {}
The new class can then be instantiated with, for instance::
>>> solver = DryFluidSolver(p=1e5, T=300.)
Subclassing BaseSolver
----------------------
:mod:`atmos.equations` has a particular set of equations relevant to
atmospheric science. It may be that you want to use a different set of
equations, whether because you want more equations than :class:`FluidSolver`
has available or because you want to use a completely different set of
equations, say, for oceanic science. In order to do this, you can write
a new module in the same style as :mod:`atmos.equations`, and create a
Solver that uses your equation module. For details on writing an equations
module, see the next section.
Assuming your equations module is called **myequations**, this is how you
would create a solver for it::
import myequations
class MySolver(BaseSolver):
# This should be set to your equation module
_equation_module = myequations
# This contains a list of assumptions from your module that you want
# to enable by default
default_assumptions = ('constant density', 'constant g')
# a solution dictionary is necessary for caching solutions between
# calls to calculate()
_solutions = {}
Writing an Equation Module
--------------------------
In order for BaseSolver to propertly interpret your equation module, it needs
to follow a certain format specification, which are described below.
quantities dictionary
+++++++++++++++++++++
There must be a variable *quantities* defined whose keys are the
abbreviations of quantities you will use (such as 'rho', 'T', 'x', etc.).
The values of the dictionary should be dictionaries with the keys 'name' and
'units', whose values are a descriptive name of the quantity (such as 'air
density', 'air temperature', or 'zonal position'), and an udunits-compatible
specification of the units of the quantity (such as 'kg/m^3', 'K', or 'm').
Some key requirements to note:
* Any quantity output by an equation or taken as input for an equation in
your module must be present in the quantities dictionary
* Your equations must always take in quantities with the units specified in
this dictionary, and return quantities with the units specified in this
dictionary.
Example::
quantities = {
'AH': {
'name': 'absolute humidity',
'units': 'kg/m^3',
},
'DSE': {
'name': 'dry static energy',
'units': 'J',
},
'e': {
'name': 'water vapor partial pressure',
'units': 'Pa',
}
}
assumptions dictionary
++++++++++++++++++++++
There must be a variable *assumptions* defined whose keys are abbreviations
for assumptions you will use (such as 'constant g', 'hydrostatic', etc.),
and values are longer (but still short) descriptions for those assumptions
(such as 'g is constant and equal to 9.81', 'the hydrostatic approximation
is valid', etc.). The values are only used when generating documentation for
your equations (and when other users are looking at your code to figure out
what your assumptions mean).
Example::
assumptions = {
'hydrostatic': 'hydrostatic balance',
'constant g': 'g is constant',
'constant Lv': 'latent heat of vaporization of water is constant',
automatic documentation
+++++++++++++++++++++++
Docstrings can be automatically generated for your equation functions using a
decorator generated by :func:`atmos.decorators.equation_docstring`. The
easiest way to use this decorator is to define the following function in
your equation module::
def autodoc(**kwargs):
return equation_docstring(quantities, assumptions, **kwargs)
You do not *need* to use this generator (or have any documentation) for your
equations, but since automatically generating docstrings ensures they are
always kept up to date when you update your functions, it is recommended.
writing equation functions
++++++++++++++++++++++++++
Any function in your module whose name follows the convention
"{quantity}_from_*" is considered to be an equation function by BaseSolver.
Here is an example equation function (assuming **autodoc** has been defined as
above)::
@autodoc(equation=r'qv = \frac{(\frac{Tv}{T} - 1)}{0.608}')
@assumes('no liquid water', 'no ice')
@overridden_by_assumptions('Tv equals T')
def qv_from_Tv_T(Tv, T):
return (Tv/T - 1.)/0.608
At the least, an equation function must:
* have a name conforming to "{quantity}_from_*" with a quantity that is present
in your quantity dictionary
* be decorated by :func:`atmos.decorators.assumes`
* take in only variables that are abbreviations present in your quantity
dictionary
* return only one variable, which is the result of the equation
If you choose to decorate with **autodoc**, you must have the decorator above
any other equation decorators so that it can use the metadata they attach to
the equation function. **autodoc** takes in an optional keyword argument
*equation* which is a latex-formatted string of the equation used.
The decorator :func:`atmos.decorators.assumes` takes in
assumption abbreviation strings which correspond to assumptions made by the
equation described in the function. This equation will only
be enabled if all of its assumptions are enabled.
The decorator :func:`atmos.decorators.overridden_by_assumptions` is
optional, and if present takes in assumption abbreviation strings which
correspond to assumptions that override the equation described in the function.
If any of these assumptions are enabled, this equation will be disabled.