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}