def spatial_variable(self, symbol): """ Creates a discretised spatial variable compatible with the FiniteElement method. Parameters ----------- symbol : :class:`pybamm.SpatialVariable` The spatial variable to be discretised. Returns ------- :class:`pybamm.Vector` Contains the discretised spatial variable """ symbol_mesh = self.mesh if symbol.name == "y": vector = pybamm.Vector(symbol_mesh["current collector"] [0].coordinates[0, :][:, np.newaxis]) elif symbol.name == "z": vector = pybamm.Vector(symbol_mesh["current collector"] [0].coordinates[1, :][:, np.newaxis]) else: raise pybamm.GeometryError( "Spatial variable must be 'y' or 'z' not {}".format( symbol.name)) return vector
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 divergence(self, symbol, discretised_symbol, boundary_conditions): """Matrix-vector multiplication to implement the divergence operator. See :meth:`pybamm.SpatialMethod.divergence` """ domain = symbol.domain submesh_list = self.mesh.combine_submeshes(*domain) divergence_matrix = self.divergence_matrix(domain) # check for particle domain if submesh_list[0].coord_sys == "spherical polar": second_dim = len(submesh_list) edges = submesh_list[0].edges # create np.array of repeated submesh[0].nodes r_numpy = np.kron(np.ones(second_dim), submesh_list[0].nodes) r_edges_numpy = np.kron(np.ones(second_dim), edges) r = pybamm.Vector(r_numpy) r_edges = pybamm.Vector(r_edges_numpy) out = (1 / (r ** 2)) * ( divergence_matrix @ ((r_edges ** 2) * discretised_symbol) ) else: out = divergence_matrix @ discretised_symbol return out
def test_base_concatenation(self): a = pybamm.Symbol("a", domain="test a") b = pybamm.Symbol("b", domain="test b") c = pybamm.Symbol("c", domain="test c") conc = pybamm.concatenation(a, b, c) self.assertEqual(conc.name, "concatenation") self.assertEqual(str(conc), "concatenation(a, b, c)") self.assertIsInstance(conc.children[0], pybamm.Symbol) self.assertEqual(conc.children[0].name, "a") self.assertEqual(conc.children[1].name, "b") self.assertEqual(conc.children[2].name, "c") d = pybamm.Vector([2], domain="test a") e = pybamm.Vector([1], domain="test b") f = pybamm.Vector([3], domain="test c") conc2 = pybamm.concatenation(d, e, f) with self.assertRaises(TypeError): conc2.evaluate() # trying to concatenate non-pybamm symbols with self.assertRaises(TypeError): pybamm.concatenation(1, 2) # concatenation of length 0 with self.assertRaisesRegex(ValueError, "Cannot create empty concatenation"): pybamm.concatenation() # concatenation of lenght 1 self.assertEqual(pybamm.concatenation(a), a)
def test_outer(self): # Outer class v = pybamm.Vector(np.ones(5), domain="current collector") w = pybamm.Vector(2 * np.ones(3), domain="test") outer = pybamm.Outer(v, w) np.testing.assert_array_equal(outer.evaluate(), 2 * np.ones((15, 1))) self.assertEqual(outer.domain, w.domain) self.assertEqual( str(outer), "outer(Column vector of length 5, Column vector of length 3)") # outer function # if there is no domain clash, normal multiplication is retured u = pybamm.Vector(np.linspace(0, 1, 5)) outer = pybamm.outer(u, v) self.assertIsInstance(outer, pybamm.Multiplication) np.testing.assert_array_equal(outer.evaluate(), u.evaluate()) # otherwise, Outer class is returned outer_fun = pybamm.outer(v, w) outer_class = pybamm.Outer(v, w) self.assertEqual(outer_fun.id, outer_class.id) # failures y = pybamm.StateVector(slice(10)) with self.assertRaisesRegex( TypeError, "right child must only contain SpatialVariable and scalars"): pybamm.Outer(v, y) with self.assertRaises(NotImplementedError): outer_fun.diff(None)
def test_simplify_outer(self): v = pybamm.Vector(np.ones(5), domain="current collector") w = pybamm.Vector(2 * np.ones(3), domain="test") outer_simp = pybamm.Outer(v, w).simplify() self.assertIsInstance(outer_simp, pybamm.Vector) np.testing.assert_array_equal(outer_simp.evaluate(), 2 * np.ones( (15, 1)))
def test_grad_1plus1d(self): mesh = get_1p1d_mesh_for_testing() spatial_methods = {"macroscale": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) a = pybamm.Variable("a", domain=["negative electrode"]) b = pybamm.Variable("b", domain=["separator"]) c = pybamm.Variable("c", domain=["positive electrode"]) var = pybamm.Concatenation(a, b, c) boundary_conditions = { var.id: { "left": (pybamm.Vector(np.linspace(0, 1, 15)), "Neumann"), "right": (pybamm.Vector(np.linspace(0, 1, 15)), "Neumann"), } } disc.bcs = boundary_conditions disc.set_variable_slices([var]) grad_eqn_disc = disc.process_symbol(pybamm.grad(var)) # Evaulate combined_submesh = mesh.combine_submeshes(*var.domain) linear_y = np.outer(np.linspace(0, 1, 15), combined_submesh[0].nodes).reshape(-1, 1) expected = np.outer(np.linspace(0, 1, 15), np.ones_like(combined_submesh[0].edges)).reshape( -1, 1) np.testing.assert_array_almost_equal( grad_eqn_disc.evaluate(None, linear_y), expected)
def broadcast(self, symbol, domain, auxiliary_domains, broadcast_type): """ Broadcast symbol to a specified domain. Parameters ---------- symbol : :class:`pybamm.Symbol` The symbol to be broadcasted domain : iterable of strings The domain to broadcast to auxiliary_domains : dict of strings The auxiliary domains for broadcasting broadcast_type : str The type of broadcast: 'primary to node', 'primary to edges', 'secondary to nodes', 'secondary to edges', 'full to nodes' or 'full to edges' Returns ------- broadcasted_symbol: class: `pybamm.Symbol` The discretised symbol of the correct size for the spatial method """ primary_domain_size = sum(self.mesh[dom].npts_for_broadcast_to_nodes for dom in domain) secondary_domain_size = self._get_auxiliary_domain_repeats( {k: v for k, v in auxiliary_domains.items() if k == "secondary"}) auxiliary_domains_size = self._get_auxiliary_domain_repeats( auxiliary_domains) full_domain_size = primary_domain_size * auxiliary_domains_size if broadcast_type.endswith("to edges"): # add one point to each domain for broadcasting to edges primary_domain_size += 1 full_domain_size = primary_domain_size * auxiliary_domains_size secondary_domain_size += 1 if broadcast_type.startswith("primary"): # Make copies of the child stacked on top of each other sub_vector = np.ones((primary_domain_size, 1)) if symbol.shape_for_testing == (): out = symbol * pybamm.Vector(sub_vector) else: # Repeat for secondary points matrix = csr_matrix( kron(eye(symbol.shape_for_testing[0]), sub_vector)) out = pybamm.Matrix(matrix) @ symbol out.domain = domain elif broadcast_type.startswith("secondary"): # Make copies of the child stacked on top of each other identity = eye(symbol.shape[0]) matrix = vstack([identity for _ in range(secondary_domain_size)]) out = pybamm.Matrix(matrix) @ symbol elif broadcast_type.startswith("full"): out = symbol * pybamm.Vector(np.ones(full_domain_size), domain=domain) out.auxiliary_domains = auxiliary_domains.copy() return out
def test_vector_zero_simplify(self): a1 = pybamm.Scalar(0) v1 = pybamm.Vector(np.zeros(10)) a2 = pybamm.Scalar(1) v2 = pybamm.Vector(np.ones(10)) for expr in [a1 * v1, v1 * a1, a2 * v1, v1 * a2, a1 * v2, v2 * a1, v1 * v2]: self.assertIsInstance(expr.simplify(), pybamm.Vector) np.testing.assert_array_equal(expr.simplify().entries, np.zeros((10, 1)))
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 broadcast(self, symbol, domain, auxiliary_domains, broadcast_type): """ Broadcast symbol to a specified domain. Parameters ---------- symbol : :class:`pybamm.Symbol` The symbol to be broadcasted domain : iterable of strings The domain to broadcast to broadcast_type : str The type of broadcast, either: 'primary' or 'full' Returns ------- broadcasted_symbol: class: `pybamm.Symbol` The discretised symbol of the correct size for the spatial method """ primary_domain_size = sum(self.mesh[dom][0].npts_for_broadcast for dom in domain) full_domain_size = sum(subdom.npts_for_broadcast for dom in domain for subdom in self.mesh[dom]) if broadcast_type == "primary": # Make copies of the child stacked on top of each other sub_vector = np.ones((primary_domain_size, 1)) if symbol.shape_for_testing == (): out = symbol * pybamm.Vector(sub_vector) else: # Repeat for secondary points matrix = csr_matrix( kron(eye(symbol.shape_for_testing[0]), sub_vector)) out = pybamm.Matrix(matrix) @ symbol out.domain = domain elif broadcast_type == "secondary": secondary_domain_size = sum( self.mesh[dom][0].npts_for_broadcast for dom in auxiliary_domains["secondary"]) kron_size = full_domain_size // primary_domain_size # Symbol may be on edges so need to calculate size carefully symbol_primary_size = symbol.shape[0] // kron_size # Make copies of the child stacked on top of each other identity = eye(symbol_primary_size) sub_matrix = vstack( [identity for _ in range(secondary_domain_size)]) # Repeat for secondary points matrix = csr_matrix(kron(eye(kron_size), sub_matrix)) out = pybamm.Matrix(matrix) @ symbol elif broadcast_type == "full": out = symbol * pybamm.Vector(np.ones(full_domain_size), domain=domain) out.auxiliary_domains = auxiliary_domains return out
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 test_numpy_concatenation_vectors(self): # with entries y = np.linspace(0, 1, 15)[:, np.newaxis] a = pybamm.Vector(y[:5]) b = pybamm.Vector(y[5:9]) c = pybamm.Vector(y[9:]) conc = pybamm.NumpyConcatenation(a, b, c) np.testing.assert_array_equal(conc.evaluate(None, y), y) # with y_slice a = pybamm.StateVector(slice(0, 10)) b = pybamm.StateVector(slice(10, 15)) c = pybamm.StateVector(slice(15, 23)) conc = pybamm.NumpyConcatenation(a, b, c) y = np.linspace(0, 1, 23)[:, np.newaxis] np.testing.assert_array_equal(conc.evaluate(None, y), y)
def test_numpy_concatenation_vector_scalar(self): # with entries 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) np.testing.assert_array_equal( conc.evaluate(y=y), np.concatenate([y, np.array([[16]]), np.array([[3]])])) # with y_slice a = pybamm.StateVector(slice(0, 10)) conc = pybamm.NumpyConcatenation(a, b, c) np.testing.assert_array_equal( conc.evaluate(y=y), np.concatenate([y, np.array([[16]]), np.array([[3]])])) # with time b = pybamm.t conc = pybamm.NumpyConcatenation(a, b, c) np.testing.assert_array_equal( conc.evaluate(16, y), np.concatenate([y, np.array([[16]]), np.array([[3]])]))
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"] cc_npts = mesh["current collector"][0].npts curr_coll_vector = pybamm.Vector(np.ones(cc_npts), domain="current collector") a = 2 * pybamm.PrimaryBroadcast(curr_coll_vector, a_dom) b = pybamm.PrimaryBroadcast(curr_coll_vector, b_dom) c = 3 * pybamm.PrimaryBroadcast(curr_coll_vector, c_dom) conc = pybamm.Concatenation(a, b, c) disc.set_variable_slices([conc]) conc_disc = disc.process_symbol(conc) jac = conc_disc.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 spatial_variable(self, symbol): """ Convert a :class:`pybamm.SpatialVariable` node to a linear algebra object that can be evaluated (here, a :class:`pybamm.Vector` on either the nodes or the edges). Parameters ----------- symbol : :class:`pybamm.SpatialVariable` The spatial variable to be discretised. Returns ------- :class:`pybamm.Vector` Contains the discretised spatial variable """ symbol_mesh = self.mesh.combine_submeshes(*symbol.domain) repeats = self._get_auxiliary_domain_repeats(symbol.auxiliary_domains) if symbol.evaluates_on_edges("primary"): entries = np.tile(symbol_mesh.edges, repeats) else: entries = np.tile(symbol_mesh.nodes, repeats) return pybamm.Vector(entries, domain=symbol.domain, auxiliary_domains=symbol.auxiliary_domains)
def test_symbol_evaluates_to_constant_number(self): a = pybamm.Scalar(3) self.assertTrue(a.evaluates_to_constant_number()) a = pybamm.Parameter("a") self.assertFalse(a.evaluates_to_constant_number()) a = pybamm.Variable("a") self.assertFalse(a.evaluates_to_constant_number()) a = pybamm.Scalar(3) - 2 self.assertTrue(a.evaluates_to_constant_number()) a = pybamm.Vector(np.ones(5)) self.assertFalse(a.evaluates_to_constant_number()) a = pybamm.Matrix(np.ones((4, 6))) self.assertFalse(a.evaluates_to_constant_number()) a = pybamm.StateVector(slice(0, 10)) self.assertFalse(a.evaluates_to_constant_number()) # Time variable returns true a = 3 * pybamm.t + 2 self.assertFalse(a.evaluates_to_constant_number())
def simplify_if_constant(symbol, keep_domains=False): """ Utility function to simplify an expression tree if it evalutes to a constant scalar, vector or matrix """ if keep_domains is True: domain = symbol.domain auxiliary_domains = symbol.auxiliary_domains else: domain = None auxiliary_domains = None if symbol.is_constant(): result = symbol.evaluate_ignoring_errors() if result is not None: if (isinstance(result, numbers.Number) or (isinstance(result, np.ndarray) and result.ndim == 0) or isinstance(result, np.bool_)): return pybamm.Scalar(result) elif isinstance(result, np.ndarray) or issparse(result): if result.ndim == 1 or result.shape[1] == 1: return pybamm.Vector(result, domain=domain, auxiliary_domains=auxiliary_domains) else: # Turn matrix of zeros into sparse matrix if isinstance(result, np.ndarray) and np.all(result == 0): result = csr_matrix(result) return pybamm.Matrix(result, domain=domain, auxiliary_domains=auxiliary_domains) return symbol
def _binary_simplify(self, left, right): """ See :meth:`pybamm.BinaryOperator.simplify()`. """ # anything multiplied by a scalar zero returns a scalar zero if is_scalar_zero(left): if isinstance(right, pybamm.Array): return pybamm.Array(np.zeros(right.shape)) else: return pybamm.Scalar(0) if is_scalar_zero(right): if isinstance(left, pybamm.Array): return pybamm.Array(np.zeros(left.shape)) else: return pybamm.Scalar(0) # if one of the children is a zero matrix, we have to be careful about shapes if is_matrix_zero(left) or is_matrix_zero(right): shape = (left * right).shape if len(shape) == 1 or shape[1] == 1: return pybamm.Vector(np.zeros(shape)) else: return pybamm.Matrix(csr_matrix(shape)) # anything multiplied by a scalar one returns itself if is_one(left): return right if is_one(right): return left return pybamm.simplify_multiplication_division(self.__class__, left, right)
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_base_concatenation(self): a = pybamm.Symbol("a") b = pybamm.Symbol("b") c = pybamm.Symbol("c") conc = pybamm.Concatenation(a, b, c) self.assertEqual(conc.name, "concatenation") self.assertIsInstance(conc.children[0], pybamm.Symbol) self.assertEqual(conc.children[0].name, "a") self.assertEqual(conc.children[1].name, "b") self.assertEqual(conc.children[2].name, "c") d = pybamm.Vector(np.array([2])) e = pybamm.Vector(np.array([1])) f = pybamm.Vector(np.array([3])) conc2 = pybamm.Concatenation(d, e, f) with self.assertRaises(TypeError): conc2.evaluate()
def test_index(self): vec = pybamm.Vector(np.array([1, 2, 3, 4, 5])) # with integer ind = vec[3] self.assertIsInstance(ind, pybamm.Index) self.assertEqual(ind.slice, slice(3, 4)) self.assertEqual(ind.evaluate(), 4) # with slice ind = vec[1:3] self.assertIsInstance(ind, pybamm.Index) self.assertEqual(ind.slice, slice(1, 3)) np.testing.assert_array_equal(ind.evaluate(), np.array([[2], [3]])) # with only stop slice ind = vec[:3] self.assertIsInstance(ind, pybamm.Index) self.assertEqual(ind.slice, slice(3)) np.testing.assert_array_equal(ind.evaluate(), np.array([[1], [2], [3]])) # errors with self.assertRaisesRegex(TypeError, "index must be integer or slice"): pybamm.Index(vec, 0.0) with self.assertRaisesRegex(ValueError, "slice size exceeds child size"): pybamm.Index(vec, 5)
def boundary_value_or_flux(self, symbol, discretised_child, bcs=None): """ Returns the average value of the symbol over the negative tab ("negative tab") or the positive tab ("positive tab") in the Finite Element Method. Overwrites the default :meth:`pybamm.SpatialMethod.boundary_value` """ # Return average value on the negative tab for "negative tab" and positive tab # for "positive tab" if isinstance(symbol, pybamm.BoundaryValue): # get integration_vector if symbol.side == "negative tab": region = "negative tab" elif symbol.side == "positive tab": region = "positive tab" domain = symbol.children[0].domain[0] integration_vector = self.boundary_integral_vector(domain, region=region) # divide integration weights by (numerical) tab width to give average value boundary_val_vector = integration_vector / ( integration_vector @ pybamm.Vector( np.ones(integration_vector.shape[1]))) elif isinstance(symbol, pybamm.BoundaryGradient): raise NotImplementedError # Return boundary value with domain given by symbol boundary_value = boundary_val_vector @ discretised_child boundary_value.domain = symbol.domain return boundary_value
def test_symbol_evaluates_to_number(self): a = pybamm.Scalar(3) self.assertTrue(a.evaluates_to_number()) a = pybamm.Parameter("a") self.assertFalse(a.evaluates_to_number()) a = pybamm.Scalar(3) * pybamm.Time() self.assertTrue(a.evaluates_to_number()) # highlight difference between this function and isinstance(a, Scalar) self.assertNotIsInstance(a, pybamm.Scalar) a = pybamm.Variable("a") self.assertFalse(a.evaluates_to_number()) a = pybamm.Scalar(3) - 2 self.assertTrue(a.evaluates_to_number()) a = pybamm.Vector(np.ones(5)) self.assertFalse(a.evaluates_to_number()) a = pybamm.Matrix(np.ones((4, 6))) self.assertFalse(a.evaluates_to_number()) a = pybamm.StateVector(slice(0, 10)) self.assertFalse(a.evaluates_to_number()) # Time variable returns true a = 3 * pybamm.t + 2 self.assertTrue(a.evaluates_to_number())
def test_simplify_inner(self): a1 = pybamm.Scalar(0) M1 = pybamm.Matrix(np.zeros((10, 10))) v1 = pybamm.Vector(np.ones(10)) a2 = pybamm.Scalar(1) M2 = pybamm.Matrix(np.ones((10, 10))) a3 = pybamm.Scalar(3) np.testing.assert_array_equal( pybamm.inner(a1, M2).simplify().evaluate().toarray(), M1.entries ) self.assertEqual(pybamm.inner(a1, a2).simplify().evaluate(), 0) np.testing.assert_array_equal( pybamm.inner(M2, a1).simplify().evaluate().toarray(), M1.entries ) self.assertEqual(pybamm.inner(a2, a1).simplify().evaluate(), 0) np.testing.assert_array_equal( pybamm.inner(M1, a3).simplify().evaluate().toarray(), M1.entries ) np.testing.assert_array_equal( pybamm.inner(v1, a3).simplify().evaluate(), 3 * v1.entries ) self.assertEqual(pybamm.inner(a2, a3).simplify().evaluate(), 3) self.assertEqual(pybamm.inner(a3, a2).simplify().evaluate(), 3) self.assertEqual(pybamm.inner(a3, a3).simplify().evaluate(), 9)
def test_simplify_kron(self): A = pybamm.Matrix(np.eye(2)) b = pybamm.Vector(np.array([[4], [5]])) kron = pybamm.Kron(A, b) kron_simp = kron.simplify() self.assertIsInstance(kron_simp, pybamm.Matrix) np.testing.assert_array_equal(kron_simp.evaluate().toarray(), np.kron(A.entries, b.entries))
def test_failure(self): t = np.ones(25) y = np.ones((120, 25)) mat = pybamm.Vector(np.ones(120), domain=["negative particle"]) disc = tests.get_p2d_discretisation_for_testing() with self.assertRaisesRegex( ValueError, "3D variable shape does not match domain shape"): pybamm.ProcessedVariable(mat, t, y, disc.mesh)
def test_index(self): vec = pybamm.StateVector(slice(0, 5)) ind = pybamm.Index(vec, 3) jac = ind.jac(vec).evaluate(y=np.linspace(0, 2, 5)).toarray() np.testing.assert_array_equal(jac, np.array([[0, 0, 0, 1, 0]])) # jac of ind of something that isn't a StateVector should return zeros const_vec = pybamm.Vector(np.ones(3)) ind = pybamm.Index(const_vec, 2) jac = ind.jac(vec).evaluate(y=np.linspace(0, 2, 5)).toarray() np.testing.assert_array_equal(jac, np.array([[0, 0, 0, 0, 0]]))
def __init__(self, *children): children = list(children) # Turn objects that evaluate to scalars to objects that evaluate to vectors, # so that we can concatenate them for i, child in enumerate(children): if child.evaluates_to_number(): children[i] = child * pybamm.Vector(np.array([1])) super().__init__(*children, name="numpy concatenation", check_domain=False, concat_fun=np.concatenate)
def test_sparse_divide(self): row = np.array([0, 3, 1, 0]) col = np.array([0, 3, 1, 2]) data = np.array([4, 5, 7, 9]) S1 = coo_matrix((data, (row, col)), shape=(4, 5)) pybammS1 = pybamm.Matrix(S1) v1 = np.ones((4, 1)) pybammv1 = pybamm.Vector(v1) np.testing.assert_array_equal( (pybammS1 / pybammv1).evaluate().toarray(), S1.toarray() / v1 )