def test_sqrt(self): a = pybamm.InputParameter("a") fun = pybamm.sqrt(a) self.assertIsInstance(fun, pybamm.Sqrt) self.assertEqual(fun.evaluate(inputs={"a": 3}), np.sqrt(3)) h = 0.0000001 self.assertAlmostEqual( fun.diff(a).evaluate(inputs={"a": 3}), (pybamm.sqrt(pybamm.Scalar(3 + h)).evaluate() - fun.evaluate(inputs={"a": 3})) / h, places=5, )
def test_model_solver_failure(self): # Create model model = pybamm.BaseModel() var = pybamm.Variable("var") model.rhs = {var: -pybamm.sqrt(var)} model.initial_conditions = {var: 1} # add events so that safe mode is used (won't be triggered) model.events = [pybamm.Event("10", var - 10)] # No need to set parameters; can use base discretisation (no spatial operators) # create discretisation disc = pybamm.Discretisation() model_disc = disc.process_model(model, inplace=False) solver = pybamm.CasadiSolver( extra_options_call={"regularity_check": False}) # Solve with failure at t=2 t_eval = np.linspace(0, 20, 100) with self.assertRaises(pybamm.SolverError): solver.solve(model_disc, t_eval) # Solve with failure at t=0 model.initial_conditions = {var: 0} model_disc = disc.process_model(model, inplace=False) t_eval = np.linspace(0, 20, 100) with self.assertRaises(pybamm.SolverError): solver.solve(model_disc, t_eval)
def electrolyte_conductivity_Landesfeind2019_base(c_e, T, coeffs): """ Conductivity of LiPF6 in solvent_X as a function of ion concentration and Temperature. The data comes from [1]. References ---------- .. [1] Landesfeind, J. and Gasteiger, H.A., 2019. Temperature and Concentration Dependence of the Ionic Transport Properties of Lithium-Ion Battery Electrolytes. Journal of The Electrochemical Society, 166(14), pp.A3079-A3097. ---------- c_e: :class: `numpy.Array` Dimensional electrolyte concentration T: :class: `numpy.Array` Dimensional temperature coeffs: :class: `numpy.Array` Fitting parameter coefficients Returns ------- :`numpy.Array` Electrolyte diffusivity """ c = c_e / 1000 # mol.m-3 -> mol.l p1, p2, p3, p4, p5, p6 = coeffs A = p1 * (1 + (T - p2)) B = 1 + p3 * sqrt(c) + p4 * (1 + p5 * exp(1000 / T)) * c C = 1 + c**4 * (p6 * exp(1000 / T)) sigma_e = A * c * B / C # mS.cm-1 return sigma_e / 10
def test_model_ode_integrate_failure(self): # Turn off warnings to ignore sqrt error warnings.simplefilter("ignore") model = pybamm.BaseModel() var = pybamm.Variable("var") model.rhs = {var: -pybamm.sqrt(var)} model.initial_conditions = {var: 1} disc = pybamm.Discretisation() disc.process_model(model) t_eval = np.linspace(0, 3, 100) solver = pybamm.ScikitsOdeSolver() # Expect solver to fail when y goes negative with self.assertRaises(pybamm.SolverError): solver.solve(model, t_eval) # Turn warnings back on warnings.simplefilter("default")
def test_solver_only_works_with_jax(self): model = pybamm.BaseModel() var = pybamm.Variable("var") model.rhs = {var: -pybamm.sqrt(var)} model.initial_conditions = {var: 1} # No need to set parameters; can use base discretisation (no spatial operators) # create discretisation disc = pybamm.Discretisation() disc.process_model(model) t_eval = np.linspace(0, 3, 100) # solver needs a model converted to jax for convert_to_format in ["casadi", "python", "something_else"]: model.convert_to_format = convert_to_format solver = pybamm.JaxSolver() with self.assertRaisesRegex(RuntimeError, "must be converted to JAX"): solver.solve(model, t_eval)
def test_model_solver_failure(self): # Turn off warnings to ignore sqrt error warnings.simplefilter("ignore") model = pybamm.BaseModel() model.convert_to_format = "python" var = pybamm.Variable("var") model.rhs = {var: -pybamm.sqrt(var)} model.initial_conditions = {var: 1} # No need to set parameters; can use base discretisation (no spatial operators) # create discretisation disc = pybamm.Discretisation() disc.process_model(model) t_eval = np.linspace(0, 3, 100) solver = pybamm.ScipySolver(rtol=1e-8, atol=1e-8, method="RK45") # Expect solver to fail when y goes negative with self.assertRaises(pybamm.SolverError): solver.solve(model, t_eval) # Turn warnings back on warnings.simplefilter("default")
def test_model_solver_events(self): # Create model model = pybamm.BaseModel() whole_cell = ["negative electrode", "separator", "positive electrode"] var1 = pybamm.Variable("var1", domain=whole_cell) var2 = pybamm.Variable("var2", domain=whole_cell) model.rhs = {var1: 0.1 * var1} model.algebraic = {var2: 2 * var1 - var2} model.initial_conditions = {var1: 1, var2: 2} model.events = [ pybamm.Event("var1 = 1.5", pybamm.min(var1 - 1.5)), pybamm.Event("var2 = 2.5", pybamm.min(var2 - 2.5)), ] disc = get_discretisation_for_testing() disc.process_model(model) # Solve using "safe" mode solver = pybamm.CasadiSolver(mode="safe", rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 5, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y.full()[0, :-1], 1.5) np.testing.assert_array_less(solution.y.full()[-1, :-1], 2.5) np.testing.assert_equal(solution.t_event[0], solution.t[-1]) np.testing.assert_array_equal(solution.y_event[:, 0], solution.y.full()[:, -1]) np.testing.assert_array_almost_equal(solution.y.full()[0], np.exp(0.1 * solution.t), decimal=5) np.testing.assert_array_almost_equal(solution.y.full()[-1], 2 * np.exp(0.1 * solution.t), decimal=5) # Solve using "safe" mode with debug off pybamm.settings.debug_mode = False solver = pybamm.CasadiSolver(mode="safe", rtol=1e-8, atol=1e-8, dt_max=1) t_eval = np.linspace(0, 5, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y.full()[0], 1.5) np.testing.assert_array_less(solution.y.full()[-1], 2.5 + 1e-10) # test the last entry is exactly 2.5 np.testing.assert_array_almost_equal(solution.y[-1, -1], 2.5, decimal=2) np.testing.assert_array_almost_equal(solution.y.full()[0], np.exp(0.1 * solution.t), decimal=5) np.testing.assert_array_almost_equal(solution.y.full()[-1], 2 * np.exp(0.1 * solution.t), decimal=5) pybamm.settings.debug_mode = True # Try dt_max=0 to enforce using all timesteps solver = pybamm.CasadiSolver(dt_max=0, rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 5, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y.full()[0], 1.5) np.testing.assert_array_less(solution.y.full()[-1], 2.5 + 1e-10) np.testing.assert_array_almost_equal(solution.y.full()[0], np.exp(0.1 * solution.t), decimal=5) np.testing.assert_array_almost_equal(solution.y.full()[-1], 2 * np.exp(0.1 * solution.t), decimal=5) # Solve using "fast with events" mode model = pybamm.BaseModel() var1 = pybamm.Variable("var1") var2 = pybamm.Variable("var2") model.rhs = {var1: 0.1 * var1} model.algebraic = {var2: 2 * var1 - var2} model.initial_conditions = {var1: 1, var2: 2} model.events = [ pybamm.Event("var1 = 1.5", var1 - 1.5), pybamm.Event("var2 = 2.5", var2 - 2.5), pybamm.Event("var1 = 1.5 switch", var1 - 2, pybamm.EventType.SWITCH), pybamm.Event("var2 = 2.5 switch", var2 - 3, pybamm.EventType.SWITCH), ] solver = pybamm.CasadiSolver(mode="fast with events", rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 5, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y.full()[0, :-1], 1.5) np.testing.assert_array_less(solution.y.full()[-1, :-1], 2.5) np.testing.assert_equal(solution.t_event[0], solution.t[-1]) np.testing.assert_array_almost_equal(solution.y_event[:, 0].flatten(), [1.25, 2.5], decimal=5) np.testing.assert_array_almost_equal(solution.y.full()[0], np.exp(0.1 * solution.t), decimal=5) np.testing.assert_array_almost_equal(solution.y.full()[-1], 2 * np.exp(0.1 * solution.t), decimal=5) # Test when an event returns nan model = pybamm.BaseModel() var = pybamm.Variable("var") model.rhs = {var: 0.1 * var} model.initial_conditions = {var: 1} model.events = [ pybamm.Event("event", var - 1.02), pybamm.Event("sqrt event", pybamm.sqrt(1.0199 - var)), ] disc = pybamm.Discretisation() disc.process_model(model) solver = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y.full()[0], 1.02 + 1e-10) np.testing.assert_array_almost_equal(solution.y[0, -1], 1.02, decimal=2)
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
def test_model_solver_events(self): # Create model model = pybamm.BaseModel() whole_cell = ["negative electrode", "separator", "positive electrode"] var1 = pybamm.Variable("var1", domain=whole_cell) var2 = pybamm.Variable("var2", domain=whole_cell) model.rhs = {var1: 0.1 * var1} model.algebraic = {var2: 2 * var1 - var2} model.initial_conditions = {var1: 1, var2: 2} model.events = [ pybamm.Event("var1 = 1.5", pybamm.min(var1 - 1.5)), pybamm.Event("var2 = 2.5", pybamm.min(var2 - 2.5)), ] disc = get_discretisation_for_testing() disc.process_model(model) # Solve using "safe" mode solver = pybamm.CasadiSolver(mode="safe", rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 5, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y[0], 1.5) np.testing.assert_array_less(solution.y[-1], 2.5) np.testing.assert_array_almost_equal(solution.y[0], np.exp(0.1 * solution.t), decimal=5) np.testing.assert_array_almost_equal(solution.y[-1], 2 * np.exp(0.1 * solution.t), decimal=5) # Solve using "safe" mode with debug off pybamm.settings.debug_mode = False solver = pybamm.CasadiSolver(mode="safe", rtol=1e-8, atol=1e-8, dt_max=1) t_eval = np.linspace(0, 5, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y[0], 1.5) np.testing.assert_array_less(solution.y[-1], 2.5) np.testing.assert_array_almost_equal(solution.y[0], np.exp(0.1 * solution.t), decimal=5) np.testing.assert_array_almost_equal(solution.y[-1], 2 * np.exp(0.1 * solution.t), decimal=5) pybamm.settings.debug_mode = True # Solve using "old safe" mode solver = pybamm.CasadiSolver(mode="old safe", rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 5, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y[0], 1.5) np.testing.assert_array_less(solution.y[-1], 2.5) np.testing.assert_array_almost_equal(solution.y[0], np.exp(0.1 * solution.t), decimal=5) np.testing.assert_array_almost_equal(solution.y[-1], 2 * np.exp(0.1 * solution.t), decimal=5) # Test when an event returns nan model = pybamm.BaseModel() var = pybamm.Variable("var") model.rhs = {var: 0.1 * var} model.initial_conditions = {var: 1} model.events = [ pybamm.Event("event", var - 1.02), pybamm.Event("sqrt event", pybamm.sqrt(1.0199 - var)), ] disc = pybamm.Discretisation() disc.process_model(model) solver = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8) solution = solver.solve(model, t_eval) np.testing.assert_array_less(solution.y[0], 1.02)
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()