ellalgo_rs/
cutting_plane.rs

1#![allow(non_snake_case)]
2
3#[derive(Debug, PartialEq, Eq)]
4pub enum CutStatus {
5    Success,
6    NoSoln,
7    NoEffect,
8    Unknown,
9}
10
11pub struct Options {
12    pub max_iters: usize, // maximum number of iterations
13    pub tolerance: f64,   // error tolerrance
14}
15
16impl Default for Options {
17    fn default() -> Options {
18        Options {
19            max_iters: 2000,
20            tolerance: 1e-20,
21        }
22    }
23}
24
25type CInfo = (bool, usize);
26
27pub trait UpdateByCutChoice<SearchSpace> {
28    type ArrayType; // f64 for 1D; ndarray::Array1<f64> for general
29    fn update_bias_cut_by(&self, space: &mut SearchSpace, grad: &Self::ArrayType) -> CutStatus;
30    fn update_central_cut_by(&self, space: &mut SearchSpace, grad: &Self::ArrayType) -> CutStatus;
31    fn update_q_by(&self, space: &mut SearchSpace, grad: &Self::ArrayType) -> CutStatus;
32}
33
34/// Oracle for feasibility problems
35pub trait OracleFeas<ArrayType> {
36    type CutChoice; // f64 for single cut; (f64, Option<f64) for parallel cut
37    fn assess_feas(&mut self, xc: &ArrayType) -> Option<(ArrayType, Self::CutChoice)>;
38}
39
40/// Oracle for feasibility problems
41pub trait OracleFeas2<ArrayType>: OracleFeas<ArrayType> {
42    fn update(&mut self, gamma: f64);
43}
44
45/// Oracle for optimization problems
46pub trait OracleOptim<ArrayType> {
47    type CutChoice; // f64 for single cut; (f64, Option<f64>) for parallel cut
48    fn assess_optim(
49        &mut self,
50        xc: &ArrayType,
51        gamma: &mut f64,
52    ) -> ((ArrayType, Self::CutChoice), bool);
53}
54
55/// Oracle for quantized optimization problems
56pub trait OracleOptimQ<ArrayType> {
57    type CutChoice; // f64 for single cut; (f64, Option<f64>) for parallel cut
58    fn assess_optim_q(
59        &mut self,
60        xc: &ArrayType,
61        gamma: &mut f64,
62        retry: bool,
63    ) -> ((ArrayType, Self::CutChoice), bool, ArrayType, bool);
64}
65
66/// Oracle for binary search
67pub trait OracleBS {
68    fn assess_bs(&mut self, gamma: f64) -> bool;
69}
70
71pub trait SearchSpace {
72    type ArrayType; // f64 for 1D; ndarray::Array1<f64> for general
73
74    fn xc(&self) -> Self::ArrayType;
75
76    fn tsq(&self) -> f64; // measure of the search space
77
78    fn update_bias_cut<T>(&mut self, cut: &(Self::ArrayType, T)) -> CutStatus
79    where
80        T: UpdateByCutChoice<Self, ArrayType = Self::ArrayType>,
81        Self: Sized;
82
83    fn update_central_cut<T>(&mut self, cut: &(Self::ArrayType, T)) -> CutStatus
84    where
85        T: UpdateByCutChoice<Self, ArrayType = Self::ArrayType>,
86        Self: Sized;
87
88    fn set_xc(&mut self, x: Self::ArrayType);
89}
90
91pub trait SearchSpaceQ {
92    type ArrayType; // f64 for 1D; ndarray::Array1<f64> for general
93
94    fn xc(&self) -> Self::ArrayType;
95
96    fn tsq(&self) -> f64; // measure of the search space
97
98    fn update_q<T>(&mut self, cut: &(Self::ArrayType, T)) -> CutStatus
99    where
100        T: UpdateByCutChoice<Self, ArrayType = Self::ArrayType>,
101        Self: Sized;
102}
103
104/// The function `cutting_plane_feas` iteratively updates a search space using a cutting plane oracle
105/// until a feasible solution is found or the maximum number of iterations is reached.
106///
107/// Arguments:
108///
109/// * `omega`: `omega` is an instance of the `Oracle` trait, which represents an oracle that provides
110///             information about the feasibility of a solution. The `Oracle` trait has a method `assess_feas` that
111///             takes a reference to the current solution `&space.xc()` and returns an optional `
112/// * `space`: The `space` parameter represents the search space in which the optimization problem is
113///             being solved. It is a mutable reference to an object that implements the `SearchSpace` trait.
114/// * `options`: The `options` parameter is of type `Options` and contains various settings for the
115///             cutting plane algorithm. It likely includes properties such as `max_iters` (maximum number of
116///             iterations), `` (tolerance for termination), and other parameters that control the behavior of
117///             the algorithm.
118///
119/// Returns:
120///
121/// The function `cutting_plane_feas` returns a tuple `(bool, usize)`. The first element of the tuple
122/// represents whether a feasible solution was obtained (`true` if yes, `false` if no), and the second
123/// element represents the number of iterations performed.
124#[allow(dead_code)]
125pub fn cutting_plane_feas<T, Oracle, Space>(
126    omega: &mut Oracle,
127    space: &mut Space,
128    options: &Options,
129) -> (Option<Space::ArrayType>, usize)
130where
131    T: UpdateByCutChoice<Space, ArrayType = Space::ArrayType>,
132    Oracle: OracleFeas<Space::ArrayType, CutChoice = T>,
133    Space: SearchSpace,
134{
135    for niter in 0..options.max_iters {
136        let cut = omega.assess_feas(&space.xc()); // query the oracle at &space.xc()
137        if cut.is_none() {
138            // feasible sol'n obtained
139            return (Some(space.xc()), niter);
140        }
141        let status = space.update_bias_cut::<T>(&cut.unwrap()); // update space
142        if status != CutStatus::Success || space.tsq() < options.tolerance {
143            return (None, niter);
144        }
145    }
146    (None, options.max_iters)
147}
148
149/// The function `cutting_plane_optim` performs cutting plane optimization on a given search space using
150/// an oracle.
151///
152/// Arguments:
153///
154/// * `omega`: The `omega` parameter is an instance of the `Oracle` trait, which represents an
155///             optimization oracle. The oracle provides information about the optimization problem, such as the
156///             objective function and constraints.
157/// * `space`: The `space` parameter represents the search space, which is a type that implements the
158///             `SearchSpace` trait. It contains the current state of the optimization problem, including the
159///             decision variables and any additional information needed for the optimization algorithm.
160/// * `gamma`: The parameter `gamma` represents the current value of the gamma function that the
161///             optimization algorithm is trying to minimize.
162/// * `options`: The `options` parameter is of type `Options` and contains various settings for the
163///             optimization algorithm. It likely includes parameters such as the maximum number of iterations
164///             (`max_iters`) and the tolerance (``) for convergence.
165#[allow(dead_code)]
166pub fn cutting_plane_optim<T, Oracle, Space>(
167    omega: &mut Oracle,
168    space: &mut Space,
169    gamma: &mut f64,
170    options: &Options,
171) -> (Option<Space::ArrayType>, usize)
172where
173    T: UpdateByCutChoice<Space, ArrayType = Space::ArrayType>,
174    Oracle: OracleOptim<Space::ArrayType, CutChoice = T>,
175    Space: SearchSpace,
176{
177    let mut x_best: Option<Space::ArrayType> = None;
178
179    for niter in 0..options.max_iters {
180        let (cut, shrunk) = omega.assess_optim(&space.xc(), gamma); // query the oracle at &space.xc()
181        let status = if shrunk {
182            // better gamma obtained
183            x_best = Some(space.xc());
184            space.update_central_cut::<T>(&cut) // update space
185        } else {
186            space.update_bias_cut::<T>(&cut) // update space
187        };
188        if status != CutStatus::Success || space.tsq() < options.tolerance {
189            return (x_best, niter);
190        }
191    }
192    (x_best, options.max_iters)
193} // END
194
195/// The function implements the cutting-plane method for solving a convex discrete optimization problem.
196///
197/// Arguments:
198///
199/// * `omega`: The parameter "omega" is an instance of the OracleOptimQ trait, which represents an
200///             oracle that provides assessments for the cutting-plane method. It is used to query the oracle for
201///             assessments on the current solution.
202/// * `space_q`: The parameter `space_q` is a mutable reference to a `Space` object, which represents
203///             the search space containing the optimal solution `x*`. It is used to update the space based on the
204///             cuts obtained from the oracle.
205/// * `gamma`: The parameter "gamma" represents the best-so-far optimal solution. It is a mutable reference
206///             to a floating-point number (f64).
207/// * `options`: The `options` parameter is a struct that contains various options for the cutting-plane
208///             method. It includes parameters such as the maximum number of iterations (`max_iters`) and the error
209///             tolerance (``). These options control the termination criteria for the method.
210#[allow(dead_code)]
211pub fn cutting_plane_optim_q<T, Oracle, Space>(
212    omega: &mut Oracle,
213    space_q: &mut Space,
214    gamma: &mut f64,
215    options: &Options,
216) -> (Option<Space::ArrayType>, usize)
217where
218    T: UpdateByCutChoice<Space, ArrayType = Space::ArrayType>,
219    Oracle: OracleOptimQ<Space::ArrayType, CutChoice = T>,
220    Space: SearchSpaceQ,
221{
222    let mut x_best: Option<Space::ArrayType> = None;
223    let mut retry = false;
224
225    for niter in 0..options.max_iters {
226        let (cut, shrunk, x_q, more_alt) = omega.assess_optim_q(&space_q.xc(), gamma, retry); // query the oracle at &space.xc()
227        if shrunk {
228            // best gamma obtained
229            x_best = Some(x_q);
230            retry = false;
231        }
232        let status = space_q.update_q::<T>(&cut); // update space
233        match &status {
234            CutStatus::Success => {
235                retry = false;
236            }
237            CutStatus::NoSoln => {
238                return (x_best, niter);
239            }
240            CutStatus::NoEffect => {
241                if !more_alt {
242                    // no more alternative cut
243                    return (x_best, niter);
244                }
245                retry = true;
246            }
247            _ => {}
248        }
249        if space_q.tsq() < options.tolerance {
250            return (x_best, niter);
251        }
252    }
253    (x_best, options.max_iters)
254} // END
255
256pub struct BSearchAdaptor<T, Oracle, Space>
257where
258    T: UpdateByCutChoice<Space, ArrayType = Space::ArrayType>,
259    Oracle: OracleFeas2<Space::ArrayType, CutChoice = T>,
260    Space: SearchSpace,
261{
262    pub omega: Oracle,
263    pub space: Space,
264    pub options: Options,
265}
266
267#[allow(dead_code)]
268impl<T, Oracle, Space> BSearchAdaptor<T, Oracle, Space>
269where
270    T: UpdateByCutChoice<Space, ArrayType = Space::ArrayType>,
271    Oracle: OracleFeas2<Space::ArrayType, CutChoice = T>,
272    Space: SearchSpace + Clone,
273{
274    pub fn new(omega: Oracle, space: Space, options: Options) -> Self {
275        BSearchAdaptor {
276            omega,
277            space,
278            options,
279        }
280    }
281}
282
283impl<T, Oracle, Space> OracleBS for BSearchAdaptor<T, Oracle, Space>
284where
285    T: UpdateByCutChoice<Space, ArrayType = Space::ArrayType>,
286    Oracle: OracleFeas2<Space::ArrayType, CutChoice = T>,
287    Space: SearchSpace + Clone,
288{
289    fn assess_bs(&mut self, gamma: f64) -> bool {
290        let mut space = self.space.clone();
291        self.omega.update(gamma);
292        let (x_feas, _) = cutting_plane_feas(&mut self.omega, &mut space, &self.options);
293        if let Some(x) = x_feas {
294            self.space.set_xc(x);
295            return true;
296        }
297        false
298    }
299}
300
301/// The `bsearch` function performs a binary search to find a feasible solution within a given interval.
302///
303/// Arguments:
304///
305/// * `omega`: The parameter `omega` is an instance of the `Oracle` trait, which is used to perform
306///             assessments on a value `x0`. The specific implementation of the `Oracle` trait is not provided in
307///             the code snippet, so it could be any type that implements the necessary methods for the binary
308///             search
309/// * `intrvl`: The `intrvl` parameter is an interval containing the gamma value `x*`. It is
310///             represented as a mutable reference to a tuple `(f64, f64)`, where the first element is the lower
311///             bound of the interval and the second element is the upper bound of the interval.
312/// * `options`: The `options` parameter is a struct that contains various options for the binary search
313///             algorithm. It includes properties such as the maximum number of iterations (`max_iters`) and the
314///             error tolerance (``). These options control the termination criteria for the algorithm.
315///
316/// Returns:
317///
318/// The function `bsearch` returns a tuple of two values: a boolean indicating whether a feasible
319/// solution was obtained, and the number of iterations performed.
320#[allow(dead_code)]
321pub fn bsearch<Oracle>(omega: &mut Oracle, intrvl: &mut (f64, f64), options: &Options) -> CInfo
322where
323    Oracle: OracleBS,
324{
325    // assume monotone
326    // auto& [lower, upper] = I;
327    let &mut (mut lower, mut upper) = intrvl;
328    assert!(lower <= upper);
329    let u_orig = upper;
330
331    for niter in 0..options.max_iters {
332        let tau = (upper - lower) / 2.0;
333        if tau < options.tolerance {
334            return (upper != u_orig, niter);
335        }
336        let mut gamma = lower; // l may be `i32` or `Fraction`
337        gamma += tau;
338        if omega.assess_bs(gamma) {
339            // feasible sol'n obtained
340            upper = gamma;
341        } else {
342            lower = gamma;
343        }
344    }
345    (upper != u_orig, options.max_iters)
346}