Source code for pybamm.expression_tree.vector_field

"""
VectorField class - a rank-1 tensor field with N components.
"""

from __future__ import annotations

import casadi

import pybamm
from pybamm.expression_tree.tensor_field import TensorField


[docs] class VectorField(TensorField): """ A node in the expression tree representing a vector field. VectorField is a convenience subclass of TensorField for rank-1 tensors with N >= 2 components. Components are stored by integer index; the properties ``lr_field`` and ``tb_field`` are aliases for ``[0]`` and ``[1]``. Parameters ---------- *components : pybamm.Symbol Two or more component symbols, all sharing the same domain. """ # ``disc_state_vector`` is attached by the discretisation for unstructured FV # edge-averaging (None until then) and is not part of the serialised form. __slots__ = ("_disc_state_vector",) _serialise_derived_params = frozenset({"disc_state_vector"}) def __init__( self, *components: pybamm.Symbol, disc_state_vector: pybamm.StateVector | None = None, ) -> None: if len(components) < 2: raise ValueError( f"VectorField requires at least 2 components, got {len(components)}" ) ref_domain = components[0]._domains["primary"] for i, c in enumerate(components[1:], start=1): if c._domains["primary"] != ref_domain: raise ValueError( f"All components must have the same domain: " f"component {i} has {c._domains['primary']}, expected {ref_domain}" ) super().__init__(list(components), domain=ref_domain, name="vector_field") self._disc_state_vector = disc_state_vector @classmethod def _from_json(cls, snippet): # N positional args, not a single list -- override TensorField._from_json. return cls(*snippet["children"]) disc_state_vector = pybamm.expression_tree.legacy_mutation.legacy_property( "_disc_state_vector", "Construct a new VectorField with disc_state_vector=value.", doc="State vector attached by the unstructured FV discretisation (else None).", )
[docs] def with_meshes( self, mesh: pybamm.Mesh, domains: dict[str, list[str]] | None = None, ) -> VectorField: """See :meth:`pybamm.Symbol.with_meshes()`; components carry the meshes too.""" domains = self._domains if domains is None else domains with_components = VectorField( *(component.with_meshes(mesh, domains) for component in self.components), disc_state_vector=self.disc_state_vector, ) return super(VectorField, with_components).with_meshes(mesh, domains)
@property def n_components(self) -> int: """Number of vector components.""" return len(self.components) # ---- aliases for structured-grid directions ---- @property def lr_field(self) -> pybamm.Symbol: """Component 0 (left-right / x).""" return self.components[0] @property def tb_field(self) -> pybamm.Symbol: """Component 1 (top-bottom / y).""" return self.components[1]
[docs] def create_copy( self, new_children: list[pybamm.Symbol] | None = None, perform_simplifications: bool = True, ) -> VectorField: if new_children is None: new_children = [ c.create_copy(perform_simplifications=perform_simplifications) for c in self.components ] return VectorField(*new_children)
def _to_casadi(self, t, y, y_dot, inputs, casadi_symbols): """See :meth:`pybamm.Symbol._to_casadi()`.""" return casadi.vertcat( *self._children_to_casadi(t, y, y_dot, inputs, casadi_symbols) )
[docs] def evaluates_on_edges(self, dimension: str) -> bool: statuses = [c.evaluates_on_edges(dimension) for c in self.components] if all(statuses): return True if not any(statuses): return False raise ValueError( "All VectorField components must either all evaluate on edges " "or none evaluate on edges" )