def test_to_equation(self): a = pybamm.Symbol("a", domain="negative particle") b = pybamm.Symbol("b", domain="current collector") c = pybamm.Symbol("c", domain="test") # Test print_name pybamm.Floor.print_name = "test" self.assertEqual(pybamm.Floor(-2.5).to_equation(), sympy.symbols("test")) # Test Negate self.assertEqual(pybamm.Negate(4).to_equation(), -4.0) # Test AbsoluteValue self.assertEqual(pybamm.AbsoluteValue(-4).to_equation(), 4.0) # Test Gradient self.assertEqual(pybamm.Gradient(a).to_equation(), sympy_Gradient("a")) # Test Divergence self.assertEqual( pybamm.Divergence(pybamm.Gradient(a)).to_equation(), sympy_Divergence(sympy_Gradient(a)), ) # Test BoundaryValue self.assertEqual( pybamm.BoundaryValue(a, "right").to_equation(), sympy.symbols("a^{surf}") ) self.assertEqual( pybamm.BoundaryValue(b, "positive tab").to_equation(), sympy.symbols(str(b)) ) self.assertEqual( pybamm.BoundaryValue(c, "left").to_equation(), sympy.symbols("c^{left}") )
def set_boundary_conditions(self, variables): T_av = variables["X-averaged cell temperature"] T_amb = variables["Ambient temperature"] # Subtract the edge cooling from the tab portion so as to not double count # Note: tab cooling is also only applied on the current collector hence # the (l_cn / l) and (l_cp / l) prefactors. # We also still have edge cooling on the region: x in (0, 1) h_tab_n_corrected = ((self.param.l_cn / self.param.l) * (self.param.h_tab_n - self.param.h_edge) / self.param.delta) h_tab_p_corrected = ((self.param.l_cp / self.param.l) * (self.param.h_tab_p - self.param.h_edge) / self.param.delta) T_av_n = pybamm.BoundaryValue(T_av, "negative tab") T_av_p = pybamm.BoundaryValue(T_av, "positive tab") self.boundary_conditions = { T_av: { "negative tab": (-h_tab_n_corrected * (T_av_n - T_amb), "Neumann"), "positive tab": (-h_tab_p_corrected * (T_av_p - T_amb), "Neumann"), } }
def get_fundamental_variables(self): T = pybamm.standard_variables.T T_cn = pybamm.BoundaryValue(T, "left") T_cp = pybamm.BoundaryValue(T, "right") variables = self._get_standard_fundamental_variables(T, T_cn, T_cp) return variables
def errors(pts, function, method_options, bcs=None): domain = "test" x = pybamm.SpatialVariable("x", domain=domain) geometry = { domain: {"primary": {x: {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}}} } submesh_types = {domain: pybamm.MeshGenerator(pybamm.Uniform1DSubMesh)} var_pts = {x: pts} mesh = pybamm.Mesh(geometry, submesh_types, var_pts) spatial_methods = {"test": pybamm.FiniteVolume(method_options)} disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var", domain="test") left_extrap = pybamm.BoundaryValue(var, "left") right_extrap = pybamm.BoundaryValue(var, "right") if bcs: model = pybamm.BaseBatteryModel() bc_dict = {var: bcs} model.boundary_conditions = bc_dict disc.bcs = disc.process_boundary_conditions(model) submesh = mesh["test"] y, l_true, r_true = function(submesh[0].nodes) disc.set_variable_slices([var]) left_extrap_processed = disc.process_symbol(left_extrap) right_extrap_processed = disc.process_symbol(right_extrap) l_error = np.abs(l_true - left_extrap_processed.evaluate(None, y)) r_error = np.abs(r_true - right_extrap_processed.evaluate(None, y)) return l_error, r_error
def get_fundamental_variables(self): T_n = pybamm.standard_variables.T_n T_s = pybamm.standard_variables.T_s T_p = pybamm.standard_variables.T_p T_cn = pybamm.BoundaryValue(T_n, "left") T_cp = pybamm.BoundaryValue(T_p, "right") T = pybamm.Concatenation(T_n, T_s, T_p) T_x_av = self._x_average(T, T_cn, T_cp) T_vol_av = self._yz_average(T_x_av) variables = self._get_standard_fundamental_variables( T_cn, T_n, T_s, T_p, T_cp, T_x_av, T_vol_av) return variables
def set_boundary_conditions(self, variables): if self.domain == "Separator": return None param = self.param conductivity, sigma_eff = self._get_conductivities(variables) i_boundary_cc = variables["Current collector current density"] c_e = variables[self.domain + " electrolyte concentration"] delta_phi = variables[self.domain + " electrode surface potential difference"] if self.domain == "Negative": c_e_flux = pybamm.BoundaryGradient(c_e, "right") flux_left = -i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "left") flux_right = ( (i_boundary_cc / pybamm.BoundaryValue(conductivity, "right")) - pybamm.BoundaryValue(param.chi(c_e) / c_e, "right") * c_e_flux - i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "right") ) lbc = (flux_left, "Neumann") rbc = (flux_right, "Neumann") lbc_c_e = (pybamm.Scalar(0), "Neumann") rbc_c_e = (c_e_flux, "Neumann") elif self.domain == "Positive": c_e_flux = pybamm.BoundaryGradient(c_e, "left") flux_left = ( (i_boundary_cc / pybamm.BoundaryValue(conductivity, "left")) - pybamm.BoundaryValue(param.chi(c_e) / c_e, "left") * c_e_flux - i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "left") ) flux_right = -i_boundary_cc * pybamm.BoundaryValue(1 / sigma_eff, "right") lbc = (flux_left, "Neumann") rbc = (flux_right, "Neumann") lbc_c_e = (c_e_flux, "Neumann") rbc_c_e = (pybamm.Scalar(0), "Neumann") # TODO: check if we still need the boundary conditions for c_e, once we have # internal boundary conditions self.boundary_conditions = { delta_phi: {"left": lbc, "right": rbc}, c_e: {"left": lbc_c_e, "right": rbc_c_e}, } if self.domain == "Negative": phi_e = variables["Electrolyte potential"] self.boundary_conditions.update( { phi_e: { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } } )
def test_neg_pos(self): mesh = get_2p1d_mesh_for_testing() spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var", domain="current collector") disc.set_variable_slices([var]) extrap_neg = pybamm.BoundaryValue(var, "negative tab") extrap_pos = pybamm.BoundaryValue(var, "positive tab") extrap_neg_disc = disc.process_symbol(extrap_neg) extrap_pos_disc = disc.process_symbol(extrap_pos) # check constant returns constant at tab constant_y = np.ones(mesh["current collector"][0].npts)[:, np.newaxis] np.testing.assert_array_almost_equal( extrap_neg_disc.evaluate(None, constant_y), 1) np.testing.assert_array_almost_equal( extrap_pos_disc.evaluate(None, constant_y), 1)
def test_symbol_new_copy(self): a = pybamm.Parameter("a") b = pybamm.Parameter("b") v_n = pybamm.Variable("v", "negative electrode") x_n = pybamm.standard_spatial_vars.x_n v_s = pybamm.Variable("v", "separator") vec = pybamm.Vector([1, 2, 3, 4, 5]) mat = pybamm.Matrix([[1, 2], [3, 4]]) mesh = get_mesh_for_testing() for symbol in [ a + b, a - b, a * b, a / b, a**b, -a, abs(a), pybamm.Function(np.sin, a), pybamm.FunctionParameter("function", {"a": a}), pybamm.grad(v_n), pybamm.div(pybamm.grad(v_n)), pybamm.upwind(v_n), pybamm.IndefiniteIntegral(v_n, x_n), pybamm.BackwardIndefiniteIntegral(v_n, x_n), pybamm.BoundaryValue(v_n, "right"), pybamm.BoundaryGradient(v_n, "right"), pybamm.PrimaryBroadcast(a, "domain"), pybamm.SecondaryBroadcast(v_n, "current collector"), pybamm.FullBroadcast(a, "domain", {"secondary": "other domain"}), pybamm.concatenation(v_n, v_s), pybamm.NumpyConcatenation(a, b, v_s), pybamm.DomainConcatenation([v_n, v_s], mesh), pybamm.Parameter("param"), pybamm.InputParameter("param"), pybamm.StateVector(slice(0, 56)), pybamm.Matrix(np.ones((50, 40))), pybamm.SpatialVariable("x", ["negative electrode"]), pybamm.t, pybamm.Index(vec, 1), pybamm.NotConstant(a), pybamm.ExternalVariable( "external variable", 20, domain="test", auxiliary_domains={"secondary": "test2"}, ), pybamm.minimum(a, b), pybamm.maximum(a, b), pybamm.SparseStack(mat, mat), ]: self.assertEqual(symbol.id, symbol.new_copy().id)
def test_symbol_new_copy(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) v_n = pybamm.Variable("v", "negative electrode") x_n = pybamm.standard_spatial_vars.x_n v_s = pybamm.Variable("v", "separator") vec = pybamm.Vector(np.array([1, 2, 3, 4, 5])) mesh = get_mesh_for_testing() for symbol in [ a + b, a - b, a * b, a / b, a**b, -a, abs(a), pybamm.Function(np.sin, a), pybamm.FunctionParameter("function", {"a": a}), pybamm.grad(v_n), pybamm.div(pybamm.grad(v_n)), pybamm.Integral(a, pybamm.t), pybamm.IndefiniteIntegral(v_n, x_n), pybamm.BackwardIndefiniteIntegral(v_n, x_n), pybamm.BoundaryValue(v_n, "right"), pybamm.BoundaryGradient(v_n, "right"), pybamm.PrimaryBroadcast(a, "domain"), pybamm.SecondaryBroadcast(v_n, "current collector"), pybamm.FullBroadcast(a, "domain", {"secondary": "other domain"}), pybamm.Concatenation(v_n, v_s), pybamm.NumpyConcatenation(a, b, v_s), pybamm.DomainConcatenation([v_n, v_s], mesh), pybamm.Parameter("param"), pybamm.InputParameter("param"), pybamm.StateVector(slice(0, 56)), pybamm.Matrix(np.ones((50, 40))), pybamm.SpatialVariable("x", ["negative electrode"]), pybamm.t, pybamm.Index(vec, 1), ]: self.assertEqual(symbol.id, symbol.new_copy().id)
def __init__(self, n=100, max_x=10, param=None): # Set fixed parameters here if param is None: param = pybamm.ParameterValues({ "Far-field concentration of A [mol cm-3]": 1e-6, "Diffusion Constant [cm2 s-1]": 7.2e-6, "Faraday Constant [C mol-1]": 96485.3328959, "Gas constant [J K-1 mol-1]": 8.314459848, "Electrode Area [cm2]": 0.07, "Temperature [K]": 297.0, "Voltage frequency [rad s-1]": 9.0152, "Voltage start [V]": 0.5, "Voltage reverse [V]": -0.1, "Voltage amplitude [V]": 0.08, "Scan Rate [V s-1]": 0.08941, }) # Create dimensional fixed parameters c_inf = pybamm.Parameter("Far-field concentration of A [mol cm-3]") D = pybamm.Parameter("Diffusion Constant [cm2 s-1]") F = pybamm.Parameter("Faraday Constant [C mol-1]") R = pybamm.Parameter("Gas constant [J K-1 mol-1]") S = pybamm.Parameter("Electrode Area [cm2]") T = pybamm.Parameter("Temperature [K]") E_start_d = pybamm.Parameter("Voltage start [V]") E_reverse_d = pybamm.Parameter("Voltage reverse [V]") deltaE_d = pybamm.Parameter("Voltage amplitude [V]") v = pybamm.Parameter("Scan Rate [V s-1]") # Create dimensional input parameters E0 = pybamm.InputParameter("Reversible Potential [non-dim]") k0 = pybamm.InputParameter("Reaction Rate [non-dim]") alpha = pybamm.InputParameter("Symmetry factor [non-dim]") Cdl = pybamm.InputParameter("Capacitance [non-dim]") Ru = pybamm.InputParameter("Uncompensated Resistance [non-dim]") omega_d = pybamm.InputParameter("Voltage frequency [rad s-1]") E0_d = pybamm.InputParameter("Reversible Potential [V]") k0_d = pybamm.InputParameter("Reaction Rate [s-1]") alpha = pybamm.InputParameter("Symmetry factor [non-dim]") Cdl_d = pybamm.InputParameter("Capacitance [F]") Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]") # Create scaling factors for non-dimensionalisation E_0 = R * T / F T_0 = E_0 / v L_0 = pybamm.sqrt(D * T_0) I_0 = D * F * S * c_inf / L_0 # Non-dimensionalise parameters E0 = E0_d / E_0 k0 = k0_d * L_0 / D Cdl = Cdl_d * S * E_0 / (I_0 * T_0) Ru = Ru_d * I_0 / E_0 omega = 2 * np.pi * omega_d * T_0 E_start = E_start_d / E_0 E_reverse = E_reverse_d / E_0 t_reverse = E_start - E_reverse deltaE = deltaE_d / E_0 # Input voltage protocol Edc_forward = -pybamm.t Edc_backwards = pybamm.t - 2 * t_reverse Eapp = E_start + \ (pybamm.t <= t_reverse) * Edc_forward + \ (pybamm.t > t_reverse) * Edc_backwards + \ deltaE * pybamm.sin(omega * pybamm.t) # create PyBaMM model object model = pybamm.BaseModel() # Create state variables for model theta = pybamm.Variable("ratio_A", domain="solution") i = pybamm.Variable("Current") # Effective potential Eeff = Eapp - i * Ru # Faradaic current i_f = pybamm.BoundaryGradient(theta, "left") # ODE equations model.rhs = { theta: pybamm.div(pybamm.grad(theta)), i: 1 / (Cdl * Ru) * (-i_f + Cdl * Eapp.diff(pybamm.t) - i), } # algebraic equations (none) model.algebraic = {} # Butler-volmer boundary condition at electrode theta_at_electrode = pybamm.BoundaryValue(theta, "left") butler_volmer = k0 * (theta_at_electrode * pybamm.exp(-alpha * (Eeff - E0)) - (1 - theta_at_electrode) * pybamm.exp( (1 - alpha) * (Eeff - E0))) # Boundary and initial conditions model.boundary_conditions = { theta: { "right": (pybamm.Scalar(1), "Dirichlet"), "left": (butler_volmer, "Neumann"), } } model.initial_conditions = { theta: pybamm.Scalar(1), i: Cdl * (-1.0 + deltaE * omega), } # set spatial variables and solution domain geometry x = pybamm.SpatialVariable('x', domain="solution") default_geometry = pybamm.Geometry({ "solution": { x: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(max_x) } } }) default_var_pts = {x: n} # Using Finite Volume discretisation on an expanding 1D grid for solution default_submesh_types = { "solution": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'}) } default_spatial_methods = {"solution": pybamm.FiniteVolume()} # model variables model.variables = { "Current [non-dim]": i, } #-------------------------------- # Set model parameters param.process_model(model) geometry = default_geometry param.process_geometry(geometry) # Create mesh and discretise model mesh = pybamm.Mesh(geometry, default_submesh_types, default_var_pts) disc = pybamm.Discretisation(mesh, default_spatial_methods) disc.process_model(model) # Create solver solver = pybamm.CasadiSolver( mode="fast", rtol=1e-9, atol=1e-9, extra_options_setup={'print_stats': False}) #model.convert_to_format = 'jax' #solver = pybamm.JaxSolver(method='BDF') #model.convert_to_format = 'python' #solver = pybamm.ScipySolver(method='BDF') # Store discretised model and solver self._model = model self._solver = solver self._fast_solver = None self._omega_d = param["Voltage frequency [rad s-1]"] self._I_0 = param.process_symbol(I_0).evaluate() self._T_0 = param.process_symbol(T_0).evaluate() self._E_0 = param.process_symbol(E_0).evaluate() self._L_0 = param.process_symbol(L_0).evaluate() self._S = param.process_symbol(S).evaluate() self._D = param.process_symbol(D).evaluate() self._default_var_points = default_var_pts
def test_process_symbol_base(self): # create discretisation mesh = get_mesh_for_testing() spatial_methods = { "macroscale": pybamm.SpatialMethod(), "negative particle": pybamm.SpatialMethod(), "positive particle": pybamm.SpatialMethod(), "current collector": pybamm.SpatialMethod(), } disc = pybamm.Discretisation(mesh, spatial_methods) # variable var = pybamm.Variable("var") var_vec = pybamm.Variable("var vec", domain=["negative electrode"]) disc.y_slices = {var.id: [slice(53)], var_vec.id: [slice(53, 93)]} var_disc = disc.process_symbol(var) self.assertIsInstance(var_disc, pybamm.StateVector) self.assertEqual(var_disc.y_slices[0], disc.y_slices[var.id][0]) # variable dot var_dot = pybamm.VariableDot("var'") var_dot_disc = disc.process_symbol(var_dot) self.assertIsInstance(var_dot_disc, pybamm.StateVectorDot) self.assertEqual(var_dot_disc.y_slices[0], disc.y_slices[var.id][0]) # scalar scal = pybamm.Scalar(5) scal_disc = disc.process_symbol(scal) self.assertIsInstance(scal_disc, pybamm.Scalar) self.assertEqual(scal_disc.value, scal.value) # vector vec = pybamm.Vector(np.array([1, 2, 3, 4])) vec_disc = disc.process_symbol(vec) self.assertIsInstance(vec_disc, pybamm.Vector) np.testing.assert_array_equal(vec_disc.entries, vec.entries) # matrix mat = pybamm.Matrix(np.array([[1, 2, 3, 4], [5, 6, 7, 8]])) mat_disc = disc.process_symbol(mat) self.assertIsInstance(mat_disc, pybamm.Matrix) np.testing.assert_array_equal(mat_disc.entries, mat.entries) # binary operator bin = var + scal bin_disc = disc.process_symbol(bin) self.assertIsInstance(bin_disc, pybamm.Addition) self.assertIsInstance(bin_disc.children[0], pybamm.StateVector) self.assertIsInstance(bin_disc.children[1], pybamm.Scalar) bin2 = scal + var bin2_disc = disc.process_symbol(bin2) self.assertIsInstance(bin2_disc, pybamm.Addition) self.assertIsInstance(bin2_disc.children[0], pybamm.Scalar) self.assertIsInstance(bin2_disc.children[1], pybamm.StateVector) # non-spatial unary operator un1 = -var un1_disc = disc.process_symbol(un1) self.assertIsInstance(un1_disc, pybamm.Negate) self.assertIsInstance(un1_disc.children[0], pybamm.StateVector) un2 = abs(var) un2_disc = disc.process_symbol(un2) self.assertIsInstance(un2_disc, pybamm.AbsoluteValue) self.assertIsInstance(un2_disc.children[0], pybamm.StateVector) # function of one variable def myfun(x): return np.exp(x) func = pybamm.Function(myfun, var) func_disc = disc.process_symbol(func) self.assertIsInstance(func_disc, pybamm.Function) self.assertIsInstance(func_disc.children[0], pybamm.StateVector) func = pybamm.Function(myfun, scal) func_disc = disc.process_symbol(func) self.assertIsInstance(func_disc, pybamm.Function) self.assertIsInstance(func_disc.children[0], pybamm.Scalar) # function of multiple variables def myfun(x, y): return np.exp(x) * y func = pybamm.Function(myfun, var, scal) func_disc = disc.process_symbol(func) self.assertIsInstance(func_disc, pybamm.Function) self.assertIsInstance(func_disc.children[0], pybamm.StateVector) self.assertIsInstance(func_disc.children[1], pybamm.Scalar) # boundary value bv_left = pybamm.BoundaryValue(var_vec, "left") bv_left_disc = disc.process_symbol(bv_left) self.assertIsInstance(bv_left_disc, pybamm.MatrixMultiplication) self.assertIsInstance(bv_left_disc.left, pybamm.Matrix) self.assertIsInstance(bv_left_disc.right, pybamm.StateVector) bv_right = pybamm.BoundaryValue(var_vec, "left") bv_right_disc = disc.process_symbol(bv_right) self.assertIsInstance(bv_right_disc, pybamm.MatrixMultiplication) self.assertIsInstance(bv_right_disc.left, pybamm.Matrix) self.assertIsInstance(bv_right_disc.right, pybamm.StateVector) # not implemented sym = pybamm.Symbol("sym") with self.assertRaises(NotImplementedError): disc.process_symbol(sym)
def test_extrapolate_2d_models(self): # create discretisation mesh = get_p2d_mesh_for_testing() method_options = { "extrapolation": { "order": "linear", "use bcs": False } } spatial_methods = { "macroscale": pybamm.FiniteVolume(method_options), "negative particle": pybamm.FiniteVolume(method_options), "positive particle": pybamm.FiniteVolume(method_options), "current collector": pybamm.FiniteVolume(method_options), } disc = pybamm.Discretisation(mesh, spatial_methods) # Microscale var = pybamm.Variable("var", domain="negative particle") extrap_right = pybamm.BoundaryValue(var, "right") disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) self.assertEqual(extrap_right_disc.domain, []) # domain for boundary values must now be explicitly set extrap_right.domain = ["negative electrode"] disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) self.assertEqual(extrap_right_disc.domain, ["negative electrode"]) # evaluate y_macro = mesh["negative electrode"][0].nodes y_micro = mesh["negative particle"][0].nodes y = np.outer(y_macro, y_micro).reshape(-1, 1) # extrapolate to r=1 --> should evaluate to y_macro np.testing.assert_array_almost_equal( extrap_right_disc.evaluate(y=y)[:, 0], y_macro) var = pybamm.Variable("var", domain="positive particle") extrap_right = pybamm.BoundaryValue(var, "right") disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) self.assertEqual(extrap_right_disc.domain, []) # domain for boundary values must now be explicitly set extrap_right.domain = ["positive electrode"] disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) self.assertEqual(extrap_right_disc.domain, ["positive electrode"]) # 2d macroscale mesh = get_1p1d_mesh_for_testing() disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var", domain="negative electrode") extrap_right = pybamm.BoundaryValue(var, "right") disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) self.assertEqual(extrap_right_disc.domain, []) # test extrapolate to "negative tab" gives same as "left" and # "positive tab" gives same "right" (see get_mesh_for_testing) var = pybamm.Variable("var", domain="current collector") disc.set_variable_slices([var]) submesh = mesh["current collector"] constant_y = np.ones_like(submesh[0].nodes[:, np.newaxis]) extrap_neg = pybamm.BoundaryValue(var, "negative tab") extrap_neg_disc = disc.process_symbol(extrap_neg) extrap_left = pybamm.BoundaryValue(var, "left") extrap_left_disc = disc.process_symbol(extrap_left) np.testing.assert_array_equal( extrap_neg_disc.evaluate(None, constant_y), extrap_left_disc.evaluate(None, constant_y), ) extrap_pos = pybamm.BoundaryValue(var, "positive tab") extrap_pos_disc = disc.process_symbol(extrap_pos) extrap_right = pybamm.BoundaryValue(var, "right") extrap_right_disc = disc.process_symbol(extrap_right) np.testing.assert_array_equal( extrap_pos_disc.evaluate(None, constant_y), extrap_right_disc.evaluate(None, constant_y), )
def test_quadratic_extrapolate_left_right(self): # create discretisation mesh = get_mesh_for_testing() method_options = { "extrapolation": { "order": "quadratic", "use bcs": False } } spatial_methods = { "macroscale": pybamm.FiniteVolume(method_options), "negative particle": pybamm.FiniteVolume(method_options), "current collector": pybamm.ZeroDimensionalMethod(method_options), } disc = pybamm.Discretisation(mesh, spatial_methods) whole_cell = ["negative electrode", "separator", "positive electrode"] macro_submesh = mesh.combine_submeshes(*whole_cell) micro_submesh = mesh["negative particle"] # Macroscale # create variable var = pybamm.Variable("var", domain=whole_cell) # boundary value should work with something more complicated than a variable extrap_left = pybamm.BoundaryValue(2 * var, "left") extrap_right = pybamm.BoundaryValue(4 - var, "right") disc.set_variable_slices([var]) extrap_left_disc = disc.process_symbol(extrap_left) extrap_right_disc = disc.process_symbol(extrap_right) # check constant extrapolates to constant constant_y = np.ones_like(macro_submesh[0].nodes[:, np.newaxis]) np.testing.assert_array_almost_equal( extrap_left_disc.evaluate(None, constant_y), 2.0) np.testing.assert_array_almost_equal( extrap_right_disc.evaluate(None, constant_y), 3.0) # check linear variable extrapolates correctly linear_y = macro_submesh[0].nodes np.testing.assert_array_almost_equal( extrap_left_disc.evaluate(None, linear_y), 0) np.testing.assert_array_almost_equal( extrap_right_disc.evaluate(None, linear_y), 3) # Fluxes extrap_flux_left = pybamm.BoundaryGradient(2 * var, "left") extrap_flux_right = pybamm.BoundaryGradient(1 - var, "right") extrap_flux_left_disc = disc.process_symbol(extrap_flux_left) extrap_flux_right_disc = disc.process_symbol(extrap_flux_right) # check constant extrapolates to constant np.testing.assert_array_almost_equal( extrap_flux_left_disc.evaluate(None, constant_y), 0) self.assertEqual(extrap_flux_right_disc.evaluate(None, constant_y), 0) # check linear variable extrapolates correctly np.testing.assert_array_almost_equal( extrap_flux_left_disc.evaluate(None, linear_y), 2) np.testing.assert_array_almost_equal( extrap_flux_right_disc.evaluate(None, linear_y), -1) # Microscale # create variable var = pybamm.Variable("var", domain="negative particle") surf_eqn = pybamm.surf(var) disc.set_variable_slices([var]) surf_eqn_disc = disc.process_symbol(surf_eqn) # check constant extrapolates to constant constant_y = np.ones_like(micro_submesh[0].nodes[:, np.newaxis]) np.testing.assert_array_almost_equal( surf_eqn_disc.evaluate(None, constant_y), 1) # check linear variable extrapolates correctly linear_y = micro_submesh[0].nodes y_surf = micro_submesh[0].edges[-1] np.testing.assert_array_almost_equal( surf_eqn_disc.evaluate(None, linear_y), y_surf)
k = k_dim * L_0_dim / D_dim(c_inf_dim) V_hat = V_hat_dim * c_inf_dim 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
def __init__(self, param=None): # Set fixed parameters here if param is None: param = pybamm.ParameterValues({ "Far-field concentration of S(soln) [mol cm-3]": 1e-6, "Diffusion Constant [cm2 s-1]": 7.2e-6, "Faraday Constant [C mol-1]": 96485.3328959, "Gas constant [J K-1 mol-1]": 8.314459848, "Electrode Area [cm2]": 0.07, "Temperature [K]": 273.0, "Voltage frequency [rad s-1]": 9.0152, "Voltage start [V]": 0.4, "Voltage reverse [V]": -0.4, "Voltage amplitude [V]": 0.0, # 0.05, "Scan Rate [V s-1]": 0.05, "Electrode Coverage [mol cm2]": 6.5e-12, }) # Create dimensional fixed parameters D = pybamm.Parameter("Diffusion Constant [cm2 s-1]") F = pybamm.Parameter("Faraday Constant [C mol-1]") R = pybamm.Parameter("Gas constant [J K-1 mol-1]") a = pybamm.Parameter("Electrode Area [cm2]") T = pybamm.Parameter("Temperature [K]") omega_d = pybamm.Parameter("Voltage frequency [rad s-1]") E_start_d = pybamm.Parameter("Voltage start [V]") E_reverse_d = pybamm.Parameter("Voltage reverse [V]") deltaE_d = pybamm.Parameter("Voltage amplitude [V]") v = pybamm.Parameter("Scan Rate [V s-1]") Gamma = pybamm.Parameter("Electrode Coverage [mol cm2]") # Create dimensional input parameters E0_d = pybamm.InputParameter("Reversible Potential [V]") k0_d = pybamm.InputParameter("Redox Rate [s-1]") kcat_d = pybamm.InputParameter("Catalytic Rate [cm3 mol-l s-1]") alpha = pybamm.InputParameter("Symmetry factor [non-dim]") Cdl_d = pybamm.InputParameter("Capacitance [F]") Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]") # Create scaling factors for non-dimensionalisation E_0 = R * T / F T_0 = E_0 / v I_0 = F * a * Gamma / T L_0 = pybamm.sqrt(D * T_0) # Non-dimensionalise parameters E0 = E0_d / E_0 k0 = k0_d * T_0 kcat = kcat_d * Gamma * L_0 / D Cdl = Cdl_d * a * E_0 / (I_0 * T_0) Ru = Ru_d * I_0 / E_0 omega = 2 * np.pi * omega_d * T_0 E_start = E_start_d / E_0 E_reverse = E_reverse_d / E_0 t_reverse = E_start - E_reverse deltaE = deltaE_d / E_0 # Input voltage protocol Edc_forward = -pybamm.t Edc_backwards = pybamm.t - 2 * t_reverse Eapp = E_start + \ (pybamm.t <= t_reverse) * Edc_forward + \ (pybamm.t > t_reverse) * Edc_backwards + \ deltaE * pybamm.sin(omega * pybamm.t) # create PyBaMM model object model = pybamm.BaseModel() # Create state variables for model theta = pybamm.Variable("O(surf) [non-dim]") c = pybamm.Variable("S(soln) [non-dim]", domain="solution") i = pybamm.Variable("Current [non-dim]") # Effective potential Eeff = Eapp - i * Ru # Faridaic current (Butler Volmer) i_f = k0 * ((1 - theta) * pybamm.exp( (1 - alpha) * (Eeff - E0)) - theta * pybamm.exp(-alpha * (Eeff - E0))) c_at_electrode = pybamm.BoundaryValue(c, "left") # Catalytic current i_cat = kcat * c_at_electrode * (1 - theta) # ODE equations model.rhs = { theta: i_f + i_cat, i: 1 / (Cdl * Ru) * (i_f + Cdl * Eapp.diff(pybamm.t) - i - i_cat), c: pybamm.div(pybamm.grad(c)), } # algebraic equations (none) model.algebraic = {} # Boundary and initial conditions model.boundary_conditions = { c: { "right": (pybamm.Scalar(1), "Dirichlet"), "left": (i_cat, "Neumann"), } } model.initial_conditions = { theta: pybamm.Scalar(1), i: Cdl * Eapp.diff(pybamm.t), c: pybamm.Scalar(1), } # set spatial variables and solution domain geometry x = pybamm.SpatialVariable('x', domain="solution") model.geometry = pybamm.Geometry({ "solution": { x: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(20) } } }) model.var_pts = {x: 100} # Using Finite Volume discretisation on an expanding 1D grid model.submesh_types = { "solution": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'}) } model.spatial_methods = {"solution": pybamm.FiniteVolume()} # model variables model.variables = { "Current [non-dim]": i, "O(surf) [non-dim]": theta, "S(soln) at electrode [non-dim]": c_at_electrode, "Applied Voltage [non-dim]": Eapp, } # -------------------------------- # Set model parameters param.process_model(model) geometry = model.geometry param.process_geometry(geometry) # Create mesh and discretise model mesh = pybamm.Mesh(geometry, model.submesh_types, model.var_pts) disc = pybamm.Discretisation(mesh, model.spatial_methods) disc.process_model(model) # Create solver model.convert_to_format = 'python' solver = pybamm.ScipySolver(method='BDF', rtol=1e-6, atol=1e-6) #solver = pybamm.CasadiSolver(mode='fast', rtol=1e-8, atol=1e-10) # Store discretised model and solver self._model = model self._param = param self._solver = solver self._omega_d = param.process_symbol(omega_d).evaluate() self._I_0 = param.process_symbol(I_0).evaluate() self._T_0 = param.process_symbol(T_0).evaluate()
def __init__(self): super().__init__() self.name = "Effective resistance in current collector model" self.param = pybamm.standard_parameters_lithium_ion # Get useful parameters param = self.param l_cn = param.l_cn l_cp = param.l_cp l_y = param.l_y sigma_cn_dbl_prime = param.sigma_cn_dbl_prime sigma_cp_dbl_prime = param.sigma_cp_dbl_prime alpha_prime = param.alpha_prime # Set model variables var = pybamm.standard_spatial_vars psi = pybamm.Variable("Current collector potential weighted sum", ["current collector"]) W = pybamm.Variable( "Perturbation to current collector potential difference", ["current collector"], ) c_psi = pybamm.Variable("Lagrange multiplier for variable `psi`") c_W = pybamm.Variable("Lagrange multiplier for variable `W`") self.variables = { "Current collector potential weighted sum": psi, "Perturbation to current collector potential difference": W, "Lagrange multiplier for variable `psi`": c_psi, "Lagrange multiplier for variable `W`": c_W, } # Algebraic equations (enforce zero mean constraint through Lagrange multiplier) # 0*LagrangeMultiplier hack otherwise gives KeyError self.algebraic = { psi: pybamm.laplacian(psi) + c_psi * pybamm.DefiniteIntegralVector(psi, vector_type="column"), W: pybamm.laplacian(W) - pybamm.source(1, W) + c_W * pybamm.DefiniteIntegralVector(W, vector_type="column"), c_psi: pybamm.Integral(psi, [var.y, var.z]) + 0 * c_psi, c_W: pybamm.Integral(W, [var.y, var.z]) + 0 * c_W, } # Boundary conditons psi_neg_tab_bc = l_cn psi_pos_tab_bc = -l_cp W_neg_tab_bc = l_y / (alpha_prime * sigma_cn_dbl_prime) W_pos_tab_bc = l_y / (alpha_prime * sigma_cp_dbl_prime) self.boundary_conditions = { psi: { "negative tab": (psi_neg_tab_bc, "Neumann"), "positive tab": (psi_pos_tab_bc, "Neumann"), }, W: { "negative tab": (W_neg_tab_bc, "Neumann"), "positive tab": (W_pos_tab_bc, "Neumann"), }, } # "Initial conditions" provides initial guess for solver # TODO: better guess than zero? self.initial_conditions = { psi: pybamm.Scalar(0), W: pybamm.Scalar(0), c_psi: pybamm.Scalar(0), c_W: pybamm.Scalar(0), } # Define effective current collector resistance psi_neg_tab = pybamm.BoundaryValue(psi, "negative tab") psi_pos_tab = pybamm.BoundaryValue(psi, "positive tab") W_neg_tab = pybamm.BoundaryValue(W, "negative tab") W_pos_tab = pybamm.BoundaryValue(W, "positive tab") R_cc = ((alpha_prime / l_y) * (sigma_cn_dbl_prime * l_cn * W_pos_tab + sigma_cp_dbl_prime * l_cp * W_neg_tab) - (psi_pos_tab - psi_neg_tab)) / (sigma_cn_dbl_prime * l_cn + sigma_cp_dbl_prime * l_cp) R_cc_dim = R_cc * param.potential_scale / param.I_typ self.variables.update({ "Current collector potential weighted sum (negative tab)": psi_neg_tab, "Current collector potential weighted sum (positive tab)": psi_pos_tab, "Perturbation to c.c. potential difference (negative tab)": W_neg_tab, "Perturbation to c.c. potential difference (positive tab)": W_pos_tab, "Effective current collector resistance": R_cc, "Effective current collector resistance [Ohm]": R_cc_dim, })
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 # terminal voltage (Note: phi_s_cn is zero at the negative tab) phi_s_cp = self.variables["Positive current collector potential"] phi_s_cp_dim = self.variables[ "Positive current collector potential [V]"] if self.options["dimensionality"] == 0: V = phi_s_cp V_dim = phi_s_cp_dim elif self.options["dimensionality"] in [1, 2]: V = pybamm.BoundaryValue(phi_s_cp, "positive tab") V_dim = pybamm.BoundaryValue(phi_s_cp_dim, "positive tab") # 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 solid phase ohmic losses": delta_phi_s_av, "X-averaged solid phase ohmic losses [V]": delta_phi_s_av_dim, "Terminal voltage": V, "Terminal voltage [V]": V_dim, }) # Battery-wide variables eta_e_av_dim = self.variables.get( "X-averaged electrolyte ohmic losses [V]", 0) eta_c_av_dim = self.variables.get( "X-averaged concentration overpotential [V]", 0) 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, }) # Cut-off voltage voltage = self.variables["Terminal voltage"] self.events["Minimum voltage"] = voltage - self.param.voltage_low_cut self.events["Maximum voltage"] = voltage - self.param.voltage_high_cut
def test_process_symbol(self): parameter_values = pybamm.ParameterValues({"a": 4, "b": 2, "c": 3}) # process parameter a = pybamm.Parameter("a") processed_a = parameter_values.process_symbol(a) self.assertIsInstance(processed_a, pybamm.Scalar) self.assertEqual(processed_a.value, 4) # process binary operation var = pybamm.Variable("var") add = a + var processed_add = parameter_values.process_symbol(add) self.assertIsInstance(processed_add, pybamm.Addition) self.assertIsInstance(processed_add.children[0], pybamm.Scalar) self.assertIsInstance(processed_add.children[1], pybamm.Variable) self.assertEqual(processed_add.children[0].value, 4) b = pybamm.Parameter("b") add = a + b processed_add = parameter_values.process_symbol(add) self.assertIsInstance(processed_add, pybamm.Scalar) self.assertEqual(processed_add.value, 6) scal = pybamm.Scalar(34) mul = a * scal processed_mul = parameter_values.process_symbol(mul) self.assertIsInstance(processed_mul, pybamm.Scalar) self.assertEqual(processed_mul.value, 136) # process integral aa = pybamm.Parameter("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", domain=["negative electrode"]) integ = pybamm.Integral(aa, x) processed_integ = parameter_values.process_symbol(integ) self.assertIsInstance(processed_integ, pybamm.Integral) self.assertIsInstance(processed_integ.children[0], pybamm.Scalar) self.assertEqual(processed_integ.children[0].value, 4) self.assertEqual(processed_integ.integration_variable[0].id, x.id) # process unary operation v = pybamm.Variable("v", domain="test") grad = pybamm.Gradient(v) processed_grad = parameter_values.process_symbol(grad) self.assertIsInstance(processed_grad, pybamm.Gradient) self.assertIsInstance(processed_grad.children[0], pybamm.Variable) # process delta function aa = pybamm.Parameter("a") delta_aa = pybamm.DeltaFunction(aa, "left", "some domain") processed_delta_aa = parameter_values.process_symbol(delta_aa) self.assertIsInstance(processed_delta_aa, pybamm.DeltaFunction) self.assertEqual(processed_delta_aa.side, "left") processed_a = processed_delta_aa.children[0] self.assertIsInstance(processed_a, pybamm.Scalar) self.assertEqual(processed_a.value, 4) # process boundary operator (test for BoundaryValue) aa = pybamm.Parameter("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", domain=["negative electrode"]) boundary_op = pybamm.BoundaryValue(aa * x, "left") processed_boundary_op = parameter_values.process_symbol(boundary_op) self.assertIsInstance(processed_boundary_op, pybamm.BoundaryOperator) processed_a = processed_boundary_op.children[0].children[0] processed_x = processed_boundary_op.children[0].children[1] self.assertIsInstance(processed_a, pybamm.Scalar) self.assertEqual(processed_a.value, 4) self.assertEqual(processed_x.id, x.id) # process broadcast whole_cell = ["negative electrode", "separator", "positive electrode"] broad = pybamm.PrimaryBroadcast(a, whole_cell) processed_broad = parameter_values.process_symbol(broad) self.assertIsInstance(processed_broad, pybamm.Broadcast) self.assertEqual(processed_broad.domain, whole_cell) self.assertIsInstance(processed_broad.children[0], pybamm.Scalar) self.assertEqual(processed_broad.children[0].evaluate(), 4) # process concatenation conc = pybamm.concatenation( pybamm.Vector(np.ones(10), domain="test"), pybamm.Vector(2 * np.ones(15), domain="test 2"), ) processed_conc = parameter_values.process_symbol(conc) self.assertIsInstance(processed_conc.children[0], pybamm.Vector) self.assertIsInstance(processed_conc.children[1], pybamm.Vector) np.testing.assert_array_equal(processed_conc.children[0].entries, 1) np.testing.assert_array_equal(processed_conc.children[1].entries, 2) # process domain concatenation c_e_n = pybamm.Variable("c_e_n", ["negative electrode"]) c_e_s = pybamm.Variable("c_e_p", ["separator"]) test_mesh = shared.get_mesh_for_testing() dom_con = pybamm.DomainConcatenation([a * c_e_n, b * c_e_s], test_mesh) processed_dom_con = parameter_values.process_symbol(dom_con) a_proc = processed_dom_con.children[0].children[0] b_proc = processed_dom_con.children[1].children[0] self.assertIsInstance(a_proc, pybamm.Scalar) self.assertIsInstance(b_proc, pybamm.Scalar) self.assertEqual(a_proc.value, 4) self.assertEqual(b_proc.value, 2) # process variable c = pybamm.Variable("c") processed_c = parameter_values.process_symbol(c) self.assertIsInstance(processed_c, pybamm.Variable) self.assertEqual(processed_c.name, "c") # process scalar d = pybamm.Scalar(14) processed_d = parameter_values.process_symbol(d) self.assertIsInstance(processed_d, pybamm.Scalar) self.assertEqual(processed_d.value, 14) # process array types e = pybamm.Vector(np.ones(4)) processed_e = parameter_values.process_symbol(e) self.assertIsInstance(processed_e, pybamm.Vector) np.testing.assert_array_equal(processed_e.evaluate(), np.ones((4, 1))) f = pybamm.Matrix(np.ones((5, 6))) processed_f = parameter_values.process_symbol(f) self.assertIsInstance(processed_f, pybamm.Matrix) np.testing.assert_array_equal(processed_f.evaluate(), np.ones((5, 6))) # process statevector g = pybamm.StateVector(slice(0, 10)) processed_g = parameter_values.process_symbol(g) self.assertIsInstance(processed_g, pybamm.StateVector) np.testing.assert_array_equal( processed_g.evaluate(y=np.ones(10)), np.ones((10, 1)) ) # not implemented sym = pybamm.Symbol("sym") with self.assertRaises(NotImplementedError): parameter_values.process_symbol(sym) # not found with self.assertRaises(KeyError): x = pybamm.Parameter("x") parameter_values.process_symbol(x)
def test_indefinite_integral(self): # create discretisation mesh = get_mesh_for_testing() spatial_methods = { "macroscale": pybamm.FiniteVolume(), "negative particle": pybamm.FiniteVolume(), "positive particle": pybamm.FiniteVolume(), "current collector": pybamm.ZeroDimensionalMethod(), } disc = pybamm.Discretisation(mesh, spatial_methods) # input a phi, take grad, then integrate to recover phi approximation # (need to test this way as check evaluated on edges using if has grad # and no div) phi = pybamm.Variable("phi", domain=["negative electrode", "separator"]) i = pybamm.grad(phi) # create test current (variable on edges) x = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) int_grad_phi = pybamm.IndefiniteIntegral(i, x) disc.set_variable_slices([phi]) # i is not a fundamental variable # Set boundary conditions (required for shape but don't matter) disc._bcs = { phi.id: { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } } int_grad_phi_disc = disc.process_symbol(int_grad_phi) left_boundary_value = pybamm.BoundaryValue(int_grad_phi, "left") left_boundary_value_disc = disc.process_symbol(left_boundary_value) combined_submesh = mesh.combine_submeshes("negative electrode", "separator") # constant case phi_exact = np.ones((combined_submesh[0].npts, 1)) phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) phi_approx += 1 # add constant of integration np.testing.assert_array_equal(phi_exact, phi_approx) self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0) # linear case phi_exact = combined_submesh[0].nodes[:, np.newaxis] phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) np.testing.assert_array_almost_equal(phi_exact, phi_approx) self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0) # sine case phi_exact = np.sin(combined_submesh[0].nodes[:, np.newaxis]) phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) np.testing.assert_array_almost_equal(phi_exact, phi_approx) self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0) # -------------------------------------------------------------------- # region which doesn't start at zero phi = pybamm.Variable("phi", domain=["separator", "positive electrode"]) i = pybamm.grad(phi) # create test current (variable on edges) x = pybamm.SpatialVariable("x", ["separator", "positive electrode"]) int_grad_phi = pybamm.IndefiniteIntegral(i, x) disc.set_variable_slices([phi]) # i is not a fundamental variable disc._bcs = { phi.id: { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } } int_grad_phi_disc = disc.process_symbol(int_grad_phi) left_boundary_value = pybamm.BoundaryValue(int_grad_phi, "left") left_boundary_value_disc = disc.process_symbol(left_boundary_value) combined_submesh = mesh.combine_submeshes("separator", "positive electrode") # constant case phi_exact = np.ones((combined_submesh[0].npts, 1)) phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) phi_approx += 1 # add constant of integration np.testing.assert_array_equal(phi_exact, phi_approx) self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0) # linear case phi_exact = (combined_submesh[0].nodes[:, np.newaxis] - combined_submesh[0].edges[0]) phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) np.testing.assert_array_almost_equal(phi_exact, phi_approx) np.testing.assert_array_almost_equal( left_boundary_value_disc.evaluate(y=phi_exact), 0) # sine case phi_exact = np.sin(combined_submesh[0].nodes[:, np.newaxis] - combined_submesh[0].edges[0]) phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) np.testing.assert_array_almost_equal(phi_exact, phi_approx) np.testing.assert_array_almost_equal( left_boundary_value_disc.evaluate(y=phi_exact), 0) # -------------------------------------------------------------------- # micrsoscale case c = pybamm.Variable("c", domain=["negative particle"]) N = pybamm.grad(c) # create test current (variable on edges) r_n = pybamm.SpatialVariable("r_n", ["negative particle"]) c_integral = pybamm.IndefiniteIntegral(N, r_n) disc.set_variable_slices([c]) # N is not a fundamental variable disc._bcs = { c.id: { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } } c_integral_disc = disc.process_symbol(c_integral) left_boundary_value = pybamm.BoundaryValue(c_integral, "left") left_boundary_value_disc = disc.process_symbol(left_boundary_value) combined_submesh = mesh["negative particle"] # constant case c_exact = np.ones((combined_submesh[0].npts, 1)) c_approx = c_integral_disc.evaluate(None, c_exact) c_approx += 1 # add constant of integration np.testing.assert_array_equal(c_exact, c_approx) self.assertEqual(left_boundary_value_disc.evaluate(y=c_exact), 0) # linear case c_exact = combined_submesh[0].nodes[:, np.newaxis] c_approx = c_integral_disc.evaluate(None, c_exact) np.testing.assert_array_almost_equal(c_exact, c_approx) np.testing.assert_array_almost_equal( left_boundary_value_disc.evaluate(y=c_exact), 0) # sine case c_exact = np.sin(combined_submesh[0].nodes[:, np.newaxis]) c_approx = c_integral_disc.evaluate(None, c_exact) np.testing.assert_array_almost_equal(c_exact, c_approx, decimal=3) np.testing.assert_array_almost_equal( left_boundary_value_disc.evaluate(y=c_exact), 0)