def test_macroscale_parameters(self): geo = pybamm.GeometricParameters() L_n = geo.L_n L_s = geo.L_s L_p = geo.L_p L_x = geo.L_x l_n = geo.l_n l_s = geo.l_s l_p = geo.l_p parameter_values = pybamm.ParameterValues( values={ "Negative electrode thickness [m]": 0.05, "Separator thickness [m]": 0.02, "Positive electrode thickness [m]": 0.21, } ) L_n_eval = parameter_values.process_symbol(L_n) L_s_eval = parameter_values.process_symbol(L_s) L_p_eval = parameter_values.process_symbol(L_p) L_x_eval = parameter_values.process_symbol(L_x) self.assertEqual( (L_n_eval + L_s_eval + L_p_eval).evaluate(), L_x_eval.evaluate() ) l_n_eval = parameter_values.process_symbol(l_n) l_s_eval = parameter_values.process_symbol(l_s) l_p_eval = parameter_values.process_symbol(l_p) self.assertAlmostEqual((l_n_eval + l_s_eval + l_p_eval).evaluate(), 1)
def yz_average(symbol): """convenience function for creating an average in the y-z-direction Parameters ---------- symbol : :class:`pybamm.Symbol` The function to be averaged Returns ------- :class:`Symbol` the new averaged symbol """ # Symbol must have domain [] or ["current collector"] if symbol.domain not in [[], ["current collector"]]: raise pybamm.DomainError( """y-z-average only implemented in the 'current collector' domain, but symbol has domains {}""".format(symbol.domain)) # If symbol doesn't have a domain, its average value is itself if symbol.domain == []: new_symbol = symbol.new_copy() new_symbol.parent = None return new_symbol # If symbol is a Broadcast, its average value is its child elif isinstance(symbol, pybamm.Broadcast): return symbol.orphans[0] # Otherwise, use Integral to calculate average value else: geo = pybamm.GeometricParameters() y = pybamm.standard_spatial_vars.y z = pybamm.standard_spatial_vars.z l_y = geo.l_y l_z = geo.l_z return Integral(symbol, [y, z]) / (l_y * l_z)
def __init__(self): # Get geometric parameters self.geo = pybamm.GeometricParameters() # Set parameters self._set_dimensional_parameters() self._set_dimensionless_parameters()
def __init__(self): # Get geometric, electrical and thermal parameters self.geo = pybamm.GeometricParameters() self.elec = pybamm.ElectricalParameters() self.therm = pybamm.ThermalParameters() # Set parameters and scales self._set_dimensional_parameters() self._set_scales() self._set_dimensionless_parameters() # Set input current self._set_input_current()
def test_geometry(self): var = pybamm.standard_spatial_vars geo = pybamm.GeometricParameters() for cc_dimension in [0, 1, 2]: geometry = pybamm.battery_geometry( current_collector_dimension=cc_dimension) self.assertIsInstance(geometry, pybamm.Geometry) self.assertIn("negative electrode", geometry) self.assertIn("negative particle", geometry) self.assertEqual(geometry["negative electrode"][var.x_n]["min"], 0) self.assertEqual(geometry["negative electrode"][var.x_n]["max"].id, geo.l_n.id) if cc_dimension == 1: self.assertIn("tabs", geometry["current collector"]) geometry = pybamm.battery_geometry(include_particles=False) self.assertNotIn("negative particle", geometry)
def __init__(self, model, param, disc, solution, operating_condition): self.model = model self.param = param self.disc = disc self.solution = solution self.operating_condition = operating_condition # Use dimensional time and space self.t = solution.t * model.timescale_eval geo = pybamm.GeometricParameters() L_x = param.evaluate(geo.L_x) self.x_n = disc.mesh["negative electrode"].nodes * L_x self.x_s = disc.mesh["separator"].nodes * L_x self.x_p = disc.mesh["positive electrode"].nodes * L_x whole_cell = ["negative electrode", "separator", "positive electrode"] self.x = disc.mesh.combine_submeshes(*whole_cell).nodes * L_x self.x_n_edge = disc.mesh["negative electrode"].edges * L_x self.x_s_edge = disc.mesh["separator"].edges * L_x self.x_p_edge = disc.mesh["positive electrode"].edges * L_x self.x_edge = disc.mesh.combine_submeshes(*whole_cell).edges * L_x if isinstance(self.model, pybamm.lithium_ion.BaseModel): R_n = param.evaluate(geo.R_n) R_p = param.evaluate(geo.R_p) self.r_n = disc.mesh["negative particle"].nodes * R_n self.r_p = disc.mesh["positive particle"].nodes * R_p self.r_n_edge = disc.mesh["negative particle"].edges * R_n self.r_p_edge = disc.mesh["positive particle"].edges * R_p # Useful parameters self.l_n = param.evaluate(geo.l_n) self.l_p = param.evaluate(geo.l_p) current_param = self.model.param.current_with_time self.i_cell = param.process_symbol(current_param).evaluate(solution.t)
def test_read_parameters(self): geo = pybamm.GeometricParameters() L_n = geo.L_n L_s = geo.L_s L_p = geo.L_p L_y = geo.L_y L_z = geo.L_z tab_n_y = geo.Centre_y_tab_n tab_n_z = geo.Centre_z_tab_n L_tab_n = geo.L_tab_n tab_p_y = geo.Centre_y_tab_p tab_p_z = geo.Centre_z_tab_p L_tab_p = geo.L_tab_p geometry = pybamm.battery_geometry(current_collector_dimension=2) self.assertEqual( set([x.name for x in geometry.parameters]), set([ x.name for x in [ L_n, L_s, L_p, L_y, L_z, tab_n_y, tab_n_z, L_tab_n, tab_p_y, tab_p_z, L_tab_p, ] ]), ) self.assertTrue( all(isinstance(x, pybamm.Parameter) for x in geometry.parameters))
def battery_geometry(include_particles=True, current_collector_dimension=0): """ A convenience function to create battery geometries. Parameters ---------- include_particles : bool Whether to include particle domains current_collector_dimensions : int, default The dimensions of the current collector. Should be 0 (default), 1 or 2 Returns ------- :class:`pybamm.Geometry` A geometry class for the battery """ var = pybamm.standard_spatial_vars geo = pybamm.GeometricParameters() l_n = geo.l_n l_s = geo.l_s geometry = { "negative electrode": {var.x_n: {"min": 0, "max": l_n}}, "separator": {var.x_s: {"min": l_n, "max": l_n + l_s}}, "positive electrode": {var.x_p: {"min": l_n + l_s, "max": 1}}, } # Add particle domains if include_particles is True: geometry.update( { "negative particle": {var.r_n: {"min": 0, "max": 1}}, "positive particle": {var.r_p: {"min": 0, "max": 1}}, } ) if current_collector_dimension == 0: geometry["current collector"] = {var.z: {"position": 1}} elif current_collector_dimension == 1: geometry["current collector"] = { var.z: {"min": 0, "max": 1}, "tabs": { "negative": {"z_centre": geo.centre_z_tab_n}, "positive": {"z_centre": geo.centre_z_tab_p}, }, } elif current_collector_dimension == 2: geometry["current collector"] = { var.y: {"min": 0, "max": geo.l_y}, var.z: {"min": 0, "max": geo.l_z}, "tabs": { "negative": { "y_centre": geo.centre_y_tab_n, "z_centre": geo.centre_z_tab_n, "width": geo.l_tab_n, }, "positive": { "y_centre": geo.centre_y_tab_p, "z_centre": geo.centre_z_tab_p, "width": geo.l_tab_p, }, }, } else: raise pybamm.GeometryError( "Invalid current collector dimension '{}' (should be 0, 1 or 2)".format( current_collector_dimension ) ) return pybamm.Geometry(geometry)
0.334, "Lower voltage cut-off [V]": 3.0, "Upper voltage cut-off [V]": 4.7, }, check_already_exists=False) param["Negative electrode OCP [V]"] = ecm.neg_OCP param["Positive electrode OCP [V]"] = ecm.pos_OCP param["Negative electrode OCP entropic change [V.K-1]"] = ecm.neg_dUdT param["Positive electrode OCP entropic change [V.K-1]"] = ecm.pos_dUdT e_width = param["Electrode width [m]"] z_edges = np.linspace(0, e_height, Nspm + 1) A_cc = param.evaluate(pybamm.GeometricParameters().A_cc) param.process_model(model) param.process_geometry(geometry) sys.setrecursionlimit(10000) var = pybamm.standard_spatial_vars var_pts = { var.x_n: 5, var.x_s: 5, var.x_p: 5, var.r_n: 10, var.r_p: 10, var.z: Nspm, }
def x_average(symbol): """convenience function for creating an average in the x-direction Parameters ---------- symbol : :class:`pybamm.Symbol` The function to be averaged Returns ------- :class:`Symbol` the new averaged symbol """ # Can't take average if the symbol evaluates on edges if symbol.evaluates_on_edges("primary"): raise ValueError( "Can't take the x-average of a symbol that evaluates on edges") # If symbol doesn't have a domain, its average value is itself if symbol.domain in [[], ["current collector"]]: new_symbol = symbol.new_copy() new_symbol.parent = None return new_symbol # If symbol is a Broadcast, its average value is its child elif isinstance(symbol, pybamm.Broadcast): return symbol.orphans[0] # If symbol is a concatenation of Broadcasts, its average value is its child elif (isinstance(symbol, pybamm.Concatenation) and all( isinstance(child, pybamm.Broadcast) for child in symbol.children) and symbol.domain == ["negative electrode", "separator", "positive electrode"]): a, b, c = [orp.orphans[0] for orp in symbol.orphans] if a.id == b.id == c.id: return a else: geo = pybamm.GeometricParameters() l_n = geo.l_n l_s = geo.l_s l_p = geo.l_p return (l_n * a + l_s * b + l_p * c) / (l_n + l_s + l_p) # Otherwise, use Integral to calculate average value else: geo = pybamm.GeometricParameters() if symbol.domain == ["negative electrode"]: x = pybamm.standard_spatial_vars.x_n l = geo.l_n elif symbol.domain == ["separator"]: x = pybamm.standard_spatial_vars.x_s l = geo.l_s elif symbol.domain == ["positive electrode"]: x = pybamm.standard_spatial_vars.x_p l = geo.l_p elif symbol.domain == [ "negative electrode", "separator", "positive electrode" ]: x = pybamm.standard_spatial_vars.x l = pybamm.Scalar(1) elif symbol.domain == ["negative particle"]: x = pybamm.standard_spatial_vars.x_n l = geo.l_n elif symbol.domain == ["positive particle"]: x = pybamm.standard_spatial_vars.x_p l = geo.l_p else: x = pybamm.SpatialVariable("x", domain=symbol.domain) v = pybamm.ones_like(symbol) l = pybamm.Integral(v, x) return Integral(symbol, x) / l