Mon, 24 Oct 2022 10:52:19 +0300
Added type for numerical errors
use numeric_literals::replace_float_literals; use std::iter::Sum; use std::marker::PhantomData; pub use nalgebra::Const; use crate::types::Float; use crate::mapping::Mapping; use crate::linops::Linear; use crate::sets::Set; pub use crate::sets::Cube; pub use crate::loc::Loc; use super::support::*; use super::bt::*; use super::refine::*; use super::aggregator::*; use super::either::*; use crate::fe_model::base::RealLocalModel; use crate::fe_model::p2_local_model::*; /// Presentation for functions constructed as a sum of components with limited support. /// Lookup of relevant components at a specific point of a [`Loc<F,N>`] domain is done /// with a bisection tree (BT). We do notn impose that the `BT` parameter would be an in /// instance [`BTImpl`] to allow performance improvements via “pre-BTFNs” that do not /// construct a BT until being added to another function. Addition and subtraction always /// use a copy-on-write clone of the BT of the left-hand-side operand, discarding /// the BT of the right-hand-side operand. #[derive(Clone,Debug)] pub struct BTFN< F : Float, G : SupportGenerator<F, N>, BT /*: BTImpl<F, N>*/, const N : usize > /*where G::SupportType : LocalAnalysis<F, A, N>*/ { bt : BT, generator : G, _phantoms : PhantomData<F>, } impl<F : Float, G, BT, const N : usize> BTFN<F, G, BT, N> where G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, BT : BTImpl<F, N> { /// Returns the [`Support`] corresponding to the identifier `d`. /// Simply wraps [`SupportGenerator::support_for`]. #[inline] fn support_for(&self, d : G::Id) -> G::SupportType { self.generator.support_for(d) } /// Create a new BTFN. The bisection tree `bt` should be pre-initialised to /// correspond to the `generator`. Use [`Self::construct`] if no preinitialised tree /// is available. Use [`Self::new_refresh`] when the aggregator may need updates. pub fn new(bt : BT, generator : G) -> Self { BTFN { bt : bt, generator : generator, _phantoms : std::marker::PhantomData, } } /// Create a new BTFN. The bisection tree `bt` should be pre-initialised to /// correspond to the `generator`, but the aggregator may be out of date. pub fn new_refresh(bt : &BT, generator : G) -> Self { // clone().refresh_aggregator(…) as opposed to convert_aggregator // ensures that type is maintained. Due to Rc-pointer copy-on-write, // the effort is not significantly different. let mut btnew = bt.clone(); btnew.refresh_aggregator(&generator); BTFN::new(btnew, generator) } /// Create a new BTFN. Unlike [`Self::new`], this constructs the bisection tree based on /// the generator. pub fn construct(domain : Cube<F, N>, depth : BT::Depth, generator : G) -> Self { let mut bt = BT::new(domain, depth); for (d, support) in generator.all_data() { bt.insert(d, &support); } Self::new(bt, generator) } /// Construct a new instance for a different aggregator pub fn convert_aggregator<ANew>(self) -> BTFN<F, G, BT::Converted<ANew>, N> where ANew : Aggregator, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, ANew, N> { BTFN::new(self.bt.convert_aggregator(&self.generator), self.generator) } /// Change the [`BTImpl`]. pub fn instantiate< BTNew : BTImpl<F, N, Data=BT::Data>, > (self, domain : Cube<F, N>, depth : BTNew::Depth) -> BTFN<F, G, BTNew, N> where G::SupportType : LocalAnalysis<F, BTNew::Agg, N> { BTFN::construct(domain, depth, self.generator) } /// Change the generator (after, e.g., a scaling of the latter). pub fn new_generator(&self, generator : G) -> Self { BTFN::new_refresh(&self.bt, generator) } /// Refresh aggregator after updates to generator fn refresh_aggregator(&mut self) { self.bt.refresh_aggregator(&self.generator); } } /// A “pre-BTFN” with no bisection tree. pub type PreBTFN<F, G, const N : usize> = BTFN<F, G, (), N>; impl<F : Float, G, const N : usize> PreBTFN<F, G, N> where G : SupportGenerator<F, N> { /// Create a new “pre-BTFN” with no bisection tree. This can feasibly only be /// multiplied by a scalar or added to or subtracted from a proper [`BTFN`]. pub fn new_pre(generator : G) -> Self { BTFN { bt : (), generator : generator, _phantoms : std::marker::PhantomData, } } } impl<F : Float, G, BT, const N : usize> BTFN<F, G, BT, N> where G : SupportGenerator<F, N, Id=usize>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, BT : BTImpl<F, N, Data=usize> { /// Helper function for implementing [`std::ops::Add`]. fn add_another<G2>(self, other : G2) -> BTFN<F, BothGenerators<G, G2>, BT, N> where G2 : SupportGenerator<F, N, Id=usize>, G2::SupportType : LocalAnalysis<F, BT::Agg, N> { let mut bt = self.bt; let both = BothGenerators(self.generator, other); for (d, support) in both.all_right_data() { bt.insert(d, &support); } BTFN { bt : bt, generator : both, _phantoms : std::marker::PhantomData, } } } macro_rules! make_btfn_add { ($lhs:ty, $preprocess:path, $($extra_trait:ident)?) => { impl<'a, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Add<BTFN<F, G2, BT2, N>> for $lhs where BT1 : BTImpl<F, N, Data=usize>, G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, G2 : SupportGenerator<F, N, Id=usize>, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N> { type Output = BTFN<F, BothGenerators<G1, G2>, BT1, N>; #[inline] fn add(self, other : BTFN<F, G2, BT2, N>) -> Self::Output { $preprocess(self).add_another(other.generator) } } impl<'a, 'b, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Add<&'b BTFN<F, G2, BT2, N>> for $lhs where BT1 : BTImpl<F, N, Data=usize>, G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, G2 : SupportGenerator<F, N, Id=usize> + Clone, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N> { type Output = BTFN<F, BothGenerators<G1, G2>, BT1, N>; #[inline] fn add(self, other : &'b BTFN<F, G2, BT2, N>) -> Self::Output { $preprocess(self).add_another(other.generator.clone()) } } } } make_btfn_add!(BTFN<F, G1, BT1, N>, std::convert::identity, ); make_btfn_add!(&'a BTFN<F, G1, BT1, N>, Clone::clone, Clone); macro_rules! make_btfn_sub { ($lhs:ty, $preprocess:path, $($extra_trait:ident)?) => { impl<'a, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Sub<BTFN<F, G2, BT2, N>> for $lhs where BT1 : BTImpl<F, N, Data=usize>, G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, G2 : SupportGenerator<F, N, Id=usize>, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N> { type Output = BTFN<F, BothGenerators<G1, G2>, BT1, N>; #[inline] fn sub(self, other : BTFN<F, G2, BT2, N>) -> Self::Output { $preprocess(self).add_another(other.generator.neg()) } } impl<'a, 'b, F : Float, G1, G2, BT1, BT2, const N : usize> std::ops::Sub<&'b BTFN<F, G2, BT2, N>> for $lhs where BT1 : BTImpl<F, N, Data=usize>, G1 : SupportGenerator<F, N, Id=usize> + $($extra_trait)?, G2 : SupportGenerator<F, N, Id=usize> + Clone, G1::SupportType : LocalAnalysis<F, BT1::Agg, N>, G2::SupportType : LocalAnalysis<F, BT1::Agg, N>, /*&'b G2 : std::ops::Neg<Output=G2>*/ { type Output = BTFN<F, BothGenerators<G1, G2>, BT1, N>; #[inline] fn sub(self, other : &'b BTFN<F, G2, BT2, N>) -> Self::Output { // FIXME: the compiler crashes in an overflow on this. //$preprocess(self).add_another(std::ops::Neg::neg(&other.generator)) $preprocess(self).add_another(other.generator.clone().neg()) } } } } make_btfn_sub!(BTFN<F, G1, BT1, N>, std::convert::identity, ); make_btfn_sub!(&'a BTFN<F, G1, BT1, N>, Clone::clone, Clone); macro_rules! make_btfn_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { impl<F : Float, G, BT, const N : usize> std::ops::$trait_assign<F> for BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N> { #[inline] fn $fn_assign(&mut self, t : F) { self.generator.$fn_assign(t); self.refresh_aggregator(); } } impl<F : Float, G, BT, const N : usize> std::ops::$trait<F> for BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N> { type Output = Self; #[inline] fn $fn(mut self, t : F) -> Self::Output { self.generator.$fn_assign(t); self.refresh_aggregator(); self } } impl<'a, F : Float, G, BT, const N : usize> std::ops::$trait<F> for &'a BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, &'a G : std::ops::$trait<F,Output=G> { type Output = BTFN<F, G, BT, N>; #[inline] fn $fn(self, t : F) -> Self::Output { self.new_generator(self.generator.$fn(t)) } } } } make_btfn_scalarop_rhs!(Mul, mul, MulAssign, mul_assign); make_btfn_scalarop_rhs!(Div, div, DivAssign, div_assign); macro_rules! make_btfn_scalarop_lhs { ($trait:ident, $fn:ident, $fn_assign:ident, $($f:ident)+) => { $( impl<G, BT, const N : usize> std::ops::$trait<BTFN<$f, G, BT, N>> for $f where BT : BTImpl<$f, N>, G : SupportGenerator<$f, N, Id=BT::Data>, G::SupportType : LocalAnalysis<$f, BT::Agg, N> { type Output = BTFN<$f, G, BT, N>; #[inline] fn $fn(self, mut a : BTFN<$f, G, BT, N>) -> Self::Output { a.generator.$fn_assign(self); a.refresh_aggregator(); a } } impl<'a, G, BT, const N : usize> std::ops::$trait<&'a BTFN<$f, G, BT, N>> for $f where BT : BTImpl<$f, N>, G : SupportGenerator<$f, N, Id=BT::Data> + Clone, G::SupportType : LocalAnalysis<$f, BT::Agg, N>, // FIXME: This causes compiler overflow /*&'a G : std::ops::$trait<$f,Output=G>*/ { type Output = BTFN<$f, G, BT, N>; #[inline] fn $fn(self, a : &'a BTFN<$f, G, BT, N>) -> Self::Output { let mut tmp = a.generator.clone(); tmp.$fn_assign(self); a.new_generator(tmp) // FIXME: Prevented by the compiler overflow above. //a.new_generator(a.generator.$fn(a)) } } )+ } } make_btfn_scalarop_lhs!(Mul, mul, mul_assign, f32 f64); make_btfn_scalarop_lhs!(Div, div, div_assign, f32 f64); macro_rules! make_btfn_unaryop { ($trait:ident, $fn:ident) => { impl<F : Float, G, BT, const N : usize> std::ops::$trait for BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N> { type Output = Self; #[inline] fn $fn(mut self) -> Self::Output { self.generator = self.generator.$fn(); self.refresh_aggregator(); self } } /*impl<'a, F : Float, G, BT, const N : usize> std::ops::$trait for &'a BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, &'a G : std::ops::$trait<Output=G> { type Output = BTFN<F, G, BT, N>; #[inline] fn $fn(self) -> Self::Output { self.new_generator(std::ops::$trait::$fn(&self.generator)) } }*/ } } make_btfn_unaryop!(Neg, neg); // // Mapping // impl<'a, F : Float, G, BT, V, const N : usize> Mapping<&'a Loc<F,N>> for BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N> + Mapping<&'a Loc<F,N>, Codomain = V>, V : Sum { type Codomain = V; fn value(&self, x : &'a Loc<F,N>) -> Self::Codomain { self.bt.iter_at(x).map(|&d| self.support_for(d).value(x)).sum() } } impl<F : Float, G, BT, V, const N : usize> Mapping<Loc<F,N>> for BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N> + Mapping<Loc<F,N>, Codomain = V>, V : Sum { type Codomain = V; fn value(&self, x : Loc<F,N>) -> Self::Codomain { self.bt.iter_at(&x).map(|&d| self.support_for(d).value(x)).sum() } } impl<F : Float, G, BT, const N : usize> GlobalAnalysis<F, BT::Agg> for BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N> { #[inline] fn global_analysis(&self) -> BT::Agg { self.bt.global_analysis() } } // // Blanket implementation of BTFN as a linear functional over objects // that are linear functionals over BTFN. // impl<X, F : Float, G, BT, const N : usize> Linear<X> for BTFN<F, G, BT, N> where BT : BTImpl<F, N>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : LocalAnalysis<F, BT::Agg, N>, X : Linear<BTFN<F, G, BT, N>, Codomain=F> { type Codomain = F; #[inline] fn apply(&self, x : &X) -> F { x.apply(self) } } /// Helper trait for performing approximate minimisation using P2 elements. pub trait P2Minimise<U, F : Float> : Set<U> { fn p2_minimise<G : Fn(&U) -> F>(&self, g : G) -> (U, F); } impl<F : Float> P2Minimise<Loc<F, 1>, F> for Cube<F, 1> { fn p2_minimise<G : Fn(&Loc<F, 1>) -> F>(&self, g : G) -> (Loc<F, 1>, F) { let interval = Simplex(self.corners()); interval.p2_model(&g).minimise(&interval) } } #[replace_float_literals(F::cast_from(literal))] impl<F : Float> P2Minimise<Loc<F, 2>, F> for Cube<F, 2> { fn p2_minimise<G : Fn(&Loc<F, 2>) -> F>(&self, g : G) -> (Loc<F, 2>, F) { if false { // Split into two triangle (simplex) with separate P2 model in each. // The six nodes of each triangle are the corners and the edges. let [a, b, c, d] = self.corners(); let [va, vb, vc, vd] = [g(&a), g(&b), g(&c), g(&d)]; let ab = midpoint(&a, &b); let bc = midpoint(&b, &c); let ca = midpoint(&c, &a); let cd = midpoint(&c, &d); let da = midpoint(&d, &a); let [vab, vbc, vca, vcd, vda] = [g(&ab), g(&bc), g(&ca), g(&cd), g(&da)]; let s1 = Simplex([a, b, c]); let m1 = P2LocalModel::<F, 2, 3, 3, 3>::new( &[a, b, c, ab, bc, ca], &[va, vb, vc, vab, vbc, vca] ); let r1@(_, v1) = m1.minimise(&s1); let s2 = Simplex([c, d, a]); let m2 = P2LocalModel::<F, 2, 3, 3, 3>::new( &[c, d, a, cd, da, ca], &[vc, vd, va, vcd, vda, vca] ); let r2@(_, v2) = m2.minimise(&s2); if v1 < v2 { r1 } else { r2 } } else { // Single P2 model for the entire cube. let [a, b, c, d] = self.corners(); let [va, vb, vc, vd] = [g(&a), g(&b), g(&c), g(&d)]; let [e, f] = match 'r' { 'm' => [(&a + &b + &c) / 3.0, (&c + &d + &a) / 3.0], 'c' => [midpoint(&a, &b), midpoint(&a, &d)], 'w' => [(&a + &b * 2.0) / 3.0, (&a + &d * 2.0) / 3.0], 'r' => { // Pseudo-randomise edge midpoints let Loc([x, y]) = a; let tmp : f64 = (x+y).as_(); match tmp.to_bits() % 4 { 0 => [midpoint(&a, &b), midpoint(&a, &d)], 1 => [midpoint(&c, &d), midpoint(&a, &d)], 2 => [midpoint(&a, &b), midpoint(&b, &c)], _ => [midpoint(&c, &d), midpoint(&b, &c)], } }, _ => [self.center(), (&a + &b) / 2.0], }; let [ve, vf] = [g(&e), g(&f)]; let m1 = P2LocalModel::<F, 2, 3, 3, 3>::new( &[a, b, c, d, e, f], &[va, vb, vc, vd, ve, vf], ); m1.minimise(self) } } } struct RefineMax; struct RefineMin; /// TODO: update doc. /// Simple refiner that keeps the difference between upper and lower bounds /// of [`Bounded`] functions within $ε$. The functions have to also be continuous /// for this refiner to ever succeed. /// The type parameter `T` should be either [`RefineMax`] or [`RefineMin`]. struct P2Refiner<F : Float, T> { bound : Option<F>, tolerance : F, max_steps : usize, #[allow(dead_code)] // `how` is just for type system purposes. how : T, } impl<F : Float, G, const N : usize> Refiner<F, Bounds<F>, G, N> for P2Refiner<F, RefineMax> where Cube<F, N> : P2Minimise<Loc<F, N>, F>, G : SupportGenerator<F, N>, G::SupportType : for<'a> Mapping<&'a Loc<F,N>,Codomain=F> + LocalAnalysis<F, Bounds<F>, N> { type Result = Option<(Loc<F, N>, F)>; type Sorting = UpperBoundSorting<F>; fn refine( &self, aggregator : &Bounds<F>, cube : &Cube<F, N>, data : &Vec<G::Id>, generator : &G, step : usize ) -> RefinerResult<Bounds<F>, Self::Result> { // g gives the negative of the value of the function presented by `data` and `generator`. let g = move |x : &Loc<F,N>| { let f = move |&d| generator.support_for(d).value(x); -data.iter().map(f).sum::<F>() }; // … so the negative of the minimum is the maximm we want. let (x, neg_v) = cube.p2_minimise(g); let v = -neg_v; if self.bound.map_or(false, |b| aggregator.upper() <= b + self.tolerance) { // The upper bound is below the maximisation threshold. Don't bother with this cube. RefinerResult::Uncertain(*aggregator, None) } else if step < self.max_steps && (aggregator.upper() - v).abs() > self.tolerance { // The function isn't refined enough in `cube`, so return None // to indicate that further subdivision is required. RefinerResult::NeedRefinement } else { // The data is refined enough, so return new hopefully better bounds // and the maximiser. let res = (x, v); let bounds = Bounds(aggregator.lower(), v); RefinerResult::Uncertain(bounds, Some(res)) } } } impl<F : Float, G, const N : usize> Refiner<F, Bounds<F>, G, N> for P2Refiner<F, RefineMin> where Cube<F, N> : P2Minimise<Loc<F, N>, F>, G : SupportGenerator<F, N>, G::SupportType : for<'a> Mapping<&'a Loc<F,N>,Codomain=F> + LocalAnalysis<F, Bounds<F>, N> { type Result = Option<(Loc<F, N>, F)>; type Sorting = LowerBoundSorting<F>; fn refine( &self, aggregator : &Bounds<F>, cube : &Cube<F, N>, data : &Vec<G::Id>, generator : &G, step : usize ) -> RefinerResult<Bounds<F>, Self::Result> { // g gives the value of the function presented by `data` and `generator`. let g = move |x : &Loc<F,N>| { let f = move |&d| generator.support_for(d).value(x); data.iter().map(f).sum::<F>() }; // Minimise it. let (x, v) = cube.p2_minimise(g); if self.bound.map_or(false, |b| aggregator.lower() >= b - self.tolerance) { // The lower bound is above the minimisation threshold. Don't bother with this cube. RefinerResult::Uncertain(*aggregator, None) } else if step < self.max_steps && (aggregator.lower() - v).abs() > self.tolerance { // The function isn't refined enough in `cube`, so return None // to indicate that further subdivision is required. RefinerResult::NeedRefinement } else { // The data is refined enough, so return new hopefully better bounds // and the minimiser. let res = (x, v); let bounds = Bounds(v, aggregator.upper()); RefinerResult::Uncertain(bounds, Some(res)) } } } struct BoundRefiner<F : Float, T> { bound : F, tolerance : F, max_steps : usize, #[allow(dead_code)] // `how` is just for type system purposes. how : T, } impl<F : Float, G, const N : usize> Refiner<F, Bounds<F>, G, N> for BoundRefiner<F, RefineMax> where G : SupportGenerator<F, N> { type Result = bool; type Sorting = UpperBoundSorting<F>; fn refine( &self, aggregator : &Bounds<F>, _cube : &Cube<F, N>, _data : &Vec<G::Id>, _generator : &G, step : usize ) -> RefinerResult<Bounds<F>, Self::Result> { if aggregator.upper() <= self.bound + self.tolerance { // Below upper bound within tolerances. Indicate uncertain success. RefinerResult::Uncertain(*aggregator, true) } else if aggregator.lower() >= self.bound - self.tolerance { // Above upper bound within tolerances. Indicate certain failure. RefinerResult::Certain(false) } else if step < self.max_steps { // No decision possible, but within step bounds - further subdivision is required. RefinerResult::NeedRefinement } else { // No decision possible, but past step bounds RefinerResult::Uncertain(*aggregator, false) } } } impl<F : Float, G, BT, const N : usize> BTFN<F, G, BT, N> where BT : BTSearch<F, N, Agg=Bounds<F>>, G : SupportGenerator<F, N, Id=BT::Data>, G::SupportType : for<'a> Mapping<&'a Loc<F,N>,Codomain=F> + LocalAnalysis<F, Bounds<F>, N>, Cube<F, N> : P2Minimise<Loc<F, N>, F> { /// Try to maximise the BTFN within stated value `tolerance`, taking at most `max_steps` /// refinement steps. Returns the maximiser and the maximum value. pub fn maximise(&mut self, tolerance : F, max_steps : usize) -> (Loc<F, N>, F) { let refiner = P2Refiner{ tolerance, max_steps, how : RefineMax, bound : None }; self.bt.search_and_refine(&refiner, &self.generator).expect("Refiner failure.").unwrap() } /// Try to maximise the BTFN within stated value `tolerance`, taking at most `max_steps` /// refinement steps. Returns the maximiser and the maximum value when one is found above /// the `bound` threshold, otherwise `None`. pub fn maximise_above(&mut self, bound : F, tolerance : F, max_steps : usize) -> Option<(Loc<F, N>, F)> { let refiner = P2Refiner{ tolerance, max_steps, how : RefineMax, bound : Some(bound) }; self.bt.search_and_refine(&refiner, &self.generator).expect("Refiner failure.") } /// Try to minimise the BTFN within stated value `tolerance`, taking at most `max_steps` /// refinement steps. Returns the minimiser and the minimum value. pub fn minimise(&mut self, tolerance : F, max_steps : usize) -> (Loc<F, N>, F) { let refiner = P2Refiner{ tolerance, max_steps, how : RefineMin, bound : None }; self.bt.search_and_refine(&refiner, &self.generator).expect("Refiner failure.").unwrap() } /// Try to minimise the BTFN within stated value `tolerance`, taking at most `max_steps` /// refinement steps. Returns the minimiser and the minimum value when one is found below /// the `bound` threshold, otherwise `None`. pub fn minimise_below(&mut self, bound : F, tolerance : F, max_steps : usize) -> Option<(Loc<F, N>, F)> { let refiner = P2Refiner{ tolerance, max_steps, how : RefineMin, bound : Some(bound) }; self.bt.search_and_refine(&refiner, &self.generator).expect("Refiner failure.") } /// Verify that the BTFN is has stated upper `bound` within stated `tolerance, taking at most /// `max_steps` refinment steps. pub fn has_upper_bound(&mut self, bound : F, tolerance : F, max_steps : usize) -> bool { let refiner = BoundRefiner{ bound, tolerance, max_steps, how : RefineMax }; self.bt.search_and_refine(&refiner, &self.generator).expect("Refiner failure.") } }