use crate::{
enums::SelectionMethod,
validation::{validate_field, ValidationError},
};
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaswLayer {
pub thickness: Option<f64>,
pub vs: Option<f64>,
pub vp: Option<f64>,
pub depth: Option<f64>,
}
impl MaswLayer {
pub fn new(thickness: f64, vs: f64, vp: f64) -> Self {
Self {
thickness: Some(thickness),
vs: Some(vs),
vp: Some(vp),
depth: None,
}
}
pub fn validate(&self, fields: &[&str]) -> Result<(), ValidationError> {
for &field in fields {
let result = match field {
"depth" => validate_field("depth", self.depth, Some(0.0), None, "masw"),
"thickness" => {
validate_field("thickness", self.thickness, Some(0.0001), None, "masw")
}
"vs" => validate_field("vs", self.vs, Some(0.0), None, "masw"),
"vp" => validate_field("vp", self.vp, Some(0.0), None, "masw"),
unknown => Err(ValidationError {
code: "masw.invalid_field".into(),
message: format!("Field '{}' is not valid for MASW.", unknown),
}),
};
result?; }
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaswExp {
pub layers: Vec<MaswLayer>,
pub name: String,
}
impl MaswExp {
pub fn new(layers: Vec<MaswLayer>, name: String) -> Self {
let mut instance = Self { layers, name }; instance.calc_depths(); instance }
pub fn calc_depths(&mut self) {
if self.layers.is_empty() {
return;
}
let mut bottom = 0.0;
for exp in &mut self.layers {
let thickness = exp.thickness.unwrap();
if thickness <= 0.0 {
panic!("Thickness of MASW experiment must be greater than zero.");
}
exp.depth = Some(bottom + thickness);
bottom += thickness;
}
}
pub fn get_layer_at_depth(&self, depth: f64) -> &MaswLayer {
self.layers
.iter()
.find(|exp| exp.depth.unwrap() >= depth)
.unwrap_or_else(|| self.layers.last().unwrap())
}
pub fn validate(&self, fields: &[&str]) -> Result<(), ValidationError> {
if self.layers.is_empty() {
return Err(ValidationError {
code: "masw.empty_layers".into(),
message: "No layers provided for MaswExp.".into(),
});
}
for layer in &self.layers {
layer.validate(fields)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Masw {
pub exps: Vec<MaswExp>,
pub idealization_method: SelectionMethod,
}
impl Masw {
pub fn new(mut exps: Vec<MaswExp>, idealization_method: SelectionMethod) -> Self {
for exp in &mut exps {
exp.calc_depths();
}
Self {
exps,
idealization_method,
}
}
pub fn add_exp(&mut self, exp: MaswExp) {
self.exps.push(exp);
}
pub fn calc_depths(&mut self) {
for exp in &mut self.exps {
exp.calc_depths();
}
}
pub fn get_idealized_exp(&mut self, name: String) -> MaswExp {
if self.exps.is_empty() {
return MaswExp::new(vec![], name);
}
let mode = self.idealization_method;
self.calc_depths();
let mut unique_depths = BTreeSet::new();
unique_depths.insert(OrderedFloat(0.0)); for exp in &self.exps {
for layer in &exp.layers {
unique_depths.insert(OrderedFloat(layer.depth.unwrap()));
}
}
let sorted_depths: Vec<f64> = unique_depths.into_iter().map(|d| d.into_inner()).collect();
let mut layers = Vec::new();
let get_mode_value = |mode: SelectionMethod, values: Vec<f64>| -> f64 {
match mode {
SelectionMethod::Min => values.iter().cloned().fold(f64::INFINITY, f64::min),
SelectionMethod::Avg => values.iter().sum::<f64>() / values.len() as f64,
SelectionMethod::Max => values.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
}
};
for depth_pair in sorted_depths.windows(2) {
let top = depth_pair[0];
let bottom = depth_pair[1];
let thickness = bottom - top;
let mut vs_at_depth = Vec::new();
let mut vp_at_depth = Vec::new();
for exp in &self.exps {
let layer = exp.get_layer_at_depth((top + bottom) / 2.0);
vs_at_depth.push(layer.vs.unwrap());
vp_at_depth.push(layer.vp.unwrap());
}
let vs = get_mode_value(mode, vs_at_depth);
let vp = get_mode_value(mode, vp_at_depth);
layers.push(MaswLayer::new(thickness, vs, vp));
}
MaswExp::new(layers, name)
}
pub fn validate(&self, fields: &[&str]) -> Result<(), ValidationError> {
if self.exps.is_empty() {
return Err(ValidationError {
code: "masw.empty_exps".into(),
message: "No experiments provided for Masw.".into(),
});
}
for exp in &self.exps {
exp.validate(fields)?;
}
Ok(())
}
}