use petektools::Variogram;
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StructuralPerturbation {
pub control_shifts: Vec<(usize, usize, f64)>,
}
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PerturbationField {
pub sd_m: f64,
pub variogram: Variogram,
}
impl PerturbationField {
#[must_use]
pub fn new(sd_m: f64, variogram: Variogram) -> Self {
Self { sd_m, variogram }
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct ZoneDraw {
pub zone: usize,
pub goc_depth_m: Option<f64>,
pub owc_depth_m: Option<f64>,
pub porosity: Option<f64>,
pub net_to_gross: Option<f64>,
pub water_saturation: Option<f64>,
pub isochore_structural: Option<PerturbationField>,
}
impl ZoneDraw {
#[must_use]
pub fn new(zone: usize) -> Self {
Self {
zone,
goc_depth_m: None,
owc_depth_m: None,
porosity: None,
net_to_gross: None,
water_saturation: None,
isochore_structural: None,
}
}
#[must_use]
pub fn with_isochore_structural(mut self, field: PerturbationField) -> Self {
self.isochore_structural = Some(field);
self
}
#[must_use]
pub fn with_owc(mut self, owc_depth_m: f64) -> Self {
self.owc_depth_m = Some(owc_depth_m);
self
}
#[must_use]
pub fn with_goc(mut self, goc_depth_m: f64) -> Self {
self.goc_depth_m = Some(goc_depth_m);
self
}
#[must_use]
pub fn with_priors(mut self, porosity: f64, net_to_gross: f64, water_saturation: f64) -> Self {
self.porosity = Some(porosity);
self.net_to_gross = Some(net_to_gross);
self.water_saturation = Some(water_saturation);
self
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct RealizationDraw {
pub area_m2: f64,
pub gross_height_m: f64,
pub contact_depth_m: f64,
pub goc_depth_m: Option<f64>,
pub porosity: f64,
pub net_to_gross: f64,
pub water_saturation: f64,
pub seed_index: u64,
pub structural: Option<StructuralPerturbation>,
pub top_structural: Option<PerturbationField>,
pub sw_gas: Option<f64>,
pub property_shifts: Vec<(String, f64)>,
pub zones: Vec<ZoneDraw>,
}
impl RealizationDraw {
#[must_use]
pub fn new(
area_m2: f64,
gross_height_m: f64,
contact_depth_m: f64,
porosity: f64,
net_to_gross: f64,
water_saturation: f64,
seed_index: u64,
) -> Self {
Self {
area_m2,
gross_height_m,
contact_depth_m,
goc_depth_m: None,
porosity,
net_to_gross,
water_saturation,
seed_index,
structural: None,
top_structural: None,
sw_gas: None,
property_shifts: Vec::new(),
zones: Vec::new(),
}
}
#[must_use]
pub fn with_zone_draw(mut self, zone: ZoneDraw) -> Self {
self.zones.retain(|z| z.zone != zone.zone);
self.zones.push(zone);
self
}
#[must_use]
pub fn with_property_shift(mut self, property: impl Into<String>, delta: f64) -> Self {
let property = property.into();
self.property_shifts.retain(|(p, _)| p != &property);
self.property_shifts.push((property, delta));
self
}
#[must_use]
pub fn property_shift(&self, property: &str) -> f64 {
self.property_shifts
.iter()
.find(|(p, _)| p == property)
.map_or(0.0, |(_, d)| *d)
}
#[must_use]
pub fn with_structural(mut self, perturbation: StructuralPerturbation) -> Self {
self.structural = Some(perturbation);
self
}
#[must_use]
pub fn with_top_structural(mut self, field: PerturbationField) -> Self {
self.top_structural = Some(field);
self
}
#[must_use]
pub fn with_sw_gas(mut self, sw_gas: f64) -> Self {
self.sw_gas = Some(sw_gas);
self
}
#[must_use]
pub fn with_goc(mut self, goc_depth_m: f64) -> Self {
self.goc_depth_m = Some(goc_depth_m);
self
}
#[must_use]
pub fn with_area(mut self, area_m2: f64) -> Self {
self.area_m2 = area_m2;
self
}
#[must_use]
pub fn with_gross(mut self, gross_height_m: f64) -> Self {
self.gross_height_m = gross_height_m;
self
}
#[must_use]
pub fn with_contact(mut self, contact_depth_m: f64) -> Self {
self.contact_depth_m = contact_depth_m;
self
}
#[must_use]
pub fn with_porosity(mut self, porosity: f64) -> Self {
self.porosity = porosity;
self
}
#[must_use]
pub fn with_ntg(mut self, net_to_gross: f64) -> Self {
self.net_to_gross = net_to_gross;
self
}
#[must_use]
pub fn with_sw(mut self, water_saturation: f64) -> Self {
self.water_saturation = water_saturation;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn named_setters_override_only_their_field() {
let base = RealizationDraw::new(100.0, 50.0, 5000.0, 0.25, 0.8, 0.3, 7);
let d = base
.clone()
.with_area(200.0)
.with_gross(60.0)
.with_contact(5100.0)
.with_porosity(0.30)
.with_ntg(0.9)
.with_sw(0.2);
assert_eq!(
(d.area_m2, d.gross_height_m, d.contact_depth_m),
(200.0, 60.0, 5100.0)
);
assert_eq!(
(d.porosity, d.net_to_gross, d.water_saturation),
(0.30, 0.9, 0.2)
);
assert_eq!(d.seed_index, 7);
assert_eq!(d.goc_depth_m, None);
assert_eq!(d.structural, None);
}
}