# Copyright (C) 2008-2012 Johan Hake
#
# This file is part of ModelParameters.
#
# ModelParameters is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ModelParameters is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ModelParameters. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["Param", "ScalarParam", "OptionParam", "ConstParam", "ArrayParam"\
"SlaveParam", "eval_param_expr", "dummy_sym"]
# System imports
# Conditional sympy import
try:
from .sympytools import sp, store_symbol_parameter, \
value_namespace, symbols_from_expr, symbol_to_param
from .codegeneration import pythoncode, sympycode
dummy_sym = sp.Dummy("")
except ImportError as e:
sp = None
dummy_sym = None
import copy
import six
# local imports
from .config import *
from .logger import *
from .utils import check_arg, check_kwarg, scalars, value_formatter,\
Range, tuplewrap, integers, nptypes, Timer
from .utils import _np as np
import pint
ureg = pint.UnitRegistry()
option_types = scalars + (str,)
def _process_other(other):
"""Helper function for magic methods
in ScalarParam.
Arguments
---------
other : scalar or Param
Either a scalar (int , float, ...) of a parameter
(Param, ScalarParam, ...) that also holds info about
`name`, and `unit`.
Returns
-------
value : float
The value of other
name : str
The name of other (if not available name = "")
unit : str
The unit of other (if not available unit = "1")
"""
def from_param(other_):
value = other_.value
name = other_.name
unit = getattr(other_, 'unit', '1')
return (value, name, unit)
if isinstance(other, scalars):
return (other, "", "1")
elif isinstance(other, Param):
return from_param(other)
else:
# Check it other has an attribute called param
# which is the case for gotran.Paramerter
if hasattr(other, 'param'):
return from_param(other.param)
# I don't know how to handle this
raise ValueError('Unknown type {}'.format(type(other)))
def _eq(eq):
if len(eq) <= 1:
return eq
else:
return(all(np.hstack(eq)))
[docs]class Param(object):
"""
A simple type checking class for a single value
"""
def __init__(self, value, name="", description="", **kwargs):
"""
Initialize the Param
Arguments
---------
value : any
The initial value of this parameter. The type of value is stored
for future type checks
name : str (optional)
The name of the parameter. Used in pretty printing
description : str (optional)
A description associated with the Parameter
"""
check_kwarg(name, "name", str)
self._value = value
self.value_type = type(value)
self._not_in_str = None
self._in_str = None
self._in_range = None
self._name = name
self._description = description
[docs] def copy(self, include_checkarg=True, include_name=True, \
include_description=True):
"""
Return a copy of the parameter
Arguments
---------
include_checkarg : bool
If include checkargs in new Param
include_name : bool
If include name in new Param
include_description : bool
If include description in new Param
"""
repr_str = "%s(value%s%s%s)" % (\
self.__class__.__name__, \
self._check_arg() if include_checkarg else "", \
self._name_arg() if include_name else "", \
self._description_arg() if include_description else "")
# FIXME: Over load copy in SlaveParam instead?
if isinstance(self, SlaveParam):
value = copy.copy(self._expr)
else:
value = copy.copy(self._value)
# Evaluate the repr str with a copy of the value
return eval(repr_str, globals(), dict(value=value))
@property
def description(self):
return self._description
def _get_name(self):
return self._name
def _set_name(self, name):
check_arg(name, str)
if self._name:
value_error("Cannot set name attribute of %s, it is already set "\
"to '%s'" % (self.__class__.__name__, self._name))
self._name = name
[docs] def update(self, value):
self.setvalue(value, False)
[docs] def setvalue(self, value, check=True):
"""
Try to set the value using the check
"""
if check:
self._value = self.check(value)
else:
self._value = value
[docs] def getvalue(self):
"""
Return the value
"""
return self._value
name = property(_get_name, _set_name)
value = property(getvalue, setvalue)
[docs] def check(self, value):
"""
Check the value using the type and any range check
"""
# Fist check if the value is an int or float.
# (Treat these independently)
name_str = " '%s'" % self.name if self.name else ""
if self.value_type in scalars and \
isinstance(value, scalars):
if isinstance(value, float) and self.value_type == int:
info("Converting %s to %d%s", value, int(value), \
name_str)
value = self.value_type(value)
if self.value_type in [bool] and isinstance(value, int) and \
not isinstance(value, bool):
info("Converting %s to '%s' while setting parameter%s", \
value, bool(value), name_str)
value = self.value_type(value)
if not isinstance(value, self.value_type):
if self.value_type == nptypes:
type_name = "scalar or np.ndarray"
else:
type_name = self.value_type.__name__
type_error("expected '%s' while setting parameter%s"%\
(type_name, name_str))
if self._in_range is not None:
if not self._in_range(value):
value_error("Illegal value%s: %s"%\
(name_str, self.format_data(value, True)))
return value
def __repr__(self):
"""
Returns an executable version of the Param
"""
return self.repr()
[docs] def repr(self, include_checkarg=True, include_name=True, \
include_description=True):
"""
Returns an executable version of the Param including optional arguments
Arguments
---------
include_checkarg : bool
If include checkargs in new Param
include_name : bool
If include name in new Param
include_description : bool
If include description in new Param
"""
value_str = str(self._expr) if isinstance(self, SlaveParam) else \
value_formatter(self.value)
return "%s(%s%s%s%s)" % (\
self.__class__.__name__, \
value_str, self._check_arg() if include_checkarg else "", \
self._name_arg() if include_name else "", \
self._description_arg() if include_description else "")
def _name_arg(self):
return ", name='%s'" % self._name if self._name else ""
def _description_arg(self):
return ", description='%s'" % self._description \
if self._description else ""
def _check_arg(self):
return ""
def __str__(self):
"""
Returns a nice representation of the Param
"""
return self.format_data()
[docs] def convert_to(self, unit):
"""Convert parameter to a different unit than the current one.
Parameters
----------
unit : str
The new unit
Returns
-------
Param
Return the same prameter with the new unit
Example
-------
>>> p_s = ScalarParam(1.0, unit="s")
>>> p_ms = p_s.convert_to('ms')
>>> print('value = {}, unit = {}'.format(p_ms.value), p_ms.unit))
value = 1000.0, unit = 'milliseconds'
"""
new = ureg('{}*{}'.format(self.value, self.unit)).to(unit)
return self.__class__(value=new.magnitude, unit=new.u.format_babel())
def _op(self, other, operator, reverse=False, check_units=False):
"""
Compute `self [op] other`.
For example if op = * if will return
the product in a pint Quantity object.
Arguments
---------
other : scalar or Param
The other parameter
operator : str
The operator to apply
reverse : bool
Reverse the argumnents to the operator, i.e instead
of `self [op] other` you return `other [op] self`.
If operator is commutative then this is redundant.
check_units: bool
If you are trying to add together quantities with different
units you are in trouble, but if you are adding a scalar you
might don't want to throw an exception. Set this to true and
scalars will be treated as if they had same units.
Returns
-------
results : ScalarParam
Returns `self [op] other` with correct units and value
"""
other_value, other_name, other_unit = \
_process_other(other)
self_value, self_name, self_unit = \
_process_other(self)
assert isinstance(operator, str)
if check_units:
# Check if units match up
if not (ureg(self_unit) == ureg(other_unit)):
msg = ('Units does not match when computing {} {} {}: '
'{} != {}'.format(self, operator, other,
self_unit, other_unit))
# If one is dimensionless assume that it is OK,
# and keep the unit of the other
if self_unit == "1":
self_unit = other_unit
elif other_unit == "1":
other_unit = self_unit
equation = ('({self_value}*{self_unit})'
'{operator}'
'({other_value}*{other_unit})')
if reverse:
kwargs = dict(other_value=self_value,
other_unit=self_unit,
self_value=other_value,
self_unit=other_unit)
else:
kwargs = dict(other_value=other_value,
other_unit=other_unit,
self_value=self_value,
self_unit=self_unit)
kwargs['operator'] = operator
new = ureg(equation.format(**kwargs))
return self.__class__(value=new.magnitude, unit=new.u.format_babel())
def _cmp(self, other, cmp_op, check_units=True):
"""Compare self and other with custom
comparison operator
Parameters
----------
other : Param or scalar
The parameter you want to compare self to
cmp_op : str
Comparison operator
Returns
-------
bool
Returns the value of `self cmp_op other`.
"""
other_value, other_name, other_unit = \
_process_other(other)
self_value, self_name, self_unit = \
_process_other(self)
if check_units:
# Check if units match up
if not (ureg(self_unit) == ureg(other_unit)):
msg = ('Units does not match when comparing {} {} {}: '
'{} != {}'.format(self, cmp_op, other,
self_unit, other_unit))
warning(msg)
# If one is dimensionless assume that it is OK,
# and keep the unit of the other
if self_unit == "1":
self_unit = other_unit
elif other_unit == "1":
other_unit = self_unit
self_ureg = ureg('{}*{}'.format(self_value, self_unit))
other_ureg = ureg('{}*{}'.format(other_value, other_unit))
return eval('self_ureg {} other_ureg'.format(cmp_op))
def __getstate__(self):
"""This is what is saved when you pickle this object.
"""
unit = '1' if not hasattr(self, 'unit') else self.unit
return dict(name=self.name, unit=unit, value=self.value,
description=self.description)
def __setstate__(self, state):
"""This is how we retrieve the pickled object.
"""
self.__class__.__init__(self, **state)
def __eq__(self, other):
# return self._cmp(other, "==")
return _eq(isinstance(other, self.__class__) and \
(self._name == other._name, self._value == other._value, \
self.__class__ == other.__class__))
def __lt__(self, other):
return self._cmp(other, "<")
def __gt__(self, other):
return self._cmp(other, ">")
def __le__(self, other):
return self._cmp(other, "<=")
def __ge__(self, other):
return self._cmp(other, ">=")
def __truediv__(self, other):
# Python 3
return self._op(other, '/')
def __rtruediv__(self, other):
# Python 3
return self._op(other, '/', reverse=True)
def __div__(self, other):
# Python 2
return self._op(other, '/')
def __rdiv__(self, other):
# Python 2
return self._op(other, '/', reverse=True)
def __mul__(self, other):
return self._op(other, '*')
def __rmul__(self, other):
return self._op(other, '*', reverse=True)
def __add__(self, other):
return self._op(other, '+', check_units=True)
def __radd__(self, other):
return self._op(other, '+', reverse=True, check_units=True)
def __sub__(self, other):
return self._op(other, '-', check_units=True)
def __rsub__(self, other):
return self._op(other, '-', reverse=True, check_units=True)
def __pow__(self, other):
other_value, other_name, other_unit = \
_process_other(other)
assert other_unit == "1", "Exponents cannot have unit"
self_value, self_name, self_unit = \
_process_other(self)
new = ureg("({}*{})**{}".format(self_value, self_unit, other_value))
return self.__class__(value=new.magnitude, unit=new.u.format_babel())
def __abs__(self):
return abs(self.value)
[docs]class OptionParam(Param):
"""
A simple type and options checking class for a single value
"""
def __init__(self, value, options, name="", description=""):
"""
Initialize the OptionParam
Arguments
---------
value : scalars or str
The initial value of this parameter. The type of value is stored
for future type checks
options : list
A list of acceptable values for the parameter
name : str (optional)
The name of the parameter. Used in pretty printing
description : str (optional)
A description associated with the Parameter
"""
check_arg(options, list)
if len(options) < 2:
value_error("expected the options argument to be at least of length 2")
super(OptionParam, self).__init__(value, name, description)
# Check valid types for an 'option check'
for option in options:
if not isinstance(option, option_types):
type_error("options can only be 'str' and scalars got: '%s'" % \
type(option).__name__)
# Define a 'check function'
self._in_range = lambda value : value in options
# Define some string used for pretty print
self._in_str = b"%%s \xe2\x88\x88 %s".decode('utf-8') % repr(options)
self._not_in_str = b"%%s \xe2\x88\x89 %s".decode('utf-8') % repr(options)
# Define a 'repr string'
#self._repr_str = "OptionParam(%%s, %s)" % repr(options)
# Set the value using the check functionality
self.setvalue(value)
# Check that all values in options has the same type
for val in options:
if not isinstance(val, self.value_type):
type_error("All values of the 'option check' " +\
"need to be of type: '%s'" % type(self._value).__name__)
# Store options
self._options = options
def _check_arg(self):
return ", %s" % repr(self._options)
def __eq__(self, other):
return _eq(isinstance(other, self.__class__) and \
(self._name == other._name, self._value == other._value, \
self.__class__ == other.__class__, \
self._options == other._options))
[docs] def repr(self, include_checkarg=True, include_name=True, \
include_description=True):
"""
Returns an executable version of the Param including optional arguments
Arguments
---------
include_checkarg : bool
If include checkargs in new Param
include_name : bool
If include name in new Param
include_description : bool
If include description in new Param
"""
if not include_checkarg:
warning("'include_checkarg' must be 'True' in OptionParam.repr.")
include_checkarg = True
return super(OptionParam, self).repr(include_checkarg, include_name, \
include_description)
[docs]class ConstParam(Param):
"""
A Constant parameter which prevent any change of values
"""
def __init__(self, value, name="", description=""):
"""
Initialize the ConstParam
Arguments
---------
value : scalars or str
The initial value of this parameter. The type of value is stored
for future type checks
name : str (optional)
The name of the parameter. Used in pretty printing
description : str (optional)
A description associated with the Parameter
"""
Param.__init__(self, value, name, description)
# Define a 'check function'
self._in_range = lambda x : x == self._value
# Define some string used for pretty print
self._in_str = "%s - Constant"
self._not_in_str = "%%s != %s" % self._value
self.setvalue(value)
[docs]class TypelessParam(Param):
"""
A Typeless parameter allowing any change of value, including type changes
"""
def __init__(self, value, name="", description=""):
"""
Initialize the TypelessParam
Arguments
---------
value : scalars or str
The initial value of this parameter. The type of value is stored
for future type checks
name : str (optional)
The name of the parameter. Used in pretty printing
description : str (optional)
A description associated with the Parameter
"""
Param.__init__(self, value, name, description)
# Set allowed types to all Python objects
self.value_type = object
[docs]class ScalarParam(Param):
"""
A simple type and range checking class for a scalar value
"""
def __init__(self, value, ge=None, le=None, gt=None, lt=None, \
unit="1", name="", description=""):
"""
Creating a ScalarParam
Arguments
---------
value : scalar
The initial value of this parameter
gt : scalar (optional)
Greater than, range control of argument
ge : scalar (optional)
Greater than or equal, range control of argument
lt : scalar (optional)
Lesser than, range control of argument
le : scalar (optional)
Lesser than or equal, range control of argument
unit : str (optional, if sympy is available)
The unit of the scalar parameter
name : str (optional)
The name of the parameter. Used in pretty printing
description : str (optional)
A description associated with the Parameter
"""
if isinstance(value, Param):
param_value, param_name, param_unit = \
_process_other(value)
if unit == "1":
unit = param_unit
value = param_value
check_arg(value, scalars, 0, ScalarParam)
super(ScalarParam, self).__init__(value, name, description)
self._range = Range(ge, le, gt, lt)
self._in_range = self._range._in_range
check_kwarg(unit, "unit", six.string_types)
self._unit = unit
# Define some string used for pretty print
self._in_str = self._range._in_str
self._not_in_str = self._range._not_in_str
# Create symbol
if name == "":
self._sym = dummy_sym
elif sp is None:
self._sym = None
else:
self._sym = sp.Symbol(name, real=True, imaginary=False,
commutative=True, hermitian=True, complex=True)
# Store parameter
store_symbol_parameter(self)
# Set the value using the check functionality
# (Only if not called from derived class)
if type(self) == ScalarParam:
self.setvalue(value)
def _get_name(self):
return self._name
def _set_name(self, name):
"""
Set the name. Can only be done if not set during instantiation
"""
check_arg(name, str)
super(ScalarParam, self)._set_name(name)
if sp is None:
return
# Create a new symbol with the updated name
self._sym = sp.Symbol(name, real=True, imaginary=False,
commutative=True, hermitian=True, complex=True)
# Store parameter
store_symbol_parameter(self)
[docs] def copy(self, include_checkarg=True, include_name=True, \
include_description=True, include_unit=True):
"""
Return a copy of the parameter
Arguments
---------
include_checkarg : bool
If include checkargs in new Param
include_name : bool
If include name in new Param
include_description : bool
If include description in new Param
include_unit : bool
If include unit in new Param
"""
repr_str = "%s(value%s%s%s%s)" % (\
self.__class__.__name__, \
self._check_arg() if include_checkarg else "", \
self._unit_arg() if include_unit else "", \
self._name_arg() if include_name else "", \
self._description_arg() if include_description else "")
# FIXME: Over load copy in SlaveParam instead?
if isinstance(self, SlaveParam):
value = copy.copy(self._expr)
else:
value = copy.copy(self._value)
# Evaluate the repr str with a copy of the value
return eval(repr_str, globals(), dict(value=value))
[docs] def repr(self, include_checkarg=True, include_name=True, \
include_description=True, include_unit=True):
"""
Returns an executable version of the Param including optional arguments
Arguments
---------
include_checkarg : bool
If include checkargs in new Param
include_name : bool
If include name in new Param
include_description : bool
If include description in new Param
include_unit : bool
If include unit in new Param
"""
value_str = str(self._expr) if isinstance(self, SlaveParam) else \
value_formatter(self.value)
return "%s(%s%s%s%s%s)" % (\
self.__class__.__name__, \
value_str, self._check_arg() if include_checkarg else "", \
self._unit_arg() if include_unit else "", \
self._name_arg() if include_name else "", \
self._description_arg() if include_description else "")
[docs] def update(self, param):
"""
Update parameter with value of new parameter.
Take into account unit conversion if applicable.
Arguments
---------
param : ScalarParameter or scalar
The parameter with the new value
"""
check_arg(param, scalars + (ScalarParam,))
msg="Update parameter {}. ".format(self._get_name())
self_value, self_name, self_unit = \
_process_other(self)
param_value, param_name, param_unit = \
_process_other(param)
if param_unit == "1":
# Assume same Units
param_unit = self_unit
if self_unit == "1":
# Make the new unit the same as the given paramevter
self_unit = param_unit
# Convert to same unit as self
quantity = ureg('{}*{}'.format(param_value, param_unit)).to(self_unit)
return self.setvalue(quantity.m)
def _unit_arg(self):
return ", unit='%s'"%self._unit if self._unit != "1" else ""
[docs] def get_sym(self):
return self._sym
name = property(_get_name, _set_name)
sym = property(get_sym)
@property
def unit(self):
"""
Return the unit
"""
return self._unit
def _check_arg(self):
"""
Return a repr of the check arguments
"""
if self._range.arg_repr_str:
return ", " + self._range.arg_repr_str
return ""
def __eq__(self, other):
return _eq(isinstance(other, self.__class__) and \
(self._name == other._name, self._value == other._value, \
self.__class__ == other.__class__, \
self._range == other._range))
[docs]class ArrayParam(ScalarParam):
"""
A numpy Array based parameter
"""
def __init__(self, value, size=None, ge=None, le=None, gt=None, lt=None, \
unit="1", name="", description=""):
"""
Creating an ArrayParam
Arguments
---------
value : scalar, np.ndarray
The initial value of this parameter
size : integer (optional)
Set the size of the ns.array. If set value must be a scalar
gt : scalar (optional)
Greater than, range control of argument
ge : scalar (optional)
Greater than or equal, range control of argument
lt : scalar (optional)
Lesser than, range control of argument
le : scalar (optional)
Lesser than or equal, range control of argument
unit : str (optional, if sympy is available)
The unit of the scalar parameter
name : str (optional)
The name of the parameter. Used in pretty printing
description : str (optional)
A description associated with the Parameter
"""
if np is None:
error("numpy is not installed so ArrayParam is not available")
# If setting value using size
if size is not None:
# If a size is provided a scalar is expected for the value
check_kwarg(size, "size", integers, ArrayParam, ge=1)
check_arg(value, scalars, 0, ArrayParam)
# Create numpy array based on the passed value
# Use intc and float_ to be compatible with c code.
value = np.array([value]*size, dtype=np.intc \
if isinstance(value, integers) \
else np.float_)
# If setting value using only value argument
else:
# Allow using list of scalars
if isinstance(value, list):
check_arg(value, list, 0, ArrayParam, scalars)
if len(value)==0:
value_error("expected a list with at least 1 element")
value = np.fromiter(value, dtype=type(value[0]))
check_arg(value, nptypes, 0, ArrayParam)
# Fist check any scalars passed
if isinstance(value, integers):
value = np.array([value], dtype=np.intc)
elif isinstance(value, scalars):
value = np.array([value], dtype=np.float_)
# Then check passed NumPy arrays
elif value.dtype in integers:
value = value.astype(np.intc)
elif value.dtype in scalars:
value = value.astype(np.float_)
else:
type_error("expected a scalar or a scalar valued np.ndarray"
"or list as value argument.")
# Init super class with dummy value
super(ArrayParam, self).__init__(value[0], ge, le, gt, lt, unit, \
name, description)
# Assign value
self._value = value
self.value_type = nptypes
# Use setvalue to set value using the range
self.setvalue(value)
[docs] def setvalue(self, value):
"""
Set value of ArrayParameter
"""
# An initial slive for the whole array
index = slice(0,len(self._value)+1)
# Tuple means index assignment
# FIXME: Add support for slices
if isinstance(value, tuple):
if len(value) != 2:
value_error("expected a tuple of length 2 when assigning "\
"single items")
if not isinstance(value[0], integers):
value_error("expected first value in index assignment to be"\
" an integer")
if not isinstance(value[1], scalars):
value_error("expected second value in index assignment to be"\
" an scalar")
index = value[0]
value = value[1]
check_arg(value, nptypes, context=ArrayParam.setvalue)
if isinstance(value, np.ndarray):
if len(value) != len(self._value):
value_error("expected the passed array to be of "\
"size: '%d'"%len(self._value))
# Assign value
self._value[index] = self.check(value)
value = property(Param.getvalue, setvalue)
[docs] def resize(self, newsize):
"""
Change the size of the Array
"""
if not isinstance(newsize, integers):
error("expected newsize argument to be an int")
newsize = int(newsize)
# Resize array if size is changed
if len(self._value) != newsize:
self._value = np.resize(self._value, newsize)
[docs]class SlaveParam(ScalarParam):
"""
A slave parameter defined by other parameters
"""
def __init__(self, expr, unit="1", name="", description=""):
if sp is None:
error("sympy is not installed so SlaveParam is not available")
if not isinstance(expr, (ScalarParam, sp.Basic)):
type_error("expected an expression of model symbols.")
if isinstance(expr, ScalarParam):
expr = expr.sym
if not all(isinstance(atom, (sp.NumberSymbol, sp.Number, sp.Symbol, \
sp.Dummy)) for atom in expr.atoms()):
type_error("expected expression of model symbols.")
ScalarParam.__init__(self, 0.0, name=name, description=description, \
unit=unit)
# Store the original expression used to evaluate the value of
# the SlaveParam
self._expr = expr
# Store parameters that are available at the time of construction
# FIXME: Does not work...
#self._param_ns = {}
#for symbol in symbols_from_expr(expr, include_derivatives=True):
# try:
# self._param_ns[sympycode(symbol)] = symbol_to_param(symbol)
# except:
# pass
[docs] def setvalue(self, value):
"""
A setvalue method which always fails
"""
type_error("cannot assign to a SlaveParam")
[docs] def getvalue(self):
"""
Return a computed value of the Parameters
"""
timer = Timer("Eval Slave parameter")
return eval_param_expr(self._expr, include_derivatives=True)
# FIXME: Does not work...
#return eval_param_expr(self._expr, param_ns=self._param_ns, \
# include_derivatives=True)
value = property(getvalue, setvalue)
@property
def expr(self):
"""
Return the stored expression
"""
return self._expr
[docs]def eval_param_expr(expr, param_ns=None, include_derivatives=False, ns=None):
"""
Eval an expression of symbols of ScalarParam
Arguments
---------
expr : expression of ParamSymbols
The expression to be evaulated
param_ns : dict (optional)
A namespace containing the parameters for which the expr should be
evaluated with.
include_derivatives : bool (optional)
If True not only symbols are evaulated but also derivatives
ns : dict (optional)
A namespace in which the expression will be evaluated in
"""
if sp is None:
error("sympy is not installed so evaluation of expressions"\
" is not available")
# Create name space which the expression will be evaluated in
ns = ns or {}
# Get values
if param_ns is None:
value_ns = value_namespace(expr, include_derivatives=include_derivatives)
else:
value_ns = {}
for symbol in symbols_from_expr(expr, \
include_derivatives=include_derivatives):
sym_name = sympycode(symbol)
# First try to get param from passed param_ns
param = param_ns.get(sym_name)
# If not available try to get it from the global dict
if param is None:
param = symbol_to_param(symbol)
# And store it for future usage
#param_ns[sym_name] = param
# Store value
value_ns[sym_name] = param.value
# First check if we have numpy arrays
if np and any(isinstance(value, np.ndarray) for value in list(value_ns.values())):
# Second check if they have the same length
all_length = [len(value) for value in list(value_ns.values()) \
if isinstance(value, np.ndarray)]
same_length = all(all_length[0] == l for l in all_length)
if not same_length:
value_error("expected all ArrayParams in an expression "\
"to be of equal size.")
# Update name space with numpy name space
namespace = "np"
ns["np"] = np
else:
# No numpy arrays and we choose math name space to evaulate expression
import math
namespace = "math"
ns["math"] = math
# Update namespace with values
ns.update(value_ns)
return eval(pythoncode(expr, namespace=namespace), {}, ns)
__all__ = [_name for _name in list(globals().keys()) if _name[0] != "_"]