use num_complex::Complex;
use sprs::{CsMat, TriMat};
use crate::hodge::HodgeStar;
use crate::mesh::Mesh;
use cartan_core::Manifold;
#[derive(Debug, Clone)]
pub struct Section<const K: u32> {
pub values: Vec<Complex<f64>>,
}
impl<const K: u32> Section<K> {
pub fn zeros(nv: usize) -> Self {
Self {
values: vec![Complex::new(0.0, 0.0); nv],
}
}
pub fn uniform(nv: usize, z: Complex<f64>) -> Self {
Self {
values: vec![z; nv],
}
}
pub fn n_vertices(&self) -> usize {
self.values.len()
}
pub fn add(&self, other: &Self) -> Self {
assert_eq!(self.values.len(), other.values.len());
Self {
values: self
.values
.iter()
.zip(&other.values)
.map(|(a, b)| a + b)
.collect(),
}
}
pub fn scale(&self, s: Complex<f64>) -> Self {
Self {
values: self.values.iter().map(|z| z * s).collect(),
}
}
pub fn scale_real(&self, s: f64) -> Self {
Self {
values: self.values.iter().map(|z| z * s).collect(),
}
}
pub fn norms(&self) -> Vec<f64> {
self.values.iter().map(|z| z.norm()).collect()
}
pub fn mean_norm(&self) -> f64 {
let n = self.values.len() as f64;
self.values.iter().map(|z| z.norm()).sum::<f64>() / n
}
pub fn l2_inner(&self, other: &Self, dual_areas: &[f64]) -> Complex<f64> {
self.values
.iter()
.zip(&other.values)
.zip(dual_areas)
.map(|((a, b), &area)| a.conj() * b * area)
.sum()
}
pub fn normalise(&mut self, eps: f64) {
for z in &mut self.values {
let n = z.norm();
if n > eps {
*z /= n;
}
}
}
pub fn scalar_order(&self) -> Vec<f64> {
self.values.iter().map(|z| 2.0 * z.norm()).collect()
}
pub fn mean_scalar_order(&self) -> f64 {
let s = self.scalar_order();
s.iter().sum::<f64>() / s.len() as f64
}
pub fn to_real_components(&self) -> (Vec<f64>, Vec<f64>) {
let q1 = self.values.iter().map(|z| z.re).collect();
let q2 = self.values.iter().map(|z| z.im).collect();
(q1, q2)
}
pub fn from_real_components(q1: &[f64], q2: &[f64]) -> Self {
assert_eq!(q1.len(), q2.len());
Self {
values: q1
.iter()
.zip(q2)
.map(|(&r, &i)| Complex::new(r, i))
.collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectionAngles {
pub primal: Vec<f64>,
pub dual: Vec<f64>,
}
impl ConnectionAngles {
pub fn from_mesh<M: Manifold>(mesh: &Mesh<M, 3, 2>, manifold: &M) -> Self {
let ne = mesh.n_boundaries();
let mut primal = vec![0.0; ne];
let mut dual = vec![0.0; ne];
for e in 0..ne {
let [vi, vj] = mesh.boundaries[e];
let pi = &mesh.vertices[vi];
let pj = &mesh.vertices[vj];
let log_ij = manifold
.log(pi, pj)
.unwrap_or_else(|_| manifold.zero_tangent(pi));
let log_ji = manifold
.log(pj, pi)
.unwrap_or_else(|_| manifold.zero_tangent(pj));
let angle_i = tangent_angle_2d(manifold, mesh, vi, &log_ij);
let angle_j = tangent_angle_2d(manifold, mesh, vj, &log_ji);
primal[e] = angle_j - angle_i - std::f64::consts::PI;
let cofaces = &mesh.boundary_simplices[e];
if cofaces.len() == 2 {
let angle_f0 = face_reference_angle(manifold, mesh, cofaces[0], e);
let angle_f1 = face_reference_angle(manifold, mesh, cofaces[1], e);
dual[e] = angle_f1 - angle_f0 - std::f64::consts::PI;
}
}
Self { primal, dual }
}
}
fn tangent_angle_2d<M: Manifold>(
manifold: &M,
mesh: &Mesh<M, 3, 2>,
v: usize,
tangent: &M::Tangent,
) -> f64 {
let pv = &mesh.vertices[v];
let first_edge = mesh.vertex_boundaries[v][0];
let [e0, e1] = mesh.boundaries[first_edge];
let neighbor = if e0 == v { e1 } else { e0 };
let ref_dir = manifold
.log(pv, &mesh.vertices[neighbor])
.unwrap_or_else(|_| manifold.zero_tangent(pv));
let rr = manifold.inner(pv, &ref_dir, &ref_dir);
let tt = manifold.inner(pv, tangent, tangent);
let rt = manifold.inner(pv, &ref_dir, tangent);
if rr < 1e-30 || tt < 1e-30 {
return 0.0;
}
let cos_a = rt / (rr.sqrt() * tt.sqrt());
let second_edge = if mesh.vertex_boundaries[v].len() > 1 {
mesh.vertex_boundaries[v][1]
} else {
first_edge
};
let [s0, s1] = mesh.boundaries[second_edge];
let neighbor2 = if s0 == v { s1 } else { s0 };
let ref_dir2 = manifold
.log(pv, &mesh.vertices[neighbor2])
.unwrap_or_else(|_| manifold.zero_tangent(pv));
let r2r = manifold.inner(pv, &ref_dir2, &ref_dir);
let r2t = manifold.inner(pv, &ref_dir2, tangent);
let r2_perp_component = r2t - r2r * rt / rr;
let r2r2 = manifold.inner(pv, &ref_dir2, &ref_dir2);
let ref_cross = r2r2 - r2r * r2r / rr;
let sin_sign = if ref_cross > 1e-30 {
r2_perp_component.signum()
} else {
1.0
};
let sin_a = sin_sign * (1.0 - cos_a.clamp(-1.0, 1.0).powi(2)).sqrt();
sin_a.atan2(cos_a)
}
fn face_reference_angle<M: Manifold>(
manifold: &M,
mesh: &Mesh<M, 3, 2>,
face: usize,
edge: usize,
) -> f64 {
let simplex = &mesh.simplices[face];
let [e0, e1] = mesh.boundaries[edge];
let p0 = &mesh.vertices[simplex[0]];
let edge_at_p0 = if e0 == simplex[0] {
manifold
.log(p0, &mesh.vertices[e1])
.unwrap_or_else(|_| manifold.zero_tangent(p0))
} else if e1 == simplex[0] {
manifold
.log(p0, &mesh.vertices[e0])
.unwrap_or_else(|_| manifold.zero_tangent(p0))
} else {
manifold
.log(p0, &mesh.vertices[e0])
.unwrap_or_else(|_| manifold.zero_tangent(p0))
};
tangent_angle_2d(manifold, mesh, simplex[0], &edge_at_p0)
}
#[derive(Debug, Clone)]
pub struct BochnerLaplacian<const K: u32> {
pub n_vertices: usize,
matrix: CsMat<Complex<f64>>,
}
impl<const K: u32> BochnerLaplacian<K> {
pub fn from_mesh_data(
mesh: &Mesh<impl Manifold, 3, 2>,
hodge: &HodgeStar,
connection: &ConnectionAngles,
) -> Self {
let nv = mesh.n_vertices();
let ne = mesh.n_boundaries();
let star0 = hodge.star0();
let star1 = hodge.star1();
let mut triplets = TriMat::new((nv, nv));
let inv_star0: Vec<f64> = (0..nv)
.map(|i| {
if star0[i].abs() > 1e-30 {
-1.0 / star0[i]
} else {
0.0
}
})
.collect();
for e in 0..ne {
let [vi, vj] = mesh.boundaries[e];
let w = star1[e];
let omega = connection.primal[e];
let phase = Complex::from_polar(1.0, -(K as f64) * omega);
triplets.add_triplet(vi, vj, inv_star0[vi] * (-w * phase));
triplets.add_triplet(vj, vi, inv_star0[vj] * (-w * phase.conj()));
triplets.add_triplet(vi, vi, Complex::new(inv_star0[vi] * w, 0.0));
triplets.add_triplet(vj, vj, Complex::new(inv_star0[vj] * w, 0.0));
}
let raw = triplets.to_csc();
Self {
n_vertices: nv,
matrix: raw,
}
}
pub fn apply(&self, z: &Section<K>) -> Section<K> {
let input: Vec<Complex<f64>> = z.values.clone();
let mut output = vec![Complex::new(0.0, 0.0); self.n_vertices];
for (col, col_view) in self.matrix.outer_iterator().enumerate() {
for (row, &val) in col_view.iter() {
output[row] += val * input[col];
}
}
Section { values: output }
}
pub fn matrix(&self) -> &CsMat<Complex<f64>> {
&self.matrix
}
}
pub fn defect_charges<const K: u32>(
section: &Section<K>,
connection: &ConnectionAngles,
mesh: &Mesh<impl Manifold, 3, 2>,
gaussian_curvature_per_face: &[f64],
) -> Vec<f64> {
let nf = mesh.n_simplices();
let mut charges = vec![0.0; nf];
let two_pi = 2.0 * std::f64::consts::PI;
let k = K as f64;
for f in 0..nf {
let simplex = &mesh.simplices[f];
let mut winding = 0.0;
for local_edge in 0..3 {
let vi = simplex[local_edge];
let vj = simplex[(local_edge + 1) % 3];
let edge_idx = find_edge(mesh, vi, vj);
let omega = connection.primal[edge_idx];
let [e0, _e1] = mesh.boundaries[edge_idx];
let oriented_omega = if e0 == vi { omega } else { -omega };
let zi = section.values[vi];
let zj = section.values[vj];
if zi.norm() < 1e-30 || zj.norm() < 1e-30 {
continue;
}
let phase = Complex::from_polar(1.0, -k * oriented_omega);
let transported = phase * zi;
let ratio = zj / transported;
winding += ratio.arg();
}
charges[f] = winding / (two_pi * k) + gaussian_curvature_per_face[f] / two_pi;
}
charges
}
fn find_edge(mesh: &Mesh<impl Manifold, 3, 2>, vi: usize, vj: usize) -> usize {
let key = if vi < vj { [vi, vj] } else { [vj, vi] };
for &e in &mesh.vertex_boundaries[vi] {
let [e0, e1] = mesh.boundaries[e];
let sorted = if e0 < e1 { [e0, e1] } else { [e1, e0] };
if sorted == key {
return e;
}
}
panic!("edge ({vi}, {vj}) not found in mesh");
}
pub fn section_to_q_components(section: &Section<2>) -> (Vec<f64>, Vec<f64>) {
section.to_real_components()
}
pub fn q_components_to_section(q1: &[f64], q2: &[f64]) -> Section<2> {
Section::<2>::from_real_components(q1, q2)
}