1use nalgebra::{DVector, SVector};
8
9use cartan_core::Manifold;
10use cartan_dec::mesh::Mesh;
11use cartan_dec::stokes::StokesSolverAL;
12use cartan_dec::Operators;
13
14use volterra_dec::curved_stokes::CurvedStokesSolver;
15use volterra_dec::stokes_dec::{self, VelocityFieldDec};
16
17#[derive(Debug, Clone)]
19pub struct FlowField {
20 pub velocity_3d: Vec<[f64; 3]>,
22 pub div_residual: f64,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum StokesBackend {
29 StreamFunction,
31 KillingOperator,
33}
34
35pub trait StokesSolver {
37 fn solve(&mut self, force_3d: &[[f64; 3]]) -> FlowField;
41}
42
43pub struct KillingOperatorSolver {
45 inner: StokesSolverAL,
46 n_vertices: usize,
47}
48
49impl KillingOperatorSolver {
50 pub fn new<M: Manifold<Point = SVector<f64, 3>>>(
52 mesh: &Mesh<M, 3, 2>,
53 penalty: f64,
54 tolerance: f64,
55 ) -> Self {
56 let nv = mesh.n_vertices();
57 let inner = StokesSolverAL::new(mesh, penalty, tolerance, 100, 1000);
58 Self { inner, n_vertices: nv }
59 }
60
61 pub fn new_with_iters<M: Manifold<Point = SVector<f64, 3>>>(
63 mesh: &Mesh<M, 3, 2>,
64 penalty: f64,
65 tolerance: f64,
66 max_al: usize,
67 max_cg: usize,
68 ) -> Self {
69 let nv = mesh.n_vertices();
70 let inner = StokesSolverAL::new(mesh, penalty, tolerance, max_al, max_cg);
71 Self { inner, n_vertices: nv }
72 }
73}
74
75impl StokesSolver for KillingOperatorSolver {
76 fn solve(&mut self, force_3d: &[[f64; 3]]) -> FlowField {
77 let nv = self.n_vertices;
78 assert_eq!(force_3d.len(), nv);
79
80 let mut force_flat = vec![0.0; 3 * nv];
82 for v in 0..nv {
83 force_flat[v * 3] = force_3d[v][0];
84 force_flat[v * 3 + 1] = force_3d[v][1];
85 force_flat[v * 3 + 2] = force_3d[v][2];
86 }
87
88 let result = self.inner.solve(&force_flat);
89
90 let mut velocity_3d = vec![[0.0; 3]; nv];
92 for v in 0..nv {
93 velocity_3d[v][0] = result.velocity[v * 3];
94 velocity_3d[v][1] = result.velocity[v * 3 + 1];
95 velocity_3d[v][2] = result.velocity[v * 3 + 2];
96 }
97
98 FlowField {
99 velocity_3d,
100 div_residual: result.div_residual,
101 }
102 }
103}
104
105#[allow(dead_code)]
111pub struct StreamFunctionStokes {
112 inner: CurvedStokesSolver,
113 n_vertices: usize,
114 er: f64,
116 simplices: Vec<[usize; 3]>,
118 coords: Vec<[f64; 3]>,
120 dual_areas: Vec<f64>,
122 boundaries: Vec<[usize; 2]>,
124 vertex_boundaries: Vec<Vec<usize>>,
126 star1: Vec<f64>,
128}
129
130impl StreamFunctionStokes {
131 pub fn new<M: Manifold>(
136 ops: &Operators<M, 3, 2>,
137 mesh: &Mesh<M, 3, 2>,
138 gaussian_k: &[f64],
139 er: f64,
140 ) -> Result<Self, String> {
141 let n_vertices = mesh.n_vertices();
142 let inner = CurvedStokesSolver::new(ops, mesh, gaussian_k)?;
143
144 let coords = stokes_dec::extract_coords(mesh);
145
146 let mut dual_areas = vec![0.0_f64; n_vertices];
148 for &[i0, i1, i2] in &mesh.simplices {
149 let e01 = sub3(coords[i1], coords[i0]);
150 let e02 = sub3(coords[i2], coords[i0]);
151 let cr = cross3(e01, e02);
152 let face_area = 0.5 * norm3(cr);
153 let third = face_area / 3.0;
154 dual_areas[i0] += third;
155 dual_areas[i1] += third;
156 dual_areas[i2] += third;
157 }
158
159 let s1 = ops.hodge.star1();
160 let star1: Vec<f64> = (0..s1.len()).map(|i| s1[i]).collect();
161
162 Ok(Self {
163 inner,
164 n_vertices,
165 er,
166 simplices: mesh.simplices.clone(),
167 coords,
168 dual_areas,
169 boundaries: mesh.boundaries.clone(),
170 vertex_boundaries: mesh.vertex_boundaries.clone(),
171 star1,
172 })
173 }
174}
175
176impl StokesSolver for StreamFunctionStokes {
177 fn solve(&mut self, force_3d: &[[f64; 3]]) -> FlowField {
178 let nv = self.n_vertices;
179 assert_eq!(force_3d.len(), nv);
180
181 let omega = discrete_curl(force_3d, &self.simplices, &self.coords, &self.dual_areas, nv);
185
186 let (psi, _) = self.inner.solve(&omega, self.er);
188
189 let vel = velocity_from_psi(
191 nv,
192 &psi,
193 &self.boundaries,
194 &self.vertex_boundaries,
195 &self.coords,
196 );
197
198 FlowField {
199 velocity_3d: vel.v,
200 div_residual: 0.0, }
202 }
203}
204
205fn discrete_curl(
211 force: &[[f64; 3]],
212 simplices: &[[usize; 3]],
213 coords: &[[f64; 3]],
214 dual_areas: &[f64],
215 nv: usize,
216) -> DVector<f64> {
217 let mut omega = vec![0.0_f64; nv];
218
219 for &[i0, i1, i2] in simplices {
220 let p0 = coords[i0];
221 let p1 = coords[i1];
222 let p2 = coords[i2];
223
224 let e01 = sub3(p1, p0);
225 let e12 = sub3(p2, p1);
226 let e20 = sub3(p0, p2);
227
228 let f01 = mid3(force[i0], force[i1]);
230 let f12 = mid3(force[i1], force[i2]);
231 let f20 = mid3(force[i2], force[i0]);
232
233 let circ_01 = dot3(f01, e01);
235 let circ_12 = dot3(f12, e12);
236 let circ_20 = dot3(f20, e20);
237
238 omega[i0] += 0.5 * (circ_01 - circ_20);
241 omega[i1] += 0.5 * (circ_12 - circ_01);
242 omega[i2] += 0.5 * (circ_20 - circ_12);
243 }
244
245 for i in 0..nv {
247 if dual_areas[i] > 1e-30 {
248 omega[i] /= dual_areas[i];
249 }
250 }
251
252 DVector::from_vec(omega)
253}
254
255fn velocity_from_psi(
260 nv: usize,
261 psi: &DVector<f64>,
262 boundaries: &[[usize; 2]],
263 vertex_boundaries: &[Vec<usize>],
264 coords: &[[f64; 3]],
265) -> VelocityFieldDec {
266 let ne = boundaries.len();
267 let mut vel = vec![[0.0_f64; 3]; nv];
268
269 for e in 0..ne {
278 let [v0, v1] = boundaries[e];
279 let dpsi = psi[v1] - psi[v0];
280
281 let edge = sub3(coords[v1], coords[v0]);
282 let edge_len = norm3(edge);
283 if edge_len < 1e-30 {
284 continue;
285 }
286 let edge_hat = scale3(edge, 1.0 / edge_len);
287
288 let mid = mid3(coords[v0], coords[v1]);
294 let mid_len = norm3(mid);
295 let approx_normal = if mid_len > 1e-14 {
296 let n = scale3(mid, 1.0 / mid_len);
297 let d = dot3(n, edge_hat);
299 let corrected = [n[0] - d * edge_hat[0], n[1] - d * edge_hat[1], n[2] - d * edge_hat[2]];
300 let cl = norm3(corrected);
301 if cl > 1e-14 { scale3(corrected, 1.0 / cl) } else { [0.0, 0.0, 1.0] }
302 } else {
303 [0.0, 0.0, 1.0]
304 };
305
306 let dual_dir = cross3(approx_normal, edge_hat);
307 let vel_magnitude = dpsi / edge_len;
308 let u_contrib = scale3(dual_dir, vel_magnitude);
309
310 vel[v0] = add3(vel[v0], scale3(u_contrib, 0.5));
311 vel[v1] = add3(vel[v1], scale3(u_contrib, 0.5));
312 }
313
314 for (v, edges) in vel.iter_mut().zip(vertex_boundaries) {
316 let valence = edges.len() as f64;
317 if valence > 0.0 {
318 *v = scale3(*v, 1.0 / valence);
319 }
320 }
321
322 VelocityFieldDec { v: vel, n_vertices: nv }
323}
324
325fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { [a[0] - b[0], a[1] - b[1], a[2] - b[2]] }
327fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { [a[0] + b[0], a[1] + b[1], a[2] + b[2]] }
328fn mid3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { [0.5 * (a[0] + b[0]), 0.5 * (a[1] + b[1]), 0.5 * (a[2] + b[2])] }
329fn scale3(a: [f64; 3], s: f64) -> [f64; 3] { [a[0] * s, a[1] * s, a[2] * s] }
330fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 { a[0] * b[0] + a[1] * b[1] + a[2] * b[2] }
331fn norm3(a: [f64; 3]) -> f64 { dot3(a, a).sqrt() }
332fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
333 [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
334}