def test_arcsinh(self): a = pybamm.InputParameter("a") fun = pybamm.arcsinh(a) self.assertIsInstance(fun, pybamm.Arcsinh) self.assertEqual(fun.evaluate(inputs={"a": 3}), np.arcsinh(3)) h = 0.0000001 self.assertAlmostEqual( fun.diff(a).evaluate(inputs={"a": 3}), (pybamm.arcsinh(pybamm.Scalar(3 + h)).evaluate() - fun.evaluate(inputs={"a": 3})) / h, places=5, ) # Test broadcast gets switched broad_a = pybamm.PrimaryBroadcast(a, "test") fun_broad = pybamm.arcsinh(broad_a) self.assertEqual(fun_broad.id, pybamm.PrimaryBroadcast(fun, "test").id) broad_a = pybamm.FullBroadcast(a, "test", "test2") fun_broad = pybamm.arcsinh(broad_a) self.assertEqual(fun_broad.id, pybamm.FullBroadcast(fun, "test", "test2").id) # Test recursion broad_a = pybamm.PrimaryBroadcast(pybamm.PrimaryBroadcast(a, "test"), "test2") fun_broad = pybamm.arcsinh(broad_a) self.assertEqual( fun_broad.id, pybamm.PrimaryBroadcast(pybamm.PrimaryBroadcast(fun, "test"), "test2").id, )
def test_arcsinh(self): a = pybamm.InputParameter("a") fun = pybamm.arcsinh(a) self.assertIsInstance(fun, pybamm.Arcsinh) self.assertEqual(fun.evaluate(inputs={"a": 3}), np.arcsinh(3)) h = 0.0000001 self.assertAlmostEqual( fun.diff(a).evaluate(inputs={"a": 3}), (pybamm.arcsinh(pybamm.Scalar(3 + h)).evaluate() - fun.evaluate(inputs={"a": 3})) / h, places=5, )
def _get_overpotential(self, j, j0, ne, T): return (2 * (1 + self.param.Theta * T) / ne) * pybamm.arcsinh(j / (2 * j0))
def __init__(self, name="Doyle-Fuller-Newman half cell model", options=None): super().__init__({}, name) pybamm.citations.register("marquis2019asymptotic") # `param` is a class containing all the relevant parameters and functions for # this model. These are purely symbolic at this stage, and will be set by the # `ParameterValues` class when the model is processed. param = self.param options = options or {"working electrode": None} if options["working electrode"] not in ["negative", "positive"]: raise ValueError( "The option 'working electrode' should be either 'positive'" " or 'negative'" ) self.options.update(options) working_electrode = options["working electrode"] ###################### # Variables ###################### # Variables that depend on time only are created without a domain Q = pybamm.Variable("Discharge capacity [A.h]") # Define some useful scalings pot = param.potential_scale i_typ = param.current_scale # Variables that vary spatially are created with a domain. Depending on # which is the working electrode we need to define a set variables or another if working_electrode == "negative": # Electrolyte concentration c_e_n = pybamm.Variable( "Negative electrolyte concentration", domain="negative electrode" ) c_e_s = pybamm.Variable( "Separator electrolyte concentration", domain="separator" ) # Concatenations combine several variables into a single variable, to # simplify implementing equations that hold over several domains c_e = pybamm.Concatenation(c_e_n, c_e_s) # Electrolyte potential phi_e_n = pybamm.Variable( "Negative electrolyte potential", domain="negative electrode" ) phi_e_s = pybamm.Variable( "Separator electrolyte potential", domain="separator" ) phi_e = pybamm.Concatenation(phi_e_n, phi_e_s) # Particle concentrations are variables on the particle domain, but also # vary in the x-direction (electrode domain) and so must be provided with # auxiliary domains c_s_n = pybamm.Variable( "Negative particle concentration", domain="negative particle", auxiliary_domains={"secondary": "negative electrode"}, ) # Set concentration in positive particle to be equal to the initial # concentration as it is not the working electrode x_p = pybamm.PrimaryBroadcast( pybamm.standard_spatial_vars.x_p, "positive particle" ) c_s_p = param.c_n_init(x_p) # Electrode potential phi_s_n = pybamm.Variable( "Negative electrode potential", domain="negative electrode" ) # Set potential in positive electrode to be equal to the initial OCV phi_s_p = param.U_p(pybamm.surf(param.c_p_init(x_p)), param.T_init) else: c_e_p = pybamm.Variable( "Positive electrolyte concentration", domain="positive electrode" ) c_e_s = pybamm.Variable( "Separator electrolyte concentration", domain="separator" ) # Concatenations combine several variables into a single variable, to # simplify implementing equations that hold over several domains c_e = pybamm.Concatenation(c_e_s, c_e_p) # Electrolyte potential phi_e_s = pybamm.Variable( "Separator electrolyte potential", domain="separator" ) phi_e_p = pybamm.Variable( "Positive electrolyte potential", domain="positive electrode" ) phi_e = pybamm.Concatenation(phi_e_s, phi_e_p) # Particle concentrations are variables on the particle domain, but also # vary in the x-direction (electrode domain) and so must be provided with # auxiliary domains c_s_p = pybamm.Variable( "Positive particle concentration", domain="positive particle", auxiliary_domains={"secondary": "positive electrode"}, ) # Set concentration in negative particle to be equal to the initial # concentration as it is not the working electrode x_n = pybamm.PrimaryBroadcast( pybamm.standard_spatial_vars.x_n, "negative particle" ) c_s_n = param.c_n_init(x_n) # Electrode potential phi_s_p = pybamm.Variable( "Positive electrode potential", domain="positive electrode" ) # Set potential in negative electrode to be equal to the initial OCV phi_s_n = param.U_n(pybamm.surf(param.c_n_init(x_n)), param.T_init) # Constant temperature T = param.T_init ###################### # Other set-up ###################### # Current density i_cell = param.current_with_time # Porosity and Tortuosity # Primary broadcasts are used to broadcast scalar quantities across a domain # into a vector of the right shape, for multiplying with other vectors eps_n = pybamm.PrimaryBroadcast( pybamm.Parameter("Negative electrode porosity"), "negative electrode" ) eps_s = pybamm.PrimaryBroadcast( pybamm.Parameter("Separator porosity"), "separator" ) eps_p = pybamm.PrimaryBroadcast( pybamm.Parameter("Positive electrode porosity"), "positive electrode" ) if working_electrode == "negative": eps = pybamm.Concatenation(eps_n, eps_s) tor = pybamm.Concatenation(eps_n ** param.b_e_n, eps_s ** param.b_e_s) else: eps = pybamm.Concatenation(eps_s, eps_p) tor = pybamm.Concatenation(eps_s ** param.b_e_s, eps_p ** param.b_e_p) # Interfacial reactions # Surf takes the surface value of a variable, i.e. its boundary value on the # right side. This is also accessible via `boundary_value(x, "right")`, with # "left" providing the boundary value of the left side c_s_surf_n = pybamm.surf(c_s_n) c_s_surf_p = pybamm.surf(c_s_p) if working_electrode == "negative": j0_n = param.j0_n(c_e_n, c_s_surf_n, T) / param.C_r_n j_n = ( 2 * j0_n * pybamm.sinh( param.ne_n / 2 * (phi_s_n - phi_e_n - param.U_n(c_s_surf_n, T)) ) ) j_s = pybamm.PrimaryBroadcast(0, "separator") j_p = pybamm.PrimaryBroadcast(0, "positive electrode") j = pybamm.Concatenation(j_n, j_s) else: j0_p = param.gamma_p * param.j0_p(c_e_p, c_s_surf_p, T) / param.C_r_p j_p = ( 2 * j0_p * pybamm.sinh( param.ne_p / 2 * (phi_s_p - phi_e_p - param.U_p(c_s_surf_p, T)) ) ) j_s = pybamm.PrimaryBroadcast(0, "separator") j_n = pybamm.PrimaryBroadcast(0, "negative electrode") j = pybamm.Concatenation(j_s, j_p) ###################### # State of Charge ###################### I = param.dimensional_current_with_time # The `rhs` dictionary contains differential equations, with the key being the # variable in the d/dt self.rhs[Q] = I * param.timescale / 3600 # Initial conditions must be provided for the ODEs self.initial_conditions[Q] = pybamm.Scalar(0) ###################### # Particles ###################### if working_electrode == "negative": # The div and grad operators will be converted to the appropriate matrix # multiplication at the discretisation stage N_s_n = -param.D_n(c_s_n, T) * pybamm.grad(c_s_n) self.rhs[c_s_n] = -(1 / param.C_n) * pybamm.div(N_s_n) # Boundary conditions must be provided for equations with spatial # derivatives self.boundary_conditions[c_s_n] = { "left": (pybamm.Scalar(0), "Neumann"), "right": ( -param.C_n * j_n / param.a_R_n / param.D_n(c_s_surf_n, T), "Neumann", ), } # c_n_init can in general be a function of x # Note the broadcasting, for domains x_n = pybamm.PrimaryBroadcast( pybamm.standard_spatial_vars.x_n, "negative particle" ) self.initial_conditions[c_s_n] = param.c_n_init(x_n) # Events specify points at which a solution should terminate self.events += [ pybamm.Event( "Minimum negative particle surface concentration", pybamm.min(c_s_surf_n) - 0.01, ), pybamm.Event( "Maximum negative particle surface concentration", (1 - 0.01) - pybamm.max(c_s_surf_n), ), ] else: # The div and grad operators will be converted to the appropriate matrix # multiplication at the discretisation stage N_s_p = -param.D_p(c_s_p, T) * pybamm.grad(c_s_p) self.rhs[c_s_p] = -(1 / param.C_p) * pybamm.div(N_s_p) # Boundary conditions must be provided for equations with spatial # derivatives self.boundary_conditions[c_s_p] = { "left": (pybamm.Scalar(0), "Neumann"), "right": ( -param.C_p * j_p / param.a_R_p / param.gamma_p / param.D_p(c_s_surf_p, T), "Neumann", ), } # c_p_init can in general be a function of x # Note the broadcasting, for domains x_p = pybamm.PrimaryBroadcast( pybamm.standard_spatial_vars.x_p, "positive particle" ) self.initial_conditions[c_s_p] = param.c_p_init(x_p) # Events specify points at which a solution should terminate self.events += [ pybamm.Event( "Minimum positive particle surface concentration", pybamm.min(c_s_surf_p) - 0.01, ), pybamm.Event( "Maximum positive particle surface concentration", (1 - 0.01) - pybamm.max(c_s_surf_p), ), ] ###################### # Current in the solid ###################### eps_s_n = pybamm.Parameter("Negative electrode active material volume fraction") eps_s_p = pybamm.Parameter("Positive electrode active material volume fraction") if working_electrode == "negative": sigma_eff_n = param.sigma_n * eps_s_n ** param.b_s_n i_s_n = -sigma_eff_n * pybamm.grad(phi_s_n) self.boundary_conditions[phi_s_n] = { "left": ( i_cell / pybamm.boundary_value(-sigma_eff_n, "left"), "Neumann", ), "right": (pybamm.Scalar(0), "Neumann"), } # The `algebraic` dictionary contains differential equations, with the key # being the main scalar variable of interest in the equation self.algebraic[phi_s_n] = pybamm.div(i_s_n) + j_n # Initial conditions must also be provided for algebraic equations, as an # initial guess for a root-finding algorithm which calculates consistent # initial conditions self.initial_conditions[phi_s_n] = param.U_n( param.c_n_init(0), param.T_init ) else: sigma_eff_p = param.sigma_p * eps_s_p ** param.b_s_p i_s_p = -sigma_eff_p * pybamm.grad(phi_s_p) self.boundary_conditions[phi_s_p] = { "left": (pybamm.Scalar(0), "Neumann"), "right": ( i_cell / pybamm.boundary_value(-sigma_eff_p, "right"), "Neumann", ), } self.algebraic[phi_s_p] = pybamm.div(i_s_p) + j_p # Initial conditions must also be provided for algebraic equations, as an # initial guess for a root-finding algorithm which calculates consistent # initial conditions self.initial_conditions[phi_s_p] = param.U_p( param.c_p_init(1), param.T_init ) ###################### # Electrolyte concentration ###################### N_e = -tor * param.D_e(c_e, T) * pybamm.grad(c_e) self.rhs[c_e] = (1 / eps) * ( -pybamm.div(N_e) / param.C_e + (1 - param.t_plus(c_e)) * j / param.gamma_e ) dce_dx = ( -(1 - param.t_plus(c_e)) * i_cell * param.C_e / (tor * param.gamma_e * param.D_e(c_e, T)) ) if working_electrode == "negative": self.boundary_conditions[c_e] = { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.boundary_value(dce_dx, "right"), "Neumann"), } else: self.boundary_conditions[c_e] = { "left": (pybamm.boundary_value(dce_dx, "left"), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } self.initial_conditions[c_e] = param.c_e_init self.events.append( pybamm.Event( "Zero electrolyte concentration cut-off", pybamm.min(c_e) - 0.002 ) ) ###################### # Current in the electrolyte ###################### i_e = (param.kappa_e(c_e, T) * tor * param.gamma_e / param.C_e) * ( param.chi(c_e, T) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e) ) self.algebraic[phi_e] = pybamm.div(i_e) - j ref_potential = param.U_n_ref / pot if working_electrode == "negative": self.boundary_conditions[phi_e] = { "left": (pybamm.Scalar(0), "Neumann"), "right": (ref_potential, "Dirichlet"), } else: self.boundary_conditions[phi_e] = { "left": (ref_potential, "Dirichlet"), "right": (pybamm.Scalar(0), "Neumann"), } self.initial_conditions[phi_e] = ref_potential ###################### # (Some) variables ###################### L_Li = pybamm.Parameter("Lithium counter electrode thickness [m]") sigma_Li = pybamm.Parameter("Lithium counter electrode conductivity [S.m-1]") j_Li = pybamm.Parameter( "Lithium counter electrode exchange-current density [A.m-2]" ) if working_electrode == "negative": voltage = pybamm.boundary_value(phi_s_n, "left") - ref_potential voltage_dim = pot * pybamm.boundary_value(phi_s_n, "left") vdrop_Li = 2 * pybamm.arcsinh( i_cell * i_typ / j_Li ) + L_Li * i_typ * i_cell / (sigma_Li * pot) vdrop_Li_dim = ( 2 * pot * pybamm.arcsinh(i_cell * i_typ / j_Li) + L_Li * i_typ * i_cell / sigma_Li ) else: voltage = pybamm.boundary_value(phi_s_p, "right") - ref_potential voltage_dim = param.U_p_ref + pot * voltage vdrop_Li = -( 2 * pybamm.arcsinh(i_cell * i_typ / j_Li) + L_Li * i_typ * i_cell / (sigma_Li * pot) ) vdrop_Li_dim = -( 2 * pot * pybamm.arcsinh(i_cell * i_typ / j_Li) + L_Li * i_typ * i_cell / sigma_Li ) c_s_surf_p_av = pybamm.x_average(c_s_surf_p) c_s_surf_n_av = pybamm.x_average(c_s_surf_n) # The `variables` dictionary contains all variables that might be useful for # visualising the solution of the model self.variables = { "Time [s]": param.timescale * pybamm.t, "Negative particle surface concentration": c_s_surf_n, "X-averaged negative particle surface concentration": c_s_surf_n_av, "Negative particle concentration": c_s_n, "Negative particle surface concentration [mol.m-3]": param.c_n_max * c_s_surf_n, "X-averaged negative particle surface concentration " "[mol.m-3]": param.c_n_max * c_s_surf_n_av, "Negative particle concentration [mol.m-3]": param.c_n_max * c_s_n, "Electrolyte concentration": c_e, "Electrolyte concentration [mol.m-3]": param.c_e_typ * c_e, "Positive particle surface concentration": c_s_surf_p, "X-averaged positive particle surface concentration": c_s_surf_p_av, "Positive particle concentration": c_s_p, "Positive particle surface concentration [mol.m-3]": param.c_p_max * c_s_surf_p, "X-averaged positive particle surface concentration " "[mol.m-3]": param.c_p_max * c_s_surf_p_av, "Positive particle concentration [mol.m-3]": param.c_p_max * c_s_p, "Current [A]": I, "Negative electrode potential": phi_s_n, "Negative electrode potential [V]": pot * phi_s_n, "Negative electrode open circuit potential": param.U_n(c_s_surf_n, T), "Electrolyte potential": phi_e, "Electrolyte potential [V]": -param.U_n_ref + pot * phi_e, "Positive electrode potential": phi_s_p, "Positive electrode potential [V]": (param.U_p_ref - param.U_n_ref) + pot * phi_s_p, "Positive electrode open circuit potential": param.U_p(c_s_surf_p, T), "Voltage drop": voltage, "Voltage drop [V]": voltage_dim, "Terminal voltage": voltage + vdrop_Li, "Terminal voltage [V]": voltage_dim + vdrop_Li_dim, }
def __init__(self, name="Single Particle Model"): super().__init__({}, name) pybamm.citations.register("Marquis2019") # `param` is a class containing all the relevant parameters and functions for # this model. These are purely symbolic at this stage, and will be set by the # `ParameterValues` class when the model is processed. param = self.param ###################### # Variables ###################### # Variables that depend on time only are created without a domain Q = pybamm.Variable("Discharge capacity [A.h]") # Variables that vary spatially are created with a domain c_s_n = pybamm.Variable( "X-averaged negative particle concentration", domain="negative particle" ) c_s_p = pybamm.Variable( "X-averaged positive particle concentration", domain="positive particle" ) # Constant temperature T = param.T_init ###################### # Other set-up ###################### # Current density i_cell = param.current_with_time j_n = i_cell / param.l_n j_p = -i_cell / param.l_p ###################### # State of Charge ###################### I = param.dimensional_current_with_time # The `rhs` dictionary contains differential equations, with the key being the # variable in the d/dt self.rhs[Q] = I * param.timescale / 3600 # Initial conditions must be provided for the ODEs self.initial_conditions[Q] = pybamm.Scalar(0) ###################### # Particles ###################### # The div and grad operators will be converted to the appropriate matrix # multiplication at the discretisation stage N_s_n = -param.D_n(c_s_n, T) * pybamm.grad(c_s_n) N_s_p = -param.D_p(c_s_p, T) * pybamm.grad(c_s_p) self.rhs[c_s_n] = -(1 / param.C_n) * pybamm.div(N_s_n) self.rhs[c_s_p] = -(1 / param.C_p) * pybamm.div(N_s_p) # Surf takes the surface value of a variable, i.e. its boundary value on the # right side. This is also accessible via `boundary_value(x, "right")`, with # "left" providing the boundary value of the left side c_s_surf_n = pybamm.surf(c_s_n) c_s_surf_p = pybamm.surf(c_s_p) # Boundary conditions must be provided for equations with spatial derivatives self.boundary_conditions[c_s_n] = { "left": (pybamm.Scalar(0), "Neumann"), "right": ( -param.C_n * j_n / param.a_R_n / param.D_n(c_s_surf_n, T), "Neumann", ), } self.boundary_conditions[c_s_p] = { "left": (pybamm.Scalar(0), "Neumann"), "right": ( -param.C_p * j_p / param.a_R_p / param.gamma_p / param.D_p(c_s_surf_p, T), "Neumann", ), } # c_n_init and c_p_init are functions, but for the SPM we evaluate them at x=0 # and x=1 since there is no x-dependence in the particles self.initial_conditions[c_s_n] = param.c_n_init(0) self.initial_conditions[c_s_p] = param.c_p_init(1) # Events specify points at which a solution should terminate self.events += [ pybamm.Event( "Minimum negative particle surface concentration", pybamm.min(c_s_surf_n) - 0.01, ), pybamm.Event( "Maximum negative particle surface concentration", (1 - 0.01) - pybamm.max(c_s_surf_n), ), pybamm.Event( "Minimum positive particle surface concentration", pybamm.min(c_s_surf_p) - 0.01, ), pybamm.Event( "Maximum positive particle surface concentration", (1 - 0.01) - pybamm.max(c_s_surf_p), ), ] # Note that the SPM does not have any algebraic equations, so the `algebraic` # dictionary remains empty ###################### # (Some) variables ###################### # Interfacial reactions j0_n = param.j0_n(1, c_s_surf_n, T) / param.C_r_n j0_p = param.gamma_p * param.j0_p(1, c_s_surf_p, T) / param.C_r_p eta_n = (2 / param.ne_n) * pybamm.arcsinh(j_n / (2 * j0_n)) eta_p = (2 / param.ne_p) * pybamm.arcsinh(j_p / (2 * j0_p)) phi_s_n = 0 phi_e = -eta_n - param.U_n(c_s_surf_n, T) phi_s_p = eta_p + phi_e + param.U_p(c_s_surf_p, T) V = phi_s_p whole_cell = ["negative electrode", "separator", "positive electrode"] # The `variables` dictionary contains all variables that might be useful for # visualising the solution of the model # Primary broadcasts are used to broadcast scalar quantities across a domain # into a vector of the right shape, for multiplying with other vectors self.variables = { "Negative particle surface concentration": pybamm.PrimaryBroadcast( c_s_surf_n, "negative electrode" ), "Electrolyte concentration": pybamm.PrimaryBroadcast(1, whole_cell), "Positive particle surface concentration": pybamm.PrimaryBroadcast( c_s_surf_p, "positive electrode" ), "Current [A]": I, "Negative electrode potential": pybamm.PrimaryBroadcast( phi_s_n, "negative electrode" ), "Electrolyte potential": pybamm.PrimaryBroadcast(phi_e, whole_cell), "Positive electrode potential": pybamm.PrimaryBroadcast( phi_s_p, "positive electrode" ), "Terminal voltage": V, } self.events += [ pybamm.Event("Minimum voltage", V - param.voltage_low_cut), pybamm.Event("Maximum voltage", V - param.voltage_high_cut), ]
def __init__(self, name="Doyle-Fuller-Newman half cell model", options=None): super().__init__({}, name) pybamm.citations.register("Marquis2019") # `param` is a class containing all the relevant parameters and functions for # this model. These are purely symbolic at this stage, and will be set by the # `ParameterValues` class when the model is processed. param = self.param options = options or {"working electrode": None} if options["working electrode"] not in ["negative", "positive"]: raise ValueError( "The option 'working electrode' should be either 'positive'" " or 'negative'" ) self.options.update(options) working_electrode = options["working electrode"] if working_electrode == "negative": R_w_typ = param.R_n_typ else: R_w_typ = param.R_p_typ # Set default length scales self.length_scales = { "working electrode": param.L_x, "separator": param.L_x, "working particle": R_w_typ, "current collector y": param.L_z, "current collector z": param.L_z, } ###################### # Variables ###################### # Variables that depend on time only are created without a domain Q = pybamm.Variable("Discharge capacity [A.h]") # Define some useful scalings pot = param.potential_scale i_typ = param.current_scale # Variables that vary spatially are created with a domain. c_e_s = pybamm.Variable( "Separator electrolyte concentration", domain="separator" ) c_e_w = pybamm.Variable( "Working electrolyte concentration", domain="working electrode" ) c_e = pybamm.concatenation(c_e_s, c_e_w) c_s_w = pybamm.Variable( "Working particle concentration", domain="working particle", auxiliary_domains={"secondary": "working electrode"}, ) phi_s_w = pybamm.Variable( "Working electrode potential", domain="working electrode" ) phi_e_s = pybamm.Variable("Separator electrolyte potential", domain="separator") phi_e_w = pybamm.Variable( "Working electrolyte potential", domain="working electrode" ) phi_e = pybamm.concatenation(phi_e_s, phi_e_w) # Constant temperature T = param.T_init ###################### # Other set-up ###################### # Current density i_cell = param.current_with_time # Define particle surface concentration # Surf takes the surface value of a variable, i.e. its boundary value on the # right side. This is also accessible via `boundary_value(x, "right")`, with # "left" providing the boundary value of the left side c_s_surf_w = pybamm.surf(c_s_w) # Define parameters. We need to assemble them differently depending on the # working electrode if working_electrode == "negative": # Porosity and Tortuosity # Primary broadcasts are used to broadcast scalar quantities across a domain # into a vector of the right shape, for multiplying with other vectors eps_s = pybamm.PrimaryBroadcast( pybamm.Parameter("Separator porosity"), "separator" ) eps_w = pybamm.PrimaryBroadcast( pybamm.Parameter("Negative electrode porosity"), "working electrode" ) b_e_s = param.b_e_s b_e_w = param.b_e_n # Interfacial reactions j0_w = param.j0_n(c_e_w, c_s_surf_w, T) / param.C_r_n U_w = param.U_n ne_w = param.ne_n # Particle diffusion parameters D_w = param.D_n C_w = param.C_n a_R_w = param.a_R_n gamma_w = pybamm.Scalar(1) c_w_init = param.c_n_init # Electrode equation parameters eps_s_w = pybamm.Parameter( "Negative electrode active material volume fraction" ) b_s_w = param.b_s_n sigma_w = param.sigma_n # Other parameters (for outputs) c_w_max = param.c_n_max U_ref = param.U_n_ref phi_s_w_ref = pybamm.Scalar(0) L_w = param.L_n else: # Porosity and Tortuosity eps_s = pybamm.PrimaryBroadcast( pybamm.Parameter("Separator porosity"), "separator" ) eps_w = pybamm.PrimaryBroadcast( pybamm.Parameter("Positive electrode porosity"), "working electrode" ) b_e_s = param.b_e_s b_e_w = param.b_e_p # Interfacial reactions j0_w = param.gamma_p * param.j0_p(c_e_w, c_s_surf_w, T) / param.C_r_p U_w = param.U_p ne_w = param.ne_p # Particle diffusion parameters D_w = param.D_p C_w = param.C_p a_R_w = param.a_R_p gamma_w = param.gamma_p c_w_init = param.c_p_init # Electrode equation parameters eps_s_w = pybamm.Parameter( "Positive electrode active material volume fraction" ) b_s_w = param.b_s_p sigma_w = param.sigma_p # Other parameters (for outputs) c_w_max = param.c_p_max U_ref = param.U_p_ref phi_s_w_ref = param.U_p_ref - param.U_n_ref L_w = param.L_p eps = pybamm.concatenation(eps_s, eps_w) tor = pybamm.concatenation(eps_s ** b_e_s, eps_w ** b_e_w) j_w = ( 2 * j0_w * pybamm.sinh(ne_w / 2 * (phi_s_w - phi_e_w - U_w(c_s_surf_w, T))) ) j_s = pybamm.PrimaryBroadcast(0, "separator") j = pybamm.concatenation(j_s, j_w) ###################### # State of Charge ###################### I = param.dimensional_current_with_time # The `rhs` dictionary contains differential equations, with the key being the # variable in the d/dt self.rhs[Q] = I * param.timescale / 3600 # Initial conditions must be provided for the ODEs self.initial_conditions[Q] = pybamm.Scalar(0) ###################### # Particles ###################### # The div and grad operators will be converted to the appropriate matrix # multiplication at the discretisation stage N_s_w = -D_w(c_s_w, T) * pybamm.grad(c_s_w) self.rhs[c_s_w] = -(1 / C_w) * pybamm.div(N_s_w) # Boundary conditions must be provided for equations with spatial # derivatives self.boundary_conditions[c_s_w] = { "left": (pybamm.Scalar(0), "Neumann"), "right": ( -C_w * j_w / a_R_w / gamma_w / D_w(c_s_surf_w, T), "Neumann", ), } # c_w_init can in general be a function of x # Note the broadcasting, for domains x_w = pybamm.PrimaryBroadcast(half_cell_spatial_vars.x_w, "working particle") self.initial_conditions[c_s_w] = c_w_init(x_w) # Events specify points at which a solution should terminate self.events += [ pybamm.Event( "Minimum working particle surface concentration", pybamm.min(c_s_surf_w) - 0.01, ), pybamm.Event( "Maximum working particle surface concentration", (1 - 0.01) - pybamm.max(c_s_surf_w), ), ] ###################### # Current in the solid ###################### sigma_eff_w = sigma_w * eps_s_w ** b_s_w i_s_w = -sigma_eff_w * pybamm.grad(phi_s_w) self.boundary_conditions[phi_s_w] = { "left": (pybamm.Scalar(0), "Neumann"), "right": ( i_cell / pybamm.boundary_value(-sigma_eff_w, "right"), "Neumann", ), } self.algebraic[phi_s_w] = pybamm.div(i_s_w) + j_w # Initial conditions must also be provided for algebraic equations, as an # initial guess for a root-finding algorithm which calculates consistent # initial conditions self.initial_conditions[phi_s_w] = U_w(c_w_init(1), param.T_init) ###################### # Electrolyte concentration ###################### N_e = -tor * param.D_e(c_e, T) * pybamm.grad(c_e) self.rhs[c_e] = (1 / eps) * ( -pybamm.div(N_e) / param.C_e + (1 - param.t_plus(c_e, T)) * j / param.gamma_e ) dce_dx = ( -(1 - param.t_plus(c_e, T)) * i_cell * param.C_e / (tor * param.gamma_e * param.D_e(c_e, T)) ) self.boundary_conditions[c_e] = { "left": (pybamm.boundary_value(dce_dx, "left"), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } self.initial_conditions[c_e] = param.c_e_init self.events.append( pybamm.Event( "Zero electrolyte concentration cut-off", pybamm.min(c_e) - 0.002 ) ) ###################### # Current in the electrolyte ###################### i_e = (param.kappa_e(c_e, T) * tor * param.gamma_e / param.C_e) * ( param.chi(c_e, T) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e) ) self.algebraic[phi_e] = pybamm.div(i_e) - j ref_potential = param.U_n_ref / pot self.boundary_conditions[phi_e] = { "left": (ref_potential, "Dirichlet"), "right": (pybamm.Scalar(0), "Neumann"), } self.initial_conditions[phi_e] = ref_potential ###################### # (Some) variables ###################### L_Li = pybamm.Parameter("Lithium counter electrode thickness [m]") sigma_Li = pybamm.Parameter("Lithium counter electrode conductivity [S.m-1]") j_Li = pybamm.Parameter( "Lithium counter electrode exchange-current density [A.m-2]" ) vdrop_cell = pybamm.boundary_value(phi_s_w, "right") - ref_potential vdrop_Li = -( 2 * pybamm.arcsinh(i_cell * i_typ / j_Li) + L_Li * i_typ * i_cell / (sigma_Li * pot) ) voltage = vdrop_cell + vdrop_Li c_e_total = pybamm.x_average(eps * c_e) c_s_surf_w_av = pybamm.x_average(c_s_surf_w) c_s_rav = pybamm.r_average(c_s_w) c_s_vol_av = pybamm.x_average(eps_s_w * c_s_rav) # The `variables` dictionary contains all variables that might be useful for # visualising the solution of the model self.variables = { "Time [s]": param.timescale * pybamm.t, "Working particle surface concentration": c_s_surf_w, "X-averaged working particle surface concentration": c_s_surf_w_av, "Working particle concentration": c_s_w, "Working particle surface concentration [mol.m-3]": c_w_max * c_s_surf_w, "X-averaged working particle surface concentration " "[mol.m-3]": c_w_max * c_s_surf_w_av, "Working particle concentration [mol.m-3]": c_w_max * c_s_w, "Total lithium in working electrode": c_s_vol_av, "Total lithium in working electrode [mol]": c_s_vol_av * c_w_max * L_w * param.A_cc, "Electrolyte concentration": c_e, "Electrolyte concentration [mol.m-3]": param.c_e_typ * c_e, "Total electrolyte concentration": c_e_total, "Total electrolyte concentration [mol]": c_e_total * param.c_e_typ * L_w * param.L_s * param.A_cc, "Current [A]": I, "Working electrode potential": phi_s_w, "Working electrode potential [V]": phi_s_w_ref + pot * phi_s_w, "Working electrode open circuit potential": U_w(c_s_surf_w, T), "Working electrode open circuit potential [V]": U_ref + pot * U_w(c_s_surf_w, T), "Electrolyte potential": phi_e, "Electrolyte potential [V]": -param.U_n_ref + pot * phi_e, "Voltage drop in the cell": vdrop_cell, "Voltage drop in the cell [V]": phi_s_w_ref + param.U_n_ref + pot * vdrop_cell, "Terminal voltage": voltage, "Terminal voltage [V]": phi_s_w_ref + param.U_n_ref + pot * voltage, }