def test_solver_citations(self): # Test that solving each solver adds the right citations citations = pybamm.citations citations._reset() self.assertNotIn("virtanen2020scipy", citations._papers_to_cite) pybamm.ScipySolver() self.assertIn("virtanen2020scipy", citations._papers_to_cite) citations._reset() self.assertNotIn("virtanen2020scipy", citations._papers_to_cite) pybamm.AlgebraicSolver() self.assertIn("virtanen2020scipy", citations._papers_to_cite) if pybamm.have_scikits_odes(): citations._reset() self.assertNotIn("scikits-odes", citations._papers_to_cite) pybamm.ScikitsOdeSolver() self.assertIn("scikits-odes", citations._papers_to_cite) citations._reset() self.assertNotIn("scikits-odes", citations._papers_to_cite) pybamm.ScikitsDaeSolver() self.assertIn("scikits-odes", citations._papers_to_cite) if pybamm.have_idaklu(): citations._reset() self.assertNotIn("hindmarsh2005sundials", citations._papers_to_cite) pybamm.IDAKLUSolver() self.assertIn("hindmarsh2005sundials", citations._papers_to_cite)
def test_changing_grid(self): model = pybamm.lithium_ion.SPM() solver = pybamm.IDAKLUSolver() # load parameter values and geometry geometry = model.default_geometry param = model.default_parameter_values # Process parameters param.process_model(model) param.process_geometry(geometry) # Calculate time for each solver and each number of grid points var = pybamm.standard_spatial_vars t_eval = np.linspace(0, 3600, 100) for npts in [100, 200]: # discretise var_pts = { spatial_var: npts for spatial_var in [var.x_n, var.x_s, var.x_p, var.r_n, var.r_p] } mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) model_disc = disc.process_model(model, inplace=False) # solve solver.solve(model_disc, t_eval)
def test_dae_external_temperature(self): model_options = { "thermal": "x-full", "external submodels": ["thermal"], } model = pybamm.lithium_ion.DFN(model_options) neg_pts = 5 sep_pts = 3 pos_pts = 5 tot_pts = neg_pts + sep_pts + pos_pts var_pts = { pybamm.standard_spatial_vars.x_n: neg_pts, pybamm.standard_spatial_vars.x_s: sep_pts, pybamm.standard_spatial_vars.x_p: pos_pts, pybamm.standard_spatial_vars.r_n: 5, pybamm.standard_spatial_vars.r_p: 5, } solver = pybamm.IDAKLUSolver() sim = pybamm.Simulation(model, var_pts=var_pts, solver=solver) sim.build() t_eval = np.linspace(0, 100, 3) x = np.linspace(0, 1, tot_pts) for i in np.arange(1, len(t_eval) - 1): dt = t_eval[i + 1] - t_eval[i] T = (np.sin(2 * np.pi * x) * np.sin(2 * np.pi * 100 * t_eval[i]))[:, np.newaxis] external_variables = {"Cell temperature": T} sim.step(dt, external_variables=external_variables)
def default_solver(self): "Return default solver based on whether model is ODE model or DAE model" if len(self.algebraic) == 0: return pybamm.ScipySolver() elif pybamm.have_idaklu() and self.use_jacobian is True: # KLU solver requires jacobian to be provided return pybamm.IDAKLUSolver() else: return pybamm.CasadiSolver(mode="safe")
def test_on_spme(self): model = pybamm.lithium_ion.SPMe() geometry = model.default_geometry param = model.default_parameter_values param.process_model(model) param.process_geometry(geometry) mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) t_eval = np.linspace(0, 3600, 100) solution = pybamm.IDAKLUSolver().solve(model, t_eval) np.testing.assert_array_less(1, solution.t.size)
def test_dae_solver_algebraic_model(self): model = pybamm.BaseModel() var = pybamm.Variable("var") model.algebraic = {var: var + 1} model.initial_conditions = {var: 0} disc = pybamm.Discretisation() disc.process_model(model) solver = pybamm.IDAKLUSolver() t_eval = np.linspace(0, 1) solution = solver.solve(model, t_eval) np.testing.assert_array_equal(solution.y, -1)
def test_set_atol(self): model = pybamm.lithium_ion.SPMe() geometry = model.default_geometry param = model.default_parameter_values param.process_model(model) param.process_geometry(geometry) mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) solver = pybamm.IDAKLUSolver(root_method="lm") variable_tols = {"Electrolyte concentration": 1e-3} solver.set_atol_by_variable(variable_tols, model)
def test_set_tol_by_variable(self): model = pybamm.lithium_ion.SPMe() geometry = model.default_geometry param = model.default_parameter_values param.process_model(model) param.process_geometry(geometry) mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) t_eval = np.linspace(0, 3600, 100) solver = pybamm.IDAKLUSolver() variable_tols = {"Porosity times concentration": 1e-3} solver.set_atol_by_variable(variable_tols, model) solver.solve(model, t_eval)
def test_failures(self): # this test implements a python version of the ida Roberts # example provided in sundials # see sundials ida examples pdf model = pybamm.BaseModel() model.use_jacobian = False u = pybamm.Variable("u") model.rhs = {u: -0.1 * u} model.initial_conditions = {u: 1} disc = pybamm.Discretisation() disc.process_model(model) solver = pybamm.IDAKLUSolver(root_method="lm") t_eval = np.linspace(0, 3, 100) with self.assertRaisesRegex(pybamm.SolverError, "KLU requires the Jacobian"): solver.solve(model, t_eval)
def test_2p1d(self): model_options = { "current collector": "potential pair", "dimensionality": 2, "external submodels": ["current collector"], } model = pybamm.lithium_ion.DFN(model_options) yz_pts = 3 var_pts = { pybamm.standard_spatial_vars.x_n: 4, pybamm.standard_spatial_vars.x_s: 4, pybamm.standard_spatial_vars.x_p: 4, pybamm.standard_spatial_vars.r_n: 4, pybamm.standard_spatial_vars.r_p: 4, pybamm.standard_spatial_vars.y: yz_pts, pybamm.standard_spatial_vars.z: yz_pts, } solver = pybamm.IDAKLUSolver() sim = pybamm.Simulation(model, var_pts=var_pts, solver=solver) # Simulate 100 seconds t_eval = np.linspace(0, 100, 3) for i in np.arange(1, len(t_eval) - 1): dt = t_eval[i + 1] - t_eval[i] # provide phi_s_n and i_cc phi_s_n = np.zeros((yz_pts ** 2, 1)) i_boundary_cc = np.ones((yz_pts ** 2, 1)) external_variables = { "Negative current collector potential": phi_s_n, "Current collector current density": i_boundary_cc, } sim.step(dt, external_variables=external_variables) # obtain phi_s_n from the pybamm solution at the current time phi_s_p = sim.get_variable_array("Positive current collector potential") self.assertTrue(phi_s_p.shape, (yz_pts ** 2, 1))
def test_ida_roberts_klu(self): # this test implements a python version of the ida Roberts # example provided in sundials # see sundials ida examples pdf for form in ["python", "casadi"]: model = pybamm.BaseModel() model.convert_to_format = form u = pybamm.Variable("u") v = pybamm.Variable("v") model.rhs = {u: 0.1 * v} model.algebraic = {v: 1 - v} model.initial_conditions = {u: 0, v: 1} model.events = [ pybamm.Event("1", u - 0.2), pybamm.Event("2", v), ] disc = pybamm.Discretisation() disc.process_model(model) solver = pybamm.IDAKLUSolver(root_method="lm") t_eval = np.linspace(0, 3, 100) solution = solver.solve(model, t_eval) # test that final time is time of event # y = 0.1 t + y0 so y=0.2 when t=2 np.testing.assert_array_almost_equal(solution.t[-1], 2.0) # test that final value is the event value np.testing.assert_array_almost_equal(solution.y[0, -1], 0.2) # test that y[1] remains constant np.testing.assert_array_almost_equal( solution.y[1, :], np.ones(solution.t.shape) ) # test that y[0] = to true solution true_solution = 0.1 * solution.t np.testing.assert_array_almost_equal(solution.y[0, :], true_solution)
def test_ida_roberts_klu(self): # this test implements a python version of the ida Roberts # example provided in sundials # see sundials ida examples pdf # times and initial conditions t_eval = np.linspace(0, 3, 100) y0 = np.array([0.0, 1.0]) # Standard pybamm functions def jac(t, y): J = np.zeros((2, 2)) J[0][0] = 0.0 J[0][1] = 1.0 J[1][0] = 0.0 J[1][1] = -1.0 return sparse.csr_matrix(J) def event_1(t, y): return y[0] - 0.2 def event_2(t, y): return y[1] - 0.0 events = np.array([event_1, event_2]) def res(t, y, yp): # must be of form r = f(t, y) - y' r = np.zeros((y.size,)) r[0] = 0.1 * y[1] r[1] = 1 - y[1] r[0] += -yp[0] return r mass_matrix_dense = np.zeros((2, 2)) mass_matrix_dense[0][0] = 1 mass_matrix = sparse.csr_matrix(mass_matrix_dense) def rhs(t, y): return np.array([0.1 * y[1]]) def alg(t, y): return np.array([1 - y[1]]) solver = pybamm.IDAKLUSolver() solver.residuals = res solver.rhs = rhs solver.algebraic = alg solution = solver.integrate(res, y0, t_eval, events, mass_matrix, jac) # test that final time is time of event # y = 0.1 t + y0 so y=0.2 when t=2 np.testing.assert_array_almost_equal(solution.t[-1], 2.0) # test that final value is the event value np.testing.assert_array_almost_equal(solution.y[0, -1], 0.2) # test that y[1] remains constant np.testing.assert_array_almost_equal( solution.y[1, :], np.ones(solution.t.shape) ) # test that y[0] = to true solution true_solution = 0.1 * solution.t np.testing.assert_array_almost_equal(solution.y[0, :], true_solution)
var.z: 5, } # depnding on number of points in y-z plane may need to increase recursion depth... sys.setrecursionlimit(10000) mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) # solve model -- simulate one hour discharge tau = param.process_symbol( pybamm.standard_parameters_lithium_ion.tau_discharge) t_end = 3600 / tau.evaluate(0) t_eval = np.linspace(0, t_end, 120) solution = pybamm.IDAKLUSolver().solve(model, t_eval) # TO DO: 2+1D automated plotting phi_s_cn = pybamm.ProcessedVariable( model.variables["Negative current collector potential [V]"], solution.t, solution.y, mesh=mesh, ) phi_s_cp = pybamm.ProcessedVariable( model.variables["Positive current collector potential [V]"], solution.t, solution.y, mesh=mesh, ) T = pybamm.ProcessedVariable(
var = pybamm.standard_spatial_vars var_pts = {var.x_n: 50, var.x_s: 50, var.x_p: 50, var.r_n: 20, var.r_p: 20} mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) # solve model t_eval = np.linspace(0, 3600, 100) casadi_sol = pybamm.CasadiSolver(atol=1e-8, rtol=1e-8).solve(model, t_eval) solutions = [casadi_sol] if pybamm.have_idaklu(): klu_sol = pybamm.IDAKLUSolver(atol=1e-8, rtol=1e-8).solve(model, t_eval) solutions.append(klu_sol) else: pybamm.logger.error( """ Cannot solve model with IDA KLU solver as solver is not installed. Please consult installation instructions on GitHub. """ ) if pybamm.have_scikits_odes(): scikits_sol = pybamm.ScikitsDaeSolver(atol=1e-8, rtol=1e-8).solve(model, t_eval) solutions.append(scikits_sol) else: pybamm.logger.error( """ Cannot solve model with Scikits DAE solver as solver is not installed.
model.default_var_pts) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) # tolerances (rtol, atol) tols = [[1e-8, 1e-8], [1e-6, 1e-6], [1e-3, 1e-6], [1e-3, 1e-3]] # solve model solutions = [None] * len(tols) voltages = [None] * len(tols) voltage_rmse = [None] * len(tols) labels = [None] * len(tols) t_eval = np.linspace(0, 3000, 100) for i, tol in enumerate(tols): if pybamm.have_idaklu(): solver = pybamm.IDAKLUSolver(rtol=tol[0], atol=tol[1]) else: solver = pybamm.CasadiSolver(rtol=tol[0], atol=tol[1]) solutions[i] = solver.solve(model, t_eval) voltages[i] = solutions[i]["Terminal voltage [V]"](solutions[i].t) voltage_rmse[i] = pybamm.rmse(voltages[0], voltages[i]) labels[i] = "rtol = {}, atol = {}".format(tol[0], tol[1]) # print RMSE voltage errors vs tighest tolerance for i, tol in enumerate(tols): print("rtol = {}, atol = {}, solve time = {} s, Voltage RMSE = {}".format( tol[0], tol[1], solutions[i].solve_time, voltage_rmse[i])) # plot plot = pybamm.QuickPlot(solutions, labels=labels) plot.dynamic_plot()