コード例 #1
0
ファイル: test_cvode.py プロジェクト: Huan-Yang/pyodesys
def test_NativeSys__PartiallySolvedSystem__roots(idx):
    def f(x, y, p):
        return [-p[0] * y[0], p[0] * y[0] - p[1] * y[1], p[1] * y[1]]

    def roots(x, y):
        return ([y[0] - y[1]], [y[0] - y[2]], [y[1] - y[2]])[idx]

    odesys = SymbolicSys.from_callback(f, 3, 2, roots_cb=roots)
    _p, _q, tend = 7, 3, 0.7
    dep0 = (1, 0, 0)
    ref = [0.11299628093544488, 0.20674119231833346, 0.3541828705348678]

    def check(odesys):
        res = odesys.integrate(tend,
                               dep0, (_p, _q),
                               integrator='cvode',
                               return_on_root=True)
        assert abs(res.xout[-1] - ref[idx]) < 1e-7

    check(odesys)
    native = NativeSys.from_other(odesys)
    check(native)

    psys = PartiallySolvedSystem(
        odesys, lambda t0, xyz, par0, be:
        {odesys.dep[0]: xyz[0] * be.exp(-par0[0] * (odesys.indep - t0))})
    check(psys)
    pnative = NativeSys.from_other(psys)
    check(pnative)
コード例 #2
0
def _test_chained_multi_native(NativeSys,
                               integrator='cvode',
                               rtol_close=0.02,
                               atol=1e-10,
                               rtol=1e-14,
                               steps_fact=1,
                               **kwargs):
    logc, logt, reduced = kwargs.pop('logc'), kwargs.pop('logt'), kwargs.pop(
        'reduced')
    zero_time, zero_conc, nonnegative = kwargs.pop('zero_time'), kwargs.pop(
        'zero_conc'), kwargs.pop('nonnegative')
    logexp = (sp.log, sp.exp)

    ny, nk = 3, 3
    k = (.04, 1e4, 3e7)
    init_conc = (1, zero_conc, zero_conc)
    tend = 1e11
    _yref_1e11 = (0.2083340149701255e-7, 0.8333360770334713e-13,
                  0.9999999791665050)

    lin_s = SymbolicSys.from_callback(get_ode_exprs(logc=False, logt=False)[0],
                                      ny,
                                      nk,
                                      lower_bounds=[0] *
                                      ny if nonnegative else None)
    logexp = (sp.log, sp.exp)

    if reduced:
        if logc or logt:
            PartSolvSys = PartiallySolvedSystem  # we'll add NativeSys further down below
        else:

            class PartSolvSys(PartiallySolvedSystem, NativeSys):
                pass

        other1, other2 = [_ for _ in range(3) if _ != (reduced - 1)]

        def reduced_analytic(x0, y0, p0):
            return {
                lin_s.dep[reduced - 1]:
                y0[0] + y0[1] + y0[2] - lin_s.dep[other1] - lin_s.dep[other2]
            }

        our_sys = PartSolvSys(lin_s, reduced_analytic)
    else:
        our_sys = lin_s

    if logc or logt:

        class TransformedNativeSys(TransformedSys, NativeSys):
            pass

        SS = symmetricsys(logexp if logc else None,
                          logexp if logt else None,
                          SuperClass=TransformedNativeSys)
        our_sys = SS.from_other(our_sys)

    ori_sys = NativeSys.from_other(lin_s)

    for sys_iter, kw in [([our_sys, ori_sys], {
            'nsteps': [100 * steps_fact, 1613 * 1.01 * steps_fact],
            'return_on_error': [True, False]
    }), ([ori_sys], {
            'nsteps': [1705 * 1.01 * steps_fact]
    })]:
        results = integrate_chained(sys_iter,
                                    kw, [(zero_time, tend)] * 3,
                                    [init_conc] * 3, [k] * 3,
                                    integrator=integrator,
                                    atol=atol,
                                    rtol=rtol,
                                    **kwargs)

        for res in results:
            x, y, nfo = res
            assert np.allclose(_yref_1e11,
                               y[-1, :],
                               atol=1e-16,
                               rtol=rtol_close)
            assert nfo['success'] == True  # noqa
            assert nfo['nfev'] > 100
            assert nfo['njev'] > 10
            assert nfo['nsys'] in (1, 2)
コード例 #3
0
def get_odesys(rsys, include_params=True, substitutions=None, SymbolicSys=None, unit_registry=None,
               output_conc_unit=None, output_time_unit=None, cstr=False, constants=None, **kwargs):
    """ Creates a :class:`pyneqsys.SymbolicSys` from a :class:`ReactionSystem`

    The parameters passed to RateExpr will contain the key ``'time'`` corresponding to the
    independent variable of the IVP.

    Parameters
    ----------
    rsys : ReactionSystem
        Each reaction of ``rsys`` will have their :meth:`Reaction.rate_expr()` invoked.
        Note that if :attr:`Reaction.param` is not a :class:`RateExpr` (or convertible to
        one through :meth:`as_RateExpr`) it will be used to construct a :class:`MassAction`
        instance.
    include_params : bool (default: True)
        Whether rate constants should be included into the rate expressions or
        left as free parameters in the :class:`pyneqsys.SymbolicSys` instance.
    substitutions : dict, optional
        Variable substitutions used by rate expressions (in respective Reaction.param).
        values are allowed to be values of instances of :class:`Expr`.
    SymbolicSys : class (optional)
        Default : :class:`pyneqsys.SymbolicSys`.
    unit_registry: dict (optional)
        See :func:`chempy.units.get_derived_units`.
    output_conc_unit : unit (Optional)
    output_time_unit : unit (Optional)
    cstr : bool
        Generate expressions for continuously stirred tank reactor.
    constants : module
        e.g. ``chempy.units.default_constants``, parameter keys not found in
        substitutions will be looked for as an attribute of ``constants`` when provided.
    \\*\\*kwargs :
        Keyword arguemnts passed on to `SymbolicSys`.

    Returns
    -------
    pyodesys.symbolic.SymbolicSys
    extra : dict, with keys:
        - param_keys : list of str instances
        - unique : OrderedDict mapping str to value (possibly None)
        - p_units : list of units
        - max_euler_step_cb : callable or None
        - linear_dependencies : None or factory of solver callback
        - rate_exprs_cb : callable
        - cstr_fr_fc : None or (feed-ratio-key, subtance-key-to-feed-conc-key-map)

    Examples
    --------
    >>> from chempy import Equilibrium, ReactionSystem
    >>> eq = Equilibrium({'Fe+3', 'SCN-'}, {'FeSCN+2'}, 10**2)
    >>> substances = 'Fe+3 SCN- FeSCN+2'.split()
    >>> rsys = ReactionSystem(eq.as_reactions(kf=3.0), substances)
    >>> odesys, extra = get_odesys(rsys)
    >>> init_conc = {'Fe+3': 1.0, 'SCN-': .3, 'FeSCN+2': 0}
    >>> tout, Cout, info = odesys.integrate(5, init_conc)
    >>> Cout[-1, :].round(4)
    array([0.7042, 0.0042, 0.2958])

    """
    if SymbolicSys is None:
        from pyodesys.symbolic import SymbolicSys

    r_exprs = [rxn.rate_expr() for rxn in rsys.rxns]
    _ori_pk = set.union(*(ratex.all_parameter_keys() for ratex in r_exprs))
    _ori_uk = set.union(*(ratex.all_unique_keys() for ratex in r_exprs))
    _subst_pk = set()
    _active_subst = OrderedDict()
    _passive_subst = OrderedDict()
    substitutions = substitutions or {}

    unique = OrderedDict()
    unique_units = {}

    cstr_fr_fc = (
        'feedratio',
        OrderedDict([(sk, 'fc_'+sk) for sk in rsys.substances])
    ) if cstr is True else cstr

    if cstr_fr_fc:
        _ori_pk.add(cstr_fr_fc[0])
        for k in cstr_fr_fc[1].values():
            _ori_pk.add(k)

    def _reg_unique_unit(k, arg_dim, idx):
        if unit_registry is None:
            return
        unique_units[k] = reduce(mul, [1]+[unit_registry[dim]**v for dim, v in arg_dim[idx].items()])

    def _get_arg_dim(expr, rxn):
        if unit_registry is None:
            return None
        else:
            return expr.args_dimensionality(reaction=rxn)

    def _reg_unique(expr, rxn=None):
        if not isinstance(expr, Expr):
            raise NotImplementedError("Currently only Expr sub classes are supported.")

        if expr.args is None:
            for idx, k in enumerate(expr.unique_keys):
                if k not in substitutions:
                    unique[k] = None
                    _reg_unique_unit(k, _get_arg_dim(expr, rxn), idx)
        else:
            for idx, arg in enumerate(expr.args):
                if isinstance(arg, Expr):
                    _reg_unique(arg, rxn=rxn)
                elif expr.unique_keys is not None and idx < len(expr.unique_keys):
                    uk = expr.unique_keys[idx]
                    if uk not in substitutions:
                        unique[uk] = arg
                        _reg_unique_unit(uk, _get_arg_dim(expr, rxn), idx)

    for sk, sv in substitutions.items():
        if sk not in _ori_pk and sk not in _ori_uk:
            raise ValueError("Substitution: '%s' does not appear in any rate expressions." % sk)
        if isinstance(sv, Expr):
            _subst_pk.update(sv.parameter_keys)
            _active_subst[sk] = sv
            if not include_params:
                _reg_unique(sv)
        else:
            # if unit_registry is None:
            if unit_registry is not None:
                sv = unitless_in_registry(sv, unit_registry)
            _passive_subst[sk] = sv

    all_pk = []
    for pk in filter(lambda x: x not in substitutions and x != 'time',
                     _ori_pk.union(_subst_pk)):
        if hasattr(constants, pk):
            const = getattr(constants, pk)
            if unit_registry is None:
                const = magnitude(const)
            else:
                const = unitless_in_registry(const, unit_registry)

            _passive_subst[pk] = const
        else:
            all_pk.append(pk)

    if not include_params:
        for rxn, ratex in zip(rsys.rxns, r_exprs):
            _reg_unique(ratex, rxn)

    all_pk_with_unique = list(chain(all_pk, filter(lambda k: k not in all_pk, unique.keys())))
    if include_params:
        param_names_for_odesys = all_pk
    else:
        param_names_for_odesys = all_pk_with_unique

    if unit_registry is None:
        p_units = None
    else:
        # We need to make rsys_params unitless and create
        # a pre- & post-processor for SymbolicSys
        pk_units = [_get_derived_unit(unit_registry, k) for k in all_pk]
        p_units = pk_units if include_params else (pk_units + [unique_units[k] for k in unique])
        new_r_exprs = []
        for ratex in r_exprs:
            _pu, _new_ratex = ratex.dedimensionalisation(unit_registry)
            new_r_exprs.append(_new_ratex)
        r_exprs = new_r_exprs

        time_unit = get_derived_unit(unit_registry, 'time')
        conc_unit = get_derived_unit(unit_registry, 'concentration')

        def post_processor(x, y, p):
            time = x*time_unit
            if output_time_unit is not None:
                time = rescale(time, output_time_unit)
            conc = y*conc_unit
            if output_conc_unit is not None:
                conc = rescale(conc, output_conc_unit)
            return time, conc, np.array([elem*p_unit for elem, p_unit in zip(p.T, p_units)], dtype=object).T

        kwargs['to_arrays_callbacks'] = (
            lambda x: to_unitless(x, time_unit),
            lambda y: to_unitless(y, conc_unit),
            lambda p: np.array([to_unitless(px, p_unit) for px, p_unit in zip(
                p.T if hasattr(p, 'T') else p, p_units)]).T
        )
        kwargs['post_processors'] = kwargs.get('post_processors', []) + [post_processor]

    def dydt(t, y, p, backend=math):
        variables = dict(chain(y.items(), p.items()))
        if 'time' in variables:
            raise ValueError("Key 'time' is reserved.")
        variables['time'] = t
        for k, act in _active_subst.items():
            if unit_registry is not None and act.args:
                _, act = act.dedimensionalisation(unit_registry)
            variables[k] = act(variables, backend=backend)
        variables.update(_passive_subst)
        return rsys.rates(variables, backend=backend, ratexs=r_exprs, cstr_fr_fc=cstr_fr_fc)

    def reaction_rates(t, y, p, backend=math):
        variables = dict(chain(y.items(), p.items()))
        if 'time' in variables:
            raise ValueError("Key 'time' is reserved.")
        variables['time'] = t
        for k, act in _active_subst.items():
            if unit_registry is not None and act.args:
                _, act = act.dedimensionalisation(unit_registry)
            variables[k] = act(variables, backend=backend)
        variables.update(_passive_subst)
        return [ratex(variables, backend=backend, reaction=rxn) for
                rxn, ratex in zip(rsys.rxns, r_exprs)]

    names = [s.name for s in rsys.substances.values()]
    latex_names = [None if s.latex_name is None else ('\\mathrm{' + s.latex_name + '}')
                   for s in rsys.substances.values()]

    compo_vecs, compo_names = rsys.composition_balance_vectors()

    odesys = SymbolicSys.from_callback(
        dydt, dep_by_name=True, par_by_name=True, names=names,
        latex_names=latex_names, param_names=param_names_for_odesys,
        linear_invariants=None if len(compo_vecs) == 0 else compo_vecs,
        linear_invariant_names=None if len(compo_names) == 0 else list(map(str, compo_names)),
        **kwargs)

    symbolic_ratexs = reaction_rates(
        odesys.indep, dict(zip(odesys.names, odesys.dep)),
        dict(zip(odesys.param_names, odesys.params)), backend=odesys.be)
    rate_exprs_cb = odesys._callback_factory(symbolic_ratexs)

    if rsys.check_balance(strict=True):
        # Composition available, we can provide callback for calculating
        # maximum allowed Euler forward step at start of integration.
        def max_euler_step_cb(x, y, p=()):
            _x, _y, _p = odesys.pre_process(*odesys.to_arrays(x, y, p))
            upper_bounds = rsys.upper_conc_bounds(_y)
            fvec = odesys.f_cb(_x[0], _y, _p)
            h = []
            for idx, fcomp in enumerate(fvec):
                if fcomp == 0:
                    h.append(float('inf'))
                elif fcomp > 0:
                    h.append((upper_bounds[idx] - _y[idx])/fcomp)
                else:  # fcomp < 0
                    h.append(-_y[idx]/fcomp)
            min_h = min(h)
            return min(min_h, 1)

        def linear_dependencies(preferred=None):
            if preferred is not None:
                if len(preferred) == 0:
                    raise ValueError("No preferred substance keys provided")
                if len(preferred) >= len(rsys.substances):
                    raise ValueError("Cannot remove all concentrations from linear dependencies")
                for k in preferred:
                    if k not in rsys.substances:
                        raise ValueError("Unknown substance key: %s" % k)

            def analytic_solver(x0, y0, p0, be):
                if preferred is None:
                    _preferred = None
                else:
                    _preferred = list(preferred)
                A = be.Matrix(compo_vecs)
                rA, pivots = A.rref()

                analytic_exprs = OrderedDict()
                for ri, ci1st in enumerate(pivots):
                    for idx in range(ci1st, odesys.ny):
                        key = odesys.names[idx]
                        if rA[ri, idx] == 0:
                            continue
                        if _preferred is None or key in _preferred:
                            terms = [rA[ri, di]*(odesys.dep[di] - y0[odesys.dep[di]])
                                     for di in range(ci1st, odesys.ny) if di != idx]
                            analytic_exprs[odesys[key]] = y0[odesys.dep[idx]] - sum(terms)/rA[ri, idx]
                            if _preferred is not None:
                                _preferred.remove(key)
                            break
                for k in reversed(list(analytic_exprs.keys())):
                    analytic_exprs[k] = analytic_exprs[k].subs(analytic_exprs)
                if _preferred is not None and len(_preferred) > 0:
                    raise ValueError("Failed to obtain analytic expression for: %s" % ', '.join(_preferred))
                return analytic_exprs

            return analytic_solver

    else:
        max_euler_step_cb = None
        linear_dependencies = None

    return odesys, {
        'param_keys': all_pk,
        'unique': unique,
        'p_units': p_units,
        'max_euler_step_cb': max_euler_step_cb,
        'linear_dependencies': linear_dependencies,
        'rate_exprs_cb': rate_exprs_cb,
        'cstr_fr_fc': cstr_fr_fc,
        'unit_registry': unit_registry
    }
コード例 #4
0
ファイル: ode.py プロジェクト: treverhines/chempy
def get_odesys(rsys,
               include_params=True,
               substitutions=None,
               SymbolicSys=None,
               unit_registry=None,
               output_conc_unit=None,
               output_time_unit=None,
               **kwargs):
    """ Creates a :class:`pyneqsys.SymbolicSys` from a :class:`ReactionSystem`

    Parameters
    ----------
    rsys : ReactionSystem
        note that if :attr:`param` if not RateExpr (or convertible to one through
        :meth:`_as_RateExpr`) it will be used to construct a :class:`MassAction` instance.
    include_params : bool (default: False)
        whether rate constants should be included into the rate expressions or
        left as free parameters in the :class:`pyneqsys.SymbolicSys` instance.
    substitutions : dict, optional
        variable substitutions used by rate expressions (in respective Reaction.param).
        values are allowed to be tuple like: (new_vars, callback)
    SymbolicSys : class (optional)
        default : :class:`pyneqsys.SymbolicSys`
    unit_registry: dict (optional)
        see :func:`chempy.units.get_derived_units`
    output_conc_unit : unit (Optional)
    output_time_unit : unit (Optional)
    \*\*kwargs :
        Keyword arguemnts pass on to `SymbolicSys`

    Returns
    -------
    pyodesys.symbolic.SymbolicSys
    param_keys
    unique_keys
    p_units

    Examples
    --------
    >>> from chempy import Equilibrium, ReactionSystem
    >>> eq = Equilibrium({'Fe+3', 'SCN-'}, {'FeSCN+2'}, 10**2)
    >>> substances = 'Fe+3 SCN- FeSCN+2'.split()
    >>> rsys = ReactionSystem(eq.as_reactions(kf=3.0), substances)
    >>> odesys = get_odesys(rsys)[0]
    >>> init_conc = {'Fe+3': 1.0, 'SCN-': .3, 'FeSCN+2': 0}
    >>> tout, Cout, info = odesys.integrate(5, init_conc)
    >>> Cout[-1, :].round(4)
    array([ 0.7042,  0.0042,  0.2958])

    """
    if SymbolicSys is None:
        from pyodesys.symbolic import SymbolicSys

    substance_keys = list(rsys.substances.keys())

    if 'names' not in kwargs:
        kwargs['names'] = list(rsys.substances.values())  # pyodesys>=0.5.3

    r_exprs = [rxn.rate_expr() for rxn in rsys.rxns]

    _original_param_keys = set.union(*(set(ratex.parameter_keys)
                                       for ratex in r_exprs))
    _from_subst = set()
    _active_subst = {}
    _passive_subst = {}
    substitutions = substitutions or {}
    for key, v in substitutions.items():
        if key not in _original_param_keys:
            raise ValueError(
                "Substitution: '%s' does not appear in any rate expressions.")
        if isinstance(v, Expr):
            _from_subst.update(v.parameter_keys)
            _active_subst[key] = v
        else:
            _passive_subst[key] = v
    param_keys = list(
        filter(lambda x: x not in substitutions,
               _original_param_keys.union(_from_subst)))

    unique_keys = []
    p_defaults = []
    if not include_params:
        for ratex in r_exprs:
            if ratex.unique_keys is not None:
                unique_keys.extend(ratex.unique_keys)
                p_defaults.extend(ratex.args)
    if unit_registry is None:

        def pre_processor(x, y, p):
            return (x, rsys.as_per_substance_array(y),
                    [p[k] for k in param_keys] + [p[k] for k in unique_keys])

        def post_processor(x, y, p):
            return (
                x,
                y,  # dict(zip(substance_keys, y)),
                dict(zip(param_keys + unique_keys, p)))

        p_units = [None] * (len(param_keys) + len(unique_keys))
    else:
        # We need to make rsys_params unitless and create
        # a pre- & post-processor for SymbolicSys
        p_units = [get_derived_unit(unit_registry, k) for k in param_keys]
        new_r_exprs = []
        for ratex in r_exprs:
            _pu, _new_rate = ratex._recursive_as_RateExpr(
            ).dedimensionalisation(unit_registry)
            p_units.extend(_pu)
            new_r_exprs.append(_new_rate)
        r_exprs = new_r_exprs

        time_unit = get_derived_unit(unit_registry, 'time')
        conc_unit = get_derived_unit(unit_registry, 'concentration')

        def pre_processor(x, y, p):
            return (to_unitless(x, time_unit),
                    rsys.as_per_substance_array(to_unitless(y, conc_unit)), [
                        to_unitless(p[k], p_unit) for k, p_unit in zip(
                            chain(param_keys, unique_keys), p_units)
                    ])

        def post_processor(x, y, p):
            time = x * time_unit
            if output_time_unit is not None:
                time = time.rescale(output_time_unit)
            conc = y * conc_unit
            if output_conc_unit is not None:
                conc = conc.rescale(output_conc_unit)
            return time, conc, [
                elem * p_unit for elem, p_unit in zip(p, p_units)
            ]

    kwargs['pre_processors'] = [pre_processor] + kwargs.get(
        'pre_processors', [])
    kwargs['post_processors'] = kwargs.get('post_processors',
                                           []) + [post_processor]

    def dydt(t, y, p, backend=math):
        variables = dict(
            chain(zip(substance_keys, y), zip(param_keys, p[:len(param_keys)]),
                  zip(unique_keys, p[len(param_keys):])))
        for k, act in _active_subst.items():
            if unit_registry is not None:
                _, act = act.dedimensionalisation(unit_registry)
            variables[k] = act(variables, backend=backend)
        variables.update(_passive_subst)
        return dCdt(rsys, [rat(variables, backend=backend) for rat in r_exprs])

    return SymbolicSys.from_callback(
        dydt, len(substance_keys),
        len(param_keys) + (0 if include_params else len(unique_keys)),
        **kwargs), param_keys, unique_keys, p_units
コード例 #5
0
ファイル: ode.py プロジェクト: bjodah/chempy
def get_odesys(rsys, include_params=True, substitutions=None, SymbolicSys=None, unit_registry=None,
               output_conc_unit=None, output_time_unit=None, cstr=False, constants=None, **kwargs):
    """ Creates a :class:`pyneqsys.SymbolicSys` from a :class:`ReactionSystem`

    The parameters passed to RateExpr will contain the key ``'time'`` corresponding to the
    independent variable of the IVP.

    Parameters
    ----------
    rsys : ReactionSystem
        Each reaction of ``rsys`` will have their :meth:`Reaction.rate_expr()` invoked.
        Note that if :attr:`Reaction.param` is not a :class:`RateExpr` (or convertible to
        one through :meth:`as_RateExpr`) it will be used to construct a :class:`MassAction`
        instance.
    include_params : bool (default: True)
        Whether rate constants should be included into the rate expressions or
        left as free parameters in the :class:`pyneqsys.SymbolicSys` instance.
    substitutions : dict, optional
        Variable substitutions used by rate expressions (in respective Reaction.param).
        values are allowed to be values of instances of :class:`Expr`.
    SymbolicSys : class (optional)
        Default : :class:`pyneqsys.SymbolicSys`.
    unit_registry: dict (optional)
        See :func:`chempy.units.get_derived_units`.
    output_conc_unit : unit (Optional)
    output_time_unit : unit (Optional)
    cstr : bool
        Generate expressions for continuously stirred tank reactor.
    constants : module
        e.g. ``chempy.units.default_constants``, parameter keys not found in
        substitutions will be looked for as an attribute of ``constants`` when provided.
    \\*\\*kwargs :
        Keyword arguemnts passed on to `SymbolicSys`.

    Returns
    -------
    pyodesys.symbolic.SymbolicSys
    extra : dict, with keys:
        - param_keys : list of str instances
        - unique : OrderedDict mapping str to value (possibly None)
        - p_units : list of units
        - max_euler_step_cb : callable or None
        - linear_dependencies : None or factory of solver callback
        - rate_exprs_cb : callable
        - cstr_fr_fc : None or (feed-ratio-key, subtance-key-to-feed-conc-key-map)

    Examples
    --------
    >>> from chempy import Equilibrium, ReactionSystem
    >>> eq = Equilibrium({'Fe+3', 'SCN-'}, {'FeSCN+2'}, 10**2)
    >>> substances = 'Fe+3 SCN- FeSCN+2'.split()
    >>> rsys = ReactionSystem(eq.as_reactions(kf=3.0), substances)
    >>> odesys, extra = get_odesys(rsys)
    >>> init_conc = {'Fe+3': 1.0, 'SCN-': .3, 'FeSCN+2': 0}
    >>> tout, Cout, info = odesys.integrate(5, init_conc)
    >>> Cout[-1, :].round(4)
    array([0.7042, 0.0042, 0.2958])

    """
    if SymbolicSys is None:
        from pyodesys.symbolic import SymbolicSys

    r_exprs = [rxn.rate_expr() for rxn in rsys.rxns]
    _ori_pk = set.union(*(ratex.all_parameter_keys() for ratex in r_exprs))
    _ori_uk = set.union(*(ratex.all_unique_keys() for ratex in r_exprs))
    _subst_pk = set()
    _active_subst = OrderedDict()
    _passive_subst = OrderedDict()
    substitutions = substitutions or {}

    unique = OrderedDict()
    unique_units = {}

    cstr_fr_fc = (
        'feedratio',
        OrderedDict([(sk, 'fc_'+sk) for sk in rsys.substances])
    ) if cstr is True else cstr

    if cstr_fr_fc:
        _ori_pk.add(cstr_fr_fc[0])
        for k in cstr_fr_fc[1].values():
            _ori_pk.add(k)

    def _reg_unique_unit(k, arg_dim, idx):
        if unit_registry is None:
            return
        unique_units[k] = reduce(mul, [1]+[unit_registry[dim]**v for dim, v in arg_dim[idx].items()])

    def _get_arg_dim(expr, rxn):
        if unit_registry is None:
            return None
        else:
            return expr.args_dimensionality(reaction=rxn)

    def _reg_unique(expr, rxn=None):
        if not isinstance(expr, Expr):
            raise NotImplementedError("Currently only Expr sub classes are supported.")

        if expr.args is None:
            for idx, k in enumerate(expr.unique_keys):
                if k not in substitutions:
                    unique[k] = None
                    _reg_unique_unit(k, _get_arg_dim(expr, rxn), idx)
        else:
            for idx, arg in enumerate(expr.args):
                if isinstance(arg, Expr):
                    _reg_unique(arg, rxn=rxn)
                elif expr.unique_keys is not None and idx < len(expr.unique_keys):
                    uk = expr.unique_keys[idx]
                    if uk not in substitutions:
                        unique[uk] = arg
                        _reg_unique_unit(uk, _get_arg_dim(expr, rxn), idx)

    for sk, sv in substitutions.items():
        if sk not in _ori_pk and sk not in _ori_uk:
            raise ValueError("Substitution: '%s' does not appear in any rate expressions." % sk)
        if isinstance(sv, Expr):
            _subst_pk.update(sv.parameter_keys)
            _active_subst[sk] = sv
            if not include_params:
                _reg_unique(sv)
        else:
            # if unit_registry is None:
            if unit_registry is not None:
                sv = unitless_in_registry(sv, unit_registry)
            _passive_subst[sk] = sv

    all_pk = []
    for pk in filter(lambda x: x not in substitutions and x != 'time',
                     _ori_pk.union(_subst_pk)):
        if hasattr(constants, pk):
            const = getattr(constants, pk)
            if unit_registry is None:
                const = magnitude(const)
            else:
                const = unitless_in_registry(const, unit_registry)

            _passive_subst[pk] = const
        else:
            all_pk.append(pk)

    if not include_params:
        for rxn, ratex in zip(rsys.rxns, r_exprs):
            _reg_unique(ratex, rxn)

    all_pk_with_unique = list(chain(all_pk, filter(lambda k: k not in all_pk, unique.keys())))
    if include_params:
        param_names_for_odesys = all_pk
    else:
        param_names_for_odesys = all_pk_with_unique

    if unit_registry is None:
        p_units = None
    else:
        # We need to make rsys_params unitless and create
        # a pre- & post-processor for SymbolicSys
        pk_units = [_get_derived_unit(unit_registry, k) for k in all_pk]
        p_units = pk_units if include_params else (pk_units + [unique_units[k] for k in unique])
        new_r_exprs = []
        for ratex in r_exprs:
            _pu, _new_ratex = ratex.dedimensionalisation(unit_registry)
            new_r_exprs.append(_new_ratex)
        r_exprs = new_r_exprs

        time_unit = get_derived_unit(unit_registry, 'time')
        conc_unit = get_derived_unit(unit_registry, 'concentration')

        def post_processor(x, y, p):
            time = x*time_unit
            if output_time_unit is not None:
                time = rescale(time, output_time_unit)
            conc = y*conc_unit
            if output_conc_unit is not None:
                conc = rescale(conc, output_conc_unit)
            return time, conc, np.array([elem*p_unit for elem, p_unit in zip(p.T, p_units)], dtype=object).T

        kwargs['to_arrays_callbacks'] = (
            lambda x: to_unitless(x, time_unit),
            lambda y: to_unitless(y, conc_unit),
            lambda p: np.array([to_unitless(px, p_unit) for px, p_unit in zip(
                p.T if hasattr(p, 'T') else p, p_units)]).T
        )
        kwargs['post_processors'] = kwargs.get('post_processors', []) + [post_processor]

    def dydt(t, y, p, backend=math):
        variables = dict(chain(y.items(), p.items()))
        if 'time' in variables:
            raise ValueError("Key 'time' is reserved.")
        variables['time'] = t
        for k, act in _active_subst.items():
            if unit_registry is not None and act.args:
                _, act = act.dedimensionalisation(unit_registry)
            variables[k] = act(variables, backend=backend)
        variables.update(_passive_subst)
        return rsys.rates(variables, backend=backend, ratexs=r_exprs, cstr_fr_fc=cstr_fr_fc)

    def reaction_rates(t, y, p, backend=math):
        variables = dict(chain(y.items(), p.items()))
        if 'time' in variables:
            raise ValueError("Key 'time' is reserved.")
        variables['time'] = t
        for k, act in _active_subst.items():
            if unit_registry is not None and act.args:
                _, act = act.dedimensionalisation(unit_registry)
            variables[k] = act(variables, backend=backend)
        variables.update(_passive_subst)
        return [ratex(variables, backend=backend, reaction=rxn) for
                rxn, ratex in zip(rsys.rxns, r_exprs)]

    names = [s.name for s in rsys.substances.values()]
    latex_names = [None if s.latex_name is None else ('\\mathrm{' + s.latex_name + '}')
                   for s in rsys.substances.values()]

    compo_vecs, compo_names = rsys.composition_balance_vectors()

    odesys = SymbolicSys.from_callback(
        dydt, dep_by_name=True, par_by_name=True, names=names,
        latex_names=latex_names, param_names=param_names_for_odesys,
        linear_invariants=None if len(compo_vecs) == 0 else compo_vecs,
        linear_invariant_names=None if len(compo_names) == 0 else list(map(str, compo_names)),
        **kwargs)

    symbolic_ratexs = reaction_rates(
        odesys.indep, dict(zip(odesys.names, odesys.dep)),
        dict(zip(odesys.param_names, odesys.params)), backend=odesys.be)
    rate_exprs_cb = odesys._callback_factory(symbolic_ratexs)

    if rsys.check_balance(strict=True):
        # Composition available, we can provide callback for calculating
        # maximum allowed Euler forward step at start of integration.
        def max_euler_step_cb(x, y, p=()):
            _x, _y, _p = odesys.pre_process(*odesys.to_arrays(x, y, p))
            upper_bounds = rsys.upper_conc_bounds(_y)
            fvec = odesys.f_cb(_x[0], _y, _p)
            h = []
            for idx, fcomp in enumerate(fvec):
                if fcomp == 0:
                    h.append(float('inf'))
                elif fcomp > 0:
                    h.append((upper_bounds[idx] - _y[idx])/fcomp)
                else:  # fcomp < 0
                    h.append(-_y[idx]/fcomp)
            min_h = min(h)
            return min(min_h, 1)

        def linear_dependencies(preferred=None):
            if preferred is not None:
                if len(preferred) == 0:
                    raise ValueError("No preferred substance keys provided")
                if len(preferred) >= len(rsys.substances):
                    raise ValueError("Cannot remove all concentrations from linear dependencies")
                for k in preferred:
                    if k not in rsys.substances:
                        raise ValueError("Unknown substance key: %s" % k)

            def analytic_solver(x0, y0, p0, be):
                if preferred is None:
                    _preferred = None
                else:
                    _preferred = list(preferred)
                A = be.Matrix(compo_vecs)
                rA, pivots = A.rref()

                analytic_exprs = OrderedDict()
                for ri, ci1st in enumerate(pivots):
                    for idx in range(ci1st, odesys.ny):
                        key = odesys.names[idx]
                        if rA[ri, idx] == 0:
                            continue
                        if _preferred is None or key in _preferred:
                            terms = [rA[ri, di]*(odesys.dep[di] - y0[odesys.dep[di]])
                                     for di in range(ci1st, odesys.ny) if di != idx]
                            analytic_exprs[odesys[key]] = y0[odesys.dep[idx]] - sum(terms)/rA[ri, idx]
                            if _preferred is not None:
                                _preferred.remove(key)
                            break
                for k in reversed(list(analytic_exprs.keys())):
                    analytic_exprs[k] = analytic_exprs[k].subs(analytic_exprs)
                if _preferred is not None and len(_preferred) > 0:
                    raise ValueError("Failed to obtain analytic expression for: %s" % ', '.join(_preferred))
                return analytic_exprs

            return analytic_solver

    else:
        max_euler_step_cb = None
        linear_dependencies = None

    return odesys, {
        'param_keys': all_pk,
        'unique': unique,
        'p_units': p_units,
        'max_euler_step_cb': max_euler_step_cb,
        'linear_dependencies': linear_dependencies,
        'rate_exprs_cb': rate_exprs_cb,
        'cstr_fr_fc': cstr_fr_fc,
        'unit_registry': unit_registry
    }
コード例 #6
0
ファイル: ex.py プロジェクト: git-alice/triangle_platform
from pyodesys.symbolic import SymbolicSys


def f(t, y, p):
    return [y[1], -y[0] + p[0] * y[1] * (1 - y[0]**2)]


odesys = SymbolicSys.from_callback(f, 2, 1)
xout, yout, info = odesys.integrate(10, [1, 0], [1],
                                    integrator='odeint',
                                    nsteps=1000)
_ = odesys.plot_result()
import matplotlib.pyplot as plt
plt.show()  # doctest: +SKIP
コード例 #7
0
ファイル: ode.py プロジェクト: adelq/chempy
def get_odesys(rsys, include_params=True, substitutions=None,
               SymbolicSys=None,
               unit_registry=None, output_conc_unit=None,
               output_time_unit=None, **kwargs):
    """ Creates a :class:`pyneqsys.SymbolicSys` from a :class:`ReactionSystem`

    Parameters
    ----------
    rsys : ReactionSystem
        note that if :attr:`param` if not RateExpr (or convertible to one through
        :meth:`_as_RateExpr`) it will be used to construct a :class:`MassAction` instance.
    include_params : bool (default: False)
        whether rate constants should be included into the rate expressions or
        left as free parameters in the :class:`pyneqsys.SymbolicSys` instance.
    substitutions : dict, optional
        variable substitutions used by rate expressions (in respective Reaction.param).
        values are allowed to be tuple like: (new_vars, callback)
    SymbolicSys : class (optional)
        default : :class:`pyneqsys.SymbolicSys`
    unit_registry: dict (optional)
        see :func:`chempy.units.get_derived_units`
    output_conc_unit : unit (Optional)
    output_time_unit : unit (Optional)
    \*\*kwargs :
        Keyword arguemnts pass on to `SymbolicSys`

    Returns
    -------
    pyodesys.symbolic.SymbolicSys
    param_keys
    unique_keys
    p_units

    Examples
    --------
    >>> from chempy import Equilibrium, ReactionSystem
    >>> eq = Equilibrium({'Fe+3', 'SCN-'}, {'FeSCN+2'}, 10**2)
    >>> substances = 'Fe+3 SCN- FeSCN+2'.split()
    >>> rsys = ReactionSystem(eq.as_reactions(kf=3.0), substances)
    >>> odesys = get_odesys(rsys)[0]
    >>> init_conc = {'Fe+3': 1.0, 'SCN-': .3, 'FeSCN+2': 0}
    >>> tout, Cout, info = odesys.integrate(5, init_conc)
    >>> Cout[-1, :].round(4)
    array([ 0.7042,  0.0042,  0.2958])

    """
    if SymbolicSys is None:
        from pyodesys.symbolic import SymbolicSys

    substance_keys = list(rsys.substances.keys())

    if 'names' not in kwargs:
        kwargs['names'] = list(rsys.substances.values())  # pyodesys>=0.5.3

    r_exprs = [rxn.rate_expr() for rxn in rsys.rxns]

    _original_param_keys = set.union(*(set(ratex.parameter_keys) for ratex in r_exprs))
    _from_subst = set()
    _active_subst = {}
    _passive_subst = {}
    substitutions = substitutions or {}
    for key, v in substitutions.items():
        if key not in _original_param_keys:
            raise ValueError("Substitution: '%s' does not appear in any rate expressions.")
        if isinstance(v, Expr):
            _from_subst.update(v.parameter_keys)
            _active_subst[key] = v
        else:
            _passive_subst[key] = v
    param_keys = list(filter(lambda x: x not in substitutions, _original_param_keys.union(_from_subst)))

    unique_keys = []
    p_defaults = []
    if not include_params:
        for ratex in r_exprs:
            if ratex.unique_keys is not None:
                unique_keys.extend(ratex.unique_keys)
                p_defaults.extend(ratex.args)
    if unit_registry is None:
        def pre_processor(x, y, p):
            return (
                x,
                rsys.as_per_substance_array(y),
                [p[k] for k in param_keys] + [p[k] for k in unique_keys]
            )

        def post_processor(x, y, p):
            return (
                x,
                y,  # dict(zip(substance_keys, y)),
                dict(zip(param_keys+unique_keys, p))
            )
        p_units = [None]*(len(param_keys) + len(unique_keys))
    else:
        # We need to make rsys_params unitless and create
        # a pre- & post-processor for SymbolicSys
        p_units = [get_derived_unit(unit_registry, k) for k in param_keys]
        new_r_exprs = []
        for ratex in r_exprs:
            _pu, _new_rate = ratex._recursive_as_RateExpr().dedimensionalisation(unit_registry)
            p_units.extend(_pu)
            new_r_exprs.append(_new_rate)
        r_exprs = new_r_exprs

        time_unit = get_derived_unit(unit_registry, 'time')
        conc_unit = get_derived_unit(unit_registry, 'concentration')

        def pre_processor(x, y, p):
            return (
                to_unitless(x, time_unit),
                rsys.as_per_substance_array(to_unitless(y, conc_unit)),
                [to_unitless(p[k], p_unit) for k, p_unit in zip(chain(param_keys, unique_keys), p_units)]
            )

        def post_processor(x, y, p):
            time = x*time_unit
            if output_time_unit is not None:
                time = time.rescale(output_time_unit)
            conc = y*conc_unit
            if output_conc_unit is not None:
                conc = conc.rescale(output_conc_unit)
            return time, conc, [elem*p_unit for elem, p_unit in zip(p, p_units)]

    kwargs['pre_processors'] = [pre_processor] + kwargs.get('pre_processors', [])
    kwargs['post_processors'] = kwargs.get('post_processors', []) + [post_processor]

    def dydt(t, y, p, backend=math):
        variables = dict(chain(
            zip(substance_keys, y),
            zip(param_keys, p[:len(param_keys)]),
            zip(unique_keys, p[len(param_keys):])
        ))
        for k, act in _active_subst.items():
            if unit_registry is not None:
                _, act = act.dedimensionalisation(unit_registry)
            variables[k] = act(variables, backend=backend)
        variables.update(_passive_subst)
        return dCdt(rsys, [rat(variables, backend=backend) for rat in r_exprs])

    return SymbolicSys.from_callback(
        dydt, len(substance_keys),
        len(param_keys) + (0 if include_params else len(unique_keys)),
        **kwargs), param_keys, unique_keys, p_units