use crate::units::SrsError;
use petekstatic::model::{Input, PerturbationField, Sampler, TrendSurface};
use petektools::geostat::experimental_variogram;
use petektools::{Variogram, VariogramModel};
pub fn perturbation_field(
sd_m: f64,
model: VariogramModel,
range_m: f64,
) -> Result<PerturbationField, SrsError> {
let vgm = Variogram::new(model, 0.0, 1.0, range_m)?;
Ok(PerturbationField::new(sd_m, vgm))
}
#[derive(Debug, Clone)]
pub struct DistSpec {
sampler: Sampler,
clamp: Option<(f64, f64)>,
}
impl DistSpec {
fn wrap(sampler: Result<Sampler, petektools::AlgoError>) -> Result<Self, SrsError> {
Ok(Self {
sampler: sampler?,
clamp: None,
})
}
pub fn normal(mean: f64, sd: f64) -> Result<Self, SrsError> {
Self::wrap(Sampler::new_normal(mean, sd))
}
pub fn lognormal(mean: f64, sd: f64) -> Result<Self, SrsError> {
Self::wrap(Sampler::new_lognormal(mean, sd))
}
pub fn uniform(lo: f64, hi: f64) -> Result<Self, SrsError> {
Self::wrap(Sampler::new_uniform(lo, hi))
}
pub fn triangular(lo: f64, mode: f64, hi: f64) -> Result<Self, SrsError> {
Self::wrap(Sampler::new_triangular(lo, mode, hi))
}
pub fn truncated_normal(mean: f64, sd: f64, lo: f64, hi: f64) -> Result<Self, SrsError> {
Self::wrap(Sampler::new_truncated_normal(mean, sd, lo, hi))
}
pub fn level_shift(sd: f64) -> Result<Self, SrsError> {
Self::normal(0.0, sd)
}
pub fn constant(value: f64) -> Result<Self, SrsError> {
if !value.is_finite() {
return Err(SrsError::InvalidInput(format!(
"constant needs a finite value, got {value}"
)));
}
let width = (value.abs() * 1e-9).max(1e-9);
Self::uniform(value, value + width)
}
pub fn clamped(mut self, lo: f64, hi: f64) -> Result<Self, SrsError> {
if lo >= hi {
return Err(SrsError::InvalidInput(format!(
"clamp needs lo < hi, got [{lo}, {hi}]"
)));
}
self.clamp = Some((lo, hi));
Ok(self)
}
pub fn to_input(&self) -> Result<Input, SrsError> {
match self.clamp {
None => Ok(Input::plain(self.sampler)),
Some((lo, hi)) => Input::clamped(self.sampler, lo, hi).map_err(SrsError::from),
}
}
pub fn sample_vec<R: rand::Rng>(&self, n: usize, rng: &mut R) -> Vec<f64> {
let mut v = self.sampler.sample_n(n, rng);
if let Some((lo, hi)) = self.clamp {
for x in &mut v {
*x = x.clamp(lo, hi);
}
}
v
}
}
#[derive(Debug, Clone)]
pub struct VgmSpec {
variogram: Variogram,
}
impl VgmSpec {
fn build(
model: VariogramModel,
range_m: f64,
sill: f64,
nugget: f64,
) -> Result<Self, SrsError> {
Ok(Self {
variogram: Variogram::new(model, nugget, sill, range_m)?,
})
}
pub fn spherical(range_m: f64, sill: f64, nugget: f64) -> Result<Self, SrsError> {
Self::build(VariogramModel::Spherical, range_m, sill, nugget)
}
pub fn exponential(range_m: f64, sill: f64, nugget: f64) -> Result<Self, SrsError> {
Self::build(VariogramModel::Exponential, range_m, sill, nugget)
}
pub fn gaussian(range_m: f64, sill: f64, nugget: f64) -> Result<Self, SrsError> {
Self::build(VariogramModel::Gaussian, range_m, sill, nugget)
}
pub fn fit(
model: VariogramModel,
coords: &[[f64; 3]],
lag: f64,
n_lags: usize,
) -> Result<Self, SrsError> {
let exp = experimental_variogram(coords, lag, n_lags)?;
Ok(Self {
variogram: Variogram::fit(model, &exp)?,
})
}
pub fn variogram(&self) -> &Variogram {
&self.variogram
}
}
#[derive(Debug, Clone, Copy)]
pub enum LayersSpec {
Count(usize),
Thickness(f64),
}
impl LayersSpec {
pub fn nk(&self, gross_m: f64) -> usize {
match *self {
LayersSpec::Count(n) => n.max(1),
LayersSpec::Thickness(dz) => {
if dz <= 0.0 || !gross_m.is_finite() || gross_m <= 0.0 {
1
} else {
((gross_m / dz).round() as usize).max(1)
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct TrendSpec {
trend: TrendSurface,
corr: f64,
}
impl TrendSpec {
#[allow(clippy::too_many_arguments)]
pub fn collocated(
ncol: usize,
nrow: usize,
values: Vec<f64>,
origin_x: f64,
origin_y: f64,
node_dx: f64,
node_dy: f64,
corr: f64,
) -> Result<Self, SrsError> {
let trend = TrendSurface::new(ncol, nrow, values)
.map_err(SrsError::from)?
.with_georef(origin_x, origin_y, node_dx, node_dy);
Ok(Self { trend, corr })
}
pub fn parts(&self) -> (TrendSurface, f64) {
(self.trend.clone(), self.corr)
}
}