use crate::error::StaticError;
use crate::grid::geometry::{CornerPointGeom, Pillar};
use crate::grid::grid::Grid;
use crate::grid::index::Dims;
use crate::grid::point::Point3;
#[derive(Debug, Clone, Copy)]
pub struct BoxSpec {
pub area_m2: f64,
pub gross_height_m: f64,
pub dims: Dims,
pub top_depth_m: f64,
pub aspect_ratio: f64,
}
impl BoxSpec {
#[must_use]
pub fn square(area_m2: f64, gross_height_m: f64, dims: Dims) -> Self {
Self {
area_m2,
gross_height_m,
dims,
top_depth_m: 0.0,
aspect_ratio: 1.0,
}
}
}
pub fn build_box(spec: BoxSpec) -> Result<Grid, StaticError> {
fn positive(x: f64) -> bool {
x.is_finite() && x > 0.0
}
let require = |ok: bool, what: &str, value: f64| -> Result<(), StaticError> {
if ok {
Ok(())
} else {
Err(StaticError::InvalidInput(format!(
"{what} must be a finite value > 0, got {value}"
)))
}
};
require(positive(spec.area_m2), "area", spec.area_m2)?;
require(
positive(spec.gross_height_m),
"gross height",
spec.gross_height_m,
)?;
require(
positive(spec.aspect_ratio),
"aspect ratio",
spec.aspect_ratio,
)?;
let dims = spec.dims;
let area_m2 = spec.area_m2;
let lx = (area_m2 * spec.aspect_ratio).sqrt();
let ly = (area_m2 / spec.aspect_ratio).sqrt();
let dx = lx / dims.ni as f64;
let dy = ly / dims.nj as f64;
let dz = spec.gross_height_m / dims.nk as f64;
let z_top = spec.top_depth_m;
let z_bot = z_top + spec.gross_height_m;
let mut coord = Vec::with_capacity(dims.pillar_count());
for jp in 0..=dims.nj {
for ip in 0..=dims.ni {
let x = ip as f64 * dx;
let y = jp as f64 * dy;
coord.push(Pillar {
top: Point3::new(x, y, z_top),
bottom: Point3::new(x, y, z_bot),
});
}
}
let mut zcorn = Vec::with_capacity(dims.cell_count() * 8);
for c in dims.iter() {
let zt = z_top + c.k as f64 * dz;
let zb = zt + dz;
zcorn.extend_from_slice(&[zt, zt, zt, zt, zb, zb, zb, zb]);
}
Ok(Grid::new(CornerPointGeom::new(dims, coord, zcorn)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grid::index::Ijk;
#[test]
fn bulk_volume_equals_area_times_height() {
let spec = BoxSpec::square(400_000.0, 50.0, Dims::new(10, 8, 5).unwrap());
let grid = build_box(spec).unwrap();
let expected = 400_000.0 * 50.0;
let v = grid.bulk_volume();
assert!(
(v - expected).abs() / expected < 1e-10,
"bulk volume {v} != {expected}"
);
}
#[test]
fn cell_count_matches_dims() {
let grid = build_box(BoxSpec::square(40.0, 30.0, Dims::new(4, 5, 6).unwrap())).unwrap();
assert_eq!(grid.cell_count(), 120);
}
#[test]
fn cells_are_uniform_cuboids() {
let dims = Dims::new(3, 3, 3).unwrap();
let grid = build_box(BoxSpec::square(90.0, 60.0, dims)).unwrap();
let v0 = grid.cell(Ijk::new(0, 0, 0)).volume();
for c in grid.cells() {
assert!(
(c.volume() - v0).abs() / v0 < 1e-9,
"non-uniform cell {c:?}"
);
}
}
#[test]
fn depth_increases_with_k() {
let dims = Dims::new(2, 2, 4).unwrap();
let grid = build_box(BoxSpec {
area_m2: 400_000.0,
gross_height_m: 80.0,
dims,
top_depth_m: 5000.0,
aspect_ratio: 1.0,
})
.unwrap();
let top = grid.cell(Ijk::new(0, 0, 0)).top_depth();
let deep = grid.cell(Ijk::new(0, 0, 3)).top_depth();
assert!((top - 5000.0).abs() < 1e-9);
assert!(deep > top);
assert!((grid.cell(Ijk::new(0, 0, 0)).dz() - 20.0).abs() < 1e-9);
}
#[test]
fn rejects_nonpositive_inputs() {
let d = Dims::new(2, 2, 2).unwrap();
assert!(build_box(BoxSpec::square(0.0, 10.0, d)).is_err());
assert!(build_box(BoxSpec::square(10.0, 0.0, d)).is_err());
}
}