1use num_complex::Complex;
10use nalgebra::SVector;
11use sprs::{CsMat, TriMat};
12
13use cartan_core::Manifold;
14use cartan_dec::line_bundle::{ConnectionAngles, BochnerLaplacian};
15use cartan_dec::mesh::Mesh;
16use cartan_dec::hodge::HodgeStar;
17
18use crate::nematic_field_2d::NematicField2D;
19use crate::stokes_trait::StokesSolver;
20use volterra_dec::semi_lagrangian::SemiLagrangian;
21use volterra_dec::stokes_dec::VelocityFieldDec;
22
23
24#[derive(Debug, Clone)]
26pub struct EngineParams {
27 pub pe: f64,
29 pub lambda: f64,
31 pub epsilon: f64,
33 pub dt: f64,
35 pub activity_sign: f64,
37}
38
39impl Default for EngineParams {
40 fn default() -> Self {
41 Self {
42 pe: 1.0,
43 lambda: 1.0,
44 epsilon: 0.01,
45 dt: 0.01,
46 activity_sign: -1.0,
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct StepDiagnostics {
54 pub time: f64,
56 pub mean_order: f64,
58 pub stokes_residual: f64,
60 pub step: usize,
62}
63
64#[allow(dead_code)]
68pub struct ActiveNematicEngine {
69 params: EngineParams,
71 bochner: BochnerLaplacian<2>,
73 semi_lag: SemiLagrangian,
75 stokes: Box<dyn StokesSolver>,
77 diffusion_matrix: CsMat<f64>,
81 connection: ConnectionAngles,
83 dual_areas: Vec<f64>,
85 coords: Vec<[f64; 3]>,
87 simplices: Vec<[usize; 3]>,
89 n_vertices: usize,
91 step: usize,
93 time: f64,
95}
96
97impl ActiveNematicEngine {
98 pub fn new<M: Manifold<Point = SVector<f64, 3>>>(
103 mesh: &Mesh<M, 3, 2>,
104 manifold: &M,
105 hodge: &HodgeStar,
106 params: EngineParams,
107 stokes: Box<dyn StokesSolver>,
108 ) -> Self {
109 let nv = mesh.n_vertices();
110 let connection = ConnectionAngles::from_mesh(mesh, manifold);
111 let bochner = BochnerLaplacian::<2>::from_mesh_data(mesh, hodge, &connection);
112
113 let dual_areas: Vec<f64> = (0..nv).map(|i| hodge.star0()[i]).collect();
114
115 let coords: Vec<[f64; 3]> = mesh.vertices.iter()
116 .map(|v| [v[0], v[1], v[2]])
117 .collect();
118
119 let simplices: Vec<[usize; 3]> = mesh.simplices.clone();
120
121 let triangles: Vec<[usize; 3]> = simplices.clone();
123 let semi_lag = SemiLagrangian::new(coords.clone(), triangles);
124
125 let dt_over_pe = params.dt / params.pe;
133 let lap_mat = bochner.matrix();
134 let n = nv;
135 let mut triplets = TriMat::new((n, n));
136 for i in 0..n {
138 triplets.add_triplet(i, i, 1.0);
139 }
140 for (col, col_view) in lap_mat.outer_iterator().enumerate() {
142 for (row, val) in col_view.iter() {
143 triplets.add_triplet(row, col, -dt_over_pe * val.re);
144 }
145 }
146 let diffusion_matrix = triplets.to_csc();
147
148 Self {
149 params,
150 bochner,
151 semi_lag,
152 stokes,
153 diffusion_matrix,
154 connection,
155 dual_areas,
156 coords,
157 simplices,
158 n_vertices: nv,
159 step: 0,
160 time: 0.0,
161 }
162 }
163
164 pub fn step(&mut self, field: &mut NematicField2D) -> StepDiagnostics {
168 let dt = self.params.dt;
169
170 let force = self.compute_active_force(field);
172 let flow = self.stokes.solve(&force);
173
174 let velocity: Vec<[f64; 3]> = flow.velocity_3d;
177 let (q1_adv, q2_adv) = self.advect_components(field, &velocity, dt);
178
179 let q1_diff = self.implicit_diffusion_solve(&q1_adv);
181 let q2_diff = self.implicit_diffusion_solve(&q2_adv);
182
183 for v in 0..self.n_vertices {
185 field.values_mut()[v] = Complex::new(q1_diff[v], q2_diff[v]);
186 }
187
188 field.normalise();
190
191 self.step += 1;
192 self.time += dt;
193
194 StepDiagnostics {
195 time: self.time,
196 mean_order: field.mean_scalar_order(),
197 stokes_residual: flow.div_residual,
198 step: self.step,
199 }
200 }
201
202 fn compute_active_force(&self, field: &NematicField2D) -> Vec<[f64; 3]> {
207 let nv = self.n_vertices;
208 let mut force = vec![[0.0; 3]; nv];
209
210 let vals = field.values();
211 let zeta = self.params.activity_sign * self.params.pe;
212
213 for &[i0, i1, i2] in &self.simplices {
216 let v0 = self.coords[i0];
217 let v1 = self.coords[i1];
218 let v2 = self.coords[i2];
219
220 let e01 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
221 let e02 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
222 let cross = [
223 e01[1] * e02[2] - e01[2] * e02[1],
224 e01[2] * e02[0] - e01[0] * e02[2],
225 e01[0] * e02[1] - e01[1] * e02[0],
226 ];
227 let area2 = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
228 if area2 < 1e-30 {
229 continue;
230 }
231 let n = [cross[0] / area2, cross[1] / area2, cross[2] / area2];
232 let area = area2 / 2.0;
233
234 let grad_phi = [
236 cross_with_normal(n, [v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]], area),
237 cross_with_normal(n, [v0[0] - v2[0], v0[1] - v2[1], v0[2] - v2[2]], area),
238 cross_with_normal(n, [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]], area),
239 ];
240
241 let q1 = [vals[i0].re, vals[i1].re, vals[i2].re];
242 let q2 = [vals[i0].im, vals[i1].im, vals[i2].im];
243
244 let mut grad_q1 = [0.0; 3];
246 let mut grad_q2 = [0.0; 3];
247 for local in 0..3 {
248 for d in 0..3 {
249 grad_q1[d] += grad_phi[local][d] * q1[local];
250 grad_q2[d] += grad_phi[local][d] * q2[local];
251 }
252 }
253
254 let w = zeta * area / 3.0;
257 for &vi in &[i0, i1, i2] {
258 for d in 0..3 {
259 force[vi][d] += w * (grad_q1[d] + grad_q2[d]);
260 }
261 }
262 }
263
264 force
265 }
266
267 fn advect_components(
269 &self,
270 field: &NematicField2D,
271 velocity: &[[f64; 3]],
272 dt: f64,
273 ) -> (Vec<f64>, Vec<f64>) {
274 let qfield = field.to_qfield_dec();
275 let vel = VelocityFieldDec {
276 v: velocity.to_vec(),
277 n_vertices: self.n_vertices,
278 };
279
280 let q_adv = self.semi_lag.advect_with_params(&qfield, &vel, dt, self.params.lambda);
281 (q_adv.q1, q_adv.q2)
282 }
283
284 fn implicit_diffusion_solve(&self, rhs: &[f64]) -> Vec<f64> {
286 let n = rhs.len();
287 let mut x = rhs.to_vec();
288 let mut r: Vec<f64> = {
289 let ax = sparse_matvec(&self.diffusion_matrix, &x);
290 rhs.iter().zip(&ax).map(|(b, a)| b - a).collect()
291 };
292 let mut p = r.clone();
293 let mut rs_old: f64 = r.iter().map(|ri| ri * ri).sum();
294
295 if rs_old.sqrt() < 1e-14 {
296 return x;
297 }
298
299 for _ in 0..1000 {
300 let ap = sparse_matvec(&self.diffusion_matrix, &p);
301 let pap: f64 = p.iter().zip(&ap).map(|(pi, api)| pi * api).sum();
302 if pap.abs() < 1e-30 {
303 break;
304 }
305 let alpha = rs_old / pap;
306
307 for i in 0..n {
308 x[i] += alpha * p[i];
309 r[i] -= alpha * ap[i];
310 }
311
312 let rs_new: f64 = r.iter().map(|ri| ri * ri).sum();
313 if rs_new.sqrt() < 1e-14 {
314 break;
315 }
316
317 let beta = rs_new / rs_old;
318 for i in 0..n {
319 p[i] = r[i] + beta * p[i];
320 }
321 rs_old = rs_new;
322 }
323
324 x
325 }
326
327 pub fn time(&self) -> f64 {
329 self.time
330 }
331
332 pub fn step_count(&self) -> usize {
334 self.step
335 }
336}
337
338fn cross_with_normal(n: [f64; 3], e: [f64; 3], area: f64) -> [f64; 3] {
340 let inv_2a = 0.5 / area;
341 [
342 inv_2a * (n[1] * e[2] - n[2] * e[1]),
343 inv_2a * (n[2] * e[0] - n[0] * e[2]),
344 inv_2a * (n[0] * e[1] - n[1] * e[0]),
345 ]
346}
347
348fn sparse_matvec(mat: &CsMat<f64>, x: &[f64]) -> Vec<f64> {
350 let nrows = mat.rows();
351 let mut y = vec![0.0; nrows];
352 for (col, col_view) in mat.outer_iterator().enumerate() {
353 let xc = x[col];
354 if xc.abs() < 1e-30 {
355 continue;
356 }
357 for (row, &val) in col_view.iter() {
358 y[row] += val * xc;
359 }
360 }
361 y
362}