use num_complex::Complex;
use nalgebra::SVector;
use sprs::{CsMat, TriMat};
use cartan_core::Manifold;
use cartan_dec::line_bundle::{ConnectionAngles, BochnerLaplacian};
use cartan_dec::mesh::Mesh;
use cartan_dec::hodge::HodgeStar;
use crate::nematic_field_2d::NematicField2D;
use crate::stokes_trait::StokesSolver;
use volterra_dec::semi_lagrangian::SemiLagrangian;
use volterra_dec::stokes_dec::VelocityFieldDec;
#[derive(Debug, Clone)]
pub struct EngineParams {
pub pe: f64,
pub lambda: f64,
pub epsilon: f64,
pub dt: f64,
pub activity_sign: f64,
}
impl Default for EngineParams {
fn default() -> Self {
Self {
pe: 1.0,
lambda: 1.0,
epsilon: 0.01,
dt: 0.01,
activity_sign: -1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct StepDiagnostics {
pub time: f64,
pub mean_order: f64,
pub stokes_residual: f64,
pub step: usize,
}
#[allow(dead_code)]
pub struct ActiveNematicEngine {
params: EngineParams,
bochner: BochnerLaplacian<2>,
semi_lag: SemiLagrangian,
stokes: Box<dyn StokesSolver>,
diffusion_matrix: CsMat<f64>,
connection: ConnectionAngles,
dual_areas: Vec<f64>,
coords: Vec<[f64; 3]>,
simplices: Vec<[usize; 3]>,
n_vertices: usize,
step: usize,
time: f64,
}
impl ActiveNematicEngine {
pub fn new<M: Manifold<Point = SVector<f64, 3>>>(
mesh: &Mesh<M, 3, 2>,
manifold: &M,
hodge: &HodgeStar,
params: EngineParams,
stokes: Box<dyn StokesSolver>,
) -> Self {
let nv = mesh.n_vertices();
let connection = ConnectionAngles::from_mesh(mesh, manifold);
let bochner = BochnerLaplacian::<2>::from_mesh_data(mesh, hodge, &connection);
let dual_areas: Vec<f64> = (0..nv).map(|i| hodge.star0()[i]).collect();
let coords: Vec<[f64; 3]> = mesh.vertices.iter()
.map(|v| [v[0], v[1], v[2]])
.collect();
let simplices: Vec<[usize; 3]> = mesh.simplices.clone();
let triangles: Vec<[usize; 3]> = simplices.clone();
let semi_lag = SemiLagrangian::new(coords.clone(), triangles);
let dt_over_pe = params.dt / params.pe;
let lap_mat = bochner.matrix();
let n = nv;
let mut triplets = TriMat::new((n, n));
for i in 0..n {
triplets.add_triplet(i, i, 1.0);
}
for (col, col_view) in lap_mat.outer_iterator().enumerate() {
for (row, val) in col_view.iter() {
triplets.add_triplet(row, col, -dt_over_pe * val.re);
}
}
let diffusion_matrix = triplets.to_csc();
Self {
params,
bochner,
semi_lag,
stokes,
diffusion_matrix,
connection,
dual_areas,
coords,
simplices,
n_vertices: nv,
step: 0,
time: 0.0,
}
}
pub fn step(&mut self, field: &mut NematicField2D) -> StepDiagnostics {
let dt = self.params.dt;
let force = self.compute_active_force(field);
let flow = self.stokes.solve(&force);
let velocity: Vec<[f64; 3]> = flow.velocity_3d;
let (q1_adv, q2_adv) = self.advect_components(field, &velocity, dt);
let q1_diff = self.implicit_diffusion_solve(&q1_adv);
let q2_diff = self.implicit_diffusion_solve(&q2_adv);
for v in 0..self.n_vertices {
field.values_mut()[v] = Complex::new(q1_diff[v], q2_diff[v]);
}
field.normalise();
self.step += 1;
self.time += dt;
StepDiagnostics {
time: self.time,
mean_order: field.mean_scalar_order(),
stokes_residual: flow.div_residual,
step: self.step,
}
}
fn compute_active_force(&self, field: &NematicField2D) -> Vec<[f64; 3]> {
let nv = self.n_vertices;
let mut force = vec![[0.0; 3]; nv];
let vals = field.values();
let zeta = self.params.activity_sign * self.params.pe;
for &[i0, i1, i2] in &self.simplices {
let v0 = self.coords[i0];
let v1 = self.coords[i1];
let v2 = self.coords[i2];
let e01 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
let e02 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
let cross = [
e01[1] * e02[2] - e01[2] * e02[1],
e01[2] * e02[0] - e01[0] * e02[2],
e01[0] * e02[1] - e01[1] * e02[0],
];
let area2 = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
if area2 < 1e-30 {
continue;
}
let n = [cross[0] / area2, cross[1] / area2, cross[2] / area2];
let area = area2 / 2.0;
let grad_phi = [
cross_with_normal(n, [v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]], area),
cross_with_normal(n, [v0[0] - v2[0], v0[1] - v2[1], v0[2] - v2[2]], area),
cross_with_normal(n, [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]], area),
];
let q1 = [vals[i0].re, vals[i1].re, vals[i2].re];
let q2 = [vals[i0].im, vals[i1].im, vals[i2].im];
let mut grad_q1 = [0.0; 3];
let mut grad_q2 = [0.0; 3];
for local in 0..3 {
for d in 0..3 {
grad_q1[d] += grad_phi[local][d] * q1[local];
grad_q2[d] += grad_phi[local][d] * q2[local];
}
}
let w = zeta * area / 3.0;
for &vi in &[i0, i1, i2] {
for d in 0..3 {
force[vi][d] += w * (grad_q1[d] + grad_q2[d]);
}
}
}
force
}
fn advect_components(
&self,
field: &NematicField2D,
velocity: &[[f64; 3]],
dt: f64,
) -> (Vec<f64>, Vec<f64>) {
let qfield = field.to_qfield_dec();
let vel = VelocityFieldDec {
v: velocity.to_vec(),
n_vertices: self.n_vertices,
};
let q_adv = self.semi_lag.advect_with_params(&qfield, &vel, dt, self.params.lambda);
(q_adv.q1, q_adv.q2)
}
fn implicit_diffusion_solve(&self, rhs: &[f64]) -> Vec<f64> {
let n = rhs.len();
let mut x = rhs.to_vec();
let mut r: Vec<f64> = {
let ax = sparse_matvec(&self.diffusion_matrix, &x);
rhs.iter().zip(&ax).map(|(b, a)| b - a).collect()
};
let mut p = r.clone();
let mut rs_old: f64 = r.iter().map(|ri| ri * ri).sum();
if rs_old.sqrt() < 1e-14 {
return x;
}
for _ in 0..1000 {
let ap = sparse_matvec(&self.diffusion_matrix, &p);
let pap: f64 = p.iter().zip(&ap).map(|(pi, api)| pi * api).sum();
if pap.abs() < 1e-30 {
break;
}
let alpha = rs_old / pap;
for i in 0..n {
x[i] += alpha * p[i];
r[i] -= alpha * ap[i];
}
let rs_new: f64 = r.iter().map(|ri| ri * ri).sum();
if rs_new.sqrt() < 1e-14 {
break;
}
let beta = rs_new / rs_old;
for i in 0..n {
p[i] = r[i] + beta * p[i];
}
rs_old = rs_new;
}
x
}
pub fn time(&self) -> f64 {
self.time
}
pub fn step_count(&self) -> usize {
self.step
}
}
fn cross_with_normal(n: [f64; 3], e: [f64; 3], area: f64) -> [f64; 3] {
let inv_2a = 0.5 / area;
[
inv_2a * (n[1] * e[2] - n[2] * e[1]),
inv_2a * (n[2] * e[0] - n[0] * e[2]),
inv_2a * (n[0] * e[1] - n[1] * e[0]),
]
}
fn sparse_matvec(mat: &CsMat<f64>, x: &[f64]) -> Vec<f64> {
let nrows = mat.rows();
let mut y = vec![0.0; nrows];
for (col, col_view) in mat.outer_iterator().enumerate() {
let xc = x[col];
if xc.abs() < 1e-30 {
continue;
}
for (row, &val) in col_view.iter() {
y[row] += val * xc;
}
}
y
}