def _get_coupled_variables_from_potential(self, variables, phi_e_av): i_boundary_cc = variables["Current collector current density"] param = self.param l_n = param.l_n l_p = param.l_p x_n = pybamm.standard_spatial_vars.x_n x_p = pybamm.standard_spatial_vars.x_p phi_e_n = pybamm.PrimaryBroadcast(phi_e_av, ["negative electrode"]) phi_e_s = pybamm.PrimaryBroadcast(phi_e_av, ["separator"]) phi_e_p = pybamm.PrimaryBroadcast(phi_e_av, ["positive electrode"]) phi_e = pybamm.Concatenation(phi_e_n, phi_e_s, phi_e_p) i_e_n = pybamm.outer(i_boundary_cc, x_n / l_n) i_e_s = pybamm.PrimaryBroadcast(i_boundary_cc, ["separator"]) i_e_p = pybamm.outer(i_boundary_cc, (1 - x_p) / l_p) i_e = pybamm.Concatenation(i_e_n, i_e_s, i_e_p) variables.update(self._get_standard_potential_variables(phi_e, phi_e_av)) variables.update(self._get_standard_current_variables(i_e)) eta_c_av = pybamm.Scalar(0) # concentration overpotential delta_phi_e_av = pybamm.Scalar(0) # ohmic losses variables.update(self._get_split_overpotential(eta_c_av, delta_phi_e_av)) return variables
def get_coupled_variables(self, variables): param = self.param x_n = pybamm.standard_spatial_vars.x_n x_p = pybamm.standard_spatial_vars.x_p # j_n = variables["Negative electrode interfacial current density"] # j_p = variables["Positive electrode interfacial current density"] # Volume-averaged velocity # v_box_n = param.beta_n * pybamm.IndefiniteIntegral(j_n, x_n) # # Shift v_box_p to be equal to 0 at x_p = 1 # v_box_p = param.beta_p * ( # pybamm.IndefiniteIntegral(j_p, x_p) - pybamm.Integral(j_p, x_p) # ) j_n_av = variables[ "X-averaged negative electrode interfacial current density"] j_p_av = variables[ "X-averaged positive electrode interfacial current density"] # Volume-averaged velocity v_box_n = param.beta_n * pybamm.outer(j_n_av, x_n) v_box_p = param.beta_p * pybamm.outer(j_p_av, x_p - 1) v_box_s, dVbox_dz = self._separator_velocity(variables) v_box = pybamm.Concatenation(v_box_n, v_box_s, v_box_p) variables.update(self._get_standard_velocity_variables(v_box)) variables.update( self._get_standard_vertical_velocity_variables(dVbox_dz)) return variables
def test_outer(self): # Outer class v = pybamm.Vector(np.ones(5), domain="current collector") w = pybamm.Vector(2 * np.ones(3), domain="test") outer = pybamm.Outer(v, w) np.testing.assert_array_equal(outer.evaluate(), 2 * np.ones((15, 1))) self.assertEqual(outer.domain, w.domain) self.assertEqual( str(outer), "outer(Column vector of length 5, Column vector of length 3)") # outer function # if there is no domain clash, normal multiplication is retured u = pybamm.Vector(np.linspace(0, 1, 5)) outer = pybamm.outer(u, v) self.assertIsInstance(outer, pybamm.Multiplication) np.testing.assert_array_equal(outer.evaluate(), u.evaluate()) # otherwise, Outer class is returned outer_fun = pybamm.outer(v, w) outer_class = pybamm.Outer(v, w) self.assertEqual(outer_fun.id, outer_class.id) # failures y = pybamm.StateVector(slice(10)) with self.assertRaisesRegex( TypeError, "right child must only contain SpatialVariable and scalars"): pybamm.Outer(v, y) with self.assertRaises(NotImplementedError): outer_fun.diff(None)
def get_coupled_variables(self, variables): param = self.param l_n = param.l_n l_s = param.l_s l_p = param.l_p x_s = pybamm.standard_spatial_vars.x_s x_p = pybamm.standard_spatial_vars.x_p # Unpack eps_s_0_av = variables["Leading-order x-averaged separator porosity"] eps_p_0_av = variables[ "Leading-order x-averaged positive electrode porosity"] # Diffusivities D_ox_s = (eps_s_0_av**param.b_s) * param.curlyD_ox D_ox_p = (eps_p_0_av**param.b_p) * param.curlyD_ox # Reactions sj_ox_p = sum(reaction["Positive"]["s_ox"] * variables["Leading-order x-averaged " + reaction["Positive"]["aj"].lower()] for reaction in self.reactions.values()) # Fluxes N_ox_n_1 = pybamm.FullBroadcast(0, "negative electrode", "current collector") N_ox_s_1 = -pybamm.PrimaryBroadcast(sj_ox_p * l_p, "separator") N_ox_p_1 = pybamm.outer(sj_ox_p, x_p - 1) # Concentrations c_ox_n_1 = pybamm.FullBroadcast(0, "negative electrode", "current collector") c_ox_s_1 = pybamm.outer(sj_ox_p * l_p / D_ox_s, x_s - l_n) c_ox_p_1 = pybamm.outer( -sj_ox_p / (2 * D_ox_p), (x_p - 1)**2 - l_p**2) + pybamm.PrimaryBroadcast( sj_ox_p * l_p * l_s / D_ox_s, "positive electrode") # Update variables c_ox = pybamm.Concatenation(param.C_e * c_ox_n_1, param.C_e * c_ox_s_1, param.C_e * c_ox_p_1) variables.update(self._get_standard_concentration_variables(c_ox)) N_ox = pybamm.Concatenation(param.C_e * N_ox_n_1, param.C_e * N_ox_s_1, param.C_e * N_ox_p_1) variables.update(self._get_standard_flux_variables(N_ox)) return variables
def get_coupled_variables(self, variables): eps_0 = variables["Leading-order porosity"] c_e_0_av = variables[ "Leading-order x-averaged electrolyte concentration"] c_e = variables["Electrolyte concentration"] # i_e = variables["Electrolyte current density"] v_box_0 = variables["Leading-order volume-averaged velocity"] T_0 = variables["Leading-order cell temperature"] param = self.param whole_cell = ["negative electrode", "separator", "positive electrode"] N_e_diffusion = (-(eps_0**param.b) * pybamm.PrimaryBroadcast( param.D_e(c_e_0_av, T_0), whole_cell) * pybamm.grad(c_e)) # N_e_migration = (param.C_e * param.t_plus) / param.gamma_e * i_e # N_e_convection = c_e * v_box_0 # N_e = N_e_diffusion + N_e_migration + N_e_convection if v_box_0.id == pybamm.Scalar(0).id: N_e = N_e_diffusion else: N_e = N_e_diffusion + pybamm.outer(v_box_0, c_e) variables.update(self._get_standard_flux_variables(N_e)) return variables
def get_coupled_variables(self, variables): i_boundary_cc_0 = variables[ "Leading-order current collector current density"] # import parameters and spatial variables l_n = self.param.l_n l_p = self.param.l_p x_n = pybamm.standard_spatial_vars.x_n x_p = pybamm.standard_spatial_vars.x_p eps_0 = variables["Leading-order x-averaged " + self.domain.lower() + " electrode porosity"] phi_s_cn = variables["Negative current collector potential"] if self._domain == "Negative": sigma_eff_0 = self.param.sigma_n * (1 - eps_0)**self.param.b_n phi_s = pybamm.PrimaryBroadcast( phi_s_cn, "negative electrode") + pybamm.outer( i_boundary_cc_0 / sigma_eff_0, x_n * (x_n - 2 * l_n) / (2 * l_n)) i_s = pybamm.outer(i_boundary_cc_0, 1 - x_n / l_n) elif self.domain == "Positive": delta_phi_p_av = variables[ "X-averaged positive electrode surface potential difference"] phi_e_p_av = variables["X-averaged positive electrolyte potential"] sigma_eff_0 = self.param.sigma_p * (1 - eps_0)**self.param.b_p const = (delta_phi_p_av + phi_e_p_av + (i_boundary_cc_0 / sigma_eff_0) * (1 - l_p / 3)) phi_s = pybamm.PrimaryBroadcast( const, ["positive electrode"]) - pybamm.outer( i_boundary_cc_0 / sigma_eff_0, x_p + (x_p - 1)**2 / (2 * l_p)) i_s = pybamm.outer(i_boundary_cc_0, 1 - (1 - x_p) / l_p) variables.update(self._get_standard_potential_variables(phi_s)) variables.update(self._get_standard_current_variables(i_s)) if self.domain == "Positive": variables.update( self._get_standard_whole_cell_variables(variables)) return variables
def get_coupled_variables(self, variables): param = self.param x_n = pybamm.standard_spatial_vars.x_n x_p = pybamm.standard_spatial_vars.x_p j_n_av = variables["X-averaged negative electrode interfacial current density"] j_p_av = variables["X-averaged positive electrode interfacial current density"] # Volume-averaged velocity v_box_n = param.beta_n * pybamm.outer(j_n_av, x_n) v_box_p = param.beta_p * pybamm.outer(j_p_av, x_p - 1) v_box_s, dVbox_dz = self._separator_velocity(variables) v_box = pybamm.Concatenation(v_box_n, v_box_s, v_box_p) variables.update(self._get_standard_velocity_variables(v_box)) variables.update(self._get_standard_vertical_velocity_variables(dVbox_dz)) return variables
def get_coupled_variables(self, variables): """ Returns variables which are derived from the fundamental variables in the model. """ i_boundary_cc = variables["Current collector current density"] phi_s_cn = variables["Negative current collector potential"] # import parameters and spatial variables l_n = self.param.l_n l_p = self.param.l_p x_n = pybamm.standard_spatial_vars.x_n x_p = pybamm.standard_spatial_vars.x_p if self.domain == "Negative": phi_s = pybamm.PrimaryBroadcast(phi_s_cn, "negative electrode") i_s = pybamm.outer(i_boundary_cc, 1 - x_n / l_n) elif self.domain == "Positive": # recall delta_phi = phi_s - phi_e delta_phi_p_av = variables[ "X-averaged positive electrode surface potential difference"] phi_e_p_av = variables["X-averaged positive electrolyte potential"] v = delta_phi_p_av + phi_e_p_av phi_s = pybamm.PrimaryBroadcast(v, ["positive electrode"]) i_s = pybamm.outer(i_boundary_cc, 1 - (1 - x_p) / l_p) variables.update(self._get_standard_potential_variables(phi_s)) variables.update(self._get_standard_current_variables(i_s)) if self.domain == "Positive": variables.update( self._get_standard_whole_cell_variables(variables)) return variables
def _separator_velocity(self, variables): """ A private method to calculate x- and z-components of velocity in the separator Parameters ---------- variables : dict Dictionary of variables in the whole model. Returns ------- v_box_s : :class:`pybamm.Symbol` The x-component of velocity in the separator dVbox_dz : :class:`pybamm.Symbol` The z-component of velocity in the separator """ # Set up param = self.param l_n = pybamm.geometric_parameters.l_n l_s = pybamm.geometric_parameters.l_s x_s = pybamm.standard_spatial_vars.x_s # Difference in negative and positive electrode velocities determines the # velocity in the separator i_boundary_cc = variables["Current collector current density"] v_box_n_right = param.beta_n * i_boundary_cc v_box_p_left = param.beta_p * i_boundary_cc d_vbox_s__dx = (v_box_p_left - v_box_n_right) / l_s # Simple formula for velocity in the separator dVbox_dz = pybamm.Concatenation( pybamm.FullBroadcast( 0, "negative electrode", auxiliary_domains={"secondary": "current collector"}, ), pybamm.PrimaryBroadcast(-d_vbox_s__dx, "separator"), pybamm.FullBroadcast( 0, "positive electrode", auxiliary_domains={"secondary": "current collector"}, ), ) v_box_s = pybamm.outer(d_vbox_s__dx, (x_s - l_n)) + pybamm.PrimaryBroadcast( v_box_n_right, "separator") return v_box_s, dVbox_dz
def test_outer(self): var = pybamm.Variable("var", ["current collector"]) x = pybamm.SpatialVariable("x_s", ["separator"]) # create discretisation disc = get_1p1d_discretisation_for_testing() mesh = disc.mesh # process Outer variable disc.set_variable_slices([var]) outer = pybamm.outer(var, x) outer_disc = disc.process_symbol(outer) self.assertIsInstance(outer_disc, pybamm.Outer) self.assertIsInstance(outer_disc.children[0], pybamm.StateVector) self.assertIsInstance(outer_disc.children[1], pybamm.Vector) self.assertEqual( outer_disc.shape, (mesh["separator"][0].npts * mesh["current collector"][0].npts, 1), )
def get_coupled_variables(self, variables): # NOTE: the heavy use of Broadcast and outer in this method is mainly so # that products are handled correctly when using 1 or 2D current collector # models. In standard 1D battery models outer behaves as a normal multiply. # In the future, multiply will automatically handle switching between # normal multiply and outer products as appropriate. c_e_av = self.unpack(variables) i_boundary_cc_0 = variables[ "Leading-order current collector current density"] c_e = variables["Electrolyte concentration"] delta_phi_n_av = variables[ "X-averaged negative electrode surface potential difference"] phi_s_n_av = variables["X-averaged negative electrode potential"] eps_n_av = variables[ "Leading-order x-averaged negative electrode porosity"] eps_s_av = variables["Leading-order x-averaged separator porosity"] eps_p_av = variables[ "Leading-order x-averaged positive electrode porosity"] # Note: here we want the average of the temperature over the negative # electrode, separator and positive electrode (not including the current # collectors) T = variables["Cell temperature"] T_av = pybamm.x_average(T) c_e_n, c_e_s, c_e_p = c_e.orphans param = self.param l_n = param.l_n l_p = param.l_p x_n = pybamm.standard_spatial_vars.x_n x_s = pybamm.standard_spatial_vars.x_s x_p = pybamm.standard_spatial_vars.x_p # bulk conductivities kappa_n_av = param.kappa_e(c_e_av, T_av) * eps_n_av**param.b_n kappa_s_av = param.kappa_e(c_e_av, T_av) * eps_s_av**param.b_s kappa_p_av = param.kappa_e(c_e_av, T_av) * eps_p_av**param.b_p chi_av = param.chi(c_e_av) if chi_av.domain == ["current collector"]: chi_av_n = pybamm.PrimaryBroadcast(chi_av, "negative electrode") chi_av_s = pybamm.PrimaryBroadcast(chi_av, "separator") chi_av_p = pybamm.PrimaryBroadcast(chi_av, "positive electrode") else: chi_av_n = chi_av chi_av_s = chi_av chi_av_p = chi_av # electrolyte current i_e_n = pybamm.outer(i_boundary_cc_0, x_n / l_n) i_e_s = pybamm.PrimaryBroadcast(i_boundary_cc_0, "separator") i_e_p = pybamm.outer(i_boundary_cc_0, (1 - x_p) / l_p) i_e = pybamm.Concatenation(i_e_n, i_e_s, i_e_p) # electrolyte potential phi_e_const = ( -delta_phi_n_av + phi_s_n_av - (chi_av * pybamm.x_average( self._higher_order_macinnes_function( c_e_n / pybamm.PrimaryBroadcast(c_e_av, "negative electrode")))) - ((i_boundary_cc_0 * param.C_e * l_n / param.gamma_e) * (1 / (3 * kappa_n_av) - 1 / kappa_s_av))) phi_e_n = ( pybamm.PrimaryBroadcast(phi_e_const, "negative electrode") + (chi_av_n * self._higher_order_macinnes_function( c_e_n / pybamm.PrimaryBroadcast(c_e_av, "negative electrode"))) - pybamm.outer( i_boundary_cc_0 * (param.C_e / param.gamma_e) / kappa_n_av, (x_n**2 - l_n**2) / (2 * l_n), ) - pybamm.PrimaryBroadcast( i_boundary_cc_0 * l_n * (param.C_e / param.gamma_e) / kappa_s_av, "negative electrode", )) phi_e_s = ( pybamm.PrimaryBroadcast(phi_e_const, "separator") + (chi_av_s * self._higher_order_macinnes_function( c_e_s / pybamm.PrimaryBroadcast(c_e_av, "separator"))) - pybamm.outer( i_boundary_cc_0 * param.C_e / param.gamma_e / kappa_s_av, x_s)) phi_e_p = ( pybamm.PrimaryBroadcast(phi_e_const, "positive electrode") + (chi_av_p * self._higher_order_macinnes_function( c_e_p / pybamm.PrimaryBroadcast(c_e_av, "positive electrode"))) - pybamm.outer( i_boundary_cc_0 * (param.C_e / param.gamma_e) / kappa_p_av, (x_p * (2 - x_p) + l_p**2 - 1) / (2 * l_p), ) - pybamm.PrimaryBroadcast( i_boundary_cc_0 * (1 - l_p) * (param.C_e / param.gamma_e) / kappa_s_av, "positive electrode", )) phi_e = pybamm.Concatenation(phi_e_n, phi_e_s, phi_e_p) phi_e_av = pybamm.x_average(phi_e) # concentration overpotential eta_c_av = chi_av * (pybamm.x_average( self._higher_order_macinnes_function( c_e_p / pybamm.PrimaryBroadcast(c_e_av, "positive electrode"))) - pybamm.x_average( self._higher_order_macinnes_function( c_e_n / pybamm.PrimaryBroadcast( c_e_av, "negative electrode")))) # average electrolyte ohmic losses delta_phi_e_av = -(param.C_e * i_boundary_cc_0 / param.gamma_e) * ( param.l_n / (3 * kappa_n_av) + param.l_s / (kappa_s_av) + param.l_p / (3 * kappa_p_av)) variables.update( self._get_standard_potential_variables(phi_e, phi_e_av)) variables.update(self._get_standard_current_variables(i_e)) variables.update( self._get_split_overpotential(eta_c_av, delta_phi_e_av)) return variables
def get_coupled_variables(self, variables): param = self.param l_n = param.l_n l_s = param.l_s l_p = param.l_p x_n = pybamm.standard_spatial_vars.x_n x_s = pybamm.standard_spatial_vars.x_s x_p = pybamm.standard_spatial_vars.x_p # Unpack T_0 = variables["Leading-order cell temperature"] c_e_0 = variables["Leading-order x-averaged electrolyte concentration"] # v_box_0 = variables["Leading-order volume-averaged velocity"] dc_e_0_dt = variables["Leading-order electrolyte concentration change"] eps_n_0 = variables[ "Leading-order x-averaged negative electrode porosity"] eps_s_0 = variables["Leading-order x-averaged separator porosity"] eps_p_0 = variables[ "Leading-order x-averaged positive electrode porosity"] deps_n_0_dt = variables[ "Leading-order x-averaged negative electrode porosity change"] deps_p_0_dt = variables[ "Leading-order x-averaged positive electrode porosity change"] # Combined time derivatives d_epsc_n_0_dt = c_e_0 * deps_n_0_dt + eps_n_0 * dc_e_0_dt d_epsc_s_0_dt = eps_s_0 * dc_e_0_dt d_epsc_p_0_dt = c_e_0 * deps_p_0_dt + eps_p_0 * dc_e_0_dt # Right-hand sides rhs_n = d_epsc_n_0_dt - sum(reaction["Negative"]["s"] * variables[ "Leading-order x-averaged " + reaction["Negative"]["aj"].lower()] for reaction in self.reactions.values()) rhs_s = d_epsc_s_0_dt rhs_p = d_epsc_p_0_dt - sum(reaction["Positive"]["s"] * variables[ "Leading-order x-averaged " + reaction["Positive"]["aj"].lower()] for reaction in self.reactions.values()) # Diffusivities D_e_n = (eps_n_0**param.b_n) * param.D_e(c_e_0, T_0) D_e_s = (eps_s_0**param.b_s) * param.D_e(c_e_0, T_0) D_e_p = (eps_p_0**param.b_p) * param.D_e(c_e_0, T_0) # Fluxes N_e_n_1 = -pybamm.outer(rhs_n, x_n) N_e_s_1 = -(pybamm.outer(rhs_s, (x_s - l_n)) + pybamm.PrimaryBroadcast(rhs_n * l_n, "separator")) N_e_p_1 = -pybamm.outer(rhs_p, (x_p - 1)) # Concentrations c_e_n_1 = pybamm.outer(rhs_n / (2 * D_e_n), x_n**2 - l_n**2) c_e_s_1 = pybamm.outer(rhs_s / 2, (x_s - l_n)**2) + pybamm.outer( rhs_n * l_n / D_e_s, x_s - l_n) c_e_p_1 = pybamm.outer( rhs_p / (2 * D_e_p), (x_p - 1)**2 - l_p**2) + pybamm.PrimaryBroadcast( (rhs_s * l_s**2 / (2 * D_e_s)) + (rhs_n * l_n * l_s / D_e_s), "positive electrode", ) # Correct for integral c_e_n_1_av = -rhs_n * l_n**3 / (3 * D_e_n) c_e_s_1_av = (rhs_s * l_s**3 / 6 + rhs_n * l_n * l_s**2 / 2) / D_e_s c_e_p_1_av = (-rhs_p * l_p**3 / (3 * D_e_p) + (rhs_s * l_s**2 * l_p / (2 * D_e_s)) + (rhs_n * l_n * l_s * l_p / D_e_s)) A_e = -(eps_n_0 * c_e_n_1_av + eps_s_0 * c_e_s_1_av + eps_p_0 * c_e_p_1_av) / (l_n * eps_n_0 + l_s * eps_s_0 + l_p * eps_p_0) c_e_n_1 += pybamm.PrimaryBroadcast(A_e, "negative electrode") c_e_s_1 += pybamm.PrimaryBroadcast(A_e, "separator") c_e_p_1 += pybamm.PrimaryBroadcast(A_e, "positive electrode") c_e_n_1_av += A_e c_e_s_1_av += A_e c_e_p_1_av += A_e # Update variables c_e = pybamm.Concatenation( pybamm.PrimaryBroadcast(c_e_0, "negative electrode") + param.C_e * c_e_n_1, pybamm.PrimaryBroadcast(c_e_0, "separator") + param.C_e * c_e_s_1, pybamm.PrimaryBroadcast(c_e_0, "positive electrode") + param.C_e * c_e_p_1, ) variables.update(self._get_standard_concentration_variables(c_e)) # Update with analytical expressions for first-order x-averages variables.update({ "X-averaged first-order negative electrolyte concentration": c_e_n_1_av, "X-averaged first-order separator concentration": c_e_s_1_av, "X-averaged first-order positive electrolyte concentration": c_e_p_1_av, }) N_e = pybamm.Concatenation(param.C_e * N_e_n_1, param.C_e * N_e_s_1, param.C_e * N_e_p_1) variables.update(self._get_standard_flux_variables(N_e)) return variables