def test_integrate_nondimensionalisation__g_values(from_rsys): from chempy import Reaction, ReactionSystem from chempy.units import allclose, default_units as u rstr = "-> H + OH; Radiolytic({'radiolytic_yield': 2.1e-7*mol/J})" if from_rsys: rxn = Reaction.from_string(rstr, None) rsys = ReactionSystem([rxn], 'H OH') rd = ReactionDiffusion.from_ReactionSystem( rsys, unit_registry=SI_base_registry, variables=dict(doserate=0.15*u.Gy/u.s, density=0.998*u.kg/u.dm3)) assert rd.g_value_parents == [-1] assert rd.g_values == [[2.1e-7]*2] assert abs(rd.fields[0][0] - 0.15*998) < 1e-14 else: rd = ReactionDiffusion.nondimensionalisation( 2, [[]], [[0, 1]], [2.1e-7*0.15*0.998*u.molar/u.second], unit_registry=SI_base_registry) C0 = [3*molar, 4*molar] tout = np.linspace(0, 1)*day integr = Integration(rd, C0, tout, integrator='scipy') k_m3_p_mol_p_sec = 0.15*998*2.1e-7 t_sec = np.linspace(0, 24*3600) C0_mol_p_m3 = [3000, 4000] Cref_mol_p_m3 = np.empty(integr.Cout.squeeze().shape) Cref_mol_p_m3[:, 0] = C0_mol_p_m3[0] + k_m3_p_mol_p_sec*t_sec Cref_mol_p_m3[:, 1] = C0_mol_p_m3[1] + k_m3_p_mol_p_sec*t_sec print(integr.with_units('Cout').squeeze()) print(integr.with_units('Cout').squeeze() - Cref_mol_p_m3*u.mole/u.metre**3) assert allclose(integr.with_units('tout'), t_sec*u.s) assert allclose(integr.with_units('Cout').squeeze(), Cref_mol_p_m3*u.mole/u.metre**3)
def test_n_jac_diags(n_jac_diags): N, n, nstencil = 10, 1, 7 rd = ReactionDiffusion(n, [], [], [], N=N, nstencil=nstencil, n_jac_diags=n_jac_diags, D=[9]) assert np.allclose(rd.xcenters, [.05, .15, .25, .35, .45, .55, .65, .75, .85, .95]) y0 = np.ones(N) # Dense jref_cdns = np.zeros((n*N, n*N), order='F') jout_cdns = np.zeros((n*N, n*N), order='F') sm = SymRD.from_rd(rd) sm.dense_jac(0.0, y0.flatten(), jref_cdns) rd.dense_jac_cmaj(0.0, y0.flatten(), jout_cdns) assert np.allclose(jout_cdns, jref_cdns) # Banded jref_cbnd = rd.alloc_jout(order='F', pad=0) jout_cbnd = rd.alloc_jout(order='F') sm.banded_jac(0.0, y0.flatten(), jref_cbnd) rd.banded_jac_cmaj(0.0, y0.flatten(), jout_cbnd) assert np.allclose(jout_cbnd[rd.n*rd.n_jac_diags:, :], jref_cbnd) # Compressed jref_cmprs = rd.alloc_jout_compressed() jout_cmprs = rd.alloc_jout_compressed() sm.compressed_jac(0.0, y0.flatten(), jref_cmprs) rd.compressed_jac_cmaj(0.0, y0.flatten(), jout_cmprs) assert np.allclose(jout_cmprs, jref_cmprs)
def test_integrate_nondimensionalisation(from_rsys): from chempy import Reaction, ReactionSystem from chempy.units import allclose, default_units as u # 2A -> B if from_rsys: rxn = Reaction.from_string('2 A -> B; 2e-3*metre**3/mol/hour', None) rsys = ReactionSystem([rxn], 'A B') rd = ReactionDiffusion.from_ReactionSystem(rsys, unit_registry=SI_base_registry) else: rd = ReactionDiffusion.nondimensionalisation( 2, [[0, 0]], [[1]], [2e-9/(umol/metre**3)/hour], unit_registry=SI_base_registry) C0 = [3*molar, 4*molar] tout = np.linspace(0, 1)*day integr = Integration(rd, C0, tout, integrator='scipy') k_m3_p_mol_p_sec = 2e-3/3600 t_sec = np.linspace(0, 24*3600) C0_mol_p_m3 = [3000, 4000] Cref_mol_p_m3 = np.empty(integr.Cout.squeeze().shape) Cref_mol_p_m3[:, 0] = 1/(C0_mol_p_m3[0]**-1 + 2*k_m3_p_mol_p_sec*t_sec) missing_A = (C0_mol_p_m3[0] - Cref_mol_p_m3[:, 0]) Cref_mol_p_m3[:, 1] = C0_mol_p_m3[1] + missing_A/2 assert allclose(integr.with_units('tout'), t_sec*u.s) assert allclose(integr.with_units('Cout').squeeze(), Cref_mol_p_m3*u.mol/u.metre**3, rtol=1e-6)
def test_chained_parameter_variation(): # A -> B names = ['A', 'B'] rd = ReactionDiffusion(len(names), [], [], k=[], substance_names=names, g_value_parents=[0], g_values=[[0, 1]], param_names=['doserate']) durations = [1., 3., 2.] y0 = [13., 7.] ic = dict(zip(names, y0)) doserates = [.3, .11, .7] npoints = 3 odesys = rd._as_odesys(variables_from_params=dict( density=lambda self, params: 1.0 )) res = odesys.chained_parameter_variation( durations, ic, {'doserate': doserates}, npoints=npoints, integrate_kwargs=dict(atol={k: 1e-8 for k in odesys.names})) assert res.xout.size == npoints*len(durations) + 1 assert res.xout[0] == 0 assert np.all(res.yout[0, :] == y0) expected = [.3]*npoints + [.11]*npoints + [.7]*(npoints+1) assert np.all(res.params[:, odesys.param_names.index('doserate')] == expected) cumulative = 0.0 for dr, dur in zip(doserates, durations): mask = (cumulative <= res.xout) & (res.xout <= cumulative + dur) cumulative += dur t, y = res.xout[mask], res.yout[mask, :] a, b = y[:, 0], y[:, 1] refa = a[0] refb = b[0] + (t - t[0])*dr*a[0] assert np.allclose(refa, a) assert np.allclose(refb, b) res.extend_by_integration(np.sum(durations)+1, dict(doserate=doserates[-1]), integrator='cvode') assert abs(res.yout[-1, 1] - (refb[-1] + doserates[-1]*a[0])) < 1e-8
def test_ReactionDiffusion__f__wrong_fout_dimension(): y0 = np.array([2.0, 3.0]) k = 5.0 # A -> B rd = ReactionDiffusion(2, [[0]], [[1]], [k]) fout = np.ones((1,))*99 # fout too small with pytest.raises(AssertionError): rd.f(0.0, y0, fout)
def test_ReactionDiffusion__only_1_reaction__logy__logt(N): # See <test_ReactionDiffusion__only_1_reaction__logy_logt.png> t0 = 3.0 y0 = np.array([2.0, 3.0]*N) k = 5.0 # A -> B rd = ReactionDiffusion(2, [[0]], [[1]], [k], N, D=[0.0, 0.0], logy=True, logt=True) fref = np.array([(-k*t0, t0*k*y0[i*2]/y0[i*2+1]) for i in range(N)]).flatten() _test_f_and_dense_jac_rmaj(rd, rd.logb(t0), np.log(y0), fref)
def test_ReactionDiffusion__only_1_species_diffusion_3bins(log): # Diffusion without reaction # 3 bins t0 = 3.0 logy, logt = log D = 17.0 y0 = np.array([23.0, 27.0, 37.0]) N = 3 x = [5.0, 7.0, 13.0, 15.0] xc = [4.0, 6.0, 10.0, 14.0, 16.0] nstencil = 3 rd = ReactionDiffusion(1, [], [], [], D=[D], x=x, N=N, logy=logy, logt=logt, lrefl=False, rrefl=False, nstencil=nstencil) assert np.allclose(rd.xc, xc) w = [1/16, -1/8, 1/16] # finite diff. weights for 2nd order deriv for i in range(N): assert np.allclose(rd.D_weight[i*nstencil:(i+1)*nstencil], w) J = D*(w[0]*y0[0] + w[1]*y0[1] + w[2]*y0[2]) fref = np.array([J, J, J]) if logy: fref /= y0 if logt: fref *= t0 if logy: jref = D*np.array([ # jref[i, k] = ... [w[k]*y0[k]/y0[i] if k != i else -1/y0[k]*sum([ w[j]*y0[j] if j != k else 0 for j in range(3) ]) for k in range(3)] for i in range(3) ]) jref[0, 2] = 0.0 # dense_jac_rmaj only computes banded approx. jref[2, 0] = 0.0 # same as above. else: jref = D*np.array([[w[k] if abs(k-i) < 2 else 0.0 for k in range(3)] for i in range(3)]) if logt: jref *= t0 y = rd.logb(y0) if logy else y0 t = rd.logb(t0) if logt else t0 _test_f_and_dense_jac_rmaj(rd, t, y, fref, jref) jout_bnd = np.zeros((4, 3), order='F') rd.banded_jac_cmaj(t, y, jout_bnd) jref_bnd = get_banded(jref, 1, 3) assert np.allclose(jout_bnd[1:, :], jref_bnd)
def test_integrators(log): logy, logt, use_log2 = log t0, tend, nt = 5.0, 17.0, 42 tout = np.linspace(t0, tend, nt+1) # Update the dict if more integrators are added: solver_kwargs = { 'scipy1': { 'atol': [1e-8, 1e-8], 'rtol': 1e-8, 'tout': tout }, 'scipy2': { 'atol': 1e-8, 'rtol': 1e-8, 'tout': (t0, tend), 'dense_output': True }, 'cvode1': { 'atol': [1e-8, 1e-8], 'rtol': 1e-8, 'method': 'bdf', 'tout': tout }, 'cvode2': { 'atol': 1e-8, 'rtol': 1e-8, 'method': 'adams', 'tout': tout, 'C0_is_log': True } } # A -> B n = 2 k0 = 0.13 rd = ReactionDiffusion(n, [[0]], [[1]], k=[k0], logy=logy, logt=logt, use_log2=use_log2) y0 = [3.0, 1.0] results = [] for solver, kwargs in solver_kwargs.items(): _y0 = rd.logb(y0) if kwargs.get('C0_is_log', False) else y0 integr = Integration(rd, _y0, integrator=solver[:-1], **kwargs) if not kwargs.get('dense_output', False): results.append(integr.Cout) for result in results[1:]: assert np.allclose(results[0][0], result[0])
def test_from_ReactionSystem__g_values__multiple_types(): from chempy import Reaction, ReactionSystem as RS from chempy.kinetics.rates import mk_Radiolytic RABG = mk_Radiolytic('alpha', 'beta', 'gamma') dens, yields_k, yields_v = .7, 'ya yb yg'.split(), [3, 5, 7] rxn = Reaction({}, {'H': 2}, RABG(yields_k)) doserates = {'doserate_alpha': 11, 'doserate_beta': 13, 'doserate_gamma': 17} yields = dict(zip(yields_k, yields_v)) params = dict(doserates) params.update(yields) params['density'] = dens ref = .7*2*(3*11 + 5*13 + 7*17) rat = rxn.rate(params) assert abs(rat['H'] - ref) < 1e-13 assert RABG.parameter_keys == ('density', 'doserate_alpha', 'doserate_beta', 'doserate_gamma') assert RABG.argument_names == tuple('radiolytic_yield_%s' % k for k in 'alpha beta gamma'.split()) rs = RS([rxn], checks=()) rd = ReactionDiffusion.from_ReactionSystem(rs, variables=params) gv = rd.g_values assert len(gv) == 3 assert np.allclose(sorted(gv), [[v*2] for v in sorted(yields_v)]) assert len(rd.fields) == 3 assert len(rd.fields[0]) == 1 assert np.allclose(sorted(np.array(rd.fields).squeeze()), sorted([drat*dens for drat in doserates.values()])) fout = rd.alloc_fout() rd.f(0, np.array([0.0]), fout) assert np.allclose(fout, ref)
def test_radyields2pdf_table(): rsys = _get_rsys() rd = ReactionDiffusion.from_ReactionSystem(rsys) tempdir = tempfile.mkdtemp() try: radyields2pdf_table(rd, tempdir) finally: shutil.rmtree(tempdir)
def test_ReactionDiffusion__only_1_field_dep_reaction_logy_logt(N): t0 = 3.0 y0 = np.concatenate([np.array([2.0, 3.0])/(x+1) for x in range(N)]) k = 5.0 # A -> B rd = ReactionDiffusion(2, [], [], [], N, D=[0.0, 0.0], fields=[[x+1 for x in range(N)]], g_values=[[-k, k]], g_value_parents=[0], logy=True, logt=True) def k_(bi): return k*(bi+1) fref = np.array([(-k_(i)*t0, k_(i)*t0*y0[i*2]/y0[i*2+1]) for i in range(N)]).flatten() _test_f_and_dense_jac_rmaj(rd, rd.logb(t0), np.log(y0), fref)
def test_ReactionDiffusion__only_1_reaction__logy(N): # See <test_ReactionDiffusion__only_1_reaction__logy.png> t0 = 3.0 y0 = np.array([2.0, 3.0]*N) k = 5.0 # A -> B rd = ReactionDiffusion(2, [[0]], [[1]], [k], N, D=[0.0, 0.0], logy=True) fref = np.array([(-k, k*y0[i*2]/y0[i*2+1]) for i in range(N)]).flatten() _test_f(rd, t0, rd.logb(y0), fref) jref = np.zeros((2*N, 2*N)) for i in range(N): A = y0[i*2] B = y0[i*2+1] jref[i*2+1, i*2] = k/B*A jref[i*2+1, i*2+1] = -k/B*A _test_dense_jac_rmaj(rd, t0, rd.logb(y0), jref)
def test_ReactionSystem__to_ReactionDiffusion(): sbstncs = mk_sn_dict_from_names('AB') r1 = Reaction({'A': 2}, {'B': 1}, 3.0) rsys = ReactionSystem([r1], sbstncs) rd = ReactionDiffusion.from_ReactionSystem(rsys) assert rd.stoich_active == [[0, 0]] assert rd.stoich_prod == [[1]] assert rd.k == [3.0]
def test_dc(): n = 7 k = [1]*(n-1) rd = ReactionDiffusion(n, [[i] for i in range(n-1)], [[i] for i in range(1, n)], k=k) fout = rd.alloc_fout() y0 = [0]*n y0[0] = 1 y_ = [0] + y0 y0 = np.asarray(y0, dtype=np.float64) rd.f(0, y0, fout) pos, neg = [0]+k, k+[0] _test_f(rd, 0, y0) fref = [pos[i]*y_[i] - neg[i]*y_[i+1] for i in range(n)] assert np.allclose(fref, fout) jout = rd.alloc_jout(order='C') rd.dense_jac_rmaj(0, y0, jout) jref = np.zeros_like(jout) for i in range(n): if i < n - 1: jref[i, i] = -k[i] if i > 0: jref[i, i-1] = k[i-1] assert np.allclose(jref, jout) _test_dense_jac_rmaj(rd, 0, y0)
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_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_autobinary(): from chemreac.chemistry import ( Reaction, ReactionSystem, mk_sn_dict_from_names ) sbstncs = mk_sn_dict_from_names('AB') k = 3.0 r1 = Reaction({'A': 2}, {'B': 1}, k) rsys = ReactionSystem([r1], sbstncs) rd = ReactionDiffusion.from_ReactionSystem(rsys) _test_f_and_dense_jac_rmaj(rd, 0, [1, 37], [-2*3, 3])
def test_chemistry(): sbstncs = mk_sn_dict_from_names('ABC', D=[.1, .2, .3]) r1 = Reaction({'A': 1, 'B': 1}, {'C': 1}, 0.3) rsys = ReactionSystem([r1], sbstncs) rd = ReactionDiffusion.from_ReactionSystem(rsys) serialized_rd = load(JSON_PATH) assert rd.stoich_active == serialized_rd.stoich_active assert rd.stoich_prod == serialized_rd.stoich_prod assert rd.stoich_inact == serialized_rd.stoich_inact assert np.allclose(rd.k, serialized_rd.k) assert np.allclose(rd.D, serialized_rd.D)
def test_ReactionDiffusion__only_1_field_dep_reaction_logy(N): y0 = np.concatenate([np.array([2.0, 3.0])/(x+1) for x in range(N)]) k = 5.0 # A -> B rd = ReactionDiffusion(2, [], [], [], N, D=[0.0, 0.0], fields=[[x+1 for x in range(N)]], g_values=[[-k, k]], g_value_parents=[0], logy=True) def k_(bi): return k*(bi+1) fref = np.array([(-k_(i), k_(i)*y0[i*2]/y0[i*2+1]) for i in range(N)]).flatten() if N == 1: jref = np.array([[0, 0], [k*y0[0]/y0[1], -k*y0[0]/y0[1]]]) else: jref = None _test_f_and_dense_jac_rmaj(rd, 0, rd.logb(y0), fref, jref)
def main(tdelay=1.0, B0=0.6, noiselvl=3e-4, nt=200, eps_l=4200.0, plot=False): """ Solution: 1. non-linear fit to: A) if approx equal conc A+A -> C B) else: A+B -> C 2. Use as guess for guess and shoot. A + B <-> C """ Keq = 10.0 k_fw_true = 1.3 ktrue = [1.3, k_fw_true/Keq] c0 = [1.0, B0, 0.0] ttrue = np.linspace(0, 10, nt) rd_eq = ReactionDiffusion(3, [[0, 1], [2]], [[2], [0, 1]], k=ktrue) tinp, yinp = simulate_stopped_flow( rd_eq, ttrue, c0, ktrue, noiselvl, eps_l, tdelay) k_fw_opt, d_opt, eps_l_opt = fit_binary_eq_from_temporal_abs_data( tinp, yinp, c0, Keq, True) rd_eq.k = [k_fw_opt, k_fw_opt/Keq] integr = run(rd_eq, c0, ttrue) yopt = integr.Cout[:, 0, 2]*eps_l_opt if plot: import matplotlib.pyplot as plt # Plot plt.subplot(2, 1, 1) plt.plot(tinp, yinp, label='Input data') plt.plot(ttrue-tdelay, yopt, label='Shooting Opt (k={})'.format(k_fw_opt)) plt.legend(loc='best') plt.subplot(2, 1, 2) plt.plot(tinp, yinp, label='Input data') # TODO: this needs to be improved... yquad = (B0-1/(1/B0+k_fw_opt*ttrue))*eps_l_opt plt.plot(ttrue-tdelay, yquad, label='Equal initial conc treatment') plt.legend(loc='best') plt.show()
def test_integrated_conc(params): geom, logx = params N = 8192 x0, xend = 0.11, 1.37 x = np.linspace(x0, xend, N+1) rd = ReactionDiffusion(1, [], [], [], D=[0], N=N, x=np.log(x) if logx else x, geom=geom, logx=logx) xc = rd.expb(rd.xcenters) if logx else rd.xcenters y = xc*np.exp(-xc) def primitive(t): if geom == 'f': return -(t+1)*np.exp(-t) elif geom == 'c': return 2*np.exp(-t)*np.pi*(-2 - 2*t - t**2) elif geom == 's': return 4*np.exp(-t)*np.pi*(-6 - 6*t - 3*t**2 - t**3) else: raise NotImplementedError res = rd.integrated_conc(y) ref = (primitive(xend) - primitive(x0)) assert abs(res - ref) < 1e-8
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 main(logy=False, logt=False, unit_registry=None): # A -> B n = 2 k0 = 0.13 * second**-1 if unit_registry is None: unit_registry = SI_base_registry rd = ReactionDiffusion.nondimensionalisation( n, [[0]], [[1]], k=[k0], logy=logy, logt=logt, unit_registry=unit_registry) y0 = [3.0e3 * mole/metre**3, 1.0*molar] t0, tend, nt = 5.0*second, 17.0*second, 42 tout = linspace(t0, tend, nt) Cref = molar*np.array([3.0*np.exp(-0.13*second**-1*(tout-t0)), 1.0 + 3.0*(1 - np.exp( -0.13*second**-1*(tout-t0)))]).transpose() # scipy integr1 = Integration( rd, y0, tout, integrator='scipy') return integr1, Cref, rd
def test_autodimerization(arrhenius): # A + A -> B from chemreac.chemistry import ( Reaction, ReactionSystem, mk_sn_dict_from_names ) sbstncs = mk_sn_dict_from_names('AB') if arrhenius: from chempy.kinetics.arrhenius import ArrheniusParam k = ArrheniusParam(7e11, -8.314472*298.15*(np.log(3) - np.log(7) - 11*np.log(10))) variables = {'temperature': 298.15} param = 3.0 else: param = k = 3.0 variables = None r1 = Reaction({'A': 2}, {'B': 1}, k) rsys = ReactionSystem([r1], sbstncs) rd = ReactionDiffusion.from_ReactionSystem(rsys, variables=variables) t = np.linspace(0, 5, 3) A0, B0 = 1.0, 0.0 integr = run(rd, [A0, B0], t) Aref = 1/(1/A0+2*param*t) yref = np.vstack((Aref, (A0-Aref)/2)).transpose() assert np.allclose(integr.yout[:, 0, :], yref)
def test_from_ReactionSystem__fields(): from chempy import Reaction as R, ReactionSystem as RS from chempy.kinetics.rates import mk_Radiolytic gvals = [2, 3, 5] rs = RS([R({}, {'H', 'OH'}, Rad(v)) for v, Rad in zip( gvals, map(mk_Radiolytic, 'alpha beta gamma'.split()))]) for s in rs.substances.values(): s.data['D'] = 0.0 # no diffusion nbins = 73 fields = OrderedDict([('alpha', np.linspace(7, 11, nbins)), ('beta', np.linspace(13, 17, nbins)), ('gamma', np.linspace(19, 23, nbins))]) rd = ReactionDiffusion.from_ReactionSystem(rs, variables={'density': 998}, fields=fields, N=nbins) C0 = {'H': np.linspace(29, 31, nbins), 'OH': np.linspace(37, 41, nbins)} integr = Integration(rd, np.array([C0[k]*np.ones(nbins) for k in rd.substance_names]).T, [0, 43], integrator='cvode') assert integr.tout[-1] == 43 assert integr.tout.size > 2 Cref = np.array([C0[sk] + integr.tout.reshape((-1, 1))*reduce(add, [ fields[fk]*g for fk, g in zip(fields, gvals) ]).reshape((1, -1)) for sk in rd.substance_names]).transpose(1, 2, 0) assert np.allclose(integr.Cout, Cref)
def integrate_rd(D=2e-3, t0=3., tend=7., x0=0.0, xend=1.0, mu=None, N=32, nt=25, geom='f', logt=False, logy=False, logx=False, random=False, nstencil=3, lrefl=False, rrefl=False, num_jacobian=False, method='bdf', plot=False, atol=1e-6, rtol=1e-6, efield=False, random_seed=42, verbose=False, use_log2=False): if random_seed: np.random.seed(random_seed) n = 1 mu = float(mu or x0) tout = np.linspace(t0, tend, nt) assert geom in 'fcs' # Setup the grid logb = (lambda arg: log(arg)/log(2)) if use_log2 else log _x0 = logb(x0) if logx else x0 _xend = logb(xend) if logx else xend x = np.linspace(_x0, _xend, N+1) if random: x += (np.random.random(N+1)-0.5)*(_xend-_x0)/(N+2) mob = 0.3 # Initial conditions y0 = { 'f': y0_flat_cb, 'c': y0_cylindrical_cb, 's': y0_spherical_cb }[geom](x, logx) # Setup the system stoich_active = [] stoich_prod = [] k = [] assert not lrefl assert not rrefl rd = ReactionDiffusion( n, stoich_active, stoich_prod, k, N, D=[D], z_chg=[1], mobility=[mob], x=x, geom=geom, logy=logy, logt=logt, logx=logx, nstencil=nstencil, lrefl=lrefl, rrefl=rrefl, use_log2=use_log2 ) if efield: if geom != 'f': raise ValueError("Only analytic sol. for flat drift implemented.") rd.efield = efield_cb(rd.xcenters, logx) # Analytic reference values t = tout.copy().reshape((nt, 1)) Cref = np.repeat(y0[np.newaxis, :, np.newaxis], nt, axis=0) if efield: Cref += t.reshape((nt, 1, 1))*mob # Run the integration integr = run(rd, y0, tout, atol=atol, rtol=rtol, with_jacobian=(not num_jacobian), method=method) Cout, info = integr.Cout, integr.info if verbose: print(info) def lin_err(i=slice(None), j=slice(None)): return integr.Cout[i, :, j] - Cref[i, :, j] rmsd = np.sum(lin_err()**2 / N, axis=1)**0.5 ave_rmsd_over_atol = np.average(rmsd, axis=0)/info['atol'] # Plot results if plot: import matplotlib.pyplot as plt def _plot(y, c, ttl=None, apply_exp_on_y=False): plt.plot(rd.xcenters, rd.expb(y) if apply_exp_on_y else y, c=c) if N < 100: plt.vlines(rd.x, 0, np.ones_like(rd.x)*max(y), linewidth=.1, colors='gray') plt.xlabel('x / m') plt.ylabel('C / M') if ttl: plt.title(ttl) for i in range(nt): c = 1-tout[i]/tend c = (1.0-c, .5-c/2, .5-c/2) # over time: dark red -> light red plt.subplot(4, 1, 1) _plot(Cout[i, :, 0], c, 'Simulation (N={})'.format(rd.N), apply_exp_on_y=logy) plt.subplot(4, 1, 2) _plot(Cref[i, :, 0], c, 'Analytic', apply_exp_on_y=logy) ax_err = plt.subplot(4, 1, 3) plot_solver_linear_error(integr, Cref, ax_err, ti=i, bi=slice(None), color=c, fill=(i == 0)) plt.title('Linear rel error / Log abs. tol. (={})'.format( info['atol'])) plt.subplot(4, 1, 4) tspan = [tout[0], tout[-1]] plt.plot(tout, rmsd[:, 0] / info['atol'], 'r') plt.plot(tspan, [ave_rmsd_over_atol[0]]*2, 'r--') plt.xlabel('Time / s') plt.ylabel(r'$\sqrt{\langle E^2 \rangle} / atol$') plt.tight_layout() plt.show() return tout, Cout, info, ave_rmsd_over_atol, rd
def integrate_rd(D=2e-3, t0=3., tend=7., x0=0.0, xend=1.0, mu=None, N=64, nt=42, geom='f', logt=False, logy=False, logx=False, random=False, k=0.0, nstencil=3, linterpol=False, rinterpol=False, num_jacobian=False, method='bdf', scale_x=False, atol=1e-6, rtol=1e-6, efield=False, random_seed=42): if t0 == 0.0: raise ValueError("t0==0 => Dirac delta function C0 profile.") if random_seed: np.random.seed(random_seed) decay = (k != 0.0) n = 2 if decay else 1 mu = float(mu or x0) tout = np.linspace(t0, tend, nt) assert geom in 'fcs' geom = {'f': FLAT, 'c': CYLINDRICAL, 's': SPHERICAL}[geom] analytic = { FLAT: flat_analytic, CYLINDRICAL: cylindrical_analytic, SPHERICAL: spherical_analytic }[geom] # Setup the grid _x0 = log(x0) if logx else x0 _xend = log(xend) if logx else xend x = np.linspace(_x0, _xend, N + 1) if random: x += (np.random.random(N + 1) - 0.5) * (_xend - _x0) / (N + 2) rd = ReactionDiffusion(2 if decay else 1, [[0]] if decay else [], [[1]] if decay else [], [k] if decay else [], N, D=[D] * (2 if decay else 1), z_chg=[1] * (2 if decay else 1), mobility=[0.01] * (2 if decay else 1), x=x, geom=geom, logy=logy, logt=logt, logx=logx, nstencil=nstencil, lrefl=not linterpol, rrefl=not rinterpol, xscale=1 / (x[1] - x[0]) if scale_x else 1.0) if efield: if geom != FLAT: raise ValueError("Only analytic sol. for flat drift implemented.") rd.efield = _efield_cb(rd.xcenters) # Calc initial conditions / analytic reference values t = tout.copy().reshape((nt, 1)) yref = analytic(rd.xcenters, t, D, mu, x0, xend, 0.01 if efield else 0, logy, logx).reshape(nt, N, 1) if decay: yref = np.concatenate((yref, yref), axis=2) if logy: yref[:, :, 0] += -k * t yref[:, :, 1] += np.log(1 - np.exp(-k * t)) else: yref[:, :, 0] *= np.exp(-k * t) yref[:, :, 1] *= 1 - np.exp(-k * t) # Run the integration integr = run(rd, yref[0, ...], tout, atol=atol, rtol=rtol, with_jacobian=(not num_jacobian), method=method, C0_is_log=logy)
def test_ReactionSystem__from_ReactionDiffusion(): rd = ReactionDiffusion(2, [[0]], [[1]], [1]) rsys = rd.to_ReactionSystem('AB') assert len(rsys.rxns) == 1
def integrate_rd(N=64, geom='f', nspecies=1, nstencil=3, D=2e-3, t0=3.0, tend=7., x0=0.0, xend=1.0, center=None, nt=42, logt=False, logy=False, logx=False, random=False, p=0, a=0.2, linterpol=False, rinterpol=False, ilu_limit=5.0, n_jac_diags=-1, num_jacobian=False, method='bdf', integrator='cvode', iter_type='undecided', linear_solver='default', atol=1e-8, rtol=1e-10, efield=False, random_seed=42, mobility=0.01, plot=False, savefig='None', verbose=False, yscale='linear', vline_limit=100, use_log2=False, Dexpr='[D]*nspecies', check_conserv=False ): # remember: anayltic_N_scaling.main kwargs # Example: # python3 analytic_diffusion.py --plot --Dexpr "D*np.exp(10*(x[:-1]+np.diff(x)/2))" if t0 == 0.0: raise ValueError("t0==0 => Dirac delta function C0 profile.") if random_seed: np.random.seed(random_seed) # decay = (nspecies > 1) # n = 2 if decay else 1 center = float(center or x0) tout = np.linspace(t0, tend, nt) assert geom in 'fcs' analytic = { 'f': flat_analytic, 'c': cylindrical_analytic, 's': spherical_analytic }[geom] # Setup the grid logx0 = math.log(x0) if logx else None logxend = math.log(xend) if logx else None if logx and use_log2: logx0 /= math.log(2) logxend /= math.log(2) _x0 = logx0 if logx else x0 _xend = logxend if logx else xend x = np.linspace(_x0, _xend, N+1) if random: x += (np.random.random(N+1)-0.5)*(_xend-_x0)/(N+2) def _k(si): return (si+p)*math.log(a+1) k = [_k(i+1) for i in range(nspecies-1)] rd = ReactionDiffusion( nspecies, [[i] for i in range(nspecies-1)], [[i+1] for i in range(nspecies-1)], k, N, D=eval(Dexpr), z_chg=[1]*nspecies, mobility=[mobility]*nspecies, x=x, geom=geom, logy=logy, logt=logt, logx=logx, nstencil=nstencil, lrefl=not linterpol, rrefl=not rinterpol, ilu_limit=ilu_limit, n_jac_diags=n_jac_diags, use_log2=use_log2 ) if efield: if geom != 'f': raise ValueError("Only analytic sol. for flat drift implemented.") rd.efield = _efield_cb(rd.xcenters) # Calc initial conditions / analytic reference values t = tout.copy().reshape((nt, 1)) yref = analytic(rd.xcenters, t, D, center, x0, xend, -mobility if efield else 0, logy, logx, use_log2).reshape(nt, N, 1) if nspecies > 1: from batemaneq import bateman_parent bateman_out = np.array(bateman_parent(k, tout)).T terminal = (1 - np.sum(bateman_out, axis=1)).reshape((nt, 1)) bateman_out = np.concatenate((bateman_out, terminal), axis=1).reshape( (nt, 1, nspecies)) if logy: yref = yref + rd.logb(bateman_out) else: yref = yref * bateman_out # Run the integration integr = run(rd, yref[0, ...], tout, atol=atol, rtol=rtol, with_jacobian=(not num_jacobian), method=method, iter_type=iter_type, linear_solver=linear_solver, C0_is_log=logy, integrator=integrator) info = integr.info if logy: def lin_err(i, j): linref = rd.expb(yref[i, :, j]) linerr = rd.expb(integr.yout[i, :, j])-linref linatol = np.average(yref[i, :, j]) linrtol = linatol return linerr/(linrtol*np.abs(linref)+linatol) if logy: rmsd = np.sum(lin_err(slice(None), slice(None))**2 / N, axis=1)**0.5 else: rmsd = np.sum((yref-integr.yout)**2 / N, axis=1)**0.5 ave_rmsd_over_atol = np.average(rmsd, axis=0)/atol if verbose: # Print statistics from pprint import pprint pprint(info) pprint(ave_rmsd_over_atol) # Plot results if plot: import matplotlib.pyplot as plt plt.figure(figsize=(6, 10)) # colors: (0.5, 0.5, 0.5), (0.5, 0.5, 1), ... base_colors = list(product([.5, 1], repeat=3))[1:-1] def color(ci, ti): return np.array(base_colors[ci % len(base_colors)])*tout[ti]/tend for ti in range(nt): plt.subplot(4, 1, 1) for si in range(nspecies): plt.plot(rd.xcenters, integr.Cout[ti, :, si], c=color(si, ti), label=None if ti < nt - 1 else rd.substance_names[si]) plt.subplot(4, 1, 2) for si in range(nspecies): plt.plot(rd.xcenters, rd.expb(yref[ti, :, si]) if logy else yref[ti, :, si], c=color(si, ti)) plt.subplot(4, 1, 3) if logy: for si in range(nspecies): plt.plot(rd.xcenters, lin_err(ti, si)/atol, c=color(si, ti)) else: for si in range(nspecies): plt.plot( rd.xcenters, (yref[ti, :, si] - integr.yout[ti, :, si])/atol, c=color(si, ti)) if N < vline_limit: for idx in range(1, 4): plt.subplot(4, 1, idx) for bi in range(N): plt.axvline(rd.x[bi], color='gray') plt.subplot(4, 1, 1) plt.title('Simulation (N={})'.format(rd.N)) plt.xlabel('x / m') plt.ylabel('C / M') plt.gca().set_yscale(yscale) plt.legend() plt.subplot(4, 1, 2) plt.title('Analytic solution') plt.gca().set_yscale(yscale) plt.subplot(4, 1, 3) plt.title('Linear rel. error / Abs. tol. (={})'.format(atol)) plt.subplot(4, 1, 4) plt.title('RMS error vs. time'.format(atol)) tspan = [tout[0], tout[-1]] for si in range(nspecies): plt.plot(tout, rmsd[:, si] / atol, c=color(si, -1)) plt.plot(tspan, [ave_rmsd_over_atol[si]]*2, c=color(si, -1), ls='--') plt.xlabel('Time / s') plt.ylabel(r'$\sqrt{\langle E^2 \rangle} / atol$') plt.tight_layout() save_and_or_show_plot(savefig=savefig) if check_conserv: tot_amount = np.zeros(tout.size) for ti in range(tout.size): for si in range(nspecies): tot_amount[ti] += rd.integrated_conc(integr.yout[ti, :, si]) if plot: plt.plot(tout, tot_amount) plt.show() assert np.allclose(tot_amount[0], tot_amount[1:]) return tout, integr.yout, info, ave_rmsd_over_atol, rd, rmsd
def test_integrators(log): logy, logt, use_log2 = log t0, tend, nt = 5.0, 17.0, 42 tout = np.linspace(t0, tend, nt+1) # Update the dict if more integrators are added: solver_kwargs = { 'scipy1': { 'atol': [1e-8, 1e-8], 'rtol': 1e-8, 'tout': tout }, 'scipy2': { 'atol': 1e-8, 'rtol': 1e-8, 'tout': (t0, tend), 'dense_output': True }, 'cvode1': { 'atol': [1e-8, 1e-8], 'rtol': 1e-8, 'method': 'bdf', 'tout': tout }, 'cvode2': { 'atol': 1e-8, 'rtol': 1e-8, 'method': 'adams', 'tout': tout, 'C0_is_log': True, 'ew_ele': True }, 'cvode3': { 'atol': [1e-8, 1e-8], 'rtol': 1e-8, 'method': 'bdf', 'tout': (t0, tend), 'ew_ele': True }, 'cvode4': { 'atol': [1e-8, 1e-8], 'rtol': 1e-8, 'method': 'bdf', 'tout': tend-t0, }, 'cvode5': { 'atol': [1e-8, 1e-8], 'rtol': 1e-8, 'method': 'bdf', 'tout': (t0, tend), 'constraints': [1.0, 1.0] } } import pycvodes if logy or pycvodes.sundials_version < (3, 2, 0): solver_kwargs.pop('cvode5') # sundials >=3.2.0 required for constraints # A -> B n = 2 k0 = 0.13 rd = ReactionDiffusion(n, [[0]], [[1]], k=[k0], logy=logy, logt=logt, use_log2=use_log2) y0 = [3.0, 1.0] results = [] for solver, kwargs in solver_kwargs.items(): _y0 = rd.logb(y0) if kwargs.get('C0_is_log', False) else y0 integr = Integration(rd, _y0, integrator=solver[:-1], **kwargs) if not kwargs.get('dense_output', False): results.append(integr.Cout) ew_ele = integr.info.get('ew_ele', None) if ew_ele is not None: assert np.all(np.abs(np.prod(ew_ele, axis=1)) < 2) for result in results[1:]: assert np.allclose(results[0][0], result[0])
def integrate_rd(D=2e-3, t0=3., tend=7., x0=0.0, xend=1.0, mu=None, N=64, nt=42, geom='f', logt=False, logy=False, logx=False, random=False, k=0.0, nstencil=3, linterpol=False, rinterpol=False, num_jacobian=False, method='bdf', scale_x=False, atol=1e-6, rtol=1e-6, efield=False, random_seed=42): if t0 == 0.0: raise ValueError("t0==0 => Dirac delta function C0 profile.") if random_seed: np.random.seed(random_seed) decay = (k != 0.0) n = 2 if decay else 1 mu = float(mu or x0) tout = np.linspace(t0, tend, nt) assert geom in 'fcs' geom = {'f': FLAT, 'c': CYLINDRICAL, 's': SPHERICAL}[geom] analytic = { FLAT: flat_analytic, CYLINDRICAL: cylindrical_analytic, SPHERICAL: spherical_analytic }[geom] # Setup the grid _x0 = log(x0) if logx else x0 _xend = log(xend) if logx else xend x = np.linspace(_x0, _xend, N+1) if random: x += (np.random.random(N+1)-0.5)*(_xend-_x0)/(N+2) rd = ReactionDiffusion( 2 if decay else 1, [[0]] if decay else [], [[1]] if decay else [], [k] if decay else [], N, D=[D]*(2 if decay else 1), z_chg=[1]*(2 if decay else 1), mobility=[0.01]*(2 if decay else 1), x=x, geom=geom, logy=logy, logt=logt, logx=logx, nstencil=nstencil, lrefl=not linterpol, rrefl=not rinterpol, xscale=1/(x[1]-x[0]) if scale_x else 1.0 ) if efield: if geom != FLAT: raise ValueError("Only analytic sol. for flat drift implemented.") rd.efield = _efield_cb(rd.xcenters) # Calc initial conditions / analytic reference values t = tout.copy().reshape((nt, 1)) yref = analytic(rd.xcenters, t, D, mu, x0, xend, 0.01 if efield else 0, logy, logx).reshape(nt, N, 1) if decay: yref = np.concatenate((yref, yref), axis=2) if logy: yref[:, :, 0] += -k*t yref[:, :, 1] += np.log(1-np.exp(-k*t)) else: yref[:, :, 0] *= np.exp(-k*t) yref[:, :, 1] *= 1-np.exp(-k*t) # Run the integration integr = run(rd, yref[0, ...], tout, atol=atol, rtol=rtol, with_jacobian=(not num_jacobian), method=method, C0_is_log=logy)