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