#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
pub mod prelude;
pub const COULOMB_CONSTANT: f64 = 8.987_551_792_3e9;
fn all_finite(values: &[f64]) -> bool {
values.iter().all(|value| value.is_finite())
}
fn finite_result(value: f64) -> Option<f64> {
value.is_finite().then_some(value)
}
#[must_use]
pub fn charge_from_current_time(current: f64, time: f64) -> Option<f64> {
if !all_finite(&[current, time]) || time < 0.0 {
return None;
}
finite_result(current * time)
}
#[must_use]
pub fn current_from_charge_time(charge: f64, time: f64) -> Option<f64> {
if !all_finite(&[charge, time]) || time <= 0.0 {
return None;
}
finite_result(charge / time)
}
#[must_use]
pub fn voltage(current: f64, resistance: f64) -> Option<f64> {
if !all_finite(&[current, resistance]) || resistance < 0.0 {
return None;
}
finite_result(current * resistance)
}
#[must_use]
pub fn current(voltage: f64, resistance: f64) -> Option<f64> {
if !all_finite(&[voltage, resistance]) || resistance <= 0.0 {
return None;
}
finite_result(voltage / resistance)
}
#[must_use]
pub fn resistance(voltage: f64, current: f64) -> Option<f64> {
if !all_finite(&[voltage, current]) || current == 0.0 {
return None;
}
let resistance = voltage / current;
if !resistance.is_finite() || resistance < 0.0 {
None
} else {
Some(resistance)
}
}
#[must_use]
pub fn conductance(resistance: f64) -> Option<f64> {
if !resistance.is_finite() || resistance <= 0.0 {
return None;
}
finite_result(1.0 / resistance)
}
#[must_use]
pub fn resistance_from_conductance(conductance: f64) -> Option<f64> {
if !conductance.is_finite() || conductance <= 0.0 {
return None;
}
finite_result(1.0 / conductance)
}
#[must_use]
pub fn power_from_voltage_current(voltage: f64, current: f64) -> Option<f64> {
if !all_finite(&[voltage, current]) {
return None;
}
finite_result(voltage * current)
}
#[must_use]
pub fn power_from_current_resistance(current: f64, resistance: f64) -> Option<f64> {
if !all_finite(&[current, resistance]) || resistance < 0.0 {
return None;
}
finite_result(current * current * resistance)
}
#[must_use]
pub fn power_from_voltage_resistance(voltage: f64, resistance: f64) -> Option<f64> {
if !all_finite(&[voltage, resistance]) || resistance <= 0.0 {
return None;
}
finite_result((voltage * voltage) / resistance)
}
#[must_use]
pub fn energy_from_power_time(power: f64, time: f64) -> Option<f64> {
if !all_finite(&[power, time]) || time < 0.0 {
return None;
}
finite_result(power * time)
}
#[must_use]
pub fn energy_from_voltage_charge(voltage: f64, charge: f64) -> Option<f64> {
if !all_finite(&[voltage, charge]) {
return None;
}
finite_result(voltage * charge)
}
#[must_use]
pub fn series_resistance(resistances: &[f64]) -> Option<f64> {
let mut total = 0.0;
for &resistance in resistances {
if !resistance.is_finite() || resistance < 0.0 {
return None;
}
total += resistance;
}
finite_result(total)
}
#[must_use]
pub fn parallel_resistance(resistances: &[f64]) -> Option<f64> {
if resistances.is_empty() {
return None;
}
let mut reciprocal_sum = 0.0;
for &resistance in resistances {
if !resistance.is_finite() || resistance <= 0.0 {
return None;
}
reciprocal_sum += 1.0 / resistance;
}
finite_result(1.0 / reciprocal_sum)
}
#[must_use]
pub fn coulomb_force(charge_a: f64, charge_b: f64, distance: f64) -> Option<f64> {
if !all_finite(&[charge_a, charge_b, distance]) || distance <= 0.0 {
return None;
}
finite_result(COULOMB_CONSTANT * charge_a * charge_b / (distance * distance))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ElectricalLoad {
pub voltage: f64,
pub resistance: f64,
}
impl ElectricalLoad {
#[must_use]
pub fn new(voltage: f64, resistance: f64) -> Option<Self> {
if !voltage.is_finite() || !resistance.is_finite() || resistance <= 0.0 {
return None;
}
Some(Self {
voltage,
resistance,
})
}
#[must_use]
pub fn current(&self) -> Option<f64> {
current(self.voltage, self.resistance)
}
#[must_use]
pub fn power(&self) -> Option<f64> {
power_from_voltage_resistance(self.voltage, self.resistance)
}
}
#[cfg(test)]
mod tests {
use super::{
COULOMB_CONSTANT, ElectricalLoad, charge_from_current_time, conductance, coulomb_force,
current, current_from_charge_time, energy_from_power_time, energy_from_voltage_charge,
parallel_resistance, power_from_current_resistance, power_from_voltage_current,
power_from_voltage_resistance, resistance, resistance_from_conductance, series_resistance,
voltage,
};
const EPSILON: f64 = 1.0e-12;
fn assert_some_close(actual: Option<f64>, expected: f64) {
let actual = actual.expect("expected Some value");
let tolerance = expected.abs().max(1.0) * EPSILON;
assert!(
(actual - expected).abs() <= tolerance,
"expected {expected}, got {actual}"
);
}
#[test]
fn charge_helpers_cover_common_relationships() {
assert_eq!(charge_from_current_time(2.0, 3.0), Some(6.0));
assert_eq!(charge_from_current_time(2.0, -1.0), None);
assert_eq!(current_from_charge_time(10.0, 2.0), Some(5.0));
assert_eq!(current_from_charge_time(10.0, 0.0), None);
}
#[test]
fn ohms_law_helpers_cover_common_relationships() {
assert_eq!(voltage(2.0, 5.0), Some(10.0));
assert_eq!(voltage(2.0, -5.0), None);
assert_eq!(current(10.0, 5.0), Some(2.0));
assert_eq!(current(10.0, 0.0), None);
assert_eq!(resistance(10.0, 2.0), Some(5.0));
assert_eq!(resistance(10.0, 0.0), None);
assert_eq!(resistance(-10.0, 2.0), None);
}
#[test]
fn conductance_helpers_cover_common_relationships() {
assert_some_close(conductance(5.0), 0.2);
assert_eq!(conductance(0.0), None);
assert_some_close(resistance_from_conductance(0.2), 5.0);
assert_eq!(resistance_from_conductance(0.0), None);
}
#[test]
fn power_helpers_cover_common_relationships() {
assert_eq!(power_from_voltage_current(10.0, 2.0), Some(20.0));
assert_eq!(power_from_current_resistance(2.0, 5.0), Some(20.0));
assert_eq!(power_from_voltage_resistance(10.0, 5.0), Some(20.0));
}
#[test]
fn energy_helpers_cover_common_relationships() {
assert_eq!(energy_from_power_time(20.0, 3.0), Some(60.0));
assert_eq!(energy_from_power_time(20.0, -1.0), None);
assert_eq!(energy_from_voltage_charge(10.0, 2.0), Some(20.0));
}
#[test]
fn resistance_network_helpers_cover_common_relationships() {
assert_eq!(series_resistance(&[1.0, 2.0, 3.0]), Some(6.0));
assert_eq!(series_resistance(&[]), Some(0.0));
assert_eq!(series_resistance(&[1.0, -2.0]), None);
assert_eq!(parallel_resistance(&[2.0, 2.0]), Some(1.0));
assert_eq!(parallel_resistance(&[]), None);
assert_eq!(parallel_resistance(&[2.0, 0.0]), None);
}
#[test]
fn coulomb_force_helpers_cover_common_relationships() {
assert_some_close(coulomb_force(1.0, 1.0, 1.0), COULOMB_CONSTANT);
assert_some_close(coulomb_force(1.0, -1.0, 1.0), -COULOMB_CONSTANT);
assert_eq!(coulomb_force(1.0, 1.0, 0.0), None);
}
#[test]
fn electrical_load_delegates_to_public_helpers() {
let load = ElectricalLoad::new(10.0, 5.0).expect("valid load");
assert_eq!(load.current(), Some(2.0));
assert_eq!(load.power(), Some(20.0));
assert_eq!(ElectricalLoad::new(10.0, 0.0), None);
}
#[test]
fn non_finite_values_are_rejected() {
assert_eq!(charge_from_current_time(f64::NAN, 1.0), None);
assert_eq!(current_from_charge_time(1.0, f64::INFINITY), None);
assert_eq!(power_from_voltage_current(f64::INFINITY, 1.0), None);
assert_eq!(series_resistance(&[1.0, f64::NAN]), None);
}
}