Skip to main content

optimization_engine/core/fbs/
fbs_optimizer.rs

1//! FBS Algorithm
2//!
3use crate::{
4    constraints,
5    core::{
6        fbs::fbs_engine::FBSEngine, fbs::FBSCache, AlgorithmEngine, ExitStatus, Optimizer, Problem,
7        SolverStatus,
8    },
9    matrix_operations, FunctionCallResult, SolverError,
10};
11use num::Float;
12use std::time;
13
14const MAX_ITER: usize = 100_usize;
15
16/// Optimiser using forward-backward splitting iterations (projected gradient)
17///
18/// Note that an `FBSOptimizer` holds a reference to an instance of `FBSEngine`
19/// which needs to be created externally. A mutable reference to that `FBSEgnine`
20/// is provided to the optimizer.
21///
22/// The `FBSEngine` is supposed to be updated whenever you need to solve
23/// a different optimization problem.
24///
25///
26pub struct FBSOptimizer<'a, GradientType, ConstraintType, CostType, T = f64>
27where
28    T: Float,
29    GradientType: Fn(&[T], &mut [T]) -> FunctionCallResult,
30    CostType: Fn(&[T], &mut T) -> FunctionCallResult,
31    ConstraintType: constraints::Constraint<T>,
32{
33    fbs_engine: FBSEngine<'a, GradientType, ConstraintType, CostType, T>,
34    max_iter: usize,
35    max_duration: Option<time::Duration>,
36}
37
38impl<'a, GradientType, ConstraintType, CostType, T>
39    FBSOptimizer<'a, GradientType, ConstraintType, CostType, T>
40where
41    T: Float,
42    GradientType: Fn(&[T], &mut [T]) -> FunctionCallResult + 'a,
43    CostType: Fn(&[T], &mut T) -> FunctionCallResult + 'a,
44    ConstraintType: constraints::Constraint<T> + 'a,
45{
46    /// Constructs a new instance of `FBSOptimizer`
47    ///
48    /// ## Arguments
49    ///
50    /// - `problem`: problem definition
51    /// - `cache`: instance of `FBSCache`
52    #[must_use]
53    pub fn new(
54        problem: Problem<'a, GradientType, ConstraintType, CostType, T>,
55        cache: &'a mut FBSCache<T>,
56    ) -> Self {
57        FBSOptimizer {
58            fbs_engine: FBSEngine::new(problem, cache),
59            max_iter: MAX_ITER,
60            max_duration: None,
61        }
62    }
63
64    /// Sets the tolerance
65    ///
66    /// ## Panics
67    ///
68    /// The method panics if the specified tolerance is not positive
69    #[must_use]
70    pub fn with_tolerance(
71        self,
72        tolerance: T,
73    ) -> FBSOptimizer<'a, GradientType, ConstraintType, CostType, T> {
74        assert!(tolerance > T::zero());
75
76        self.fbs_engine.cache.tolerance = tolerance;
77        self
78    }
79
80    /// Sets the maximum number of iterations
81    #[must_use]
82    pub fn with_max_iter(
83        mut self,
84        max_iter: usize,
85    ) -> FBSOptimizer<'a, GradientType, ConstraintType, CostType, T> {
86        self.max_iter = max_iter;
87        self
88    }
89
90    /// Sets the maximum number of iterations
91    #[must_use]
92    pub fn with_max_duration(
93        mut self,
94        max_duration: time::Duration,
95    ) -> FBSOptimizer<'a, GradientType, ConstraintType, CostType, T> {
96        self.max_duration = Some(max_duration);
97        self
98    }
99
100    /// Solves the optimization problem for decision variables of scalar type `T`.
101    pub fn solve(&mut self, u: &mut [T]) -> Result<SolverStatus<T>, SolverError> {
102        let now = web_time::Instant::now();
103
104        self.fbs_engine.init(u)?;
105
106        let mut num_iter: usize = 0;
107        let mut step_flag = self.fbs_engine.step(u)?;
108
109        if let Some(dur) = self.max_duration {
110            while step_flag && num_iter < self.max_iter && now.elapsed() <= dur {
111                num_iter += 1;
112                step_flag = self.fbs_engine.step(u)?
113            }
114        } else {
115            while step_flag && num_iter < self.max_iter {
116                num_iter += 1;
117                step_flag = self.fbs_engine.step(u)?
118            }
119        }
120
121        let mut cost_value = T::zero();
122        (self.fbs_engine.problem.cost)(u, &mut cost_value)?;
123
124        if !matrix_operations::is_finite(u) || !cost_value.is_finite() {
125            return Err(SolverError::NotFiniteComputation(
126                "final FBS iterate or cost is non-finite",
127            ));
128        }
129
130        Ok(SolverStatus::new(
131            if num_iter < self.max_iter {
132                ExitStatus::Converged
133            } else {
134                ExitStatus::NotConvergedIterations
135            },
136            num_iter,
137            now.elapsed(),
138            self.fbs_engine.cache.norm_fpr,
139            cost_value,
140        ))
141    }
142}
143
144impl<'life, GradientType, ConstraintType, CostType, T> Optimizer<T>
145    for FBSOptimizer<'life, GradientType, ConstraintType, CostType, T>
146where
147    T: Float,
148    GradientType: Fn(&[T], &mut [T]) -> FunctionCallResult + 'life,
149    CostType: Fn(&[T], &mut T) -> FunctionCallResult + 'life,
150    ConstraintType: constraints::Constraint<T> + 'life,
151{
152    fn solve(&mut self, u: &mut [T]) -> Result<SolverStatus<T>, SolverError> {
153        FBSOptimizer::solve(self, u)
154    }
155}