#![allow(clippy::cast_precision_loss)]
#![allow(clippy::float_cmp)]
use super::{ClosureObstacle, ConstantObstacle, Obstacle, ObstacleChernoff};
use crate::{
chernoff::ChernoffFunction, diffusion::DiffusionChernoff, error::SemiflowError, grid::Grid1D,
grid_fn::GridFn1D, scratch::ScratchPool,
};
#[test]
fn constant_obstacle_finite_ok() {
assert!(ConstantObstacle::<f64>::new(-0.5).is_ok());
}
#[test]
fn constant_obstacle_nan_err() {
let err = ConstantObstacle::<f64>::new(f64::NAN).unwrap_err();
assert!(matches!(err, SemiflowError::DomainViolation { .. }));
}
#[test]
fn constant_projection_lifts_below_floor() {
let grid = Grid1D::new(0.0_f64, 1.0, 5).unwrap();
let mut u = GridFn1D::from_fn(grid, |x| x - 0.5); let obs = ConstantObstacle::new(0.0_f64).unwrap();
obs.project_in_place(&mut u).unwrap();
for &v in &u.values {
assert!(v >= 0.0, "value {v} below obstacle 0");
}
assert_eq!(u.values[4], 0.5);
}
#[test]
fn closure_projection_matches_pointwise_max() {
let grid = Grid1D::new(0.0_f64, 1.0, 7).unwrap();
let mut u = GridFn1D::from_fn(grid, |_| 0.0_f64);
let g = |x: f64| 0.2 * (x - 0.5); let obs = ClosureObstacle::new(g);
obs.project_in_place(&mut u).unwrap();
for i in 0..grid.n {
let x = grid.x_at(i);
assert_eq!(u.values[i], 0.0_f64.max(g(x)));
}
}
#[test]
fn active_set_is_strict_continuation_set() {
let grid = Grid1D::new(0.0_f64, 1.0, 5).unwrap();
let w = GridFn1D::from_fn(grid, |x| x); let obs = ConstantObstacle::new(0.5_f64).unwrap();
let mut active = [false; 5];
obs.active_set_into(&w, &mut active).unwrap();
assert_eq!(active, [false, false, false, true, true]);
}
#[test]
fn active_set_length_mismatch_err() {
let grid = Grid1D::new(0.0_f64, 1.0, 5).unwrap();
let w = GridFn1D::from_fn(grid, |_| 1.0_f64);
let obs = ConstantObstacle::new(0.0_f64).unwrap();
let mut active = [false; 3];
let err = obs.active_set_into(&w, &mut active).unwrap_err();
assert!(matches!(err, SemiflowError::DomainViolation { .. }));
}
#[test]
fn obstacle_chernoff_projects_after_step() {
let grid = Grid1D::new(0.0_f64, 1.0, 9).unwrap();
let diff = DiffusionChernoff::new(|_| 0.5, |_| 0.0, |_| 0.0, 0.5, grid);
let obs = ConstantObstacle::new(0.3_f64).unwrap();
let kernel = ObstacleChernoff::new(diff, obs).unwrap();
let u0 = GridFn1D::from_fn(grid, |_| 0.1_f64); let mut dst = u0.zeroed_like();
let mut scratch = ScratchPool::new();
kernel
.apply_into(0.001, &u0, &mut dst, &mut scratch)
.unwrap();
for &v in &dst.values {
assert!(v >= 0.3 - 1e-12, "value {v} below obstacle 0.3");
}
assert_eq!(kernel.order(), 1);
}
#[test]
fn obstacle_chernoff_growth_inherits_inner() {
let grid = Grid1D::new(0.0_f64, 1.0, 5).unwrap();
let diff = DiffusionChernoff::new(|_| 0.5, |_| 0.0, |_| 0.0, 0.5, grid);
let inner_growth = diff.growth();
let obs = ConstantObstacle::new(-1.0_f64).unwrap();
let kernel = ObstacleChernoff::new(diff, obs).unwrap();
assert_eq!(kernel.growth(), inner_growth);
}
#[test]
fn active_set_adjoint_unsupported_inner_errs() {
let grid = Grid1D::new(0.0_f64, 1.0, 5).unwrap();
let diff = DiffusionChernoff::new(|_| 0.5, |_| 0.0, |_| 0.0, 0.5, grid);
let obs = ConstantObstacle::new(0.0_f64).unwrap();
let kernel = ObstacleChernoff::new(diff, obs).unwrap();
let w = GridFn1D::from_fn(grid, |_| 1.0_f64);
let lam = GridFn1D::from_fn(grid, |_| 1.0_f64);
let mut out = w.zeroed_like();
let mut scratch = ScratchPool::new();
let err = kernel
.apply_active_set_adjoint_into(0.01, &w, &lam, &mut out, &mut scratch)
.unwrap_err();
assert!(matches!(err, SemiflowError::UnsupportedOperation { .. }));
}