Date: 2026-08-14 Purpose: The mathematical derivation behind BELFEM's Lagrange and Nédélec (edge) elements: interpolation operators, barycentric coordinates, the geometry Jacobian, Whitney edge functions for TRI3/TRI6/TET4/TET10, curl operators, and the edge/face generation concept. Module: src/fem/interpolation Origin: Distilled from Christian Messe's quasi-magnetostatic theory notes (LaTeX, written in the Lagrange-multiplier era of the code). Transcription errata in the notes were fixed during extraction. Each equation block names its TeX source for traceability. Scope: This document covers the volume elements TRI3, TRI6, TET4 and TET10. The LINE3 and HEX8 elements and the thin-shell family (QUAD4TS, PENTA6TS, HEX8TB) are documented in nedelec.md and nedelec_thinshell.md. For the class framework (EdgeFunction lifecycle, factory, DOF counts), see nedelec.md; for the weak forms these operators serve, see ../../maxwell/doc/maxwell_weak_forms.md.
(source: introduction/notation.tex)
In finite element theory, a scalar field phi(x) is discretized by linearly combining an interpolation function with basis values. These basis values represent the degrees of freedom against which the system of equations is solved. They are often attributed to the values the field assumes at the supporting nodes of the computational mesh. In this case, we collect the nodal values in a column vector:
The superscript is the node number; the hat marks a value attributed to a node. The spatially dependent interpolation function N is defined by the chosen element type and is also called shape function or ansatz function. Elements that connect the basis to nodal values are called Lagrangian elements. That is by far not the only possibility: Hermitian elements (mechanical beams and plates) connect the basis to nodal values and their spatial derivatives, and isogeometric elements use B-spline functions.
Within the context of the Maxwell equations, it is beneficial to associate the supporting basis with the edges of an element. This way it can be guaranteed that Ampère's circuital law is fulfilled. For the magnetic field h, scalar values h_tilde^k are associated with the edges, and the edge-based interpolation function E contains the geometric information that translates the scalar basis into a vector field:
For obvious reasons, these elements are called edge elements; the names Whitney elements and Nédélec elements (Nédélec 1980) are also common in the literature.
If a vector field a(x) is instead discretized with the Lagrangian approach, the interpolation function carries no directional information; the vector components are interpolated as independent scalar fields, with the degrees of freedom in node-wise order:
The spatial gradient of the scalar field is obtained by differentiating the shape function, which creates the B-operator (derivatives are indicated by a comma, so N^i_,x abbreviates dN^i/dx):
In analogy to the gradient operator B, the curl operator C is introduced. For the node-interpolated vector field a, the magnetic flux density is:
(The notes carried sign slips in the first column block of this matrix; the form above is the corrected one, consistent with (curl a)_y = a_x,z - a_z,x and (curl a)_z = a_y,x - a_x,y.)
For the edge-interpolated field h, the current density follows the same pattern with the curl of the edge functions:
In 2D the operators are built the same way. The magnetic vector potential reduces to the scalar a_z:
For the non-conducting parts, interpolated with the scalar potential phi:
A sign-convention note: this rule is unsigned, as in the original notes. BELFEM's physical convention in the phi-domain is h = -grad phi (Arsenault et al. 2023); the phi kernel (src/fem/maxwell/matrices/mt_maxwell_phi.cpp) deliberately drops the minus because the mass term integral mu |h|^2 is even in the sign. Keep that in mind before wiring B phi_hat into anything where the sign survives, such as an interface term.
And for the conducting parts, using the edge field:
In the axisymmetric case the magnetic vector potential exists only in the tangential direction (the notes mislabeled it "scalar potential" here):
The 1/r term comes from b_z = (1/r) d(r a_t)/dr. The phi-domain and conducting-domain rules follow the 2D pattern with (r, z) in place of (x, y); the current density is the tangential component j_t in both cases.
(source: triangle/triangle_lagrange.tex)
The parameter coordinates are the barycentric coordinates (xi, eta, zeta) with the unity requirement:
Due to the unity requirement, the third coordinate can be expressed through the first two, so the interpolation for a TRI3 reads:
The B-operator must contain the spatial derivatives of N in the geometry space. Applying the chain rule, the derivatives are evaluated in the parameter space and transformed:
where J is the geometry Jacobian. In 2D:
The transposed symbol in this equation points out a common pitfall when implementing finite elements: a Jacobian matrix is used, for example, when a Newton-Raphson iteration is performed. Any math book that is not related to finite elements will provide the definition without the transpose (Bronshtein). When formulating finite elements, however, the geometry Jacobian is always used in its transposed form. For that reason, most relevant finite element books omit the transposed symbol. This may improve readability, but can cause confusion and antagonizing pain when implementing the equations. Reader's discretion is advised.
BELFEM stores exactly this transposed form: in EF_TRI3::link() (cl_EF_TRI3.cpp:39-42), mJ rows run over the parameters, which is J^T in the math-book convention.
Both straight-edged triangles and tetrahedra have the convenient feature that the geometry Jacobian remains constant within the element, regardless of the order of the interpolation function. This is not the case for quadrilaterals or hexahedra. For the straight triangle:
The TRI6 adds three midside nodes. The construction of N and B works the same way as for the linear element. If the triangle has straight edges, the geometry Jacobian collapses to the constant form above; for curved edges, J depends on xi and eta.
(source: triangle/triangle_nedelec.tex)
Ampère's circuital law states that the integral of the magnetic field over an arbitrary closed loop within the computational domain must equal the electric current in the enclosed area. This cannot be unconditionally fulfilled if Lagrangian elements are used. Even worse, it cannot be guaranteed that the computation converges to the physical solution. The remedy is a basis connected to the edges rather than the nodes (Nédélec 1980).
Although h is a vector field, the directional information is implicitly given by the direction of the edge in the geometry space. The edge degree of freedom therefore reduces to a scalar, which significantly lowers the number of unknowns. It also means that the edge function depends on the actual shape of the triangle. With the local edge numbering edge k connecting the barycentric pair (lambda_a, lambda_b) in cyclic order (xi,eta), (eta,zeta), (zeta,xi), the three edge vectors are the Whitney 1-forms:
The spatial gradients of the parameter coordinates come from the inverse of the geometry Jacobian, and the unity requirement gives:
This is exactly what EF_TRI3 implements: precompute() stores the barycentric pair per edge (cl_EF_TRI3.cpp:80-95) and E() assembles s_k (G_k grad(next) - H_k grad(prev)) (:104-113).
The scalar s_k indicates the orientation of the edge. To determine it, the topology of the mesh must be known. Each edge is owned by one global direction; the sign is:
The signs are fetched in link() via aElement->edge_directions(mS). With s_k baked in, the basis has unit circulation on its own global edge: int_edge_j E_k . dl = delta_jk in global orientation (equivalently s_k delta_jk in the element-local direction). This "DOF equals circulation" convention is the load-bearing contract for DOF sharing between elements; see nedelec.md §5. Note that first-order Whitney forms satisfy it automatically, with no extra normalization factor.
The entries of the C-operator are found by deriving the edge functions in the parameter space and transforming with the chain rule. On a straight-edged triangle, E^i_x,x and E^i_y,y vanish, and after a few lines of algebra (the MATLAB fragment matlab_curl/fragment.m in the notes repository reproduces it symbolically) the operator collapses to a constant:
Implemented verbatim in EF_TRI3::link() (cl_EF_TRI3.cpp:64-68). The result is plausible: each Whitney form lambda_a grad lambda_b - lambda_b grad lambda_a has curl = 2 grad lambda_a x grad lambda_b, which is constant on an affine element.
There is a family of at least four element types introduced by Nédélec himself (Nédélec 1980, 1986), and more in the literature (Zaglmayr 2006). The simplest is the first kind family, with two degrees of freedom per edge and two additional degrees of freedom on the face. The edge functions are:
The three face functions are:
They fulfill the property F^1 + F^2 + F^3 = 0, so any two of them span the face space while the third is redundant and is not part of the interpolation vector. The factor 4 is not required; it slightly improves the conditioning of the matrix.
When assigning the degree-of-freedom indices, remember that the order of the two edge dofs is swapped when the sign of the edge is negative. The curl operator is computed with the same technique as for the linear element. An implementation should distinguish straight-edged from curved-edged second-order elements; for the latter, the derivatives of grad xi and grad eta must be computed as well.
Convention note (2026-08-14, TRI6 only): the notes and the implementation (cl_EF_TRI6.cpp) agree on these formulas, but the two parent-edge functions each carry circulation s/2 on their edge, not 1 and 0 (hand integral: H_0 - G_0 = 2 - 3t and H_1 - G_1 = 3t - 1, both integrate to 1/2). The blanket unit-circulation statement in nedelec.md §5 holds for the first-order elements; for TRI6 (marked proof-of-concept there) the second-order edge dofs are moments of the Whitney pair, and the formulas here are the authoritative record of what is implemented. Do not renormalize one without the other. This note does not extend to TET10: the notes' TET10 edge polynomials are twice the TRI6 pair and integrate to unit circulation, see §4.1.
(source: triangle/triangle_nedelec.tex, edge table rewritten to the implemented convention)
Element numbering schemes are not standardized across libraries, so node and edge numbers must be defined explicitly. BELFEM follows the EXODUS II scheme (Schoof & Yarberry 1994). The implemented coordinate assignment is (see cl_IF_TET4.hpp:28-31):
Note the swap: node 2 carries zeta and node 3 carries eta. This is a mean booby trap. On the EXODUS triangle, node k naturally carries the k-th barycentric coordinate, so one would assume the tetrahedron continues the pattern (node 2 with eta, node 3 with zeta). It must not: on an EXODUS-ordered tetrahedron that naive map is left-handed, det J < 0, so the coordinates of nodes 2 and 3 have to be exchanged to keep the Jacobian positive. Check the map against cl_IF_TET4.hpp before writing any TET formula; assuming the triangle pattern is exactly how the two edge-function defects of 2026-08-14 entered the code. With the EXODUS edge topology (edge k connects nodes (1,2), (2,3), (3,1), (1,4), (2,4), (3,4)), the six Whitney pairs in the implemented convention are:
(The notes list the pairs in the generic order (xi,eta), (eta,zeta), (zeta,xi), ..., which does not match the implemented node-coordinate assignment; the table above is the one to code against, cf. cl_EF_TET4.cpp:179-213.)
Historical note (2026-08-14): the edge 3 entry (eta -> xi, code index 2) of EF_TET4::E() used to compute eta grad xi - xi grad zeta instead of eta grad xi - xi grad eta, so E disagreed with its own curl operator mC(:,2) and the defective basis carried circulation 1/2 on its own edge and -1/2 on edge 1. Found in the extraction audit, confirmed by an exact symbolic probe cross-checked against DefElement's published degree-0 basis, and fixed the same day (cl_EF_TET4.cpp:194-196 now matches the table above).
Using the partition of unity, tau is substituted with 1 - xi - eta - zeta and grad tau with -(grad xi + grad eta + grad zeta). Deriving the edge functions and applying the chain rule yields the curl operator; on the affine TET each column is again constant:
which is what EF_TET4::link() precomputes (cl_EF_TET4.cpp:113-138).
The edge interpolation functions follow the TRI6 pattern, two per edge, with tau taking the role of the fourth coordinate on the edges towards node 4. In the notes' generic coordinate labels (pairs (xi,eta), (eta,zeta), (zeta,xi), (xi,tau), (eta,tau), (zeta,tau); remap the pairing per the table in §4 before coding against it, cf. E16 in the drift record):
Note that these polynomials are twice the TRI6 pair, and each parent-edge function integrates to unit circulation on its edge, unlike the TRI6 convention of §3.3.
The face functions come in triples per face with the same redundancy property as on the triangle (each triple sums to zero, two of three are active):
(The notes dropped the + before the 16 term of F^11; fixed here.)
The multiplicities of the edge and face functions do not matter for the span (one might divide the former by two and the latter by eight); what matters is that the same convention is used consistently on both sides of a shared entity. As with the second-order triangle, the two dofs of an edge are swapped when the edge sign is negative; whether that swapping happens in the topology table or directly in the edge function vector is up to the programmer.
When two elements share a common face, a face ownership must be defined: for example, the element with the lower ID owns the face, and dofs 1 and 2 of each face are active with respect to the owner. The borrowing element must then find out how it is oriented with respect to the owner and activate the corresponding face functions in its E and C operators.
Historical note (2026-08-14): the scalar tables in cl_EF_TET10.cpp precompute() used to be transcribed in the notes' generic coordinate labels while the gradient assignments followed the implemented node map: eta and zeta were exchanged in every scalar factor touching them, the exact trap described in §4. The defect was found by an exact symbolic probe (24 edge-dof conformity violations, face candidates leaking onto edges), fixed at its source (the table generator tmp/tet10/tet10_generate.m used the naive node map), regenerated through the re-pinned MATLAB pipeline, and ported back; the final gate parsed the edited C++ tables and confirmed exact unit circulation, zero face leakage, and curl consistency for all 24 dofs. The same day, the new runtime battery (tests/fem/test_EdgeFunctions.cpp) caught a third instance of the class: rows 8-9 of the mNeta/mNzeta shape-derivative tables (which build the Jacobian) carried each other's values, making the eta and zeta Jacobian columns identical (singular J at generic points); fixed the same day, battery 22/22 green. TET10 remains proof-of-concept pending a full 3D regression run.
If the element has curved edges, the gradients grad xi, grad eta, grad zeta are no longer constant and must be derived as well.
(source: triangle/triangle_nedelec.tex; concept only, the implemented generation lives in src/mesh)
Most mesh generators provide node coordinates and element topology; the edge information is implicit. It can be generated with a sort-unique pass: allocate an array over all element edges, assign each edge the key
where a_e < b_e are the two node numbers and n is the number of nodes. Populate by looping over all elements, sort ascending, apply unique. The node numbers are recovered with a_e = d_e mod n and b_e = d_e div n, and the edge direction can be defined as pointing from a_e to b_e. The generation of faces in 3D works exactly the same way, except that a_e and b_e now refer to the indices of the two elements sharing the face, and n to the total number of elements on the mesh (keying by sorted node triples works just as well).
BELFEM's actual edge and face construction (including ownership and direction rules used by edge_directions()) is implemented in the mesh module; see src/mesh/doc/. The formula above is the concept, not a citation of the implementation.