Differential Forms
Fields live on the cells of a Lattice.
A 0-form assigns a value to every site, a 1-form to every link, a 2-form to every plaquette, and so on; a p-form in D dimensions has \(\binom{D}{p}\) components per site.
- class supervillain.lattice.Form(input_array, *, degree, lattice, dtype=None)[source]
Bases:
ndarrayA differential p-form on a Lattice.
In the interlaced \((2N)^D\) array a p-form component with odd directions \(I\) lives at interlaced coordinates \(\xi\) with
\[\begin{split}\xi_k = \begin{cases} 2 x_k & k \notin I \\ 2 x_k + 1 & k \in I \end{cases}\end{split}\]so the even directions are site directions and the odd directions are the directions the cell spans. In both cases \(\lfloor \xi_k / 2 \rfloor = x_k\): the physical site is always the floor of the interlaced coordinate divided by 2.
Actually storing data in this interlaced layout is very wasteful—for a \(p\)-form only \(\binom{D}{p}/2^D\) of the array elements are used. This introduces a nontrivial memory and speed overhead. Instead, we store the data in a dense format in an array of shape \(\left(\binom{D}{p}, N, N, ..., N\right)\).
The 0th axis indexes the \(\binom{D}{p}\) components, listed in lexicographic order by the sorted tuple of directions that are “form directions”. The remaining axes index the physical lattice site itself, \(x\).
A
Formis a subclass ofnumpy.ndarray, so it supports all the usual numpy operations. Element-wise operations (±,*,/, unary−,abs,**,np.sqrt,np.isclose,==, and so on) return a Form of the same degree when all Form operands share a degree. Mixed-degree arithmetic is left as a plain ndarray because the degree of the result would be ambiguous, butthe wedge productis defined below.Reductions (
sum,max,min, and so on) return plain numpy scalars or arrays.- classmethod spatial_shape(*, degree, lattice)[source]
- Returns
Shape of a form with degree
degreeonlatticewith \(D\) dimensions and \(N\) sites,(C(D,p), N, N, ..., N).- Return type
tuple
- component(*dirs)[source]
View of a single component’s spatial data, shape (N,…,N).
- Parameters
*dirs (int or tuple of int) – Direction indices, either as separate arguments
f.component(0, 2)or as a single tuplef.component((0, 2)).- Returns
A view of shape
(N,...,N); writes back to the Form.- Return type
np.ndarray
- to_interlaced()[source]
Embed the compact form into a \((2N)^D\) interlaced array.
Component \(I\) occupies the sub-array where direction \(k\) uses odd indices if \(k \in I\) and even indices otherwise; every non-p-form site is zero.
Inverse of
from_interlaced().- Returns
Plain array of shape
(2N, 2N, ..., 2N).- Return type
np.ndarray
- classmethod from_interlaced(p, data, lattice=None)[source]
Construct a dense Form from an interlaced \((2N)^D\) array.
Inverse of
to_interlaced().- Parameters
p (int) – Form degree.
data (np.ndarray) – Interlaced array of shape
(2N, 2N, ..., 2N); only sites with exactly p odd coordinates are read.lattice (Lattice, optional) – Inferred from
data.shapeif omitted.
- Returns
The compact p-form whose interlaced embedding reproduces
dataat p-form sites.- Return type
- face_sum()[source]
Sum this p-form onto its (p-1)-faces, returning a (p-1)-form.
For each (p-1)-form output component \(M\) at site \(x\):
\[g_M[x] = \sum_{O \supset M} \big( f_O[x] + f_O[x - \hat{e}_e] \big)\]where the sum runs over p-cells \(O = M \cup \{e\}\).
- Returns
The (p-1)-form face sum, or
0if this is a 0-form.- Return type
- coface_sum()[source]
Sum this p-form onto incident (p+1)-cofaces, returning a (p+1)-form.
For each (p+1)-form output component \(O\) at site \(x\):
\[g_O[x] = \sum_{M \subset O} \big( f_M[x] + f_M[x + \hat{e}_{o_j}] \big)\]where the sum runs over p-faces \(M = O \setminus \{o_j\}\) of \(O\). Dual to
face_sum(); unliked(), all contributions enter unsigned.- Returns
The (p+1)-form coface sum, or
0if this is a D-form.- Return type
Translation
Forms can be translated around the (periodic) lattice.
- supervillain.lattice.pull(form, shift)[source]
Translation operator \(T_{\Delta x}\): pull content from position \(x + \Delta x\) to \(x\).
\[T_{\Delta x} f[\ldots, x] = \texttt{pull}(f, \Delta x)[\ldots, x] = f[\ldots, x + \Delta x] \quad \text{(periodic)}\]- Parameters
form (np.ndarray) – Array whose last
len(shift)axes are the spatial directions.shift (sequence of int) – One integer per spatial direction.
- Return type
np.ndarray
- supervillain.lattice.push(form, shift)[source]
Translate the form forward by \(\Delta x\).
\[\texttt{push}(f, \Delta x)[\ldots, x] = f[\ldots, x - \Delta x] \quad \text{(periodic)}\]- Parameters
form (np.ndarray) – Array whose last
len(shift)axes are the spatial directions.shift (sequence of int) – One integer per spatial direction.
- Return type
np.ndarray
Sign Conventions
A component of a \(p\)-form is labeled by a strictly increasing tuple of \(p\) directions, stored along axis 0 (see The Interlaced Picture): the component \(f_I\) with \(I = (i_1 < \cdots < i_p)\) is the coefficient of \(dx_{i_1} \wedge \cdots \wedge dx_{i_p}\), with the factors in increasing order.
Every sign in the exterior calculus has the same origin: an operation naturally produces a tuple of directions out of order, and restoring sorted order costs the signature of the sorting permutation. Write \(\sigma(t)\) for the sign of the permutation that sorts the tuple \(t\), and \(\frown\) for concatenation. Write \(\Delta_e A[x] = A[x + \hat{e}_e] - A[x]\) for the forward finite difference in direction \(e\), and \(\nabla^*_e A[x] = A[x] - A[x - \hat{e}_e]\) for the backward finite difference.
Exterior Derivative
Adding direction \(e\) wedges \(dx_e\) onto the front of \(dx_{O \setminus e}\); sorting it into place costs \(\sigma\big((e) \frown (O \setminus e)\big) = (-1)^{\#\{i \in O \setminus e \;:\; i < e\}}\).
- supervillain.lattice.d(f)[source]
The exterior derivative of a p-form, a (p+1)-form.
For each output component \(O = (o_0, \ldots, o_p)\):
\[(df)_O[x] = \sum_{j=0}^{p} (-1)^j \, \Delta_{o_j} f_{O \setminus \{o_j\}}[x]\]where \(\Delta_k A[x] = A[x + \hat{e}_k] - A[x]\) is the forward finite difference. The sign \((-1)^j\) is the signature of the permutation sorting \(o_j\) into the remaining directions (see Sign Conventions).
The exterior derivative is exact, meaning that it satisfies
which is satisfied by supervillain.lattice.d().
This identity is tested by test_d_nilpotent in test/test_lattice.py.
Codifferential
The codifferential is defined as the formal adjoint of the exterior derivative,
where the \(\langle \cdot, \cdot \rangle\) is inner product.
This identity is tested by the test_compact_adjointness test in test/test_lattice.py.
The same insertion sign accounts for removing \(e\) from the sorted source tuple \(M \cup \{e\}\); the overall minus makes \(\delta\) the formal adjoint of \(d\).
- supervillain.lattice.delta(f)[source]
Codifferential (formal adjoint of d) of a p-form, returning a (p-1)-form.
For each output component \(M = (m_0, \ldots, m_{p-1})\) and each direction \(e \notin M\), let \(j = \#\{m \in M : m < e\}\) (the position where \(e\) would be inserted to keep \(M \cup \{e\}\) sorted):
\[(\delta f)_M[x] = - \sum_{e \notin M} (-1)^j \, \nabla^*_e f_{M \cup \{e\}}[x]\]where \(\nabla^*_e A[x] = A[x] - A[x - \hat{e}_e]\) is the backward finite difference. With the overall minus, \(\delta\) is the formal adjoint of
d()under the componentwise inner product ((1)).
The codifferential is nilpotent, meaning that it satisfies
This identity is tested by the test_codifferential_nilpotent test in test/test_lattice.py.
The continuum identity \(\delta = (-1)^{D(k+1)+1}\,\star\,d\,\star\) holds on the lattice only up to a translation; see the note below.
Laplacian
The Hodge–de Rham Laplacian is the symmetric combination of the exterior derivative and the codifferential,
mapping a \(p\)-form to a \(p\)-form. Because \(\delta\) is the adjoint of \(d\) ((1)), the Laplacian is self-adjoint and positive semidefinite,
checked by the test_laplacian_self_adjoint and test_laplacian_positive_semidefinite tests in test/test_lattice.py.
On the flat periodic lattice \(d\) and \(\delta\) are constant-coefficient combinations of the commuting shift operators \(T_k\), so the Weitzenböck cross-terms cancel through \(\{dx_k \wedge,\, \iota_l\} = \delta_{kl}\) and the Laplacian acts diagonally on each component \(I\) as the negative of the ordinary nearest-neighbor scalar Laplacian,
with no mixing between the \(\binom{D}{p}\) components.
This diagonal form agrees with the explicit composition \(d\delta + \delta d\), as checked by the test_laplacian_matches_d_delta test in test/test_lattice.py.
- supervillain.lattice.laplacian(f)[source]
Hodge–de Rham Laplacian of a p-form, a p-form of the same degree.
The Laplacian (or Laplace–de Rham operator) is
\[\Delta = d\delta + \delta d,\]the symmetric combination of the
exterior derivativeand its formal adjoint thecodifferential. Becausedelta()is the adjoint ofd(), the Laplacian is self-adjoint and positive semidefinite under the componentwise inner product,\[\langle \Delta f, f \rangle = \langle d f, d f \rangle + \langle \delta f, \delta f \rangle \geq 0.\]
Hodge Star
For each output component \(J\) (a sorted \((D-p)\)-tuple of directions), let \(I = \{0,\ldots,D-1\} \setminus J\) be its complement:
The sign sorts the concatenation \((I, J)\) into \((0, \ldots, D-1)\), so that \(dx_I \wedge [\sigma(I \frown J)\, dx_J] = dx_0 \wedge \cdots \wedge dx_{D-1}\); the shift \(-\hat{e}_I\) aligns the dual cell with the original in the interlaced geometry.
- supervillain.lattice.star(f)[source]
Hodge star of a p-form, a (D-p)-form.
For each output component \(J\) (a sorted \((D-p)\)-tuple of directions), let \(I\) be the complement of \(J\) in \(\{0, \ldots, D-1\}\):
\[(\star f)_J[x] = \sigma(I \frown J) \; f_I[x - \hat{e}_I]\]where \(\sigma(I \frown J) = (-1)^{\#\{(i, j) \in I \times J \,:\, i > j\}}\) is the sign of the permutation sorting the concatenation \((I \frown J)\) and \(\hat{e}_I = \sum_{k \in I} \hat{e}_k\).
In the discrete interlaced geometry, a p-form and its Hodge dual are centered at different lattice positions. The shift aligns them so that the inner-product identity holds after summing over the lattice:
\[\sum_{x, I} a_I[x] \, b_I[x] = \sum_x (a \wedge \star b)_{(0, \ldots, D-1)}[x]\]For \(p = 0\) and \(p = D\) the shift is trivial (\(\hat{e}_I = 0\) or the shifts cancel in the wedge).
The Hodge inner-product identity
holds exactly, which is checked by the test_hodge_inner_product test in test/test_lattice.py.
Wedge Product
On the lattice the wedge product sums over all shuffles \(O = A \sqcup B\), where \(A\) are the \(n\) directions of \(a\) and \(B\) are the \(m\) directions of \(b\):
Each shuffle contributes the sign that sorts \((A, B)\) back into \(O\), and \(b\) is evaluated on the far side of the \(a\)-cell.
- supervillain.lattice.wedge(a, b)[source]
Wedge product of an n-form a and an m-form b, an (n+m)-form.
For each output component \(O = (o_0, \ldots, o_{n+m-1})\), the sum over all shuffles \(O = A \sqcup B\) (\(A\) the \(n\) \(a\)-directions, \(B\) the \(m\) \(b\)-directions):
\[(a \wedge b)_O[x] = \sum_{O = A \sqcup B} \sigma(A \frown B) \; a_A[x] \; b_B[x + \hat{e}_A]\]where \(\hat{e}_A = \sum_{k \in A} \hat{e}_k\) and \(\sigma(A \frown B) = (-1)^{\#\{(k, j) \in A \times B \,:\, j < k\}}\) is the sign of the permutation sorting \((A \frown B)\) back into \(O\).
The wedge product is bilinear,
which is checked by the test_wedge_bilinear test in test/test_lattice.py,
and it is associative,
which is checked by the test_wedge_associative test in test/test_lattice.py.
The Leibniz rule
holds exactly, which is checked by the test_leibniz_rule test in test/test_lattice.py.
The wedge also satisfies the inner product identity
which holds exactly, as checked by the test_wedge_hodge_inner_product test in test/test_lattice.py.
Unlike the continuum, however, the lattice wedge product is not anti-commutative; see the note below.
Differences from the Continuum
In the continuum, on a Riemannian manifold with Euclidean signature, the Hodge star squares to
Danger
On the lattice the identity picks up a spatial shift,
a backward shift of \(\hat{e}_{\mathrm{all}} = \sum_\mu \hat{e}_\mu\) — one step in every direction.
The shift drops out of any periodic sum.
This is tested by test_star_star in test/test_lattice.py.
In the continuum we expect anti/commutativity
to hold.
Danger
On the lattice this property fails!
In the continuum, on a Riemannian manifold with Euclidean signature, the Hodge star satisfies
Danger
On the lattice the identity picks up a spatial shift,
where \(\hat{e}_{\mathrm{all}} = \sum_\mu \hat{e}_\mu\) and \(T_{\Delta x}\) is the translation operator in pull() by \(\Delta x\).
The shift by \(\hat{e}_{\mathrm{all}}\) drops out of any periodic sum, which is why the adjoint identity (1) holds exactly despite it.
This is tested by test_star_d_star_equals_shifted_delta in test/test_lattice.py.
The translation commutes with the other operations, as tested in test_star_d_star_equals_delta_shifted in test/test_lattice.py.