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 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 integrate_rd(D=-3e-1, t0=0.0, tend=7., x0=0.1, xend=1.0, N=1024, base=0.5, offset=0.25, 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, savefig='None', atol=1e-6, rtol=1e-6, random_seed=42, surf_chg=(0.0, 0.0), sigma_q=101, sigma_skew=0.5, verbose=False, eps_rel=80.10, use_log2=False): """ A negative D (diffusion coefficent) denotes: mobility := -D D := 0 A positive D calculates mobility from Einstein-Smoluchowski relation """ assert 0 <= base and base <= 1 assert 0 <= offset and offset <= 1 if random_seed: np.random.seed(random_seed) n = 2 if D < 0: mobility = -D D = 0 else: mobility = electrical_mobility_from_D(D, 1, 298.15) print(D, mobility) # 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) # Setup the system stoich_active = [] stoich_prod = [] k = [] rd = ReactionDiffusion( n, stoich_active, stoich_prod, k, N, D=[D, D], z_chg=[1, -1], mobility=[mobility, -mobility], x=x, geom=geom, logy=logy, logt=logt, logx=logx, nstencil=nstencil, lrefl=lrefl, rrefl=rrefl, auto_efield=True, surf_chg=surf_chg, eps_rel=eps_rel, # water at 20 deg C faraday_const=1, vacuum_permittivity=1, use_log2=use_log2 ) # Initial conditions sigma = (xend-x0)/sigma_q sigma = [(1-sigma_skew)*sigma, sigma_skew*sigma] y0 = np.vstack(pair_of_gaussians( rd.xcenters, [base+offset, base-offset], sigma, logy, logx, geom, use_log2)).transpose() if logy: y0 = sigm(y0) if plot: # Plot initial E-field import matplotlib.pyplot as plt plt.figure(figsize=(6, 10)) rd.calc_efield((rd.expb(y0) if logy else y0).flatten()) plt.subplot(4, 1, 3) plt.plot(rd.xcenters, rd.efield, label="E at t=t0") plt.plot(rd.xcenters, rd.xcenters*0, label="0") # Run the integration tout = np.linspace(t0, tend, nt) integr = run(rd, y0, tout, atol=atol, rtol=rtol, sigm_damp=True, C0_is_log=logy, with_jacobian=(not num_jacobian), method=method) Cout = integr.Cout if verbose: print(integr.info) # Plot results if plot: def _plot(y, ttl=None, **kwargs): plt.plot(rd.xcenters, y, **kwargs) plt.xlabel((('log_%s({})' % ('2' if use_log2 else 'e')) if logx else '{}').format('x / m')) plt.ylabel('C / M') if ttl: plt.title(ttl) for i in range(nt): plt.subplot(4, 1, 1) c = 1-tout[i]/tend c = (1.0-c, .5-c/2, .5-c/2) _plot(Cout[i, :, 0], 'Simulation (N={})'.format(rd.N), c=c, label='$z_A=1$' if i == nt-1 else None) _plot(Cout[i, :, 1], c=c[::-1], label='$z_B=-1$' if i == nt-1 else None) plt.legend() plt.subplot(4, 1, 2) delta_y = Cout[i, :, 0] - Cout[i, :, 1] _plot(delta_y, 'Diff'.format(rd.N), c=[c[2], c[0], c[1]], label='A-B (positive excess)' if i == nt-1 else None) plt.legend(loc='best') plt.xlabel("$x~/~m$") plt.ylabel(r'Concentration / M') ylim = plt.gca().get_ylim() if N < 100: plt.vlines(rd.x, ylim[0], ylim[1], linewidth=1.0, alpha=0.2, colors='gray') plt.subplot(4, 1, 3) plt.plot(rd.xcenters, rd.efield, label="E at t=tend") plt.xlabel("$x~/~m$") plt.ylabel("$E~/~V\cdot m^{-1}$") plt.legend() for i in range(3): plt.subplot(4, 1, i+1) ylim = plt.gca().get_ylim() for d in (-1, 1): center_loc = [x0+(base+d*offset)*(xend-x0)]*2 plt.plot(rd.logb(center_loc) if logx else center_loc, ylim, '--k') plt.subplot(4, 1, 4) for i in range(n): amount = [rd.integrated_conc(Cout[j, :, i]) for j in range(nt)] plt.plot(tout, amount, c=c[::(1, -1)[i]], label=chr(ord('A')+i)) plt.xlabel('Time / s') plt.ylabel('Amount / mol') plt.legend(loc='best') plt.tight_layout() save_and_or_show_plot(savefig=savefig) return tout, Cout, integr.info, rd