def test_symbol_replacements(self): a = pybamm.Parameter("a") b = pybamm.Parameter("b") c = pybamm.Parameter("c") d = pybamm.Parameter("d") replacer = pybamm.SymbolReplacer({a: b, c: d}) for symbol_in, symbol_out in [ (a, b), # just the symbol (a + a, b + b), # binary operator (2 * pybamm.sin(a), 2 * pybamm.sin(b)), # function (3 * b, 3 * b), # no replacement (a + c, b + d), # two replacements ]: replaced_symbol = replacer.process_symbol(symbol_in) self.assertEqual(replaced_symbol.id, symbol_out.id) var1 = pybamm.Variable("var 1", domain="dom 1") var2 = pybamm.Variable("var 2", domain="dom 2") var3 = pybamm.Variable("var 3", domain="dom 1") conc = pybamm.concatenation(var1, var2) replacer = pybamm.SymbolReplacer({var1: var3}) replaced_symbol = replacer.process_symbol(conc) self.assertEqual(replaced_symbol.id, pybamm.concatenation(var3, var2).id)
def test_advanced_functions(self): a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) y = np.array([5, 3]) # func = a * pybamm.exp(b) self.assertAlmostEqual(func.diff(a).evaluate(y=y)[0], np.exp(3)) func = pybamm.exp(a + 2 * b + a * b) + a * pybamm.exp(b) self.assertEqual( func.diff(a).evaluate(y=y), (4 * np.exp(3 * 5 + 5 + 2 * 3) + np.exp(3)) ) self.assertEqual( func.diff(b).evaluate(y=y), np.exp(3) * (7 * np.exp(3 * 5 + 5 + 3) + 5) ) # func = pybamm.sin(pybamm.cos(a * 4) / 2) * pybamm.cos(4 * pybamm.exp(b / 3)) self.assertEqual( func.diff(a).evaluate(y=y), -2 * np.sin(20) * np.cos(np.cos(20) / 2) * np.cos(4 * np.exp(1)), ) self.assertEqual( func.diff(b).evaluate(y=y), -4 / 3 * np.exp(1) * np.sin(4 * np.exp(1)) * np.sin(np.cos(20) / 2), ) # func = pybamm.sin(a * b) self.assertEqual(func.diff(a).evaluate(y=y), 3 * np.cos(15))
def test_functions(self): y = pybamm.StateVector(slice(0, 4)) u = pybamm.StateVector(slice(0, 2)) v = pybamm.StateVector(slice(2, 4)) const = pybamm.Scalar(1) y0 = np.array([1.0, 2.0, 3.0, 4.0]) func = pybamm.sin(u) jacobian = np.array([[np.cos(1), 0, 0, 0], [0, np.cos(2), 0, 0]]) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.cos(v) jacobian = np.array([[0, 0, -np.sin(3), 0], [0, 0, 0, -np.sin(4)]]) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.sin(3 * u * v) jacobian = np.array( [ [9 * np.cos(9), 0, 3 * np.cos(9), 0], [0, 12 * np.cos(24), 0, 6 * np.cos(24)], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.cos(5 * pybamm.exp(u + v)) jacobian = np.array( [ [ -5 * np.exp(4) * np.sin(5 * np.exp(4)), 0, -5 * np.exp(4) * np.sin(5 * np.exp(4)), 0, ], [ 0, -5 * np.exp(6) * np.sin(5 * np.exp(6)), 0, -5 * np.exp(6) * np.sin(5 * np.exp(6)), ], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) # when child evaluates to number func = pybamm.Sin(const) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(0, dfunc_dy) # several children func = pybamm.Function(test_multi_var_function, 2 * y, 3 * y) jacobian = np.diag(5 * np.ones(4)) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())
def test_functions(self): y = pybamm.StateVector(slice(0, 8)) u = pybamm.StateVector(slice(0, 2), slice(4, 6)) v = pybamm.StateVector(slice(2, 4), slice(6, 8)) y0 = np.arange(1, 9) const = pybamm.Scalar(1) func = pybamm.sin(u) jacobian = np.array( [ [np.cos(1), 0, 0, 0, 0, 0, 0, 0], [0, np.cos(2), 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, np.cos(5), 0, 0, 0], [0, 0, 0, 0, 0, np.cos(6), 0, 0], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.cos(v) jacobian = np.array( [ [0, 0, -np.sin(3), 0, 0, 0, 0, 0], [0, 0, 0, -np.sin(4), 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, -np.sin(7), 0], [0, 0, 0, 0, 0, 0, 0, -np.sin(8)], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.sin(3 * u * v) jacobian = np.array( [ [9 * np.cos(9), 0, 3 * np.cos(9), 0, 0, 0, 0, 0], [0, 12 * np.cos(24), 0, 6 * np.cos(24), 0, 0, 0, 0], [0, 0, 0, 0, 21 * np.cos(105), 0, 15 * np.cos(105), 0], [0, 0, 0, 0, 0, 24 * np.cos(144), 0, 18 * np.cos(144)], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) # when child evaluates to number func = pybamm.sin(const) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(0, dfunc_dy) # several children func = pybamm.Function(test_multi_var_function, 2 * y, 3 * y) jacobian = np.diag(5 * np.ones(8)) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())
def test_sin(self): a = pybamm.InputParameter("a") fun = pybamm.sin(a) self.assertIsInstance(fun, pybamm.Sin) self.assertEqual(fun.children[0].id, a.id) self.assertEqual(fun.evaluate(inputs={"a": 3}), np.sin(3)) h = 0.0000001 self.assertAlmostEqual( fun.diff(a).evaluate(inputs={"a": 3}), (pybamm.sin(pybamm.Scalar(3 + h)).evaluate() - fun.evaluate(inputs={"a": 3})) / h, places=5, )
def test_sin(self): a = pybamm.Scalar(3) fun = pybamm.sin(a) self.assertIsInstance(fun, pybamm.Sin) self.assertEqual(fun.children[0].id, a.id) self.assertEqual(fun.evaluate(), np.sin(3)) self.assertEqual(fun.diff(a).evaluate(), np.cos(3))
def test_diff(self): a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) y = np.array([5]) func = pybamm.Function(test_function, a) self.assertEqual(func.diff(a).evaluate(y=y), 2) self.assertEqual(func.diff(func).evaluate(), 1) func = pybamm.sin(a) self.assertEqual(func.evaluate(y=y), np.sin(a.evaluate(y=y))) self.assertEqual(func.diff(a).evaluate(y=y), np.cos(a.evaluate(y=y))) func = pybamm.exp(a) self.assertEqual(func.evaluate(y=y), np.exp(a.evaluate(y=y))) self.assertEqual(func.diff(a).evaluate(y=y), np.exp(a.evaluate(y=y))) # multiple variables func = pybamm.Function(test_multi_var_function, 4 * a, 3 * a) self.assertEqual(func.diff(a).evaluate(y=y), 7) func = pybamm.Function(test_multi_var_function, 4 * a, 3 * b) self.assertEqual(func.diff(a).evaluate(y=np.array([5, 6])), 4) self.assertEqual(func.diff(b).evaluate(y=np.array([5, 6])), 3) func = pybamm.Function(test_multi_var_function_cube, 4 * a, 3 * b) self.assertEqual(func.diff(a).evaluate(y=np.array([5, 6])), 4) self.assertEqual( func.diff(b).evaluate(y=np.array([5, 6])), 3 * 3 * (3 * 6) ** 2 ) # exceptions func = pybamm.Function( test_multi_var_function_cube, 4 * a, 3 * b, derivative="derivative" ) with self.assertRaises(ValueError): func.diff(a)
def beta(T): T_inf = pybamm.FunctionParameter("T_inf", {"x": self.x}) h = pybamm.Parameter("h") eps0 = pybamm.Parameter("eps0") return (1e-4 * (1.0 + 5.0 * pybamm.sin(3 * np.pi * T / 200.0) + pybamm.exp(0.02 * T)) / eps0 + h * (T_inf - T) / (T_inf**4 - T**4) / eps0)
def test_model_solver_with_bounds(self): # Note: we need a better test case to test this functionality properly # Create model model = pybamm.BaseModel() var1 = pybamm.Variable("var1", bounds=(0, 10)) model.algebraic = {var1: pybamm.sin(var1) + 1} model.initial_conditions = {var1: pybamm.Scalar(3)} model.variables = {"var1": var1} # Solve solver = pybamm.CasadiAlgebraicSolver(tol=1e-12) solution = solver.solve(model) np.testing.assert_array_almost_equal(solution["var1"].data, 3 * np.pi / 2)
def test_model_solver_minimize_with_bounds(self): # Note: we need a better test case to test this functionality properly # Create model model = pybamm.BaseModel() var1 = pybamm.Variable("var1", bounds=(0, 10)) model.algebraic = {var1: pybamm.sin(var1) + 1} model.initial_conditions = {var1: pybamm.Scalar(3)} model.variables = {"var1": var1} # Solve solver = pybamm.AlgebraicSolver("minimize", tol=1e-16) solution = solver.solve(model) np.testing.assert_array_almost_equal( model.variables["var1"].evaluate(t=None, y=solution.y), 3 * np.pi / 2, decimal=4, )
def __init__(self, e_class): self.dim_dict=e_class.dim_dict self.nd_dict=e_class.nd_param.nd_param_dict self.model=pybamm.BaseModel() self.parameter_dict={} self.pybam_val_dict={} self.simulation_options=e_class.simulation_options self.dim_keys=self.nd_dict.keys() self.time=e_class.time_vec for key in self.dim_keys: self.parameter_dict[key]=pybamm.InputParameter(key) self.pybam_val_dict[key]=None self.current=pybamm.Variable("current") self.theta=pybamm.Variable("theta") if self.simulation_options["method"]=="dcv": Edc_forward = pybamm.t Edc_backwards = -(pybamm.t - 2*self.parameter_dict["tr"]) E_t = self.parameter_dict["E_start"]+ \ (pybamm.t <= self.parameter_dict["tr"]) * Edc_forward + \ (pybamm.t > self.parameter_dict["tr"]) * Edc_backwards elif self.simulation_options["method"]=="sinusoidal": E_t=self.parameter_dict["E_start"]+self.parameter_dict["d_E"]+(self.parameter_dict["d_E"]*pybamm.sin((self.parameter_dict["nd_omega"]*pybamm.t)+self.parameter_dict["phase"])) elif self.simulation_options["method"]=="ramped": Edc_forward = pybamm.t Edc_backwards = -(pybamm.t - 2*self.parameter_dict["tr"]) E_t = self.parameter_dict["E_start"]+ \ (pybamm.t <= self.parameter_dict["tr"]) * Edc_forward + \ (pybamm.t > self.parameter_dict["tr"]) * Edc_backwards+\ (self.parameter_dict["d_E"]*pybamm.sin((self.parameter_dict["nd_omega"]*pybamm.t)+self.parameter_dict["phase"])) Er=E_t-(self.parameter_dict["Ru"]*self.current) ErE0=Er-self.parameter_dict["E_0"] alpha=self.parameter_dict["alpha"] if "Cdlinv" not in e_class.optim_list: Cdlp=self.parameter_dict["Cdl"]*(1+self.parameter_dict["CdlE1"]*Er+self.parameter_dict["CdlE2"]*(Er**2)+self.parameter_dict["CdlE3"]*(Er**3)) else: Cdlp=(pybamm.t <= self.parameter_dict["tr"]) *(self.parameter_dict["Cdl"]*(1+self.parameter_dict["CdlE1"]*Er+self.parameter_dict["CdlE2"]*(Er**2)+self.parameter_dict["CdlE3"]*(Er**3)))+\ (pybamm.t > self.parameter_dict["tr"]) *(self.parameter_dict["Cdlinv"]*(1+self.parameter_dict["CdlE1inv"]*Er+self.parameter_dict["CdlE2inv"]*(Er**2)+self.parameter_dict["CdlE3inv"]*(Er**3))) self.model.variables={"current":self.current, "theta":self.theta} d_thetadt=((1-self.theta)*self.parameter_dict["k_0"]*pybamm.exp((1-alpha)*ErE0))-(self.theta*self.parameter_dict["k_0"]*pybamm.exp((-alpha)*ErE0)) dIdt=(E_t.diff(pybamm.t)-(self.current/Cdlp)+self.parameter_dict["gamma"]*d_thetadt*(1/Cdlp))/self.parameter_dict["Ru"] self.model.rhs={self.current:dIdt, self.theta:d_thetadt} self.disc=pybamm.Discretisation() self.model.initial_conditions={self.theta:pybamm.Scalar(1), self.current:pybamm.Scalar(0)} self.disc.process_model(self.model)
def get_error(m): # create mesh and discretisation p2d, uniform in x mesh = get_p2d_mesh_for_testing(3, m) disc = pybamm.Discretisation(mesh, spatial_methods) submesh = mesh["negative particle"] r = submesh[0].nodes r_edge = pybamm.standard_spatial_vars.r_n_edge N = r_edge ** 2 * pybamm.sin(r_edge) div_eqn = pybamm.div(N) # Define exact solutions # N = r**2*sin(r) --> div(N) = 4*r*sin(r) - r**2*cos(r) div_exact = 4 * r * np.sin(r) + r ** 2 * np.cos(r) div_exact = np.kron(np.ones(len(submesh)), div_exact) # Discretise and evaluate div_eqn_disc = disc.process_symbol(div_eqn) div_approx = div_eqn_disc.evaluate() return div_approx[:, 0] - div_exact
def get_error(n): # create mesh and discretisation (single particle) mesh = get_mesh_for_testing(rpts=n) disc = pybamm.Discretisation(mesh, spatial_methods) submesh = mesh["negative particle"] r = submesh.nodes r_edge = pybamm.SpatialVariableEdge("r_n", domain=["negative particle"]) # Define flux and bcs N = r_edge ** 2 * pybamm.sin(r_edge) div_eqn = pybamm.div(N) # Define exact solutions # N = r**3 --> div(N) = 5 * r**2 div_exact = 4 * r * np.sin(r) + r ** 2 * np.cos(r) # Discretise and evaluate div_eqn_disc = disc.process_symbol(div_eqn) div_approx = div_eqn_disc.evaluate() # Return difference between approx and exact return div_approx[:, 0] - div_exact
def get_error(n): # create mesh and discretisation (single particle) mesh = get_mesh_for_testing(rpts=n) disc = pybamm.Discretisation(mesh, spatial_methods) submesh = mesh["negative particle"] r = submesh[0].nodes r_edge = pybamm.standard_spatial_vars.r_n_edge # Define flux and bcs N = r_edge * pybamm.sin(r_edge) div_eqn = pybamm.div(N) # Define exact solutions # N = r*sin(r) --> div(N) = 3*sin(r) + r*cos(r) div_exact = 3 * np.sin(r) + r * np.cos(r) # Discretise and evaluate div_eqn_disc = disc.process_symbol(div_eqn) div_approx = div_eqn_disc.evaluate() # Return difference between approx and exact return div_approx[:, 0] - div_exact
def get_error(m): # create mesh and discretisation p2d, x-dependent mesh = get_p2d_mesh_for_testing(6, m) disc = pybamm.Discretisation(mesh, spatial_methods) submesh_r = mesh["negative particle"] r = submesh_r.nodes r_edge = pybamm.standard_spatial_vars.r_n_edge x = pybamm.standard_spatial_vars.x_n N = pybamm.PrimaryBroadcast( x, "negative particle") * (r_edge**2 * pybamm.sin(r_edge)) div_eqn = pybamm.div(N) # Define exact solutions # N = r**2*sin(r) --> div(N) = 4*r*sin(r) - r**2*cos(r) div_exact = 4 * r * np.sin(r) + r**2 * np.cos(r) div_exact = np.kron(mesh["negative electrode"].nodes, div_exact) # Discretise and evaluate div_eqn_disc = disc.process_symbol(div_eqn) div_approx = div_eqn_disc.evaluate() return div_approx[:, 0] - div_exact
def my_fun(t, A, omega): return A * pybamm.sin(2 * np.pi * omega * t)
def __init__(self, param=None): # Set fixed parameters here if param is None: param = pybamm.ParameterValues({ "Far-field concentration of S(soln) [mol cm-3]": 1e-6, "Diffusion Constant [cm2 s-1]": 7.2e-6, "Faraday Constant [C mol-1]": 96485.3328959, "Gas constant [J K-1 mol-1]": 8.314459848, "Electrode Area [cm2]": 0.07, "Temperature [K]": 273.0, "Voltage frequency [rad s-1]": 9.0152, "Voltage start [V]": 0.4, "Voltage reverse [V]": -0.4, "Voltage amplitude [V]": 0.0, # 0.05, "Scan Rate [V s-1]": 0.05, "Electrode Coverage [mol cm2]": 6.5e-12, }) # Create dimensional fixed parameters D = pybamm.Parameter("Diffusion Constant [cm2 s-1]") F = pybamm.Parameter("Faraday Constant [C mol-1]") R = pybamm.Parameter("Gas constant [J K-1 mol-1]") a = pybamm.Parameter("Electrode Area [cm2]") T = pybamm.Parameter("Temperature [K]") omega_d = pybamm.Parameter("Voltage frequency [rad s-1]") E_start_d = pybamm.Parameter("Voltage start [V]") E_reverse_d = pybamm.Parameter("Voltage reverse [V]") deltaE_d = pybamm.Parameter("Voltage amplitude [V]") v = pybamm.Parameter("Scan Rate [V s-1]") Gamma = pybamm.Parameter("Electrode Coverage [mol cm2]") # Create dimensional input parameters E0_d = pybamm.InputParameter("Reversible Potential [V]") k0_d = pybamm.InputParameter("Redox Rate [s-1]") kcat_d = pybamm.InputParameter("Catalytic Rate [cm3 mol-l s-1]") alpha = pybamm.InputParameter("Symmetry factor [non-dim]") Cdl_d = pybamm.InputParameter("Capacitance [F]") Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]") # Create scaling factors for non-dimensionalisation E_0 = R * T / F T_0 = E_0 / v I_0 = F * a * Gamma / T L_0 = pybamm.sqrt(D * T_0) # Non-dimensionalise parameters E0 = E0_d / E_0 k0 = k0_d * T_0 kcat = kcat_d * Gamma * L_0 / D Cdl = Cdl_d * a * E_0 / (I_0 * T_0) Ru = Ru_d * I_0 / E_0 omega = 2 * np.pi * omega_d * T_0 E_start = E_start_d / E_0 E_reverse = E_reverse_d / E_0 t_reverse = E_start - E_reverse deltaE = deltaE_d / E_0 # Input voltage protocol Edc_forward = -pybamm.t Edc_backwards = pybamm.t - 2 * t_reverse Eapp = E_start + \ (pybamm.t <= t_reverse) * Edc_forward + \ (pybamm.t > t_reverse) * Edc_backwards + \ deltaE * pybamm.sin(omega * pybamm.t) # create PyBaMM model object model = pybamm.BaseModel() # Create state variables for model theta = pybamm.Variable("O(surf) [non-dim]") c = pybamm.Variable("S(soln) [non-dim]", domain="solution") i = pybamm.Variable("Current [non-dim]") # Effective potential Eeff = Eapp - i * Ru # Faridaic current (Butler Volmer) i_f = k0 * ((1 - theta) * pybamm.exp( (1 - alpha) * (Eeff - E0)) - theta * pybamm.exp(-alpha * (Eeff - E0))) c_at_electrode = pybamm.BoundaryValue(c, "left") # Catalytic current i_cat = kcat * c_at_electrode * (1 - theta) # ODE equations model.rhs = { theta: i_f + i_cat, i: 1 / (Cdl * Ru) * (i_f + Cdl * Eapp.diff(pybamm.t) - i - i_cat), c: pybamm.div(pybamm.grad(c)), } # algebraic equations (none) model.algebraic = {} # Boundary and initial conditions model.boundary_conditions = { c: { "right": (pybamm.Scalar(1), "Dirichlet"), "left": (i_cat, "Neumann"), } } model.initial_conditions = { theta: pybamm.Scalar(1), i: Cdl * Eapp.diff(pybamm.t), c: pybamm.Scalar(1), } # set spatial variables and solution domain geometry x = pybamm.SpatialVariable('x', domain="solution") model.geometry = pybamm.Geometry({ "solution": { x: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(20) } } }) model.var_pts = {x: 100} # Using Finite Volume discretisation on an expanding 1D grid model.submesh_types = { "solution": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'}) } model.spatial_methods = {"solution": pybamm.FiniteVolume()} # model variables model.variables = { "Current [non-dim]": i, "O(surf) [non-dim]": theta, "S(soln) at electrode [non-dim]": c_at_electrode, "Applied Voltage [non-dim]": Eapp, } # -------------------------------- # Set model parameters param.process_model(model) geometry = model.geometry param.process_geometry(geometry) # Create mesh and discretise model mesh = pybamm.Mesh(geometry, model.submesh_types, model.var_pts) disc = pybamm.Discretisation(mesh, model.spatial_methods) disc.process_model(model) # Create solver model.convert_to_format = 'python' solver = pybamm.ScipySolver(method='BDF', rtol=1e-6, atol=1e-6) #solver = pybamm.CasadiSolver(mode='fast', rtol=1e-8, atol=1e-10) # Store discretised model and solver self._model = model self._param = param self._solver = solver self._omega_d = param.process_symbol(omega_d).evaluate() self._I_0 = param.process_symbol(I_0).evaluate() self._T_0 = param.process_symbol(T_0).evaluate()
def __init__(self, n=100, max_x=10, param=None): # Set fixed parameters here if param is None: param = pybamm.ParameterValues({ "Far-field concentration of A [mol cm-3]": 1e-6, "Diffusion Constant [cm2 s-1]": 7.2e-6, "Faraday Constant [C mol-1]": 96485.3328959, "Gas constant [J K-1 mol-1]": 8.314459848, "Electrode Area [cm2]": 0.07, "Temperature [K]": 297.0, "Voltage frequency [rad s-1]": 9.0152, "Voltage start [V]": 0.5, "Voltage reverse [V]": -0.1, "Voltage amplitude [V]": 0.08, "Scan Rate [V s-1]": 0.08941, }) # Create dimensional fixed parameters c_inf = pybamm.Parameter("Far-field concentration of A [mol cm-3]") D = pybamm.Parameter("Diffusion Constant [cm2 s-1]") F = pybamm.Parameter("Faraday Constant [C mol-1]") R = pybamm.Parameter("Gas constant [J K-1 mol-1]") S = pybamm.Parameter("Electrode Area [cm2]") T = pybamm.Parameter("Temperature [K]") E_start_d = pybamm.Parameter("Voltage start [V]") E_reverse_d = pybamm.Parameter("Voltage reverse [V]") deltaE_d = pybamm.Parameter("Voltage amplitude [V]") v = pybamm.Parameter("Scan Rate [V s-1]") # Create dimensional input parameters E0 = pybamm.InputParameter("Reversible Potential [non-dim]") k0 = pybamm.InputParameter("Reaction Rate [non-dim]") alpha = pybamm.InputParameter("Symmetry factor [non-dim]") Cdl = pybamm.InputParameter("Capacitance [non-dim]") Ru = pybamm.InputParameter("Uncompensated Resistance [non-dim]") omega_d = pybamm.InputParameter("Voltage frequency [rad s-1]") E0_d = pybamm.InputParameter("Reversible Potential [V]") k0_d = pybamm.InputParameter("Reaction Rate [s-1]") alpha = pybamm.InputParameter("Symmetry factor [non-dim]") Cdl_d = pybamm.InputParameter("Capacitance [F]") Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]") # Create scaling factors for non-dimensionalisation E_0 = R * T / F T_0 = E_0 / v L_0 = pybamm.sqrt(D * T_0) I_0 = D * F * S * c_inf / L_0 # Non-dimensionalise parameters E0 = E0_d / E_0 k0 = k0_d * L_0 / D Cdl = Cdl_d * S * E_0 / (I_0 * T_0) Ru = Ru_d * I_0 / E_0 omega = 2 * np.pi * omega_d * T_0 E_start = E_start_d / E_0 E_reverse = E_reverse_d / E_0 t_reverse = E_start - E_reverse deltaE = deltaE_d / E_0 # Input voltage protocol Edc_forward = -pybamm.t Edc_backwards = pybamm.t - 2 * t_reverse Eapp = E_start + \ (pybamm.t <= t_reverse) * Edc_forward + \ (pybamm.t > t_reverse) * Edc_backwards + \ deltaE * pybamm.sin(omega * pybamm.t) # create PyBaMM model object model = pybamm.BaseModel() # Create state variables for model theta = pybamm.Variable("ratio_A", domain="solution") i = pybamm.Variable("Current") # Effective potential Eeff = Eapp - i * Ru # Faradaic current i_f = pybamm.BoundaryGradient(theta, "left") # ODE equations model.rhs = { theta: pybamm.div(pybamm.grad(theta)), i: 1 / (Cdl * Ru) * (-i_f + Cdl * Eapp.diff(pybamm.t) - i), } # algebraic equations (none) model.algebraic = {} # Butler-volmer boundary condition at electrode theta_at_electrode = pybamm.BoundaryValue(theta, "left") butler_volmer = k0 * (theta_at_electrode * pybamm.exp(-alpha * (Eeff - E0)) - (1 - theta_at_electrode) * pybamm.exp( (1 - alpha) * (Eeff - E0))) # Boundary and initial conditions model.boundary_conditions = { theta: { "right": (pybamm.Scalar(1), "Dirichlet"), "left": (butler_volmer, "Neumann"), } } model.initial_conditions = { theta: pybamm.Scalar(1), i: Cdl * (-1.0 + deltaE * omega), } # set spatial variables and solution domain geometry x = pybamm.SpatialVariable('x', domain="solution") default_geometry = pybamm.Geometry({ "solution": { x: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(max_x) } } }) default_var_pts = {x: n} # Using Finite Volume discretisation on an expanding 1D grid for solution default_submesh_types = { "solution": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'}) } default_spatial_methods = {"solution": pybamm.FiniteVolume()} # model variables model.variables = { "Current [non-dim]": i, } #-------------------------------- # Set model parameters param.process_model(model) geometry = default_geometry param.process_geometry(geometry) # Create mesh and discretise model mesh = pybamm.Mesh(geometry, default_submesh_types, default_var_pts) disc = pybamm.Discretisation(mesh, default_spatial_methods) disc.process_model(model) # Create solver solver = pybamm.CasadiSolver( mode="fast", rtol=1e-9, atol=1e-9, extra_options_setup={'print_stats': False}) #model.convert_to_format = 'jax' #solver = pybamm.JaxSolver(method='BDF') #model.convert_to_format = 'python' #solver = pybamm.ScipySolver(method='BDF') # Store discretised model and solver self._model = model self._solver = solver self._fast_solver = None self._omega_d = param["Voltage frequency [rad s-1]"] self._I_0 = param.process_symbol(I_0).evaluate() self._T_0 = param.process_symbol(T_0).evaluate() self._E_0 = param.process_symbol(E_0).evaluate() self._L_0 = param.process_symbol(L_0).evaluate() self._S = param.process_symbol(S).evaluate() self._D = param.process_symbol(D).evaluate() self._default_var_points = default_var_pts