polycal/lib.rs
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
//! polycal is a library for fitting, and using, polynomial calibration functions.
//!
//! It's goal is to offer a simple, application agnostic, interface allowing for easy integration
//! into code bases in many fields.
//!
//! The implementation follows the ISO/TS 28038 standard where possible.
//!
//! To use the library, we first create a [`Problem`] using some known calibration data. Calibration
//! data is composed of independent, or stimulus variables. These describe the inputs to the
//! measurement model. For each stimulus there is an associated response, or dependent variable. A
//! calibration problem can be constructed from known stimulus and response data. Currently the
//! inputs must be provided as [`ndarray::ArrayView1`].
//!
//! ```
//! use ndarray::Array1;
//! use polycal::ProblemBuilder;
//!
//! let a = 1.;
//! let b = 2.;
//! let stimulus: Array1<f64> = Array1::range(0., 10., 0.5);
//! let num_data_points = stimulus.len();
//! let response: Array1<f64> = stimulus
//! .iter()
//! .map(|x| a + b * x)
//! .collect();
//!
//! let problem = ProblemBuilder::new(stimulus.view(), response.view())
//! .unwrap()
//! .build();
//!
//! let maximum_degree = 5;
//!
//! let best_fit = problem.solve(maximum_degree).unwrap();
//!
//! for (expected, actual) in response.into_iter().zip(stimulus.into_iter().map(|x|
//! best_fit.certain_response(x).unwrap())).skip(1).take(num_data_points-2) {
//! assert!((expected - actual).abs() < 1e-5);;
//! }
//! ```
//!
//! We can account for uncertainties in the *response* variables at present. These can be attached
//! to a [`ProblemBuilder`] as:
//!
//! ```
//! use ndarray::Array1;
//! use polycal::ProblemBuilder;
//!
//! let a = 1.;
//! let b = 2.;
//! let stimulus: Array1<f64> = Array1::range(0., 10., 0.5);
//! let num_data_points = stimulus.len();
//! let response: Array1<f64> = stimulus
//! .iter()
//! .map(|x| a + b * x)
//! .collect();
//! let independent_uncertainty: Array1<f64> = response
//! .iter()
//! .map(|x| x / 1000.0)
//! .collect();
//!
//! let problem = ProblemBuilder::new(stimulus.view(), response.view())
//! .unwrap()
//! .with_independent_variance(independent_uncertainty.view())
//! .unwrap()
//! .build();
//! ```
//! Note that all methods with [`panic`] if the provided stimulus, response and uncertainties
//! contain unequal numbers of elements.
//!
//! ## Reconstruction
//!
//! Given a [`Fit`] we can reconstruct unknown response from known stimulus values. This uses the
//! calculated polynomial series directly.
//! ```
//! use polycal::{AbsUncertainty, Uncertainty};
//! use ndarray::Array1;
//! use polycal::ProblemBuilder;
//!
//! let a = 1.;
//! let b = 2.;
//! let stimulus: Array1<f64> = Array1::range(0., 10., 0.5);
//! let num_data_points = stimulus.len();
//! let response: Array1<f64> = stimulus
//! .iter()
//! .map(|x| a + b * x)
//! .collect();
//! let independent_uncertainty: Array1<f64> = response
//! .iter()
//! .map(|x| x / 1000.0)
//! .collect();
//!
//! let problem = ProblemBuilder::new(stimulus.view(), response.view())
//! .unwrap()
//! .with_independent_variance(independent_uncertainty.view())
//! .unwrap()
//! .build();
//!
//! let maximum_degree = 5;
//! let best_fit = problem.solve(maximum_degree).unwrap();
//!
//! let known_stimulus = AbsUncertainty::new(1.0, 0.01);
//! let estimated_response = best_fit.response(known_stimulus);
//! ```
//! Alternatively we can calculate unknown stimulus values from known response values. This
//! numerically minimises the residual of the fit. An initial guess and maximum iteration count can
//! be provided.
//! ```
//! use polycal::{AbsUncertainty, Uncertainty};
//! use ndarray::Array1;
//! use polycal::ProblemBuilder;
//!
//! let a = 1.;
//! let b = 2.;
//! let stimulus: Array1<f64> = Array1::range(0., 10., 0.5);
//! let num_data_points = stimulus.len();
//! let response: Array1<f64> = stimulus
//! .iter()
//! .map(|x| a + b * x)
//! .collect();
//! let independent_uncertainty: Array1<f64> = response
//! .iter()
//! .map(|x| x / 1000.0)
//! .collect();
//!
//! let problem = ProblemBuilder::new(stimulus.view(), response.view())
//! .unwrap()
//! .with_independent_variance(independent_uncertainty.view())
//! .unwrap()
//! .build();
//!
//! let maximum_degree = 5;
//! let best_fit = problem.solve(maximum_degree).unwrap();
//!
//! let known_response = AbsUncertainty::new(1.0, 0.01);
//! let initial_guess = None;
//! let max_iter = Some(100);
//! let estimated_stimulus = best_fit.stimulus(
//! known_response,
//! initial_guess,
//! max_iter
//! );
//! ```
//!
#![allow(dead_code)]
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
mod builder;
mod calculate;
mod chebyshev;
mod error;
mod problem;
mod solvers;
mod utils;
extern crate blas_src;
pub type PolyCalResult<T, E> = ::std::result::Result<T, PolyCalError<E>>;
pub use builder::{ProblemBuilder, Set, Unset};
pub use calculate::Fit;
pub use cert::{AbsUncertainty, RelUncertainty, Uncertainty};
pub use chebyshev::{ChebyshevBuilder, PolynomialSeries, Series};
pub use error::PolyCalError;
pub use problem::{Constraint, Problem, ScoringStrategy};
pub use argmin::core::ArgminFloat;
pub use argmin_math::{
ArgminAdd, ArgminConj, ArgminDot, ArgminL2Norm, ArgminMul, ArgminSub, ArgminZeroLike,
};