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 CPTLayer {
pub depth: Option<f64>, pub cone_resistance: Option<f64>, pub sleeve_friction: Option<f64>, pub pore_pressure: Option<f64>, pub friction_ratio: Option<f64>, }
impl Default for CPTLayer {
fn default() -> Self {
Self {
depth: Some(0.0),
cone_resistance: Some(0.0),
sleeve_friction: Some(0.0),
pore_pressure: None,
friction_ratio: None,
}
}
}
impl CPTLayer {
pub fn new(depth: f64, qc: f64, fs: f64, u2: Option<f64>) -> Self {
Self {
depth: Some(depth),
cone_resistance: Some(qc),
sleeve_friction: Some(fs),
pore_pressure: u2,
friction_ratio: None,
}
}
pub fn calc_friction_ratio(&mut self) {
if self.cone_resistance.unwrap() != 0.0 {
self.friction_ratio =
Some((self.sleeve_friction.unwrap() / self.cone_resistance.unwrap()) * 100.0);
}
}
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, "cpt"),
"cone_resistance" => validate_field(
"cone_resistance",
self.cone_resistance,
Some(0.0),
None,
"cpt",
),
"sleeve_friction" => validate_field(
"sleeve_friction",
self.sleeve_friction,
Some(0.0),
None,
"cpt",
),
"pore_pressure" => {
validate_field("pore_pressure", self.pore_pressure, Some(0.0), None, "cpt")
}
"friction_ratio" => validate_field(
"friction_ratio",
self.friction_ratio,
Some(0.0),
None,
"cpt",
),
unknown => Err(ValidationError {
code: "cpt.invalid_field".into(),
message: format!("Field '{}' is not valid for CPT.", unknown),
}),
};
result?; }
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CPTExp {
pub layers: Vec<CPTLayer>,
pub name: String,
}
impl CPTExp {
pub fn new(layers: Vec<CPTLayer>, name: String) -> Self {
Self { layers, name }
}
pub fn add_layer(&mut self, layer: CPTLayer) {
self.layers.push(layer);
}
pub fn get_layer_at_depth(&self, depth: f64) -> &CPTLayer {
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: "cpt.empty_layers".into(),
message: "No layers provided for CPTExp.".into(),
});
}
for layer in &self.layers {
layer.validate(fields)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CPT {
pub exps: Vec<CPTExp>,
pub idealization_method: SelectionMethod,
}
impl CPT {
pub fn new(exps: Vec<CPTExp>, idealization_method: SelectionMethod) -> Self {
Self {
exps,
idealization_method,
}
}
pub fn add_exp(&mut self, exp: CPTExp) {
self.exps.push(exp);
}
pub fn get_idealized_exp(&self, name: String) -> CPTExp {
if self.exps.is_empty() {
return CPTExp::new(vec![], name);
}
let mode = self.idealization_method;
let mut unique_depths = BTreeSet::new();
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 in sorted_depths {
let mut qc_at_depth = Vec::new();
let mut fs_at_depth = Vec::new();
let mut u2_at_depth = Vec::new();
for exp in &self.exps {
let layer = exp.get_layer_at_depth(depth);
qc_at_depth.push(layer.cone_resistance.unwrap());
fs_at_depth.push(layer.sleeve_friction.unwrap());
u2_at_depth.push(layer.pore_pressure.unwrap_or(0.0));
}
let qc = get_mode_value(mode, qc_at_depth);
let fs = get_mode_value(mode, fs_at_depth);
let u2 = get_mode_value(mode, u2_at_depth);
layers.push(CPTLayer::new(depth, qc, fs, Some(u2)));
}
CPTExp::new(layers, name)
}
pub fn validate(&self, fields: &[&str]) -> Result<(), ValidationError> {
if self.exps.is_empty() {
return Err(ValidationError {
code: "cpt.empty_exps".into(),
message: "No experiments found in CPT.".into(),
});
}
for exp in &self.exps {
exp.validate(fields)?;
}
Ok(())
}
}