1use super::dual_vec_multiple::DualSVecMult;
2use super::{Gradient, MixedIntegerNonLinearProgram, OptimizationResult, OuterApproximation};
3#[cfg(feature = "ipopt")]
4use ipopt::{Ipopt, IpoptOption, SolveStatus};
5use ipopt_ad::{ADProblem, BasicADProblem, CachedADProblem};
6use nalgebra::{Const, DVector, SMatrix, SVector, U1};
7use num_dual::{Derivative, DualNum};
8#[cfg(feature = "ripopt")]
9use ripopt::{solve, SolverOptions};
10use std::fmt::Debug;
11
12struct Nlp<'a, P, const N_Y1: usize, const N_Y2: usize> {
13 y: SMatrix<f64, N_Y1, N_Y2>,
14 minlp: &'a P,
15}
16
17impl<'a, P, const N_Y1: usize, const N_Y2: usize> Nlp<'a, P, N_Y1, N_Y2> {
18 pub fn new(y: SMatrix<f64, N_Y1, N_Y2>, minlp: &'a P) -> Self {
19 Self { y, minlp }
20 }
21}
22
23impl<
24 P: MixedIntegerNonLinearProgram<N_X, N_Y1, N_Y2>,
25 const N_X: usize,
26 const N_Y1: usize,
27 const N_Y2: usize,
28 > BasicADProblem<N_X> for Nlp<'_, P, N_Y1, N_Y2>
29{
30 fn bounds(&self) -> ([f64; N_X], [f64; N_X]) {
31 let vars = self.minlp.x_variables().data.0[0];
32 (vars.map(|(l, _, _)| l), vars.map(|(_, u, _)| u))
33 }
34
35 fn initial_point(&self) -> [f64; N_X] {
36 self.minlp.x_variables().data.0[0].map(|(_, _, i)| i)
37 }
38
39 fn constraint_bounds(&self) -> (Vec<f64>, Vec<f64>) {
40 self.minlp
41 .constraints()
42 .into_iter()
43 .map(|c| (c.lower_bound(), c.upper_bound()))
44 .unzip()
45 }
46}
47
48impl<
49 P: MixedIntegerNonLinearProgram<N_X, N_Y1, N_Y2>,
50 const N_X: usize,
51 const N_Y1: usize,
52 const N_Y2: usize,
53 > CachedADProblem<N_X> for Nlp<'_, P, N_Y1, N_Y2>
54{
55 type Error = P::Error;
56
57 fn evaluate<D: DualNum<f64> + Copy>(&self, x: [D; N_X]) -> Result<(D, Vec<D>), P::Error> {
58 self.minlp.evaluate(SVector::from(x), self.y.map(D::from))
59 }
60}
61
62impl<
63 M: MixedIntegerNonLinearProgram<N_X, N_Y1, N_Y2>,
64 const N_X: usize,
65 const N_Y1: usize,
66 const N_Y2: usize,
67 > OuterApproximation<'_, M, N_X, N_Y1, N_Y2>
68where
69 M::Error: Debug,
70{
71 pub fn solve_nlp(
72 &mut self,
73 y: SMatrix<f64, N_Y1, N_Y2>,
74 s: Vec<f64>,
75 ) -> Option<&OptimizationResult<N_X, N_Y1, N_Y2>> {
76 #[cfg(feature = "ipopt")]
77 return self.solve_nlp_with_options(y, s, &[]);
78 #[cfg(feature = "ripopt")]
79 self.solve_nlp_with_options(y, s, &Default::default())
80 }
81
82 pub fn solve_nlp_with_options(
83 &mut self,
84 y: SMatrix<f64, N_Y1, N_Y2>,
85 s: Vec<f64>,
86 #[cfg(feature = "ipopt")] options: &[(&str, IpoptOption)],
87 #[cfg(feature = "ripopt")] options: &SolverOptions,
88 ) -> Option<&OptimizationResult<N_X, N_Y1, N_Y2>> {
89 let key = self.minlp.y_to_string(&y);
90 if self.known_solutions.contains_key(&key) {
91 return self.known_solutions.get(&key);
92 }
93
94 let optim = Nlp::new(y, self.minlp);
95 let Ok(problem) = ADProblem::new_cached(optim) else {
96 return None;
97 };
98 #[cfg(feature = "ipopt")]
99 let mut ipopt = Ipopt::new(problem).unwrap();
100 #[cfg(feature = "ipopt")]
101 let (status, x, lambda) = {
102 for &(s, o) in options {
103 ipopt.set_option(s, o);
104 }
105 let res = ipopt.solve();
106 (
107 matches!(res.status, SolveStatus::SolveSucceeded)
108 || matches!(res.status, SolveStatus::SolvedToAcceptableLevel),
109 res.solver_data.solution.primal_variables,
110 res.solver_data.solution.constraint_multipliers,
111 )
112 };
113 #[cfg(feature = "ripopt")]
114 let res = solve(&problem, options);
115 #[cfg(feature = "ripopt")]
116 let (status, x, lambda) = {
117 (
118 matches!(res.status, ripopt::SolveStatus::Optimal)
119 || matches!(res.status, ripopt::SolveStatus::Acceptable),
120 &res.x,
121 &res.constraint_multipliers,
122 )
123 };
124 if status {
125 let x = SVector::from_column_slice(x);
126 let (objective, constraints) = Self::gradients(self.minlp, y, x);
127 let result = OptimizationResult::new(
128 key.clone(),
129 y,
130 s,
131 objective,
132 constraints,
133 x,
134 DVector::from_column_slice(lambda),
135 );
136 self.known_solutions.insert(key.clone(), result);
137 self.known_solutions.get(&key)
138 } else {
139 None
141 }
142 }
143
144 fn gradients(
145 minlp: &M,
146 y: SMatrix<f64, N_Y1, N_Y2>,
147 x: SVector<f64, N_X>,
148 ) -> (Gradient<N_X, N_Y1, N_Y2>, Vec<Gradient<N_X, N_Y1, N_Y2>>) {
149 let mut y_dual = y.map(DualSVecMult::from_re);
150 let mut x_dual = x.map(DualSVecMult::from_re);
151 for (i, y) in y_dual.iter_mut().enumerate() {
152 y.eps1 = Derivative::derivative_generic(Const::<N_Y1>, Const::<N_Y2>, i);
153 }
154 for (i, x) in x_dual.iter_mut().enumerate() {
155 x.eps2 = Derivative::derivative_generic(Const::<N_X>, U1, i);
156 }
157 let (f, con) = minlp
158 .evaluate(x_dual, y_dual)
159 .expect("Unexpected error ocurred in the calculation of gradients!");
160 let f = (
161 f.re,
162 f.eps1.unwrap_generic(Const::<N_Y1>, Const::<N_Y2>),
163 f.eps2.unwrap_generic(Const::<N_X>, U1),
164 );
165 let con = con
166 .into_iter()
167 .map(|con| {
168 (
169 con.re,
170 con.eps1.unwrap_generic(Const::<N_Y1>, Const::<N_Y2>),
171 con.eps2.unwrap_generic(Const::<N_X>, U1),
172 )
173 })
174 .collect();
175 (f, con)
176 }
177}