#!/usr/bin/env python3
# system modules
import collections
import inspect
import copy
# internal modules
from . import interfaces
from . import utils
# external modules
import numpy as np
[docs]class Equation(utils.LoggerObject,utils.ReprObject):
"""
Base class for equations
Args:
description (str, optional): short equation description
long_description (str, optional): long equation description
variable (StateVariable, optional): the variable obtained by solving
the equation
input (SetOfInterfaceValues, optional): set of values needed by the
equation
"""
def __init__(self, variable = None,
description = None, long_description = None,
input = None,
):
if not variable is None:
self.variable = variable
if not description is None:
self.description = description
if not input is None:
self.input = input
if not long_description is None:
self.long_description = long_description
##################
### Properties ###
##################
@property
def variable(self):
"""
The variable the equation is able to solve for
:type: :any:`StateVariable`
"""
try: self._variable
except AttributeError: self._variable = self._default_variable
return self._variable
@variable.setter
def variable(self, newvar):
assert isinstance(newvar, interfaces.StateVariable), \
"variable has to be instance of StateVariable"
self._variable = newvar
@property
def _default_variable(self):
"""
The default variable if none was given
:type: :any:`StateVariable`
"""
return interfaces.StateVariable()
@property
def description(self):
"""
The description of the equation
:type: :any:`str`
"""
try: self._description
except AttributeError: self._description = self._default_description
return self._description
@description.setter
def description(self, newdescription):
assert isinstance(newdescription, str), "description has to be str"
self._description = newdescription
@property
def _default_description(self):
"""
The default description if none was given
:type: :any:`str`
"""
return "an equation"
@property
def input(self):
"""
The input needed by the equation. Only real dependencies should be
included. If the equation depends on the :any:`variable`, it should also
be included in :any:`input`.
:type: :any:`SetOfInterfaceValues`
"""
try: self._input
except AttributeError: self._input = self._default_input
return self._input
@input.setter
def input(self, newinput):
assert isinstance(newinput, interfaces.SetOfInterfaceValues), \
"input has to be SetOfInterfaceValues"
self._input = newinput
@property
def _default_input(self):
"""
The default input if none was given
:type: :any:`SetOfInterfaceValues`
"""
return interfaces.SetOfInterfaceValues()
@property
def long_description(self):
"""
The longer description of this equation
:type: :any:`str`
"""
try: self._long_description
except AttributeError:
self._long_description = self._default_long_description
return self._long_description
@long_description.setter
def long_description(self, newlong_description):
assert isinstance(newlong_description, str), \
"long_description has to be str"
self._long_description = newlong_description
@property
def _default_long_description(self):
"""
The default long description if none was given
:type: :any:`str`
"""
return "This is an equation."
###############
### Methods ###
###############
[docs] def depends_on(self, id):
""" Check if this equation depends on a given :any:`InterfaceValue`
Args:
id (str or InterfaceValue): an :any:`InterfaceValue` or an id
Returns:
bool : ``True`` if 'id' is in :any:`input`, ``False`` otherwise
"""
try: ident = id.id
except AttributeError: ident = id
assert isinstance(ident,str), \
"id is neither str nor has it an 'id' attribute"
return ident in self.input.keys()
[docs] def __str__(self): # pragma: no cover
"""
Stringification
Returns:
str : a summary
"""
string = (
" \"{description}\" \n"
"----------- {variable} -----------\n"
"{long_description}"
).format(description=self.description, variable=self.variable.id,
long_description = self.long_description)
return string
[docs]class DerivativeEquation(Equation):
"""
Class to represent a derivative equation
"""
###############
### Methods ###
###############
[docs] def linear_factor(self, time = None): # pragma: no cover
"""
Calculate the derivative's linear factor in front of the variable
Args:
time (single numeric, optional): the time to calculate the
derivative. Defaults to the variable's current (last) time.
Returns:
numpy.ndarray : the equation's linear factor at the corresponding
time
"""
raise NotImplementedError("subclasses must override this method")
[docs] def independent_addend(self, time = None): # pragma: no cover
"""
Calculate the derivative's addend part that is independent of the
variable.
Args:
time (single numeric, optional): the time to calculate the
derivative. Defaults to the variable's current (last) time.
Returns:
numpy.ndarray : the equation's variable-independent addend at the
corresponding time
"""
raise NotImplementedError("subclasses must override this method")
[docs] def nonlinear_addend(
self, time = None, variablevalue = None): # pragma: no cover
"""
Calculate the derivative's addend part that is nonlinearly dependent
of the variable.
Args:
time (single numeric, optional): the time to calculate the
derivative. Defaults to the variable's current (last) time.
variablevalue (numpy.ndarray, optional): the variable vaulue to use.
Defaults to the value of self.variable at the given time.
Returns:
numpy.ndarray : the equation's nonlinear addend at the corresponding
time
"""
raise NotImplementedError("subclasses must override this method")
[docs] def derivative(self, time = None, variablevalue = None): # pragma: no cover
""" Calculate the derivative (right-hand-side) of the equation
Args:
time (single numeric, optional): the time to calculate the
derivative. Defaults to the variable's current (last) time.
variablevalue (numpy.ndarray, optional): the variable vaulue to use.
Defaults to the value of self.variable at the given time.
Returns:
numpy.ndarray : the derivatives corresponding to the given time
"""
if variablevalue is None: var = self.variable(time)
else: var = variablevalue
# calculate the derivative parts
linear_factor = self.linear_factor(time = time)
independent_addend = self.independent_addend(time = time)
nonlinear_addend = self.nonlinear_addend(
time = time, variablevalue = var)
# merge parts
deriv = linear_factor * var + independent_addend + nonlinear_addend
return deriv
[docs]class PrognosticEquation(DerivativeEquation):
"""
Class to represent prognostic equations
"""
pass
[docs]class DiagnosticEquation(Equation):
"""
Class to represent diagnostic equations
"""
pass
########################
### Set of Equations ###
########################
[docs]class SetOfEquations(utils.SetOfObjects):
"""
Base class for sets of Equations
Args:
elements (:any:`list` of :any:`Equation`, optional): the list of
:any:`Equation` instances
"""
def __init__(self, elements = []):
utils.SetOfObjects.__init__(self, # call SetOfObjects constructor
elements = elements,
element_type = Equation, # only Equation is allowed
)
###############
### Methods ###
###############
[docs] def _object_to_key(self, obj):
"""
key transformation function.
Args:
obj (object): the element
Returns:
str : the unique key for this object. The :any:`Equation.variable`'s
:any:`id` is used.
"""
return obj.variable.id