pub struct M2N {
pub moments: Vec<f64>,
pub epsilon: f64,
pub factor: f64,
pub n_runs: usize,
pub variant: usize,
pub max_iter: usize,
pub num_workers: isize,
pub new_moments: Vec<f64>,
pub parameters: Vec<f64>,
pub error: f64,
}Expand description
An EF3M fitter for a mixture of two Gaussians: the target moments, the search settings, and the best fit found so far.
Build it with M2N::new or M2N::with_defaults and run M2N::mp_fit or
M2N::single_fit_loop. Parameter vectors are [mu_1, mu_2, sigma_1, sigma_2, p_1].
Fields§
§moments: Vec<f64>Target raw moments E[x^k], k = 1..=5. The fit needs all five.
epsilon: f64Convergence tolerance on p_1; also the spacing of the mu_2 start grid, which has
about 1 / epsilon points. Must be finite and > 0 (M2N::single_fit_loop checks).
factor: f64Width of the mu_2 start grid in standard deviations: starts run from
m_1 + epsilon * factor * sigma to about m_1 + factor * sigma.
n_runs: usizeNumber of independent searches M2N::mp_fit runs.
variant: usize1 to fit four moments (M2N::iter_4) or 2 to fit five (M2N::iter_5).
max_iter: usizeMaximum iterations of one attempt in M2N::fit; 0 runs none.
num_workers: isizeignored: M2N::mp_fit runs serially
Ignored: M2N::mp_fit runs serially. Kept only so existing code still compiles.
new_moments: Vec<f64>Scratch: the moments implied by the last iterate of M2N::fit (or by
M2N::get_moments with return_result = false).
parameters: Vec<f64>Best parameters found so far, [mu_1, mu_2, sigma_1, sigma_2, p_1]; all zeros until
an admissible iterate improves on the initial error.
error: f64Squared moment error of parameters; starts at the sum of squared target moments.
Implementations§
Source§impl M2N
impl M2N
Sourcepub fn new(
moments: Vec<f64>,
epsilon: f64,
factor: f64,
n_runs: usize,
variant: usize,
max_iter: usize,
num_workers: isize,
) -> Self
pub fn new( moments: Vec<f64>, epsilon: f64, factor: f64, n_runs: usize, variant: usize, max_iter: usize, num_workers: isize, ) -> Self
Creates a fitter; see the field docs for each argument. Nothing is validated here:
M2N::single_fit_loop and M2N::mp_fit check moments and variant.
num_workers is ignored (M2N::mp_fit runs serially).
parameters starts at zeros and error at the sum of squared moments.
Sourcepub fn with_defaults(moments: Vec<f64>) -> Self
pub fn with_defaults(moments: Vec<f64>) -> Self
Creates a fitter with the defaults epsilon = 1e-5, factor = 5, n_runs = 1,
variant = 2 (five moments, the more accurate variant) and max_iter = 100_000.
Sourcepub fn get_moments(
&mut self,
parameters: &[f64; 5],
return_result: bool,
) -> Option<Vec<f64>>
pub fn get_moments( &mut self, parameters: &[f64; 5], return_result: bool, ) -> Option<Vec<f64>>
The first five raw moments E[x^k], k = 1..=5, of the mixture with the given
parameters [mu_1, mu_2, sigma_1, sigma_2, p_1].
With return_result = true the moments are returned; with false they are stored in
M2N::new_moments and None is returned. Taking exactly five parameters by type
means a short slice cannot reach the indexing (convert a slice with
slice.try_into()); the values themselves are not validated.
§Examples
See the module documentation.
Sourcepub fn iter_4(&self, mu_2: f64, p_1: f64) -> Result<Vec<f64>, InputError>
pub fn iter_4(&self, mu_2: f64, p_1: f64) -> Result<Vec<f64>, InputError>
One step of the four-moment variant (variant 1): from a guess of mu_2 and p_1,
solves mu_1 from m_1, sigma_2 from m_3, sigma_1 from m_2, and a new p_1
from m_4.
Returns [mu_1, mu_2, sigma_1, sigma_2, p_1_new] (with mu_2 unchanged), or an empty
vector if the step is inadmissible: a zero denominator, a negative variance, or a new
p_1 outside [0, 1] (NaN included).
§Errors
InputError::TooShort if M2N::moments has fewer than four entries.
§Examples
See the module documentation.
Sourcepub fn iter_5(&self, mu_2: f64, p_1: f64) -> Result<Vec<f64>, InputError>
pub fn iter_5(&self, mu_2: f64, p_1: f64) -> Result<Vec<f64>, InputError>
One step of the five-moment variant. Like the paper, it takes the positive root for mu_2,
so on raw moments it cannot return mu_2 < 0; single_fit_loop therefore runs this variant
on centred moments (see centred_fit_loop).
From a guess of mu_2 and p_1, solves mu_1 from m_1, sigma_2 from m_3,
sigma_1 from m_2, a new mu_2 from m_4 (positive root) and a new p_1 from
m_5. Returns [mu_1, mu_2_new, sigma_1, sigma_2, p_1_new], or an empty vector if the
step is inadmissible: a zero denominator, a negative variance or mu_2^2,
1 - p_1 < 1e-4, or a new p_1 outside [0, 1] (NaN included). Called directly on
raw moments it still takes the positive root (#115).
§Errors
InputError::TooShort if M2N::moments has fewer than five entries.
§Examples
See the module documentation.
Sourcepub fn fit(&mut self, mu_2: f64) -> Result<(), InputError>
pub fn fit(&mut self, mu_2: f64) -> Result<(), InputError>
One EF3M attempt from the starting mu_2 and a random starting p_1 in [0, 1).
Iterates M2N::iter_4 or M2N::iter_5 (by M2N::variant) until p_1 moves by
less than M2N::epsilon, a step is inadmissible, or M2N::max_iter iterations
have run. Whenever an iterate’s squared moment error (over the first five
target moments) beats
M2N::error, it replaces M2N::parameters and M2N::error, so they always
hold the best iterate seen across calls, not the last one. The attempt runs on
M2N::moments as given (for variant 2, on raw moments it cannot return
mu_2 < 0); M2N::single_fit_loop is the entry point that centres them.
§Errors
InputError::OutOfRangeifvariantis not 1 or 2.InputError::TooShortifM2N::momentshas fewer than four entries (variant 1) or five (variant 2).
Inadmissible steps and hitting max_iter end the attempt with Ok(()).
Sourcepub fn single_fit_loop(
&mut self,
epsilon_override: Option<f64>,
) -> Result<Vec<FitResultRow>, InputError>
pub fn single_fit_loop( &mut self, epsilon_override: Option<f64>, ) -> Result<Vec<FitResultRow>, InputError>
One EF3M search: runs M2N::fit from each start
mu_2 = m_1 + i * epsilon * factor * sigma, i = 1, 2, ... up to about
1 / epsilon, where sigma is the standard deviation implied by the moments, and
returns the best fit found.
epsilon_override, when Some, replaces M2N::epsilon. The search state (M2N::parameters,
M2N::error) is reset first, and holds the best fit afterwards.
Variant 2 with a non-zero mean is fitted about the mean: the search runs on the
moments of X - E[X] and the mean is added back to mu_1 and mu_2, so a negative
mu_2 can be recovered (#115). The row’s error is always measured against the raw
moments in M2N::moments.
Returns at most one row: none if no start produced an admissible iterate that improves on the initial error (the sum of squared moments). The result is random.
§Errors
InputError::TooShortifM2N::momentshas fewer than five entries.InputError::OutOfRangeifM2N::variantis not 1 or 2, or if the epsilon in use (epsilon_override, elseM2N::epsilon) is not finite and> 0. Zero used to build a start grid ofusize::MAXpoints that never finished, and a negative or NaN value silently gave an empty result. Nothing is modified when this is returned.
Non-finite or inconsistent moments (for example a negative implied variance) are not
errors: every attempt fails and the result is empty. The grid has about 1 / epsilon
points, so the run time grows as epsilon shrinks.
use openquant::ef3m::M2N;
// Exact moments of 0.7 N(-1, 1) + 0.3 N(2, 0.5^2), fitted with five moments.
let truth = [-1.0, 2.0, 1.0, 0.5, 0.7];
let moments = M2N::with_defaults(vec![]).get_moments(&truth, true).unwrap();
let mut m2n = M2N::new(moments.clone(), 1e-4, 5.0, 1, 2, 100_000, 1);
let rows = m2n.single_fit_loop(None).unwrap();
assert_eq!(rows.len(), 1);
let fit = [rows[0].mu_1, rows[0].mu_2, rows[0].sigma_1, rows[0].sigma_2, rows[0].p_1];
// The starting p_1 is random, but at this epsilon the fit recovers the mixture.
assert!(fit.iter().zip(&truth).all(|(a, b)| (a - b).abs() < 0.05), "{fit:?}");
// The row's error is the squared error of its own parameters on the raw moments.
let implied = m2n.get_moments(&fit, true).unwrap();
let error: f64 = moments.iter().zip(&implied).map(|(a, b)| (a - b).powi(2)).sum();
assert!((error - rows[0].error).abs() < 1e-9);
// Fewer than five moments are rejected.
assert!(M2N::with_defaults(vec![0.0, 1.0]).single_fit_loop(None).is_err());Sourcepub fn mp_fit(&self) -> Result<Vec<FitResultRow>, InputError>
pub fn mp_fit(&self) -> Result<Vec<FitResultRow>, InputError>
Runs M2N::single_fit_loop M2N::n_runs times, each on a fresh copy of self,
and concatenates the rows (at most one per run). self is not modified.
The runs execute serially (the deprecated num_workers field is ignored). Summarise the
rows with most_likely_parameters.
§Errors
Any error of M2N::single_fit_loop: InputError::TooShort for fewer than five
moments, InputError::OutOfRange for a variant other than 1 or 2 or an epsilon
that is not finite and > 0.
use openquant::ef3m::{most_likely_parameters, M2N};
let truth = [-1.0, 2.0, 1.0, 0.5, 0.7];
let moments = M2N::with_defaults(vec![]).get_moments(&truth, true).unwrap();
let rows = M2N::new(moments, 1e-4, 5.0, 5, 2, 100_000, 1).mp_fit().unwrap();
assert_eq!(rows.len(), 5);
let fit = most_likely_parameters(&rows, None, 1_000);
assert!((fit["mu_2"] - 2.0).abs() < 0.05 && (fit["p_1"] - 0.7).abs() < 0.05);
let bad_variant = M2N::new(vec![0.0; 5], 1e-4, 5.0, 1, 3, 100, 1);
assert!(bad_variant.mp_fit().is_err());Trait Implementations§
Auto Trait Implementations§
impl Freeze for M2N
impl RefUnwindSafe for M2N
impl Send for M2N
impl Sync for M2N
impl Unpin for M2N
impl UnsafeUnpin for M2N
impl UnwindSafe for M2N
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> Key for Twhere
T: Clone,
impl<T> Key for Twhere
T: Clone,
§impl<T> Pointable for T
impl<T> Pointable for T
§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read more§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.