Unstructured Finite Volume#

class pybamm.FiniteVolumeUnstructured(options=None)[source]#

Cell-centered finite volume method on unstructured meshes.

Supports triangles and quadrilaterals (2D), tetrahedra and hexahedra (3D). Operators:

  • Laplacian – Two-Point Flux Approximation (TPFA) with an implicit non-orthogonal correction: the face normal is split as \(\hat n = \alpha \hat e + \mathbf{k}\) along the unit centroid-to-centroid direction \(\hat e\), so the normal derivative is \(\alpha (u_j - u_i)/d + \mathbf{k}\cdot\nabla u_f\) with the cross term taken from the Green-Gauss gradient. On orthogonal meshes \(\mathbf{k} = 0\) and this is plain TPFA.

  • Gradient – Green-Gauss cell-centroid reconstruction

  • Divergence – face-flux summation (adjoint of gradient)

  • Boundary conditions – ghost-cell (Dirichlet) / direct injection (Neumann)

Neumann boundary values on the named axis sides ("left"/"right", "front"/"back", "bottom"/"top") are coordinate-direction derivatives (\(\partial u/\partial x\), etc.), matching pybamm.FiniteVolume; e.g. u = x takes value +1 on both "left" and "right". Values on any other face tag (Gmsh region names, "iface_*") are outward-normal derivatives \(\partial u/\partial n\).

Parameters:

options (dict, optional) – Passed through to pybamm.SpatialMethod. Additionally "non-orthogonal correction" selects the decomposition of the face normal: "over-relaxed" (default, \(\alpha = 1/\cos \theta\), favouring diagonal dominance) or "minimum" (\(\alpha = \cos\theta\), the smallest cross term). Both are exact on linear fields.

Extends: pybamm.spatial_methods.spatial_method.SpatialMethod

boundary_integral(child, discretised_child, region)[source]#

Integral of the owner-cell values of child over the boundary faces of region ("entire" = all exterior faces).

boundary_value_or_flux(symbol, discretised_child, bcs=None)[source]#

Owner-cell values on a boundary side (zeroth-order boundary value); corner sides return the single closest boundary cell.

broadcast(symbol, domains, broadcast_type)[source]#

See pybamm.SpatialMethod.broadcast().

build(mesh)[source]#

See pybamm.SpatialMethod.build().

concatenation(disc_children)[source]#

See pybamm.SpatialMethod.concatenation().

definite_integral_matrix(child, vector_type='row', integration_dimension='primary')[source]#

Cell-volume weights of the primary domain as a pybamm.Matrix, one block per auxiliary-domain repeat.

Parameters:
  • child (pybamm.Symbol) – The symbol being integrated.

  • vector_type (str, optional) – "row" (default) or "column".

  • integration_dimension (str, optional) – Only "primary" is supported: cells have no secondary structure to integrate over.

Raises:

NotImplementedError – For a non-primary integration_dimension.

delta_function(symbol, discretised_symbol)[source]#

Implements the delta function on the approriate side for a spatial method.

Parameters:
  • symbol (pybamm.Symbol) – The symbol to which is being integrated

  • discretised_symbol (pybamm.Symbol) – The discretised symbol of the correct size

div_D_grad(div_symbol, grad_child, disc_D, disc_u, boundary_conditions)[source]#

Discretise div(D * grad(u)) as a single TPFA operation.

Fully symbolic — works for both constant and state-dependent scalar D. Internal-face fluxes use the distance-weighted harmonic mean of D (resistances in series, as pybamm.FiniteVolume does for coefficients of a gradient, so material interfaces carry the exact two-cell flux) and the two-point normal derivative plus its non-orthogonal cross term (see _tpfa_matrix()). D must be strictly positive.

This method is only reached when the expression is written as div(D * grad(u)) (a single product, matched syntactically during discretisation); other flux forms go through the generic gradient()/divergence() operators, which cannot apply boundary conditions conservatively and raise instead.

divergence(symbol, discretised_symbol, boundary_conditions)[source]#

Face-flux divergence of a cell-centred vector field.

Boundary faces use zeroth-order flux extrapolation, so fluxes built from BC-carrying gradients are rejected (see the raise below).

gradient(symbol, discretised_symbol, boundary_conditions)[source]#

Least-squares cell-centroid gradient, returned as a pybamm.VectorField with one component per dimension.

Exact on linear fields for any cell shape: every face contributes one directional-derivative equation — towards the neighbour centroid (internal faces), towards the face centroid holding the prescribed value (Dirichlet), or the normal derivative itself (Neumann). Boundary faces without a condition contribute nothing.

gradient_squared(symbol, discretised_symbol, boundary_conditions)[source]#

Pointwise |grad u|^2 via gradient().

indefinite_integral(child, discretised_child, direction)[source]#

Implements the indefinite integral for a spatial method.

Parameters:
  • child (pybamm.Symbol) – The symbol to which is being integrated

  • discretised_child (pybamm.Symbol) – The discretised symbol of the correct size

  • direction (str) – The direction of integration

Returns:

Contains the result of acting the discretised indefinite integral on the child discretised_symbol

Return type:

class: pybamm.Array

integral(child, discretised_child, integration_dimension, integration_variable=None)[source]#

Volume integral over the primary domain (cell-volume weights).

internal_neumann_condition(left_symbol_disc, right_symbol_disc, left_mesh, right_mesh, left_bcs=None, right_bcs=None)[source]#

Normal gradient across the interface between two submeshes, one value per interface face (outward from left_mesh).

On unstructured meshes this is the two-point difference plus the non-orthogonal cross term (see _tpfa_matrix()), whose face gradient is fitted on both sides with the interface faces as cross-mesh rows. left_bcs/right_bcs are the external boundary conditions of each side, used by that fit.

laplacian(symbol, discretised_symbol, boundary_conditions)[source]#

Laplacian Matrix @ discretised_symbol + bc_rhs: the two-point flux plus the non-orthogonal cross term, which is built from the BC-aware Green-Gauss gradient so it stays fully implicit.

process_binary_operators(bin_op, left, right, disc_left, disc_right)[source]#

Apply a binary operator componentwise when either operand is a pybamm.VectorField, lifting scalars to N components.

set_internal_bcs_for_concat(disc, var, children, outer_bcs)[source]#

Build internal BC dict for each Concatenation child by walking its mesh’s interface_data graph instead of assuming consecutive 1D-stack pairs.

Returns None when no iface_<other> face buckets exist in any child mesh — that means there’s no graph-discovered topology, so the caller should fall through to the legacy 1D-stack pairwise routine.

For each child T_a on submesh mesh_a (graph case):
  • Pass through any user-supplied outer_bcs whose tag matches an external boundary tag present in mesh_a.boundary_faces.

  • For each interface mesh_a ↔ mesh_b (one entry per neighbor in mesh_a.interface_data), set iface_<b> to the discretised internal Neumann gradient between T_a and the matching child T_b.

spatial_variable(symbol)[source]#

Return a vector of cell-centroid coordinates for symbol’s direction (or its leading name token, e.g. x_n -> x), tiled over auxiliary domains. Raises pybamm.DomainError rather than guessing when neither identifies a coordinate.