ellalgo_rs/oracles/profit_oracle.rs
1use crate::cutting_plane::{OracleOptim, OracleOptimQ};
2use ndarray::prelude::*;
3
4type Arr = Array1<f64>;
5type Cut = (Arr, f64);
6
7/// The ProfitOracle struct represents an oracle for a profit maximization problem with specific
8/// parameters.
9///
10/// This example is taken from [Aliabadi and Salahi, 2013]:
11///
12/// max p(A x1**alpha x2**beta) - v1 * x1 - v2 * x2
13/// s.t. x1 <= k
14///
15/// where:
16///
17/// p(scale x1**alpha x2**beta): Cobb-Douglas production function
18/// p: the market price per unit
19/// scale: the scale of production
20/// alpha, beta: the output elasticities
21/// x: input quantity
22/// v: output price
23/// k: a given constant that restricts the quantity of x1
24///
25/// Reference:
26///
27/// * Aliabadi, Hossein, and Maziar Salahi. "Robust Geometric Programming Approach to Profit Maximization
28/// with Interval Uncertainty." Computer Science Journal Of Moldova 61.1 (2013): 86-96.
29///
30/// Properties:
31///
32/// * `log_p_scale`: The natural logarithm of the scale parameter of the Cobb-Douglas production
33/// function. It represents the overall scale of production.
34/// * `log_k`: The natural logarithm of the constant k that restricts the quantity of x1.
35/// * `price_out`: The `price_out` property represents the output prices `v1` and `v2` in the profit
36/// maximization problem. It is of type `Arr`, which is likely a shorthand for an array or vector data
37/// structure.
38/// * `elasticities`: An array representing the output elasticities (alpha and beta) in the profit
39/// maximization problem.
40#[derive(Debug)]
41pub struct ProfitOracle {
42 idx: i32,
43 log_p_scale: f64,
44 log_k: f64,
45 price_out: Arr,
46 pub elasticities: Arr,
47 log_cobb: f64,
48 vx: f64,
49 q: Arr,
50}
51
52impl ProfitOracle {
53 /// The function `new` constructs a new ProfitOracle object with given parameters.
54 ///
55 /// Arguments:
56 ///
57 /// * `p`: The parameter `p` represents the market price per unit. It is a floating-point number (f64)
58 /// that indicates the price at which the product is sold in the market.
59 /// * `scale`: The scale parameter represents the scale of production. It determines the quantity of
60 /// output produced.
61 /// * `k`: The parameter `k` is a given constant that restricts the quantity of `x1`. It is used in the
62 /// calculation of the profit oracle object.
63 /// * `elasticities`: The parameter "elasticities" represents the output elasticities, which are
64 /// coefficients that measure the responsiveness of the quantity of output to changes in the inputs. It
65 /// is expected to be an array or list of values.
66 /// * `price_out`: The `price_out` parameter represents the output price. It is of type `Arr`, which
67 /// suggests that it is an array or collection of values. The specific type of `Arr` is not specified in
68 /// the code snippet, so it could be an array, a vector, or any other collection type
69 ///
70 /// Returns:
71 ///
72 /// The `new` function is returning an instance of the `ProfitOracle` struct.
73 pub fn new(params: (f64, f64, f64), elasticities: Arr, price_out: Arr) -> Self {
74 let (unit_price, scale, limit) = params;
75 let log_p_scale = (unit_price * scale).ln();
76 let log_k = limit.ln();
77 ProfitOracle {
78 idx: -1,
79 log_p_scale,
80 log_k,
81 price_out,
82 elasticities,
83 log_cobb: 0.0,
84 vx: 0.0,
85 q: Arr::zeros(2),
86 }
87 }
88
89 /// The function assess_feas calculates the gradient and objective function value for an optimization
90 /// problem in Rust.
91 ///
92 /// Arguments:
93 ///
94 /// * `y`: A reference to an array of f64 values.
95 /// * `gamma`: The parameter `gamma` is a mutable reference to a `f64` variable.
96 fn assess_feas(&mut self, y: &Arr, gamma: &mut f64) -> Option<(Arr, f64)> {
97 let num_constraints = 2;
98 for _ in 0..num_constraints {
99 self.idx += 1;
100 if self.idx == num_constraints {
101 self.idx = 0; // round robin
102 }
103 let fj = match self.idx {
104 0 => y[0] - self.log_k, // y0 <= log k
105 1 => {
106 self.log_cobb = self.log_p_scale + self.elasticities.dot(y);
107 self.q = &self.price_out * y.mapv(f64::exp);
108 self.vx = self.q[0] + self.q[1];
109 (*gamma + self.vx).ln() - self.log_cobb
110 }
111 _ => unreachable!(),
112 };
113 if fj > 0.0 {
114 return Some((
115 match self.idx {
116 0 => array![1.0, 0.0],
117 1 => &self.q / (*gamma + self.vx) - &self.elasticities,
118 _ => unreachable!(),
119 },
120 fj,
121 ));
122 }
123 }
124
125 None
126 }
127}
128
129impl OracleOptim<Arr> for ProfitOracle {
130 type CutChoice = f64; // single cut
131
132 /// The function assess_optim calculates the gradient and objective function value for an optimization
133 /// problem in Rust.
134 ///
135 /// Arguments:
136 ///
137 /// * `y`: A reference to an array of f64 values.
138 /// * `gamma`: The parameter `gamma` is a mutable reference to a `f64` variable.
139 fn assess_optim(&mut self, y: &Arr, gamma: &mut f64) -> ((Arr, f64), bool) {
140 if let Some(cut) = self.assess_feas(y, gamma) {
141 return (cut, false);
142 }
143
144 let te = self.log_cobb.exp();
145 *gamma = te - self.vx;
146 let grad = (&self.q / te) - &self.elasticities;
147 ((grad, 0.0), true)
148 }
149}
150
151/// The `ProfitRbOracle` struct is an implementation of an oracle for a profit maximization problem with
152/// robustness. It is used to solve a profit maximization problem where the parameters have interval
153/// uncertainty. The implementation is based on the approach described in the paper "Robust Geometric
154/// Programming Approach to Profit Maximization with Interval Uncertainty" by Aliabadi and Salahi.
155/// The `ProfitRbOracle` struct is an implementation of an oracle for a profit maximization problem with
156/// robustness.
157///
158/// Reference:
159///
160/// * Aliabadi, Hossein, and Maziar Salahi. "Robust Geometric Programming Approach to Profit Maximization
161/// with Interval Uncertainty." Computer Science Journal Of Moldova 61.1 (2013): 86-96.
162#[derive(Debug)]
163pub struct ProfitRbOracle {
164 uie: [f64; 2],
165 omega: ProfitOracle,
166 elasticities: Arr,
167}
168
169impl ProfitRbOracle {
170 /// The function `new` constructs a new ProfitRbOracle object with given parameters.
171 ///
172 /// Arguments:
173 ///
174 /// * `p`: The market price per unit.
175 /// * `scale`: The `scale` parameter represents the scale of production. It determines the level of
176 /// output or production for a given set of inputs. It can be thought of as the size or capacity of the
177 /// production process.
178 /// * `k`: A given constant that restricts the quantity of x1.
179 /// * `aa`: The parameter `aa` represents the output elasticities. It is an array that contains the
180 /// elasticities of each output variable.
181 /// * `price_out`: The parameter `price_out` represents the output price. It is of type `Arr`, which
182 /// suggests that it is an array or vector of values.
183 /// * `e`: Parameters for uncertainty.
184 /// * `e3`: The parameter `e3` represents the uncertainty in the market price per unit. It is used to
185 /// adjust the market price in the calculation of the `omega` variable.
186 ///
187 /// Returns:
188 ///
189 /// The `new` function is returning an instance of the `ProfitRbOracle` struct.
190 pub fn new(
191 params: (f64, f64, f64),
192 aa: Arr,
193 price_out: Arr,
194 vparams: (f64, f64, f64, f64, f64),
195 ) -> Self {
196 let (e1, e2, e3, e4, e5) = vparams;
197 let uie = [e1, e2];
198 let params_rb = (params.0 - e3, params.1, params.2 - e4);
199 let omega = ProfitOracle::new(
200 params_rb,
201 aa.clone(),
202 price_out + Arr::from_vec(vec![e5, e5]),
203 );
204 ProfitRbOracle {
205 uie,
206 omega,
207 elasticities: aa,
208 }
209 }
210}
211
212impl OracleOptim<Arr> for ProfitRbOracle {
213 type CutChoice = f64; // single cut
214
215 /// The `assess_optim` function takes an input quantity `y` and updates the best-so-far optimal value
216 /// `gamma` based on the elasticities and returns a cut and the updated best-so-far value.
217 ///
218 /// Arguments:
219 ///
220 /// * `y`: The parameter `y` is an input quantity represented as an array (`Arr`) in log scale.
221 /// * `gamma`: The parameter `gamma` is the best-so-far optimal value. It is passed as a mutable reference
222 /// (`&mut f64`) so that its value can be updated within the function.
223 fn assess_optim(&mut self, y: &Arr, gamma: &mut f64) -> (Cut, bool) {
224 let mut a_rb = self.elasticities.clone();
225 for i in 0..2 {
226 a_rb[i] += if y[i] > 0.0 {
227 -self.uie[i]
228 } else {
229 self.uie[i]
230 };
231 }
232 self.omega.elasticities = a_rb;
233 self.omega.assess_optim(y, gamma)
234 }
235}
236
237/// The ProfitOracleQ struct is an oracle for the profit maximization problem in a discrete version.
238///
239/// Properties:
240///
241/// * `omega`: The `omega` property is an instance of the `ProfitOracle` struct. It is used to calculate
242/// the profit for a given input.
243/// * `yd`: The variable `yd` is an array that represents the discrete version of y values
244pub struct ProfitOracleQ {
245 omega: ProfitOracle,
246 yd: Arr,
247}
248
249impl ProfitOracleQ {
250 /// The function `new` constructs a new `ProfitOracleQ` object with given parameters.
251 ///
252 /// Arguments:
253 ///
254 /// * `p`: The parameter `p` represents the market price per unit. It is a floating-point number (f64)
255 /// that indicates the price at which a unit of the product is sold in the market.
256 /// * `scale`: The "scale" parameter represents the scale of production, which refers to the level of
257 /// output or production of a particular good or service. It indicates the quantity of goods or services
258 /// produced within a given time period.
259 /// * `k`: A given constant that restricts the quantity of x1. It is used to limit the quantity of a
260 /// particular input (x1) in the production process.
261 /// * `elasticities`: The parameter `elasticities` represents the output elasticities. Output elasticity measures the
262 /// responsiveness of the quantity of output to a change in the input quantity. It indicates how much
263 /// the quantity of output changes in response to a change in the quantity of input.
264 /// * `price_out`: The parameter `price_out` represents the output price. It is an array that contains
265 /// the prices of the outputs.
266 ///
267 /// Returns:
268 ///
269 /// The `new` function is returning an instance of the `ProfitOracleQ` struct.
270 pub fn new(params: (f64, f64, f64), elasticities: Arr, price_out: Arr) -> Self {
271 ProfitOracleQ {
272 yd: array![0.0, 0.0],
273 omega: ProfitOracle::new(params, elasticities, price_out),
274 }
275 }
276}
277
278impl OracleOptimQ<Arr> for ProfitOracleQ {
279 type CutChoice = f64; // single cut
280
281 /// The `assess_optim_q` function takes in an input quantity `y` in log scale, updates the best-so-far
282 /// optimal value `gamma`, and returns a cut and the updated best-so-far value.
283 ///
284 /// Arguments:
285 ///
286 /// * `y`: The parameter `y` is an input quantity in log scale. It is of type `Arr`, which is likely an
287 /// array or vector type.
288 /// * `gamma`: The parameter `gamma` represents the best-so-far optimal value. It is a mutable reference to
289 /// a `f64` value, which means it can be modified within the function.
290 /// * `retry`: A boolean value indicating whether it is a retry or not.
291 fn assess_optim_q(&mut self, y: &Arr, gamma: &mut f64, retry: bool) -> (Cut, bool, Arr, bool) {
292 if !retry {
293 if let Some(cut) = self.omega.assess_feas(y, gamma) {
294 return (cut, false, y.clone(), true);
295 }
296
297 // let mut xd = y.mapv(f64::exp).mapv(f64::round);
298 let mut xd = y.map(|x| x.exp().round());
299 if xd[0] == 0.0 {
300 xd[0] = 1.0; // nearest integer than 0
301 }
302 if xd[1] == 0.0 {
303 xd[1] = 1.0;
304 }
305 self.yd = xd.mapv(f64::ln);
306 }
307 let ((grad, beta), shrunk) = self.omega.assess_optim(&self.yd, gamma);
308 let beta = beta + grad.dot(&(&self.yd - y));
309 ((grad, beta), shrunk, self.yd.clone(), !retry)
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::{ProfitOracle, ProfitOracleQ, ProfitRbOracle};
316 // use super::{ProfitOracle, ProfitOracleQ, ProfitRbOracle};
317 use crate::cutting_plane::{cutting_plane_optim, cutting_plane_optim_q, Options};
318 use crate::ell::Ell;
319 use ndarray::array;
320
321 #[test]
322 pub fn test_profit_oracle() {
323 let unit_price = 20.0;
324 let scale = 40.0;
325 let limit = 30.5;
326 let params = (unit_price, scale, limit);
327 let elasticities = array![0.1, 0.4];
328 let price_out = array![10.0, 35.0];
329
330 let mut ellip = Ell::new(array![100.0, 100.0], array![0.0, 0.0]);
331 let mut omega = ProfitOracle::new(params, elasticities, price_out);
332 let mut gamma = 0.0;
333 let options = Options::default();
334 let (y_opt, niter) = cutting_plane_optim(&mut omega, &mut ellip, &mut gamma, &options);
335 assert!(y_opt.is_some());
336 if let Some(y) = y_opt {
337 assert!(y[0] <= limit.ln());
338 }
339 assert_eq!(niter, 83, "regression test");
340 }
341
342 #[test]
343 pub fn test_profit_oracle_rb() {
344 let unit_price = 20.0;
345 let scale = 40.0;
346 let limit = 30.5;
347 let params = (unit_price, scale, limit);
348 let elasticities = array![0.1, 0.4];
349 let price_out = array![10.0, 35.0];
350 let e1 = 0.003;
351 let e2 = 0.007;
352 let e3 = 1.0;
353 let e4 = 1.0;
354 let e5 = 1.0;
355 let vparams = (e1, e2, e3, e4, e5);
356
357 let mut ellip = Ell::new(array![100.0, 100.0], array![0.0, 0.0]);
358 let mut omega = ProfitRbOracle::new(params, elasticities, price_out, vparams);
359 let mut gamma = 0.0;
360 let options = Options::default();
361 let (y_opt, niter) = cutting_plane_optim(&mut omega, &mut ellip, &mut gamma, &options);
362 assert!(y_opt.is_some());
363 if let Some(y) = y_opt {
364 assert!(y[0] <= limit.ln());
365 }
366 assert_eq!(niter, 90, "regression test");
367 }
368
369 #[test]
370 pub fn test_profit_oracle_q() {
371 let unit_price = 20.0;
372 let scale = 40.0;
373 let limit = 30.5;
374 let params = (unit_price, scale, limit);
375 let elasticities = array![0.1, 0.4];
376 let price_out = array![10.0, 35.0];
377
378 let mut ellip = Ell::new(array![100.0, 100.0], array![0.0, 0.0]);
379 let mut omega = ProfitOracleQ::new(params, elasticities, price_out);
380 let mut gamma = 0.0;
381 let options = Options::default();
382 let (y_opt, niter) = cutting_plane_optim_q(&mut omega, &mut ellip, &mut gamma, &options);
383 assert!(y_opt.is_some());
384 if let Some(y) = y_opt {
385 assert!(y[0] <= limit.ln());
386 }
387 assert_eq!(niter, 29, "regression test");
388 }
389}