1use crate::amr::quadtree::{CellData, CellId, Morton2D, QuadTree};
30use std::collections::HashMap;
31
32#[inline]
42fn sign_smooth(phi: f64, eps: f64) -> f64 {
43 phi / (phi * phi + eps * eps).sqrt()
44}
45
46fn godunov_gradient_magnitude(
57 phi_c: f64,
58 phi_xm: f64,
59 phi_xp: f64,
60 phi_ym: f64,
61 phi_yp: f64,
62 dx: f64,
63 dy: f64,
64 sign: f64,
65) -> f64 {
66 let dm_x = (phi_c - phi_xm) / dx;
67 let dp_x = (phi_xp - phi_c) / dx;
68 let dm_y = (phi_c - phi_ym) / dy;
69 let dp_y = (phi_yp - phi_c) / dy;
70
71 let ax = if sign > 0.0 {
73 f64::max(f64::max(dm_x, 0.0).powi(2), f64::min(dp_x, 0.0).powi(2))
74 } else {
75 f64::max(f64::min(dm_x, 0.0).powi(2), f64::max(dp_x, 0.0).powi(2))
76 };
77 let ay = if sign > 0.0 {
78 f64::max(f64::max(dm_y, 0.0).powi(2), f64::min(dp_y, 0.0).powi(2))
79 } else {
80 f64::max(f64::min(dm_y, 0.0).powi(2), f64::max(dp_y, 0.0).powi(2))
81 };
82 (ax + ay).sqrt()
83}
84
85pub struct LevelSet {
98 pub tree: QuadTree,
100 pub delta: f64,
102}
103
104impl LevelSet {
105 pub fn new(tree: QuadTree, phi0: impl Fn(f64, f64) -> f64) -> Self {
111 let mut ls = LevelSet { tree, delta: 3.0 };
112 let leaves: Vec<CellId> = ls.tree.leaves();
114 for id in leaves {
115 if let Some(cell) = ls.tree.cells.get(&id) {
116 let (cx, cy) = cell.center(&ls.tree.domain);
117 let phi = phi0(cx, cy);
118 let n_vars = ls.tree.n_vars;
119 let mut vals = vec![0.0f64; n_vars];
120 vals[0] = phi;
121 if let Some(cell) = ls.tree.cells.get_mut(&id) {
123 cell.values = vals;
124 }
125 }
126 }
127 let coarsest_dx = ls.tree.dx_at_level(0);
129 ls.delta = 3.0 * coarsest_dx;
130 ls
131 }
132
133 pub fn set_delta(&mut self, delta: f64) {
135 self.delta = delta;
136 }
137
138 pub fn phi(&self, id: CellId) -> Option<f64> {
140 self.tree.cells.get(&id).map(|c| c.values[0])
141 }
142
143 fn phi_snapshot(&self) -> HashMap<CellId, f64> {
145 self.tree
146 .leaves()
147 .into_iter()
148 .filter_map(|id| {
149 let phi = self.tree.cells.get(&id)?.values[0];
150 Some((id, phi))
151 })
152 .collect()
153 }
154
155 fn get_phi_or_ancestor(snap: &HashMap<CellId, f64>, tree: &QuadTree, id: CellId) -> f64 {
157 if let Some(&phi) = snap.get(&id) {
158 return phi;
159 }
160 let mut cur = id;
162 loop {
163 match cur.parent() {
164 None => return 0.0,
165 Some(p) => {
166 if let Some(&phi) = snap.get(&p) {
167 return phi;
168 }
169 cur = p;
170 }
171 }
172 }
173 }
174
175 fn neighbour_phi(&self, id: CellId, snap: &HashMap<CellId, f64>, di: i64, dj: i64) -> f64 {
181 let lv = id.level();
182 let size = 1u32.checked_shl(lv as u32).unwrap_or(u32::MAX);
183 let (ix, iy) = id.grid_coords();
184 let ni = ix as i64 + di;
185 let nj = iy as i64 + dj;
186 if ni < 0 || nj < 0 || ni >= size as i64 || nj >= size as i64 {
187 return snap.get(&id).copied().unwrap_or(0.0);
189 }
190 let nbr_id = CellId::new(lv, Morton2D::encode(ni as u32, nj as u32));
191 Self::get_phi_or_ancestor(snap, &self.tree, nbr_id)
192 }
193
194 pub fn reinitialize(&mut self, n_iters: usize) {
199 for _ in 0..n_iters {
200 let snap = self.phi_snapshot();
201 let leaves: Vec<CellId> = self.tree.leaves().into_iter().collect();
202
203 let mut updates: Vec<(CellId, f64)> = Vec::new();
204
205 for &id in &leaves {
206 let phi_c = match snap.get(&id) {
207 Some(&v) => v,
208 None => continue,
209 };
210 if phi_c.abs() >= self.delta {
212 continue;
213 }
214
215 let lv = id.level();
216 let dx = self.tree.dx_at_level(lv);
217 let dy = self.tree.dy_at_level(lv);
218 let eps = 0.5 * dx.min(dy);
219
220 let sign0 = sign_smooth(phi_c, eps);
221
222 let phi_xm = self.neighbour_phi(id, &snap, -1, 0);
223 let phi_xp = self.neighbour_phi(id, &snap, 1, 0);
224 let phi_ym = self.neighbour_phi(id, &snap, 0, -1);
225 let phi_yp = self.neighbour_phi(id, &snap, 0, 1);
226
227 let grad_mag = godunov_gradient_magnitude(
228 phi_c, phi_xm, phi_xp, phi_ym, phi_yp, dx, dy, sign0,
229 );
230
231 let dt = 0.5 * dx.min(dy);
233 let new_phi = phi_c - dt * sign0 * (grad_mag - 1.0);
234 updates.push((id, new_phi));
235 }
236
237 for (id, new_phi) in updates {
239 if let Some(cell) = self.tree.cells.get_mut(&id) {
240 cell.values[0] = new_phi;
241 }
242 }
243 }
244 }
245
246 pub fn advect(
252 &mut self,
253 vx: &dyn Fn(f64, f64, f64) -> f64,
254 vy: &dyn Fn(f64, f64, f64) -> f64,
255 t: f64,
256 dt: f64,
257 ) {
258 let snap = self.phi_snapshot();
259 let leaves: Vec<CellId> = self.tree.leaves();
260 let mut updates: Vec<(CellId, f64)> = Vec::new();
261
262 for &id in &leaves {
263 let phi_c = match snap.get(&id) {
264 Some(&v) => v,
265 None => continue,
266 };
267 if phi_c.abs() >= self.delta {
268 continue;
269 }
270
271 let lv = id.level();
272 let dx = self.tree.dx_at_level(lv);
273 let dy = self.tree.dy_at_level(lv);
274
275 let cell = match self.tree.cells.get(&id) {
276 Some(c) => c,
277 None => continue,
278 };
279 let (cx, cy) = cell.center(&self.tree.domain);
280 let u = vx(cx, cy, t);
281 let v_y = vy(cx, cy, t);
282
283 let dphi_x = if u >= 0.0 {
285 let phi_xm = self.neighbour_phi(id, &snap, -1, 0);
286 (phi_c - phi_xm) / dx
287 } else {
288 let phi_xp = self.neighbour_phi(id, &snap, 1, 0);
289 (phi_xp - phi_c) / dx
290 };
291 let dphi_y = if v_y >= 0.0 {
292 let phi_ym = self.neighbour_phi(id, &snap, 0, -1);
293 (phi_c - phi_ym) / dy
294 } else {
295 let phi_yp = self.neighbour_phi(id, &snap, 0, 1);
296 (phi_yp - phi_c) / dy
297 };
298
299 let new_phi = phi_c - dt * (u * dphi_x + v_y * dphi_y);
300 updates.push((id, new_phi));
301 }
302
303 for (id, new_phi) in updates {
304 if let Some(cell) = self.tree.cells.get_mut(&id) {
305 cell.values[0] = new_phi;
306 }
307 }
308 }
309
310 pub fn interface_cells(&self) -> Vec<CellId> {
316 let snap = self.phi_snapshot();
317 let mut result = Vec::new();
318
319 for (&id, &phi_c) in &snap {
320 if phi_c.abs() >= self.delta {
321 continue;
322 }
323 let nbrs = self.tree.neighbors_of(id);
324 let crosses_zero = nbrs.iter().any(|&nbr| {
325 let phi_n = Self::get_phi_or_ancestor(&snap, &self.tree, nbr);
326 phi_c * phi_n < 0.0
327 });
328 if crosses_zero {
329 result.push(id);
330 }
331 }
332 result
333 }
334
335 pub fn gradient(&self, id: CellId) -> Option<(f64, f64)> {
337 let snap = self.phi_snapshot();
338 let phi_c = *snap.get(&id)?;
339 let lv = id.level();
340 let dx = self.tree.dx_at_level(lv);
341 let dy = self.tree.dy_at_level(lv);
342
343 let phi_xm = self.neighbour_phi(id, &snap, -1, 0);
344 let phi_xp = self.neighbour_phi(id, &snap, 1, 0);
345 let phi_ym = self.neighbour_phi(id, &snap, 0, -1);
346 let phi_yp = self.neighbour_phi(id, &snap, 0, 1);
347
348 let gx = if (phi_xm - phi_c).abs() < 1e-100 && (phi_xp - phi_c).abs() < 1e-100 {
350 0.0
351 } else {
352 (phi_xp - phi_xm) / (2.0 * dx)
353 };
354 let gy = if (phi_ym - phi_c).abs() < 1e-100 && (phi_yp - phi_c).abs() < 1e-100 {
355 0.0
356 } else {
357 (phi_yp - phi_ym) / (2.0 * dy)
358 };
359 Some((gx, gy))
360 }
361
362 fn min_dx(&self) -> f64 {
364 self.tree
365 .leaves()
366 .iter()
367 .map(|id| self.tree.dx_at_level(id.level()))
368 .fold(f64::INFINITY, f64::min)
369 }
370
371 pub fn narrow_band_count(&self) -> usize {
373 self.tree
374 .leaves()
375 .iter()
376 .filter(|&&id| {
377 self.tree
378 .cells
379 .get(&id)
380 .map(|c| c.values[0].abs() < self.delta)
381 .unwrap_or(false)
382 })
383 .count()
384 }
385
386 pub fn cell_data(&self, id: CellId) -> Option<&CellData> {
388 self.tree.cells.get(&id)
389 }
390}
391
392#[cfg(test)]
397mod tests {
398 use super::*;
399
400 fn uniform_tree(k: u8, n_vars: usize) -> QuadTree {
402 let mut tree = QuadTree::new([-1.0, 1.0, -1.0, 1.0], k, n_vars);
403 for _ in 0..k {
404 let current_leaves: Vec<_> = tree.leaves();
405 for id in current_leaves {
406 tree.refine_cell(id);
407 }
408 }
409 tree
410 }
411
412 #[test]
413 fn test_level_set_circle_signs() {
414 let tree = uniform_tree(2, 1);
416 let ls = LevelSet::new(tree, |x, y| (x * x + y * y).sqrt() - 0.5);
417 let near_origin = ls
419 .tree
420 .leaves()
421 .iter()
422 .find(|&&id| {
423 ls.tree
424 .cells
425 .get(&id)
426 .map(|c| {
427 let (cx, cy) = c.center(&ls.tree.domain);
428 cx.abs() < 0.25 && cy.abs() < 0.25
429 })
430 .unwrap_or(false)
431 })
432 .copied();
433 if let Some(id) = near_origin {
434 assert!(ls.phi(id).unwrap_or(0.0) < 0.0, "inside circle → φ < 0");
435 }
436 let far_corner = ls
438 .tree
439 .leaves()
440 .iter()
441 .find(|&&id| {
442 ls.tree
443 .cells
444 .get(&id)
445 .map(|c| {
446 let (cx, cy) = c.center(&ls.tree.domain);
447 cx > 0.7 && cy > 0.7
448 })
449 .unwrap_or(false)
450 })
451 .copied();
452 if let Some(id) = far_corner {
453 assert!(ls.phi(id).unwrap_or(0.0) > 0.0, "outside circle → φ > 0");
454 }
455 }
456
457 #[test]
458 fn test_reinitialize_preserves_zero_set() {
459 let tree = uniform_tree(3, 1);
460 let mut ls = LevelSet::new(tree, |x, y| (x * x + y * y).sqrt() - 0.5);
461 let ifaces_before: Vec<_> = ls.interface_cells();
463 assert!(!ifaces_before.is_empty(), "should have interface cells");
464
465 ls.reinitialize(5);
466
467 let ifaces_after = ls.interface_cells();
470 for id in &ifaces_after {
471 let phi = ls.phi(*id).unwrap_or(f64::INFINITY);
472 let dx = ls.tree.dx_at_level(id.level());
474 assert!(
475 phi.abs() < 2.0 * dx + 1e-6,
476 "interface cell has |φ|={} > 2*dx={} after reinit",
477 phi.abs(),
478 2.0 * dx
479 );
480 }
481 }
482
483 #[test]
484 fn test_interface_cells_small_phi() {
485 let tree = uniform_tree(3, 1);
486 let ls = LevelSet::new(tree, |x, y| (x * x + y * y).sqrt() - 0.5);
487 let ifaces = ls.interface_cells();
488 assert!(!ifaces.is_empty(), "circle should produce interface cells");
489 for id in &ifaces {
490 let phi = ls.phi(*id).unwrap_or(f64::INFINITY);
491 let dx = ls.tree.dx_at_level(id.level());
492 assert!(
493 phi.abs() < 2.0 * dx + 1e-6,
494 "interface cell |φ|={} should be < 2*dx={}",
495 phi.abs(),
496 2.0 * dx
497 );
498 }
499 }
500
501 #[test]
502 fn test_advect_translates_interface() {
503 let tree = uniform_tree(3, 1);
505 let mut ls = LevelSet::new(tree, |x, _y| x); let leaves = ls.tree.leaves();
507 let dt = 0.05;
508 ls.advect(&|_, _, _| 1.0, &|_, _, _| 0.0, 0.0, dt);
510 let near_dt: Vec<_> = leaves
512 .iter()
513 .filter(|&&id| {
514 ls.tree
515 .cells
516 .get(&id)
517 .map(|c| {
518 let (cx, _) = c.center(&ls.tree.domain);
519 (cx - dt).abs() < 0.15
520 })
521 .unwrap_or(false)
522 })
523 .collect();
524 let any_small: bool = near_dt
525 .iter()
526 .any(|&&id| ls.phi(id).map(|phi| phi.abs() < 0.25).unwrap_or(false));
527 assert!(any_small, "interface should have moved towards x=dt");
528 }
529
530 #[test]
531 fn test_narrow_band_count() {
532 let tree = uniform_tree(4, 1);
535 let mut ls = LevelSet::new(tree, |x, y| (x * x + y * y).sqrt() - 0.5);
536 let fine_dx = ls.tree.dx_at_level(4);
538 ls.set_delta(3.0 * fine_dx);
539 let nb = ls.narrow_band_count();
540 assert!(nb > 0, "should have narrow-band cells around the circle");
541 let total = ls.tree.leaves().len();
542 assert!(
543 nb < total,
544 "narrow band ({nb}) should be smaller than total leaves ({total})"
545 );
546 }
547
548 #[test]
549 fn test_sign_smooth_properties() {
550 assert!(sign_smooth(1.0, 0.1) > 0.0);
552 assert!(sign_smooth(-1.0, 0.1) < 0.0);
553 assert_eq!(sign_smooth(0.0, 0.1), 0.0);
554 }
555}