extern crate nalgebra as na;
use coorder::Coord;
use derham::{cochain::Cochain, project::derham_map, section::CoordFieldExt};
use exterior::{exterior_bases, exterior_dim, Dim, MultiForm};
use formoniq::{
assemble::assemble_galvec,
fe::fe_l2_error,
operators::SourceElVec,
problems::dirac::{solve_dirac_source, MixedField},
whitney_complex::WhitneyComplex,
};
use glatt::field::DiffFormClosure;
use gramian::{CausalType, Gramian, Metric};
use multiindex::Sign;
use simplicial::{
atlas::SimplexQuadRule, gen::cartesian::CartesianGrid, geometry::coord::mesh::MeshCoords,
linalg::Vector,
};
use std::f64::consts::PI;
const TIME_SCALE: f64 = 0.7;
const MASS: f64 = 1.5;
const PHASE: f64 = 0.3;
fn main() {
lorentzian_star_table();
clifford_dispersion();
for dim in [2, 3, 4] {
let nsubs: &[usize] = match dim {
2 => &[2, 4, 8, 16],
3 => &[2, 4, 8],
_ => &[1, 2, 4],
};
convergence(dim, nsubs);
}
}
const AXES: [&str; 4] = ["t", "x", "y", "z"];
fn blade_name(blade: &multiindex::Combination) -> String {
blade
.iter()
.map(|i| format!("d{}", AXES[i]))
.collect::<Vec<_>>()
.join("^")
}
fn lorentzian_star_table() {
let dim = 4;
let eta = Metric::minkowski(dim);
println!("Lorentzian Hodge star on 2-forms of Minkowski R^(1,3) (mostly-plus):");
for blade in exterior_bases(dim, 2) {
let form = MultiForm::from_blade_signed(dim, Sign::Pos, blade);
let star = form.hodge_star(&eta);
let star_star = star.hodge_star(&eta);
let (coeff, star_blade) = star
.basis_iter()
.find(|(c, _)| *c != 0.0)
.expect("star of a blade is a blade");
let sign = if coeff > 0.0 { '+' } else { '-' };
let involution = star_star.coeffs()[blade.rank()];
println!(
" *({:>5}) = {sign}{:<5} ** = {involution:+.0}",
blade_name(&blade),
blade_name(&star_blade),
);
}
println!();
}
fn clifford_dispersion() {
println!("Wave covector a and its causal character under eta (mostly-plus):");
for dim in [2, 3, 4] {
let eta = Metric::minkowski(dim);
let a = wave_covector(dim);
let norm_sq = a.inner(&a, &eta);
let causal = CausalType::from_norm_sq(norm_sq);
println!(" dim {dim}: <a,a> = {norm_sq:+.4} ({causal:?}) -- D^2 plane-wave symbol");
}
println!();
}
fn wave_covector(dim: Dim) -> MultiForm {
let components = [0.9, 0.5, 0.3, 0.2];
MultiForm::line(PI * Vector::from_column_slice(&components[..dim]))
}
fn polarization(dim: Dim, grade: usize) -> MultiForm {
MultiForm::new(
Vector::from_fn(exterior_dim(dim, grade), |i, _| {
((3 * i + 2 * grade) % 5) as f64 / 2.0 - 1.0
}),
dim,
grade,
)
}
fn convergence(dim: Dim, nsubs: &[usize]) {
let eta = Metric::minkowski(dim);
let a = wave_covector(dim);
let a_sharp = a.sharp(&eta);
let a_vec = a.coeffs().clone();
let clifford_component = |k: usize| -> MultiForm {
let mut component = MultiForm::zero(dim, k);
if k >= 1 {
component += a.wedge(&polarization(dim, k - 1));
}
if k < dim {
component -= polarization(dim, k + 1).interior_product(&a_sharp);
}
component
};
println!(
"Dirac-Kahler (D + m) u = J on [0,{TIME_SCALE}] x [0,1]^{}, eta = diag(-1,+1,..): dim {dim}, m = {MASS}",
dim - 1
);
println!(
" {:>5} | {:>9} | {:>10} | {:>6} | {:>10} | {:>6}",
"nsub", "dofs", "L2 error", "rate", "interp err", "rate"
);
let mut previous: Option<(usize, f64, f64)> = None;
for &nsub in nsubs {
let (topology, coords) = CartesianGrid::new_unit(dim, nsub).triangulate();
let mut matrix = coords.into_matrix();
matrix.row_mut(0).scale_mut(TIME_SCALE);
let euclidean = MeshCoords::new(matrix.clone());
let spacetime = MeshCoords::with_ambient(matrix, Gramian::minkowski(dim));
let regge = spacetime.to_edge_lengths_sq(&topology);
let euclidean_lengths = euclidean.to_edge_lengths_sq(&topology);
let whitney = WhitneyComplex::new(&topology, ®ge);
let relative = whitney.relative();
let mut loads = Vec::with_capacity(dim + 1);
let mut lift = Vec::with_capacity(dim + 1);
let mut exact_sections = Vec::with_capacity(dim + 1);
for k in 0..=dim {
let omega = polarization(dim, k);
let cliff = clifford_component(k);
let (a_exact, omega_exact) = (a_vec.clone(), omega.clone());
let exact = DiffFormClosure::new(
move |p: &Coord| (p.vector().dot(&a_exact) + PHASE).sin() * omega_exact.clone(),
dim,
k,
);
let a_source = a_vec.clone();
let source = DiffFormClosure::new(
move |p: &Coord| {
let phase = p.vector().dot(&a_source) + PHASE;
phase.cos() * cliff.clone() + MASS * phase.sin() * omega.clone()
},
dim,
k,
);
let exact_section = exact.pullback_on(&topology, &euclidean);
let source_section = source.pullback_on(&topology, &euclidean);
lift.push(derham_map(&exact_section, &topology, 3));
loads.push(Cochain::new(
k,
assemble_galvec(
&topology,
®ge,
SourceElVec::new(&source_section, Some(SimplexQuadRule::degree(dim, 3))),
),
));
exact_sections.push(exact);
}
let lift = MixedField::new(lift);
let solution = solve_dirac_source(&relative, MASS, &MixedField::new(loads), &lift);
let l2_error_of = |field: &MixedField| -> f64 {
(0..=dim)
.map(|k| {
let section = exact_sections[k].pullback_on(&topology, &euclidean);
fe_l2_error(field.grade(k), §ion, &topology, &euclidean_lengths).powi(2)
})
.sum::<f64>()
.sqrt()
};
let error = l2_error_of(&solution);
let interp_error = l2_error_of(&lift);
let ndofs: usize = (0..=dim).map(|k| whitney.ndofs(k)).sum();
if nsub == nsubs[0] {
let mut census = [0usize; 3];
for edge in topology.edges().handle_iter() {
use simplicial::geometry::metric::mesh::EdgeRefExt;
match edge.causal_type(®ge) {
gramian::CausalType::Timelike => census[0] += 1,
gramian::CausalType::Null => census[1] += 1,
gramian::CausalType::Spacelike => census[2] += 1,
}
}
println!(
" regge edge census at nsub={nsub}: {} timelike, {} null, {} spacelike",
census[0], census[1], census[2]
);
}
let rates = previous
.map(|(n, e, i)| {
let h_ratio = (nsub as f64 / n as f64).ln();
(
(e / error).ln() / h_ratio,
(i / interp_error).ln() / h_ratio,
)
})
.map_or(("--".into(), "--".into()), |(re, ri): (f64, f64)| {
(format!("{re:.2}"), format!("{ri:.2}"))
});
println!(
" {:>5} | {:>9} | {:>10.3e} | {:>6} | {:>10.3e} | {:>6}",
nsub, ndofs, error, rates.0, interp_error, rates.1
);
previous = Some((nsub, error, interp_error));
}
println!();
}