1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! A package for handling quantities with uncertainties.
//!
//! The [`ValUnc`] type represents a quantity with a mean value `val` and
//! uncertainties `unc`. It is designed to be used with [newtypes] that wrap a
//! basic numeric type, e.g. `f64`. This allows for the type to define how
//! uncertainties should be propagated, with minimal confusion.
//!
//! [newtypes]: https://doc.rust-lang.org/1.0.0/style/features/types/newtype.html
//!
//! The [`traits`] module defines some traits that are necessary for
//! uncertainty types to be implemented in order for the related type to be
//! implemented for `ValUnc`. For example, in order to implement `Add` for
//! `ValUnc`, all of the uncertainty types used must implement [`UncAdd`].
//! These are opt-in and only the traits used need to be implemented.
//!
//! An example implementation of an uncertainty type is provided in [`unc`]. It
//! uses standard error propagation rules (see [this][uncertainty]), and
//! hopefully can be used if it fits your use case.
//!
//! [uncertainty]: https://en.wikipedia.org/wiki/Propagation_of_uncertainty
//!
//! # Features
//!
//! The `serde` feature can be enabled for use with [`serde`]. A `ValUnc<V, U>`
//! is (de)serialized as a `(V, U)` or if `unc` is zero, according to
//! [`num-traits::Zero`], just a `V`.
//!
//! [`serde`]: https://serde.rs
//! [`num-traits::Zero`]: https://docs.rs/num-traits/*/num_traits/identities/trait.Zero.html
//!
//! # Examples
//!
//! The following demonstrates how one would go about creating uncertainty
//! types and implementing the traits necessary for doing math with `ValUnc`
//! (only `Add`, in this case). Notably, the implementations of `UncAdd` are
//! different. The two uncertainties, though, can be used together in one
//! `ValUnc`.
//!
//! ```
//! use val_unc::{ValUnc, UncAdd};
//!
//! // This is a type for statistical uncertainties.
//! // The result of adding two `StatUnc`s is the square root of the sum of the squares.
//! #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
//! struct StatUnc(pub f64);
//!
//! impl<T> UncAdd<T> for StatUnc {
//!     fn unc_add(self, _self_val: T, other: Self, _other_val: T) -> Self {
//!         Self(f64::sqrt(f64::powi(self.0, 2) + f64::powi(other.0, 2)))
//!     }
//! }
//!
//! // This is a type for systematic uncertainties.
//! // The result of adding two `SysUnc`s is the sum of the two.
//! #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
//! struct SysUnc(pub f64);
//!
//! impl<T> UncAdd<T> for SysUnc {
//!     fn unc_add(self, _self_val: T, other: Self, _other_val: T) -> Self {
//!         Self(self.0 + other.0)
//!     }
//! }
//!
//! // Create two values and add them together
//! let v1 = ValUnc::new(10.2, (StatUnc(4.0), SysUnc(1.25)));
//! let v2 = ValUnc::new(8.5, (StatUnc(3.0), SysUnc(1.25)));
//! // You can use destructuring to unpack the results
//! let ValUnc { val, unc: (stat, sys) } = v1 + v2;
//!
//! assert!(f64::abs(val - 18.7) <= std::f64::EPSILON);
//! assert!(f64::abs(stat.0 - 5.0) <= std::f64::EPSILON);
//! assert!(f64::abs(sys.0 - 2.5) <= std::f64::EPSILON);
//! ```

pub mod traits;
pub mod unc;
pub mod val_unc;

pub use self::{traits::*, unc::*, val_unc::*};

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add() {
        #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
        struct StatUnc(pub f64);

        impl<T> UncAdd<T> for StatUnc {
            fn unc_add(self, _self_val: T, other: Self, _other_val: T) -> Self {
                Self(f64::sqrt(f64::powi(self.0, 2) + f64::powi(other.0, 2)))
            }
        }

        #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
        struct SysUnc(pub f64);

        impl<T> UncAdd<T> for SysUnc {
            fn unc_add(self, _self_val: T, other: Self, _other_val: T) -> Self {
                Self(self.0 + other.0)
            }
        }

        // Create two values and add them together
        let v1 = ValUnc::new(10.2, (StatUnc(4.0), SysUnc(1.25)));
        let v2 = ValUnc::new(8.5, (StatUnc(3.0), SysUnc(1.25)));
        // You can use destructuring to unpack the results
        let ValUnc {
            val,
            unc: (stat, sys),
        } = v1 + v2;

        assert!(f64::abs(val - 18.7) <= std::f64::EPSILON);
        assert!(f64::abs(stat.0 - 5.0) <= std::f64::EPSILON);
        assert!(f64::abs(sys.0 - 2.5) <= std::f64::EPSILON);
    }
}