"""
Parameter module
================
This module contains the basic parameter class to hold model's parameters values.
It allows to manipulate dimensional and nondimensional parameter easily.
Examples
--------
>>> from qgs.params.params import ScaleParams
>>> from qgs.params.parameter import Parameter, ParametersArray
>>> import numpy as np
>>> # defining a scale object to help Parameter compute the nondimensionalization
>>> sc = ScaleParams()
>>> # creating a parameter initialized with a nondimensional value but returning a
>>> # dimensional one when called
>>> sigma = Parameter(0.2e0, input_dimensional=False, scale_object=sc,
... units='[m^2][s^-2][Pa^-2]',
... description="static stability of the atmosphere",
... return_dimensional=True)
>>> sigma
2.1581898457499433e-06
>>> sigma.nondimensional_value
0.2
>>> sigma.return_dimensional
True
>>> # creating a parameter initialized with a dimensional value but returning a
>>> # nondimensional one when called
>>> sigma = Parameter(2.1581898457499433e-06, input_dimensional=True, scale_object=sc,
... units='[m^2][s^-2][Pa^-2]',
... description="static stability of the atmosphere",
... return_dimensional=False)
>>> sigma
0.2
>>> sigma.dimensional_value
2.1581898457499433e-06
>>> sigma.return_dimensional
False
>>> # creating a parameters array initialized with a nondimensional values and returning
>>> # nondimensional ones when called
>>> s = ParametersArray(np.array([[0.1,0.2],[0.3,0.4]]), input_dimensional=False, scale_object=sc, units='[s^-1]',
... description="atmosphere bottom friction coefficient")
>>> s
ArrayParameters([[0.1, 0.2],
[0.3, 0.4]], dtype=object)
>>> # dimensional values can also be retrieved with
>>> s.dimensional_values
array([[1.0320000000000001e-05, 2.0640000000000002e-05],
[3.096e-05, 4.1280000000000005e-05]], dtype=object)
>>> # you can also ask for the dimensional value of one particular value of the array
>>> s[0,0]
0.1
>>> s[0,0].dimensional_value
1.0320000000000001e-05
Main class
----------
"""
import warnings
import numpy as np
from fractions import Fraction
# TODO: Automatize warnings and errors
[docs]
class ScalingParameter(float):
"""Class of model's dimension parameter.
Parameters
----------
value: float
Value of the parameter.
units: str, optional
The units of the provided value. Used to compute the conversion between dimensional and nondimensional
value. Should be specified by joining atoms like `'[unit^power]'`, e.g '`[m^2][s^-2][Pa^-2]'`.
Empty by default.
description: str, optional
String describing the parameter.
dimensional: bool, optional
Indicate if the value of the parameter is dimensional or not. Default to `True`.
symbol: ~sympy.core.symbol.Symbol, optional
A `Sympy`_ symbol to represent the parameter in symbolic expressions.
symbolic_expression: ~sympy.core.expr.Expr, optional
A `Sympy`_ expression to represent a relationship to other parameters.
Notes
-----
Parameter is immutable. Once instantiated, it cannot be altered. To create a new parameter, one must
re-instantiate it.
.. _Sympy: https://www.sympy.org/
"""
def __new__(cls, value, units="", description="", dimensional=False, symbol=None, symbolic_expression=None):
f = float.__new__(cls, value)
f._dimensional = dimensional
f._units = units
f._description = description
f._symbol = symbol
f._symbolic_expression = symbolic_expression
return f
@property
def symbol(self):
"""~sympy.core.symbol.Symbol: Returns the symbol of the parameter."""
return self._symbol
@property
def symbolic_expression(self):
"""~sympy.core.expr.Expr: Returns the symbolic expression of the parameter."""
if self._symbolic_expression is None and self._symbol is not None:
return self._symbol
else:
return self._symbolic_expression
@property
def dimensional(self):
"""bool: Indicate if the returned value is dimensional or not."""
return self._dimensional
@property
def units(self):
"""str: The units of the dimensional value."""
return self._units
@property
def description(self):
"""str: Description of the parameter."""
return self._description
def __add__(self, other):
res = float(self) + other
if isinstance(other, (Parameter, ScalingParameter)):
if self.units != other.units:
raise ArithmeticError("ScalingParameter class: Impossible to add two parameters with different units.")
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol + other.symbol
else:
expr = None
descr = self.description + " + " + other.description
else:
if self.symbol is not None:
expr = self.symbol + (other.symbolic_expression)
descr = self.description + " + (" + other.description + ")"
else:
expr = None
descr = self.description + " + " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) + other.symbol
descr = "(" + self.description + ") + " + other.description
else:
expr = None
descr = self.description + " + " + other.description
else:
expr = (self.symbolic_expression) + (other.symbolic_expression)
descr = "(" + self.description + ") + (" + other.description + ")"
if isinstance(other, Parameter):
return Parameter(res, input_dimensional=other.input_dimensional,
return_dimensional=other.return_dimensional, scale_object=other._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
else:
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol + other
descr = self.description + " + " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) + other
descr = "(" + self.description + ") + " + str(other)
else:
expr = None
descr = self.description + " + " + str(other)
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
res = float(self) - other
if isinstance(other, (Parameter, ScalingParameter)):
if self.units != other.units:
raise ArithmeticError("ScalingParameter class: Impossible to subtract two parameters with different units.")
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol - other.symbol
else:
expr = None
descr = self.description + " - " + other.description
else:
if self.symbol is not None:
expr = self.symbol - (other.symbolic_expression)
descr = self.description + " - (" + other.description + ")"
else:
expr = None
descr = self.description + " - " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) - other.symbol
descr = "(" + self.description + ") - " + other.description
else:
expr = None
descr = self.description + " - " + other.description
else:
expr = (self.symbolic_expression) - (other.symbolic_expression)
descr = "(" + self.description + ") - (" + other.description + ")"
if isinstance(other, Parameter):
return Parameter(res, input_dimensional=other.input_dimensional,
return_dimensional=other.return_dimensional, scale_object=other._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
else:
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol - other
descr = self.description + " - " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) - other
descr = "(" + self.description + ") - " + str(other)
else:
expr = None
descr = self.description + " - " + str(other)
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __rsub__(self, other):
try:
res = other - float(self)
if self.symbol is not None:
expr = other - self.symbol
descr = str(other) + " - " + self.description
elif self.symbolic_expression is not None:
expr = other - (self.symbolic_expression)
descr = str(other) + " - (" + self.description + ")"
else:
expr = None
descr = str(other) + " - " + self.description
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __mul__(self, other):
res = float(self) * other
if isinstance(other, (Parameter, ScalingParameter)):
units = _combine_units(self.units, other.units, '+')
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol * other.symbol
else:
expr = None
descr = self.description + " * " + other.description
else:
if self.symbol is not None:
expr = self.symbol * (other.symbolic_expression)
descr = self.description + " * (" + other.description + ")"
else:
expr = None
descr = self.description + " * " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) * other.symbol
descr = "(" + self.description + ") * " + other.description
else:
expr = None
descr = self.description + " * " + other.description
else:
expr = (self.symbolic_expression) * (other.symbolic_expression)
descr = "(" + self.description + ") * (" + other.description + ")"
if isinstance(other, Parameter):
return Parameter(res, input_dimensional=other.input_dimensional, return_dimensional=other.return_dimensional,
scale_object=other._scale_object, description=descr,
units=units, symbol=None, symbolic_expression=expr)
else:
return ScalingParameter(res, description=descr, units=units, symbol=None, symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol * other
descr = self.description + " * " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) * other
descr = "(" + self.description + ") * " + str(other)
else:
expr = None
descr = self.description + " * " + str(other)
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __rmul__(self, other):
return self.__mul__(other)
def __truediv__(self, other):
res = float(self) / other
if isinstance(other, (ScalingParameter, Parameter)):
units = _combine_units(self.units, other.units, '-')
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol / other.symbol
else:
expr = None
descr = self.description + " / " + other.description
else:
if self.symbol is not None:
expr = self.symbol / (other.symbolic_expression)
descr = self.description + " / (" + other.description + ")"
else:
expr = None
descr = self.description + " / " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) / other.symbol
descr = "(" + self.description + ") / " + other.description
else:
expr = None
descr = self.description + " / " + other.description
else:
expr = (self.symbolic_expression) / (other.symbolic_expression)
descr = "(" + self.description + ") / (" + other.description + ")"
if isinstance(other, Parameter):
return Parameter(res, input_dimensional=other.input_dimensional, return_dimensional=other.return_dimensional,
scale_object=other._scale_object, description=descr,
units=units, symbol=None, symbolic_expression=expr)
else:
return ScalingParameter(res, description=descr, units=units, symbol=None, symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol / other
descr = self.description + " / " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) / other
descr = "(" + self.description + ") / " + str(other)
else:
expr = None
descr = self.description + " / " + str(other)
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __rtruediv__(self, other):
res = other / float(self)
try:
if self.symbol is not None:
expr = other / self.symbol
descr = str(other) + " / " + self.description
elif self.symbolic_expression is not None:
expr = other / (self.symbolic_expression)
descr = str(other) + " / (" + self.description + ")"
else:
expr = None
descr = str(other) + " / " + self.description
return ScalingParameter(res, description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __pow__(self, power, modulo=None):
if modulo is not None:
raise NotImplemented('ScalingParameter class: Modular exponentiation not implemented')
res = float(self) ** power
if int(power) == power:
ul = self.units.split('][')
ul[0] = ul[0][1:]
ul[-1] = ul[-1][:-1]
usl = list()
for us in ul:
up = us.split('^')
if len(up) == 1:
up.append("1")
usl.append(tuple(up))
units_elements = list()
for us in usl:
units_elements.append(list((us[0], str(int(us[1]) * power))))
units = list()
for us in units_elements:
if us is not None:
if int(us[1]) != 1:
units.append("[" + us[0] + "^" + us[1] + "]")
else:
units.append("[" + us[0] + "]")
units = "".join(units)
if self.symbolic_expression is not None:
expr = (self.symbolic_expression) ** power
descr = "(" + self.description + ") to the power "+str(power)
elif self.symbol is not None:
expr = self.symbol ** power
descr = self.description + " to the power "+str(power)
else:
expr = None
descr = self.description + " to the power "+str(power)
else:
power_fraction = Fraction(power)
ul = self.units.split('][')
ul[0] = ul[0][1:]
ul[-1] = ul[-1][:-1]
usl = list()
for us in ul:
up = us.split('^')
if len(up) == 1:
up.append("1")
usl.append(tuple(up))
units_elements = list()
for us in usl:
new_power = int(us[1]) * power_fraction.numerator / power_fraction.denominator
if int(new_power) == new_power:
units_elements.append(list((us[0], str(new_power))))
else:
raise ArithmeticError("ScalingParameter class: Only support integer exponent in units")
units = list()
for us in units_elements:
if us is not None:
if int(us[1]) != 1:
units.append("[" + us[0] + "^" + us[1] + "]")
else:
units.append("[" + us[0] + "]")
units = "".join(units)
if self.symbolic_expression is not None:
expr = (self.symbolic_expression) ** power
descr = "(" + self.description + ") to the power " + str(power)
elif self.symbol is not None:
expr = self.symbol ** power
descr = self.description + " to the power " + str(power)
else:
expr = None
descr = self.description + " to the power " + str(power)
return ScalingParameter(res, description=descr, units=units, symbol=None, symbolic_expression=expr)
[docs]
class Parameter(float):
"""Base class of model's parameter.
Parameters
----------
value: float
Value of the parameter.
input_dimensional: bool, optional
Specify whether the value provided is dimensional or not. Default to `True`.
units: str, optional
The units of the provided value. Used to compute the conversion between dimensional and nondimensional
value. Should be specified by joining atoms like `'[unit^power]'`, e.g '`[m^2][s^-2][Pa^-2]'`.
Empty by default.
scale_object: ScaleParams, optional
A scale parameters object to compute the conversion between dimensional and nondimensional value.
`None` by default. If `None`, cannot transform between dimensional and nondimentional value.
description: str, optional
String describing the parameter.
symbol: ~sympy.core.symbol.Symbol, optional
A `Sympy`_ symbol to represent the parameter in symbolic expressions.
symbolic_expression: ~sympy.core.expr.Expr, optional
A `Sympy`_ expression to represent a relationship to other parameters.
return_dimensional: bool, optional
Defined if the value returned by the parameter is dimensional or not. Default to `False`.
Notes
-----
Parameter is immutable. Once instantiated, it cannot be altered. To create a new parameter, one must
re-instantiate it.
Warnings
--------
If no scale_object argument is provided, cannot transform between the dimensional and nondimentional value !
.. _Sympy: https://www.sympy.org/
"""
def __new__(cls, value, input_dimensional=True, units="", scale_object=None, description="",
symbol=None, return_dimensional=False, symbolic_expression=None):
no_scale = False
if return_dimensional:
if input_dimensional:
evalue = value
else:
if scale_object is None:
return_dimensional = False
evalue = value
no_scale = True
else:
evalue = value / cls._conversion_factor(units, scale_object)
else:
if input_dimensional:
if scale_object is None:
return_dimensional = True
evalue = value
no_scale = True
else:
try:
evalue = value * cls._conversion_factor(units, scale_object)
except:
print(description)
print(symbol)
print(units)
print(cls._conversion_factor(units, scale_object))
print(scale_object)
else:
evalue = value
if no_scale:
warnings.warn("Parameter configured to perform dimensional conversion " +
"but without specifying a ScaleParams object: Conversion disabled!")
f = float.__new__(cls, evalue)
f._input_dimensional = input_dimensional
f._return_dimensional = return_dimensional
f._units = units
f._scale_object = scale_object
f._description = description
f._symbol = symbol
f._symbolic_expression = symbolic_expression
return f
@property
def dimensional_value(self):
"""float: Returns the dimensional value."""
if self._return_dimensional:
return self
else:
return self / self._nondimensionalization
@property
def nondimensional_value(self):
"""float: Returns the nondimensional value."""
if self._return_dimensional:
return self * self._nondimensionalization
else:
return self
@property
def symbol(self):
"""~sympy.core.symbol.Symbol: Returns the symbol of the parameter."""
return self._symbol
@property
def symbolic_expression(self):
"""~sympy.core.expr.Expr: Returns the symbolic expression of the parameter."""
if self._symbolic_expression is None and self._symbol is not None:
return self._symbol
else:
return self._symbolic_expression
@property
def input_dimensional(self):
"""bool: Indicate if the provided value is dimensional or not."""
return self._input_dimensional
@property
def return_dimensional(self):
"""bool: Indicate if the returned value is dimensional or not."""
return self._return_dimensional
@classmethod
def _conversion_factor(cls, units, scale_object):
factor = 1.
ul = units.split('][')
ul[0] = ul[0][1:]
ul[-1] = ul[-1][:-1]
for us in ul:
up = us.split('^')
if len(up) == 1:
up.append("1")
if up[0] == 'm':
factor *= scale_object.L ** (-int(up[1]))
elif up[0] == 's':
factor *= scale_object.f0 ** (int(up[1]))
elif up[0] == 'Pa':
factor *= scale_object.deltap ** (-int(up[1]))
return factor
@property
def units(self):
"""str: The units of the dimensional value."""
return self._units
@property
def description(self):
"""str: Description of the parameter."""
return self._description
@property
def _nondimensionalization(self):
if self._scale_object is None:
return 1.
else:
return self._conversion_factor(self._units, self._scale_object)
def __add__(self, other):
res = float(self) + other
if isinstance(other, (Parameter, ScalingParameter)):
if isinstance(other, Parameter) and self.return_dimensional != other.return_dimensional:
raise ArithmeticError("Parameter class: Impossible to subtract a dimensional parameter with a non-dimensional one.")
if self.units != other.units:
raise ArithmeticError("Parameter class: Impossible to add two parameters with different units.")
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol + other.symbol
else:
expr = None
descr = self.description + " + " + other.description
else:
if self.symbol is not None:
expr = self.symbol + (other.symbolic_expression)
descr = self.description + " + (" + other.description + ")"
else:
expr = None
descr = self.description + " + " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) + other.symbol
descr = "(" + self.description + ") + " + other.description
else:
expr = None
descr = self.description + " + " + other.description
else:
expr = (self.symbolic_expression) + (other.symbolic_expression)
descr = "(" + self.description + ") + (" + other.description + ")"
return Parameter(res, input_dimensional=self.return_dimensional,
return_dimensional=self.return_dimensional, scale_object=self._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol + other
descr = self.description + " + " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) + other
descr = "(" + self.description + ") + " + str(other)
else:
expr = None
descr = self.description + " + " + str(other)
return Parameter(res, input_dimensional=self.return_dimensional,
return_dimensional=self.return_dimensional, scale_object=self._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
res = float(self) - other
if isinstance(other, (Parameter, ScalingParameter)):
if isinstance(other, Parameter) and self.return_dimensional != other.return_dimensional:
raise ArithmeticError("Parameter class: Impossible to subtract a dimensional parameter with a non-dimensional one.")
if self.units != other.units:
raise ArithmeticError("Parameter class: Impossible to subtract two parameters with different units.")
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol - other.symbol
else:
expr = None
descr = self.description + " - " + other.description
else:
if self.symbol is not None:
expr = self.symbol - (other.symbolic_expression)
descr = self.description + " - (" + other.description + ")"
else:
expr = None
descr = self.description + " - " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) - other.symbol
descr = "(" + self.description + ") - " + other.description
else:
expr = None
descr = self.description + " - " + other.description
else:
expr = (self.symbolic_expression) - (other.symbolic_expression)
descr = "(" + self.description + ") - (" + other.description + ")"
return Parameter(res, input_dimensional=self.return_dimensional,
return_dimensional=self.return_dimensional, scale_object=self._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol - other
descr = self.description + " - " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) - other
descr = "(" + self.description + ") - " + str(other)
else:
expr = None
descr = self.description + " - " + str(other)
return Parameter(res, input_dimensional=self.return_dimensional,
return_dimensional=self.return_dimensional, scale_object=self._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __rsub__(self, other):
res = other - float(self)
try:
if self.symbol is not None:
expr = other - self.symbol
descr = str(other) + " - " + self.description
elif self.symbolic_expression is not None:
expr = other - (self.symbolic_expression)
descr = str(other) + " - (" + self.description + ")"
else:
expr = None
descr = str(other) + " - " + self.description
return Parameter(res, input_dimensional=self.return_dimensional,
return_dimensional=self.return_dimensional, scale_object=self._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __mul__(self, other):
res = float(self) * other
if isinstance(other, (Parameter, ScalingParameter)):
if hasattr(other, "units"):
units = _combine_units(self.units, other.units, '+')
else:
units = ""
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol * other.symbol
else:
expr = None
descr = self.description + " * " + other.description
else:
if self.symbol is not None:
expr = self.symbol * (other.symbolic_expression)
descr = self.description + " * (" + other.description + ")"
else:
expr = None
descr = self.description + " * " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) * other.symbol
descr = "(" + self.description + ") * " + other.description
else:
expr = None
descr = self.description + " * " + other.description
else:
expr = (self.symbolic_expression) * (other.symbolic_expression)
descr = "(" + self.description + ") * (" + other.description + ")"
return Parameter(res, input_dimensional=self.return_dimensional, return_dimensional=self.return_dimensional,
scale_object=self._scale_object, description=descr, units=units, symbol=None,
symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol * other
descr = self.description + " * " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) * other
descr = "(" + self.description + ") * " + str(other)
else:
expr = None
descr = self.description + " * " + str(other)
return Parameter(res, input_dimensional=self.return_dimensional, return_dimensional=self.return_dimensional,
scale_object=self._scale_object, description=descr, units=self.units, symbol=None,
symbolic_expression=expr)
except:
return res
def __rmul__(self, other):
return self.__mul__(other)
def __truediv__(self, other):
res = float(self) / other
if isinstance(other, (ScalingParameter, Parameter)):
units = _combine_units(self.units, other.units, '-')
if self.symbolic_expression is None:
if other.symbolic_expression is None:
if self.symbol is not None and other.symbol is not None:
expr = self.symbol / other.symbol
else:
expr = None
descr = self.description + " / " + other.description
else:
if self.symbol is not None:
expr = self.symbol / (other.symbolic_expression)
descr = self.description + " / (" + other.description + ")"
else:
expr = None
descr = self.description + " / " + other.description
else:
if other.symbolic_expression is None:
if other.symbol is not None:
expr = (self.symbolic_expression) / other.symbol
descr = "(" + self.description + ") / " + other.description
else:
expr = None
descr = self.description + " / " + other.description
else:
expr = (self.symbolic_expression) / (other.symbolic_expression)
descr = "(" + self.description + ") / (" + other.description + ")"
return Parameter(res, input_dimensional=self.return_dimensional, return_dimensional=self.return_dimensional,
scale_object=self._scale_object, description=descr, units=units, symbol=None,
symbolic_expression=expr)
else:
try:
if self.symbol is not None:
expr = self.symbol / other
descr = self.description + " / " + str(other)
elif self.symbolic_expression is not None:
expr = (self.symbolic_expression) / other
descr = "(" + self.description + ") / " + str(other)
else:
expr = None
descr = self.description + " / " + str(other)
return Parameter(res, input_dimensional=self.return_dimensional, return_dimensional=self.return_dimensional,
scale_object=self._scale_object, description=descr, units=self.units, symbol=None,
symbolic_expression=expr)
except:
return res
def __rtruediv__(self, other):
res = other / float(self)
try:
if self.symbol is not None:
expr = other / self.symbol
descr = str(other) + " / " + self.description
elif self.symbolic_expression is not None:
expr = other / (self.symbolic_expression)
descr = str(other) + " / (" + self.description + ")"
else:
expr = None
descr = str(other) + " / " + self.description
return Parameter(res, input_dimensional=self.return_dimensional,
return_dimensional=self.return_dimensional, scale_object=self._scale_object,
description=descr, units=self.units, symbol=None, symbolic_expression=expr)
except:
return res
def __pow__(self, power, modulo=None):
if modulo is not None:
raise NotImplemented('Parameter class: Modular exponentiation not implemented')
res = float(self) ** power
if int(power) == power:
ul = self.units.split('][')
ul[0] = ul[0][1:]
ul[-1] = ul[-1][:-1]
usl = list()
for us in ul:
up = us.split('^')
if len(up) == 1:
up.append("1")
usl.append(tuple(up))
units_elements = list()
for us in usl:
units_elements.append(list((us[0], str(int(us[1]) * power))))
units = list()
for us in units_elements:
if us is not None:
if int(us[1]) != 1:
units.append("[" + us[0] + "^" + us[1] + "]")
else:
units.append("[" + us[0] + "]")
units = "".join(units)
if self.symbolic_expression is not None:
expr = (self.symbolic_expression) ** power
descr = "(" + self.description + ") to the power "+str(power)
elif self.symbol is not None:
expr = self.symbol ** power
descr = self.description + " to the power "+str(power)
else:
expr = None
descr = self.description + " to the power "+str(power)
else:
power_fraction = Fraction(power)
ul = self.units.split('][')
ul[0] = ul[0][1:]
ul[-1] = ul[-1][:-1]
usl = list()
for us in ul:
up = us.split('^')
if len(up) == 1:
up.append("1")
usl.append(tuple(up))
units_elements = list()
for us in usl:
new_power = int(us[1]) * power_fraction.numerator / power_fraction.denominator
if int(new_power) == new_power:
units_elements.append(list((us[0], str(int(new_power)))))
else:
raise ArithmeticError("Parameter class: Only support integer exponent in units")
units = list()
for us in units_elements:
if us is not None:
if int(us[1]) != 1:
units.append("[" + us[0] + "^" + us[1] + "]")
else:
units.append("[" + us[0] + "]")
units = "".join(units)
if self.symbolic_expression is not None:
expr = (self.symbolic_expression) ** power
descr = "(" + self.description + ") to the power "+str(power)
elif self.symbol is not None:
expr = self.symbol ** power
descr = self.description + " to the power "+str(power)
else:
expr = None
descr = self.description + " to the power "+str(power)
return Parameter(res, input_dimensional=self.return_dimensional, return_dimensional=self.return_dimensional,
description=descr, units=units, scale_object=self._scale_object, symbol=None,
symbolic_expression=expr)
[docs]
class ParametersArray(np.ndarray):
"""Base class of model's array of parameters.
Parameters
----------
values: list(float) or ~numpy.ndarray(float) or list(Parameter) or ~numpy.ndarray(Parameter) or list(ScalingParameter) or ~numpy.ndarray(ScalingParameter)
Values of the parameter array.
input_dimensional: bool, optional
Specify whether the value provided is dimensional or not. Default to `True`.
units: str, optional
The units of the provided value. Used to compute the conversion between dimensional and nondimensional
value. Should be specified by joining atoms like `'[unit^power]'`, e.g '`[m^2][s^-2][Pa^-2]'`.
Empty by default.
scale_object: ScaleParams, optional
A scale parameters object to compute the conversion between dimensional and nondimensional value.
`None` by default. If `None`, cannot transform between dimensional and nondimentional value.
description: str or list(str) or array(str), optional
String or an iterable of strings, describing the parameters.
If an iterable, should have the same length or shape as `values`.
symbols: ~sympy.core.symbol.Symbol or list(~sympy.core.symbol.Symbol) or ~numpy.ndarray(~sympy.core.symbol.Symbol), optional
A `Sympy`_ symbol or an iterable of symbols, to represent the parameters in symbolic expressions.
If an iterable, should have the same length or shape as `values`.
symbolic_expressions: ~sympy.core.expr.Expr or list(~sympy.core.expr.Expr) or ~numpy.ndarray(~sympy.core.expr.Expr), optional
A `Sympy`_ expression or an iterable of expressions, to represent a relationship to other parameters.
If an iterable, should have the same length or shape as `values`.
return_dimensional: bool, optional
Defined if the value returned by the parameter is dimensional or not. Default to `False`.
Warnings
--------
If no scale_object argument is provided, cannot transform between the dimensional and nondimensional value !
.. _Sympy: https://www.sympy.org/
"""
def __new__(cls, values, input_dimensional=True, units="", scale_object=None, description="",
symbols=None, symbolic_expressions=None, return_dimensional=False):
if isinstance(values, (tuple, list)):
new_arr = np.empty(len(values), dtype=object)
for i, val in enumerate(values):
if isinstance(description, (tuple, list, np.ndarray)):
descr = description[i]
else:
descr = description
if isinstance(symbols, (tuple, list, np.ndarray)):
sy = symbols[i]
else:
sy = symbols
if isinstance(symbolic_expressions, (tuple, list, np.ndarray)):
expr = symbolic_expressions[i]
else:
expr = symbolic_expressions
new_arr[i] = Parameter(val, input_dimensional=input_dimensional, units=units, scale_object=scale_object, description=descr,
return_dimensional=return_dimensional, symbol=sy, symbolic_expression=expr)
else:
if isinstance(values.flatten()[0], (Parameter, ScalingParameter)):
new_arr = values.copy()
else:
new_arr = np.empty_like(values, dtype=object)
for idx in np.ndindex(values.shape):
if isinstance(description, np.ndarray):
descr = description[idx]
else:
descr = description
if isinstance(symbols, np.ndarray):
sy = symbols[idx]
else:
sy = symbols
if isinstance(symbolic_expressions, np.ndarray):
expr = symbolic_expressions[idx]
else:
expr = symbolic_expressions
new_arr[idx] = Parameter(values[idx], input_dimensional=input_dimensional, units=units, scale_object=scale_object, description=descr,
return_dimensional=return_dimensional, symbol=sy, symbolic_expression=expr)
arr = np.asarray(new_arr).view(cls)
arr._input_dimensional = input_dimensional
arr._return_dimensional = return_dimensional
arr._units = units
arr._scale_object = scale_object
return arr
def __array_finalize__(self, arr):
if arr is None:
return
self._input_dimensional = getattr(arr, '_input_dimensional', True)
self._units = getattr(arr, '_units', "")
self._return_dimensional = getattr(arr, '_return_dimensional', False)
self._scale_object = getattr(arr, '_scale_object', None)
@property
def dimensional_values(self):
"""float: Returns the dimensional value."""
if self._return_dimensional:
return self
else:
return np.array(self / self._nondimensionalization)
@property
def nondimensional_values(self):
"""float: Returns the nondimensional value."""
if self._return_dimensional:
return np.array(self * self._nondimensionalization)
else:
return self
@property
def symbols(self):
"""~numpy.ndarray(~sympy.core.symbol.Symbol): Returns the symbol of the parameters in the array."""
symbols = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
symbols[idx] = self[idx].symbol
return symbols
@property
def symbolic_expressions(self):
"""~numpy.ndarray(~sympy.core.expr.Expr): Returns the symbolic expressions of the parameters in the array."""
symbolic_expressions = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
symbolic_expressions[idx] = self[idx].symbolic_expression
return symbolic_expressions
@property
def input_dimensional(self):
"""bool: Indicate if the provided value is dimensional or not."""
return self._input_dimensional
@property
def return_dimensional(self):
"""bool: Indicate if the returned value is dimensional or not."""
return self._return_dimensional
@classmethod
def _conversion_factor(cls, units, scale_object):
factor = 1.
ul = units.split('][')
ul[0] = ul[0][1:]
ul[-1] = ul[-1][:-1]
for us in ul:
up = us.split('^')
if len(up) == 1:
up.append("1")
if up[0] == 'm':
factor *= scale_object.L ** (-int(up[1]))
elif up[0] == 's':
factor *= scale_object.f0 ** (int(up[1]))
elif up[0] == 'Pa':
factor *= scale_object.deltap ** (-int(up[1]))
return factor
@property
def units(self):
"""str: The units of the dimensional value."""
return self._units
@property
def descriptions(self):
"""~numpy.ndarray(str): Description of the parameters in the array."""
descr = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
descr[idx] = self[idx].description
return descr
@property
def _nondimensionalization(self):
if self._scale_object is None:
return 1.
else:
return self._conversion_factor(self._units, self._scale_object)
def __add__(self, other):
if isinstance(other, (Parameter, ScalingParameter, float, int)):
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] + other
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
elif isinstance(other, ParametersArray):
if other.shape == self.shape: # Does not do broadcast
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] + other[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
else:
return self + other
else:
return self + other
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
if isinstance(other, (Parameter, ScalingParameter, float, int)):
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] - other
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
elif isinstance(other, ParametersArray):
if other.shape == self.shape: # Does not do broadcast
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] - other[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
else:
return self - other
else:
return self - other
def __rsub__(self, other):
if isinstance(other, (Parameter, ScalingParameter, float, int)):
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = other - self[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
elif isinstance(other, ParametersArray):
if other.shape == self.shape: # Does not do broadcast
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = other - self[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
else:
return other - self
else:
return other - self
def __mul__(self, other):
if isinstance(other, (Parameter, ScalingParameter, float, int)):
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] * other
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
elif isinstance(other, ParametersArray):
if other.shape == self.shape: # Does not do broadcast
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] * other[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
else:
return self * other
else:
return self * other
def __rmul__(self, other):
return self.__mul__(other)
def __truediv__(self, other):
if isinstance(other, (Parameter, ScalingParameter, float, int)):
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] / other
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
elif isinstance(other, ParametersArray):
if other.shape == self.shape: # Does not do broadcast
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = self[idx] / other[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
else:
return self / other
else:
return self / other
def __rtruediv__(self, other):
if isinstance(other, (Parameter, ScalingParameter, float, int)):
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = other / self[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
elif isinstance(other, ParametersArray):
if other.shape == self.shape: # Does not do broadcast
res = np.empty(self.shape, dtype=object)
for idx in np.ndindex(self.shape):
res[idx] = other / self[idx]
item = res[idx]
return ParametersArray(res, input_dimensional=item.return_dimensional, return_dimensional=item.return_dimensional,
units=item.units, scale_object=self._scale_object)
else:
return other / self
else:
return other / self
def _combine_units(units1, units2, operation):
ul = units1.split('][')
ul[0] = ul[0][1:]
ul[-1] = ul[-1][:-1]
ol = units2.split('][')
ol[0] = ol[0][1:]
ol[-1] = ol[-1][:-1]
usl = list()
for us in ul:
up = us.split('^')
if len(up) == 1:
up.append("1")
if up[0]:
usl.append(tuple(up))
osl = list()
for os in ol:
op = os.split('^')
if len(op) == 1:
op.append("1")
if op[0]:
osl.append(tuple(op))
units_elements = list()
for us in usl:
new_us = [us[0]]
i = 0
for os in osl:
if os[0] == us[0]:
if operation == '-':
power = int(os[1]) - int(us[1])
else:
power = int(os[1]) + int(us[1])
del osl[i]
break
i += 1
else:
power = int(us[1])
if power != 0:
new_us.append(str(power))
units_elements.append(new_us)
if len(osl) != 0:
units_elements += osl
units = list()
for us in units_elements:
if us is not None:
if int(us[1]) != 1:
units.append("[" + us[0] + "^" + us[1] + "]")
else:
units.append("[" + us[0] + "]")
return "".join(units)