From 7ff854c01b108fde002a53cac49052cd6d58dc7d Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Mon, 14 Nov 2022 14:45:59 -0500 Subject: [PATCH] #2418 working on tests --- examples/scripts/compare_lithium_ion.py | 4 +- .../scripts/compare_lithium_ion_two_phase.py | 11 +- pybamm/discretisations/discretisation.py | 17 +- pybamm/geometry/battery_geometry.py | 23 ++- pybamm/meshes/one_dimensional_submeshes.py | 24 +-- .../lithium_ion/basic_dfn.py | 40 +++-- .../lithium_ion/basic_dfn_composite.py | 33 +++- .../interface/kinetics/total_main_kinetics.py | 11 +- .../interface/lithium_plating/base_plating.py | 48 ++--- .../open_circuit_potential/single_ocp.py | 2 +- .../submodels/interface/sei/base_sei.py | 33 ++-- .../submodels/interface/sei/sei_growth.py | 7 + .../submodels/particle/polynomial_profile.py | 2 - pybamm/parameters/lithium_ion_parameters.py | 5 +- pybamm/solvers/algebraic_solver.py | 166 ++++++++---------- pybamm/solvers/casadi_algebraic_solver.py | 14 +- pybamm/solvers/casadi_solver.py | 2 +- .../test_interface/test_butler_volmer.py | 38 ++-- .../test_interface/test_lithium_ion.py | 12 +- .../test_lithium_ion_parameters.py | 34 ++-- 20 files changed, 270 insertions(+), 256 deletions(-) diff --git a/examples/scripts/compare_lithium_ion.py b/examples/scripts/compare_lithium_ion.py index 4f4c59382a..07bdcebaa2 100644 --- a/examples/scripts/compare_lithium_ion.py +++ b/examples/scripts/compare_lithium_ion.py @@ -9,7 +9,7 @@ pybamm.lithium_ion.SPM(), pybamm.lithium_ion.SPMe(), pybamm.lithium_ion.DFN(), - # pybamm.lithium_ion.NewmanTobias(), + pybamm.lithium_ion.NewmanTobias(), ] # create and run simulations @@ -18,7 +18,7 @@ sim = pybamm.Simulation( model, # parameter_values=pybamm.ParameterValues("Ecker2015"), - solver=pybamm.CasadiSolver(dt_max=600), # root_method="lm"), + solver=pybamm.CasadiSolver(dt_max=600), # , root_method="lm"), ) sim.solve([0, 3600]) sims.append(sim) diff --git a/examples/scripts/compare_lithium_ion_two_phase.py b/examples/scripts/compare_lithium_ion_two_phase.py index 46ef789287..e510551cef 100644 --- a/examples/scripts/compare_lithium_ion_two_phase.py +++ b/examples/scripts/compare_lithium_ion_two_phase.py @@ -32,11 +32,16 @@ "Average negative primary particle concentration", "Average negative secondary particle concentration", ], - "X-averaged negative electrode primary interfacial current density [A.m-2]", - "X-averaged negative electrode secondary interfacial current density [A.m-2]", + [ + "X-averaged negative electrode primary volumetric " + "interfacial current density [A.m-3]", + "X-averaged negative electrode secondary volumetric " + "interfacial current density [A.m-3]", + "X-averaged negative electrode volumetric " + "interfacial current density [A.m-3]", + ], "X-averaged negative electrode primary open circuit potential [V]", "X-averaged negative electrode secondary open circuit potential [V]", - "X-averaged negative electrode volumetric interfacial current density", "Average positive particle concentration [mol.m-3]", "Terminal voltage [V]", ], diff --git a/pybamm/discretisations/discretisation.py b/pybamm/discretisations/discretisation.py index ea848abe8e..0d2fc0c747 100644 --- a/pybamm/discretisations/discretisation.py +++ b/pybamm/discretisations/discretisation.py @@ -310,8 +310,8 @@ def set_variable_slices(self, variables): y_slices[variable].append(slice(start, end)) y_slices_explicit[variable].append(slice(start, end)) # Add to bounds - lower_bounds.extend([variable.bounds[0]] * (end - start)) - upper_bounds.extend([variable.bounds[1]] * (end - start)) + lower_bounds.extend([variable.bounds[0].evaluate()] * (end - start)) + upper_bounds.extend([variable.bounds[1].evaluate()] * (end - start)) # Increment start start = end @@ -1000,12 +1000,15 @@ def _process_symbol(self, symbol): # create new children without scale and reference # the scale and reference will be applied to the concatenation instead new_children = [] + old_y_slices = self.y_slices.copy() for child in symbol.children: - child = child.create_copy() - child._scale = 1 - child._reference = 0 - child.set_id() - new_children.append(self.process_symbol(child)) + child_no_scale = child.create_copy() + child_no_scale._scale = 1 + child_no_scale._reference = 0 + child_no_scale.set_id() + self.y_slices[child_no_scale] = self.y_slices[child] + new_children.append(self.process_symbol(child_no_scale)) + self.y_slices = old_y_slices new_symbol = spatial_method.concatenation(new_children) # apply scale to the whole concatenation return symbol.reference + symbol.scale * new_symbol diff --git a/pybamm/geometry/battery_geometry.py b/pybamm/geometry/battery_geometry.py index 6ec791ca6c..4967a7643c 100644 --- a/pybamm/geometry/battery_geometry.py +++ b/pybamm/geometry/battery_geometry.py @@ -33,7 +33,7 @@ def battery_geometry( """ options = pybamm.BatteryModelOptions(options or {}) - geo = pybamm.geometric_parameters + geo = pybamm.GeometricParameters(options) L_n = geo.n.L L_s = geo.s.L L_n_L_s = L_n + L_s @@ -48,22 +48,27 @@ def battery_geometry( } # Add particle domains if include_particles is True: - geometry.update( - { - "negative particle": {"r_n": {"min": 0, "max": geo.n.prim.R_typ}}, - "positive particle": {"r_p": {"min": 0, "max": geo.p.prim.R_typ}}, - } - ) for domain in ["negative", "positive"]: + if options.electrode_types[domain] == "planar": + continue + geo_domain = geo.domain_params[domain] + d = domain[0] + geometry.update( + { + f"{domain} particle": { + f"r_{d}": {"min": 0, "max": geo_domain.prim.R_typ} + }, + } + ) phases = int(getattr(options, domain)["particle phases"]) if phases >= 2: geometry.update( { f"{domain} primary particle": { - "r_n_prim": {"min": 0, "max": geo.n.prim.R_typ} + f"r_{d}_prim": {"min": 0, "max": geo_domain.prim.R_typ} }, f"{domain} secondary particle": { - "r_n_sec": {"min": 0, "max": geo.n.sec.R_typ} + f"r_{d}_sec": {"min": 0, "max": geo_domain.sec.R_typ} }, } ) diff --git a/pybamm/meshes/one_dimensional_submeshes.py b/pybamm/meshes/one_dimensional_submeshes.py index ca4811b469..831a8db893 100644 --- a/pybamm/meshes/one_dimensional_submeshes.py +++ b/pybamm/meshes/one_dimensional_submeshes.py @@ -374,28 +374,18 @@ def __init__(self, lims, npts, edges=None, order=2): coord_sys = spatial_var.coord_sys + array = np.array( + [ + ((order + 1) - 1 - 2 * i) / (2 * (order + 1) - 2) + for i in range(order + 1) + ] + ) cv_edges = np.array( [edges[0]] + [ x for (a, b) in zip(edges[:-1], edges[1:]) - for x in np.flip( - a - + 0.5 - * (b - a) - * ( - 1 - + np.sin( - np.pi - * np.array( - [ - ((order + 1) - 1 - 2 * i) / (2 * (order + 1) - 2) - for i in range(order + 1) - ] - ) - ) - ) - )[1:] + for x in np.flip(a + 0.5 * (b - a) * (1 + np.sin(np.pi * array)))[1:] ] ) diff --git a/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py b/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py index f61f89965c..e068accbc9 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py @@ -42,13 +42,19 @@ def __init__(self, name="Doyle-Fuller-Newman model"): Q = pybamm.Variable("Discharge capacity [A.h]") # Variables that vary spatially are created with a domain c_e_n = pybamm.Variable( - "Negative electrolyte concentration [mol.m-3]", domain="negative electrode" + "Negative electrolyte concentration [mol.m-3]", + domain="negative electrode", + scale=param.c_e_typ, ) c_e_s = pybamm.Variable( - "Separator electrolyte concentration [mol.m-3]", domain="separator" + "Separator electrolyte concentration [mol.m-3]", + domain="separator", + scale=param.c_e_typ, ) c_e_p = pybamm.Variable( - "Positive electrolyte concentration [mol.m-3]", domain="positive electrode" + "Positive electrolyte concentration [mol.m-3]", + domain="positive electrode", + scale=param.c_e_typ, ) # Concatenations combine several variables into a single variable, to simplify # implementing equations that hold over several domains @@ -56,13 +62,19 @@ def __init__(self, name="Doyle-Fuller-Newman model"): # Electrolyte potential phi_e_n = pybamm.Variable( - "Negative electrolyte potential [V]", domain="negative electrode" + "Negative electrolyte potential [V]", + domain="negative electrode", + reference=-param.n.prim.U_init, ) phi_e_s = pybamm.Variable( - "Separator electrolyte potential [V]", domain="separator" + "Separator electrolyte potential [V]", + domain="separator", + reference=-param.n.prim.U_init, ) phi_e_p = pybamm.Variable( - "Positive electrolyte potential [V]", domain="positive electrode" + "Positive electrolyte potential [V]", + domain="positive electrode", + reference=-param.n.prim.U_init, ) phi_e = pybamm.concatenation(phi_e_n, phi_e_s, phi_e_p) @@ -71,7 +83,9 @@ def __init__(self, name="Doyle-Fuller-Newman model"): "Negative electrode potential [V]", domain="negative electrode" ) phi_s_p = pybamm.Variable( - "Positive electrode potential [V]", domain="positive electrode" + "Positive electrode potential [V]", + domain="positive electrode", + reference=param.ocv_init, ) # Particle concentrations are variables on the particle domain, but also vary in # the x-direction (electrode domain) and so must be provided with auxiliary @@ -80,11 +94,13 @@ def __init__(self, name="Doyle-Fuller-Newman model"): "Negative particle concentration [mol.m-3]", domain="negative particle", auxiliary_domains={"secondary": "negative electrode"}, + scale=param.n.prim.c_max, ) c_s_p = pybamm.Variable( "Positive particle concentration [mol.m-3]", domain="positive particle", auxiliary_domains={"secondary": "positive electrode"}, + scale=param.p.prim.c_max, ) # Constant temperature @@ -129,16 +145,14 @@ def __init__(self, name="Doyle-Fuller-Newman model"): c_s_surf_n = pybamm.surf(c_s_n) sto_surf_n = c_s_surf_n / param.n.prim.c_max j0_n = param.n.prim.j0(c_e_n, c_s_surf_n, T) - eta_n = ( - phi_s_n - phi_e_n - (param.n.prim.U(sto_surf_n, T) - param.n.prim.U_init) - ) + eta_n = phi_s_n - phi_e_n - param.n.prim.U(sto_surf_n, T) Feta_RT_n = param.F * eta_n / (param.R * T) j_n = 2 * j0_n * pybamm.sinh(param.n.prim.ne / 2 * Feta_RT_n) c_s_surf_p = pybamm.surf(c_s_p) sto_surf_p = c_s_surf_p / param.p.prim.c_max j0_p = param.p.prim.j0(c_e_p, c_s_surf_p, T) - eta_p = phi_s_p - phi_e_p - (param.p.prim.U(sto_surf_p, T) - param.ocv_init) + eta_p = phi_s_p - phi_e_p - param.p.prim.U(sto_surf_p, T) Feta_RT_p = param.F * eta_p / (param.R * T) j_s = pybamm.PrimaryBroadcast(0, "separator") j_p = 2 * j0_p * pybamm.sinh(param.p.prim.ne / 2 * Feta_RT_p) @@ -207,7 +221,7 @@ def __init__(self, name="Doyle-Fuller-Newman model"): # initial guess for a root-finding algorithm which calculates consistent initial # conditions self.initial_conditions[phi_s_n] = pybamm.Scalar(0) - self.initial_conditions[phi_s_p] = pybamm.Scalar(0) # param.ocv_init + self.initial_conditions[phi_s_p] = param.ocv_init ###################### # Current in the electrolyte @@ -220,7 +234,7 @@ def __init__(self, name="Doyle-Fuller-Newman model"): "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } - self.initial_conditions[phi_e] = pybamm.Scalar(0) # -param.n.prim.U_init + self.initial_conditions[phi_e] = -param.n.prim.U_init ###################### # Electrolyte concentration diff --git a/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py b/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py index b49fc68bd1..f358d0a44f 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py @@ -28,7 +28,7 @@ class BasicDFNComposite(BaseModel): **Extends:** :class:`pybamm.lithium_ion.BaseModel` """ - def __init__(self, name="Doyle-Fuller-Newman model"): + def __init__(self, name="Composite graphite/silicon Doyle-Fuller-Newman model"): options = {"particle phases": ("2", "1")} super().__init__(options, name) pybamm.citations.register("Ai2022") @@ -44,13 +44,19 @@ def __init__(self, name="Doyle-Fuller-Newman model"): Q = pybamm.Variable("Discharge capacity [A.h]") # Variables that vary spatially are created with a domain c_e_n = pybamm.Variable( - "Negative electrolyte concentration [mol.m-3]", domain="negative electrode" + "Negative electrolyte concentration [mol.m-3]", + domain="negative electrode", + scale=param.c_e_typ, ) c_e_s = pybamm.Variable( - "Separator electrolyte concentration [mol.m-3]", domain="separator" + "Separator electrolyte concentration [mol.m-3]", + domain="separator", + scale=param.c_e_typ, ) c_e_p = pybamm.Variable( - "Positive electrolyte concentration [mol.m-3]", domain="positive electrode" + "Positive electrolyte concentration [mol.m-3]", + domain="positive electrode", + scale=param.c_e_typ, ) # Concatenations combine several variables into a single variable, to simplify # implementing equations that hold over several domains @@ -58,13 +64,19 @@ def __init__(self, name="Doyle-Fuller-Newman model"): # Electrolyte potential phi_e_n = pybamm.Variable( - "Negative electrolyte potential [V]", domain="negative electrode" + "Negative electrolyte potential [V]", + domain="negative electrode", + reference=-param.n.prim.U_init, ) phi_e_s = pybamm.Variable( - "Separator electrolyte potential [V]", domain="separator" + "Separator electrolyte potential [V]", + domain="separator", + reference=-param.n.prim.U_init, ) phi_e_p = pybamm.Variable( - "Positive electrolyte potential [V]", domain="positive electrode" + "Positive electrolyte potential [V]", + domain="positive electrode", + reference=-param.n.prim.U_init, ) phi_e = pybamm.concatenation(phi_e_n, phi_e_s, phi_e_p) @@ -73,7 +85,9 @@ def __init__(self, name="Doyle-Fuller-Newman model"): "Negative electrode potential [V]", domain="negative electrode" ) phi_s_p = pybamm.Variable( - "Positive electrode potential [V]", domain="positive electrode" + "Positive electrode potential [V]", + domain="positive electrode", + reference=param.ocv_init, ) # Particle concentrations are variables on the particle domain, but also vary in # the x-direction (electrode domain) and so must be provided with auxiliary @@ -82,16 +96,19 @@ def __init__(self, name="Doyle-Fuller-Newman model"): "Negative primary particle concentration [mol.m-3]", domain="negative primary particle", auxiliary_domains={"secondary": "negative electrode"}, + scale=param.n.prim.c_max, ) c_s_n_p2 = pybamm.Variable( "Negative secondary particle concentration [mol.m-3]", domain="negative secondary particle", auxiliary_domains={"secondary": "negative electrode"}, + scale=param.n.sec.c_max, ) c_s_p = pybamm.Variable( "Positive particle concentration [mol.m-3]", domain="positive particle", auxiliary_domains={"secondary": "positive electrode"}, + scale=param.p.prim.c_max, ) # Constant temperature diff --git a/pybamm/models/submodels/interface/kinetics/total_main_kinetics.py b/pybamm/models/submodels/interface/kinetics/total_main_kinetics.py index dcd328e02e..cc709d2856 100644 --- a/pybamm/models/submodels/interface/kinetics/total_main_kinetics.py +++ b/pybamm/models/submodels/interface/kinetics/total_main_kinetics.py @@ -41,8 +41,13 @@ def get_coupled_variables(self, variables): ] for phase in phases ) - variables[ - f"{Domain} electrode volumetric interfacial current density [A.m-3]" - ] = sumvar + variables.update( + { + f"{Domain} electrode volumetric " + "interfacial current density [A.m-3]": sumvar, + f"X-averaged {domain} electrode volumetric " + "interfacial current density [A.m-3]": pybamm.x_average(sumvar), + } + ) return variables diff --git a/pybamm/models/submodels/interface/lithium_plating/base_plating.py b/pybamm/models/submodels/interface/lithium_plating/base_plating.py index 53eb791a44..02e27ba3f3 100644 --- a/pybamm/models/submodels/interface/lithium_plating/base_plating.py +++ b/pybamm/models/submodels/interface/lithium_plating/base_plating.py @@ -34,32 +34,38 @@ def __init__(self, param, options=None): def get_coupled_variables(self, variables): # Update some common variables - zero_av = pybamm.PrimaryBroadcast(0, "current collector") - zero = pybamm.FullBroadcast(0, "positive electrode", "current collector") - j_plating = variables["Lithium plating interfacial current density [A.m-2]"] - j_plating_av = variables[ - "X-averaged lithium plating interfacial current density [A.m-2]" - ] - if self.options.negative["particle phases"] == "1": - a = variables["Negative electrode surface area to volume ratio [m-1]"] - else: - a = variables[ - "Negative electrode primary surface area to volume ratio [m-1]" + if self.options.electrode_types["negative"] == "porous": + j_plating = variables["Lithium plating interfacial current density [A.m-2]"] + j_plating_av = variables[ + "X-averaged lithium plating interfacial current density [A.m-2]" ] - a_j_plating = a * j_plating - a_j_plating_av = pybamm.x_average(a_j_plating) + if self.options.negative["particle phases"] == "1": + a = variables["Negative electrode surface area to volume ratio [m-1]"] + else: + a = variables[ + "Negative electrode primary surface area to volume ratio [m-1]" + ] + a_j_plating = a * j_plating + a_j_plating_av = pybamm.x_average(a_j_plating) + + variables.update( + { + "Negative electrode lithium plating interfacial current " + "density [A.m-2]": j_plating, + "X-averaged negative electrode lithium plating " + "interfacial current density [A.m-2]": j_plating_av, + "Lithium plating volumetric " + "interfacial current density [A.m-3]": a_j_plating, + "X-averaged lithium plating volumetric " + "interfacial current density [A.m-3]": a_j_plating_av, + } + ) + zero_av = pybamm.PrimaryBroadcast(0, "current collector") + zero = pybamm.FullBroadcast(0, "positive electrode", "current collector") variables.update( { - "Negative electrode lithium plating interfacial current " - "density [A.m-2]": j_plating, - "X-averaged negative electrode lithium plating " - "interfacial current density [A.m-2]": j_plating_av, - "Lithium plating volumetric " - "interfacial current density [A.m-3]": a_j_plating, - "X-averaged lithium plating volumetric " - "interfacial current density [A.m-3]": a_j_plating_av, "X-averaged positive electrode lithium plating " "interfacial current density [A.m-2]": zero_av, "X-averaged positive electrode lithium plating volumetric " diff --git a/pybamm/models/submodels/interface/open_circuit_potential/single_ocp.py b/pybamm/models/submodels/interface/open_circuit_potential/single_ocp.py index 8d68517400..886e47be15 100644 --- a/pybamm/models/submodels/interface/open_circuit_potential/single_ocp.py +++ b/pybamm/models/submodels/interface/open_circuit_potential/single_ocp.py @@ -39,7 +39,7 @@ def get_coupled_variables(self, variables): dUdT = self.phase_param.dUdT(sto) elif self.reaction == "lithium metal plating": T = variables[f"{Domain} electrode temperature [K]"] - ocp = self.param.n.U_ref + ocp = 0 * T dUdT = 0 * T elif self.reaction == "lead-acid main": c_e = variables[f"{Domain} electrolyte concentration [mol.m-3]"] diff --git a/pybamm/models/submodels/interface/sei/base_sei.py b/pybamm/models/submodels/interface/sei/base_sei.py index f4ad288140..cbbd3aacde 100644 --- a/pybamm/models/submodels/interface/sei/base_sei.py +++ b/pybamm/models/submodels/interface/sei/base_sei.py @@ -32,23 +32,23 @@ def __init__(self, param, options, phase="primary", cracks=False): def get_coupled_variables(self, variables): # Update some common variables - zero_av = pybamm.PrimaryBroadcast(0, "current collector") - zero = pybamm.FullBroadcast(0, "positive electrode", "current collector") - j_sei_av = variables[ - f"X-averaged {self.reaction_name}interfacial current density [A.m-2]" - ] - j_sei = variables[f"{self.reaction_name}interfacial current density [A.m-2]"] - if self.options.negative["particle phases"] == "1": - a = variables["Negative electrode surface area to volume ratio [m-1]"] - else: - a = variables[ - "Negative electrode primary surface area to volume ratio [m-1]" + if self.reaction_loc != "interface": + j_sei_av = variables[ + f"X-averaged {self.reaction_name}interfacial current density [A.m-2]" + ] + j_sei = variables[ + f"{self.reaction_name}interfacial current density [A.m-2]" ] - a_j_sei = a * j_sei - a_j_sei_av = pybamm.x_average(a_j_sei) + if self.options.negative["particle phases"] == "1": + a = variables["Negative electrode surface area to volume ratio [m-1]"] + else: + a = variables[ + "Negative electrode primary surface area to volume ratio [m-1]" + ] + a_j_sei = a * j_sei + a_j_sei_av = pybamm.x_average(a_j_sei) - if self.reaction_loc != "interface": variables.update( { f"X-averaged negative electrode {self.reaction_name}interfacial " @@ -65,6 +65,8 @@ def get_coupled_variables(self, variables): self._get_standard_volumetric_current_density_variables(variables) ) + zero_av = pybamm.PrimaryBroadcast(0, "current collector") + zero = pybamm.FullBroadcast(0, "positive electrode", "current collector") variables.update( { f"Positive electrode {self.reaction} " @@ -318,6 +320,9 @@ def _get_standard_reaction_variables(self, j_inner, j_outer): return variables def _get_standard_volumetric_reaction_variables(self, variables): + if self.options.electrode_types["negative"] == "planar": + return variables + Domain = self.domain.capitalize() phase_name = self.phase_name reaction_name = self.reaction_name diff --git a/pybamm/models/submodels/interface/sei/sei_growth.py b/pybamm/models/submodels/interface/sei/sei_growth.py index 8b5f52173c..3cb8c19558 100644 --- a/pybamm/models/submodels/interface/sei/sei_growth.py +++ b/pybamm/models/submodels/interface/sei/sei_growth.py @@ -46,10 +46,15 @@ def get_fundamental_variables(self): Ls = [] for pos in ["inner", "outer"]: Pos = pos.capitalize() + if pos == "inner": + scale = self.phase_param.L_inner_0 + else: + scale = self.phase_param.L_outer_0 if self.reaction_loc == "x-average": L_av = pybamm.Variable( f"X-averaged {pos} {self.reaction_name}thickness [m]", domain="current collector", + scale=scale, ) L_av.print_name = f"L_{pos}_av" L = pybamm.PrimaryBroadcast(L_av, "negative electrode") @@ -58,11 +63,13 @@ def get_fundamental_variables(self): f"{Pos} {self.reaction_name}thickness [m]", domain="negative electrode", auxiliary_domains={"secondary": "current collector"}, + scale=scale, ) elif self.reaction_loc == "interface": L = pybamm.Variable( f"{Pos} {self.reaction_name}thickness [m]", domain="current collector", + scale=scale, ) L.print_name = f"L_{pos}" Ls.append(L) diff --git a/pybamm/models/submodels/particle/polynomial_profile.py b/pybamm/models/submodels/particle/polynomial_profile.py index ad6ffd7087..39404aec16 100644 --- a/pybamm/models/submodels/particle/polynomial_profile.py +++ b/pybamm/models/submodels/particle/polynomial_profile.py @@ -226,7 +226,6 @@ def get_coupled_variables(self, variables): def set_rhs(self, variables): domain, Domain = self.domain_Domain - phase_param = self.phase_param if self.size_distribution is False: c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] @@ -264,7 +263,6 @@ def set_algebraic(self, variables): return domain, Domain = self.domain_Domain - phase_param = self.phase_param c_s_surf = variables[f"{Domain} particle surface concentration [mol.m-3]"] c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] diff --git a/pybamm/parameters/lithium_ion_parameters.py b/pybamm/parameters/lithium_ion_parameters.py index 07001d8cfa..c45839f3bb 100644 --- a/pybamm/parameters/lithium_ion_parameters.py +++ b/pybamm/parameters/lithium_ion_parameters.py @@ -260,6 +260,7 @@ def _set_parameters(self): return self.rho_c_p_cc = self.therm.rho_c_p_cc + self.lambda_cc = self.therm.lambda_cc x = pybamm.SpatialVariable( f"x_{domain[0]}", @@ -540,7 +541,9 @@ def j0(self, c_e, c_s_surf, T): tol = pybamm.settings.tolerances["j0__c_e"] c_e = pybamm.maximum(c_e, tol) tol = pybamm.settings.tolerances["j0__c_s"] - c_s_surf = pybamm.maximum(pybamm.minimum(c_s_surf, 1 - tol), tol) + c_s_surf = pybamm.maximum( + pybamm.minimum(c_s_surf, (1 - tol) * self.c_max), tol * self.c_max + ) domain, Domain = self.domain_Domain inputs = { "Electrolyte concentration [mol.m-3]": c_e, diff --git a/pybamm/solvers/algebraic_solver.py b/pybamm/solvers/algebraic_solver.py index 30e8a79bdc..a891681ace 100644 --- a/pybamm/solvers/algebraic_solver.py +++ b/pybamm/solvers/algebraic_solver.py @@ -135,100 +135,82 @@ def jac_fn(y_alg): else: jac_fn = None - itr = 0 - maxiter = 2 - success = False - while not success: - # Methods which use least-squares are specified as either "lsq", - # which uses the default method, or with "lsq__methodname" - if self.method.startswith("lsq"): - - if self.method == "lsq": - method = "trf" - else: - method = self.method[5:] - if jac_fn is None: - jac_fn = "2-point" - timer.reset() - sol = optimize.least_squares( - root_fun, - y0_alg, - method=method, - ftol=self.tol, - jac=jac_fn, - bounds=model.bounds, - **self.extra_options, - ) - integration_time += timer.time() - # Methods which use minimize are specified as either "minimize", - # which uses the default method, or with "minimize__methodname" - elif self.method.startswith("minimize"): - # Adapt the root function for minimize - def root_norm(y): - return np.sum(root_fun(y) ** 2) - - if jac_fn is None: - jac_norm = None - else: - - def jac_norm(y): - return np.sum(2 * root_fun(y) * jac_fn(y), 0) - - if self.method == "minimize": - method = None - else: - method = self.method[10:] - extra_options = self.extra_options - if np.any(model.bounds[0] != -np.inf) or np.any( - model.bounds[1] != np.inf - ): - bounds = [ - (lb, ub) for lb, ub in zip(model.bounds[0], model.bounds[1]) - ] - extra_options["bounds"] = bounds - timer.reset() - sol = optimize.minimize( - root_norm, - y0_alg, - method=method, - tol=self.tol, - jac=jac_norm, - **extra_options, - ) - integration_time += timer.time() + # Methods which use least-squares are specified as either "lsq", + # which uses the default method, or with "lsq__methodname" + if self.method.startswith("lsq"): + + if self.method == "lsq": + method = "trf" else: - timer.reset() - sol = optimize.root( - root_fun, - y0_alg, - method=self.method, - tol=self.tol, - jac=jac_fn, - options=self.extra_options, - ) - integration_time += timer.time() - - if sol.success and np.all(abs(sol.fun) < self.tol): - # update initial guess for the next iteration - y0_alg = sol.x - # update solution array - y_alg[:, idx] = y0_alg - success = True - elif not sol.success: - raise pybamm.SolverError( - "Could not find acceptable solution: {}".format(sol.message) - ) + method = self.method[5:] + if jac_fn is None: + jac_fn = "2-point" + timer.reset() + sol = optimize.least_squares( + root_fun, + y0_alg, + method=method, + ftol=self.tol, + jac=jac_fn, + bounds=model.bounds, + **self.extra_options, + ) + integration_time += timer.time() + # Methods which use minimize are specified as either "minimize", + # which uses the default method, or with "minimize__methodname" + elif self.method.startswith("minimize"): + # Adapt the root function for minimize + def root_norm(y): + return np.sum(root_fun(y) ** 2) + + if jac_fn is None: + jac_norm = None else: - y0_alg = sol.x - if itr > maxiter: - raise pybamm.SolverError( - "Could not find acceptable solution: solver terminated " - "successfully, but maximum solution error " - "({}) above tolerance ({})".format( - np.max(abs(sol.fun)), self.tol - ) - ) - itr += 1 + + def jac_norm(y): + return np.sum(2 * root_fun(y) * jac_fn(y), 0) + + if self.method == "minimize": + method = None + else: + method = self.method[10:] + extra_options = self.extra_options + if np.any(model.bounds[0] != -np.inf) or np.any( + model.bounds[1] != np.inf + ): + bounds = [ + (lb, ub) for lb, ub in zip(model.bounds[0], model.bounds[1]) + ] + extra_options["bounds"] = bounds + timer.reset() + sol = optimize.minimize( + root_norm, + y0_alg, + method=method, + tol=self.tol, + jac=jac_norm, + **extra_options, + ) + integration_time += timer.time() + else: + timer.reset() + sol = optimize.root( + root_fun, + y0_alg, + method=self.method, + tol=self.tol, + jac=jac_fn, + options=self.extra_options, + ) + integration_time += timer.time() + + if sol.success: + # update solution array + y_alg[:, idx] = y0_alg + else: + raise pybamm.SolverError( + "Could not find acceptable solution: {}".format(sol.message) + ) # Concatenate differential part y_diff = np.r_[[y0_diff] * len(t_eval)].T diff --git a/pybamm/solvers/casadi_algebraic_solver.py b/pybamm/solvers/casadi_algebraic_solver.py index 5043188dc7..d55a6882c2 100644 --- a/pybamm/solvers/casadi_algebraic_solver.py +++ b/pybamm/solvers/casadi_algebraic_solver.py @@ -157,9 +157,7 @@ def _integrate(self, model, t_eval, inputs_dict=None): # If there are no symbolic inputs, check the function is below the tol # Skip this check if there are symbolic inputs - if success and ( - (not any(np.isnan(fun))) # and np.all(casadi.fabs(fun) < self.tol)) - ): + if success and (not any(np.isnan(fun))): # update initial guess for the next iteration y0_alg = y_alg_sol y0 = casadi.vertcat(y0_diff, y0_alg) @@ -176,16 +174,6 @@ def _integrate(self, model, t_eval, inputs_dict=None): raise pybamm.SolverError( "Could not find acceptable solution: solver returned NaNs" ) - else: - raise pybamm.SolverError( - """ - Could not find acceptable solution: solver terminated - successfully, but maximum solution error ({}) - above tolerance ({}) - """.format( - casadi.mmax(casadi.fabs(fun)), self.tol - ) - ) # Concatenate differential part y_diff = casadi.horzcat(*[y0_diff] * len(t_eval)) diff --git a/pybamm/solvers/casadi_solver.py b/pybamm/solvers/casadi_solver.py index 3165ec9f6a..99c0cadf37 100644 --- a/pybamm/solvers/casadi_solver.py +++ b/pybamm/solvers/casadi_solver.py @@ -209,7 +209,7 @@ def _integrate(self, model, t_eval, inputs_dict=None): if dt_max < dt_eval_max: pybamm.logger.debug( "Setting dt_max to be as big as the largest step in " - "t_eval ({})".format(dt_max, dt_eval_max) + f"t_eval ({dt_eval_max})" ) dt_max = dt_eval_max termination_due_to_small_dt = False diff --git a/tests/integration/test_models/test_submodels/test_interface/test_butler_volmer.py b/tests/integration/test_models/test_submodels/test_interface/test_butler_volmer.py index dcf4459c17..251e26a3bd 100644 --- a/tests/integration/test_models/test_submodels/test_interface/test_butler_volmer.py +++ b/tests/integration/test_models/test_submodels/test_interface/test_butler_volmer.py @@ -43,21 +43,21 @@ def setUp(self): self.variables = { "Negative electrode surface potential difference [V]": self.delta_phi_s_n, "Positive electrode surface potential difference [V]": self.delta_phi_s_p, - "Negative electrolyte concentration": self.c_e_n, - "Positive electrolyte concentration": self.c_e_p, - "Negative particle surface concentration": self.c_s_n_surf, - "Positive particle surface concentration": self.c_s_p_surf, - "Current collector current density": pybamm.Scalar(1), - "Negative electrode temperature": 0, - "Positive electrode temperature": 0, + "Negative electrolyte concentration [mol.m-3]": self.c_e_n, + "Positive electrolyte concentration [mol.m-3]": self.c_e_p, + "Negative particle surface concentration [mol.m-3]": self.c_s_n_surf, + "Positive particle surface concentration [mol.m-3]": self.c_s_p_surf, + "Current collector current density [A.m-2]": pybamm.Scalar(1), + "Negative electrode temperature [K]": 300, + "Positive electrode temperature [K]": 300, "Negative electrode surface area to volume ratio [m-1]": 1 + 0 * self.c_e_n, "Positive electrode surface area to volume ratio [m-1]": 1 + 0 * self.c_e_p, "X-averaged negative electrode surface area to volume ratio [m-1]": 1, "X-averaged positive electrode surface area to volume ratio [m-1]": 1, "Negative electrode interface utilisation": 1, "Positive electrode interface utilisation": 1, - "Negative electrode open circuit potential": pybamm.Scalar(0), - "Positive electrode open circuit potential": pybamm.Scalar(0), + "Negative electrode open circuit potential [V]": pybamm.Scalar(0), + "Positive electrode open circuit potential [V]": pybamm.Scalar(0), "Sum of electrolyte reaction source terms [A.m-3]": pybamm.Scalar(1), "Sum of interfacial current densities [A.m-3]": pybamm.Scalar(1), "Sum of negative electrode volumetric" @@ -95,7 +95,7 @@ def test_creation(self): "primary", ) j_n = model_n.get_coupled_variables(self.variables)[ - "Negative electrode interfacial current density" + "Negative electrode interfacial current density [A.m-2]" ] model_p = pybamm.kinetics.SymmetricButlerVolmer( param, @@ -109,7 +109,7 @@ def test_creation(self): "primary", ) j_p = model_p.get_coupled_variables(self.variables)[ - "Positive electrode interfacial current density" + "Positive electrode interfacial current density [A.m-2]" ] # negative electrode Butler-Volmer is Multiplication @@ -134,7 +134,7 @@ def test_set_parameters(self): "primary", ) j_n = model_n.get_coupled_variables(self.variables)[ - "Negative electrode interfacial current density" + "Negative electrode interfacial current density [A.m-2]" ] model_p = pybamm.kinetics.SymmetricButlerVolmer( param, @@ -148,7 +148,7 @@ def test_set_parameters(self): "primary", ) j_p = model_p.get_coupled_variables(self.variables)[ - "Positive electrode interfacial current density" + "Positive electrode interfacial current density [A.m-2]" ] # Process parameters @@ -176,7 +176,7 @@ def test_discretisation(self): "primary", ) j_n = model_n.get_coupled_variables(self.variables)[ - "Negative electrode interfacial current density" + "Negative electrode interfacial current density [A.m-2]" ] model_p = pybamm.kinetics.SymmetricButlerVolmer( param, @@ -190,7 +190,7 @@ def test_discretisation(self): "primary", ) j_p = model_p.get_coupled_variables(self.variables)[ - "Positive electrode interfacial current density" + "Positive electrode interfacial current density [A.m-2]" ] j = pybamm.concatenation(j_n, pybamm.PrimaryBroadcast(0, ["separator"]), j_p) @@ -263,7 +263,7 @@ def j_n(c_e): "Negative electrolyte concentration": c_e, } return model_n.get_coupled_variables(variables)[ - "Negative electrode interfacial current density" + "Negative electrode interfacial current density [A.m-2]" ].orphans[0] def j_p(c_e): @@ -273,7 +273,7 @@ def j_p(c_e): "Positive electrolyte concentration": c_e, } return model_p.get_coupled_variables(variables)[ - "Positive electrode interfacial current density" + "Positive electrode interfacial current density [A.m-2]" ].orphans[0] c_e = pybamm.InputParameter("c_e") @@ -334,7 +334,7 @@ def j_n(delta_phi): "Negative electrolyte concentration": 1, } return model_n.get_coupled_variables(variables)[ - "Negative electrode interfacial current density" + "Negative electrode interfacial current density [A.m-2]" ].orphans[0] def j_p(delta_phi): @@ -344,7 +344,7 @@ def j_p(delta_phi): "Positive electrolyte concentration": 1, } return model_p.get_coupled_variables(variables)[ - "Positive electrode interfacial current density" + "Positive electrode interfacial current density [A.m-2]" ].orphans[0] delta_phi = pybamm.InputParameter("delta_phi") diff --git a/tests/integration/test_models/test_submodels/test_interface/test_lithium_ion.py b/tests/integration/test_models/test_submodels/test_interface/test_lithium_ion.py index c1970d527e..ffddbcd135 100644 --- a/tests/integration/test_models/test_submodels/test_interface/test_lithium_ion.py +++ b/tests/integration/test_models/test_submodels/test_interface/test_lithium_ion.py @@ -21,12 +21,12 @@ def setUp(self): pybamm.Variable("particle conc", domain=["positive particle"]) ) self.variables = { - "Negative electrolyte concentration": c_e_n, - "Positive electrolyte concentration": c_e_p, - "Negative particle surface concentration": self.c_s_n_surf, - "Positive particle surface concentration": self.c_s_p_surf, - "Negative electrode temperature": 0, - "Positive electrode temperature": 0, + "Negative electrolyte concentration [mol.m-3]": c_e_n, + "Positive electrolyte concentration [mol.m-3]": c_e_p, + "Negative particle surface concentration [mol.m-3]": self.c_s_n_surf, + "Positive particle surface concentration [mol.m-3]": self.c_s_p_surf, + "Negative electrode temperature [K]": 300, + "Positive electrode temperature [K]": 300, } self.options = {"particle size": "single"} diff --git a/tests/unit/test_parameters/test_lithium_ion_parameters.py b/tests/unit/test_parameters/test_lithium_ion_parameters.py index aea03f71fd..2d4259b18c 100644 --- a/tests/unit/test_parameters/test_lithium_ion_parameters.py +++ b/tests/unit/test_parameters/test_lithium_ion_parameters.py @@ -99,43 +99,29 @@ def test_lithium_ion(self): values.evaluate(param.n.sigma(param.T_ref)), 100, 3 ) - # neg rescaled - np.testing.assert_almost_equal( - values.evaluate(param.n.sigma_prime(param.T_ref)), 0.7814, 1 - ) - # pos np.testing.assert_almost_equal( values.evaluate(param.p.sigma(param.T_ref)), 10, 3 ) - # pos rescaled - np.testing.assert_almost_equal( - values.evaluate(param.p.sigma_prime(param.T_ref)), 0.07814, 1 - ) - def test_thermal_parameters(self): values = pybamm.lithium_ion.BaseModel().default_parameter_values param = pybamm.LithiumIonParameters() T = param.T_ref # Density - np.testing.assert_almost_equal( - values.evaluate(param.n.rho_c_p_cc(T)), 1.9019, 2 - ) - np.testing.assert_almost_equal(values.evaluate(param.n.rho_c_p(T)), 0.6403, 2) - np.testing.assert_almost_equal(values.evaluate(param.s.rho_c_p(T)), 0.1535, 2) - np.testing.assert_almost_equal(values.evaluate(param.p.rho_c_p(T)), 1.2605, 2) - np.testing.assert_almost_equal( - values.evaluate(param.p.rho_c_p_cc(T)), 1.3403, 2 - ) + np.testing.assert_equal(values.evaluate(param.n.rho_c_p_cc(T)), 8954 * 385) + np.testing.assert_equal(values.evaluate(param.n.rho_c_p(T)), 1657 * 700) + np.testing.assert_equal(values.evaluate(param.s.rho_c_p(T)), 397 * 700) + np.testing.assert_equal(values.evaluate(param.p.rho_c_p(T)), 3262 * 700) + np.testing.assert_equal(values.evaluate(param.p.rho_c_p_cc(T)), 2707 * 897) # Thermal conductivity - np.testing.assert_almost_equal(values.evaluate(param.n.lambda_cc(T)), 6.7513, 2) - np.testing.assert_almost_equal(values.evaluate(param.n.lambda_(T)), 0.0296, 2) - np.testing.assert_almost_equal(values.evaluate(param.s.lambda_(T)), 0.0027, 2) - np.testing.assert_almost_equal(values.evaluate(param.p.lambda_(T)), 0.0354, 2) - np.testing.assert_almost_equal(values.evaluate(param.p.lambda_cc(T)), 3.9901, 2) + np.testing.assert_equal(values.evaluate(param.n.lambda_cc(T)), 401) + np.testing.assert_equal(values.evaluate(param.n.lambda_(T)), 1.7) + np.testing.assert_equal(values.evaluate(param.s.lambda_(T)), 0.16) + np.testing.assert_equal(values.evaluate(param.p.lambda_(T)), 2.1) + np.testing.assert_equal(values.evaluate(param.p.lambda_cc(T)), 237) # other thermal parameters np.testing.assert_equal(values.evaluate(param.T_init), 298.15)