#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
pub mod prelude;
fn finite(value: f64) -> Option<f64> {
value.is_finite().then_some(value)
}
fn nonnegative(value: f64) -> bool {
value.is_finite() && value >= 0.0
}
fn positive(value: f64) -> bool {
value.is_finite() && value > 0.0
}
#[must_use]
pub fn buoyant_force(
fluid_density: f64,
displaced_volume: f64,
gravitational_acceleration: f64,
) -> Option<f64> {
if !nonnegative(fluid_density)
|| !nonnegative(displaced_volume)
|| !gravitational_acceleration.is_finite()
{
return None;
}
finite(fluid_density * displaced_volume * gravitational_acceleration)
}
#[must_use]
pub fn displaced_volume_from_buoyant_force(
buoyant_force: f64,
fluid_density: f64,
gravitational_acceleration: f64,
) -> Option<f64> {
if !nonnegative(buoyant_force)
|| !positive(fluid_density)
|| !gravitational_acceleration.is_finite()
|| gravitational_acceleration == 0.0
{
return None;
}
let volume = buoyant_force / (fluid_density * gravitational_acceleration);
if volume < 0.0 {
return None;
}
finite(volume)
}
#[must_use]
pub fn hydrostatic_pressure(
fluid_density: f64,
gravitational_acceleration: f64,
depth: f64,
) -> Option<f64> {
if !nonnegative(fluid_density) || !nonnegative(depth) || !gravitational_acceleration.is_finite()
{
return None;
}
finite(fluid_density * gravitational_acceleration * depth)
}
#[must_use]
pub fn absolute_pressure(
surface_pressure: f64,
fluid_density: f64,
gravitational_acceleration: f64,
depth: f64,
) -> Option<f64> {
if !nonnegative(surface_pressure)
|| !nonnegative(fluid_density)
|| !nonnegative(depth)
|| !gravitational_acceleration.is_finite()
{
return None;
}
let hydrostatic_gradient = fluid_density * gravitational_acceleration;
finite(hydrostatic_gradient.mul_add(depth, surface_pressure))
}
#[must_use]
pub fn volumetric_flow_rate(area: f64, velocity: f64) -> Option<f64> {
if !nonnegative(area) || !velocity.is_finite() {
return None;
}
finite(area * velocity)
}
#[must_use]
pub fn velocity_from_flow_rate(flow_rate: f64, area: f64) -> Option<f64> {
if !flow_rate.is_finite() || !positive(area) {
return None;
}
finite(flow_rate / area)
}
#[must_use]
pub fn mass_flow_rate(density: f64, volumetric_flow_rate: f64) -> Option<f64> {
if !nonnegative(density) || !volumetric_flow_rate.is_finite() {
return None;
}
finite(density * volumetric_flow_rate)
}
#[must_use]
pub fn continuity_velocity(area_a: f64, velocity_a: f64, area_b: f64) -> Option<f64> {
if !nonnegative(area_a) || !velocity_a.is_finite() || !positive(area_b) {
return None;
}
finite(area_a * velocity_a / area_b)
}
#[must_use]
pub fn continuity_area(area_a: f64, velocity_a: f64, velocity_b: f64) -> Option<f64> {
if !nonnegative(area_a)
|| !velocity_a.is_finite()
|| !velocity_b.is_finite()
|| velocity_b == 0.0
{
return None;
}
let area = area_a * velocity_a / velocity_b;
if area < 0.0 {
return None;
}
finite(area)
}
#[must_use]
pub fn bernoulli_pressure(
reference_pressure: f64,
density: f64,
reference_velocity: f64,
velocity: f64,
gravitational_acceleration: f64,
reference_height: f64,
height: f64,
) -> Option<f64> {
if !nonnegative(reference_pressure)
|| !nonnegative(density)
|| !reference_velocity.is_finite()
|| !velocity.is_finite()
|| !gravitational_acceleration.is_finite()
|| !reference_height.is_finite()
|| !height.is_finite()
{
return None;
}
let velocity_delta = velocity.mul_add(-velocity, reference_velocity * reference_velocity);
let pressure_without_height = (0.5 * density).mul_add(velocity_delta, reference_pressure);
let hydrostatic_gradient = density * gravitational_acceleration;
finite(hydrostatic_gradient.mul_add(reference_height - height, pressure_without_height))
}
#[must_use]
pub fn dynamic_pressure(density: f64, velocity: f64) -> Option<f64> {
if !nonnegative(density) || !velocity.is_finite() {
return None;
}
let pressure = 0.5 * density * velocity.powi(2);
if pressure < 0.0 {
return None;
}
finite(pressure)
}
#[must_use]
pub fn reynolds_number(
density: f64,
velocity: f64,
characteristic_length: f64,
dynamic_viscosity: f64,
) -> Option<f64> {
if !nonnegative(density)
|| !velocity.is_finite()
|| !nonnegative(characteristic_length)
|| !positive(dynamic_viscosity)
{
return None;
}
finite(density * velocity.abs() * characteristic_length / dynamic_viscosity)
}
#[must_use]
pub fn kinematic_viscosity(dynamic_viscosity: f64, density: f64) -> Option<f64> {
if !nonnegative(dynamic_viscosity) || !positive(density) {
return None;
}
finite(dynamic_viscosity / density)
}
#[must_use]
pub fn dynamic_viscosity(kinematic_viscosity: f64, density: f64) -> Option<f64> {
if !nonnegative(kinematic_viscosity) || !nonnegative(density) {
return None;
}
finite(kinematic_viscosity * density)
}
#[must_use]
pub fn drag_force(density: f64, velocity: f64, drag_coefficient: f64, area: f64) -> Option<f64> {
if !nonnegative(density)
|| !velocity.is_finite()
|| !nonnegative(drag_coefficient)
|| !nonnegative(area)
{
return None;
}
let force = 0.5 * density * velocity.powi(2) * drag_coefficient * area;
if force < 0.0 {
return None;
}
finite(force)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Fluid {
pub density: f64,
pub dynamic_viscosity: Option<f64>,
}
impl Fluid {
#[must_use]
pub fn new(density: f64) -> Option<Self> {
if !nonnegative(density) {
return None;
}
Some(Self {
density,
dynamic_viscosity: None,
})
}
#[must_use]
pub fn with_dynamic_viscosity(density: f64, dynamic_viscosity: f64) -> Option<Self> {
if !nonnegative(density) || !nonnegative(dynamic_viscosity) {
return None;
}
Some(Self {
density,
dynamic_viscosity: Some(dynamic_viscosity),
})
}
#[must_use]
pub fn buoyant_force(
&self,
displaced_volume: f64,
gravitational_acceleration: f64,
) -> Option<f64> {
buoyant_force(self.density, displaced_volume, gravitational_acceleration)
}
#[must_use]
pub fn hydrostatic_pressure(&self, gravitational_acceleration: f64, depth: f64) -> Option<f64> {
hydrostatic_pressure(self.density, gravitational_acceleration, depth)
}
#[must_use]
pub fn dynamic_pressure(&self, velocity: f64) -> Option<f64> {
dynamic_pressure(self.density, velocity)
}
#[must_use]
pub fn reynolds_number(&self, velocity: f64, characteristic_length: f64) -> Option<f64> {
let dynamic_viscosity = self.dynamic_viscosity?;
reynolds_number(
self.density,
velocity,
characteristic_length,
dynamic_viscosity,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PipeFlow {
pub area: f64,
pub velocity: f64,
}
impl PipeFlow {
#[must_use]
pub fn new(area: f64, velocity: f64) -> Option<Self> {
if !nonnegative(area) || !velocity.is_finite() {
return None;
}
Some(Self { area, velocity })
}
#[must_use]
pub fn volumetric_flow_rate(&self) -> Option<f64> {
volumetric_flow_rate(self.area, self.velocity)
}
#[must_use]
pub fn mass_flow_rate(&self, density: f64) -> Option<f64> {
mass_flow_rate(density, self.volumetric_flow_rate()?)
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::{
Fluid, PipeFlow, absolute_pressure, bernoulli_pressure, buoyant_force, continuity_area,
continuity_velocity, displaced_volume_from_buoyant_force, drag_force, dynamic_pressure,
dynamic_viscosity, hydrostatic_pressure, kinematic_viscosity, mass_flow_rate,
reynolds_number, velocity_from_flow_rate, volumetric_flow_rate,
};
fn approx_eq(left: f64, right: f64, tolerance: f64) {
let delta = (left - right).abs();
assert!(
delta <= tolerance,
"left={left} right={right} delta={delta} tolerance={tolerance}"
);
}
#[test]
fn buoyancy_helpers_cover_valid_and_invalid_inputs() {
approx_eq(
buoyant_force(1000.0, 0.01, 9.80665).unwrap(),
98.0665,
1.0e-10,
);
assert_eq!(buoyant_force(-1000.0, 0.01, 9.80665), None);
assert_eq!(buoyant_force(1000.0, -0.01, 9.80665), None);
approx_eq(
displaced_volume_from_buoyant_force(98.0665, 1000.0, 9.80665).unwrap(),
0.01,
1.0e-12,
);
assert_eq!(
displaced_volume_from_buoyant_force(98.0665, 0.0, 9.80665),
None
);
}
#[test]
fn hydrostatic_helpers_cover_valid_and_invalid_inputs() {
approx_eq(
hydrostatic_pressure(1000.0, 9.80665, 10.0).unwrap(),
98_066.5,
1.0e-9,
);
assert_eq!(hydrostatic_pressure(1000.0, 9.80665, -1.0), None);
approx_eq(
absolute_pressure(101_325.0, 1000.0, 9.80665, 10.0).unwrap(),
199_391.5,
1.0e-9,
);
assert_eq!(absolute_pressure(-1.0, 1000.0, 9.80665, 10.0), None);
}
#[test]
fn flow_rate_helpers_cover_valid_and_invalid_inputs() {
assert_eq!(volumetric_flow_rate(2.0, 3.0), Some(6.0));
assert_eq!(volumetric_flow_rate(2.0, -3.0), Some(-6.0));
assert_eq!(volumetric_flow_rate(-2.0, 3.0), None);
assert_eq!(velocity_from_flow_rate(6.0, 2.0), Some(3.0));
assert_eq!(velocity_from_flow_rate(6.0, 0.0), None);
assert_eq!(mass_flow_rate(1000.0, 0.5), Some(500.0));
assert_eq!(mass_flow_rate(-1000.0, 0.5), None);
}
#[test]
fn continuity_helpers_cover_valid_and_invalid_inputs() {
assert_eq!(continuity_velocity(2.0, 3.0, 1.0), Some(6.0));
assert_eq!(continuity_velocity(2.0, 3.0, 0.0), None);
assert_eq!(continuity_area(2.0, 3.0, 6.0), Some(1.0));
assert_eq!(continuity_area(2.0, 3.0, 0.0), None);
}
#[test]
fn bernoulli_and_dynamic_pressure_cover_common_cases() {
assert_eq!(dynamic_pressure(1000.0, 3.0), Some(4500.0));
assert_eq!(dynamic_pressure(-1000.0, 3.0), None);
let pressure = bernoulli_pressure(100_000.0, 1000.0, 4.0, 2.0, 9.80665, 10.0, 5.0);
assert!(pressure.is_some_and(f64::is_finite));
}
#[test]
fn viscosity_helpers_cover_reynolds_and_conversions() {
approx_eq(
reynolds_number(1000.0, 2.0, 0.1, 0.001).unwrap(),
200_000.0,
1.0e-9,
);
assert_eq!(reynolds_number(1000.0, 2.0, 0.1, 0.0), None);
approx_eq(
kinematic_viscosity(0.001, 1000.0).unwrap(),
0.000_001,
1.0e-15,
);
assert_eq!(kinematic_viscosity(0.001, 0.0), None);
approx_eq(
dynamic_viscosity(0.000_001, 1000.0).unwrap(),
0.001,
1.0e-15,
);
assert_eq!(dynamic_viscosity(-0.000_001, 1000.0), None);
}
#[test]
fn drag_force_handles_standard_cases() {
approx_eq(drag_force(1.225, 10.0, 0.47, 1.0).unwrap(), 28.7875, 1.0e-9);
approx_eq(
drag_force(1.225, -10.0, 0.47, 1.0).unwrap(),
28.7875,
1.0e-9,
);
assert_eq!(drag_force(1.225, 10.0, -0.47, 1.0), None);
}
#[test]
fn fluid_methods_delegate_to_public_functions() {
approx_eq(
Fluid::new(1000.0)
.unwrap()
.hydrostatic_pressure(9.80665, 10.0)
.unwrap(),
98_066.5,
1.0e-9,
);
approx_eq(
Fluid::with_dynamic_viscosity(1000.0, 0.001)
.unwrap()
.reynolds_number(2.0, 0.1)
.unwrap(),
200_000.0,
1.0e-9,
);
assert_eq!(Fluid::new(1000.0).unwrap().reynolds_number(2.0, 0.1), None);
assert_eq!(Fluid::new(-1000.0), None);
}
#[test]
fn pipe_flow_methods_delegate_to_public_functions() {
assert_eq!(
PipeFlow::new(2.0, 3.0).unwrap().volumetric_flow_rate(),
Some(6.0)
);
assert_eq!(
PipeFlow::new(2.0, 3.0).unwrap().mass_flow_rate(1000.0),
Some(6000.0)
);
assert_eq!(PipeFlow::new(-2.0, 3.0), None);
}
}