def test_get_odesys__Eyring(): R = 8.314472 T_K = 300 dH = 80e3 dS = 10 rsys1 = ReactionSystem.from_string(""" NOBr -> NO + Br; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format(dH=dH, dS=dS), substances='NOBr NO Br'.split()) kref = 20836643994.118652*T_K*np.exp(-(dH - T_K*dS)/(R*T_K)) NOBr0_M = 0.7 init_cond = dict( NOBr=NOBr0_M*u.M, NO=0*u.M, Br=0*u.M ) t = 5*u.second params = dict( temperature=T_K*u.K ) def check(rsys): odesys, extra = get_odesys(rsys, unit_registry=SI_base_registry, constants=const) res = odesys.integrate(t, init_cond, params, integrator='cvode') NOBr_ref = NOBr0_M*np.exp(-kref*to_unitless(res.xout, u.second)) ref = np.array([NOBr_ref] + [NOBr0_M - NOBr_ref]*2).T cmp = to_unitless(res.yout, u.M) assert np.allclose(cmp, ref) check(rsys1) rsys2 = ReactionSystem.from_string(""" NOBr -> NO + Br; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format(dH=dH, dS=dS), substances='NOBr NO Br'.split()) check(rsys2)
def test_get_odesys__Eyring_2nd_order_reversible(): R = 8.314472 T_K = 273.15 + 20 # 20 degree celsius kB = 1.3806504e-23 h = 6.62606896e-34 dHf = 74e3 dSf = R*np.log(h/kB/T_K*1e16) dHb = 79e3 dSb = dSf - 23 rsys1 = ReactionSystem.from_string(""" Fe+3 + SCN- -> FeSCN+2; EyringParam(dH={dHf}*J/mol, dS={dSf}*J/K/mol) FeSCN+2 -> Fe+3 + SCN-; EyringParam(dH={dHb}*J/mol, dS={dSb}*J/K/mol) """.format(dHf=dHf, dSf=dSf, dHb=dHb, dSb=dSb)) kf_ref = 20836643994.118652*T_K*np.exp(-(dHf - T_K*dSf)/(R*T_K)) kb_ref = 20836643994.118652*T_K*np.exp(-(dHb - T_K*dSb)/(R*T_K)) Fe0 = 6e-3 SCN0 = 2e-3 init_cond = { 'Fe+3': Fe0*u.M, 'SCN-': SCN0*u.M, 'FeSCN+2': 0*u.M } t = 3*u.second def check(rsys, params): odes, extra = get_odesys(rsys, include_params=False, unit_registry=SI_base_registry, constants=const) for odesys in [odes, odes.as_autonomous()]: res = odesys.integrate(t, init_cond, params, integrator='cvode') t_sec = to_unitless(res.xout, u.second) FeSCN_ref = binary_rev(t_sec, kf_ref, kb_ref, 0, Fe0, SCN0) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index('FeSCN+2')] = FeSCN_ref ref[:, odesys.names.index('Fe+3')] = Fe0 - FeSCN_ref ref[:, odesys.names.index('SCN-')] = SCN0 - FeSCN_ref assert np.allclose(cmp, ref) check(rsys1, {'temperature': T_K*u.K}) rsys2 = ReactionSystem.from_string(""" Fe+3 + SCN- -> FeSCN+2; MassAction(EyringHS([{dHf}*J/mol, {dSf}*J/K/mol])) FeSCN+2 -> Fe+3 + SCN-; MassAction(EyringHS([{dHb}*J/mol, {dSb}*J/K/mol])) """.format(dHf=dHf, dSf=dSf, dHb=dHb, dSb=dSb)) check(rsys2, {'temperature': T_K*u.K}) rsys3 = ReactionSystem.from_string(""" Fe+3 + SCN- -> FeSCN+2; MassAction(EyringHS.fk('dHf', 'dSf')) FeSCN+2 -> Fe+3 + SCN-; MassAction(EyringHS.fk('dHb', 'dSb')) """) check(rsys3, dict(temperature=T_K*u.K, dHf=dHf*u.J/u.mol, dSf=dSf*u.J/u.mol/u.K, dHb=dHb*u.J/u.mol, dSb=dSb*u.J/u.mol/u.K))
def test_get_odesys__Eyring_1st_order_linearly_ramped_temperature(): from scipy.special import expi def analytic_unit0(t, T0, dH, dS): R = 8.314472 kB = 1.3806504e-23 h = 6.62606896e-34 A = kB/h*np.exp(dS/R) B = dH/R return np.exp(A*( (-B**2*np.exp(B/T0)*expi(-B/T0) - T0*(B - T0))*np.exp(-B/T0) + (B**2*np.exp(B/(t + T0))*expi(-B/(t + T0)) - (t + T0)*(-B + t + T0))*np.exp(-B/(t + T0)) )/2) T_K = 290 dH = 80e3 dS = 10 rsys1 = ReactionSystem.from_string(""" NOBr -> NO + Br; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format(dH=dH, dS=dS)) NOBr0_M = 0.7 init_cond = dict( NOBr=NOBr0_M*u.M, NO=0*u.M, Br=0*u.M ) t = 20*u.second def check(rsys): odes, extra = get_odesys(rsys, unit_registry=SI_base_registry, constants=const, substitutions={ 'temperature': RampedTemp([T_K*u.K, 1*u.K/u.s])}) for odesys in [odes, odes.as_autonomous()]: res = odesys.integrate(t, init_cond, integrator='cvode') t_sec = to_unitless(res.xout, u.second) NOBr_ref = NOBr0_M*analytic_unit0(t_sec, T_K, dH, dS) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index('NOBr')] = NOBr_ref ref[:, odesys.names.index('Br')] = NOBr0_M - NOBr_ref ref[:, odesys.names.index('NO')] = NOBr0_M - NOBr_ref assert np.allclose(cmp, ref) check(rsys1) rsys2 = ReactionSystem.from_string(""" NOBr -> NO + Br; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format(dH=dH, dS=dS)) check(rsys2)
def test_get_odesys__linear_dependencies__preferred(substances): rsys = ReactionSystem.from_string( "\n".join(["H2O -> H+ + OH-; 1e-4", "OH- + H+ -> H2O; 1e10"]), substances ) assert isinstance(rsys.substances, OrderedDict) odesys, extra = get_odesys(rsys) af_H2O_H = extra["linear_dependencies"](["H+", "H2O"]) import sympy y0 = {k: sympy.Symbol(k + "0") for k in rsys.substances} af_H2O_H( None, {odesys[k]: v for k, v in y0.items()}, None, sympy ) # ensure idempotent exprs_H2O_H = af_H2O_H(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) ref_H2O_H = { "H2O": y0["H2O"] + y0["OH-"] - odesys["OH-"], # oxygen "H+": 2 * y0["H2O"] + y0["H+"] + y0["OH-"] - odesys["OH-"] - 2 * (y0["H2O"] + y0["OH-"] - odesys["OH-"]), # hydrogen } for k, v in ref_H2O_H.items(): assert (exprs_H2O_H[odesys[k]] - v) == 0
def test_create_odesys(): rsys = ReactionSystem.from_string(""" A -> B; 'k1' B + C -> P; 'k2' """, substance_factory=Substance) odesys, odesys_extra = create_odesys(rsys, unit_registry=SI_base_registry) tend = 10 c0 = {'A': 1e-6, 'B': 0, 'C': 1, 'P': 0} p = dict(k1=3, k2=4) tend_units = tend*u.s p_units = {'k1': p['k1']/u.s, 'k2': p['k2']/u.M/u.s} c0_units = {k: v*u.molar for k, v in c0.items()} validation = odesys_extra['validate'](dict(c0_units, **p_units)) P, = validation['not_seen'] assert P.name == 'P' ref_rates = {'A': -p_units['k1']*c0_units['A'], 'P': p_units['k2']*c0_units['B']*c0_units['C']} ref_rates['B'] = -ref_rates['A'] - ref_rates['P'] ref_rates['C'] = -ref_rates['P'] assert validation['rates'] == ref_rates result1, result1_extra = odesys_extra['unit_aware_solve']( tend_units, c0_units, p_units, integrator='cvode') assert result1.info['success'] result2 = odesys.integrate(tend, c0, p, integrator='cvode') assert np.allclose(result2.yout[-1, :], to_unitless(result1.yout[-1, :], u.molar))
def test_get_native__first_step(solve): integrator = 'cvode' forgive = 20 def k(num): return "MassAction(unique_keys=('k%d',))" % num lines = [ # fictitious isomerization "CNO -> ONC; %s" % k(1), "ONC -> NCO; %s" % k(2), "NCO -> CON; %s" % k(3) ] rsys = ReactionSystem.from_string('\n'.join(lines), 'CNO ONC NCO CON') odesys, extra = get_odesys(rsys, include_params=False) if len(solve) > 0: from pyodesys.symbolic import PartiallySolvedSystem odesys = PartiallySolvedSystem(odesys, extra['linear_dependencies'](solve)) c0 = defaultdict(float, {'CNO': .7}) rate_coeffs = (1e78, 2, 3.) args = (5, c0, dict(zip('k1 k2 k3'.split(), rate_coeffs))) kwargs = dict(integrator=integrator, atol=1e-9, rtol=1e-9, nsteps=1000) native = get_native(rsys, odesys, integrator) h0 = extra['max_euler_step_cb'](0, *args[1:]) xout1, yout1, info1 = odesys.integrate(*args, first_step=h0, **kwargs) xout2, yout2, info2 = native.integrate(*args, **kwargs) ref1 = decay_get_Cref(rate_coeffs, [c0[key] for key in native.names], xout1) ref2 = decay_get_Cref(rate_coeffs, [c0[key] for key in native.names], xout2) allclose_kw = dict(atol=kwargs['atol']*forgive, rtol=kwargs['rtol']*forgive) assert np.allclose(yout1[:, :3], ref1, **allclose_kw) assert info2['success'] assert info2['nfev'] > 10 and info2['nfev'] > 1 and info2['time_cpu'] < 10 and info2['time_wall'] < 10 assert np.allclose(yout2[:, :3], ref2, **allclose_kw)
def test_get_odesys__linear_dependencies__PartiallySolvedSystem(preferred): import sympy from pyodesys.symbolic import PartiallySolvedSystem rsys = ReactionSystem.from_string('\n'.join( ['H2O -> H+ + OH-; 1e-4', 'OH- + H+ -> H2O; 1e10'])) odesys, extra = get_odesys(rsys) c0 = {'H2O': 0, 'H+': 2e-7, 'OH-': 3e-7} h0max = extra['max_euler_step_cb'](0, c0) analytic_factory = extra['linear_dependencies']() y0 = {k: sympy.Symbol(k + '0') for k in rsys.substances} analytic_factory(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) psys = PartiallySolvedSystem(odesys, analytic_factory) xout, yout, info = psys.integrate(1, c0, atol=1e-12, rtol=1e-10, first_step=h0max * 1e-12, integrator='cvode') c_reac = c0['H+'], c0['OH-'] H2O_ref = binary_rev(xout, 1e10, 1e-4, c0['H2O'], max(c_reac), min(c_reac)) assert np.allclose(yout[:, psys.names.index('H2O')], H2O_ref) assert np.allclose(yout[:, psys.names.index('H+')], c0['H+'] + c0['H2O'] - H2O_ref) assert np.allclose(yout[:, psys.names.index('OH-')], c0['OH-'] + c0['H2O'] - H2O_ref)
def test_get_native__conc_roots(): M, s = u.M, u.s rsys = ReactionSystem.from_string("2 O3 -> 3 O2; 'k2'") u_reg = SI_base_registry.copy() odesys, extra = get_odesys(rsys, include_params=False, unit_registry=u_reg) c0 = {"O3": 4.2e-3 * M, "O2": 0 * M} cr = ["O2"] native = get_native(rsys, odesys, "cvode", conc_roots=cr) tend = 1e5 * u.s params = {"k2": logspace_from_lin(1e-3 / M / s, 1e3 / M / s, 14)} tgt_O2 = 1e-3 * M results = native.integrate( tend, c0, params, integrator="native", return_on_root=True, special_settings=[unitless_in_registry(tgt_O2, u_reg)], ) assert len(results) == params["k2"].size # dydt = -p*y**2 # 1/y0 - 1/y = -2*pt # t = 1/2/p*(1/y - 1/y0) tgt_O3 = c0["O3"] - 2 / 3 * tgt_O2 for r in results: ref = rescale( 1 / 2 / r.named_param("k2") * (1 / tgt_O3 - 1 / c0["O3"]), u.s) assert allclose(r.xout[-1], ref, rtol=1e-6)
def test_get_odesys__Eyring_2nd_order_linearly_ramped_temperature(): from scipy.special import expi def analytic_unit0(t, k, m, dH, dS): R = 8.314472 kB = 1.3806504e-23 h = 6.62606896e-34 A = kB/h*np.exp(dS/R) B = dH/R return k*np.exp(B*(k*t + 2*m)/(m*(k*t + m)))/( A*(-B**2*np.exp(B/(k*t + m))*expi(-B/(k*t + m)) - B*k*t - B*m + k**2*t**2 + 2*k*m*t + m**2)*np.exp(B/m) + (A*B**2*np.exp(B/m)*expi(-B/m) - A*m*(-B + m) + k*np.exp(B/m))*np.exp(B/(k*t + m)) ) T_K = 290 dTdt_Ks = 3 dH = 80e3 dS = 10 rsys1 = ReactionSystem.from_string(""" 2 NO2 -> N2O4; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format(dH=dH, dS=dS)) NO2_M = 1.0 init_cond = dict( NO2=NO2_M*u.M, N2O4=0*u.M ) t = 20*u.second def check(rsys): odes, extra = get_odesys(rsys, unit_registry=SI_base_registry, constants=const, substitutions={ 'temperature': RampedTemp([T_K*u.K, dTdt_Ks*u.K/u.s])}) for odesys in [odes, odes.as_autonomous()]: res = odesys.integrate(t, init_cond, integrator='cvode') t_sec = to_unitless(res.xout, u.second) NO2_ref = analytic_unit0(t_sec, dTdt_Ks, T_K, dH, dS) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index('NO2')] = NO2_ref ref[:, odesys.names.index('N2O4')] = (NO2_M - NO2_ref)/2 assert np.allclose(cmp, ref) check(rsys1) rsys2 = ReactionSystem.from_string(""" 2 NO2 -> N2O4; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format(dH=dH, dS=dS)) check(rsys2)
def test_get_odesys__Eyring_2nd_order_linearly_ramped_temperature(): from scipy.special import expi def analytic_unit0(t, k, m, dH, dS): R = 8.314472 kB = 1.3806504e-23 h = 6.62606896e-34 A = kB/h*np.exp(dS/R) B = dH/R return k*np.exp(B*(k*t + 2*m)/(m*(k*t + m)))/( A*(-B**2*np.exp(B/(k*t + m))*expi(-B/(k*t + m)) - B*k*t - B*m + k**2*t**2 + 2*k*m*t + m**2)*np.exp(B/m) + (A*B**2*np.exp(B/m)*expi(-B/m) - A*m*(-B + m) + k*np.exp(B/m))*np.exp(B/(k*t + m)) ) T_K = 290 dTdt_Ks = 3 dH = 80e3 dS = 10 rsys1 = ReactionSystem.from_string(""" 2 NO2 -> N2O4; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format(dH=dH, dS=dS)) NO2_M = 1.0 init_cond = dict( NO2=NO2_M*u.M, N2O4=0*u.M ) t = 20*u.second def check(rsys): odes, extra = get_odesys(rsys, unit_registry=SI_base_registry, constants=const, substitutions={ 'temperature': RampedTemp([T_K*u.K, dTdt_Ks*u.K/u.s])}) for odesys in [odes, odes.as_autonomous()]: res = odesys.integrate(t, init_cond, integrator='cvode') t_sec = to_unitless(res.xout, u.second) NO2_ref = analytic_unit0(t_sec, dTdt_Ks, T_K, dH, dS) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index('NO2')] = NO2_ref ref[:, odesys.names.index('N2O4')] = (NO2_M - NO2_ref)/2 assert np.allclose(cmp, ref) check(rsys1) rsys2 = ReactionSystem.from_string(""" 2 NO2 -> N2O4; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format(dH=dH, dS=dS)) check(rsys2)
def test_get_odesys__max_euler_step_cb(): rsys = ReactionSystem.from_string('\n'.join(['H2O -> H+ + OH-; 1e-4', 'OH- + H+ -> H2O; 1e10'])) odesys, extra = get_odesys(rsys) r1 = 1.01e-4 r2 = 6e-4 dH2Odt = r2 - r1 euler_ref = 2e-7/dH2Odt assert abs(extra['max_euler_step_cb'](0, {'H2O': 1.01, 'H+': 2e-7, 'OH-': 3e-7}) - euler_ref)/euler_ref < 1e-8
def test_get_odesys__max_euler_step_cb(): rsys = ReactionSystem.from_string('\n'.join(['H2O -> H+ + OH-; 1e-4', 'OH- + H+ -> H2O; 1e10'])) odesys, extra = get_odesys(rsys) r1 = 1.01e-4 r2 = 6e-4 dH2Odt = r2 - r1 euler_ref = 2e-7/dH2Odt assert abs(extra['max_euler_step_cb'](0, {'H2O': 1.01, 'H+': 2e-7, 'OH-': 3e-7}) - euler_ref)/euler_ref < 1e-8
def test_get_native__named_parameter__units(dep_scaling): rsys = ReactionSystem.from_string( """ -> H; 'p' H + H -> H2; 'k2' """, checks=("substance_keys", "duplicate", "duplicate_names"), ) from pyodesys.symbolic import ScaledSys kwargs = ({} if dep_scaling == 1 else dict(SymbolicSys=ScaledSys, dep_scaling=dep_scaling)) odesys, extra = get_odesys(rsys, include_params=False, unit_registry=SI_base_registry, **kwargs) c0 = {"H": 42e-6 * u.molar, "H2": 17 * u.micromolar} native = get_native(rsys, odesys, "cvode") tend = 7 * 60 * u.minute g_rho_Ddot = g, rho, Ddot = 2 * u.per100eV, 998 * u.g / u.dm3, 314 * u.Gy / u.hour params = {"p": reduce(mul, g_rho_Ddot), "k2": 53 / u.molar / u.minute} result = native.integrate(tend, c0, params, atol=1e-15, rtol=1e-15, integrator="cvode", nsteps=16000) assert result.info["success"] def analytic_H(t, p, k, H0): # dH/dt = p - k2*H**2 x0 = np.sqrt(2) * np.sqrt(p) x1 = x0 x2 = np.sqrt(k) x3 = t * x1 * x2 x4 = H0 * x2 x5 = np.sqrt(x0 + 2 * x4) x6 = np.sqrt(-1 / (2 * H0 * x2 - x0)) x7 = x5 * x6 * np.exp(x3) x8 = np.exp(-x3) / (x5 * x6) return x1 * (x7 - x8) / (2 * x2 * (x7 + x8)) t_ul = to_unitless(result.xout, u.s) p_ul = to_unitless(params["p"], u.micromolar / u.s) ref_H_uM = analytic_H( t_ul, p_ul, to_unitless(params["k2"], 1 / u.micromolar / u.s), to_unitless(c0["H"], u.micromolar), ) ref_H2_uM = (to_unitless(c0["H2"], u.micromolar) + to_unitless(c0["H"], u.micromolar) / 2 + t_ul * p_ul / 2 - ref_H_uM / 2) assert np.allclose(to_unitless(result.named_dep("H"), u.micromolar), ref_H_uM) assert np.allclose(to_unitless(result.named_dep("H2"), u.micromolar), ref_H2_uM)
def test_get_native__Radiolytic__named_parameter__units(dep_scaling): rsys = ReactionSystem.from_string(""" -> H; Radiolytic(2*per100eV) H + H -> H2; 'k2' """, checks=('substance_keys', 'duplicate', 'duplicate_names')) gval = 2 * u.per100eV from pyodesys.symbolic import ScaledSys kwargs = {} if dep_scaling == 1 else dict(SymbolicSys=ScaledSys, dep_scaling=dep_scaling) odesys, extra = get_odesys(rsys, include_params=False, unit_registry=SI_base_registry, **kwargs) c0 = {'H': 42e-6 * u.molar, 'H2': 17e3 * u.nanomolar} native = get_native(rsys, odesys, 'cvode') tend = 7 * 60 * u.minute params = { 'doserate': 314 * u.Gy / u.hour, 'k2': 53 / u.molar / u.minute, 'density': 998 * u.g / u.dm3 } result = native.integrate(tend, c0, params, atol=1e-15, rtol=1e-15, integrator='cvode', nsteps=8000) assert result.info['success'] def analytic_H(t, p, k, H0): # dH/dt = p - k2*H**2 x0 = np.sqrt(2) * np.sqrt(p) x1 = x0 x2 = np.sqrt(k) x3 = t * x1 * x2 x4 = H0 * x2 x5 = np.sqrt(x0 + 2 * x4) x6 = np.sqrt(-1 / (2 * H0 * x2 - x0)) x7 = x5 * x6 * np.exp(x3) x8 = np.exp(-x3) / (x5 * x6) return x1 * (x7 - x8) / (2 * x2 * (x7 + x8)) t_ul = to_unitless(result.xout, u.s) p_ul = to_unitless(params['doserate'] * params['density'] * gval, u.micromolar / u.s) ref_H_uM = analytic_H(t_ul, p_ul, to_unitless(params['k2'], 1 / u.micromolar / u.s), to_unitless(c0['H'], u.micromolar)) ref_H2_uM = to_unitless(c0['H2'], u.micromolar) + to_unitless( c0['H'], u.micromolar) / 2 + t_ul * p_ul / 2 - ref_H_uM / 2 assert np.allclose(to_unitless(result.named_dep('H'), u.micromolar), ref_H_uM) assert np.allclose(to_unitless(result.named_dep('H2'), u.micromolar), ref_H2_uM)
def test_chained_parameter_variation_from_ReactionSystem(): g_E_mol_J = 2.1e-7 rsys = ReactionSystem.from_string( """ (H2O) -> e-(aq) + H+ + OH; Radiolytic(%.2e*mol/J) 2 OH -> H2O2; 3.6e9/M/s H+ + OH- -> H2O; 1.4e11/M/s H2O -> H+ + OH-; 1.4e-3/s N2O + e-(aq) -> N2 + O-; 9.6e9/M/s O- + H+ -> OH; 1e11/M/s """ % g_E_mol_J # neglecting a large body of reactions (just a test-case after all) ) ureg = SI_base_registry field_u = get_derived_unit(ureg, 'doserate') * get_derived_unit(ureg, 'density') rd = ReactionDiffusion.from_ReactionSystem(rsys, fields=[[0*field_u]], unit_registry=ureg, param_names=['doserate']) dens_kg_dm3 = 0.998 odesys = rd._as_odesys( variables_from_params=dict( density=lambda self, params: dens_kg_dm3*1e3*u.kg/u.m**3 ) ) npoints = 5 durations = [59*u.second, 42*u.minute, 2*u.hour] doserates = [135*u.Gy/u.s, 11*u.Gy/u.s, 180*u.Gy/u.minute] M = u.molar ic = defaultdict(lambda: 0*M, {'H2O': 55.4*M, 'H+': 1e-7*M, 'OH-': 1e-7*M, 'N2O': 20e-3*M}) result = odesys.chained_parameter_variation(durations, ic, {'doserate': doserates}, npoints=npoints) ref_xout_s = [0] for dur in map(lambda dur: to_unitless(dur, u.s), durations): ref_xout_s += list(np.linspace(ref_xout_s[-1], ref_xout_s[-1] + dur, npoints+1)[1:]) assert allclose(result.xout, ref_xout_s*u.s) N2_M = to_unitless(result.named_dep('N2'), u.M) H2O2_M = to_unitless(result.named_dep('H2O2'), u.M) e_accum_molar = 0 for i, (dur, dr) in enumerate(zip(durations, doserates)): dur_s = to_unitless(dur, u.s) dr_Gy_s = to_unitless(dr, u.Gy/u.s) local_ts = np.linspace(0, dur_s, npoints+1) # local_ic = {k: result.named_dep(k)[i*npoints] for k in odesys.names} for j, (lt, ld) in enumerate(zip(local_ts[1:], np.diff(local_ts))): e_accum_molar += ld*g_E_mol_J*dr_Gy_s*dens_kg_dm3 assert abs(N2_M[i*npoints + j + 1] - e_accum_molar)/e_accum_molar < 1e-3 assert abs(H2O2_M[i*npoints + j + 1] - e_accum_molar)/e_accum_molar < 1e-3 res2 = odesys.integrate(durations[0], ic, {'doserate': doserates[0]}, integrator='cvode') dr2 = res2.params[res2.odesys.param_names.index('doserate')] assert np.asarray(res2.params).shape[-1] == len(odesys.param_names) assert allclose(dr2, doserates[0]) assert allclose(res2.xout[-1], durations[0]) assert allclose(res2.named_dep('N2')[-1], durations[0]*doserates[0]*g_E_mol_J*u.mol/u.J*dens_kg_dm3*u.kg/u.dm3) to_unitless(res2.xout, u.s) to_unitless(res2.yout, u.molar) to_unitless(dr2, u.Gy/u.s)
def test_get_odesys__Eyring_2nd_order(): R = 8.314472 T_K = 300 dH = 80e3 dS = 10 rsys1b = ReactionSystem.from_string(""" NO + Br -> NOBr; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format(dH=dH, dS=dS)) c0 = 1 # mol/dm3 === 1000 mol/m3 kbref = 20836643994.118652*T_K*np.exp(-(dH - T_K*dS)/(R*T_K))/c0 NO0_M = 1.5 Br0_M = 0.7 init_cond = dict( NOBr=0*u.M, NO=NO0_M*u.M, Br=Br0_M*u.M ) t = 5*u.second params = dict( temperature=T_K*u.K ) def analytic_b(t): U, V = NO0_M, Br0_M d = U - V return (U*(1 - np.exp(-kbref*t*d)))/(U/V - np.exp(-kbref*t*d)) def check(rsys): odesys, extra = get_odesys(rsys, unit_registry=SI_base_registry, constants=const) res = odesys.integrate(t, init_cond, params, integrator='cvode') t_sec = to_unitless(res.xout, u.second) NOBr_ref = analytic_b(t_sec) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index('NOBr')] = NOBr_ref ref[:, odesys.names.index('Br')] = Br0_M - NOBr_ref ref[:, odesys.names.index('NO')] = NO0_M - NOBr_ref assert np.allclose(cmp, ref) check(rsys1b) rsys2b = ReactionSystem.from_string(""" NO + Br -> NOBr; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format(dH=dH, dS=dS)) check(rsys2b)
def test_get_odesys__Eyring_2nd_order(): R = 8.314472 T_K = 300 dH = 80e3 dS = 10 rsys1b = ReactionSystem.from_string(""" NO + Br -> NOBr; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format(dH=dH, dS=dS)) c0 = 1 # mol/dm3 === 1000 mol/m3 kbref = 20836643994.118652*T_K*np.exp(-(dH - T_K*dS)/(R*T_K))/c0 NO0_M = 1.5 Br0_M = 0.7 init_cond = dict( NOBr=0*u.M, NO=NO0_M*u.M, Br=Br0_M*u.M ) t = 5*u.second params = dict( temperature=T_K*u.K ) def analytic_b(t): U, V = NO0_M, Br0_M d = U - V return (U*(1 - np.exp(-kbref*t*d)))/(U/V - np.exp(-kbref*t*d)) def check(rsys): odesys, extra = get_odesys(rsys, unit_registry=SI_base_registry, constants=const) res = odesys.integrate(t, init_cond, params, integrator='cvode') t_sec = to_unitless(res.xout, u.second) NOBr_ref = analytic_b(t_sec) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index('NOBr')] = NOBr_ref ref[:, odesys.names.index('Br')] = Br0_M - NOBr_ref ref[:, odesys.names.index('NO')] = NO0_M - NOBr_ref assert np.allclose(cmp, ref) check(rsys1b) rsys2b = ReactionSystem.from_string(""" NO + Br -> NOBr; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format(dH=dH, dS=dS)) check(rsys2b)
def test_from_ReactionSystem__g_values(): from chempy import ReactionSystem as RS rs = RS.from_string('-> H + OH; Radiolytic(2.1e-7)', checks=()) rd = ReactionDiffusion.from_ReactionSystem(rs, variables={'density': 998, 'doserate': 0.15}) gv = rd.g_values assert len(gv) == 1 assert np.allclose(gv[0], rs.as_per_substance_array({'H': 2.1e-7, 'OH': 2.1e-7})) assert len(rd.fields) == 1 assert len(rd.fields[0]) == 1 assert np.allclose(rd.fields[0][0], 998*0.15)
def test_create_odesys__Radiolytic(): rsys1 = ReactionSystem.from_string(""" -> e-(aq); Radiolytic.fk('g_emaq') """, checks=()) ic1 = {'e-(aq)': 0.0} t1 = 5 p1 = dict(g_emaq=42.0, doserate=17.0, density=5.0) odesys1, odesys_extra = create_odesys(rsys1) result1 = odesys1.integrate(t1, ic1, p1) yref1 = result1.xout * p1['g_emaq'] * p1['doserate'] * p1['density'] assert np.allclose(yref1, result1.yout.squeeze())
def test_get_odesys__Eyring(): R = 8.314472 T_K = 300 dH = 80e3 dS = 10 rsys1 = ReactionSystem.from_string( """ NOBr -> NO + Br; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format( dH=dH, dS=dS ), substances="NOBr NO Br".split(), ) kref = 20836643994.118652 * T_K * np.exp(-(dH - T_K * dS) / (R * T_K)) NOBr0_M = 0.7 init_cond = dict(NOBr=NOBr0_M * u.M, NO=0 * u.M, Br=0 * u.M) t = 5 * u.second params = dict(temperature=T_K * u.K) def check(rsys): odesys, extra = get_odesys( rsys, unit_registry=SI_base_registry, constants=const ) res = odesys.integrate(t, init_cond, params, integrator="cvode") NOBr_ref = NOBr0_M * np.exp(-kref * to_unitless(res.xout, u.second)) ref = np.array([NOBr_ref] + [NOBr0_M - NOBr_ref] * 2).T cmp = to_unitless(res.yout, u.M) assert np.allclose(cmp, ref) check(rsys1) rsys2 = ReactionSystem.from_string( """ NOBr -> NO + Br; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format( dH=dH, dS=dS ), substances="NOBr NO Br".split(), ) check(rsys2)
def test_get_native__a_substance_no_composition(solve): rsys = ReactionSystem.from_string('\n'.join(['H2O -> H2O+ + e-(aq); 1e-8', 'e-(aq) + H2O+ -> H2O; 1e10'])) odesys, extra = get_odesys(rsys) c0 = {'H2O': 0, 'H2O+': 2e-9, 'e-(aq)': 3e-9} if len(solve) > 0: from pyodesys.symbolic import PartiallySolvedSystem odesys = PartiallySolvedSystem(odesys, extra['linear_dependencies'](solve)) odesys = get_native(rsys, odesys, 'gsl') xout, yout, info = odesys.integrate(1, c0, atol=1e-15, rtol=1e-15, integrator='gsl') c_reac = c0['H2O+'], c0['e-(aq)'] H2O_ref = binary_rev(xout, 1e10, 1e-4, c0['H2O'], max(c_reac), min(c_reac)) assert np.allclose(yout[:, odesys.names.index('H2O')], H2O_ref) assert np.allclose(yout[:, odesys.names.index('H2O+')], c0['H2O+'] + c0['H2O'] - H2O_ref) assert np.allclose(yout[:, odesys.names.index('e-(aq)')], c0['e-(aq)'] + c0['H2O'] - H2O_ref)
def test_from_ReactionSystem__g_values__units(): from chempy import ReactionSystem as RS from chempy.units import SI_base_registry, default_units as u rs = RS.from_string('-> H + OH; Radiolytic(2.1*per100eV)', checks=()) variables = {'density': .998 * u.kg/u.dm3, 'doserate': 0.15*u.Gy/u.s} rd = ReactionDiffusion.from_ReactionSystem(rs, variables=variables, unit_registry=SI_base_registry) gv = rd.g_values per100eV_as_mol_per_joule = 1.0364268556366418e-07 ref = 2.1 * per100eV_as_mol_per_joule assert len(gv) == 1 assert np.allclose(gv[0], rs.as_per_substance_array({'H': ref, 'OH': ref})) assert len(rd.fields) == 1 assert len(rd.fields[0]) == 1 assert np.allclose(rd.fields[0][0], 998*0.15)
def test_create_odesys__Radiolytic(): rsys1 = ReactionSystem.from_string(""" -> e-(aq); Radiolytic.fk('g_emaq') """, checks=()) ic1 = {'e-(aq)': 0.0} t1 = 5 p1 = dict( g_emaq=42.0, doserate=17.0, density=5.0 ) odesys1, odesys_extra = create_odesys(rsys1) result1 = odesys1.integrate(t1, ic1, p1) yref1 = result1.xout*p1['g_emaq']*p1['doserate']*p1['density'] assert np.allclose(yref1, result1.yout.squeeze())
def test_get_odesys_rsys_with_units__named_params(): rsys = ReactionSystem.from_string(""" A -> B; 'k1' B + C -> P; 'k2' """, substance_factory=Substance) odesys, extra = get_odesys(rsys, include_params=False, unit_registry=SI_base_registry) tend = 10 tend_units = tend*u.s c0 = {'A': 1e-6, 'B': 0, 'C': 1, 'P': 0} p = {'k1': 3, 'k2': 4} p_units = {'k1': 3/u.s, 'k2': 4/u.M/u.s} c0_units = {k: v*u.molar for k, v in c0.items()} result1 = odesys.integrate(tend_units, c0_units, p_units, integrator='odeint') assert result1.info['success'] with pytest.raises(Exception): odesys.integrate(tend, c0, p, integrator='odeint')
def test_get_odesys__linear_dependencies__preferred(substances): rsys = ReactionSystem.from_string('\n'.join(['H2O -> H+ + OH-; 1e-4', 'OH- + H+ -> H2O; 1e10']), substances) assert isinstance(rsys.substances, OrderedDict) odesys, extra = get_odesys(rsys) af_H2O_H = extra['linear_dependencies'](['H+', 'H2O']) import sympy y0 = {k: sympy.Symbol(k+'0') for k in rsys.substances} af_H2O_H(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) # ensure idempotent exprs_H2O_H = af_H2O_H(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) ref_H2O_H = { 'H2O': y0['H2O'] + y0['OH-'] - odesys['OH-'], # oxygen 'H+': 2*y0['H2O'] + y0['H+'] + y0['OH-'] - odesys['OH-'] - 2*( y0['H2O'] + y0['OH-'] - odesys['OH-']) # hydrogen } for k, v in ref_H2O_H.items(): assert (exprs_H2O_H[odesys[k]] - v) == 0
def test_get_odesys__max_euler_step_cb(): rsys = ReactionSystem.from_string( "\n".join(["H2O -> H+ + OH-; 1e-4", "OH- + H+ -> H2O; 1e10"]) ) odesys, extra = get_odesys(rsys) r1 = 1.01e-4 r2 = 6e-4 dH2Odt = r2 - r1 euler_ref = 2e-7 / dH2Odt assert ( abs( extra["max_euler_step_cb"](0, {"H2O": 1.01, "H+": 2e-7, "OH-": 3e-7}) - euler_ref ) / euler_ref < 1e-8 )
def test_get_native__Radiolytic__named_parameter__units(scaling_density): scaling, density = scaling_density rsys = ReactionSystem.from_string(""" -> H; Radiolytic(2*per100eV) H + H -> H2; 'k2' """, checks=('substance_keys', 'duplicate', 'duplicate_names')) gval = 2*u.per100eV from pyodesys.symbolic import ScaledSys kwargs = {} if scaling == 1 else dict(SymbolicSys=ScaledSys, dep_scaling=scaling) dens = {'density': 998*u.g/u.dm3} odesys, extra = get_odesys(rsys, include_params=False, substitutions=dens if density else {}, unit_registry=SI_base_registry, **kwargs) c0 = {'H': 42e-6*u.molar, 'H2': 17e3*u.nanomolar} native = get_native(rsys, odesys, 'cvode') tend = 7*60*u.minute params = {'doserate': 314*u.Gy/u.hour, 'k2': 53/u.molar/u.minute} if not density: params.update(dens) result = native.integrate(tend, c0, params, atol=1e-15, rtol=1e-15, integrator='cvode', nsteps=8000) assert result.info['success'] def analytic_H(t, p, k, H0): # dH/dt = p - k2*H**2 x0 = np.sqrt(2)*np.sqrt(p) x1 = x0 x2 = np.sqrt(k) x3 = t*x1*x2 x4 = H0*x2 x5 = np.sqrt(x0 + 2*x4) x6 = np.sqrt(-1/(2*H0*x2 - x0)) x7 = x5*x6*np.exp(x3) x8 = np.exp(-x3)/(x5*x6) return x1*(x7 - x8)/(2*x2*(x7 + x8)) t_ul = to_unitless(result.xout, u.s) p_ul = to_unitless(params['doserate']*dens['density']*gval, u.micromolar/u.s) ref_H_uM = analytic_H( t_ul, p_ul, to_unitless(params['k2'], 1/u.micromolar/u.s), to_unitless(c0['H'], u.micromolar) ) ref_H2_uM = to_unitless(c0['H2'], u.micromolar) + to_unitless(c0['H'], u.micromolar)/2 + t_ul*p_ul/2 - ref_H_uM/2 assert np.allclose(to_unitless(result.named_dep('H'), u.micromolar), ref_H_uM) assert np.allclose(to_unitless(result.named_dep('H2'), u.micromolar), ref_H2_uM)
def test_get_odesys__linear_dependencies__preferred(substances): rsys = ReactionSystem.from_string('\n'.join(['H2O -> H+ + OH-; 1e-4', 'OH- + H+ -> H2O; 1e10']), substances) assert isinstance(rsys.substances, OrderedDict) odesys, extra = get_odesys(rsys) af_H2O_H = extra['linear_dependencies'](['H+', 'H2O']) import sympy y0 = {k: sympy.Symbol(k+'0') for k in rsys.substances} af_H2O_H(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) # ensure idempotent exprs_H2O_H = af_H2O_H(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) ref_H2O_H = { 'H2O': y0['H2O'] + y0['OH-'] - odesys['OH-'], # oxygen 'H+': 2*y0['H2O'] + y0['H+'] + y0['OH-'] - odesys['OH-'] - 2*( y0['H2O'] + y0['OH-'] - odesys['OH-']) # hydrogen } for k, v in ref_H2O_H.items(): assert (exprs_H2O_H[odesys[k]] - v) == 0
def test_get_odesys_rsys_with_units__named_params(): rsys = ReactionSystem.from_string(""" A -> B; 'k1' B + C -> P; 'k2' """, substance_factory=Substance) odesys, extra = get_odesys(rsys, include_params=False, unit_registry=SI_base_registry) tend = 10 tend_units = tend*u.s c0 = {'A': 1e-6, 'B': 0, 'C': 1, 'P': 0} p = {'k1': 3, 'k2': 4} p_units = {'k1': 3/u.s, 'k2': 4/u.M/u.s} c0_units = {k: v*u.molar for k, v in c0.items()} result1 = odesys.integrate(tend_units, c0_units, p_units, integrator='odeint') assert result1.info['success'] with pytest.raises(Exception): odesys.integrate(tend, c0, p, integrator='odeint')
def test_get_odesys_rsys_with_units(): rsys = ReactionSystem.from_string(""" A -> B; 0.096/s B + C -> P; 4e3/M/s """, substance_factory=Substance) with pytest.raises(Exception): get_odesys(rsys) # not a strict test, SI_base_registry could be made the default odesys, extra = get_odesys(rsys, unit_registry=SI_base_registry) tend = 10 tend_units = tend*u.s c0 = {'A': 1e-6, 'B': 0, 'C': 1, 'P': 0} c0_units = {k: v*u.molar for k, v in c0.items()} result1 = odesys.integrate(tend_units, c0_units, integrator='gsl') assert result1.info['success'] with pytest.raises(Exception): odesys.integrate(tend, c0, integrator='gsl')
def test_get_odesys_rsys_with_units(): rsys = ReactionSystem.from_string(""" A -> B; 0.096/s B + C -> P; 4e3/M/s """, substance_factory=Substance) with pytest.raises(Exception): get_odesys(rsys) # not a strict test, SI_base_registry could be made the default odesys, extra = get_odesys(rsys, unit_registry=SI_base_registry) tend = 10 tend_units = tend*u.s c0 = {'A': 1e-6, 'B': 0, 'C': 1, 'P': 0} c0_units = {k: v*u.molar for k, v in c0.items()} result1 = odesys.integrate(tend_units, c0_units, integrator='gsl') assert result1.info['success'] with pytest.raises(Exception): odesys.integrate(tend, c0, integrator='gsl')
def test_get_odesys__linear_dependencies__PartiallySolvedSystem(preferred): import sympy from pyodesys.symbolic import PartiallySolvedSystem rsys = ReactionSystem.from_string('\n'.join(['H2O -> H+ + OH-; 1e-4', 'OH- + H+ -> H2O; 1e10'])) odesys, extra = get_odesys(rsys) c0 = {'H2O': 0, 'H+': 2e-7, 'OH-': 3e-7} h0max = extra['max_euler_step_cb'](0, c0) analytic_factory = extra['linear_dependencies']() y0 = {k: sympy.Symbol(k+'0') for k in rsys.substances} analytic_factory(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) psys = PartiallySolvedSystem(odesys, analytic_factory) xout, yout, info = psys.integrate(1, c0, atol=1e-12, rtol=1e-10, first_step=h0max*1e-12, integrator='cvode') c_reac = c0['H+'], c0['OH-'] H2O_ref = binary_rev(xout, 1e10, 1e-4, c0['H2O'], max(c_reac), min(c_reac)) assert np.allclose(yout[:, psys.names.index('H2O')], H2O_ref) assert np.allclose(yout[:, psys.names.index('H+')], c0['H+'] + c0['H2O'] - H2O_ref) assert np.allclose(yout[:, psys.names.index('OH-')], c0['OH-'] + c0['H2O'] - H2O_ref)
def test_create_odesys__validate__catalyst(): rsys1 = ReactionSystem.from_string(""" H2O2 + Pt -> 2 OH + Pt; 'k_decomp' """) ic1 = defaultdict(lambda: 0*u.molar, {'H2O2': 3.0*u.molar, 'Pt': 0.5*u.molar}) t1 = linspace(0*u.s, .3*u.s, 7) p1 = dict(k_decomp=42/u.second/u.molar) odesys1, odesys_extra = create_odesys(rsys1) validation = odesys_extra['validate'](dict(ic1, **p1)) assert not validation['not_seen'] dedim_ctx = _mk_dedim(SI_base_registry) (t, c, _p), dedim_extra = dedim_ctx['dedim_tcp'](t1, [ic1[k] for k in odesys1.names], p1) result1 = odesys1.integrate(t, c, _p) tout = result1.xout * dedim_extra['unit_time'] cout = result1.yout * dedim_extra['unit_conc'] yref1 = ic1['H2O2']*np.exp(-tout*ic1['Pt']*p1['k_decomp']) assert allclose(yref1, cout[:, odesys1.names.index('H2O2')], rtol=1e-6)
def test_create_odesys__validate__catalyst(): rsys1 = ReactionSystem.from_string(""" H2O2 + Pt -> 2 OH + Pt; 'k_decomp' """) ic1 = defaultdict(lambda: 0*u.molar, {'H2O2': 3.0*u.molar, 'Pt': 0.5*u.molar}) t1 = linspace(0*u.s, .3*u.s, 7) p1 = dict(k_decomp=42/u.second/u.molar) odesys1, odesys_extra = create_odesys(rsys1) validation = odesys_extra['validate'](dict(ic1, **p1)) assert not validation['not_seen'] dedim_ctx = _mk_dedim(SI_base_registry) (t, c, _p), dedim_extra = dedim_ctx['dedim_tcp'](t1, [ic1[k] for k in odesys1.names], p1) result1 = odesys1.integrate(t, c, _p) tout = result1.xout * dedim_extra['unit_time'] cout = result1.yout * dedim_extra['unit_conc'] yref1 = ic1['H2O2']*np.exp(-tout*ic1['Pt']*p1['k_decomp']) assert allclose(yref1, cout[:, odesys1.names.index('H2O2')], rtol=1e-6)
def test_get_native__named_parameter__units(dep_scaling): rsys = ReactionSystem.from_string(""" -> H; 'p' H + H -> H2; 'k2' """, checks=('substance_keys', 'duplicate', 'duplicate_names')) from pyodesys.symbolic import ScaledSys kwargs = {} if dep_scaling == 1 else dict(SymbolicSys=ScaledSys, dep_scaling=dep_scaling) odesys, extra = get_odesys(rsys, include_params=False, unit_registry=SI_base_registry, **kwargs) c0 = {'H': 42e-6*u.molar, 'H2': 17*u.micromolar} native = get_native(rsys, odesys, 'cvode') tend = 7*60*u.minute g_rho_Ddot = g, rho, Ddot = 2*u.per100eV, 998*u.g/u.dm3, 314*u.Gy/u.hour params = { 'p': reduce(mul, g_rho_Ddot), 'k2': 53/u.molar/u.minute } result = native.integrate(tend, c0, params, atol=1e-15, rtol=1e-15, integrator='cvode', nsteps=16000) assert result.info['success'] def analytic_H(t, p, k, H0): # dH/dt = p - k2*H**2 x0 = np.sqrt(2)*np.sqrt(p) x1 = x0 x2 = np.sqrt(k) x3 = t*x1*x2 x4 = H0*x2 x5 = np.sqrt(x0 + 2*x4) x6 = np.sqrt(-1/(2*H0*x2 - x0)) x7 = x5*x6*np.exp(x3) x8 = np.exp(-x3)/(x5*x6) return x1*(x7 - x8)/(2*x2*(x7 + x8)) t_ul = to_unitless(result.xout, u.s) p_ul = to_unitless(params['p'], u.micromolar/u.s) ref_H_uM = analytic_H( t_ul, p_ul, to_unitless(params['k2'], 1/u.micromolar/u.s), to_unitless(c0['H'], u.micromolar) ) ref_H2_uM = to_unitless(c0['H2'], u.micromolar) + to_unitless(c0['H'], u.micromolar)/2 + t_ul*p_ul/2 - ref_H_uM/2 assert np.allclose(to_unitless(result.named_dep('H'), u.micromolar), ref_H_uM) assert np.allclose(to_unitless(result.named_dep('H2'), u.micromolar), ref_H2_uM)
def test_get_odesys__cstr(): rsys = ReactionSystem.from_string("2 H2O2 -> O2 + 2 H2O; 5") odesys, extra = get_odesys(rsys, cstr=True) fr, fc = extra['cstr_fr_fc'] tout, c0 = np.linspace(0, .13, 7), {'H2O2': 2, 'O2': 4, 'H2O': 3} params = {fr: 13, fc['H2O2']: 11, fc['O2']: 43, fc['H2O']: 45} res = odesys.integrate(tout, c0, params) from chempy.kinetics.integrated import binary_irrev_cstr def get_analytic(result, k, n): ref = binary_irrev_cstr(result.xout, 5, result.named_dep('H2O2')[0], result.named_dep(k)[0], result.named_param(fc['H2O2']), result.named_param(fc[k]), result.named_param(fr), n) return np.array(ref).T ref_O2 = get_analytic(res, 'O2', 1) ref_H2O = get_analytic(res, 'H2O', 2) assert np.allclose(res.named_dep('H2O2'), ref_O2[:, 0]) assert np.allclose(res.named_dep('H2O2'), ref_H2O[:, 0]) assert np.allclose(res.named_dep('O2'), ref_O2[:, 1]) assert np.allclose(res.named_dep('H2O'), ref_H2O[:, 1])
def test_get_native__conc_roots(): M, s = u.M, u.s rsys = ReactionSystem.from_string("2 O3 -> 3 O2; 'k2'") u_reg = SI_base_registry.copy() odesys, extra = get_odesys(rsys, include_params=False, unit_registry=u_reg) c0 = {'O3': 4.2e-3*M, 'O2': 0*M} cr = ['O2'] native = get_native(rsys, odesys, 'cvode', conc_roots=cr) tend = 1e5*u.s params = {'k2': logspace_from_lin(1e-3/M/s, 1e3/M/s, 14)} tgt_O2 = 1e-3*M results = native.integrate(tend, c0, params, integrator='native', return_on_root=True, special_settings=[unitless_in_registry(tgt_O2, u_reg)]) assert len(results) == params['k2'].size # dydt = -p*y**2 # 1/y0 - 1/y = -2*pt # t = 1/2/p*(1/y - 1/y0) tgt_O3 = c0['O3'] - 2/3 * tgt_O2 for r in results: ref = rescale(1/2/r.named_param('k2')*(1/tgt_O3 - 1/c0['O3']), u.s) assert allclose(r.xout[-1], ref, rtol=1e-6)
def test_create_odesys(): rsys = ReactionSystem.from_string( """ A -> B; 'k1' B + C -> P; 'k2' """, substance_factory=Substance, ) odesys, odesys_extra = create_odesys(rsys, unit_registry=SI_base_registry) tend_ul = 10 init_conc_ul = {"A": 1e-6, "B": 0, "C": 1} params_ul = dict(k1=3, k2=4) tend = tend_ul * u.s params = {"k1": params_ul["k1"] / u.s, "k2": params_ul["k2"] / u.M / u.s} init_conc = {k: v * u.molar for k, v in init_conc_ul.items()} validation = odesys_extra["validate"](dict(init_conc, **params)) (P,) = validation["not_seen"] assert P == "P" ref_rates = { "A": -params["k1"] * init_conc["A"], "P": params["k2"] * init_conc["B"] * init_conc["C"], } ref_rates["B"] = -ref_rates["A"] - ref_rates["P"] ref_rates["C"] = -ref_rates["P"] assert validation["rates"] == ref_rates result1, result1_extra = odesys_extra["unit_aware_solve"]( tend, defaultdict(lambda: 0 * u.molar, init_conc), params, integrator="cvode" ) assert result1.info["success"] result2 = odesys.integrate( tend_ul, defaultdict(lambda: 0, init_conc_ul), params_ul, integrator="cvode" ) assert np.allclose(result2.yout[-1, :], to_unitless(result1.yout[-1, :], u.molar))
def test_get_odesys_rsys_with_units__named_params(): rsys = ReactionSystem.from_string( """ A -> B; 'k1' B + C -> P; 'k2' """, substance_factory=Substance, ) odesys, extra = get_odesys( rsys, include_params=False, unit_registry=SI_base_registry ) tend = 10 tend_units = tend * u.s c0 = {"A": 1e-6, "B": 0, "C": 1, "P": 0} p = {"k1": 3, "k2": 4} p_units = {"k1": 3 / u.s, "k2": 4 / u.M / u.s} c0_units = {k: v * u.molar for k, v in c0.items()} result1 = odesys.integrate(tend_units, c0_units, p_units, integrator="odeint") assert result1.info["success"] with pytest.raises(Exception): odesys.integrate(tend, c0, p, integrator="odeint")
def test_create_odesys__validate__catalyst(): rsys1 = ReactionSystem.from_string( """ H2O2 + Pt -> 2 OH + Pt; 'k_decomp' """ ) ic1 = defaultdict(lambda: 0 * u.molar, {"H2O2": 3.0 * u.molar, "Pt": 0.5 * u.molar}) t1 = linspace(0 * u.s, 0.3 * u.s, 7) p1 = dict(k_decomp=42 / u.second / u.molar) odesys1, odesys_extra = create_odesys(rsys1) validation = odesys_extra["validate"](dict(ic1, **p1)) assert validation["not_seen"] == {"OH"} dedim_ctx = _mk_dedim(SI_base_registry) (t, c, _p), dedim_extra = dedim_ctx["dedim_tcp"]( t1, [ic1[k] for k in odesys1.names], p1 ) result1 = odesys1.integrate(t, c, _p) tout = result1.xout * dedim_extra["unit_time"] cout = result1.yout * dedim_extra["unit_conc"] yref1 = ic1["H2O2"] * np.exp(-tout * ic1["Pt"] * p1["k_decomp"]) assert allclose(yref1, cout[:, odesys1.names.index("H2O2")], rtol=1e-6)
def test_get_native__a_substance_no_composition(solve): rsys = ReactionSystem.from_string("\n".join( ["H2O -> H2O+ + e-(aq); 1e-8", "e-(aq) + H2O+ -> H2O; 1e10"])) odesys, extra = get_odesys(rsys) c0 = {"H2O": 0, "H2O+": 2e-9, "e-(aq)": 3e-9} if len(solve) > 0: from pyodesys.symbolic import PartiallySolvedSystem odesys = PartiallySolvedSystem(odesys, extra["linear_dependencies"](solve)) odesys = get_native(rsys, odesys, "gsl") xout, yout, info = odesys.integrate(1, c0, atol=1e-15, rtol=1e-15, integrator="gsl") c_reac = c0["H2O+"], c0["e-(aq)"] H2O_ref = binary_rev(xout, 1e10, 1e-4, c0["H2O"], max(c_reac), min(c_reac)) assert np.allclose(yout[:, odesys.names.index("H2O")], H2O_ref) assert np.allclose(yout[:, odesys.names.index("H2O+")], c0["H2O+"] + c0["H2O"] - H2O_ref) assert np.allclose(yout[:, odesys.names.index("e-(aq)")], c0["e-(aq)"] + c0["H2O"] - H2O_ref)
def test_create_odesys(): rsys = ReactionSystem.from_string(""" A -> B; 'k1' B + C -> P; 'k2' """, substance_factory=Substance) odesys, odesys_extra = create_odesys(rsys, unit_registry=SI_base_registry) tend = 10 c0 = {'A': 1e-6, 'B': 0, 'C': 1, 'P': 0} p = dict(k1=3, k2=4) tend_units = tend * u.s p_units = {'k1': p['k1'] / u.s, 'k2': p['k2'] / u.M / u.s} c0_units = {k: v * u.molar for k, v in c0.items()} validation = odesys_extra['validate'](dict(c0_units, **p_units)) P, = validation['not_seen'] assert P.name == 'P' ref_rates = { 'A': -p_units['k1'] * c0_units['A'], 'P': p_units['k2'] * c0_units['B'] * c0_units['C'] } ref_rates['B'] = -ref_rates['A'] - ref_rates['P'] ref_rates['C'] = -ref_rates['P'] assert validation['rates'] == ref_rates result1, result1_extra = odesys_extra['unit_aware_solve']( tend_units, c0_units, p_units, integrator='cvode') assert result1.info['success'] result2 = odesys.integrate(tend, c0, p, integrator='cvode') assert np.allclose(result2.yout[-1, :], to_unitless(result1.yout[-1, :], u.molar))
def test_get_odesys__linear_dependencies__PartiallySolvedSystem(preferred): import sympy from pyodesys.symbolic import PartiallySolvedSystem rsys = ReactionSystem.from_string( "\n".join(["H2O -> H+ + OH-; 1e-4", "OH- + H+ -> H2O; 1e10"]) ) odesys, extra = get_odesys(rsys) c0 = {"H2O": 0, "H+": 2e-7, "OH-": 3e-7} h0max = extra["max_euler_step_cb"](0, c0) analytic_factory = extra["linear_dependencies"]() y0 = {k: sympy.Symbol(k + "0") for k in rsys.substances} analytic_factory(None, {odesys[k]: v for k, v in y0.items()}, None, sympy) psys = PartiallySolvedSystem(odesys, analytic_factory) xout, yout, info = psys.integrate( 1, c0, atol=1e-12, rtol=1e-10, first_step=h0max * 1e-12, integrator="cvode" ) c_reac = c0["H+"], c0["OH-"] H2O_ref = binary_rev(xout, 1e10, 1e-4, c0["H2O"], max(c_reac), min(c_reac)) assert np.allclose(yout[:, psys.names.index("H2O")], H2O_ref) assert np.allclose(yout[:, psys.names.index("H+")], c0["H+"] + c0["H2O"] - H2O_ref) assert np.allclose( yout[:, psys.names.index("OH-")], c0["OH-"] + c0["H2O"] - H2O_ref )
def test_get_odesys__Eyring_1st_order_linearly_ramped_temperature(): from scipy.special import expi def analytic_unit0(t, T0, dH, dS): R = 8.314472 kB = 1.3806504e-23 h = 6.62606896e-34 A = kB / h * np.exp(dS / R) B = dH / R return np.exp( A * ( (-(B ** 2) * np.exp(B / T0) * expi(-B / T0) - T0 * (B - T0)) * np.exp(-B / T0) + ( B ** 2 * np.exp(B / (t + T0)) * expi(-B / (t + T0)) - (t + T0) * (-B + t + T0) ) * np.exp(-B / (t + T0)) ) / 2 ) T_K = 290 dH = 80e3 dS = 10 rsys1 = ReactionSystem.from_string( """ NOBr -> NO + Br; EyringParam(dH={dH}*J/mol, dS={dS}*J/K/mol) """.format( dH=dH, dS=dS ) ) NOBr0_M = 0.7 init_cond = dict(NOBr=NOBr0_M * u.M, NO=0 * u.M, Br=0 * u.M) t = 20 * u.second def check(rsys): odes, extra = get_odesys( rsys, unit_registry=SI_base_registry, constants=const, substitutions={"temperature": RampedTemp([T_K * u.K, 1 * u.K / u.s])}, ) for odesys in [odes, odes.as_autonomous()]: res = odesys.integrate(t, init_cond, integrator="cvode") t_sec = to_unitless(res.xout, u.second) NOBr_ref = NOBr0_M * analytic_unit0(t_sec, T_K, dH, dS) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index("NOBr")] = NOBr_ref ref[:, odesys.names.index("Br")] = NOBr0_M - NOBr_ref ref[:, odesys.names.index("NO")] = NOBr0_M - NOBr_ref assert np.allclose(cmp, ref) check(rsys1) rsys2 = ReactionSystem.from_string( """ NOBr -> NO + Br; MassAction(EyringHS([{dH}*J/mol, {dS}*J/K/mol])) """.format( dH=dH, dS=dS ) ) check(rsys2)
def test_get_odesys__cstr(): rsys = ReactionSystem.from_string("2 H2O2 -> O2 + 2 H2O; 5") odesys, extra = get_odesys(rsys, cstr=True) fr, fc = extra['cstr_fr_fc'] _check_cstr(odesys, fr, fc)
def test_get_odesys__Eyring_2nd_order_reversible(): R = 8.314472 T_K = 273.15 + 20 # 20 degree celsius kB = 1.3806504e-23 h = 6.62606896e-34 dHf = 74e3 dSf = R * np.log(h / kB / T_K * 1e16) dHb = 79e3 dSb = dSf - 23 rsys1 = ReactionSystem.from_string( """ Fe+3 + SCN- -> FeSCN+2; EyringParam(dH={dHf}*J/mol, dS={dSf}*J/K/mol) FeSCN+2 -> Fe+3 + SCN-; EyringParam(dH={dHb}*J/mol, dS={dSb}*J/K/mol) """.format( dHf=dHf, dSf=dSf, dHb=dHb, dSb=dSb ) ) kf_ref = 20836643994.118652 * T_K * np.exp(-(dHf - T_K * dSf) / (R * T_K)) kb_ref = 20836643994.118652 * T_K * np.exp(-(dHb - T_K * dSb) / (R * T_K)) Fe0 = 6e-3 SCN0 = 2e-3 init_cond = {"Fe+3": Fe0 * u.M, "SCN-": SCN0 * u.M, "FeSCN+2": 0 * u.M} t = 3 * u.second def check(rsys, params): odes, extra = get_odesys( rsys, include_params=False, unit_registry=SI_base_registry, constants=const ) for odesys in [odes, odes.as_autonomous()]: res = odesys.integrate(t, init_cond, params, integrator="cvode") t_sec = to_unitless(res.xout, u.second) FeSCN_ref = binary_rev(t_sec, kf_ref, kb_ref, 0, Fe0, SCN0) cmp = to_unitless(res.yout, u.M) ref = np.empty_like(cmp) ref[:, odesys.names.index("FeSCN+2")] = FeSCN_ref ref[:, odesys.names.index("Fe+3")] = Fe0 - FeSCN_ref ref[:, odesys.names.index("SCN-")] = SCN0 - FeSCN_ref assert np.allclose(cmp, ref) check(rsys1, {"temperature": T_K * u.K}) rsys2 = ReactionSystem.from_string( """ Fe+3 + SCN- -> FeSCN+2; MassAction(EyringHS([{dHf}*J/mol, {dSf}*J/K/mol])) FeSCN+2 -> Fe+3 + SCN-; MassAction(EyringHS([{dHb}*J/mol, {dSb}*J/K/mol])) """.format( dHf=dHf, dSf=dSf, dHb=dHb, dSb=dSb ) ) check(rsys2, {"temperature": T_K * u.K}) rsys3 = ReactionSystem.from_string( """ Fe+3 + SCN- -> FeSCN+2; MassAction(EyringHS.fk('dHf', 'dSf')) FeSCN+2 -> Fe+3 + SCN-; MassAction(EyringHS.fk('dHb', 'dSb')) """ ) check( rsys3, dict( temperature=T_K * u.K, dHf=dHf * u.J / u.mol, dSf=dSf * u.J / u.mol / u.K, dHb=dHb * u.J / u.mol, dSb=dSb * u.J / u.mol / u.K, ), )
def test_create_odesys__cstr(): rsys = ReactionSystem.from_string("2 H2O2 -> O2 + 2 H2O; 'k2'") fr, fc = 'feedratio', OrderedDict([(sk, 'fc_%s' % sk) for sk in rsys.substances]) odesys, extra = create_odesys(rsys, rates_kw=dict(cstr_fr_fc=(fr, fc))) _check_cstr(odesys, fr, fc, extra_pars=dict(k2=5))
def test_get_odesys__cstr(): rsys = ReactionSystem.from_string("2 H2O2 -> O2 + 2 H2O; 5") odesys, extra = get_odesys(rsys, cstr=True) fr, fc = extra['cstr_fr_fc'] _check_cstr(odesys, fr, fc)
def test_get_native__Radiolytic__named_parameter__units(scaling_density): scaling, density = scaling_density rsys = ReactionSystem.from_string( """ -> H; Radiolytic(2*per100eV) H + H -> H2; 'k2' """, checks=("substance_keys", "duplicate", "duplicate_names"), ) gval = 2 * u.per100eV from pyodesys.symbolic import ScaledSys kwargs = {} if scaling == 1 else dict(SymbolicSys=ScaledSys, dep_scaling=scaling) dens = {"density": 998 * u.g / u.dm3} odesys, extra = get_odesys(rsys, include_params=False, substitutions=dens if density else {}, unit_registry=SI_base_registry, **kwargs) c0 = {"H": 42e-6 * u.molar, "H2": 17e3 * u.nanomolar} native = get_native(rsys, odesys, "cvode") tend = 7 * 60 * u.minute params = {"doserate": 314 * u.Gy / u.hour, "k2": 53 / u.molar / u.minute} if not density: params.update(dens) result = native.integrate(tend, c0, params, atol=1e-15, rtol=1e-15, integrator="cvode", nsteps=8000) assert result.info["success"] def analytic_H(t, p, k, H0): # dH/dt = p - k2*H**2 x0 = np.sqrt(2) * np.sqrt(p) x1 = x0 x2 = np.sqrt(k) x3 = t * x1 * x2 x4 = H0 * x2 x5 = np.sqrt(x0 + 2 * x4) x6 = np.sqrt(-1 / (2 * H0 * x2 - x0)) x7 = x5 * x6 * np.exp(x3) x8 = np.exp(-x3) / (x5 * x6) return x1 * (x7 - x8) / (2 * x2 * (x7 + x8)) t_ul = to_unitless(result.xout, u.s) p_ul = to_unitless(params["doserate"] * dens["density"] * gval, u.micromolar / u.s) ref_H_uM = analytic_H( t_ul, p_ul, to_unitless(params["k2"], 1 / u.micromolar / u.s), to_unitless(c0["H"], u.micromolar), ) ref_H2_uM = (to_unitless(c0["H2"], u.micromolar) + to_unitless(c0["H"], u.micromolar) / 2 + t_ul * p_ul / 2 - ref_H_uM / 2) assert np.allclose(to_unitless(result.named_dep("H"), u.micromolar), ref_H_uM) assert np.allclose(to_unitless(result.named_dep("H2"), u.micromolar), ref_H2_uM)
def test_create_odesys__cstr(): rsys = ReactionSystem.from_string("2 H2O2 -> O2 + 2 H2O; 'k2'") fr, fc = 'feedratio', OrderedDict([(sk, 'fc_%s' % sk) for sk in rsys.substances]) odesys, extra = create_odesys(rsys, rates_kw=dict(cstr_fr_fc=(fr, fc))) _check_cstr(odesys, fr, fc, extra_pars=dict(k2=5))
from collections import defaultdict from chempy.equilibria import EqSystem eqsys = EqSystem.from_string("""HCO3- = H+ + CO3-2; 10**-10.3 H2CO3 = H+ + HCO3-; 10**-6.3 H2O = H+ + OH-; 10**-14/55.4 """) arr, info, sane = eqsys.root(defaultdict(float, {'H2O': 55.4, 'HCO3-': 1e-2})) conc = dict(zip(eqsys.substances, arr)) from math import log10 print("pH: %.2f" % -log10(conc['H+'])) # 测量离子浓度 from chempy import ReactionSystem # The rate constants below are arbitrary rsys = ReactionSystem.from_string("""2 Fe+2 + H2O2 -> 2 Fe+3 + 2 OH-; 42 2 Fe+3 + H2O2 -> 2 Fe+2 + O2 + 2 H+; 17 H+ + OH- -> H2O; 1e10 H2O -> H+ + OH-; 1e-4""") # "[H2O]" = 1.0 (actually 55.4 at RT) from chempy.kinetics.ode import get_odesys odesys, extra = get_odesys(rsys) from collections import defaultdict import numpy as np tout = sorted(np.concatenate((np.linspace(0, 23), np.logspace(-8, 1)))) c0 = defaultdict(float, { 'Fe+2': 0.05, 'H2O2': 0.1, 'H2O': 1.0, 'H+': 1e-2, 'OH-': 1e-12 }) result = odesys.integrate(tout, c0, atol=1e-12, rtol=1e-14) import matplotlib.pyplot as plt