#[derive(Debug, Clone)]
pub struct LevelSet {
pub nx: usize,
pub ny: usize,
pub dx: f64,
pub phi: Vec<f64>,
}
impl LevelSet {
pub fn solid(nx: usize, ny: usize, dx: f64) -> Self {
Self {
nx,
ny,
dx,
phi: vec![1.0; nx * ny],
}
}
pub fn void(nx: usize, ny: usize, dx: f64) -> Self {
Self {
nx,
ny,
dx,
phi: vec![-1.0; nx * ny],
}
}
pub fn circle(nx: usize, ny: usize, dx: f64, cx_m: f64, cy_m: f64, radius_m: f64) -> Self {
let mut phi = vec![0.0f64; nx * ny];
for j in 0..ny {
for i in 0..nx {
let x = i as f64 * dx;
let y = j as f64 * dx;
let dist = ((x - cx_m).powi(2) + (y - cy_m).powi(2)).sqrt();
phi[j * nx + i] = radius_m - dist;
}
}
Self { nx, ny, dx, phi }
}
pub fn get(&self, i: usize, j: usize) -> f64 {
self.phi[j * self.nx + i]
}
pub fn set(&mut self, i: usize, j: usize, val: f64) {
self.phi[j * self.nx + i] = val;
}
pub fn material_indicator(&self) -> Vec<f64> {
self.phi
.iter()
.map(|&p| if p > 0.0 { 1.0 } else { 0.0 })
.collect()
}
pub fn volume_fraction(&self) -> f64 {
let n_mat = self.phi.iter().filter(|&&p| p > 0.0).count();
n_mat as f64 / self.phi.len() as f64
}
pub fn advance(&mut self, v_n: &[f64], dt: f64) {
assert_eq!(v_n.len(), self.phi.len());
let nx = self.nx;
let ny = self.ny;
let dx = self.dx;
let old_phi = self.phi.clone();
for j in 0..ny {
for i in 0..nx {
let idx = j * nx + i;
let vn = v_n[idx];
let phi_xp = if i + 1 < nx {
old_phi[j * nx + i + 1]
} else {
old_phi[idx]
};
let phi_xm = if i > 0 {
old_phi[j * nx + i - 1]
} else {
old_phi[idx]
};
let phi_yp = if j + 1 < ny {
old_phi[(j + 1) * nx + i]
} else {
old_phi[idx]
};
let phi_ym = if j > 0 {
old_phi[(j - 1) * nx + i]
} else {
old_phi[idx]
};
let dpx = (phi_xp - old_phi[idx]) / dx;
let dmx = (old_phi[idx] - phi_xm) / dx;
let dpy = (phi_yp - old_phi[idx]) / dx;
let dmy = (old_phi[idx] - phi_ym) / dx;
let grad_sq = if vn > 0.0 {
dmx.max(0.0).powi(2)
+ dpx.min(0.0).powi(2)
+ dmy.max(0.0).powi(2)
+ dpy.min(0.0).powi(2)
} else {
dpx.max(0.0).powi(2)
+ dmx.min(0.0).powi(2)
+ dpy.max(0.0).powi(2)
+ dmy.min(0.0).powi(2)
};
self.phi[idx] = old_phi[idx] - dt * vn * grad_sq.sqrt();
}
}
}
pub fn reinitialize(&mut self, n_steps: usize) {
let phi0 = self.phi.clone();
let nx = self.nx;
let ny = self.ny;
let dx = self.dx;
let dt = 0.5 * dx;
for _ in 0..n_steps {
let old = self.phi.clone();
for j in 0..ny {
for i in 0..nx {
let idx = j * nx + i;
let s = phi0[idx].signum();
let phi_xp = if i + 1 < nx {
old[j * nx + i + 1]
} else {
old[idx]
};
let phi_xm = if i > 0 { old[j * nx + i - 1] } else { old[idx] };
let phi_yp = if j + 1 < ny {
old[(j + 1) * nx + i]
} else {
old[idx]
};
let phi_ym = if j > 0 {
old[(j - 1) * nx + i]
} else {
old[idx]
};
let dpx = (phi_xp - old[idx]) / dx;
let dmx = (old[idx] - phi_xm) / dx;
let dpy = (phi_yp - old[idx]) / dx;
let dmy = (old[idx] - phi_ym) / dx;
let g = if s > 0.0 {
(dmx.max(0.0).powi(2)
+ dpx.min(0.0).powi(2)
+ dmy.max(0.0).powi(2)
+ dpy.min(0.0).powi(2))
.sqrt()
} else {
(dpx.max(0.0).powi(2)
+ dmx.min(0.0).powi(2)
+ dpy.max(0.0).powi(2)
+ dmy.min(0.0).powi(2))
.sqrt()
};
self.phi[idx] = old[idx] - dt * s * (g - 1.0);
}
}
}
}
pub fn perimeter(&self) -> f64 {
let nx = self.nx;
let ny = self.ny;
let mut count = 0usize;
for j in 0..ny {
for i in 0..nx {
let v = self.phi[j * nx + i];
if i + 1 < nx && v * self.phi[j * nx + i + 1] < 0.0 {
count += 1;
}
if j + 1 < ny && v * self.phi[(j + 1) * nx + i] < 0.0 {
count += 1;
}
}
}
count as f64 * self.dx
}
}
#[derive(Debug, Clone)]
pub struct LevelSetField {
pub nx: usize,
pub ny: usize,
pub dx: f64,
pub phi: Vec<f64>,
}
impl LevelSetField {
pub fn new(nx: usize, ny: usize, dx: f64) -> Self {
Self {
nx,
ny,
dx,
phi: vec![0.0; nx * ny],
}
}
pub fn from_density(rho: &[f64], nx: usize, ny: usize, dx: f64) -> Self {
assert_eq!(rho.len(), nx * ny);
let phi = rho
.iter()
.map(|&r| if r > 0.5 { dx } else { -dx })
.collect();
Self { nx, ny, dx, phi }
}
#[inline]
fn upwind_diffs(
phi: &[f64],
nx: usize,
ny: usize,
dx: f64,
i: usize,
j: usize,
) -> (f64, f64, f64, f64) {
let idx = j * nx + i;
let phi_xp = if i + 1 < nx {
phi[j * nx + i + 1]
} else {
phi[idx]
};
let phi_xm = if i > 0 { phi[j * nx + i - 1] } else { phi[idx] };
let phi_yp = if j + 1 < ny {
phi[(j + 1) * nx + i]
} else {
phi[idx]
};
let phi_ym = if j > 0 {
phi[(j - 1) * nx + i]
} else {
phi[idx]
};
let dpx = (phi_xp - phi[idx]) / dx; let dmx = (phi[idx] - phi_xm) / dx; let dpy = (phi_yp - phi[idx]) / dx; let dmy = (phi[idx] - phi_ym) / dx; (dpx, dmx, dpy, dmy)
}
pub fn reinitialize(&mut self, n_iter: usize) {
let phi0 = self.phi.clone();
let nx = self.nx;
let ny = self.ny;
let dx = self.dx;
let dt = 0.5 * dx;
for _ in 0..n_iter {
let old = self.phi.clone();
for j in 0..ny {
for i in 0..nx {
let idx = j * nx + i;
let s = phi0[idx].signum();
if s == 0.0 {
continue;
}
let (dpx, dmx, dpy, dmy) = Self::upwind_diffs(&old, nx, ny, dx, i, j);
let grad_sq = if s > 0.0 {
dmx.max(0.0).powi(2)
+ dpx.min(0.0).powi(2)
+ dmy.max(0.0).powi(2)
+ dpy.min(0.0).powi(2)
} else {
dpx.max(0.0).powi(2)
+ dmx.min(0.0).powi(2)
+ dpy.max(0.0).powi(2)
+ dmy.min(0.0).powi(2)
};
self.phi[idx] = old[idx] - dt * s * (grad_sq.sqrt() - 1.0);
}
}
}
}
pub fn to_density(&self) -> Vec<f64> {
use std::f64::consts::PI;
let eps = 1.5 * self.dx;
self.phi
.iter()
.map(|&p| {
if p < -eps {
0.0
} else if p > eps {
1.0
} else {
0.5 * (1.0 + p / eps + (PI * p / eps).sin() / PI)
}
})
.collect()
}
pub fn evolve(&mut self, vel: &[f64], dt: f64) {
assert_eq!(vel.len(), self.phi.len(), "vel length must equal nx*ny");
let nx = self.nx;
let ny = self.ny;
let dx = self.dx;
let old = self.phi.clone();
for j in 0..ny {
for i in 0..nx {
let idx = j * nx + i;
let vn = vel[idx];
let (dpx, dmx, dpy, dmy) = Self::upwind_diffs(&old, nx, ny, dx, i, j);
let grad_sq = if vn > 0.0 {
dmx.max(0.0).powi(2)
+ dpx.min(0.0).powi(2)
+ dmy.max(0.0).powi(2)
+ dpy.min(0.0).powi(2)
} else {
dpx.max(0.0).powi(2)
+ dmx.min(0.0).powi(2)
+ dpy.max(0.0).powi(2)
+ dmy.min(0.0).powi(2)
};
self.phi[idx] = old[idx] - dt * vn * grad_sq.sqrt();
}
}
}
}
pub fn extend_velocity_upwind(phi: &[f64], vel: &[f64], nx: usize, ny: usize, dx: f64) -> Vec<f64> {
assert_eq!(phi.len(), nx * ny);
assert_eq!(vel.len(), nx * ny);
let dt = 0.5 * dx;
let n_iter = 5usize;
let mut f = vel.to_vec();
for _ in 0..n_iter {
let f_old = f.clone();
for j in 0..ny {
for i in 0..nx {
let idx = j * nx + i;
let s = phi[idx].signum();
if s == 0.0 {
continue;
}
let phi_xp = if i + 1 < nx {
phi[j * nx + i + 1]
} else {
phi[idx]
};
let phi_xm = if i > 0 { phi[j * nx + i - 1] } else { phi[idx] };
let phi_yp = if j + 1 < ny {
phi[(j + 1) * nx + i]
} else {
phi[idx]
};
let phi_ym = if j > 0 {
phi[(j - 1) * nx + i]
} else {
phi[idx]
};
let gx = (phi_xp - phi_xm) / (2.0 * dx);
let gy = (phi_yp - phi_ym) / (2.0 * dx);
let gmag = (gx * gx + gy * gy).sqrt().max(1e-30);
let nx_hat = gx / gmag;
let ny_hat = gy / gmag;
let ax = s * nx_hat;
let ay = s * ny_hat;
let df_dx = if ax > 0.0 {
if i > 0 {
(f_old[idx] - f_old[j * nx + i - 1]) / dx
} else {
0.0
}
} else if i + 1 < nx {
(f_old[j * nx + i + 1] - f_old[idx]) / dx
} else {
0.0
};
let df_dy = if ay > 0.0 {
if j > 0 {
(f_old[idx] - f_old[(j - 1) * nx + i]) / dx
} else {
0.0
}
} else if j + 1 < ny {
(f_old[(j + 1) * nx + i] - f_old[idx]) / dx
} else {
0.0
};
f[idx] = f_old[idx] - dt * (ax * df_dx + ay * df_dy);
}
}
}
f
}
pub fn curvature_field(phi: &[f64], nx: usize, ny: usize, dx: f64) -> Vec<f64> {
assert_eq!(phi.len(), nx * ny);
let eps = 1e-6_f64;
let mut kappa = vec![0.0_f64; nx * ny];
for j in 0..ny {
for i in 0..nx {
let idx = j * nx + i;
let phixp = if i + 1 < nx {
phi[j * nx + i + 1]
} else {
phi[idx]
};
let phixm = if i > 0 { phi[j * nx + i - 1] } else { phi[idx] };
let phiyp = if j + 1 < ny {
phi[(j + 1) * nx + i]
} else {
phi[idx]
};
let phiym = if j > 0 {
phi[(j - 1) * nx + i]
} else {
phi[idx]
};
let phixpyp = if i + 1 < nx && j + 1 < ny {
phi[(j + 1) * nx + i + 1]
} else if i + 1 < nx {
phi[j * nx + i + 1]
} else if j + 1 < ny {
phi[(j + 1) * nx + i]
} else {
phi[idx]
};
let phixmyp = if i > 0 && j + 1 < ny {
phi[(j + 1) * nx + i - 1]
} else if i > 0 {
phi[j * nx + i - 1]
} else if j + 1 < ny {
phi[(j + 1) * nx + i]
} else {
phi[idx]
};
let phixpym = if i + 1 < nx && j > 0 {
phi[(j - 1) * nx + i + 1]
} else if i + 1 < nx {
phi[j * nx + i + 1]
} else if j > 0 {
phi[(j - 1) * nx + i]
} else {
phi[idx]
};
let phixmym = if i > 0 && j > 0 {
phi[(j - 1) * nx + i - 1]
} else if i > 0 {
phi[j * nx + i - 1]
} else if j > 0 {
phi[(j - 1) * nx + i]
} else {
phi[idx]
};
let phi_c = phi[idx];
let phi_x = (phixp - phixm) / (2.0 * dx);
let phi_y = (phiyp - phiym) / (2.0 * dx);
let phi_xx = (phixp - 2.0 * phi_c + phixm) / (dx * dx);
let phi_yy = (phiyp - 2.0 * phi_c + phiym) / (dx * dx);
let phi_xy = (phixpyp - phixmyp - phixpym + phixmym) / (4.0 * dx * dx);
let grad_sq = phi_x * phi_x + phi_y * phi_y;
let grad_mag = grad_sq.sqrt().max(eps);
let numerator =
phi_xx * phi_y * phi_y - 2.0 * phi_x * phi_y * phi_xy + phi_yy * phi_x * phi_x;
kappa[idx] = numerator / (grad_mag * grad_mag * grad_mag);
}
}
kappa
}
pub fn regularization_velocity(
phi: &[f64],
nx: usize,
ny: usize,
dx: f64,
kappa_weight: f64,
) -> Vec<f64> {
let kappa = curvature_field(phi, nx, ny, dx);
kappa.into_iter().map(|k| kappa_weight * k).collect()
}
#[derive(Debug, Clone)]
pub struct ParametricShape {
pub x: Vec<f64>,
pub y: Vec<f64>,
}
impl ParametricShape {
pub fn circle(cx: f64, cy: f64, radius: f64, n_pts: usize) -> Self {
use std::f64::consts::PI;
let x = (0..n_pts)
.map(|i| cx + radius * (2.0 * PI * i as f64 / n_pts as f64).cos())
.collect();
let y = (0..n_pts)
.map(|i| cy + radius * (2.0 * PI * i as f64 / n_pts as f64).sin())
.collect();
Self { x, y }
}
pub fn perimeter(&self) -> f64 {
let n = self.x.len();
(0..n)
.map(|i| {
let j = (i + 1) % n;
let dx = self.x[j] - self.x[i];
let dy = self.y[j] - self.y[i];
(dx * dx + dy * dy).sqrt()
})
.sum()
}
pub fn area(&self) -> f64 {
let n = self.x.len();
let sum: f64 = (0..n)
.map(|i| {
let j = (i + 1) % n;
self.x[i] * self.y[j] - self.x[j] * self.y[i]
})
.sum();
(sum / 2.0).abs()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn level_set_solid_volume_one() {
let ls = LevelSet::solid(10, 10, 1e-6);
assert!((ls.volume_fraction() - 1.0).abs() < 1e-10);
}
#[test]
fn level_set_void_volume_zero() {
let ls = LevelSet::void(10, 10, 1e-6);
assert!(ls.volume_fraction() == 0.0);
}
#[test]
fn level_set_circle_volume_fraction() {
let n = 50;
let dx = 1e-6;
let r = 15e-6;
let c = n as f64 / 2.0 * dx;
let ls = LevelSet::circle(n, n, dx, c, c, r);
let vf = ls.volume_fraction();
assert!(vf > 0.1 && vf < 0.5, "vf={vf:.3}");
}
#[test]
fn level_set_advance_does_not_panic() {
let mut ls = LevelSet::circle(20, 20, 1e-6, 10e-6, 10e-6, 5e-6);
let v_n = vec![1.0; ls.phi.len()];
ls.advance(&v_n, 0.1e-6);
}
#[test]
fn level_set_perimeter_positive() {
let ls = LevelSet::circle(20, 20, 1e-6, 10e-6, 10e-6, 5e-6);
assert!(ls.perimeter() > 0.0);
}
#[test]
fn parametric_circle_area() {
let shape = ParametricShape::circle(0.0, 0.0, 1.0, 360);
let area = shape.area();
assert!((area - std::f64::consts::PI).abs() < 0.1, "area={area:.3}");
}
#[test]
fn parametric_circle_perimeter() {
let shape = ParametricShape::circle(0.0, 0.0, 1.0, 360);
let perim = shape.perimeter();
assert!(
(perim - 2.0 * std::f64::consts::PI).abs() < 0.1,
"perim={perim:.3}"
);
}
#[test]
fn reinitialize_does_not_panic() {
let mut ls = LevelSet::circle(10, 10, 1e-6, 5e-6, 5e-6, 3e-6);
ls.reinitialize(3);
}
#[test]
fn level_set_circle_distance() {
let nx = 40;
let ny = 40;
let dx = 1.0; let cx = 20.0;
let cy = 20.0;
let radius = 8.0;
let mut phi = vec![0.0f64; nx * ny];
for j in 0..ny {
for i in 0..nx {
let x = i as f64;
let y = j as f64;
let d = ((x - cx).powi(2) + (y - cy).powi(2)).sqrt();
phi[j * nx + i] = radius - d;
}
}
let lsf = LevelSetField { nx, ny, dx, phi };
let phi_center = lsf.phi[20 * nx + 20];
assert!(phi_center > 0.0, "phi at center = {phi_center}");
let phi_exterior = lsf.phi[0];
assert!(phi_exterior < 0.0, "phi at exterior = {phi_exterior}");
}
#[test]
fn level_set_to_density() {
let nx = 40;
let ny = 40;
let dx = 1.0;
let cx = 20.0;
let cy = 20.0;
let radius = 8.0;
let mut phi = vec![0.0f64; nx * ny];
for j in 0..ny {
for i in 0..nx {
let x = i as f64;
let y = j as f64;
let d = ((x - cx).powi(2) + (y - cy).powi(2)).sqrt();
phi[j * nx + i] = radius - d;
}
}
let lsf = LevelSetField { nx, ny, dx, phi };
let density = lsf.to_density();
let rho_center = density[20 * nx + 20];
assert!(rho_center > 0.99, "density at center = {rho_center}");
let rho_exterior = density[0];
assert!(rho_exterior < 0.01, "density at exterior = {rho_exterior}");
}
#[test]
fn curvature_uniform_zero() {
let nx = 10;
let ny = 10;
let dx = 1e-6;
let phi = vec![1.0f64; nx * ny];
let kappa = curvature_field(&phi, nx, ny, dx);
for &k in &kappa {
assert!(
k.abs() < 1e-10,
"expected zero curvature for uniform phi, got {k}"
);
}
}
#[test]
fn extend_velocity_length() {
let nx = 12;
let ny = 12;
let dx = 1e-6;
let mut phi = vec![0.0f64; nx * ny];
for j in 0..ny {
for i in 0..nx {
let x = i as f64 * dx - 6e-6;
let y = j as f64 * dx - 6e-6;
phi[j * nx + i] = 3e-6 - (x * x + y * y).sqrt();
}
}
let vel = vec![1.0f64; nx * ny];
let ext = extend_velocity_upwind(&phi, &vel, nx, ny, dx);
assert_eq!(ext.len(), nx * ny);
}
#[test]
fn regularization_velocity_scaling() {
let nx = 20;
let ny = 20;
let dx = 1.0;
let cx = 10.0;
let cy = 10.0;
let radius = 5.0;
let phi: Vec<f64> = (0..ny)
.flat_map(|j| {
(0..nx).map(move |i| {
let d = ((i as f64 - cx).powi(2) + (j as f64 - cy).powi(2)).sqrt();
radius - d
})
})
.collect();
let v1 = regularization_velocity(&phi, nx, ny, dx, 1.0);
let v2 = regularization_velocity(&phi, nx, ny, dx, 2.0);
for (a, b) in v1.iter().zip(v2.iter()) {
let ratio = if a.abs() > 1e-12 { b / a } else { 2.0 };
assert!((ratio - 2.0).abs() < 1e-10, "ratio={ratio}");
}
}
#[test]
fn level_set_field_evolve_changes_phi() {
let nx = 10;
let ny = 10;
let dx = 1e-6;
let mut lsf = LevelSetField::new(nx, ny, dx);
for j in 0..ny {
for i in 0..nx {
lsf.phi[j * nx + i] = (i as f64 - 5.0) * dx;
}
}
let phi_before = lsf.phi.clone();
let vel = vec![1.0f64; nx * ny];
lsf.evolve(&vel, 0.1e-6);
let changed = phi_before
.iter()
.zip(lsf.phi.iter())
.any(|(a, b)| (a - b).abs() > 1e-20);
assert!(changed, "evolve did not change phi");
}
#[test]
fn level_set_field_from_density_all_material() {
let nx = 5;
let ny = 5;
let dx = 1e-6;
let rho = vec![1.0f64; nx * ny];
let lsf = LevelSetField::from_density(&rho, nx, ny, dx);
assert!(lsf.phi.iter().all(|&p| p > 0.0));
}
#[test]
fn level_set_field_from_density_all_void() {
let nx = 5;
let ny = 5;
let dx = 1e-6;
let rho = vec![0.0f64; nx * ny];
let lsf = LevelSetField::from_density(&rho, nx, ny, dx);
assert!(lsf.phi.iter().all(|&p| p < 0.0));
}
#[test]
fn level_set_field_reinitialize_grad_near_one() {
let nx = 30;
let ny = 30;
let dx = 1.0;
let cx = 15.0;
let cy = 15.0;
let radius = 8.0;
let mut phi: Vec<f64> = (0..ny)
.flat_map(|j| {
(0..nx).map(move |i| {
let d2 = (i as f64 - cx).powi(2) + (j as f64 - cy).powi(2);
radius * radius - d2
})
})
.collect();
let mut lsf = LevelSetField {
nx,
ny,
dx,
phi: phi.clone(),
};
lsf.reinitialize(30);
phi = lsf.phi.clone();
let mut max_err = 0.0_f64;
for j in 2..ny - 2 {
for i in 2..nx - 2 {
let idx = j * nx + i;
if phi[idx].abs() > 3.0 * dx {
continue;
}
let gx = (phi[j * nx + i + 1] - phi[j * nx + i - 1]) / (2.0 * dx);
let gy = (phi[(j + 1) * nx + i] - phi[(j - 1) * nx + i]) / (2.0 * dx);
let grad_mag = (gx * gx + gy * gy).sqrt();
let err = (grad_mag - 1.0).abs();
if err > max_err {
max_err = err;
}
}
}
assert!(
max_err < 0.25,
"max |∇φ| error near interface = {max_err:.4}"
);
}
}