def test_function_of_one_variable(self): a = pybamm.Symbol("a") funca = pybamm.Function(test_function, a) self.assertEqual(funca.name, "function (test_function)") self.assertEqual(str(funca), "test_function(a)") self.assertEqual(funca.children[0].name, a.name) b = pybamm.Scalar(1) sina = pybamm.Function(np.sin, b) self.assertEqual(sina.evaluate(), np.sin(1)) self.assertEqual(sina.name, "function ({})".format(np.sin.__name__)) c = pybamm.Vector(np.linspace(0, 1)) cosb = pybamm.Function(np.cos, c) np.testing.assert_array_equal(cosb.evaluate(), np.cos(c.evaluate())) var = pybamm.StateVector(slice(0, 100)) y = np.linspace(0, 1, 100)[:, np.newaxis] logvar = pybamm.Function(np.log1p, var) np.testing.assert_array_equal(logvar.evaluate(y=y), np.log1p(y)) # use known_evals np.testing.assert_array_equal( logvar.evaluate(y=y, known_evals={})[0], np.log1p(y) )
def test_special_functions(self): a = pybamm.Array(np.array([1, 2, 3, 4, 5])) self.assert_casadi_equal(pybamm.max(a).to_casadi(), casadi.MX(5), evalf=True) self.assert_casadi_equal(pybamm.min(a).to_casadi(), casadi.MX(1), evalf=True) b = pybamm.Array(np.array([-2])) c = pybamm.Array(np.array([3])) self.assert_casadi_equal(pybamm.Function(np.abs, b).to_casadi(), casadi.MX(2), evalf=True) self.assert_casadi_equal(pybamm.Function(np.abs, c).to_casadi(), casadi.MX(3), evalf=True) for np_fun in [ np.sqrt, np.tanh, np.cosh, np.sinh, np.exp, np.log, np.sign, np.sin, np.cos, np.arccosh, np.arcsinh, ]: self.assert_casadi_equal(pybamm.Function(np_fun, c).to_casadi(), casadi.MX(np_fun(3)), evalf=True)
def test_diff(self): a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) y = np.array([5]) func = pybamm.Function(test_function, a) self.assertEqual(func.diff(a).evaluate(y=y), 2) self.assertEqual(func.diff(func).evaluate(), 1) func = pybamm.sin(a) self.assertEqual(func.evaluate(y=y), np.sin(a.evaluate(y=y))) self.assertEqual(func.diff(a).evaluate(y=y), np.cos(a.evaluate(y=y))) func = pybamm.exp(a) self.assertEqual(func.evaluate(y=y), np.exp(a.evaluate(y=y))) self.assertEqual(func.diff(a).evaluate(y=y), np.exp(a.evaluate(y=y))) # multiple variables func = pybamm.Function(test_multi_var_function, 4 * a, 3 * a) self.assertEqual(func.diff(a).evaluate(y=y), 7) func = pybamm.Function(test_multi_var_function, 4 * a, 3 * b) self.assertEqual(func.diff(a).evaluate(y=np.array([5, 6])), 4) self.assertEqual(func.diff(b).evaluate(y=np.array([5, 6])), 3) func = pybamm.Function(test_multi_var_function_cube, 4 * a, 3 * b) self.assertEqual(func.diff(a).evaluate(y=np.array([5, 6])), 4) self.assertEqual( func.diff(b).evaluate(y=np.array([5, 6])), 3 * 3 * (3 * 6) ** 2 ) # exceptions func = pybamm.Function( test_multi_var_function_cube, 4 * a, 3 * b, derivative="derivative" ) with self.assertRaises(ValueError): func.diff(a)
def test_special_functions(self): a = pybamm.Array(np.array([1, 2, 3, 4, 5])) self.assertEqual(pybamm.max(a).to_casadi(), casadi.SX(5)) self.assertEqual(pybamm.min(a).to_casadi(), casadi.SX(1)) b = pybamm.Array(np.array([-2])) c = pybamm.Array(np.array([3])) self.assertEqual(pybamm.Function(np.abs, b).to_casadi(), casadi.SX(2)) self.assertEqual(pybamm.Function(np.abs, c).to_casadi(), casadi.SX(3))
def test_number_input(self): # with numbers log = pybamm.Function(np.log, 10) self.assertIsInstance(log.children[0], pybamm.Scalar) self.assertEqual(log.evaluate(), np.log(10)) summ = pybamm.Function(test_multi_var_function, 1, 2) self.assertIsInstance(summ.children[0], pybamm.Scalar) self.assertIsInstance(summ.children[1], pybamm.Scalar) self.assertEqual(summ.evaluate(), 3)
def test_convert_differentiated_function(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) def myfunction(x, y): return x + y**3 f = pybamm.Function(myfunction, a, b).diff(a) self.assert_casadi_equal(f.to_casadi(), casadi.MX(1), evalf=True) f = pybamm.Function(myfunction, a, b).diff(b) self.assert_casadi_equal(f.to_casadi(), casadi.MX(3), evalf=True)
def test_convert_scalar_symbols(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) c = pybamm.Scalar(-1) d = pybamm.Scalar(2) e = pybamm.Scalar(3) g = pybamm.Scalar(3.3) self.assertEqual(a.to_casadi(), casadi.MX(0)) self.assertEqual(d.to_casadi(), casadi.MX(2)) # negate self.assertEqual((-b).to_casadi(), casadi.MX(-1)) # absolute value self.assertEqual(abs(c).to_casadi(), casadi.MX(1)) # floor self.assertEqual(pybamm.Floor(g).to_casadi(), casadi.MX(3)) # ceiling self.assertEqual(pybamm.Ceiling(g).to_casadi(), casadi.MX(4)) # function def square_plus_one(x): return x**2 + 1 f = pybamm.Function(square_plus_one, b) self.assertEqual(f.to_casadi(), 2) def myfunction(x, y): return x + y f = pybamm.Function(myfunction, b, d) self.assertEqual(f.to_casadi(), casadi.MX(3)) # use classes to avoid simplification # addition self.assertEqual((pybamm.Addition(a, b)).to_casadi(), casadi.MX(1)) # subtraction self.assertEqual(pybamm.Subtraction(c, d).to_casadi(), casadi.MX(-3)) # multiplication self.assertEqual( pybamm.Multiplication(c, d).to_casadi(), casadi.MX(-2)) # power self.assertEqual(pybamm.Power(c, d).to_casadi(), casadi.MX(1)) # division self.assertEqual(pybamm.Division(b, d).to_casadi(), casadi.MX(1 / 2)) # modulo self.assertEqual(pybamm.Modulo(e, d).to_casadi(), casadi.MX(1)) # minimum and maximum self.assertEqual(pybamm.Minimum(a, b).to_casadi(), casadi.MX(0)) self.assertEqual(pybamm.Maximum(a, b).to_casadi(), casadi.MX(1))
def _function_simplify(self, simplified_children): """ Simplifies the function. Inputs ------ simplified_children: : list A list of simplified children of the function Returns ------- :: pybamm.Scalar() if no children :: pybamm.Function if there are children """ if self.takes_no_params is True: # If self.function() takes no parameters then we can always simplify it return pybamm.Scalar(self.function()) elif isinstance(self.function, pybamm.GetConstantCurrent): # If self.function() is a constant current then simplify to scalar return pybamm.Scalar(self.function.parameters_eval["Current [A]"]) else: return pybamm.Function( self.function, *simplified_children, name=self.name, derivative=self.derivative, differentiated_function=self.differentiated_function )
def _function_diff(self, children, idx): """ Derivative with respect to child number 'idx'. See :meth:`pybamm.Symbol._diff()`. """ # Store differentiated function, needed in case we want to convert to CasADi if self.derivative == "autograd": return Function( autograd.elementwise_grad(self.function, idx), *children, differentiated_function=self.function ) elif self.derivative == "derivative": if len(children) > 1: raise ValueError( """ differentiation using '.derivative()' not implemented for functions with more than one child """ ) else: # keep using "derivative" as derivative return pybamm.Function( self.function.derivative(), *children, derivative="derivative", differentiated_function=self.function )
def test_special_functions(self): a = pybamm.Array(np.array([1, 2, 3, 4, 5])) self.assert_casadi_equal(pybamm.max(a).to_casadi(), casadi.MX(5), evalf=True) self.assert_casadi_equal(pybamm.min(a).to_casadi(), casadi.MX(1), evalf=True) b = pybamm.Array(np.array([-2])) c = pybamm.Array(np.array([3])) self.assert_casadi_equal(pybamm.Function(np.abs, b).to_casadi(), casadi.MX(2), evalf=True) self.assert_casadi_equal(pybamm.Function(np.abs, c).to_casadi(), casadi.MX(3), evalf=True)
def get_interp_fun(variable_name, domain): """ Create a :class:`pybamm.Function` object using the variable, to allow plotting with :class:`'pybamm.QuickPlot'` (interpolate in space to match edges, and then create function to interpolate in time) """ variable = comsol_variables[variable_name] if domain == ["negative electrode"]: comsol_x = comsol_variables["x_n"] elif domain == ["separator"]: comsol_x = comsol_variables["x_s"] elif domain == ["positive electrode"]: comsol_x = comsol_variables["x_p"] elif domain == whole_cell: comsol_x = comsol_variables["x"] # Make sure to use dimensional space pybamm_x = mesh.combine_submeshes(*domain)[0].nodes * L_x variable = interp.interp1d(comsol_x, variable, axis=0, kind=interp_kind)(pybamm_x) def myinterp(t): return interp.interp1d(comsol_t, variable, kind=interp_kind)(t)[:, np.newaxis] # Make sure to use dimensional time fun = pybamm.Function(myinterp, pybamm.t * tau, name=variable_name + "_comsol") fun.domain = domain return fun
def get_interp_fun(variable_name, domain): """ Create a :class:`pybamm.Function` object using the variable, to allow plotting with :class:`pybamm.QuickPlot` (interpolate in space to match edges, and then create function to interpolate in time) """ variable = comsol_variables[variable_name] if domain == ["negative electrode"]: comsol_x = comsol_variables["x_n"] elif domain == ["positive electrode"]: comsol_x = comsol_variables["x_p"] elif domain == whole_cell: comsol_x = comsol_variables["x"] # Make sure to use dimensional space pybamm_x = mesh.combine_submeshes(*domain)[0].nodes * L_x variable = interp.interp1d(comsol_x, variable, axis=0)(pybamm_x) def myinterp(t): try: return interp.interp1d(comsol_t, variable, fill_value="extrapolate", bounds_error=False)(t)[:, np.newaxis] except ValueError as err: raise ValueError( """Failed to interpolate '{}' with time range [{}, {}] at time {}. Original error: {}""".format(variable_name, comsol_t[0], comsol_t[-1], t, err)) # Make sure to use dimensional time fun = pybamm.Function(myinterp, pybamm.t, name=variable_name + "_comsol") fun.domain = domain fun.mesh = mesh.combine_submeshes(*domain) fun.secondary_mesh = None return fun
def _set_dimensional_parameters(self): "Defines the dimensional parameters" self.I_typ = pybamm.Parameter("Typical current [A]") self.Q = pybamm.Parameter("Cell capacity [A.h]") self.C_rate = pybamm.AbsoluteValue(self.I_typ / self.Q) self.n_electrodes_parallel = pybamm.Parameter( "Number of electrodes connected in parallel to make a cell") self.n_cells = pybamm.Parameter( "Number of cells connected in series to make a battery") self.i_typ = pybamm.Function( np.abs, self.I_typ / (self.n_electrodes_parallel * self.geo.A_cc)) self.voltage_low_cut_dimensional = pybamm.Parameter( "Lower voltage cut-off [V]") self.voltage_high_cut_dimensional = pybamm.Parameter( "Upper voltage cut-off [V]") # Current as a function of *dimensional* time. The below is overwritten in # lithium_ion_parameters.py and lead_acid_parameters.py to use the correct # timescale used for non-dimensionalisation. For a base model, the user may # provide the typical timescale as a parameter. self.timescale = pybamm.Parameter("Typical timescale [s]") self.dimensional_current_with_time = pybamm.FunctionParameter( "Current function [A]", {"Time[s]": pybamm.t * self.timescale}) self.dimensional_current_density_with_time = ( self.dimensional_current_with_time / (self.n_electrodes_parallel * self.geo.A_cc))
def test_with_autograd(self): a = pybamm.StateVector(slice(0, 1)) y = np.array([5]) func = pybamm.Function(test_function, a) self.assertEqual(func.diff(a).evaluate(y=y), 2) self.assertEqual(func.diff(func).evaluate(), 1) func = pybamm.Function(auto_np.sin, a) self.assertEqual(func.evaluate(y=y), np.sin(a.evaluate(y=y))) self.assertEqual(func.diff(a).evaluate(y=y), np.cos(a.evaluate(y=y))) func = pybamm.Function(auto_np.exp, a) self.assertEqual(func.evaluate(y=y), np.exp(a.evaluate(y=y))) self.assertEqual(func.diff(a).evaluate(y=y), np.exp(a.evaluate(y=y))) # multiple variables func = pybamm.Function(test_multi_var_function, 4 * a, 3 * a) self.assertEqual(func.diff(a).evaluate(y=y), 7)
def test_function_unnamed(self): t = np.linspace(0, 1) entries = 2 * t interpfun = interp1d(t, entries) fun = pybamm.Function(interpfun, pybamm.t) self.assertEqual( fun.name, "function (<class 'scipy.interpolate.interpolate.interp1d'>)" )
def test_functions(self): y = pybamm.StateVector(slice(0, 4)) u = pybamm.StateVector(slice(0, 2)) v = pybamm.StateVector(slice(2, 4)) const = pybamm.Scalar(1) y0 = np.array([1.0, 2.0, 3.0, 4.0]) func = pybamm.sin(u) jacobian = np.array([[np.cos(1), 0, 0, 0], [0, np.cos(2), 0, 0]]) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.cos(v) jacobian = np.array([[0, 0, -np.sin(3), 0], [0, 0, 0, -np.sin(4)]]) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.sin(3 * u * v) jacobian = np.array( [ [9 * np.cos(9), 0, 3 * np.cos(9), 0], [0, 12 * np.cos(24), 0, 6 * np.cos(24)], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.cos(5 * pybamm.exp(u + v)) jacobian = np.array( [ [ -5 * np.exp(4) * np.sin(5 * np.exp(4)), 0, -5 * np.exp(4) * np.sin(5 * np.exp(4)), 0, ], [ 0, -5 * np.exp(6) * np.sin(5 * np.exp(6)), 0, -5 * np.exp(6) * np.sin(5 * np.exp(6)), ], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) # when child evaluates to number func = pybamm.Sin(const) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(0, dfunc_dy) # several children func = pybamm.Function(test_multi_var_function, 2 * y, 3 * y) jacobian = np.diag(5 * np.ones(4)) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())
def test_convert_differentiated_function(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) # function def sin(x): return anp.sin(x) f = pybamm.Function(sin, b).diff(b) self.assertEqual(f.to_casadi(), casadi.SX(np.cos(1))) def myfunction(x, y): return x + y**3 f = pybamm.Function(myfunction, a, b).diff(a) self.assertEqual(f.to_casadi(), casadi.SX(1)) f = pybamm.Function(myfunction, a, b).diff(b) self.assertEqual(f.to_casadi(), casadi.SX(3))
def test_function_of_multiple_variables(self): a = pybamm.Variable("a") b = pybamm.Parameter("b") func = pybamm.Function(test_multi_var_function, a, b) self.assertEqual(func.name, "function (test_multi_var_function)") self.assertEqual(func.children[0].name, a.name) self.assertEqual(func.children[1].name, b.name) # test eval and diff a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) y = np.array([5, 2]) func = pybamm.Function(test_multi_var_function, a, b) self.assertEqual(func.evaluate(y=y), 7) self.assertEqual(func.diff(a).evaluate(y=y), 1) self.assertEqual(func.diff(b).evaluate(y=y), 1) self.assertEqual(func.diff(func).evaluate(), 1)
def _diff(self, children): """ See :meth:`pybamm.Symbol._diff()`. """ if self.derivative == "autograd": return Function(autograd.elementwise_grad(self.function), *children) elif self.derivative == "derivative": # keep using "derivative" as derivative return pybamm.Function(self.function.derivative(), *children, derivative="derivative")
def test_convert_scalar_symbols(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) c = pybamm.Scalar(-1) d = pybamm.Scalar(2) self.assertEqual(a.to_casadi(), casadi.MX(0)) self.assertEqual(d.to_casadi(), casadi.MX(2)) # negate self.assertEqual((-b).to_casadi(), casadi.MX(-1)) # absolute value self.assertEqual(abs(c).to_casadi(), casadi.MX(1)) # function def sin(x): return np.sin(x) f = pybamm.Function(sin, b) self.assertEqual(f.to_casadi(), casadi.MX(np.sin(1))) def myfunction(x, y): return x + y f = pybamm.Function(myfunction, b, d) self.assertEqual(f.to_casadi(), casadi.MX(3)) # use classes to avoid simplification # addition self.assertEqual((pybamm.Addition(a, b)).to_casadi(), casadi.MX(1)) # subtraction self.assertEqual(pybamm.Subtraction(c, d).to_casadi(), casadi.MX(-3)) # multiplication self.assertEqual( pybamm.Multiplication(c, d).to_casadi(), casadi.MX(-2)) # power self.assertEqual(pybamm.Power(c, d).to_casadi(), casadi.MX(1)) # division self.assertEqual(pybamm.Division(b, d).to_casadi(), casadi.MX(1 / 2)) # minimum and maximum self.assertEqual(pybamm.Minimum(a, b).to_casadi(), casadi.MX(0)) self.assertEqual(pybamm.Maximum(a, b).to_casadi(), casadi.MX(1))
def test_functions(self): y = pybamm.StateVector(slice(0, 8)) u = pybamm.StateVector(slice(0, 2), slice(4, 6)) v = pybamm.StateVector(slice(2, 4), slice(6, 8)) y0 = np.arange(1, 9) const = pybamm.Scalar(1) func = pybamm.sin(u) jacobian = np.array( [ [np.cos(1), 0, 0, 0, 0, 0, 0, 0], [0, np.cos(2), 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, np.cos(5), 0, 0, 0], [0, 0, 0, 0, 0, np.cos(6), 0, 0], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.cos(v) jacobian = np.array( [ [0, 0, -np.sin(3), 0, 0, 0, 0, 0], [0, 0, 0, -np.sin(4), 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, -np.sin(7), 0], [0, 0, 0, 0, 0, 0, 0, -np.sin(8)], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) func = pybamm.sin(3 * u * v) jacobian = np.array( [ [9 * np.cos(9), 0, 3 * np.cos(9), 0, 0, 0, 0, 0], [0, 12 * np.cos(24), 0, 6 * np.cos(24), 0, 0, 0, 0], [0, 0, 0, 0, 21 * np.cos(105), 0, 15 * np.cos(105), 0], [0, 0, 0, 0, 0, 24 * np.cos(144), 0, 18 * np.cos(144)], ] ) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray()) # when child evaluates to number func = pybamm.sin(const) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(0, dfunc_dy) # several children func = pybamm.Function(test_multi_var_function, 2 * y, 3 * y) jacobian = np.diag(5 * np.ones(8)) dfunc_dy = func.jac(y).evaluate(y=y0) np.testing.assert_array_equal(jacobian, dfunc_dy.toarray())
def _set_input_current(self): """Set the input current""" self.dimensional_current_with_time = pybamm.FunctionParameter( "Current function [A]", {"Time [s]": pybamm.t * self.timescale}) self.dimensional_current_density_with_time = ( self.dimensional_current_with_time / (self.n_electrodes_parallel * self.geo.A_cc)) self.current_with_time = (self.dimensional_current_with_time / self.I_typ * pybamm.Function(np.sign, self.I_typ))
def test_multi_var_function_with_parameters(self): def D(a, b): return a * np.exp(b) parameter_values = pybamm.ParameterValues({"a": 3, "b": 0}) a = pybamm.Parameter("a") b = pybamm.Parameter("b") func = pybamm.Function(D, a, b) processed_func = parameter_values.process_symbol(func) self.assertIsInstance(processed_func, pybamm.Function) self.assertEqual(processed_func.evaluate(), 3)
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_convert_scalar_symbols(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) c = pybamm.Scalar(-1) d = pybamm.Scalar(2) self.assertEqual(a.to_casadi(), casadi.SX(0)) self.assertEqual(d.to_casadi(), casadi.SX(2)) # negate self.assertEqual((-b).to_casadi(), casadi.SX(-1)) # absolute value self.assertEqual(abs(c).to_casadi(), casadi.SX(1)) # function def sin(x): return np.sin(x) f = pybamm.Function(sin, b) self.assertEqual(f.to_casadi(), casadi.SX(np.sin(1))) def myfunction(x, y): return x + y f = pybamm.Function(myfunction, b, d) self.assertEqual(f.to_casadi(), casadi.SX(3)) # addition self.assertEqual((a + b).to_casadi(), casadi.SX(1)) # subtraction self.assertEqual((c - d).to_casadi(), casadi.SX(-3)) # multiplication self.assertEqual((c * d).to_casadi(), casadi.SX(-2)) # power self.assertEqual((c**d).to_casadi(), casadi.SX(1)) # division self.assertEqual((b / d).to_casadi(), casadi.SX(1 / 2))
def _function_new_copy(self, children): """Returns a new copy of the function. Inputs ------ children : : list A list of the children of the function Returns ------- : :pybamm.Function A new copy of the function """ return pybamm.Function(self.function, *children, name=self.name, derivative=self.derivative)
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 _function_new_copy(self, children): """Returns a new copy of the function. Inputs ------ children : : list A list of the children of the function Returns ------- : :pybamm.Function A new copy of the function """ return pybamm.simplify_if_constant( pybamm.Function( self.function, *children, name=self.name, derivative=self.derivative, differentiated_function=self.differentiated_function), )
def get_interp_fun_curr_coll(variable_name): """ Interpolate in space to plotting nodes, and then create function to interpolate in time that can be called for plotting at any t. """ comsol_z = comsol_variables[variable_name + "_z"] variable = comsol_variables[variable_name] variable = interp.interp1d(comsol_z, variable, axis=0, kind=interp_kind)( z_interp ) def myinterp(t): return interp.interp1d(comsol_t, variable, kind=interp_kind)(t)[ :, np.newaxis ] # Make sure to use dimensional time fun = pybamm.Function(myinterp, pybamm.t * tau, name=variable_name + "_comsol") fun.domain = "current collector" return fun
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)