Example #1
0
    def from_callback(cls,
                      cb,
                      transf_cbs,
                      nx,
                      nparams=0,
                      pre_adj=None,
                      **kwargs):
        """ Generate a TransformedSys instance from a callback

        Parameters
        ----------
        cb : callable
        transf_cbs : pair or iterable of pairs of callables
        nx : int
        nparams : int
        pre_adj : callable
        \\*\\*kwargs : passed onto TransformedSys
        """
        be = Backend(kwargs.pop('backend', None))
        x = be.real_symarray('x', nx)
        p = be.real_symarray('p', nparams)
        try:
            transf = [(transf_cbs[idx][0](xi), transf_cbs[idx][1](xi))
                      for idx, xi in enumerate(x)]
        except TypeError:
            transf = zip(_map2(transf_cbs[0], x), _map2(transf_cbs[1], x))
        try:
            exprs = cb(x, p, be)
        except TypeError:
            exprs = _ensure_3args(cb)(x, p, be)
        return cls(x, _map2l(pre_adj, exprs), transf, p, backend=be, **kwargs)
Example #2
0
    def from_callback(cls, cb, ny, nparams=0, **kwargs):
        """ Create an instance from a callback.

        Parameters
        ----------
        cb : callbable
            Signature ``rhs(x, y[:], p[:]) -> f[:]``
        ny : int
            length of y
        nparams : int (default: 0)
            length of p
        \*\*kwargs :
            keyword arguments passed onto :class:`SymbolicSys`

        Returns
        -------
        An instance of :class:`SymbolicSys`.
        """
        be = Backend(kwargs.pop('backend', None))
        x, = be.real_symarray('x', 1)
        y = be.real_symarray('y', ny)
        p = be.real_symarray('p', nparams)
        try:
            exprs = cb(x, y, p, be)
        except TypeError:
            exprs = _ensure_4args(cb)(x, y, p, be)
        return cls(zip(y, exprs), x, p, backend=be, **kwargs)
Example #3
0
    def from_callback(cls, cb, nx=None, nparams=None, **kwargs):
        """ Generate a SymbolicSys instance from a callback"""
        if kwargs.get('x_by_name', False):
            if 'names' not in kwargs:
                raise ValueError("Need ``names`` in kwargs.")
            if nx is None:
                nx = len(kwargs['names'])
            elif nx != len(kwargs['names']):
                raise ValueError("Inconsistency between nx and length of ``names``.")
        if kwargs.get('par_by_name', False):
            if 'param_names' not in kwargs:
                raise ValueError("Need ``param_names`` in kwargs.")
            if nparams is None:
                nparams = len(kwargs['param_names'])
            elif nparams != len(kwargs['param_names']):
                raise ValueError("Inconsistency between ``nparam`` and length of ``param_names``.")

        if nparams is None:
            nparams = 0

        if nx is None:
            raise ValueError("Need ``nx`` of ``names`` together with ``x_by_name==True``.")
        be = Backend(kwargs.pop('backend', None))
        x = be.real_symarray('x', nx)
        p = be.real_symarray('p', nparams)
        _x = dict(zip(kwargs['names'], x)) if kwargs.get('x_by_name', False) else x
        _p = dict(zip(kwargs['param_names'], p)) if kwargs.get('par_by_name', False) else p
        try:
            exprs = cb(_x, _p, be)
        except TypeError:
            exprs = _ensure_3args(cb)(_x, _p, be)
        return cls(x, exprs, p, backend=be, **kwargs)
Example #4
0
    def from_callback(cls, cb, transf_cbs, nx, nparams=0, pre_adj=None,
                      **kwargs):
        """ Generate a TransformedSys instance from a callback

        Parameters
        ----------
        cb: callable
        transf_cbs: pair or iterable of pairs of callables
        nx: int
        nparams: int
        pre_adj: callable
        \*\*kwargs: passed onto TransformedSys
        """
        be = Backend(kwargs.pop('backend', None))
        x = be.real_symarray('x', nx)
        p = be.real_symarray('p', nparams)
        try:
            transf = [(transf_cbs[idx][0](xi),
                       transf_cbs[idx][1](xi))
                      for idx, xi in enumerate(x)]
        except TypeError:
            transf = zip(_map2(transf_cbs[0], x), _map2(transf_cbs[1], x))
        try:
            exprs = cb(x, p, be)
        except TypeError:
            exprs = _ensure_3args(cb)(x, p, be)
        return cls(x, _map2l(pre_adj, exprs), transf, p, backend=be, **kwargs)
Example #5
0
 def __init__(self, x, exprs, params=(), jac=True, backend=None, **kwargs):
     self.x = x
     self.exprs = exprs
     self.params = params
     self._jac = jac
     self.be = Backend(backend)
     self.nf, self.nx = len(exprs), len(x)  # needed by get_*_cb
     self.band = kwargs.get('band', None)  # needed by get_*_cb
     self.module = kwargs.pop('module', 'numpy')
     super(SymbolicSys, self).__init__(self.nf, self.nx, self._get_f_cb(),
                                       self._get_j_cb(), **kwargs)
Example #6
0
def main():
    for key in 'sympy pysym symengine'.split():
        print(key)
        print('    Differentiation:')
        be = Backend(key)
        x, y = map(be.Symbol, 'x y'.split())
        expr = (x - be.acos(y))*be.exp(x + y)
        print(expr)
        Dexpr = expr.diff(y)
        print(Dexpr)
        print("")
Example #7
0
def _validate(conditions, rsys, symbols, odesys, backend=None, transform=None, ignore=('time',)):
    """ For use with create_odesys

    Parameters
    ----------
    conditions : OrderedDict
        Parameters, values with units from ``chempy.units``.
    rsys : ReactionSystem
    symbols : dict
        Mapping variable name to symbols.
    backend : module
        Module for symbolic mathematics. (defaults to SymPy)
    transform : callable for rewriting expressions

    Raises
    ------
    ``KeyError`` if a key in conditions is not in odesys.names or odesys.param_names

    """
    if backend is None:
        from sym import Backend
        backend = Backend(backend)

    if transform is None:
        if backend.__name__ != 'sympy':
            warnings.warn("Backend != SymPy, provide your own transform function.")

        def transform(arg):
            expr = backend.logcombine(arg, force=True)
            v, w = map(backend.Wild, 'v w'.split())
            expr = expr.replace(backend.log(w**v), v*backend.log(w))
            return expr

    args = [symbols[key] for key in conditions]
    seen = [False]*len(args)
    rates = {}
    for k, v in rsys.rates(symbols).items():
        expr = transform(v)
        if expr == 0:
            rate = 0 * u.molar/u.second
        else:
            rate = backend.lambdify(args, expr)(*conditions.values())
            to_unitless(rate, u.molar/u.second)
        rates[k] = rate
        seen = [b or a in expr.free_symbols for b, a in zip(seen, args)]
    not_seen = [a for s, a in zip(seen, args) if not s]
    for k in conditions:
        if k not in odesys.param_names and k not in odesys.names and k not in ignore:
            raise KeyError("Unknown param: %s" % k)
    return {'not_seen': not_seen, 'rates': rates}
Example #8
0
    def from_callback(cls, cb, ny, nparams=0, dep_transf_cbs=None,
                      indep_transf_cbs=None, **kwargs):
        """
        Create an instance from a callback.

        Analogous to :func:`SymbolicSys.from_callback`.

        Parameters
        ----------
        cb : callable
            Signature ``rhs(x, y[:], p[:]) -> f[:]``
        ny : int
            length of y
        nparams : int
            length of p
        dep_transf_cbs : iterable of pairs callables
            callables should have the signature f(yi) -> expression in yi
        indep_transf_cbs : pair of callbacks
            callables should have the signature f(x) -> expression in x
        \*\*kwargs :
            keyword arguments passed onto :class:`TransformedSys`
        """
        be = Backend(kwargs.pop('backend', None))
        x, = be.real_symarray('x', 1)
        y = be.real_symarray('y', ny)
        p = be.real_symarray('p', nparams)
        exprs = _ensure_4args(cb)(x, y, p, be)
        if dep_transf_cbs is not None:
            dep_transf = [(fw(yi), bw(yi)) for (fw, bw), yi
                          in zip(dep_transf_cbs, y)]
        else:
            dep_transf = None

        if indep_transf_cbs is not None:
            indep_transf = indep_transf_cbs[0](x), indep_transf_cbs[1](x)
        else:
            indep_transf = None

        return cls(list(zip(y, exprs)), x, dep_transf,
                   indep_transf, p, backend=be, **kwargs)
Example #9
0
    def from_callback(cls, cb, nx=None, nparams=None, **kwargs):
        """ Generate a SymbolicSys instance from a callback"""
        if kwargs.get('x_by_name', False):
            if 'names' not in kwargs:
                raise ValueError("Need ``names`` in kwargs.")
            if nx is None:
                nx = len(kwargs['names'])
            elif nx != len(kwargs['names']):
                raise ValueError(
                    "Inconsistency between nx and length of ``names``.")
        if kwargs.get('par_by_name', False):
            if 'param_names' not in kwargs:
                raise ValueError("Need ``param_names`` in kwargs.")
            if nparams is None:
                nparams = len(kwargs['param_names'])
            elif nparams != len(kwargs['param_names']):
                raise ValueError(
                    "Inconsistency between ``nparam`` and length of ``param_names``."
                )

        if nparams is None:
            nparams = 0

        if nx is None:
            raise ValueError(
                "Need ``nx`` of ``names`` together with ``x_by_name==True``.")
        be = Backend(kwargs.pop('backend', None))
        x = be.real_symarray('x', nx)
        p = be.real_symarray('p', nparams)
        _x = dict(zip(kwargs['names'], x)) if kwargs.get('x_by_name',
                                                         False) else x
        _p = dict(zip(kwargs['param_names'], p)) if kwargs.get(
            'par_by_name', False) else p
        try:
            exprs = cb(_x, _p, be)
        except TypeError:
            exprs = _ensure_3args(cb)(_x, _p, be)
        return cls(x, exprs, p, backend=be, **kwargs)
Example #10
0
    def __init__(self, dep_exprs, indep=None, params=(), jac=True, dfdx=True,
                 roots=None, backend=None, **kwargs):
        self.dep, self.exprs = zip(*dep_exprs)
        self.indep = indep
        self.params = params
        self._jac = jac
        self._dfdx = dfdx
        self.roots = roots
        self.be = Backend(backend)

        if kwargs.get('names', None) is True:
            kwargs['names'] = [y.name for y in self.dep]
        # we need self.band before super().__init__
        self.band = kwargs.get('band', None)
        super(SymbolicSys, self).__init__(
            self.get_f_ty_callback(),
            self.get_j_ty_callback(),
            self.get_dfdx_callback(),
            self.get_roots_callback(),
            nroots=None if roots is None else len(roots),
            **kwargs)
Example #11
0
def _create_odesys(rsys, substance_symbols=None, parameter_symbols=None, pretty_replace=lambda x: x,
                   backend=None, SymbolicSys=None, time_symbol=None, unit_registry=None):
    """ This will be a simpler version of get_odesys without the unit handling code.
    The motivation is to reduce complexity (the code of get_odesys is long with multiple closures).

    This will also rely on SymPy explicitly and the user will be expected to deal with SymPy
    expressions.

    Only when this function has the same capabilities as get_odesys will it become and public API
    (along with a deprecation of get_odesys).

    Parameters
    ----------
    rsys : ReactionSystem instance
    substance_symbols : OrderedDict
       If ``None``: ``rsys.substances`` will be used.
    parameter_symbols : OrderedDict
    backend : str or module
        Symbolic backend (e.g. sympy). The package ``sym`` is used as a wrapper.


    Returns
    -------
    SymbolicSys (subclass of ``pyodesys.ODESys``)
    dict :
        - ``'symbols'``: dict mapping str to symbol.
        - ``'validate'``: callable acppeting a dictionary mapping str to quantities
    """
    if backend is None:
        from sym import Backend
        backend = Backend(backend)
    if SymbolicSys is None:
        from pyodesys.symbolic import SymbolicSys

    if substance_symbols is None:
        substance_symbols = OrderedDict([(key, backend.Symbol(key)) for key in rsys.substances])
    if isinstance(substance_symbols, OrderedDict):
        if list(substance_symbols) != list(rsys.substances):
            raise ValueError("substance_symbols needs to have same (oredered) keys as rsys.substances")

    if parameter_symbols is None:
        keys = []
        for rxn in rsys.rxns:
            uk, = rxn.param.unique_keys
            keys.append(uk)
            for pk in rxn.param.parameter_keys:
                if pk not in keys:
                    keys.append(pk)
        parameter_symbols = OrderedDict([(key, backend.Symbol(key)) for key in keys])

    if not isinstance(parameter_symbols, OrderedDict):
        raise ValueError("parameter_symbols needs to be an OrderedDict")

    symbols = OrderedDict(chain(substance_symbols.items(), parameter_symbols.items()))
    symbols['time'] = time_symbol or backend.Symbol('t')
    if any(symbols['time'] == v for k, v in symbols.items() if k != 'time'):
        raise ValueError("time_symbol already in use (name clash?)")
    rates = rsys.rates(symbols)
    compo_vecs, compo_names = rsys.composition_balance_vectors()

    odesys = SymbolicSys(
        zip([substance_symbols[key] for key in rsys.substances], [rates[key] for key in rsys.substances]),
        symbols['time'],
        parameter_symbols.values(),
        names=list(rsys.substances.keys()),
        latex_names=[s.latex_name for s in rsys.substances.values()],
        param_names=parameter_symbols.keys(),
        latex_param_names=[pretty_replace(n) for n in parameter_symbols.keys()],
        linear_invariants=compo_vecs,
        linear_invariant_names=list(map(str, compo_names)),
        backend=backend,
        dep_by_name=True,
        par_by_name=True
    )

    validate = partial(_validate, rsys=rsys, symbols=symbols, odesys=odesys, backend=backend)
    return odesys, {
        'symbols': symbols,
        'validate': validate,
        'unit_aware_solve': _mk_unit_aware_solve(odesys, unit_registry, validate=validate) if unit_registry else None
    }
Example #12
0
class SymbolicSys(NeqSys):
    """
    Parameters
    ----------
    x : iterable of Symbols
    exprs : iterable of expressions for f
    params : iterable of Symbols (optional)
        list of symbols appearing in exprs which are parameters
    jac : ImmutableMatrix or bool
        If True:
            calculate jacobian from exprs
        If False:
            do not compute jacobian (numeric approximation)
        If ImmutableMatrix:
            user provided expressions for the jacobian
    backend : str or sym.Backend
        See documentation of `sym.Backend \
<https://pythonhosted.org/sym/sym.html#sym.backend.Backend>`_.
    module : str
        ``module`` keyword argument passed to ``backend.Lambdify``.
    \*\*kwargs:
        See :py:class:`pyneqsys.core.NeqSys`

    Examples
    --------
    >>> import sympy as sp
    >>> e = sp.exp
    >>> x = x0, x1 = sp.symbols('x:2')
    >>> params = a, b = sp.symbols('a b')
    >>> neqsys = SymbolicSys(x, [a*(1 - x0), b*(x1 - x0**2)], params)
    >>> xout, sol = neqsys.solve('scipy', [-10, -5], [1, 10])
    >>> print(xout)
    [ 1.  1.]
    >>> print(neqsys.get_jac()[0, 0])
    -a

    Notes
    -----
    When using SymPy as backend a limited number of unknowns is supported.
    The reason is that (currently) ``sympy.lambdify`` has an upper limit on
    number of arguments.

    """
    def __init__(self, x, exprs, params=(), jac=True, backend=None, **kwargs):
        self.x = x
        self.exprs = exprs
        self.params = params
        self._jac = jac
        self.be = Backend(backend)
        self.nf, self.nx = len(exprs), len(x)  # needed by get_*_cb
        self.band = kwargs.get('band', None)  # needed by get_*_cb
        self.module = kwargs.pop('module', 'numpy')
        super(SymbolicSys, self).__init__(self.nf, self.nx, self._get_f_cb(),
                                          self._get_j_cb(), **kwargs)

    @classmethod
    def from_callback(cls, cb, nx=None, nparams=None, **kwargs):
        """ Generate a SymbolicSys instance from a callback"""
        if kwargs.get('x_by_name', False):
            if 'names' not in kwargs:
                raise ValueError("Need ``names`` in kwargs.")
            if nx is None:
                nx = len(kwargs['names'])
            elif nx != len(kwargs['names']):
                raise ValueError(
                    "Inconsistency between nx and length of ``names``.")
        if kwargs.get('par_by_name', False):
            if 'param_names' not in kwargs:
                raise ValueError("Need ``param_names`` in kwargs.")
            if nparams is None:
                nparams = len(kwargs['param_names'])
            elif nparams != len(kwargs['param_names']):
                raise ValueError(
                    "Inconsistency between ``nparam`` and length of ``param_names``."
                )

        if nparams is None:
            nparams = 0

        if nx is None:
            raise ValueError(
                "Need ``nx`` of ``names`` together with ``x_by_name==True``.")
        be = Backend(kwargs.pop('backend', None))
        x = be.real_symarray('x', nx)
        p = be.real_symarray('p', nparams)
        _x = dict(zip(kwargs['names'], x)) if kwargs.get('x_by_name',
                                                         False) else x
        _p = dict(zip(kwargs['param_names'], p)) if kwargs.get(
            'par_by_name', False) else p
        try:
            exprs = cb(_x, _p, be)
        except TypeError:
            exprs = _ensure_3args(cb)(_x, _p, be)
        return cls(x, exprs, p, backend=be, **kwargs)

    def get_jac(self):
        """ Return the jacobian of the expressions """
        if self._jac is True:
            if self.band is None:
                f = self.be.Matrix(self.nf, 1, self.exprs)
                _x = self.be.Matrix(self.nx, 1, self.x)
                return f.jacobian(_x)
            else:
                # Banded
                return self.be.Matrix(
                    banded_jacobian(self.exprs, self.x, *self.band))
        elif self._jac is False:
            return False
        else:
            return self._jac

    def _get_f_cb(self):
        args = list(chain(self.x, self.params))
        kw = dict(module=self.module,
                  dtype=object if self.module == 'mpmath' else None)
        try:
            cb = self.be.Lambdify(args, self.exprs, **kw)
        except TypeError:
            cb = self.be.Lambdify(args, self.exprs)

        def f(x, params):
            return cb(np.concatenate((x, params), axis=-1))

        return f

    def _get_j_cb(self):
        args = list(chain(self.x, self.params))
        kw = dict(module=self.module,
                  dtype=object if self.module == 'mpmath' else None)
        try:
            cb = self.be.Lambdify(args, self.get_jac(), **kw)
        except TypeError:
            cb = self.be.Lambdify(args, self.get_jac())

        def j(x, params):
            return cb(np.concatenate((x, params), axis=-1))

        return j

    _use_symbol_latex_names = True

    def _repr_latex_(self):  # pretty printing in Jupyter notebook
        from ._sympy import NeqSysTexPrinter
        if self.latex_names and (self.latex_param_names
                                 if len(self.params) else True):
            pretty = {
                s: n
                for s, n in chain(
                    zip(self.x, self.latex_names) if self.
                    _use_symbol_latex_names else [],
                    zip(self.params, self.latex_param_names))
            }
        else:
            pretty = {}

        return '$%s$' % NeqSysTexPrinter(dict(symbol_names=pretty)).doprint(
            self.exprs)
Example #13
0
class SymbolicSys(OdeSys):
    """ ODE System from symbolic expressions

    Creates a :class:`OdeSys` instance
    from symbolic expressions. Jacboian and second derivatives
    are derived when needed.

    Parameters
    ----------
    dep_exprs : iterable of (symbol, expression)-pairs
    indep : Symbol
        Independent variable (default: None => autonomous system).
    params : iterable of symbols
        Problem parameters.
    jac : ImmutableMatrix or bool (default: True)
        if True:
            calculate jacobian from exprs
        if False:
            do not compute jacobian (use explicit steppers)
        if instance of ImmutableMatrix:
            user provided expressions for the jacobian
    roots : iterable of expressions
        Equations to look for root's for during integration
        (currently available through cvode).
    backend : str or sym.Backend
        See documentation of `sym.Backend \
<https://pythonhosted.org/sym/sym.html#sym.backend.Backend>`_.
    \*\*kwargs:
        See :py:class:`OdeSys`

    Attributes
    ----------
    dep : tuple of symbols
        Dependent variables.
    exprs : tuple of expressions
        Expressions for the derivatives of the dependent variables
        (:attr:`dep`) with respect to the independent variable (:attr:`indep`).
    indep : Symbol or None
        Independent variable (``None`` indicates autonomous system).
    params : iterable of symbols
        Problem parameters.
    roots : iterable of expressions or None
        Roots to report for during integration.
    ny : int
        ``len(self.dep)``
    be : module
        Symbolic backend.

    """

    def __init__(self, dep_exprs, indep=None, params=(), jac=True, dfdx=True,
                 roots=None, backend=None, **kwargs):
        self.dep, self.exprs = zip(*dep_exprs)
        self.indep = indep
        self.params = params
        self._jac = jac
        self._dfdx = dfdx
        self.roots = roots
        self.be = Backend(backend)

        if kwargs.get('names', None) is True:
            kwargs['names'] = [y.name for y in self.dep]
        # we need self.band before super().__init__
        self.band = kwargs.get('band', None)
        super(SymbolicSys, self).__init__(
            self.get_f_ty_callback(),
            self.get_j_ty_callback(),
            self.get_dfdx_callback(),
            self.get_roots_callback(),
            nroots=None if roots is None else len(roots),
            **kwargs)

    @classmethod
    def from_callback(cls, cb, ny, nparams=0, **kwargs):
        """ Create an instance from a callback.

        Parameters
        ----------
        cb : callbable
            Signature ``rhs(x, y[:], p[:]) -> f[:]``
        ny : int
            length of y
        nparams : int (default: 0)
            length of p
        \*\*kwargs :
            keyword arguments passed onto :class:`SymbolicSys`

        Returns
        -------
        An instance of :class:`SymbolicSys`.
        """
        be = Backend(kwargs.pop('backend', None))
        x, = be.real_symarray('x', 1)
        y = be.real_symarray('y', ny)
        p = be.real_symarray('p', nparams)
        try:
            exprs = cb(x, y, p, be)
        except TypeError:
            exprs = _ensure_4args(cb)(x, y, p, be)
        return cls(zip(y, exprs), x, p, backend=be, **kwargs)

    @classmethod
    def from_other(cls, ori, **kwargs):  # provisional
        new_kw = kwargs.copy()
        if ori.roots is not None:
            raise NotImplementedError('roots currently unsupported')
        if 'params' not in new_kw:
            new_kw['params'] = ori.params

        if len(ori.pre_processors) > 0:
            if 'pre_processors' not in new_kw:
                new_kw['pre_processors'] = []
            new_kw['pre_processors'] = ori.pre_processors + new_kw['pre_processors']

        if len(ori.post_processors) > 0:
            if 'post_processors' not in new_kw:
                new_kw['post_processors'] = []
            new_kw['post_processors'] = ori.post_processors + new_kw['post_processors']

        return cls(zip(ori.dep, ori.exprs), ori.indep, **new_kw)

    @property
    def ny(self):
        """ Number of dependent variables in the system. """
        return len(self.exprs)

    @property
    def autonomous(self):
        if self.indep is None:
            return True
        for expr in self.exprs:
            if expr.has(self.indep):
                return False
        return True

    def get_jac(self):
        """ Derives the jacobian from ``self.exprs`` and ``self.dep``. """
        if self._jac is True:
            if self.band is None:
                f = self.be.Matrix(1, self.ny, self.exprs)
                self._jac = f.jacobian(self.be.Matrix(1, self.ny, self.dep))
            else:  # Banded
                self._jac = self.be.banded_jacobian(self.exprs, self.dep, *self.band)
        elif self._jac is False:
            return False

        return self._jac

    def get_dfdx(self):
        """ Calculates 2nd derivatives of ``self.exprs`` """
        if self._dfdx is True:
            if self.indep is None:
                zero = 0*self.be.Dummy()**0
                self._dfdx = self.be.Matrix(1, self.ny, [zero]*self.ny)
            else:
                self._dfdx = self.be.Matrix(1, self.ny, [expr.diff(self.indep) for expr in self.exprs])
        elif self._dfdx is False:
            return False
        return self._dfdx

    def _callback_factory(self, exprs):
        args = [self.indep] + list(self.dep) + list(self.params)
        return _Wrapper(self.be.Lambdify(args, exprs), self.ny)

    def get_f_ty_callback(self):
        """ Generates a callback for evaluating ``self.exprs``. """
        return self._callback_factory(self.exprs)

    def get_j_ty_callback(self):
        """ Generates a callback for evaluating the jacobian. """
        j_exprs = self.get_jac()
        if j_exprs is False:
            return None
        return self._callback_factory(j_exprs)

    def get_dfdx_callback(self):
        """ Generate a callback for evaluating derivative of ``self.exprs`` """
        dfdx_exprs = self.get_dfdx()
        if dfdx_exprs is False:
            return None
        return self._callback_factory(dfdx_exprs)

    def get_roots_callback(self):
        """ Generate a callback for evaluating ``self.roots`` """
        if self.roots is None:
            return None
        return self._callback_factory(self.roots)

    # Not working yet:
    def _integrate_mpmath(self, xout, y0, params=()):
        """ Not working at the moment, need to fix
        (low priority - taylor series is a poor method)"""
        raise NotImplementedError
        xout, y0, self.internal_params = self.pre_process(xout, y0, params)
        from mpmath import odefun

        def rhs(x, y):
            rhs.ncall += 1
            return [
                e.subs(
                    ([(self.indep, x)] if self.indep is not None else []) +
                    list(zip(self.dep, y))
                ) for e in self.exprs
            ]
        rhs.ncall = 0

        cb = odefun(rhs, xout[0], y0)
        yout = []
        for x in xout:
            yout.append(cb(x))
        info = {'nrhs': rhs.ncall}
        return self.post_process(xout, yout, self.internal_params)[:2] + (info,)

    def _get_analytic_stiffness_cb(self):
        J = self.get_jac()
        eig_vals = list(J.eigenvals().keys())
        return self._callback_factory(eig_vals)

    def analytic_stiffness(self, xyp=None):
        """ Running stiffness ratio from last integration.

        Calculate sittness ratio, i.e. the ratio between the largest and
        smallest absolute eigenvalue of the (analytic) jacobian matrix.

        See :meth:`OdeSys.stiffness` for more info.
        """
        return self.stiffness(xyp, self._get_analytic_stiffness_cb())
Example #14
0
def _validate(
        conditions,
        rsys,
        symbols,
        odesys,
        backend=None,
        transform=None,
        ignore=("time", ),
        check_conditions_no_extra=False,
):
    """For use with create_odesys

    Parameters
    ----------
    conditions : OrderedDict
        Parameters, values with units from ``chempy.units``.
    rsys : ReactionSystem
    symbols : dict
        Mapping variable name to symbols.
    backend : module
        Module for symbolic mathematics. (defaults to SymPy)
    transform : callable for rewriting expressions
    check_conditions_no_extra : bool
        When True, conditions may not contain keys not referenced in any expression.

    Raises
    ------
    ``KeyError`` if a key in conditions is not in odesys.names or odesys.param_names

    """
    if backend is None:
        from sym import Backend

        backend = Backend(backend)

    if transform is None:
        if backend.__name__ != "sympy":
            warnings.warn(
                "Backend != SymPy, provide your own transform function.")

        def transform(arg):
            expr = backend.logcombine(arg, force=True)
            v, w = map(backend.Wild, "v w".split())
            return expr.replace(backend.log(w**v), v * backend.log(w))

    rates = {}
    seen = set()

    for k, v in rsys.rates(symbols).items():
        expr = transform(v)
        if expr == 0:
            rate = 0 * u.molar / u.second
        else:
            rate = None
            for term in (expr.args if hasattr(expr, "is_Add") and expr.is_Add
                         else (expr, )):
                args = sorted(expr.free_symbols, key=lambda e: e.name)
                values = [conditions[s.name] for s in args]
                result = backend.lambdify(args, term)(*map(_exact, values))
                to_unitless(result, u.molar /
                            u.second)  # raises an exception upon unit error
                if rate is None:
                    rate = result
                else:
                    rate += result
        rates[k] = rate
        seen |= set([s.name for s in expr.free_symbols])
    if check_conditions_no_extra:
        for k in conditions:
            if (k not in odesys.param_names and k not in odesys.names
                    and k not in ignore):
                raise KeyError("Unknown param: %s" % k)
    return {
        "not_seen": (set(rsys.substances) | set(conditions)) - seen,
        "rates": rates
    }
Example #15
0
def _create_odesys(
    rsys,
    substance_symbols=None,
    parameter_symbols=None,
    pretty_replace=lambda x: x,
    backend=None,
    SymbolicSys=None,
    time_symbol=None,
    unit_registry=None,
    rates_kw=None,
    parameter_expressions=None,
    symbolic_kw=None,
):
    """This will be a simpler version of get_odesys without the unit handling code.
    The motivation is to reduce complexity (the code of get_odesys is long with multiple closures).

    This will also rely on SymPy explicitly and the user will be expected to deal with SymPy
    expressions.

    Only when this function has the same capabilities as get_odesys will it become and public API
    (along with a deprecation of get_odesys).

    Parameters
    ----------
    rsys : ReactionSystem instance
    substance_symbols : OrderedDict
       If ``None``: ``rsys.substances`` will be used.
    parameter_symbols : OrderedDict
    backend : str or module
        Symbolic backend (e.g. sympy). The package ``sym`` is used as a wrapper.
    SymbolicSys: class
        See ``pyodesys`` for API.
    time_symbol : Symbol
    unit_registry : object
        e.g. ``chempy.units.SI_base_registry``
    rates_kw : dict
        Keyword arguments passed to the ``rates`` method of rsys.
    parameter_expressions : dict
        Optional overrides.
    symbolic_kw : dict
        Keyword arguments passed on to SymbolicSys.

    Returns
    -------
    SymbolicSys (subclass of ``pyodesys.ODESys``)
    dict :
        - ``'symbols'``: dict mapping str to symbol.
        - ``'validate'``: callable acppeting a dictionary mapping str to quantities
    """
    if backend is None:
        from sym import Backend

        backend = Backend(backend)
    if SymbolicSys is None:
        from pyodesys.symbolic import SymbolicSys

    if substance_symbols is None:
        substance_symbols = OrderedDict([(key, backend.Symbol(key))
                                         for key in rsys.substances])
    if isinstance(substance_symbols, OrderedDict):
        if list(substance_symbols) != list(rsys.substances):
            raise ValueError(
                "substance_symbols needs to have same (oredered) keys as rsys.substances"
            )

    if parameter_symbols is None:
        keys = []
        for rxnpar in map(attrgetter("param"), rsys.rxns):
            if isinstance(rxnpar, str):
                if rxnpar in (parameter_expressions or {}):
                    for pk in parameter_expressions[rxnpar].all_parameter_keys(
                    ):
                        keys.append(pk)
                else:
                    keys.append(rxnpar)
            elif isinstance(rxnpar, Expr):
                keys.extend(rxnpar.all_unique_keys())
                for pk in rxnpar.all_parameter_keys():
                    if pk not in keys:
                        keys.append(pk)
            else:
                raise NotImplementedError("Unknown")
        if rates_kw and "cstr_fr_fc" in rates_kw:
            flowrate_volume, feed_conc = rates_kw["cstr_fr_fc"]
            keys.append(flowrate_volume)
            keys.extend(feed_conc.values())
            assert all(sk in rsys.substances for sk in feed_conc)
        if len(keys) != len(set(keys)):
            raise ValueError("Duplicates in keys")
        parameter_symbols = OrderedDict([(key, backend.Symbol(key))
                                         for key in keys])

    if not isinstance(parameter_symbols, OrderedDict):
        raise ValueError("parameter_symbols needs to be an OrderedDict")

    symbols = OrderedDict(
        chain(substance_symbols.items(), parameter_symbols.items()))
    symbols["time"] = time_symbol or backend.Symbol("t")
    if any(symbols["time"] == v for k, v in symbols.items() if k != "time"):
        raise ValueError("time_symbol already in use (name clash?)")
    varbls = dict(symbols, **parameter_symbols)
    varbls.update(parameter_expressions or {})
    rates = rsys.rates(varbls, **(rates_kw or {}))
    compo_vecs, compo_names = rsys.composition_balance_vectors()

    odesys = SymbolicSys(
        zip(
            [substance_symbols[key] for key in rsys.substances],
            [rates[key] for key in rsys.substances],
        ),
        symbols["time"],
        parameter_symbols.values(),
        names=list(rsys.substances.keys()),
        latex_names=[s.latex_name for s in rsys.substances.values()],
        param_names=parameter_symbols.keys(),
        latex_param_names=[
            pretty_replace(n) for n in parameter_symbols.keys()
        ],
        linear_invariants=compo_vecs,
        linear_invariant_names=list(map(str, compo_names)),
        backend=backend,
        dep_by_name=True,
        par_by_name=True,
        **(symbolic_kw or {}))

    validate = partial(_validate,
                       rsys=rsys,
                       symbols=symbols,
                       odesys=odesys,
                       backend=backend)
    return odesys, {
        "symbols":
        symbols,
        "validate":
        validate,
        "unit_aware_solve":
        _mk_unit_aware_solve(odesys, unit_registry, validate=validate)
        if unit_registry else None,
    }