def test_process_inline_function_parameters(self): def D(c): return c**2 parameter_values = pybamm.ParameterValues({"Diffusivity": D}) a = pybamm.InputParameter("a") func = pybamm.FunctionParameter("Diffusivity", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"a": 3}), 9) # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) self.assertEqual(processed_diff_func.evaluate(inputs={"a": 3}), 6)
def test_multi_var_function_parameter(self): def D(a, b): return a * pybamm.exp(b) parameter_values = pybamm.ParameterValues({ "a": 3, "b": 0, "Diffusivity": D }) a = pybamm.Parameter("a") b = pybamm.Parameter("b") func = pybamm.FunctionParameter("Diffusivity", {"a": a, "b": b}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(), 3)
def a_p_of_x(self, x): """ Dimensionless surface area per unit volume distribution in x. The surface area per unit volume distribution is defined so that the actual surface area per unit volume as a function of x is given by a*a_of_x (so that a_of_x = 1 gives uniform surface area per unit volume in x). """ if self.options["particle shape"] == "spherical": # Currently the active material volume fraction is a scalar, so the # distribution of surface are per unit volume is simply the reciprocal # of the particle radius distribution return 1 / self.R_p_of_x(x) elif self.options["particle shape"] == "user": inputs = {"Through-cell distance (x_p) [m]": x} return pybamm.FunctionParameter( "Positive surface area per unit volume distribution in x", inputs)
def test_set_input_names(self): var = pybamm.Variable("var") func = pybamm.FunctionParameter("a", {"var": var}) new_input_names = ["first", "second"] func.input_names = new_input_names self.assertEqual(func.input_names, new_input_names) with self.assertRaises(TypeError): new_input_names = {"wrong": "input type"} func.input_names = new_input_names with self.assertRaises(TypeError): new_input_names = [var] func.input_names = new_input_names
def test_read_parameters(self): # Read parameters from different parts of the model model = pybamm.BaseModel() a = pybamm.Parameter("a") b = pybamm.InputParameter("b", "test") c = pybamm.Parameter("c") d = pybamm.Parameter("d") e = pybamm.Parameter("e") f = pybamm.InputParameter("f") g = pybamm.Parameter("g") h = pybamm.Parameter("h") i = pybamm.InputParameter("i") u = pybamm.Variable("u") v = pybamm.Variable("v") model.rhs = {u: -u * a} model.algebraic = {v: v - b} model.initial_conditions = {u: c, v: d} model.events = [pybamm.Event("u=e", u - e)] model.variables = {"v+f+i": v + f + i} model.boundary_conditions = { u: { "left": (g, "Dirichlet"), "right": (0, "Neumann") }, v: { "left": (0, "Dirichlet"), "right": (h, "Neumann") }, } self.assertEqual( set([x.name for x in model.parameters]), set([x.name for x in [a, b, c, d, e, f, g, h, i]]), ) self.assertTrue( all( isinstance(x, (pybamm.Parameter, pybamm.InputParameter)) for x in model.parameters)) model.variables = { "v+f+i": v + pybamm.FunctionParameter("f", {"Time [s]": pybamm.t}) + i } model.print_parameter_info()
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([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.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), ]: self.assertEqual(symbol.id, symbol.new_copy().id)
def test_process_interpolant(self): x = np.linspace(0, 10)[:, np.newaxis] data = np.hstack([x, 2 * x]) parameter_values = pybamm.ParameterValues({ "a": 3.01, "Diffusivity": ("times two", data) }) a = pybamm.Parameter("a") func = pybamm.FunctionParameter("Diffusivity", a) processed_func = parameter_values.process_symbol(func) self.assertIsInstance(processed_func, pybamm.Interpolant) self.assertEqual(processed_func.evaluate(), 6.02) # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) self.assertEqual(processed_diff_func.evaluate(), 2)
def test_shape_and_size_for_testing(self): scal = pybamm.Scalar(1) self.assertEqual(scal.shape_for_testing, scal.shape) self.assertEqual(scal.size_for_testing, scal.size) state = pybamm.StateVector(slice(10, 25)) self.assertEqual(state.shape_for_testing, state.shape) param = pybamm.Parameter("a") self.assertEqual(param.shape_for_testing, ()) func = pybamm.FunctionParameter("func", {"state": state}) self.assertEqual(func.shape_for_testing, state.shape_for_testing) concat = pybamm.Concatenation() self.assertEqual(concat.shape_for_testing, (0,)) concat = pybamm.Concatenation(state, state) self.assertEqual(concat.shape_for_testing, (30, 1)) self.assertEqual(concat.size_for_testing, 30) var = pybamm.Variable("var", domain="negative electrode") broadcast = pybamm.PrimaryBroadcast(0, "negative electrode") self.assertEqual(var.shape_for_testing, broadcast.shape_for_testing) self.assertEqual( (var + broadcast).shape_for_testing, broadcast.shape_for_testing ) var = pybamm.Variable("var", domain=["random domain", "other domain"]) broadcast = pybamm.PrimaryBroadcast(0, ["random domain", "other domain"]) self.assertEqual(var.shape_for_testing, broadcast.shape_for_testing) self.assertEqual( (var + broadcast).shape_for_testing, broadcast.shape_for_testing ) sym = pybamm.Symbol("sym") with self.assertRaises(NotImplementedError): sym.shape_for_testing
def electrolyte_diffusivity_Ecker2015(c_e, T): """ Diffusivity of LiPF6 in EC:DMC as a function of ion concentration [1, 2, 3]. References ---------- .. [1] Ecker, Madeleine, et al. "Parameterization of a physico-chemical model of a lithium-ion battery i. determination of parameters." Journal of the Electrochemical Society 162.9 (2015): A1836-A1848. .. [2] Ecker, Madeleine, et al. "Parameterization of a physico-chemical model of a lithium-ion battery ii. model validation." Journal of The Electrochemical Society 162.9 (2015): A1849-A1857. .. [3] Richardson, Giles, et. al. "Generalised single particle models for high-rate operation of graded lithium-ion electrodes: Systematic derivation and validation." Electrochemica Acta 339 (2020): 135862 Parameters ---------- c_e: :class:`pybamm.Symbol` Dimensional electrolyte concentration T: :class:`pybamm.Symbol` Dimensional temperature Returns ------- :class:`pybamm.Symbol` Solid diffusivity """ # The diffusivity epends on the electrolyte conductivity inputs = {"Electrolyte concentration [mol.m-3]": c_e, "Temperature [K]": T} sigma_e = pybamm.FunctionParameter("Electrolyte conductivity [S.m-1]", inputs) D_c_e = (constants.k_b / (constants.F * constants.q_e)) * sigma_e * T / c_e return D_c_e
def D_e_dimensional(c_e, T): "Dimensional diffusivity in electrolyte" return pybamm.FunctionParameter("Electrolyte diffusivity [m2.s-1]", c_e)
def T_amb_dim(self, t): "Dimensional ambient temperature" return pybamm.FunctionParameter("Ambient temperature [K]", {"Times [s]": t})
def j0_p_Ox_dimensional(c_e, T): "Dimensional oxygen positive electrode exchange-current density [A.m-2]" inputs = {"Electrolyte concentration [mol.m-3]": c_e, "Temperature [K]": T} return pybamm.FunctionParameter( "Positive electrode oxygen exchange-current density [A.m-2]", inputs)
def U_p_dimensional(c_e, T): "Dimensional open-circuit voltage in the positive electrode [V]" inputs = {"Electrolyte molar mass [mol.kg-1]": m_dimensional(c_e)} return pybamm.FunctionParameter( "Positive electrode open-circuit potential [V]", inputs)
def chi_dimensional(c_e): inputs = {"Electrolyte concentration [mol.m-3]": c_e} return pybamm.FunctionParameter("Darken thermodynamic factor", inputs)
def kappa_e_dimensional(c_e, T): "Dimensional electrolyte conductivity" inputs = {"Electrolyte concentration [mol.m-3]": c_e} return pybamm.FunctionParameter("Electrolyte conductivity [S.m-1]", inputs)
def chi_dimensional(c_e): return pybamm.FunctionParameter("Darken thermodynamic factor", c_e)
def t_plus(c_e): "Dimensionless transference number (i.e. c_e is dimensionless)" inputs = {"Electrolyte concentration [mol.m-3]": c_e * c_e_typ} return pybamm.FunctionParameter("Cation transference number", inputs)
def test_symbol_simplify(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) c = pybamm.Parameter("c") d = pybamm.Scalar(-1) e = pybamm.Scalar(2) g = pybamm.Variable("g") # negate self.assertIsInstance((-a).simplify(), pybamm.Scalar) self.assertEqual((-a).simplify().evaluate(), 0) self.assertIsInstance((-b).simplify(), pybamm.Scalar) self.assertEqual((-b).simplify().evaluate(), -1) # absolute value self.assertIsInstance((abs(a)).simplify(), pybamm.Scalar) self.assertEqual((abs(a)).simplify().evaluate(), 0) self.assertIsInstance((abs(d)).simplify(), pybamm.Scalar) self.assertEqual((abs(d)).simplify().evaluate(), 1) # function def sin(x): return math.sin(x) f = pybamm.Function(sin, b) self.assertIsInstance((f).simplify(), pybamm.Scalar) self.assertEqual((f).simplify().evaluate(), math.sin(1)) def myfunction(x, y): return x * y f = pybamm.Function(myfunction, a, b) self.assertIsInstance((f).simplify(), pybamm.Scalar) self.assertEqual((f).simplify().evaluate(), 0) # FunctionParameter f = pybamm.FunctionParameter("function", b) self.assertIsInstance((f).simplify(), pybamm.FunctionParameter) self.assertEqual((f).simplify().children[0].id, b.id) f = pybamm.FunctionParameter("function", a, b) self.assertIsInstance((f).simplify(), pybamm.FunctionParameter) self.assertEqual((f).simplify().children[0].id, a.id) self.assertEqual((f).simplify().children[1].id, b.id) # Gradient self.assertIsInstance((pybamm.grad(a)).simplify(), pybamm.Scalar) self.assertEqual((pybamm.grad(a)).simplify().evaluate(), 0) v = pybamm.Variable("v") self.assertIsInstance((pybamm.grad(v)).simplify(), pybamm.Gradient) # Divergence self.assertIsInstance((pybamm.div(a)).simplify(), pybamm.Scalar) self.assertEqual((pybamm.div(a)).simplify().evaluate(), 0) self.assertIsInstance((pybamm.div(v)).simplify(), pybamm.Divergence) # Integral self.assertIsInstance( (pybamm.Integral(a, pybamm.t)).simplify(), pybamm.Integral ) # BoundaryValue v_neg = pybamm.Variable("v", domain=["negative electrode"]) self.assertIsInstance( (pybamm.boundary_value(v_neg, "right")).simplify(), pybamm.BoundaryValue ) # Delta function self.assertIsInstance( (pybamm.DeltaFunction(v_neg, "right", "domain")).simplify(), pybamm.DeltaFunction, ) # addition self.assertIsInstance((a + b).simplify(), pybamm.Scalar) self.assertEqual((a + b).simplify().evaluate(), 1) self.assertIsInstance((b + b).simplify(), pybamm.Scalar) self.assertEqual((b + b).simplify().evaluate(), 2) self.assertIsInstance((b + a).simplify(), pybamm.Scalar) self.assertEqual((b + a).simplify().evaluate(), 1) # subtraction self.assertIsInstance((a - b).simplify(), pybamm.Scalar) self.assertEqual((a - b).simplify().evaluate(), -1) self.assertIsInstance((b - b).simplify(), pybamm.Scalar) self.assertEqual((b - b).simplify().evaluate(), 0) self.assertIsInstance((b - a).simplify(), pybamm.Scalar) self.assertEqual((b - a).simplify().evaluate(), 1) # addition and subtraction with matrix zero v = pybamm.Vector(np.zeros((10, 1))) self.assertIsInstance((b + v).simplify(), pybamm.Array) np.testing.assert_array_equal((b + v).simplify().evaluate(), np.ones((10, 1))) self.assertIsInstance((v + b).simplify(), pybamm.Array) np.testing.assert_array_equal((v + b).simplify().evaluate(), np.ones((10, 1))) self.assertIsInstance((b - v).simplify(), pybamm.Array) np.testing.assert_array_equal((b - v).simplify().evaluate(), np.ones((10, 1))) self.assertIsInstance((v - b).simplify(), pybamm.Array) np.testing.assert_array_equal((v - b).simplify().evaluate(), -np.ones((10, 1))) # multiplication self.assertIsInstance((a * b).simplify(), pybamm.Scalar) self.assertEqual((a * b).simplify().evaluate(), 0) self.assertIsInstance((b * a).simplify(), pybamm.Scalar) self.assertEqual((b * a).simplify().evaluate(), 0) self.assertIsInstance((b * b).simplify(), pybamm.Scalar) self.assertEqual((b * b).simplify().evaluate(), 1) self.assertIsInstance((a * a).simplify(), pybamm.Scalar) self.assertEqual((a * a).simplify().evaluate(), 0) # test when other node is a parameter self.assertIsInstance((a + c).simplify(), pybamm.Parameter) self.assertIsInstance((c + a).simplify(), pybamm.Parameter) self.assertIsInstance((c + b).simplify(), pybamm.Addition) self.assertIsInstance((b + c).simplify(), pybamm.Addition) self.assertIsInstance((a * c).simplify(), pybamm.Scalar) self.assertEqual((a * c).simplify().evaluate(), 0) self.assertIsInstance((c * a).simplify(), pybamm.Scalar) self.assertEqual((c * a).simplify().evaluate(), 0) self.assertIsInstance((b * c).simplify(), pybamm.Parameter) self.assertIsInstance((e * c).simplify(), pybamm.Multiplication) expr = (e * (e * c)).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e / (e * c)).simplify() self.assertIsInstance(expr, pybamm.Division) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 1.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e * (e / c)).simplify() self.assertIsInstance(expr, pybamm.Division) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e * (c / e)).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 1.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = ((e * c) * (c / e)).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 1.0) self.assertIsInstance(expr.children[1], pybamm.Multiplication) self.assertIsInstance(expr.children[1].children[0], pybamm.Parameter) self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter) expr = (e + (e + c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e + (e - c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Negate) self.assertIsInstance(expr.children[1].children[0], pybamm.Parameter) expr = (e + (g - c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2.0) self.assertIsInstance(expr.children[1], pybamm.Subtraction) self.assertIsInstance(expr.children[1].children[0], pybamm.Variable) self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter) expr = ((2 + c) + (c + 2)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Multiplication) self.assertIsInstance(expr.children[1].children[0], pybamm.Scalar) self.assertEqual(expr.children[1].children[0].evaluate(), 2) self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter) expr = ((-1 + c) - (c + 1) + (c - 1)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), -3.0) # check these don't simplify self.assertIsInstance((c * e).simplify(), pybamm.Multiplication) self.assertIsInstance((e / c).simplify(), pybamm.Division) self.assertIsInstance((c).simplify(), pybamm.Parameter) c1 = pybamm.Parameter("c1") self.assertIsInstance((c1 * c).simplify(), pybamm.Multiplication) # should simplify division to multiply self.assertIsInstance((c / e).simplify(), pybamm.Multiplication) self.assertIsInstance((c / b).simplify(), pybamm.Parameter) self.assertIsInstance((c * b).simplify(), pybamm.Parameter) # negation with parameter self.assertIsInstance((-c).simplify(), pybamm.Negate) self.assertIsInstance((a + b + a).simplify(), pybamm.Scalar) self.assertEqual((a + b + a).simplify().evaluate(), 1) self.assertIsInstance((b + a + a).simplify(), pybamm.Scalar) self.assertEqual((b + a + a).simplify().evaluate(), 1) self.assertIsInstance((a * b * b).simplify(), pybamm.Scalar) self.assertEqual((a * b * b).simplify().evaluate(), 0) self.assertIsInstance((b * a * b).simplify(), pybamm.Scalar) self.assertEqual((b * a * b).simplify().evaluate(), 0) # power simplification self.assertIsInstance((c ** a).simplify(), pybamm.Scalar) self.assertEqual((c ** a).simplify().evaluate(), 1) self.assertIsInstance((a ** c).simplify(), pybamm.Scalar) self.assertEqual((a ** c).simplify().evaluate(), 0) d = pybamm.Scalar(2) self.assertIsInstance((c ** d).simplify(), pybamm.Power) # division self.assertIsInstance((a / b).simplify(), pybamm.Scalar) self.assertEqual((a / b).simplify().evaluate(), 0) self.assertIsInstance((b / a).simplify(), pybamm.Scalar) self.assertEqual((b / a).simplify().evaluate(), np.inf) self.assertIsInstance((a / a).simplify(), pybamm.Scalar) self.assertTrue(np.isnan((a / a).simplify().evaluate())) self.assertIsInstance((b / b).simplify(), pybamm.Scalar) self.assertEqual((b / b).simplify().evaluate(), 1) # not implemented for Symbol sym = pybamm.Symbol("sym") with self.assertRaises(NotImplementedError): sym.simplify() # A + A = 2A (#323) a = pybamm.Parameter("A") expr = (a + a).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (a + a + a + a).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (a - a + a - a + a + a).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2) self.assertIsInstance(expr.children[1], pybamm.Parameter) # A - A = 0 (#323) expr = (a - a).simplify() self.assertIsInstance(expr, pybamm.Scalar) self.assertEqual(expr.evaluate(), 0) # B - (A+A) = B - 2*A (#323) expr = (b - (a + a)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.right, pybamm.Negate) self.assertIsInstance(expr.right.child, pybamm.Multiplication) self.assertEqual(expr.right.child.left.id, pybamm.Scalar(2).id) self.assertEqual(expr.right.child.right.id, a.id) # B - (1*A + 2*A) = B - 3*A (#323) expr = (b - (1 * a + 2 * a)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.right, pybamm.Negate) self.assertIsInstance(expr.right.child, pybamm.Multiplication) self.assertEqual(expr.right.child.left.id, pybamm.Scalar(3).id) self.assertEqual(expr.right.child.right.id, a.id) # B - (A + C) = B - (A + C) (not B - (A - C)) expr = (b - (a + c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.right, pybamm.Subtraction) self.assertEqual(expr.right.left.id, (-a).id) self.assertEqual(expr.right.right.id, c.id)
def test_process_function_parameter(self): parameter_values = pybamm.ParameterValues( { "a": 3, "func": pybamm.load_function( os.path.join( "tests", "unit", "test_parameters", "data", "process_symbol_test_function.py", ) ), "const": 254, "float_func": lambda x: 42, "mult": pybamm.InputParameter("b") * 5, "bad type": np.array([1, 2, 3]), } ) a = pybamm.InputParameter("a") # process function func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"a": 3}), 369) # process constant function const = pybamm.FunctionParameter("const", {"a": a}) processed_const = parameter_values.process_symbol(const) self.assertIsInstance(processed_const, pybamm.Scalar) self.assertEqual(processed_const.evaluate(), 254) # process case where parameter provided is a pybamm symbol # (e.g. a multiplication) mult = pybamm.FunctionParameter("mult", {"a": a}) processed_mult = parameter_values.process_symbol(mult) self.assertEqual(processed_mult.evaluate(inputs={"a": 14, "b": 63}), 63 * 5) # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) self.assertEqual(processed_diff_func.evaluate(inputs={"a": 3}), 123) # make sure diff works, despite simplifications, when the child is constant a_const = pybamm.Scalar(3) func_const = pybamm.FunctionParameter("func", {"a": a_const}) diff_func_const = func_const.diff(a_const) processed_diff_func_const = parameter_values.process_symbol(diff_func_const) self.assertEqual(processed_diff_func_const.evaluate(), 123) # function parameter that returns a python float func = pybamm.FunctionParameter("float_func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(), 42) # weird type raises error func = pybamm.FunctionParameter("bad type", {"a": a}) with self.assertRaisesRegex(TypeError, "Parameter provided for"): parameter_values.process_symbol(func) # function itself as input (different to the variable being an input) parameter_values = pybamm.ParameterValues( {"func": "[input]", "vector func": pybamm.InputParameter("vec", "test")} ) a = pybamm.Scalar(3) func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"func": 13}), 13) func = pybamm.FunctionParameter("vector func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"vec": 13}), 13) # make sure function keeps the domain of the original function def my_func(x): return 2 * x x = pybamm.standard_spatial_vars.x_n func = pybamm.FunctionParameter("func", {"x": x}) parameter_values = pybamm.ParameterValues({"func": my_func}) func1 = parameter_values.process_symbol(func) parameter_values = pybamm.ParameterValues({"func": pybamm.InputParameter("a")}) func2 = parameter_values.process_symbol(func) parameter_values = pybamm.ParameterValues( {"func": pybamm.InputParameter("a", "negative electrode")} ) func3 = parameter_values.process_symbol(func) self.assertEqual(func1.domains, func2.domains) self.assertEqual(func1.domains, func3.domains)
def _get_standard_active_material_variables(self, eps_solid): param = self.param eps_solid_av = pybamm.x_average(eps_solid) variables = { self.domain + " electrode active material volume fraction": eps_solid, "X-averaged " + self.domain.lower() + " electrode active material volume fraction": eps_solid_av, } # Update other microstructure variables # some models (e.g. lead-acid) do not have particles if self.options["particle shape"] == "no particles": if self.domain == "Negative": x = pybamm.standard_spatial_vars.x_n a = self.param.a_n(x) a_typ = self.param.a_n_typ elif self.domain == "Positive": x = pybamm.standard_spatial_vars.x_p a = self.param.a_p(x) a_typ = self.param.a_p_typ variables.update({ self.domain + " electrode surface area to volume ratio": a, self.domain + " electrode surface area to volume ratio [m-1]": a * a_typ, }) return variables else: # Update electrode capacity variables if self.domain == "Negative": L = param.L_n c_s_max = param.c_n_max elif self.domain == "Positive": L = param.L_p c_s_max = param.c_p_max C = eps_solid_av * L * param.A_cc * c_s_max * param.F / 3600 variables.update({self.domain + " electrode capacity [A.h]": C}) if self.domain == "Negative": x = pybamm.standard_spatial_vars.x_n R = self.param.R_n(x) R_dim = self.param.R_n_dimensional(x * self.param.L_x) a_typ = self.param.a_n_typ elif self.domain == "Positive": x = pybamm.standard_spatial_vars.x_p R = self.param.R_p(x) R_dim = self.param.R_p_dimensional(x * self.param.L_x) a_typ = self.param.a_p_typ R_dim_av = pybamm.x_average(R_dim) # Compute dimensional particle shape if self.options["particle shape"] == "spherical": a_dim = 3 * eps_solid / R_dim a_dim_av = 3 * eps_solid_av / R_dim_av elif self.options["particle shape"] == "user": if self.domain == "Negative": # give dimensional x as an input inputs = { "Through-cell distance (x_n) [m]": x * self.param.L_x } a_dim = pybamm.FunctionParameter( "Negative electrode surface area to volume ratio [m-1]", inputs) if self.domain == "Positive": # give dimensional x as an input inputs = { "Through-cell distance (x_p) [m]": x * self.param.L_x } a_dim = pybamm.FunctionParameter( "Positive electrode surface area to volume ratio [m-1]", inputs) a_dim_av = pybamm.x_average(a_dim) # Surface area to volume ratio is scaled with a_typ, so that it is equal to # 1 when eps_solid and R are uniform in space and time a = a_dim / a_typ a_av = a_dim_av / a_typ variables.update({ self.domain + " particle radius": R, self.domain + " particle radius [m]": R_dim, self.domain + " electrode surface area to volume ratio": a, self.domain + " electrode surface area to volume ratio [m-1]": a_dim, "X-averaged " + self.domain.lower() + " electrode surface area to volume ratio": a_av, "X-averaged " + self.domain.lower() + " electrode surface area to volume ratio [m-1]": a_dim_av, }) return variables
def U_p_dimensional(c_e, T): "Dimensional open-circuit voltage in the positive electrode [V]" return pybamm.FunctionParameter( "Positive electrode open-circuit potential [V]", m_dimensional(c_e))
def mu_dimensional(c_e): """ Dimensional viscosity of electrolyte [kg.m-1.s-1]. """ return pybamm.FunctionParameter("Electrolyte viscosity [kg.m-1.s-1]", c_e)
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) parameter_values = pybamm.ParameterValues({"x": np.nan}) with self.assertRaisesRegex(ValueError, "Parameter 'x' not found"): x = pybamm.Parameter("x") parameter_values.process_symbol(x) with self.assertRaisesRegex(ValueError, "possibly a function"): x = pybamm.FunctionParameter("x", {}) parameter_values.process_symbol(x)
def D_e_dimensional(c_e, T): "Dimensional diffusivity in electrolyte" inputs = {"Electrolyte concentration [mol.m-3]": c_e} return pybamm.FunctionParameter("Electrolyte diffusivity [m2.s-1]", inputs)
def test_function_parameter_diff(self): var = pybamm.Variable("var") func = pybamm.FunctionParameter("a", var).diff(var) self.assertEqual(func.diff_variable, var)
def kappa_e_dimensional(c_e, T): "Dimensional electrolyte conductivity" return pybamm.FunctionParameter("Electrolyte conductivity [S.m-1]", c_e)
def external_circuit_function(variables): I = variables["Current [A]"] V = variables["Terminal voltage [V]"] return V + I - pybamm.FunctionParameter("Function", {"Time [s]": pybamm.t})
def test_evaluate_for_shape(self): a = pybamm.Parameter("a") func = pybamm.FunctionParameter("func", 2 * a) self.assertIsInstance(func.evaluate_for_shape(), numbers.Number)
def test_process_integral_broadcast(self): # Test that the x-average of a broadcast, created outside of x-average, gets # processed correctly var = pybamm.Variable("var", domain="test") func = pybamm.x_average(pybamm.FunctionParameter("func", {"var": var})) param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) self.assertEqual(func_proc.id, pybamm.Scalar(2, name="func").id) # test with auxiliary domains var = pybamm.Variable( "var", domain="test", auxiliary_domains={"secondary": "test sec"} ) func = pybamm.x_average(pybamm.FunctionParameter("func", {"var": var})) param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) self.assertEqual( func_proc.id, pybamm.PrimaryBroadcast(pybamm.Scalar(2, name="func"), "test sec").id, ) var = pybamm.Variable( "var", domain="test", auxiliary_domains={"secondary": "test sec", "tertiary": "test tert"}, ) func = pybamm.x_average(pybamm.FunctionParameter("func", {"var": var})) param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) self.assertEqual( func_proc.id, pybamm.FullBroadcast( pybamm.Scalar(2, name="func"), "test sec", "test tert" ).id, ) # this should be the case even if the domain is one of the special domains var = pybamm.Variable("var", domain="negative electrode") func = pybamm.x_average(pybamm.FunctionParameter("func", {"var": var})) param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) self.assertEqual(func_proc.id, pybamm.Scalar(2, name="func").id) # special case for integral of concatenations of broadcasts var_n = pybamm.Variable("var_n", domain="negative electrode") var_s = pybamm.Variable("var_s", domain="separator") var_p = pybamm.Variable("var_p", domain="positive electrode") func_n = pybamm.FunctionParameter("func_n", {"var_n": var_n}) func_s = pybamm.FunctionParameter("func_s", {"var_s": var_s}) func_p = pybamm.FunctionParameter("func_p", {"var_p": var_p}) func = pybamm.x_average(pybamm.concatenation(func_n, func_s, func_p)) param = pybamm.ParameterValues( { "func_n": 2, "func_s": 3, "func_p": 4, "Negative electrode thickness [m]": 1, "Separator thickness [m]": 1, "Positive electrode thickness [m]": 1, } ) func_proc = param.process_symbol(func) self.assertEqual(func_proc.id, pybamm.Scalar(3).id) # with auxiliary domains var_n = pybamm.Variable( "var_n", domain="negative electrode", auxiliary_domains={"secondary": "current collector"}, ) var_s = pybamm.Variable( "var_s", domain="separator", auxiliary_domains={"secondary": "current collector"}, ) var_p = pybamm.Variable( "var_p", domain="positive electrode", auxiliary_domains={"secondary": "current collector"}, ) func_n = pybamm.FunctionParameter("func_n", {"var_n": var_n}) func_s = pybamm.FunctionParameter("func_s", {"var_s": var_s}) func_p = pybamm.FunctionParameter("func_p", {"var_p": var_p}) func = pybamm.x_average(pybamm.concatenation(func_n, func_s, func_p)) param = pybamm.ParameterValues( { "func_n": 2, "func_s": 3, "func_p": 4, "Negative electrode thickness [m]": 1, "Separator thickness [m]": 1, "Positive electrode thickness [m]": 1, } ) func_proc = param.process_symbol(func) self.assertEqual( func_proc.id, pybamm.PrimaryBroadcast(pybamm.Scalar(3), "current collector").id, )
"Positive electrode exchange-current density [A.m-2]", inputs) def j0_p_Ox_dimensional(c_e, T): "Dimensional oxygen positive electrode exchange-current density [A.m-2]" inputs = {"Electrolyte concentration [mol.m-3]": c_e, "Temperature [K]": T} return pybamm.FunctionParameter( "Positive electrode oxygen exchange-current density [A.m-2]", inputs) D_e_typ = D_e_dimensional(c_e_typ, T_ref) rho_typ = rho_dimensional(c_e_typ) mu_typ = mu_dimensional(c_e_typ) inputs = {"Electrolyte concentration [mol.m-3]": pybamm.Scalar(1)} U_n_ref = pybamm.FunctionParameter( "Negative electrode open-circuit potential [V]", inputs) inputs = {"Electrolyte concentration [mol.m-3]": pybamm.Scalar(1)} U_p_ref = pybamm.FunctionParameter( "Positive electrode open-circuit potential [V]", inputs) # -------------------------------------------------------------------------------------- "3. Scales" # concentrations electrolyte_concentration_scale = c_e_typ # electrical potential_scale = R * T_ref / F current_scale = i_typ interfacial_current_scale_n = i_typ / (a_n_dim * L_x) interfacial_current_scale_p = i_typ / (a_p_dim * L_x)