def main(y0='1,0', mu=1.0, tend=10., nt=50, savefig='None', plot=False, savetxt='None', integrator='scipy', dpi=100, kwargs='', verbose=False): assert nt > 1 y = sp.symarray('y', 2) p = sp.Symbol('p', real=True) f = [y[1], -y[0] + p * y[1] * (1 - y[0]**2)] odesys = SymbolicSys(zip(y, f), params=[p], names=True) tout = np.linspace(0, tend, nt) y0 = list(map(float, y0.split(','))) kwargs = dict(eval(kwargs) if kwargs else {}) xout, yout, info = odesys.integrate(tout, y0, [mu], integrator=integrator, **kwargs) if verbose: print(info) if savetxt != 'None': np.savetxt(stack_1d_on_left(xout, yout), savetxt) if plot: import matplotlib.pyplot as plt odesys.plot_result() plt.legend() if savefig != 'None': plt.savefig(savefig, dpi=dpi) else: plt.show()
def main(y0='1,0', mu=1.0, tend=10., nt=50, savefig='None', plot=False, savetxt='None', integrator='scipy', dpi=100, kwargs='', verbose=False): assert nt > 1 y = sp.symarray('y', 2) p = sp.Symbol('p', real=True) f = [y[1], -y[0] + p*y[1]*(1 - y[0]**2)] odesys = SymbolicSys(zip(y, f), params=[p], names=True) tout = np.linspace(0, tend, nt) y0 = list(map(float, y0.split(','))) kwargs = dict(eval(kwargs) if kwargs else {}) xout, yout, info = odesys.integrate( tout, y0, [mu], integrator=integrator, **kwargs) if verbose: print(info) if savetxt != 'None': np.savetxt(stack_1d_on_left(xout, yout), savetxt) if plot: import matplotlib.pyplot as plt odesys.plot_result() plt.legend() if savefig != 'None': plt.savefig(savefig, dpi=dpi) else: plt.show()
def update_dynamics(self): eqs = dict(self.rhs()) keys = set(eqs.keys()) symvars = [getattr(self, v) for v in self.vars] if keys <= set(self.nodes): # by node dep = flatten(zip(*symvars)) expr = flatten(sym.Matrix([eqs[node] for node in self])) elif keys <= set(self.vars): # by variable dep = it.chain.from_iterable(symvars) expr = flatten(sym.Matrix([eqs[v] for v in self.vars])) else: raise ValueError( "rhs must map either nodes to rhs or variables to rhs") dep_expr = [(d, e + Zero()) for d, e in zip(dep, expr)] if any(expr == sym.nan for _, expr in dep_expr): raise ValueError( "At least one rhs expression is NaN. Missing parameters?") self._sys = SymbolicSys(dep_expr, self.t) if self.use_native: self._native_sys = native_sys[self.integrator].from_other( self._sys) self._stale_dynamics = False
def numerical_solver(left, right, y0, params=[], params_value=[], tstart=0., tend=10., nt=100, plot=False, savetxt='None', savefig='None', integrator='scipy', dpi=100, kwargs='', verbose=False): ''' Пояснение: y0 - начальные значения для переменных в виде строки 'X0,X1, ..., Xn' tstart, tend, nt - начало, конец и количество шагов по времени соответственно ''' ''' Zip группирует в соответствии левую и правую часть системы x, y = 'x', 'y' f = ['x**2 - y', 'y-y**3+x'] list(zip([x,y], f)) -> [('x', 'x**2 - y'), ('y', 'y-y**3+x')] ''' odesys = SymbolicSys(zip(left, right), params=params, names=True) ''' Создаем точки по t''' tout = np.linspace(tstart, tend, nt) ''' Преобразуем начальные условия ''' y0 = list(map(float, y0.split(','))) '''kwargs пока не нужен''' kwargs = dict(eval(kwargs) if kwargs else {}) ''' Интегрируем ''' xout, yout, info = odesys.integrate(tout, y0, params_value, integrator=integrator, **kwargs) return xout, yout, info if verbose: print(info) if savetxt != 'None': # stack_1d_on_left(xout, yout) -> [[t_0, x1_0, x2_0, x3_0], ... , [t_n, x1_n, x2_n, x3_n]] np.savetxt(savetxt, stack_1d_on_left(xout, yout)) if plot: odesys.plot_result() plt.legend() plt.figure(figsize=(20, 10)) if savefig != 'None': plt.savefig(savefig, dpi=dpi) else: plt.show()
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)
def main(m=1, g=9.81, l=1, q1=.1, q2=.2, u1=0, u2=0, tend=10., nt=200, savefig='None', plot=False, savetxt='None', integrator='scipy', dpi=100, kwargs="", verbose=False): assert nt > 1 kwargs = dict(eval(kwargs) if kwargs else {}) odesys = SymbolicSys(get_equations(m, g, l)) tout = np.linspace(0, tend, nt) y0 = [q1, q2, u1, u2] xout, yout, info = odesys.integrate( tout, y0, integrator=integrator, **kwargs) if verbose: print(info) if savetxt != 'None': np.savetxt(stack_1d_on_left(xout, yout), savetxt) if plot: import matplotlib.pyplot as plt odesys.plot_result(xout, yout) if savefig != 'None': plt.savefig(savefig, dpi=dpi) else: plt.show()
def main(m=1, g=9.81, l=1, q1=.1, q2=.2, u1=0, u2=0, tend=10., nt=200, savefig='None', plot=False, savetxt='None', integrator='scipy', dpi=100, kwargs="", verbose=False): assert nt > 1 kwargs = dict(eval(kwargs) if kwargs else {}) odesys = SymbolicSys(get_equations(m, g, l)) tout = np.linspace(0, tend, nt) y0 = [q1, q2, u1, u2] xout, yout, info = odesys.integrate(tout, y0, integrator=integrator, **kwargs) if verbose: print(info) if savetxt != 'None': np.savetxt(stack_1d_on_left(xout, yout), savetxt) if plot: import matplotlib.pyplot as plt odesys.plot_result(xout, yout) if savefig != 'None': plt.savefig(savefig, dpi=dpi) else: plt.show()
class TimeDoublePendulum: def setup(self): self.odesys = SymbolicSys(_get_equations(1, 9.81, 1)) self.tout = np.linspace(0, 10., 200) self.y0 = [.1, .2, 0, 0] def time_integrate_scipy(self): self.odesys.integrate(self.tout, self.y0, integrator='scipy') def time_integrate_gsl(self): self.odesys.integrate(self.tout, self.y0, integrator='gsl') def time_integrate_odeint(self): self.odesys.integrate(self.tout, self.y0, integrator='odeint') def time_integrate_cvode(self): self.odesys.integrate(self.tout, self.y0, integrator='cvode')
def solve(self, initial_conditions, parameters): """ :param initial_conditions: :param parameters: :return: """ if not isinstance(initial_conditions, dict): raise TypeError('Input must be dict. Got "{}"'.format( type(initial_conditions))) if not isinstance(parameters, dict): raise TypeError('Input must be dict. Got "{}"'.format( type(parameters))) node_names = [i for i in self.ode.keys()] ode_list = [i for i in self.ode.values()] print(ode_list) NotImplementedError('Now make our new ODEs compatible with jitcode') SymbolicSys(ode_list)
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
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 }
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
def setup(self): self.odesys = SymbolicSys(_get_equations(1, 9.81, 1)) self.tout = np.linspace(0, 10., 200) self.y0 = [.1, .2, 0, 0]
def _test_render_native_code_cse(NativeSys): # regression test taken from chempy from pyodesys.symbolic import SymbolicSys from sympy import symbols, log, exp import numpy as np symbs = symbols( 'N U A L NL t T He_dis Se_dis Cp_dis Tref_dis ' 'He_u Se_u Cp_u Tref_u Ha_agg Sa_agg Ha_as Sa_as Ha_f Sa_f ' 'R h k_B') di = {s.name: s for s in symbs} class NS: pass ns = NS() ns.__dict__.update(di) def _gibbs(H, S, Cp, Tref): H2 = H + Cp * (ns.T - Tref) S2 = S + Cp * log(ns.T / Tref) return exp(-(H2 - ns.T * S2) / (ns.R * ns.T)) def _eyring(H, S): return ns.k_B / ns.h * ns.T * exp(-(H - ns.T * S) / (ns.R * ns.T)) k_agg = _eyring(di['Ha_agg'], di['Sa_agg']) k_as = _eyring(di['Ha_as'], di['Sa_as']) k_f = _eyring(di['Ha_f'], di['Sa_f']) k_dis = k_as * _gibbs( *[di[k] for k in ('He_dis', 'Se_dis', 'Cp_dis', 'Tref_dis')]) k_u = k_f * _gibbs(*[di[k] for k in ('He_u', 'Se_u', 'Cp_u', 'Tref_u')]) r_agg = k_agg * ns.U r_as = k_as * ns.N * ns.L r_f = k_f * ns.U r_dis = k_dis * ns.NL r_u = k_u * ns.N exprs = [ -r_as + r_f + r_dis - r_u, -r_agg - r_f + r_u, r_agg, r_dis - r_as, r_as - r_dis ] def _solve(odesys, **kwargs): default_c0 = defaultdict(float, {'N': 1e-9, 'L': 1e-8}) params = dict( R=8.314472, # or N_A & k_B k_B=1.3806504e-23, h=6.62606896e-34, # k_B/h == 2.083664399411865e10 K**-1 * s**-1 He_dis=-45e3, Se_dis=-400, Cp_dis=1.78e3, Tref_dis=298.15, He_u=60e3, Se_u=130.5683, Cp_u=20.5e3, Tref_u=298.15, Ha_agg=106e3, Sa_agg=70, Ha_as=4e3, Sa_as=-10, Ha_f=90e3, Sa_f=50, T=50 + 273.15) return odesys.integrate(3600 * 24, [default_c0[s.name] for s in symbs[:5]], [params[s.name] for s in symbs[6:]], **kwargs) symbolic = SymbolicSys(zip(symbs[:5], exprs), symbs[5], params=symbs[6:]) kw = dict(integrator='cvode', nsteps=35000, atol=1e-11, rtol=1e-11) ref = _solve(symbolic, **kw) assert ref.info['success'] native = NativeSys.from_other( symbolic) # <-- regression test, optional: save_temp=True sol = _solve(native, **kw) assert sol.info['success'] assert np.allclose(sol.yout[-1, :], ref.yout[-1, :])
Derivative(alpha, t): eq['dot(alpha)'], alpha: eq['alpha']}).\ doit() eq['dot(theta)'][i] = eq['dot(theta)'][i].\ subs({nu[1]: eq['nu1'], nu[2]: eq['nu2'], Derivative(alpha, t): eq['dot(alpha)'], alpha: eq['alpha']}).\ doit() psi0, psi1, psi2, theta0, theta1, theta2, x, y, alpha = symbols('psi_0, psi_1, psi_2, theta_0, theta_1, theta_2, x, y, alpha') for i in range(3): eq['dot(psi)'][i] = subs_symbols(eq['dot(psi)'][i]) eq['dot(theta)'][i] = subs_symbols(eq['dot(theta)'][i]) left = [psi0, psi1, psi2, theta0, theta1, theta2] right = [eq['dot(psi)'][0], eq['dot(psi)'][1], eq['dot(psi)'][2], eq['dot(theta)'][0], eq['dot(theta)'][1], eq['dot(theta)'][2]] print(right) print('Количество совпадает:', len(left) == len(right)) odesys = SymbolicSys(list(zip(left, right)), t, [p, r, d]) xout, yout, info = odesys.integrate(t_end, initial_conditions, params, integrator='gsl', method='rk8pd', atol=1e-11, rtol=1e-12) return xout, yout, info, left, right
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
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, }
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 }
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)
class Dynamical(Parametrized, metaclass=DynamicalMeta, vars=None): graph = GraphAttrDict() _node = NodeDict() _adj = AdjlistOuterDict() _pred = AdjlistOuterDict() def __init__(self, *args, integrator=None, use_native=False, **kwargs): super().__init__(*args, **kwargs) self.use_native = use_native self.integrator = integrator self._sys = None self._stale_dynamics = True self._native_sys = None def expire_dynamics(self): self._stale_dynamics = True @abc.abstractmethod def rhs(self): pass @property def t(self): return sym.Symbol('t') @property def vars(self): return self._vars @property @uses_dynamics def sys(self): return self._sys @property @uses_dynamics def native_sys(self): return self._native_sys @property def stale_dynamics(self): return self._stale_dynamics @uses_dynamics def f(self, t, y): sys = self.native_sys if self.use_native else self.sys return sys.f_cb(t, y) @uses_dynamics def jac(self, t, y): sys = self.native_sys if self.use_native else self.sys return sys.j_cb(t, y) @uses_dynamics def jtimes(self, t, y, v): sys = self.native_sys if self.use_native else self.sys return sys.jtimes_cb(t, y, v) def update_dynamics(self): eqs = dict(self.rhs()) keys = set(eqs.keys()) symvars = [getattr(self, v) for v in self.vars] if keys <= set(self.nodes): # by node dep = flatten(zip(*symvars)) expr = flatten(sym.Matrix([eqs[node] for node in self])) elif keys <= set(self.vars): # by variable dep = it.chain.from_iterable(symvars) expr = flatten(sym.Matrix([eqs[v] for v in self.vars])) else: raise ValueError( "rhs must map either nodes to rhs or variables to rhs") dep_expr = [(d, e + Zero()) for d, e in zip(dep, expr)] if any(expr == sym.nan for _, expr in dep_expr): raise ValueError( "At least one rhs expression is NaN. Missing parameters?") self._sys = SymbolicSys(dep_expr, self.t) if self.use_native: self._native_sys = native_sys[self.integrator].from_other( self._sys) self._stale_dynamics = False @uses_dynamics def integrate(self, *args, **kwargs): if self.use_native: return self._native_sys.integrate(*args, **kwargs) else: kw = dict(integrator=self.integrator) kw.update(kwargs) return self._sys.integrate(*args, **kw)