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); } }