use crate::error::StaticError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OilFvf(f64);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GasFvf(f64);
impl OilFvf {
pub fn new(rm3_per_sm3: f64) -> Result<Self, StaticError> {
if rm3_per_sm3.is_finite() && rm3_per_sm3 >= 1.0 {
Ok(Self(rm3_per_sm3))
} else {
Err(StaticError::InvalidInput(format!(
"oil FVF (Boi) must be finite and >= 1.0 Rm³/Sm³, got {rm3_per_sm3}"
)))
}
}
#[must_use]
pub fn value(self) -> f64 {
self.0
}
}
impl GasFvf {
pub fn new(rm3_per_sm3: f64) -> Result<Self, StaticError> {
if rm3_per_sm3.is_finite() && rm3_per_sm3 > 0.0 && rm3_per_sm3 < 1.0 {
Ok(Self(rm3_per_sm3))
} else {
Err(StaticError::InvalidInput(format!(
"gas FVF (Bgi) must be finite and in (0,1) Rm³/Sm³, got {rm3_per_sm3}"
)))
}
}
#[must_use]
pub fn value(self) -> f64 {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_fvfs_round_trip() {
assert!((OilFvf::new(1.25).unwrap().value() - 1.25).abs() < 1e-12);
assert!((GasFvf::new(0.005).unwrap().value() - 0.005).abs() < 1e-12);
}
#[test]
fn oil_fvf_rejects_below_one_and_nonfinite() {
assert!(OilFvf::new(0.9).is_err());
assert!(OilFvf::new(0.0).is_err());
assert!(OilFvf::new(f64::NAN).is_err());
}
#[test]
fn gas_fvf_rejects_out_of_unit_interval() {
assert!(GasFvf::new(0.0).is_err());
assert!(GasFvf::new(1.0).is_err());
assert!(GasFvf::new(f64::INFINITY).is_err());
}
}