def test_numpy_domain_concatenation(self): # create mesh mesh = get_mesh_for_testing() a_dom = ["negative electrode"] b_dom = ["positive electrode"] a = 2 * pybamm.Vector(np.ones_like(mesh[a_dom[0]].nodes), domain=a_dom) b = pybamm.Vector(np.ones_like(mesh[b_dom[0]].nodes), domain=b_dom) # concatenate them the "wrong" way round to check they get reordered correctly conc = pybamm.DomainConcatenation([b, a], mesh) np.testing.assert_array_equal( conc.evaluate(), np.concatenate([ np.full(mesh[a_dom[0]].npts, 2), np.full(mesh[b_dom[0]].npts, 1) ])[:, np.newaxis], ) # test size and shape self.assertEqual(conc.size, mesh[a_dom[0]].npts + mesh[b_dom[0]].npts) self.assertEqual(conc.shape, (mesh[a_dom[0]].npts + mesh[b_dom[0]].npts, 1)) # check the reordering in case a child vector has to be split up a_dom = ["separator"] b_dom = ["negative electrode", "positive electrode"] a = 2 * pybamm.Vector(np.ones_like(mesh[a_dom[0]].nodes), domain=a_dom) b = pybamm.Vector( np.concatenate([ np.full(mesh[b_dom[0]].npts, 1), np.full(mesh[b_dom[1]].npts, 3) ])[:, np.newaxis], domain=b_dom, ) conc = pybamm.DomainConcatenation([a, b], mesh) np.testing.assert_array_equal( conc.evaluate(), np.concatenate([ np.full(mesh[b_dom[0]].npts, 1), np.full(mesh[a_dom[0]].npts, 2), np.full(mesh[b_dom[1]].npts, 3), ])[:, np.newaxis], ) # test size and shape self.assertEqual( conc.size, mesh[b_dom[0]].npts + mesh[a_dom[0]].npts + mesh[b_dom[1]].npts, ) self.assertEqual( conc.shape, ( mesh[b_dom[0]].npts + mesh[a_dom[0]].npts + mesh[b_dom[1]].npts, 1, ), )
def test_jac_of_domain_concatenation(self): # create mesh mesh = get_mesh_for_testing() y = pybamm.StateVector(slice(0, 100)) # Jacobian of a DomainConcatenation of constants is a zero matrix of the # appropriate size a_dom = ["negative electrode"] b_dom = ["separator"] c_dom = ["positive electrode"] a_npts = mesh[a_dom[0]].npts b_npts = mesh[b_dom[0]].npts c_npts = mesh[c_dom[0]].npts a = 2 * pybamm.Vector(np.ones(a_npts), domain=a_dom) b = pybamm.Vector(np.ones(b_npts), domain=b_dom) c = 3 * pybamm.Vector(np.ones(c_npts), domain=c_dom) conc = pybamm.DomainConcatenation([a, b, c], mesh) jac = conc.jac(y).evaluate().toarray() np.testing.assert_array_equal(jac, np.zeros((100, 100))) # Jacobian of a DomainConcatenation of StateVectors a = 2 * pybamm.StateVector(slice(0, a_npts), domain=a_dom) b = pybamm.StateVector(slice(a_npts, a_npts + b_npts), domain=b_dom) c = 3 * pybamm.StateVector( slice(a_npts + b_npts, a_npts + b_npts + c_npts), domain=c_dom ) conc = pybamm.DomainConcatenation([a, b, c], mesh) y0 = np.ones(100) jac = conc.jac(y).evaluate(y=y0).toarray() np.testing.assert_array_equal( jac, np.diag( np.concatenate( [2 * np.ones(a_npts), np.ones(b_npts), 3 * np.ones(c_npts)] ) ), ) # multi=domain case not implemented a = 2 * pybamm.StateVector(slice(0, a_npts), domain=a_dom) b = pybamm.StateVector( slice(a_npts, a_npts + b_npts + c_npts), domain=b_dom + c_dom ) conc = pybamm.DomainConcatenation([a, b], mesh) with self.assertRaisesRegex( NotImplementedError, "jacobian only implemented for when each child has" ): conc.jac(y)
def concatenation(self, disc_children): """Discrete concatenation, taking `edge_to_node` for children that evaluate on edges. See :meth:`pybamm.SpatialMethod.concatenation` """ for idx, child in enumerate(disc_children): n_nodes = sum( len(mesh.nodes) for mesh in self.mesh.combine_submeshes(*child.domain) ) n_edges = sum( len(mesh.edges) for mesh in self.mesh.combine_submeshes(*child.domain) ) child_size = child.size if child_size != n_nodes: # Average any children that evaluate on the edges (size n_edges) to # evaluate on nodes instead, so that concatenation works properly if child_size == n_edges: disc_children[idx] = self.edge_to_node(child) else: raise pybamm.ShapeError( """ child must have size n_nodes (number of nodes in the mesh) or n_edges (number of edges in the mesh) """ ) return pybamm.DomainConcatenation(disc_children, self.mesh)
def test_concatenations(self): y = np.linspace(0, 1, 10)[:, np.newaxis] a = pybamm.Vector(y) b = pybamm.Scalar(16) c = pybamm.Scalar(3) conc = pybamm.NumpyConcatenation(a, b, c) self.assert_casadi_equal(conc.to_casadi(), casadi.MX(conc.evaluate()), evalf=True) # Domain concatenation mesh = get_mesh_for_testing() a_dom = ["negative electrode"] b_dom = ["separator"] a = 2 * pybamm.Vector(np.ones_like(mesh[a_dom[0]].nodes), domain=a_dom) b = pybamm.Vector(np.ones_like(mesh[b_dom[0]].nodes), domain=b_dom) conc = pybamm.DomainConcatenation([b, a], mesh) self.assert_casadi_equal(conc.to_casadi(), casadi.MX(conc.evaluate()), evalf=True) # 2d disc = get_1p1d_discretisation_for_testing() a = pybamm.Variable("a", domain=a_dom) b = pybamm.Variable("b", domain=b_dom) conc = pybamm.Concatenation(a, b) disc.set_variable_slices([conc]) expr = disc.process_symbol(conc) y = casadi.SX.sym("y", expr.size) x = expr.to_casadi(None, y) f = casadi.Function("f", [x], [x]) y_eval = np.linspace(0, 1, expr.size) self.assert_casadi_equal(f(y_eval), casadi.SX(expr.evaluate(y=y_eval)))
def test_domain_error(self): a = pybamm.Symbol("a") b = pybamm.Symbol("b") with self.assertRaisesRegex( pybamm.DomainError, "Cannot concatenate child 'a' with empty domain"): pybamm.DomainConcatenation([a, b], None)
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 concatenation(self, disc_children): """Discrete concatenation object. Parameters ---------- disc_children : list List of discretised children Returns ------- :class:`pybamm.DomainConcatenation` Concatenation of the discretised children """ return pybamm.DomainConcatenation(disc_children, self.mesh)
def test_domain_concatenation_domains(self): mesh = get_mesh_for_testing() # ensure concatenated domains are sorted correctly a = pybamm.Symbol("a", domain=["negative electrode"]) b = pybamm.Symbol("b", domain=["separator", "positive electrode"]) c = pybamm.Symbol("c", domain=["negative particle"]) conc = pybamm.DomainConcatenation([c, a, b], mesh) self.assertEqual( conc.domain, [ "negative electrode", "separator", "positive electrode", "negative particle", ], )
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 test_domain_concatenation_simplify(self): # create discretisation disc = get_discretisation_for_testing() mesh = disc.mesh a_dom = ["negative electrode"] b_dom = ["positive electrode"] a = 2 * pybamm.Vector(np.ones_like(mesh[a_dom[0]].nodes), domain=a_dom) b = pybamm.Vector(np.ones_like(mesh[b_dom[0]].nodes), domain=b_dom) conc = pybamm.DomainConcatenation([a, b], mesh) conc_simp = conc.simplify() # should be simplified to a vector self.assertIsInstance(conc_simp, pybamm.Vector) np.testing.assert_array_equal( conc_simp.evaluate(), np.concatenate([ np.full((mesh[a_dom[0]].npts, 1), 2), np.full((mesh[b_dom[0]].npts, 1), 1), ]), )
def test_jac_of_domain_concatenation(self): # create mesh disc = get_1p1d_discretisation_for_testing() mesh = disc.mesh y = pybamm.StateVector(slice(0, 1500)) # Jacobian of a DomainConcatenation of constants is a zero matrix of the # appropriate size a_dom = ["negative electrode"] b_dom = ["separator"] c_dom = ["positive electrode"] a_npts = mesh[a_dom[0]][0].npts b_npts = mesh[b_dom[0]][0].npts c_npts = mesh[c_dom[0]][0].npts cc_npts = mesh["current collector"][0].npts curr_coll_vector = pybamm.Vector(np.ones(cc_npts), domain="current collector") a = 2 * pybamm.Outer(curr_coll_vector, pybamm.Vector(np.ones(a_npts), domain=a_dom)) b = pybamm.Outer(curr_coll_vector, pybamm.Vector(np.ones(b_npts), domain=b_dom)) c = 3 * pybamm.Outer(curr_coll_vector, pybamm.Vector(np.ones(c_npts), domain=c_dom)) conc = pybamm.DomainConcatenation([a, b, c], mesh) jac = conc.jac(y).evaluate().toarray() np.testing.assert_array_equal(jac, np.zeros((1500, 1500))) # Jacobian of a DomainConcatenation of StateVectors a = pybamm.Variable("a", domain=a_dom) b = pybamm.Variable("b", domain=b_dom) c = pybamm.Variable("c", domain=c_dom) conc = pybamm.Concatenation(a, b, c) disc.set_variable_slices([conc]) conc_disc = disc.process_symbol(conc) y0 = np.ones(1500) jac = conc_disc.jac(y).evaluate(y=y0).toarray() np.testing.assert_array_equal(jac, np.eye(1500))
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_domain_concatenation(self): disc = get_discretisation_for_testing() mesh = disc.mesh a_dom = ["negative electrode"] b_dom = ["positive electrode"] a_pts = mesh[a_dom[0]].npts b_pts = mesh[b_dom[0]].npts a = pybamm.StateVector(slice(0, a_pts), domain=a_dom) b = pybamm.StateVector(slice(a_pts, a_pts + b_pts), domain=b_dom) y = np.empty((a_pts + b_pts, 1)) for i in range(len(y)): y[i] = i # concatenate them the "wrong" way round to check they get reordered correctly expr = pybamm.DomainConcatenation([b, a], mesh) constant_symbols = OrderedDict() variable_symbols = OrderedDict() pybamm.find_symbols(expr, constant_symbols, variable_symbols) self.assertEqual(list(variable_symbols.keys())[0], b.id) self.assertEqual(list(variable_symbols.keys())[1], a.id) self.assertEqual(list(variable_symbols.keys())[2], expr.id) var_a = pybamm.id_to_python_variable(a.id) var_b = pybamm.id_to_python_variable(b.id) self.assertEqual(len(constant_symbols), 0) self.assertEqual( list(variable_symbols.values())[2], "np.concatenate(({}[0:{}],{}[0:{}]))".format( var_a, a_pts, var_b, b_pts), ) evaluator = pybamm.EvaluatorPython(expr) result = evaluator.evaluate(y=y) np.testing.assert_allclose(result, expr.evaluate(y=y)) # check that concatenating a single domain is consistent expr = pybamm.DomainConcatenation([a], mesh) evaluator = pybamm.EvaluatorPython(expr) result = evaluator.evaluate(y=y) np.testing.assert_allclose(result, expr.evaluate(y=y)) # check the reordering in case a child vector has to be split up a_dom = ["separator"] b_dom = ["negative electrode", "positive electrode"] b0_pts = mesh[b_dom[0]].npts a0_pts = mesh[a_dom[0]].npts b1_pts = mesh[b_dom[1]].npts a = pybamm.StateVector(slice(0, a0_pts), domain=a_dom) b = pybamm.StateVector(slice(a0_pts, a0_pts + b0_pts + b1_pts), domain=b_dom) y = np.empty((a0_pts + b0_pts + b1_pts, 1)) for i in range(len(y)): y[i] = i var_a = pybamm.id_to_python_variable(a.id) var_b = pybamm.id_to_python_variable(b.id) expr = pybamm.DomainConcatenation([a, b], mesh) constant_symbols = OrderedDict() variable_symbols = OrderedDict() pybamm.find_symbols(expr, constant_symbols, variable_symbols) b0_str = "{}[0:{}]".format(var_b, b0_pts) a0_str = "{}[0:{}]".format(var_a, a0_pts) b1_str = "{}[{}:{}]".format(var_b, b0_pts, b0_pts + b1_pts) self.assertEqual(len(constant_symbols), 0) self.assertEqual( list(variable_symbols.values())[2], "np.concatenate(({},{},{}))".format(b0_str, a0_str, b1_str), ) evaluator = pybamm.EvaluatorPython(expr) result = evaluator.evaluate(y=y) np.testing.assert_allclose(result, expr.evaluate(y=y))
def test_domain_error(self): a = pybamm.Symbol("a") b = pybamm.Symbol("b") with self.assertRaisesRegex(pybamm.DomainError, "domain cannot be empty"): pybamm.DomainConcatenation([a, b], None)