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.)
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 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 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 = {}
In order for BaseSolver to propertly interpret your equation module, it needs to follow a certain format specification, which are described below.
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:
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',
}
}
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',
Docstrings can be automatically generated for your equation functions using a decorator generated by 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.
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:
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 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 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.