use nalgebra::{DVector, SVector};
use cartan_core::Manifold;
use cartan_dec::mesh::Mesh;
use cartan_dec::stokes::StokesSolverAL;
use cartan_dec::Operators;
use volterra_dec::curved_stokes::CurvedStokesSolver;
use volterra_dec::stokes_dec::{self, VelocityFieldDec};
#[derive(Debug, Clone)]
pub struct FlowField {
pub velocity_3d: Vec<[f64; 3]>,
pub div_residual: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StokesBackend {
StreamFunction,
KillingOperator,
}
pub trait StokesSolver {
fn solve(&mut self, force_3d: &[[f64; 3]]) -> FlowField;
}
pub struct KillingOperatorSolver {
inner: StokesSolverAL,
n_vertices: usize,
}
impl KillingOperatorSolver {
pub fn new<M: Manifold<Point = SVector<f64, 3>>>(
mesh: &Mesh<M, 3, 2>,
penalty: f64,
tolerance: f64,
) -> Self {
let nv = mesh.n_vertices();
let inner = StokesSolverAL::new(mesh, penalty, tolerance, 100, 1000);
Self { inner, n_vertices: nv }
}
pub fn new_with_iters<M: Manifold<Point = SVector<f64, 3>>>(
mesh: &Mesh<M, 3, 2>,
penalty: f64,
tolerance: f64,
max_al: usize,
max_cg: usize,
) -> Self {
let nv = mesh.n_vertices();
let inner = StokesSolverAL::new(mesh, penalty, tolerance, max_al, max_cg);
Self { inner, n_vertices: nv }
}
}
impl StokesSolver for KillingOperatorSolver {
fn solve(&mut self, force_3d: &[[f64; 3]]) -> FlowField {
let nv = self.n_vertices;
assert_eq!(force_3d.len(), nv);
let mut force_flat = vec![0.0; 3 * nv];
for v in 0..nv {
force_flat[v * 3] = force_3d[v][0];
force_flat[v * 3 + 1] = force_3d[v][1];
force_flat[v * 3 + 2] = force_3d[v][2];
}
let result = self.inner.solve(&force_flat);
let mut velocity_3d = vec![[0.0; 3]; nv];
for v in 0..nv {
velocity_3d[v][0] = result.velocity[v * 3];
velocity_3d[v][1] = result.velocity[v * 3 + 1];
velocity_3d[v][2] = result.velocity[v * 3 + 2];
}
FlowField {
velocity_3d,
div_residual: result.div_residual,
}
}
}
#[allow(dead_code)]
pub struct StreamFunctionStokes {
inner: CurvedStokesSolver,
n_vertices: usize,
er: f64,
simplices: Vec<[usize; 3]>,
coords: Vec<[f64; 3]>,
dual_areas: Vec<f64>,
boundaries: Vec<[usize; 2]>,
vertex_boundaries: Vec<Vec<usize>>,
star1: Vec<f64>,
}
impl StreamFunctionStokes {
pub fn new<M: Manifold>(
ops: &Operators<M, 3, 2>,
mesh: &Mesh<M, 3, 2>,
gaussian_k: &[f64],
er: f64,
) -> Result<Self, String> {
let n_vertices = mesh.n_vertices();
let inner = CurvedStokesSolver::new(ops, mesh, gaussian_k)?;
let coords = stokes_dec::extract_coords(mesh);
let mut dual_areas = vec![0.0_f64; n_vertices];
for &[i0, i1, i2] in &mesh.simplices {
let e01 = sub3(coords[i1], coords[i0]);
let e02 = sub3(coords[i2], coords[i0]);
let cr = cross3(e01, e02);
let face_area = 0.5 * norm3(cr);
let third = face_area / 3.0;
dual_areas[i0] += third;
dual_areas[i1] += third;
dual_areas[i2] += third;
}
let s1 = ops.hodge.star1();
let star1: Vec<f64> = (0..s1.len()).map(|i| s1[i]).collect();
Ok(Self {
inner,
n_vertices,
er,
simplices: mesh.simplices.clone(),
coords,
dual_areas,
boundaries: mesh.boundaries.clone(),
vertex_boundaries: mesh.vertex_boundaries.clone(),
star1,
})
}
}
impl StokesSolver for StreamFunctionStokes {
fn solve(&mut self, force_3d: &[[f64; 3]]) -> FlowField {
let nv = self.n_vertices;
assert_eq!(force_3d.len(), nv);
let omega = discrete_curl(force_3d, &self.simplices, &self.coords, &self.dual_areas, nv);
let (psi, _) = self.inner.solve(&omega, self.er);
let vel = velocity_from_psi(
nv,
&psi,
&self.boundaries,
&self.vertex_boundaries,
&self.coords,
);
FlowField {
velocity_3d: vel.v,
div_residual: 0.0, }
}
}
fn discrete_curl(
force: &[[f64; 3]],
simplices: &[[usize; 3]],
coords: &[[f64; 3]],
dual_areas: &[f64],
nv: usize,
) -> DVector<f64> {
let mut omega = vec![0.0_f64; nv];
for &[i0, i1, i2] in simplices {
let p0 = coords[i0];
let p1 = coords[i1];
let p2 = coords[i2];
let e01 = sub3(p1, p0);
let e12 = sub3(p2, p1);
let e20 = sub3(p0, p2);
let f01 = mid3(force[i0], force[i1]);
let f12 = mid3(force[i1], force[i2]);
let f20 = mid3(force[i2], force[i0]);
let circ_01 = dot3(f01, e01);
let circ_12 = dot3(f12, e12);
let circ_20 = dot3(f20, e20);
omega[i0] += 0.5 * (circ_01 - circ_20);
omega[i1] += 0.5 * (circ_12 - circ_01);
omega[i2] += 0.5 * (circ_20 - circ_12);
}
for i in 0..nv {
if dual_areas[i] > 1e-30 {
omega[i] /= dual_areas[i];
}
}
DVector::from_vec(omega)
}
fn velocity_from_psi(
nv: usize,
psi: &DVector<f64>,
boundaries: &[[usize; 2]],
vertex_boundaries: &[Vec<usize>],
coords: &[[f64; 3]],
) -> VelocityFieldDec {
let ne = boundaries.len();
let mut vel = vec![[0.0_f64; 3]; nv];
for e in 0..ne {
let [v0, v1] = boundaries[e];
let dpsi = psi[v1] - psi[v0];
let edge = sub3(coords[v1], coords[v0]);
let edge_len = norm3(edge);
if edge_len < 1e-30 {
continue;
}
let edge_hat = scale3(edge, 1.0 / edge_len);
let mid = mid3(coords[v0], coords[v1]);
let mid_len = norm3(mid);
let approx_normal = if mid_len > 1e-14 {
let n = scale3(mid, 1.0 / mid_len);
let d = dot3(n, edge_hat);
let corrected = [n[0] - d * edge_hat[0], n[1] - d * edge_hat[1], n[2] - d * edge_hat[2]];
let cl = norm3(corrected);
if cl > 1e-14 { scale3(corrected, 1.0 / cl) } else { [0.0, 0.0, 1.0] }
} else {
[0.0, 0.0, 1.0]
};
let dual_dir = cross3(approx_normal, edge_hat);
let vel_magnitude = dpsi / edge_len;
let u_contrib = scale3(dual_dir, vel_magnitude);
vel[v0] = add3(vel[v0], scale3(u_contrib, 0.5));
vel[v1] = add3(vel[v1], scale3(u_contrib, 0.5));
}
for (v, edges) in vel.iter_mut().zip(vertex_boundaries) {
let valence = edges.len() as f64;
if valence > 0.0 {
*v = scale3(*v, 1.0 / valence);
}
}
VelocityFieldDec { v: vel, n_vertices: nv }
}
fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { [a[0] - b[0], a[1] - b[1], a[2] - b[2]] }
fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { [a[0] + b[0], a[1] + b[1], a[2] + b[2]] }
fn 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])] }
fn scale3(a: [f64; 3], s: f64) -> [f64; 3] { [a[0] * s, a[1] * s, a[2] * s] }
fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 { a[0] * b[0] + a[1] * b[1] + a[2] * b[2] }
fn norm3(a: [f64; 3]) -> f64 { dot3(a, a).sqrt() }
fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
}