def test_inner(self): model = pybamm.lithium_ion.BaseModel() phi_s = pybamm.standard_variables.phi_s_n i = pybamm.grad(phi_s) model.rhs = {phi_s: pybamm.inner(i, i)} model.boundary_conditions = { phi_s: { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } } model.initial_conditions = {phi_s: pybamm.Scalar(0)} model.variables = {"inner": pybamm.inner(i, i)} # load parameter values and process model and geometry param = model.default_parameter_values geometry = model.default_geometry param.process_model(model) param.process_geometry(geometry) # set mesh mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) # check doesn't evaluate on edges anymore self.assertEqual(model.variables["inner"].evaluates_on_edges("primary"), False)
def _current_collector_heating(self, variables): "Compute Ohmic heating in current collectors" # TODO: implement grad in 0D to return a scalar zero # TODO: implement grad_squared in other spatial methods so that the if # statement can be removed # In the limit of infinitely large current collector conductivity (i.e. # 0D current collectors), the Ohmic heating in the current collectors is # zero if self.cc_dimension == 0: Q_s_cn = pybamm.Scalar(0) Q_s_cp = pybamm.Scalar(0) # Otherwise we compute the Ohmic heating for 1 or 2D current collectors elif self.cc_dimension in [1, 2]: phi_s_cn = variables["Negative current collector potential"] phi_s_cp = variables["Positive current collector potential"] if self.cc_dimension == 1: Q_s_cn = self.param.sigma_cn_prime * pybamm.inner( pybamm.grad(phi_s_cn), pybamm.grad(phi_s_cn)) Q_s_cp = self.param.sigma_cp_prime * pybamm.inner( pybamm.grad(phi_s_cp), pybamm.grad(phi_s_cp)) elif self.cc_dimension == 2: # Inner not implemented in 2D -- have to call grad_squared directly Q_s_cn = self.param.sigma_cn_prime * pybamm.grad_squared( phi_s_cn) Q_s_cp = self.param.sigma_cp_prime * pybamm.grad_squared( phi_s_cp) return Q_s_cn, Q_s_cp
def _current_collector_heating(self, variables): """Returns the heat source terms in the 1D current collector""" phi_s_cn = variables["Negative current collector potential"] phi_s_cp = variables["Positive current collector potential"] Q_s_cn = self.param.sigma_cn_prime * pybamm.inner( pybamm.grad(phi_s_cn), pybamm.grad(phi_s_cn)) Q_s_cp = self.param.sigma_cp_prime * pybamm.inner( pybamm.grad(phi_s_cp), pybamm.grad(phi_s_cp)) return Q_s_cn, Q_s_cp
def test_jac_of_inner(self): a = pybamm.Scalar(1) b = pybamm.Scalar(2) y = pybamm.StateVector(slice(0, 1)) self.assertEqual(pybamm.inner(a, b).jac(y).evaluate(), 0) self.assertEqual(pybamm.inner(a, y).jac(y).evaluate(), 1) self.assertEqual(pybamm.inner(y, b).jac(y).evaluate(), 2) vec = pybamm.StateVector(slice(0, 2)) jac = pybamm.inner(a * vec, b * vec).jac(vec).evaluate(y=np.ones(2)).toarray() np.testing.assert_array_equal(jac, 4 * np.eye(2))
def test_simplify_inner(self): a1 = pybamm.Scalar(0) M1 = pybamm.Matrix(np.zeros((10, 10))) v1 = pybamm.Vector(np.ones(10)) a2 = pybamm.Scalar(1) M2 = pybamm.Matrix(np.ones((10, 10))) a3 = pybamm.Scalar(3) np.testing.assert_array_equal( pybamm.inner(a1, M2).simplify().evaluate().toarray(), M1.entries ) self.assertEqual(pybamm.inner(a1, a2).simplify().evaluate(), 0) np.testing.assert_array_equal( pybamm.inner(M2, a1).simplify().evaluate().toarray(), M1.entries ) self.assertEqual(pybamm.inner(a2, a1).simplify().evaluate(), 0) np.testing.assert_array_equal( pybamm.inner(M1, a3).simplify().evaluate().toarray(), M1.entries ) np.testing.assert_array_equal( pybamm.inner(v1, a3).simplify().evaluate(), 3 * v1.entries ) self.assertEqual(pybamm.inner(a2, a3).simplify().evaluate(), 3) self.assertEqual(pybamm.inner(a3, a2).simplify().evaluate(), 3) self.assertEqual(pybamm.inner(a3, a3).simplify().evaluate(), 9)
def test_diff(self): a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) y = np.array([5, 3]) # power self.assertEqual((a ** b).diff(b).evaluate(y=y), 5 ** 3 * np.log(5)) self.assertEqual((a ** b).diff(a).evaluate(y=y), 3 * 5 ** 2) self.assertEqual((a ** b).diff(a ** b).evaluate(), 1) self.assertEqual( (a ** a).diff(a).evaluate(y=y), 5 ** 5 * np.log(5) + 5 * 5 ** 4 ) self.assertEqual((a ** a).diff(b).evaluate(y=y), 0) # addition self.assertEqual((a + b).diff(a).evaluate(), 1) self.assertEqual((a + b).diff(b).evaluate(), 1) self.assertEqual((a + b).diff(a + b).evaluate(), 1) self.assertEqual((a + a).diff(a).evaluate(), 2) self.assertEqual((a + a).diff(b).evaluate(), 0) # subtraction self.assertEqual((a - b).diff(a).evaluate(), 1) self.assertEqual((a - b).diff(b).evaluate(), -1) self.assertEqual((a - b).diff(a - b).evaluate(), 1) self.assertEqual((a - a).diff(a).evaluate(), 0) self.assertEqual((a + a).diff(b).evaluate(), 0) # multiplication self.assertEqual((a * b).diff(a).evaluate(y=y), 3) self.assertEqual((a * b).diff(b).evaluate(y=y), 5) self.assertEqual((a * b).diff(a * b).evaluate(y=y), 1) self.assertEqual((a * a).diff(a).evaluate(y=y), 10) self.assertEqual((a * a).diff(b).evaluate(y=y), 0) # matrix multiplication (not implemented) matmul = a @ b with self.assertRaises(NotImplementedError): matmul.diff(a) # inner self.assertEqual(pybamm.inner(a, b).diff(a).evaluate(y=y), 3) self.assertEqual(pybamm.inner(a, b).diff(b).evaluate(y=y), 5) self.assertEqual(pybamm.inner(a, b).diff(pybamm.inner(a, b)).evaluate(y=y), 1) self.assertEqual(pybamm.inner(a, a).diff(a).evaluate(y=y), 10) self.assertEqual(pybamm.inner(a, a).diff(b).evaluate(y=y), 0) # division self.assertEqual((a / b).diff(a).evaluate(y=y), 1 / 3) self.assertEqual((a / b).diff(b).evaluate(y=y), -5 / 9) self.assertEqual((a / b).diff(a / b).evaluate(y=y), 1) self.assertEqual((a / a).diff(a).evaluate(y=y), 0) self.assertEqual((a / a).diff(b).evaluate(y=y), 0)
def _get_standard_coupled_variables(self, variables): param = self.param T = variables["Cell temperature"] T_n, _, T_p = T.orphans j_n = variables["Negative electrode interfacial current density"] j_p = variables["Positive electrode interfacial current density"] eta_r_n = variables["Negative electrode reaction overpotential"] eta_r_p = variables["Positive electrode reaction overpotential"] dUdT_n = variables["Negative electrode entropic change"] dUdT_p = variables["Positive electrode entropic change"] i_e = variables["Electrolyte current density"] phi_e = variables["Electrolyte potential"] i_s_n = variables["Negative electrode current density"] i_s_p = variables["Positive electrode current density"] phi_s_n = variables["Negative electrode potential"] phi_s_p = variables["Positive electrode potential"] # Ohmic heating in solid Q_ohm_s_cn, Q_ohm_s_cp = self._current_collector_heating(variables) Q_ohm_s_n = -pybamm.inner(i_s_n, pybamm.grad(phi_s_n)) Q_ohm_s_s = pybamm.FullBroadcast(0, ["separator"], "current collector") Q_ohm_s_p = -pybamm.inner(i_s_p, pybamm.grad(phi_s_p)) Q_ohm_s = pybamm.Concatenation(Q_ohm_s_n, Q_ohm_s_s, Q_ohm_s_p) # Ohmic heating in electrolyte # TODO: change full stefan-maxwell conductivity so that i_e is always # a Concatenation if isinstance(i_e, pybamm.Concatenation): # compute by domain if possible i_e_n, i_e_s, i_e_p = i_e.orphans phi_e_n, phi_e_s, phi_e_p = phi_e.orphans Q_ohm_e_n = -pybamm.inner(i_e_n, pybamm.grad(phi_e_n)) Q_ohm_e_s = -pybamm.inner(i_e_s, pybamm.grad(phi_e_s)) Q_ohm_e_p = -pybamm.inner(i_e_p, pybamm.grad(phi_e_p)) Q_ohm_e = pybamm.Concatenation(Q_ohm_e_n, Q_ohm_e_s, Q_ohm_e_p) else: Q_ohm_e = -pybamm.inner(i_e, pybamm.grad(phi_e)) # Total Ohmic heating Q_ohm = Q_ohm_s + Q_ohm_e # Irreversible electrochemical heating Q_rxn_n = j_n * eta_r_n Q_rxn_p = j_p * eta_r_p Q_rxn = pybamm.Concatenation(*[ Q_rxn_n, pybamm.FullBroadcast(0, ["separator"], "current collector"), Q_rxn_p, ]) # Reversible electrochemical heating Q_rev_n = j_n * (param.Theta**(-1) + T_n) * dUdT_n Q_rev_p = j_p * (param.Theta**(-1) + T_p) * dUdT_p Q_rev = pybamm.Concatenation(*[ Q_rev_n, pybamm.FullBroadcast(0, ["separator"], "current collector"), Q_rev_p, ]) # Total heating Q = Q_ohm + Q_rxn + Q_rev # Compute the X-average over the entire cell, including current collectors Q_ohm_av = self._x_average(Q_ohm, Q_ohm_s_cn, Q_ohm_s_cp) Q_rxn_av = self._x_average(Q_rxn, 0, 0) Q_rev_av = self._x_average(Q_rev, 0, 0) Q_av = self._x_average(Q, Q_ohm_s_cn, Q_ohm_s_cp) # Compute volume-averaged heat source terms Q_ohm_vol_av = self._yz_average(Q_ohm_av) Q_rxn_vol_av = self._yz_average(Q_rxn_av) Q_rev_vol_av = self._yz_average(Q_rev_av) Q_vol_av = self._yz_average(Q_av) # Dimensional scaling for heat source terms Q_scale = param.i_typ * param.potential_scale / param.L_x variables.update({ "Ohmic heating": Q_ohm, "Ohmic heating [W.m-3]": Q_ohm * Q_scale, "X-averaged Ohmic heating": Q_ohm_av, "X-averaged Ohmic heating [W.m-3]": Q_ohm_av * Q_scale, "Volume-averaged Ohmic heating": Q_ohm_vol_av, "Volume-averaged Ohmic heating [W.m-3]": Q_ohm_vol_av * Q_scale, "Irreversible electrochemical heating": Q_rxn, "Irreversible electrochemical heating [W.m-3]": Q_rxn * Q_scale, "X-averaged irreversible electrochemical heating": Q_rxn_av, "X-averaged irreversible electrochemical heating [W.m-3]": Q_rxn_av * Q_scale, "Volume-averaged irreversible electrochemical heating": Q_rxn_vol_av, "Volume-averaged irreversible electrochemical heating " + "[W.m-3]": Q_rxn_vol_av * Q_scale, "Reversible heating": Q_rev, "Reversible heating [W.m-3]": Q_rev * Q_scale, "X-averaged reversible heating": Q_rev_av, "X-averaged reversible heating [W.m-3]": Q_rev_av * Q_scale, "Volume-averaged reversible heating": Q_rev_vol_av, "Volume-averaged reversible heating [W.m-3]": Q_rev_vol_av * Q_scale, "Total heating": Q, "Total heating [W.m-3]": Q * Q_scale, "X-averaged total heating": Q_av, "X-averaged total heating [W.m-3]": Q_av * Q_scale, "Volume-averaged total heating": Q_vol_av, "Volume-averaged total heating [W.m-3]": Q_vol_av * Q_scale, }) return variables
def D(cc): c_dim = c_inf_dim * cc return D_dim(c_dim) / D_dim(c_inf_dim) # variables x = pybamm.SpatialVariable("x", domain="SEI layer", coord_sys="cartesian") c = pybamm.Variable("Solvent concentration", domain="SEI layer") L = pybamm.Variable("SEI thickness") # 3. State governing equations --------------------------------------------------------- R = k * pybamm.BoundaryValue(c, "left") # SEI reaction flux N = -(1 / L) * D(c) * pybamm.grad(c) # solvent flux dcdt = (V_hat * R) * pybamm.inner(x / L, pybamm.grad(c)) - ( 1 / L) * pybamm.div(N) # solvent concentration governing equation dLdt = V_hat * R # SEI thickness governing equation model.rhs = {c: dcdt, L: dLdt} # add to model # 4. State boundary conditions --------------------------------------------------------- D_left = pybamm.BoundaryValue( D(c), "left") # pybamm requires BoundaryValue(D(c)) and not D(BoundaryValue(c)) grad_c_left = L * R / D_left # left bc c_right = pybamm.Scalar(1) # right bc # add to model model.boundary_conditions = { c: {
def _get_standard_coupled_variables(self, variables): param = self.param T = variables["Cell temperature"] T_n, _, T_p = T.orphans j_n = variables["Negative electrode interfacial current density"] j_p = variables["Positive electrode interfacial current density"] eta_r_n = variables["Negative electrode reaction overpotential"] eta_r_p = variables["Positive electrode reaction overpotential"] dUdT_n = variables["Negative electrode entropic change"] dUdT_p = variables["Positive electrode entropic change"] i_e = variables["Electrolyte current density"] phi_e = variables["Electrolyte potential"] i_s_n = variables["Negative electrode current density"] i_s_p = variables["Positive electrode current density"] phi_s_n = variables["Negative electrode potential"] phi_s_p = variables["Positive electrode potential"] Q_ohm_s_cn, Q_ohm_s_cp = self._current_collector_heating(variables) Q_ohm_s_n = -pybamm.inner(i_s_n, pybamm.grad(phi_s_n)) Q_ohm_s_s = pybamm.FullBroadcast(0, ["separator"], "current collector") Q_ohm_s_p = -pybamm.inner(i_s_p, pybamm.grad(phi_s_p)) Q_ohm_s = pybamm.Concatenation(Q_ohm_s_n, Q_ohm_s_s, Q_ohm_s_p) Q_ohm_e = -pybamm.inner(i_e, pybamm.grad(phi_e)) Q_ohm = Q_ohm_s + Q_ohm_e Q_rxn_n = j_n * eta_r_n Q_rxn_p = j_p * eta_r_p Q_rxn = pybamm.Concatenation( *[ Q_rxn_n, pybamm.FullBroadcast(0, ["separator"], "current collector"), Q_rxn_p, ] ) Q_rev_n = j_n * (param.Theta ** (-1) + T_n) * dUdT_n Q_rev_p = j_p * (param.Theta ** (-1) + T_p) * dUdT_p Q_rev = pybamm.Concatenation( *[ Q_rev_n, pybamm.FullBroadcast(0, ["separator"], "current collector"), Q_rev_p, ] ) Q = Q_ohm + Q_rxn + Q_rev # Compute the X-average over the current collectors by default. # Note: the method 'self._x_average' is overwritten by models which do # not include current collector effects, so that the average is just taken # over the negative electrode, separator and positive electrode. Q_ohm_av = self._x_average(Q_ohm, Q_ohm_s_cn, Q_ohm_s_cp) Q_rxn_av = self._x_average(Q_rxn, 0, 0) Q_rev_av = self._x_average(Q_rev, 0, 0) Q_av = self._x_average(Q, Q_ohm_s_cn, Q_ohm_s_cp) Q_ohm_vol_av = self._yz_average(Q_ohm_av) Q_rxn_vol_av = self._yz_average(Q_rxn_av) Q_rev_vol_av = self._yz_average(Q_rev_av) Q_vol_av = self._yz_average(Q_av) variables.update( { "Ohmic heating": Q_ohm, "Ohmic heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_ohm / param.L_x, "X-averaged Ohmic heating": Q_ohm_av, "X-averaged Ohmic heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_ohm_av / param.L_x, "Volume-averaged Ohmic heating": Q_ohm_vol_av, "Volume-averaged Ohmic heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_ohm_vol_av / param.L_x, "Irreversible electrochemical heating": Q_rxn, "Irreversible electrochemical heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_rxn / param.L_x, "X-averaged electrochemical heating": Q_rxn_av, "X-averaged electrochemical heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_rxn_av / param.L_x, "Volume-averaged electrochemical heating": Q_rxn_vol_av, "Volume-averaged electrochemical heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_rxn_vol_av / param.L_x, "Reversible heating": Q_rev, "Reversible heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_rev / param.L_x, "X-averaged reversible heating": Q_rev_av, "X-averaged reversible heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_rev_av / param.L_x, "Volume-averaged reversible heating": Q_rev_vol_av, "Volume-averaged reversible heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_rev_vol_av / param.L_x, "Total heating": Q, "Total heating [A.V.m-3]": param.i_typ * param.potential_scale * Q / param.L_x, "X-averaged total heating": Q_av, "X-averaged total heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_av / param.L_x, "Volume-averaged total heating": Q_vol_av, "Volume-averaged total heating [A.V.m-3]": param.i_typ * param.potential_scale * Q_vol_av / param.L_x, } ) return variables
def _binary_new_copy(self, left, right): """ See :meth:`pybamm.BinaryOperator._binary_new_copy()`. """ return pybamm.inner(left, right)