cartan

Line Bundles

On a 2-manifold, each tangent space is isomorphic to . A k-atic director (a headless director with k-fold rotational symmetry) lives in the complex line bundle with connection , where is the Levi-Civita connection 1-form.

The line_bundle module provides discrete sections of these bundles, the associated Bochner Laplacian, and exact topological defect detection.

Section<K>

Section<K> stores one Complex<f64> per vertex. The const generic K determines the k-atic order:

KField typeDefect charges
1Tangent vector (spin-1)
2Nematic (spin-2)
4Tetratic (spin-4)
6Hexatic (spin-6)
use cartan_dec::line_bundle::Section;
use num_complex::Complex;
 
// Nematic section (K=2): one complex number per vertex.
let z = Section::<2>::uniform(nv, Complex::new(0.3, 0.4));
 
// Scalar order parameter S = 2|z|.
let s = z.scalar_order();
 
// Normalise to unit order (|z| = 1 at each vertex).
z.normalise(1e-15);

Compile-time K gives type safety: you cannot accidentally add a Section<2> to a Section<4>.

Connection Angles

The discrete Levi-Civita connection is stored as angle-valued 1-forms on edges. ConnectionAngles precomputes two sets:

  • Primal (): vertex-to-vertex transport along primal edges. Used for the nematic Bochner Laplacian and defect charges.
  • Dual (): face-to-face transport along dual edges. Used for face-based vector Laplacians in Stokes solvers.

Transport of a k-atic section along primal edge multiplies by .

use cartan_dec::line_bundle::ConnectionAngles;
 
let conn = ConnectionAngles::from_mesh(&mesh, &manifold);
// conn.primal[e]: vertex-vertex connection angle for edge e
// conn.dual[e]:   face-face connection angle for edge e

Bochner Laplacian

BochnerLaplacian<K> is a sparse Hermitian complex matrix assembled from cotangent weights and connection transport phases:

Off-diagonal entry for edge : . Diagonal: sum of incident cotangent weights. Negative semi-definite by construction.

use cartan_dec::line_bundle::BochnerLaplacian;
 
let lap = BochnerLaplacian::<2>::from_mesh_data(&mesh, &hodge, &conn);
let delta_z = lap.apply(&z);  // Delta^L z

Defect Charges

Topological charges are computed per face using the discrete winding number plus the Gaussian curvature correction:

The discrete Poincare-Hopf theorem holds exactly: .

use cartan_dec::line_bundle::defect_charges;
 
let charges = defect_charges(&section, &conn, &mesh, &gauss_curvature_per_face);
let total: f64 = charges.iter().sum();
// total == chi(M) to machine precision

Veronese Map

For interop between the complex line bundle representation and the standard Q-tensor, the Veronese map sends :

use cartan_dec::line_bundle::{section_to_q_components, q_components_to_section};
 
let (q1, q2) = section_to_q_components(&section);
let recovered = q_components_to_section(&q1, &q2);

References

  • Zhu, Saintillan, Chern. "Active nematic fluids on Riemannian 2-manifolds." arXiv:2405.06044, 2024. Sections 2.1 and 3.