def test_sign(self): b = pybamm.Scalar(-4) signb = pybamm.sign(b) self.assertEqual(signb.evaluate(), -1) A = diags(np.linspace(-1, 1, 5)) b = pybamm.Matrix(A) signb = pybamm.sign(b) np.testing.assert_array_equal(np.diag(signb.evaluate().toarray()), [-1, -1, 0, 1, 1])
def test_jac_of_sign(self): y = pybamm.StateVector(slice(0, 10)) func = pybamm.sign(y) * y jac = func.jac(y) y_test = np.linspace(-2, 2, 10) np.testing.assert_array_equal(np.diag(jac.evaluate(y=y_test)), np.sign(y_test))
def test_diff(self): a = pybamm.StateVector(slice(0, 1)) y = np.array([5]) # negation self.assertEqual((-a).diff(a).evaluate(y=y), -1) self.assertEqual((-a).diff(-a).evaluate(), 1) # absolute value self.assertEqual((a**3).diff(a).evaluate(y=y), 3 * 5**2) self.assertEqual((abs(a**3)).diff(a).evaluate(y=y), 3 * 5**2) self.assertEqual((a**3).diff(a).evaluate(y=-y), 3 * 5**2) self.assertEqual((abs(a**3)).diff(a).evaluate(y=-y), -3 * 5**2) # sign self.assertEqual((pybamm.sign(a)).diff(a).evaluate(y=y), 0) # floor self.assertEqual((pybamm.Floor(a)).diff(a).evaluate(y=y), 0) # ceil self.assertEqual((pybamm.Ceiling(a)).diff(a).evaluate(y=y), 0) # spatial operator (not implemented) spatial_a = pybamm.SpatialOperator("name", a) with self.assertRaises(NotImplementedError): spatial_a.diff(a)
def set_initial_conditions(self, variables): if (self.options["total interfacial current density as a state"] == "true" and "main" in self.reaction): param = self.param j_tot_var = variables[ "Total " + self.domain.lower() + " electrode interfacial current density variable"] current_at_0 = (pybamm.FunctionParameter( "Current function [A]", {"Time [s]": 0}) / param.I_typ * pybamm.sign(param.I_typ)) if self.domain == "Negative": j_tot_av_init = current_at_0 / param.l_n elif self.domain == "Positive": j_tot_av_init = -current_at_0 / param.l_p self.initial_conditions[j_tot_var] = j_tot_av_init
def set_voltage_variables(self): ocp_n = self.variables["Negative electrode open circuit potential"] ocp_p = self.variables["Positive electrode open circuit potential"] ocp_n_av = self.variables[ "X-averaged negative electrode open circuit potential"] ocp_p_av = self.variables[ "X-averaged positive electrode open circuit potential"] ocp_n_dim = self.variables[ "Negative electrode open circuit potential [V]"] ocp_p_dim = self.variables[ "Positive electrode open circuit potential [V]"] ocp_n_av_dim = self.variables[ "X-averaged negative electrode open circuit potential [V]"] ocp_p_av_dim = self.variables[ "X-averaged positive electrode open circuit potential [V]"] ocp_n_left = pybamm.boundary_value(ocp_n, "left") ocp_n_left_dim = pybamm.boundary_value(ocp_n_dim, "left") ocp_p_right = pybamm.boundary_value(ocp_p, "right") ocp_p_right_dim = pybamm.boundary_value(ocp_p_dim, "right") ocv_av = ocp_p_av - ocp_n_av ocv_av_dim = ocp_p_av_dim - ocp_n_av_dim ocv = ocp_p_right - ocp_n_left ocv_dim = ocp_p_right_dim - ocp_n_left_dim # overpotentials eta_r_n_av = self.variables[ "X-averaged negative electrode reaction overpotential"] eta_r_n_av_dim = self.variables[ "X-averaged negative electrode reaction overpotential [V]"] eta_r_p_av = self.variables[ "X-averaged positive electrode reaction overpotential"] eta_r_p_av_dim = self.variables[ "X-averaged positive electrode reaction overpotential [V]"] delta_phi_s_n_av = self.variables[ "X-averaged negative electrode ohmic losses"] delta_phi_s_n_av_dim = self.variables[ "X-averaged negative electrode ohmic losses [V]"] delta_phi_s_p_av = self.variables[ "X-averaged positive electrode ohmic losses"] delta_phi_s_p_av_dim = self.variables[ "X-averaged positive electrode ohmic losses [V]"] delta_phi_s_av = delta_phi_s_p_av - delta_phi_s_n_av delta_phi_s_av_dim = delta_phi_s_p_av_dim - delta_phi_s_n_av_dim eta_r_av = eta_r_p_av - eta_r_n_av eta_r_av_dim = eta_r_p_av_dim - eta_r_n_av_dim # SEI film overpotential eta_sei_n_av = self.variables[ "X-averaged negative electrode SEI film overpotential"] eta_sei_p_av = self.variables[ "X-averaged positive electrode SEI film overpotential"] eta_sei_n_av_dim = self.variables[ "X-averaged negative electrode SEI film overpotential [V]"] eta_sei_p_av_dim = self.variables[ "X-averaged positive electrode SEI film overpotential [V]"] eta_sei_av = eta_sei_n_av + eta_sei_p_av eta_sei_av_dim = eta_sei_n_av_dim + eta_sei_p_av_dim # TODO: add current collector losses to the voltage in 3D self.variables.update({ "X-averaged open circuit voltage": ocv_av, "Measured open circuit voltage": ocv, "X-averaged open circuit voltage [V]": ocv_av_dim, "Measured open circuit voltage [V]": ocv_dim, "X-averaged reaction overpotential": eta_r_av, "X-averaged reaction overpotential [V]": eta_r_av_dim, "X-averaged SEI film overpotential": eta_sei_av, "X-averaged SEI film overpotential [V]": eta_sei_av_dim, "X-averaged solid phase ohmic losses": delta_phi_s_av, "X-averaged solid phase ohmic losses [V]": delta_phi_s_av_dim, }) # Battery-wide variables V = self.variables["Terminal voltage"] V_dim = self.variables["Terminal voltage [V]"] eta_e_av_dim = self.variables[ "X-averaged electrolyte ohmic losses [V]"] eta_c_av_dim = self.variables[ "X-averaged concentration overpotential [V]"] num_cells = pybamm.Parameter( "Number of cells connected in series to make a battery") self.variables.update({ "X-averaged battery open circuit voltage [V]": ocv_av_dim * num_cells, "Measured battery open circuit voltage [V]": ocv_dim * num_cells, "X-averaged battery reaction overpotential [V]": eta_r_av_dim * num_cells, "X-averaged battery solid phase ohmic losses [V]": delta_phi_s_av_dim * num_cells, "X-averaged battery electrolyte ohmic losses [V]": eta_e_av_dim * num_cells, "X-averaged battery concentration overpotential [V]": eta_c_av_dim * num_cells, "Battery voltage [V]": V_dim * num_cells, }) # Variables for calculating the equivalent circuit model (ECM) resistance # Need to compare OCV to initial value to capture this as an overpotential ocv_init = self.param.U_p( self.param.c_p_init(1), self.param.T_init) - self.param.U_n( self.param.c_n_init(0), self.param.T_init) ocv_init_dim = (self.param.U_p_ref - self.param.U_n_ref + self.param.potential_scale * ocv_init) eta_ocv = ocv - ocv_init eta_ocv_dim = ocv_dim - ocv_init_dim # Current collector current density for working out euiqvalent resistance # based on Ohm's Law i_cc = self.variables["Current collector current density"] i_cc_dim = self.variables["Current collector current density [A.m-2]"] # ECM overvoltage is OCV minus terminal voltage v_ecm = ocv - V v_ecm_dim = ocv_dim - V_dim # Current collector area for turning resistivity into resistance A_cc = self.param.A_cc # Hack to avoid division by zero if i_cc is exactly zero # If i_cc is zero, i_cc_not_zero becomes 1. But multiplying by sign(i_cc) makes # the local resistance 'zero' (really, it's not defined when i_cc is zero) i_cc_not_zero = ((i_cc > 0) + (i_cc < 0)) * i_cc + (i_cc >= 0) * (i_cc <= 0) i_cc_dim_not_zero = ( (i_cc_dim > 0) + (i_cc_dim < 0)) * i_cc_dim + (i_cc_dim >= 0) * (i_cc_dim <= 0) self.variables.update({ "Change in measured open circuit voltage": eta_ocv, "Change in measured open circuit voltage [V]": eta_ocv_dim, "Local ECM resistance": pybamm.sign(i_cc) * v_ecm / (i_cc_not_zero * A_cc), "Local ECM resistance [Ohm]": pybamm.sign(i_cc) * v_ecm_dim / (i_cc_dim_not_zero * A_cc), }) # Cut-off voltage self.events.append( pybamm.Event( "Minimum voltage", V - self.param.voltage_low_cut, pybamm.EventType.TERMINATION, )) self.events.append( pybamm.Event( "Maximum voltage", V - self.param.voltage_high_cut, pybamm.EventType.TERMINATION, )) # Cut-off open-circuit voltage (for event switch with casadi 'fast with events' # mode) # A tolerance of 1 is sufficiently small since the dimensionless voltage is # scaled with the thermal voltage (0.025V) and hence has a range of around 60 tol = 1 self.events.append( pybamm.Event( "Minimum voltage switch", V - (self.param.voltage_low_cut - tol), pybamm.EventType.SWITCH, )) self.events.append( pybamm.Event( "Maximum voltage switch", V - (self.param.voltage_high_cut + tol), pybamm.EventType.SWITCH, )) # Power I_dim = self.variables["Current [A]"] self.variables.update({"Terminal power [W]": I_dim * V_dim})