use super::GeoData;
use crate::analysis::characterise::{characterise, DistributionShape};
use crate::analysis::interpret::{net_flags, net_pay, net_to_gross, Cutoffs};
use crate::analysis::normalize::{canonical_mnemonic, harmonise_fraction};
use crate::analysis::validate::mask_out_of_range;
use crate::analysis::{HorizonInput, ModelInputs, SpatialInputs, SummaryInputs, WellCurveInput};
use crate::core::{Sidetrack, Well};
use crate::foundation::{Provenance, Result, Uncertain, Unit};
fn canonical_curve(bore: &Sidetrack, target: &str) -> Option<(Vec<f64>, Vec<f64>)> {
let log = bore
.logs()
.find(|l| canonical_mnemonic(&l.mnemonic) == target)?;
let md = log.md_slice().to_vec();
let mut values: Vec<f64> = log
.values_slice()
.iter()
.map(|v| harmonise_fraction(*v, &log.unit))
.collect();
mask_out_of_range(target, &mut values);
Some((md, values))
}
#[derive(Default)]
struct PetroAcc {
net_pay: Vec<f64>,
ntg: Vec<f64>,
phi_net: Vec<f64>,
sw_net: Vec<f64>,
}
impl GeoData {
pub fn model_inputs(&self) -> Result<ModelInputs> {
let cutoffs = Cutoffs::default();
let mut acc = PetroAcc::default();
let mut well_curves = Vec::new();
for well in self.wells().iter() {
for bore in well.sidetracks() {
self.accumulate_bore(well, bore, &cutoffs, &mut acc, &mut well_curves);
}
}
let summary = self.summary(&acc);
let spatial = SpatialInputs {
boundary: self.edge_polygon(),
horizons: self.horizons(),
well_curves,
};
Ok(ModelInputs { summary, spatial })
}
fn accumulate_bore(
&self,
well: &Well,
bore: &Sidetrack,
cutoffs: &Cutoffs,
acc: &mut PetroAcc,
well_curves: &mut Vec<WellCurveInput>,
) {
let well_id = well.bore_id(&bore.label);
for log in bore.logs() {
let mnemonic = canonical_mnemonic(&log.mnemonic);
let md = log.md_slice().to_vec();
let mut values: Vec<f64> = log
.values_slice()
.iter()
.map(|v| harmonise_fraction(*v, &log.unit))
.collect();
mask_out_of_range(&mnemonic, &mut values);
let xyz: Vec<[f64; 3]> = md
.iter()
.map(|&m| match bore.xyz(m) {
Some(p) => [p.x, p.y, p.z],
None => [f64::NAN; 3],
})
.collect();
well_curves.push(WellCurveInput {
well_id: well_id.clone(),
mnemonic,
md,
values,
xyz,
provenance: Provenance::HardData,
});
}
let (Some((md, phi)), Some((_, sw))) =
(canonical_curve(bore, "PHIE"), canonical_curve(bore, "SW"))
else {
return; };
let net = net_flags(&phi, &sw, None, cutoffs);
let depth: Vec<f64> = md.iter().map(|&m| bore.tvd(m).unwrap_or(m)).collect();
acc.net_pay.push(net_pay(&depth, &net));
acc.ntg.push(net_to_gross(&depth, &net));
for (i, &is_net) in net.iter().enumerate() {
if is_net {
acc.phi_net.push(phi[i]);
acc.sw_net.push(sw[i]);
}
}
}
fn summary(&self, acc: &PetroAcc) -> SummaryInputs {
let net_pay_m: Vec<f64> = acc
.net_pay
.iter()
.map(|&v| self.unit.convert(v, Unit::Metres))
.collect();
SummaryInputs {
area_m2: self.area_m2(),
net_pay_m: characterise(
&net_pay_m,
DistributionShape::Normal,
Provenance::Interpolated,
),
porosity_frac: characterise(
&acc.phi_net,
DistributionShape::Normal,
Provenance::HardData,
),
water_saturation_frac: characterise(
&acc.sw_net,
DistributionShape::Normal,
Provenance::HardData,
),
net_to_gross_frac: characterise(
&acc.ntg,
DistributionShape::Normal,
Provenance::Interpolated,
),
owc_depth_m: None,
goc_depth_m: None,
}
}
fn area_m2(&self) -> Uncertain {
match self.edge_polygon() {
Some(poly) => Uncertain {
value: self.unit.area_to_m2(poly.area()),
distribution: crate::foundation::Distribution::Deterministic,
provenance: Provenance::Interpolated,
},
None => Uncertain::defaulted(f64::NAN),
}
}
fn horizons(&self) -> Vec<HorizonInput> {
self.surfaces_named()
.map(|(name, surface)| HorizonInput {
name: name.to_string(),
surface: surface.clone(),
provenance: Provenance::Interpolated,
})
.collect()
}
fn edge_polygon(&self) -> Option<crate::core::PolygonSet> {
if let Some((_, poly)) = self.polygons_named().next() {
return Some(poly.clone());
}
self.surfaces().next().and_then(|s| s.edge())
}
}