ipopt/lib.rs
1// Copyright 2018 Egor Larionov
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![warn(missing_docs)]
16
17/*!
18 * # ipopt-rs
19 *
20 * This crate provides a safe Rust interface to the [Ipopt](https://projects.coin-or.org/Ipopt)
21 * non-linear optimization library. From the Ipopt webpage:
22 *
23 * > Ipopt (**I**nterior **P**oint **OPT**imizer, pronounced eye-pea-Opt) is a software package
24 * > for large-scale nonlinear optimization. It is designed to find (local) solutions of
25 * > mathematical optimization problems of the from
26 * >
27 * >```verbatim
28 * > min f(x)
29 * > x in R^n
30 * >
31 * > s.t. g_L <= g(x) <= g_U
32 * > x_L <= x <= x_U
33 * >```
34 * >
35 * > where `f(x): R^n --> R` is the objective function, and `g(x): R^n --> R^m` are the
36 * > constraint functions. The vectors `g_L` and `g_U` denote the lower and upper bounds
37 * > on the constraints, and the vectors `x_L` and `x_U` are the bounds on the variables
38 * > `x`. The functions `f(x)` and `g(x)` can be nonlinear and nonconvex, but should be
39 * > twice continuously differentiable. Note that equality constraints can be
40 * > formulated in the above formulation by setting the corresponding components of
41 * > `g_L` and `g_U` to the same value.
42 *
43 * This crate aims to
44 * - reduce the boilerplate, especially for setting up simple unconstrained problems, and
45 * - prevent common mistakes when defining optimization problems as early as possible.
46 *
47 *
48 * # Examples
49 *
50 * Solve a simple unconstrained problem using L-BFGS: minimize `(x - 1)^2 + (y -1)^2`
51 *
52 *
53 * ```
54 * use approx::*;
55 * use ipopt::*;
56 *
57 * struct NLP {
58 * }
59 *
60 * impl BasicProblem for NLP {
61 * // There are two independent variables: x and y.
62 * fn num_variables(&self) -> usize {
63 * 2
64 * }
65 * // The variables are unbounded. Any lower bound lower than -10^19 and upper bound higher
66 * // than 10^19 is treated effectively as infinity. These absolute infinity limits can be
67 * // changed via the `nlp_lower_bound_inf` and `nlp_upper_bound_inf` Ipopt options.
68 * fn bounds(&self, x_l: &mut [Number], x_u: &mut [Number]) -> bool {
69 * x_l.swap_with_slice(vec![-1e20; 2].as_mut_slice());
70 * x_u.swap_with_slice(vec![1e20; 2].as_mut_slice());
71 * true
72 * }
73 *
74 * // Set the initial conditions for the solver.
75 * fn initial_point(&self, x: &mut [Number]) -> bool {
76 * x.swap_with_slice(vec![0.0, 0.0].as_mut_slice());
77 * true
78 * }
79 *
80 * // The objective to be minimized.
81 * fn objective(&self, x: &[Number], new_x: bool, obj: &mut Number) -> bool {
82 * *obj = (x[0] - 1.0)*(x[0] - 1.0) + (x[1] - 1.0)*(x[1] - 1.0);
83 * true
84 * }
85 *
86 * // Objective gradient is used to find a new search direction to find the critical point.
87 * fn objective_grad(&self, x: &[Number], new_x: bool, grad_f: &mut [Number]) -> bool {
88 * grad_f[0] = 2.0*(x[0] - 1.0);
89 * grad_f[1] = 2.0*(x[1] - 1.0);
90 * true
91 * }
92 * }
93 *
94 * fn main() {
95 * let nlp = NLP { };
96 * let mut ipopt = Ipopt::new_unconstrained(nlp).unwrap();
97 *
98 * // Set Ipopt specific options here a list of all options is available at
99 * // https://www.coin-or.org/Ipopt/documentation/node40.html
100 * ipopt.set_option("tol", 1e-9); // set error tolerance
101 * ipopt.set_option("print_level", 5); // set the print level (5 is the default)
102 *
103 * let solve_result = ipopt.solve();
104 *
105 * assert_eq!(solve_result.status, SolveStatus::SolveSucceeded);
106 * assert_relative_eq!(solve_result.objective_value, 0.0, epsilon = 1e-10);
107 * let solution = solve_result.solver_data.solution;
108 * assert_relative_eq!(solution.primal_variables[0], 1.0, epsilon = 1e-10);
109 * assert_relative_eq!(solution.primal_variables[1], 1.0, epsilon = 1e-10);
110 * }
111 * ```
112 *
113 * See the tests for more examples including constrained optimization.
114 *
115 */
116
117use ipopt_sys as ffi;
118
119use crate::ffi::{CNLP_Bool as Bool, CNLP_Int as Int};
120pub use crate::ffi::{
121 // Index type used to access internal buffers.
122 CNLP_Index as Index, // i32
123 // Uniform floating point number type.
124 CNLP_Number as Number, // f64
125};
126
127use std::ffi::CString;
128use std::fmt::{Debug, Display, Formatter};
129use std::slice;
130
131/// The callback interface for a non-linear problem to be solved by Ipopt.
132///
133/// This trait specifies all the information needed to construct the unconstrained optimization
134/// problem (although the variables are allowed to be bounded).
135/// In the callbacks within, `x` is the independent variable and must be the same size
136/// as returned by `num_variables`.
137///
138/// Each of the callbacks required during interior point iterations are allowed to fail. In case
139/// of failure to produce values, simply return `false` where applicable. This feature could be
140/// used to tell Ipopt to try smaller perturbations for `x` for instance. If the caller returns
141/// `true` but the output data was not set, then Ipopt may produce undefined behaviour.
142pub trait BasicProblem {
143 /// Specify the indexing style used for arrays in this problem.
144 /// (Default is zero-based)
145 fn indexing_style(&self) -> IndexingStyle {
146 IndexingStyle::CStyle
147 }
148
149 /// Total number of variables of the non-linear problem.
150 fn num_variables(&self) -> usize;
151
152 /// Specify lower and upper variable bounds given by `x_l` and `x_u` respectively.
153 ///
154 /// Both slices will have the same size as what `num_variables` returns.
155 fn bounds(&self, x_l: &mut [Number], x_u: &mut [Number]) -> bool;
156
157 /// Construct the initial guess of the primal variables for Ipopt to start with.
158 ///
159 /// The given slice has the same size as `num_variables`.
160 ///
161 /// This function should return whether the output slice `x` has been populated with initial
162 /// values. If this function returns `false`, then a zero initial guess will be used.
163 fn initial_point(&self, x: &mut [Number]) -> bool;
164
165 /// Construct the initial guess of the lower and upper bounds multipliers for Ipopt to start with.
166 ///
167 /// The given slices has the same size as `num_variables`.
168 /// Note that multipliers for infinity bounds are ignored.
169 ///
170 /// This function should return whether the output slices `z_l` and `z_u` have been populated
171 /// with initial values. If this function returns `false`, then a zero initial guess will be used.
172 ///
173 /// For convenience, the default implementation initializes bounds multipliers to zero. This is
174 /// a good guess for any initial point in the interior of the feasible region.
175 fn initial_bounds_multipliers(&self, z_l: &mut [Number], z_u: &mut [Number]) -> bool {
176 for (l, u) in z_l.iter_mut().zip(z_u.iter_mut()) {
177 *l = 0.0;
178 *u = 0.0;
179 }
180 true
181 }
182
183 /// Objective function. This is the function being minimized.
184 ///
185 /// This function is internally called by Ipopt callback `eval_f`.
186 fn objective(&self, x: &[Number], new_x: bool, obj: &mut Number) -> bool;
187
188 /// The gradient of the objective function speficies the search direction to Ipopt.
189 ///
190 /// This function is internally called by Ipopt callback `eval_grad_f`.
191 fn objective_grad(&self, x: &[Number], new_x: bool, grad_f: &mut [Number]) -> bool;
192
193 /// Provide custom variable scaling.
194 ///
195 /// This method is called if the Ipopt option, `nlp_scaling_method`, is set to `user-scaling`.
196 /// Return `true` if scaling is provided and `false` otherwise: if `false` is returned, Ipopt
197 /// will not scale the variables.
198 ///
199 /// The dimension of `x_scaling` is given by `num_variables`.
200 ///
201 /// For convenience, this function returns `false` by default without modifying the `x_scaling`
202 /// slice.
203 fn variable_scaling(&self, _x_scaling: &mut [Number]) -> bool {
204 false
205 }
206
207 /// Provide custom scaling for the objective function.
208 ///
209 /// This method is called if the Ipopt option, `nlp_scaling_method`, is set to `user-scaling`.
210 /// For example if this function returns `10`, then then Ipopt solves internally an optimization
211 /// problem that has 10 times the value of the original objective function. If this function
212 /// returns `-1.0`, then Ipopt will maximize the objective instead of minimizing it.
213 ///
214 /// For convenience, this function returns `1.0` by default.
215 fn objective_scaling(&self) -> f64 {
216 1.0
217 }
218}
219
220/// An extension to the [`BasicProblem`](trait.BasicProblem.html) trait that enables full Newton
221/// iterations in Ipopt.
222///
223/// If this trait is NOT implemented by your problem, Ipopt will be set to perform
224/// [Quasi-Newton Approximation](https://www.coin-or.org/Ipopt/documentation/node31.html)
225/// for second derivatives.
226/// This interface asks for the Hessian matrix in sparse triplet form.
227pub trait NewtonProblem: BasicProblem {
228 /// Number of non-zeros in the Hessian matrix.
229 fn num_hessian_non_zeros(&self) -> usize;
230 /// Hessian indices.
231 ///
232 /// These are the row and column indices of the non-zeros
233 /// in the sparse representation of the matrix.
234 /// This is a symmetric matrix, fill the lower left triangular half only.
235 /// If your problem is constrained (i.e. you are ultimately implementing
236 /// `ConstrainedProblem`), ensure that you provide coordinates for non-zeros of the
237 /// constraint hessian as well.
238 /// This function is internally called by Ipopt callback `eval_h`.
239 fn hessian_indices(&self, rows: &mut [Index], cols: &mut [Index]) -> bool;
240 /// Objective Hessian values.
241 ///
242 /// Each value must correspond to the `row` and `column` as
243 /// specified in `hessian_indices`.
244 /// This function is internally called by Ipopt callback `eval_h` and each value is
245 /// premultiplied by `Ipopt`'s `obj_factor` as necessary.
246 fn hessian_values(&self, x: &[Number], vals: &mut [Number]) -> bool;
247}
248
249/// Extends the [`BasicProblem`](trait.BasicProblem.html) trait to enable equality and inequality constraints.
250///
251/// Equality constraints are enforced by setting the lower and upper bounds for the
252/// constraint to the same value.
253/// This type of problem is the target use case for Ipopt.
254/// NOTE: Although it's possible to run Quasi-Newton iterations on a constrained problem,
255/// it doesn't perform well in general, which is the reason why you must also provide the
256/// Hessian callbacks. However, you may still enable L-BFGS explicitly by setting the
257/// "hessian_approximation" Ipopt option to "limited-memory", in which case you should
258/// simply return `false` in `hessian_indices` and `hessian_values`.
259pub trait ConstrainedProblem: BasicProblem {
260 /// Number of equality and inequality constraints.
261 fn num_constraints(&self) -> usize;
262 /// Number of non-zeros in the constraint Jacobian.
263 fn num_constraint_jacobian_non_zeros(&self) -> usize;
264 /// Constraint function.
265 ///
266 /// This gives the value of each constraint.
267 /// The output slice `g` is guaranteed to be the same size as `num_constraints`.
268 /// This function is internally called by Ipopt callback `eval_g`.
269 fn constraint(&self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool;
270 /// Specify lower and upper bounds, `g_l` and `g_u` respectively, on the constraint function.
271 ///
272 /// Both slices will have the same size as what `num_constraints` returns.
273 fn constraint_bounds(&self, g_l: &mut [Number], g_u: &mut [Number]) -> bool;
274 /// Construct the initial guess of the constraint multipliers for Ipopt to start with.
275 ///
276 /// The given slice has the same size as `num_constraints`.
277 ///
278 /// This function should return whether the output slice `lambda` has been populated with
279 /// initial values. If this function returns `false`, then a zero initial guess will be used.
280 ///
281 /// For convenience, the default implementation initializes constraint multipliers to zero.
282 /// This is a good guess for any initial point in the interior of the feasible region.
283 fn initial_constraint_multipliers(&self, lambda: &mut [Number]) -> bool {
284 for l in lambda.iter_mut() {
285 *l = 0.0;
286 }
287 true
288 }
289 /// Constraint Jacobian indices.
290 ///
291 /// These are the row and column indices of the
292 /// non-zeros in the sparse representation of the matrix.
293 /// This function is internally called by Ipopt callback `eval_jac_g`.
294 fn constraint_jacobian_indices(&self, rows: &mut [Index], cols: &mut [Index]) -> bool;
295 /// Constraint Jacobian values.
296 ///
297 /// Each value must correspond to the `row` and
298 /// `column` as specified in `constraint_jacobian_indices`.
299 /// This function is internally called by Ipopt callback `eval_jac_g`.
300 fn constraint_jacobian_values(&self, x: &[Number], new_x: bool, vals: &mut [Number]) -> bool;
301 /// Number of non-zeros in the Hessian matrix.
302 ///
303 /// This includes the constraint Hessian.
304 fn num_hessian_non_zeros(&self) -> usize;
305 /// Hessian indices.
306 ///
307 /// These are the row and column indices of the non-zeros
308 /// in the sparse representation of the matrix.
309 /// This should be a symmetric matrix, fill the lower left triangular half only.
310 /// Ensure that you provide coordinates for non-zeros of the
311 /// objective and constraint Hessians.
312 /// This function is internally called by Ipopt callback `eval_h`.
313 fn hessian_indices(&self, rows: &mut [Index], cols: &mut [Index]) -> bool;
314 /// Hessian values.
315 ///
316 /// Each value must correspond to the `row` and `column` as
317 /// specified in `hessian_indices`.
318 /// Write the objective Hessian values multiplied by `obj_factor` and constraint
319 /// Hessian values multipled by the corresponding values in `lambda` (the Lagrange
320 /// multiplier).
321 /// This function is internally called by Ipopt callback `eval_h`.
322 fn hessian_values(
323 &self,
324 x: &[Number],
325 new_x: bool,
326 obj_factor: Number,
327 lambda: &[Number],
328 vals: &mut [Number],
329 ) -> bool;
330
331 /// Provide custom constraint function scaling.
332 ///
333 /// This method is called if the Ipopt option, `nlp_scaling_method`, is set to `user-scaling`.
334 /// Return `true` if scaling is provided and `false` otherwise: if `false` is returned, Ipopt
335 /// will not scale the constraint function.
336 ///
337 /// `g_scaling` has the same dimensions as the constraint function: the value returned by
338 /// `num_constraints`.
339 ///
340 /// For convenience, this function returns `false` by default without modifying the `g_scaling`
341 /// slice.
342 fn constraint_scaling(&self, _g_scaling: &mut [Number]) -> bool {
343 false
344 }
345}
346
347/// Type of option you can specify to Ipopt.
348/// This is used internally for conversion.
349#[derive(Clone, Copy)]
350pub enum IpoptOption<'a> {
351 /// Numeric option.
352 Num(f64),
353 /// String option.
354 Str(&'a str),
355 /// Integer option.
356 Int(i32),
357}
358
359/// Convert a floating point value to an `IpoptOption`.
360impl<'a> From<f64> for IpoptOption<'a> {
361 fn from(opt: f64) -> Self {
362 IpoptOption::Num(opt)
363 }
364}
365
366/// Convert a string to an `IpoptOption`.
367impl<'a> From<&'a str> for IpoptOption<'a> {
368 fn from(opt: &'a str) -> Self {
369 IpoptOption::Str(opt)
370 }
371}
372
373/// Convert an integer value to an `IpoptOption`.
374impl<'a> From<i32> for IpoptOption<'a> {
375 fn from(opt: i32) -> Self {
376 IpoptOption::Int(opt)
377 }
378}
379
380fn reconstruct_dual_slice<'a>(g: *const Number, num_dual_vars: usize) -> &'a [Number] {
381 if num_dual_vars == 0 {
382 &[]
383 } else {
384 unsafe { slice::from_raw_parts(g, num_dual_vars) }
385 }
386}
387
388/// The solution of the optimization problem including variables, bound multipliers and Lagrange
389/// multipliers. This struct stores immutable slices to the solution data.
390#[derive(Copy, Clone, Debug, PartialEq)]
391pub struct Solution<'a> {
392 /// This is the solution after the solve.
393 pub primal_variables: &'a [Number],
394 /// Lower bound multipliers.
395 pub lower_bound_multipliers: &'a [Number],
396 /// Upper bound multipliers.
397 pub upper_bound_multipliers: &'a [Number],
398 /// Constraint multipliers, which are available only from contrained problems.
399 pub constraint_multipliers: &'a [Number],
400}
401
402impl<'a> Solution<'a> {
403 /// Construct the solution from raw arrays returned from the Ipopt C interface.
404 fn from_raw(
405 data: ffi::CNLP_SolverData,
406 num_primal_vars: usize,
407 num_dual_vars: usize,
408 ) -> Solution<'a> {
409 dbg!(num_dual_vars);
410 dbg!(data.mult_g);
411 Solution {
412 primal_variables: unsafe { slice::from_raw_parts(data.x, num_primal_vars) },
413 lower_bound_multipliers: unsafe {
414 slice::from_raw_parts(data.mult_x_L, num_primal_vars)
415 },
416 upper_bound_multipliers: unsafe {
417 slice::from_raw_parts(data.mult_x_U, num_primal_vars)
418 },
419 constraint_multipliers: reconstruct_dual_slice(data.mult_g, num_dual_vars),
420 }
421 }
422}
423
424/// An interface to mutably access the input problem
425/// which Ipopt owns. This method also returns the solver paramters as immutable.
426#[derive(Debug, PartialEq)]
427pub struct SolverDataMut<'a, P: 'a> {
428 /// A mutable reference to the original input problem.
429 pub problem: &'a mut P,
430 /// Argument solution to the optimization problem.
431 pub solution: Solution<'a>,
432}
433
434/// An interface to access internal solver data including the input problem immutably.
435#[derive(Clone, Debug, PartialEq)]
436pub struct SolverData<'a, P: 'a> {
437 /// A mutable reference to the original input problem.
438 pub problem: &'a P,
439 /// Argument solution to the optimization problem.
440 pub solution: Solution<'a>,
441}
442
443/// Enum that indicates in which mode the algorithm is at some point in time.
444#[derive(Copy, Clone, Debug, PartialEq)]
445pub enum AlgorithmMode {
446 /// Ipopt is in regular mode.
447 Regular,
448 /// Ipopt is in restoration phase. See Ipopt documentation for details.
449 RestorationPhase,
450}
451
452/// Pieces of solver data available from Ipopt after each iteration inside the intermediate
453/// callback.
454#[derive(Copy, Clone, Debug, PartialEq)]
455pub struct IntermediateCallbackData {
456 /// Algorithm mode indicates which mode the algorithm is currently in.
457 pub alg_mod: AlgorithmMode,
458 /// The current iteration count.
459 ///
460 /// This includes regular iterations and iterations during the restoration phase.
461 pub iter_count: Index,
462 /// The unscaled objective value at the current point.
463 ///
464 /// During the restoration phase, this value remains the unscaled objective value for the
465 /// original problem.
466 pub obj_value: Number,
467 /// The unscaled constraint violation at the current point.
468 ///
469 /// This quantity is the infinity-norm (max) of the (unscaled) constraints. During the
470 /// restoration phase, this value remains the constraint violation of the original problem at
471 /// the current point. The option inf_pr_output can be used to switch to the printing of a
472 /// different quantity.
473 pub inf_pr: Number,
474 /// The scaled dual infeasibility at the current point.
475 ///
476 /// This quantity measures the infinity-norm (max) of the internal dual infeasibility, Eq. (4a)
477 /// in the [implementation
478 /// paper](https://www.coin-or.org/Ipopt/documentation/node64.html#WaecBieg06:mp), including
479 /// inequality constraints reformulated using slack variables and problem scaling. During the
480 /// restoration phase, this is the value of the dual infeasibility for the restoration phase
481 /// problem.
482 pub inf_du: Number,
483 /// The value of the barrier parameter $ \mu$.
484 pub mu: Number,
485 /// The infinity norm (max) of the primal step (for the original variables $ x$ and the
486 /// internal slack variables $ s$).
487 ///
488 /// During the restoration phase, this value includes the
489 /// values of additional variables, $ p$ and $ n$ (see Eq. (30) in [the implementation
490 /// paper](https://www.coin-or.org/Ipopt/documentation/node64.html#WaecBieg06:mp))
491 pub d_norm: Number,
492 /// The value of the regularization term for the Hessian of the Lagrangian in
493 /// the augmented system
494 ///
495 /// This is $ \delta_w$ in Eq. (26) and Section 3.1 in [the implementation
496 /// paper](https://www.coin-or.org/Ipopt/documentation/node64.html#WaecBieg06:mp).
497 /// A zero value indicates that no regularization was done.
498 pub regularization_size: Number,
499 /// The stepsize for the dual variables.
500 ///
501 /// This is $ \alpha^z_k$ in Eq. (14c) in [the implementation
502 /// paper](https://www.coin-or.org/Ipopt/documentation/node64.html#WaecBieg06:mp).
503 pub alpha_du: Number,
504 /// The stepsize for the primal variables.
505 ///
506 /// This is $ \alpha_k$ in Eq. (14a) in [the implementation
507 /// paper](https://www.coin-or.org/Ipopt/documentation/node64.html#WaecBieg06:mp).
508 pub alpha_pr: Number,
509 /// The number of backtracking line search steps (does not include second-order correction steps).
510 pub ls_trials: Index,
511}
512
513/// A data structure to store data returned by the solver.
514#[derive(Debug, PartialEq)]
515pub struct SolveResult<'a, P: 'a> {
516 /// Data available from the solver, that can be updated by the user.
517 pub solver_data: SolverDataMut<'a, P>,
518 /// These are the values of each constraint at the end of the time step.
519 pub constraint_values: &'a [Number],
520 /// Objective value.
521 pub objective_value: Number,
522 /// Solve status. This enum reports the status of the last solve.
523 pub status: SolveStatus,
524}
525
526/// Type defining the callback function for giving intermediate execution control to
527/// the user.
528///
529/// If set, it is called once per iteration, providing the user with some
530/// information on the state of the optimization. This can be used to print some user-
531/// defined output. It also gives the user a way to terminate the optimization
532/// prematurely. If this method returns false, Ipopt will terminate the optimization.
533pub type IntermediateCallback<P> = fn(&mut P, IntermediateCallbackData) -> bool;
534
535/// Ipopt non-linear optimization problem solver.
536///
537/// This structure is used to store data needed to solve these problems using first and second
538/// order methods.
539pub struct Ipopt<P: BasicProblem> {
540 /// Internal (opaque) Ipopt problem representation.
541 nlp_internal: ffi::CNLP_ProblemPtr,
542 /// User specified interface defining the problem to be solved.
543 nlp_interface: P,
544 /// Intermediate callback.
545 intermediate_callback: Option<IntermediateCallback<P>>,
546 /// Number of primal variables.
547 num_primal_variables: usize,
548 /// Number of dual variables.
549 num_dual_variables: usize,
550}
551
552/// Implement debug for Ipopt.
553impl<P: BasicProblem + Debug> Debug for Ipopt<P> {
554 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
555 write!(f,
556 "Ipopt {{ nlp_internal: {:?}, nlp_interface: {:?}, intermediate_callback: {:?}, num_primal_variables: {:?}, num_dual_variables: {:?} }}",
557 self.nlp_internal,
558 self.nlp_interface,
559 if self.intermediate_callback.is_some() { "Some" } else { "None" },
560 self.num_primal_variables,
561 self.num_dual_variables)
562 }
563}
564
565/// The only non-`Send` type in `Ipopt` is `nlp_internal`, which is a mutable raw pointer to an
566/// underlying C struct. It is safe to implement `Send` for `Ipopt` here because it cannot be
567/// copied or cloned.
568unsafe impl<P: BasicProblem> Send for Ipopt<P> {}
569
570impl<P: BasicProblem> Ipopt<P> {
571 /// Common implementation for constructing an Ipopt struct.
572 ///
573 /// This involves some unsafe code that should be isolated for easier maintenance and
574 /// debugging.
575 fn new_impl(
576 nlp_internal: ffi::CNLP_ProblemPtr,
577 nlp: P,
578 num_vars: usize,
579 num_constraints: usize,
580 ) -> Ipopt<P> {
581 let mut ipopt = Ipopt {
582 nlp_internal,
583 nlp_interface: nlp,
584 intermediate_callback: None,
585 // These two will be updated every time sizes callback is called.
586 num_primal_variables: num_vars,
587 num_dual_variables: num_constraints,
588 };
589
590 // Initialize solution arrays so we can safely call solver_data and solver_data_mut without
591 // addressing unallocated memory.
592 unsafe {
593 ffi::cnlp_init_solution(
594 ipopt.nlp_internal,
595 &mut ipopt as *mut Ipopt<P> as *mut std::ffi::c_void,
596 );
597 }
598
599 ipopt
600 }
601
602 /// Create a new unconstrained non-linear problem.
603 pub fn new_unconstrained(nlp: P) -> Result<Self, CreateError> {
604 let num_vars = nlp.num_variables();
605
606 // Ensure there is at least one variable given to optimize over.
607 if num_vars < 1 {
608 return Err(CreateError::NoOptimizationVariablesSpecified);
609 }
610
611 let mut nlp_internal: ffi::CNLP_ProblemPtr = ::std::ptr::null_mut();
612
613 let create_error = CreateProblemStatus::new(unsafe {
614 ffi::cnlp_create_problem(
615 &mut nlp_internal as *mut ffi::CNLP_ProblemPtr,
616 nlp.indexing_style() as Index,
617 Some(Self::basic_sizes),
618 Some(Self::basic_init),
619 Some(Self::variable_only_bounds),
620 Some(Self::eval_f),
621 Some(Self::eval_g_none),
622 Some(Self::eval_grad_f),
623 Some(Self::eval_jac_g_none),
624 Some(Self::eval_h_none),
625 Some(Self::basic_scaling),
626 )
627 });
628
629 if CreateProblemStatus::Success != create_error {
630 return Err(create_error.into());
631 }
632
633 assert!(Self::set_ipopt_option(
634 nlp_internal,
635 "hessian_approximation",
636 "limited-memory"
637 ));
638
639 Ok(Self::new_impl(nlp_internal, nlp, num_vars, 0))
640 }
641
642 /// Helper static function that can be used in the constructor.
643 fn set_ipopt_option<'a, O>(nlp: ffi::CNLP_ProblemPtr, name: &str, option: O) -> bool
644 where
645 O: Into<IpoptOption<'a>>,
646 {
647 let result = unsafe {
648 // Convert the input name string to a `char *` C type
649 let name_cstr = CString::new(name).unwrap();
650
651 // Match option to one of the three types of options Ipopt can receive.
652 match option.into() {
653 IpoptOption::Num(opt) => {
654 ffi::cnlp_add_num_option(nlp, name_cstr.as_ptr(), opt as Number)
655 }
656 IpoptOption::Str(opt) => {
657 // Convert option string to `char *`
658 let opt_cstr = CString::new(opt).unwrap();
659 ffi::cnlp_add_str_option(nlp, name_cstr.as_ptr(), opt_cstr.as_ptr())
660 }
661 IpoptOption::Int(opt) => {
662 ffi::cnlp_add_int_option(nlp, name_cstr.as_ptr(), opt as Int)
663 }
664 }
665 };
666 result != 0 // converts Ipopt Bool to Rust bool
667 }
668
669 /// Set an Ipopt option.
670 pub fn set_option<'a, O>(&mut self, name: &str, option: O) -> Option<&mut Self>
671 where
672 O: Into<IpoptOption<'a>>,
673 {
674 let success = Self::set_ipopt_option(self.nlp_internal, name, option);
675 if success {
676 Some(self)
677 } else {
678 None
679 }
680 }
681
682 /// Set intermediate callback.
683 pub fn set_intermediate_callback(&mut self, mb_cb: Option<IntermediateCallback<P>>)
684 where
685 P: BasicProblem,
686 {
687 self.intermediate_callback = mb_cb;
688
689 unsafe {
690 if mb_cb.is_some() {
691 ffi::cnlp_set_intermediate_callback(self.nlp_internal, Some(Self::intermediate_cb));
692 } else {
693 ffi::cnlp_set_intermediate_callback(self.nlp_internal, None);
694 }
695 }
696 }
697
698 /// Solve non-linear problem.
699 /// Return the solve status and the final value of the objective function.
700 pub fn solve(&mut self) -> SolveResult<P> {
701 let res = {
702 let udata_ptr = self as *mut Ipopt<P>;
703 unsafe { ffi::cnlp_solve(self.nlp_internal, udata_ptr as ffi::CNLP_UserDataPtr) }
704 };
705
706 let Ipopt {
707 nlp_interface: ref mut problem,
708 num_primal_variables,
709 num_dual_variables,
710 ..
711 } = *self;
712
713 SolveResult {
714 solver_data: SolverDataMut {
715 problem,
716 solution: Solution::from_raw(res.data, num_primal_variables, num_dual_variables),
717 },
718 constraint_values: reconstruct_dual_slice(res.g, num_dual_variables),
719 objective_value: res.obj_val,
720 status: SolveStatus::new(res.status),
721 }
722 }
723
724 /// Get data for inspection and updating.
725 #[allow(non_snake_case)]
726 pub fn solver_data_mut(&mut self) -> SolverDataMut<P> {
727 let Ipopt {
728 nlp_interface: ref mut problem,
729 nlp_internal,
730 num_primal_variables,
731 num_dual_variables,
732 ..
733 } = *self;
734
735 let data = unsafe { ffi::cnlp_get_solver_data(nlp_internal) };
736
737 SolverDataMut {
738 problem,
739 solution: Solution::from_raw(data, num_primal_variables, num_dual_variables),
740 }
741 }
742
743 /// Get data for inspection from the internal solver.
744 #[allow(non_snake_case)]
745 pub fn solver_data(&self) -> SolverData<P> {
746 let Ipopt {
747 nlp_interface: ref problem,
748 nlp_internal,
749 num_primal_variables,
750 num_dual_variables,
751 ..
752 } = *self;
753
754 let data = unsafe { ffi::cnlp_get_solver_data(nlp_internal) };
755
756 SolverData {
757 problem,
758 solution: Solution::from_raw(data, num_primal_variables, num_dual_variables),
759 }
760 }
761
762 /**
763 * Ipopt C API
764 */
765
766 /// Specify initial guess for variables and bounds multipliers.
767 ///
768 /// There are no constraints on basic problems.
769 unsafe extern "C" fn basic_init(
770 n: Index,
771 init_x: Bool,
772 x: *mut Number,
773 init_z: Bool,
774 z_l: *mut Number,
775 z_u: *mut Number,
776 m: Index,
777 _init_lambda: Bool,
778 _lambda: *mut Number,
779 user_data: ffi::CNLP_UserDataPtr,
780 ) -> Bool {
781 assert_eq!(m, 0);
782 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
783 if init_x != 0 {
784 let x = slice::from_raw_parts_mut(x, n as usize);
785 if !nlp.initial_point(x) {
786 for i in 0..n as usize {
787 x[i] = 0.0;
788 } // initialize to zero!
789 }
790 }
791 if init_z != 0 {
792 let z_l = slice::from_raw_parts_mut(z_l, n as usize);
793 let z_u = slice::from_raw_parts_mut(z_u, n as usize);
794 if !nlp.initial_bounds_multipliers(z_l, z_u) {
795 for i in 0..n as usize {
796 z_l[i] = 0.0;
797 z_u[i] = 0.0;
798 } // initialize to zero!
799 }
800 }
801 true as Bool
802 }
803
804 /// Specify the number of elements needed to be allocated for the variables.
805 ///
806 /// There are no Hessian, and no constraints on basic problems.
807 unsafe extern "C" fn basic_sizes(
808 n: *mut Index,
809 m: *mut Index,
810 nnz_jac_g: *mut Index,
811 nnz_h_lag: *mut Index,
812 user_data: ffi::CNLP_UserDataPtr,
813 ) -> Bool {
814 let ipopt = &mut (*(user_data as *mut Ipopt<P>));
815
816 ipopt.num_primal_variables = ipopt.nlp_interface.num_variables();
817 ipopt.num_dual_variables = 0; // No constraints
818
819 *n = ipopt.num_primal_variables as Index;
820 *m = ipopt.num_dual_variables as Index;
821
822 *nnz_jac_g = 0; // No constraints
823 *nnz_h_lag = 0; // No Hessian
824 true as Bool
825 }
826
827 /// Specify lower and upper bounds for variables.
828 ///
829 /// There are no constraints on basic problems.
830 unsafe extern "C" fn variable_only_bounds(
831 n: Index,
832 x_l: *mut Number,
833 x_u: *mut Number,
834 m: Index,
835 _g_l: *mut Number,
836 _g_u: *mut Number,
837 user_data: ffi::CNLP_UserDataPtr,
838 ) -> Bool {
839 assert_eq!(m, 0);
840 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
841 nlp.bounds(
842 slice::from_raw_parts_mut(x_l, n as usize),
843 slice::from_raw_parts_mut(x_u, n as usize),
844 ) as Bool
845 }
846
847 /// Evaluate the objective function.
848 unsafe extern "C" fn eval_f(
849 n: Index,
850 x: *const Number,
851 new_x: Bool,
852 obj_value: *mut Number,
853 user_data: ffi::CNLP_UserDataPtr,
854 ) -> Bool {
855 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
856 nlp.objective(
857 slice::from_raw_parts(x, n as usize),
858 new_x != 0,
859 &mut *obj_value,
860 ) as Bool
861 }
862
863 /// Evaluate the objective gradient.
864 unsafe extern "C" fn eval_grad_f(
865 n: Index,
866 x: *const Number,
867 new_x: Bool,
868 grad_f: *mut Number,
869 user_data: ffi::CNLP_UserDataPtr,
870 ) -> Bool {
871 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
872 nlp.objective_grad(
873 slice::from_raw_parts(x, n as usize),
874 new_x != 0,
875 slice::from_raw_parts_mut(grad_f, n as usize),
876 ) as Bool
877 }
878
879 /// Placeholder constraint function with no constraints.
880 unsafe extern "C" fn eval_g_none(
881 _n: Index,
882 _x: *const Number,
883 _new_x: Bool,
884 _m: Index,
885 _g: *mut Number,
886 _user_data: ffi::CNLP_UserDataPtr,
887 ) -> Bool {
888 true as Bool
889 }
890
891 /// Placeholder constraint derivative function with no constraints.
892 unsafe extern "C" fn eval_jac_g_none(
893 _n: Index,
894 _x: *const Number,
895 _new_x: Bool,
896 _m: Index,
897 _nele_jac: Index,
898 _irow: *mut Index,
899 _jcol: *mut Index,
900 _values: *mut Number,
901 _user_data: ffi::CNLP_UserDataPtr,
902 ) -> Bool {
903 true as Bool
904 }
905
906 /// Placeholder hessian evaluation function.
907 unsafe extern "C" fn eval_h_none(
908 _n: Index,
909 _x: *const Number,
910 _new_x: Bool,
911 _obj_factor: Number,
912 _m: Index,
913 _lambda: *const Number,
914 _new_lambda: Bool,
915 _nele_hess: Index,
916 _irow: *mut Index,
917 _jcol: *mut Index,
918 _values: *mut Number,
919 _user_data: ffi::CNLP_UserDataPtr,
920 ) -> Bool {
921 // From "Quasi-Newton Approximation of Second-Derivatives" in Ipopt docs:
922 // "If you are using the C or Fortran interface, you still need to implement [eval_h],
923 // but [it] should return false or IERR=1, respectively, and don't need to do
924 // anything else."
925 false as Bool
926 }
927
928 /// Specify custom scaling parameters.
929 ///
930 /// This function is called by Ipopt when `nlp_scaling_method` is set to `user-scaling`. Basic
931 /// problems have no constraint scaling.
932 unsafe extern "C" fn basic_scaling(
933 obj_scaling: *mut Number,
934 use_x_scaling: *mut Bool,
935 n: Index,
936 x_scaling: *mut Number,
937 use_g_scaling: *mut Bool,
938 m: Index,
939 _g_scaling: *mut Number,
940 user_data: ffi::CNLP_UserDataPtr,
941 ) -> Bool {
942 assert_eq!(m, 0);
943 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
944 *obj_scaling = nlp.objective_scaling();
945 *use_x_scaling =
946 nlp.variable_scaling(slice::from_raw_parts_mut(x_scaling, n as usize)) as Bool;
947 *use_g_scaling = false as Bool;
948 true as Bool
949 }
950
951 /// Intermediate callback.
952 unsafe extern "C" fn intermediate_cb(
953 alg_mod: ffi::CNLP_AlgorithmMode,
954 iter_count: Index,
955 obj_value: Number,
956 inf_pr: Number,
957 inf_du: Number,
958 mu: Number,
959 d_norm: Number,
960 regularization_size: Number,
961 alpha_du: Number,
962 alpha_pr: Number,
963 ls_trials: Index,
964 user_data: ffi::CNLP_UserDataPtr,
965 ) -> Bool {
966 let ip = &mut (*(user_data as *mut Ipopt<P>));
967 if let Some(callback) = ip.intermediate_callback {
968 (callback)(
969 &mut ip.nlp_interface,
970 IntermediateCallbackData {
971 alg_mod: match alg_mod {
972 0 => AlgorithmMode::Regular,
973 _ => AlgorithmMode::RestorationPhase,
974 },
975 iter_count,
976 obj_value,
977 inf_pr,
978 inf_du,
979 mu,
980 d_norm,
981 regularization_size,
982 alpha_du,
983 alpha_pr,
984 ls_trials,
985 },
986 ) as Bool
987 } else {
988 true as Bool
989 }
990 }
991}
992
993impl<P: NewtonProblem> Ipopt<P> {
994 /// Create a new second order problem, which will be solved using the Newton-Raphson method.
995 pub fn new_newton(nlp: P) -> Result<Self, CreateError> {
996 let num_vars = nlp.num_variables();
997
998 // Ensure there is at least one variable given to optimize over.
999 if num_vars < 1 {
1000 return Err(CreateError::NoOptimizationVariablesSpecified);
1001 }
1002
1003 let mut nlp_internal: ffi::CNLP_ProblemPtr = ::std::ptr::null_mut();
1004
1005 let create_error = CreateProblemStatus::new(unsafe {
1006 ffi::cnlp_create_problem(
1007 &mut nlp_internal as *mut ffi::CNLP_ProblemPtr,
1008 nlp.indexing_style() as Index,
1009 Some(Self::newton_sizes),
1010 Some(Self::basic_init),
1011 Some(Self::variable_only_bounds),
1012 Some(Self::eval_f),
1013 Some(Self::eval_g_none),
1014 Some(Self::eval_grad_f),
1015 Some(Self::eval_jac_g_none),
1016 Some(Self::eval_h),
1017 Some(Self::basic_scaling),
1018 )
1019 });
1020
1021 if create_error != CreateProblemStatus::Success {
1022 return Err(create_error.into());
1023 }
1024
1025 Ok(Self::new_impl(nlp_internal, nlp, num_vars, 0))
1026 }
1027
1028 /**
1029 * Ipopt C API
1030 */
1031
1032 /// Specify the number of elements needed to be allocated for the variables.
1033 ///
1034 /// There are no Hessian, and no constraints on Newton problems.
1035 unsafe extern "C" fn newton_sizes(
1036 n: *mut Index,
1037 m: *mut Index,
1038 nnz_jac_g: *mut Index,
1039 nnz_h_lag: *mut Index,
1040 user_data: ffi::CNLP_UserDataPtr,
1041 ) -> Bool {
1042 Self::basic_sizes(n, m, nnz_jac_g, nnz_h_lag, user_data);
1043 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1044 *nnz_h_lag = nlp.num_hessian_non_zeros() as Index;
1045 true as Bool
1046 }
1047
1048 /// Evaluate the Hessian matrix.
1049 unsafe extern "C" fn eval_h(
1050 n: Index,
1051 x: *const Number,
1052 _new_x: Bool,
1053 obj_factor: Number,
1054 _m: Index,
1055 _lambda: *const Number,
1056 _new_lambda: Bool,
1057 nele_hess: Index,
1058 irow: *mut Index,
1059 jcol: *mut Index,
1060 values: *mut Number,
1061 user_data: ffi::CNLP_UserDataPtr,
1062 ) -> Bool {
1063 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1064 if values.is_null() {
1065 /* return the structure. */
1066 nlp.hessian_indices(
1067 slice::from_raw_parts_mut(irow, nele_hess as usize),
1068 slice::from_raw_parts_mut(jcol, nele_hess as usize),
1069 ) as Bool
1070 } else {
1071 /* return the values. */
1072 let result = nlp.hessian_values(
1073 slice::from_raw_parts(x, n as usize),
1074 slice::from_raw_parts_mut(values, nele_hess as usize),
1075 ) as Bool;
1076 // This problem has no constraints so we can multiply each entry by the
1077 // objective factor.
1078 let start_idx = nlp.indexing_style() as isize;
1079 for i in start_idx..nele_hess as isize {
1080 *values.offset(i) *= obj_factor;
1081 }
1082 result
1083 }
1084 }
1085}
1086
1087impl<P: ConstrainedProblem> Ipopt<P> {
1088 /// Create a new constrained non-linear problem.
1089 pub fn new(nlp: P) -> Result<Self, CreateError> {
1090 let num_vars = nlp.num_variables();
1091
1092 // Ensure there is at least one variable given to optimize over.
1093 if num_vars < 1 {
1094 return Err(CreateError::NoOptimizationVariablesSpecified);
1095 }
1096
1097 let num_constraints = nlp.num_constraints();
1098 let num_constraint_jac_nnz = nlp.num_constraint_jacobian_non_zeros();
1099
1100 // It doesn't make sense to have constant constraints. Also if there is a non-trivial
1101 // constraint jacobian, there better be some constraints.
1102 // We check for these here explicitly to prevent hard-to-debug errors down the line.
1103 if (num_constraints > 0 && num_constraint_jac_nnz == 0)
1104 || (num_constraints == 0 && num_constraint_jac_nnz > 0)
1105 {
1106 return Err(CreateError::InvalidConstraintJacobian {
1107 num_constraints,
1108 num_constraint_jac_nnz,
1109 });
1110 }
1111
1112 let mut nlp_internal: ffi::CNLP_ProblemPtr = ::std::ptr::null_mut();
1113
1114 let create_error = CreateProblemStatus::new(unsafe {
1115 ffi::cnlp_create_problem(
1116 &mut nlp_internal as *mut ffi::CNLP_ProblemPtr,
1117 nlp.indexing_style() as Index,
1118 Some(Self::sizes),
1119 Some(Self::init),
1120 Some(Self::bounds),
1121 Some(Self::eval_f),
1122 Some(Self::eval_g),
1123 Some(Self::eval_grad_f),
1124 Some(Self::eval_jac_g),
1125 Some(Self::eval_full_h),
1126 Some(Self::scaling),
1127 )
1128 });
1129
1130 if create_error != CreateProblemStatus::Success {
1131 return Err(create_error.into());
1132 }
1133
1134 Ok(Self::new_impl(nlp_internal, nlp, num_vars, num_constraints))
1135 }
1136
1137 /**
1138 * Ipopt C API
1139 */
1140
1141 /// Specify initial guess for variables, bounds multipliers and constraint multipliers.
1142 unsafe extern "C" fn init(
1143 n: Index,
1144 init_x: Bool,
1145 x: *mut Number,
1146 init_z: Bool,
1147 z_l: *mut Number,
1148 z_u: *mut Number,
1149 m: Index,
1150 init_lambda: Bool,
1151 lambda: *mut Number,
1152 user_data: ffi::CNLP_UserDataPtr,
1153 ) -> Bool {
1154 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1155 if init_x != 0 {
1156 let x = slice::from_raw_parts_mut(x, n as usize);
1157 if !nlp.initial_point(x) {
1158 for i in 0..n as usize {
1159 x[i] = 0.0;
1160 } // initialize to zero!
1161 }
1162 }
1163 if init_z != 0 {
1164 let z_l = slice::from_raw_parts_mut(z_l, n as usize);
1165 let z_u = slice::from_raw_parts_mut(z_u, n as usize);
1166 if !nlp.initial_bounds_multipliers(z_l, z_u) {
1167 for i in 0..n as usize {
1168 z_l[i] = 0.0;
1169 z_u[i] = 0.0;
1170 } // initialize to zero!
1171 }
1172 }
1173 if init_lambda != 0 {
1174 let lambda = slice::from_raw_parts_mut(lambda, m as usize);
1175 if !nlp.initial_constraint_multipliers(lambda) {
1176 for i in 0..m as usize {
1177 lambda[i] = 0.0;
1178 } // initialize to zero!
1179 }
1180 }
1181 true as Bool
1182 }
1183
1184 /// Specify the number of elements needed to be allocated for various arrays.
1185 unsafe extern "C" fn sizes(
1186 n: *mut Index,
1187 m: *mut Index,
1188 nnz_jac_g: *mut Index,
1189 nnz_h_lag: *mut Index,
1190 user_data: ffi::CNLP_UserDataPtr,
1191 ) -> Bool {
1192 let ipopt = &mut (*(user_data as *mut Ipopt<P>));
1193 ipopt.num_primal_variables = ipopt.nlp_interface.num_variables();
1194 ipopt.num_dual_variables = ipopt.nlp_interface.num_constraints();
1195
1196 *n = ipopt.num_primal_variables as Index;
1197 *m = ipopt.num_dual_variables as Index;
1198
1199 *nnz_jac_g = ipopt.nlp_interface.num_constraint_jacobian_non_zeros() as Index;
1200 *nnz_h_lag = ipopt.nlp_interface.num_hessian_non_zeros() as Index;
1201 true as Bool
1202 }
1203
1204 /// Specify lower and upper bounds for variables and the constraint function.
1205 unsafe extern "C" fn bounds(
1206 n: Index,
1207 x_l: *mut Number,
1208 x_u: *mut Number,
1209 m: Index,
1210 g_l: *mut Number,
1211 g_u: *mut Number,
1212 user_data: ffi::CNLP_UserDataPtr,
1213 ) -> Bool {
1214 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1215 (nlp.bounds(
1216 slice::from_raw_parts_mut(x_l, n as usize),
1217 slice::from_raw_parts_mut(x_u, n as usize),
1218 ) && nlp.constraint_bounds(
1219 slice::from_raw_parts_mut(g_l, m as usize),
1220 slice::from_raw_parts_mut(g_u, m as usize),
1221 )) as Bool
1222 }
1223
1224 /// Evaluate the constraint function.
1225 unsafe extern "C" fn eval_g(
1226 n: Index,
1227 x: *const Number,
1228 new_x: Bool,
1229 m: Index,
1230 g: *mut Number,
1231 user_data: ffi::CNLP_UserDataPtr,
1232 ) -> Bool {
1233 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1234 nlp.constraint(
1235 slice::from_raw_parts(x, n as usize),
1236 new_x != 0,
1237 slice::from_raw_parts_mut(g, m as usize),
1238 ) as Bool
1239 }
1240
1241 /// Evaluate the constraint Jacobian.
1242 unsafe extern "C" fn eval_jac_g(
1243 n: Index,
1244 x: *const Number,
1245 new_x: Bool,
1246 _m: Index,
1247 nele_jac: Index,
1248 irow: *mut Index,
1249 jcol: *mut Index,
1250 values: *mut Number,
1251 user_data: ffi::CNLP_UserDataPtr,
1252 ) -> Bool {
1253 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1254 if values.is_null() {
1255 /* return the structure of the Jacobian */
1256 nlp.constraint_jacobian_indices(
1257 slice::from_raw_parts_mut(irow, nele_jac as usize),
1258 slice::from_raw_parts_mut(jcol, nele_jac as usize),
1259 ) as Bool
1260 } else {
1261 /* return the values of the Jacobian of the constraints */
1262 nlp.constraint_jacobian_values(
1263 slice::from_raw_parts(x, n as usize),
1264 new_x != 0,
1265 slice::from_raw_parts_mut(values, nele_jac as usize),
1266 ) as Bool
1267 }
1268 }
1269
1270 /// Evaluate the Hessian matrix.
1271 ///
1272 /// Compared to `eval_h` from `NewtonProblem`, this version includes the constraint Hessians.
1273 unsafe extern "C" fn eval_full_h(
1274 n: Index,
1275 x: *const Number,
1276 new_x: Bool,
1277 obj_factor: Number,
1278 m: Index,
1279 lambda: *const Number,
1280 _new_lambda: Bool,
1281 nele_hess: Index,
1282 irow: *mut Index,
1283 jcol: *mut Index,
1284 values: *mut Number,
1285 user_data: ffi::CNLP_UserDataPtr,
1286 ) -> Bool {
1287 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1288 if values.is_null() {
1289 /* return the structure. */
1290 nlp.hessian_indices(
1291 slice::from_raw_parts_mut(irow, nele_hess as usize),
1292 slice::from_raw_parts_mut(jcol, nele_hess as usize),
1293 ) as Bool
1294 } else {
1295 /* return the values. */
1296 nlp.hessian_values(
1297 slice::from_raw_parts(x, n as usize),
1298 new_x != 0,
1299 obj_factor,
1300 slice::from_raw_parts(lambda, m as usize),
1301 slice::from_raw_parts_mut(values, nele_hess as usize),
1302 ) as Bool
1303 }
1304 }
1305
1306 /// Specify custom scaling parameters.
1307 ///
1308 /// This function is called by Ipopt when `nlp_scaling_method` is set to `user-scaling`.
1309 unsafe extern "C" fn scaling(
1310 obj_scaling: *mut Number,
1311 use_x_scaling: *mut Bool,
1312 n: Index,
1313 x_scaling: *mut Number,
1314 use_g_scaling: *mut Bool,
1315 m: Index,
1316 g_scaling: *mut Number,
1317 user_data: ffi::CNLP_UserDataPtr,
1318 ) -> Bool {
1319 let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
1320 *obj_scaling = nlp.objective_scaling();
1321 *use_x_scaling =
1322 nlp.variable_scaling(slice::from_raw_parts_mut(x_scaling, n as usize)) as Bool;
1323 *use_g_scaling =
1324 nlp.constraint_scaling(slice::from_raw_parts_mut(g_scaling, m as usize)) as Bool;
1325 true as Bool
1326 }
1327}
1328
1329/// Free the memory allocated on the C side.
1330impl<P: BasicProblem> Drop for Ipopt<P> {
1331 fn drop(&mut self) {
1332 unsafe {
1333 ffi::cnlp_free_problem(self.nlp_internal);
1334 }
1335 }
1336}
1337
1338/// Zero-based indexing (C Style) or one-based indexing (Fortran style).
1339#[derive(Copy, Clone, Debug, PartialEq)]
1340pub enum IndexingStyle {
1341 /// C-style array indexing starting from 0.
1342 CStyle = 0,
1343 /// Fortran-style array indexing starting from 1.
1344 FortranStyle = 1,
1345}
1346
1347/// Program return status.
1348#[derive(Copy, Clone, Debug, PartialEq)]
1349pub enum SolveStatus {
1350 /// Console Message: `EXIT: Optimal Solution Found.`
1351 ///
1352 /// This message indicates that IPOPT found a (locally) optimal point within the desired tolerances.
1353 SolveSucceeded,
1354 /// Console Message: `EXIT: Solved To Acceptable Level.`
1355 ///
1356 /// This indicates that the algorithm did not converge to the "desired" tolerances, but that
1357 /// it was able to obtain a point satisfying the "acceptable" tolerance level as specified by
1358 /// the [`acceptable_*`
1359 /// ](https://www.coin-or.org/Ipopt/documentation/node42.html#opt:acceptable_tol) options. This
1360 /// may happen if the desired tolerances are too small for the current problem.
1361 SolvedToAcceptableLevel,
1362 /// Console Message: `EXIT: Feasible point for square problem found.`
1363 ///
1364 /// This message is printed if the problem is "square" (i.e., it has as many equality
1365 /// constraints as free variables) and IPOPT found a feasible point.
1366 FeasiblePointFound,
1367 /// Console Message: `EXIT: Converged to a point of local infeasibility. Problem may be
1368 /// infeasible.`
1369 ///
1370 /// The restoration phase converged to a point that is a minimizer for the constraint violation
1371 /// (in the l1-norm), but is not feasible for the original problem. This indicates that
1372 /// the problem may be infeasible (or at least that the algorithm is stuck at a locally
1373 /// infeasible point). The returned point (the minimizer of the constraint violation) might
1374 /// help you to find which constraint is causing the problem. If you believe that the NLP is
1375 /// feasible, it might help to start the optimization from a different point.
1376 InfeasibleProblemDetected,
1377 /// Console Message: `EXIT: Search Direction is becoming Too Small.`
1378 ///
1379 /// This indicates that IPOPT is calculating very small step sizes and is making very little
1380 /// progress. This could happen if the problem has been solved to the best numerical accuracy
1381 /// possible given the current scaling.
1382 SearchDirectionBecomesTooSmall,
1383 /// Console Message: `EXIT: Iterates diverging; problem might be unbounded.`
1384 ///
1385 /// This message is printed if the max-norm of the iterates becomes larger than the value of
1386 /// the option [`diverging_iterates_tol`
1387 /// ](https://www.coin-or.org/Ipopt/documentation/node42.html#opt:diverging_iterates_tol).
1388 /// This can happen if the problem is unbounded below and the iterates are diverging.
1389 DivergingIterates,
1390 /// Console Message: `EXIT: Stopping optimization at current point as requested by user.`
1391 ///
1392 /// This message is printed if the user call-back method intermediate_callback returned false
1393 /// (see Section [3.3.4](https://www.coin-or.org/Ipopt/documentation/node23.html#sec:add_meth)).
1394 UserRequestedStop,
1395 /// Console Message: `EXIT: Maximum Number of Iterations Exceeded.`
1396 ///
1397 /// This indicates that IPOPT has exceeded the maximum number of iterations as specified by the
1398 /// option [`max_iter`](https://www.coin-or.org/Ipopt/documentation/node42.html#opt:max_iter).
1399 MaximumIterationsExceeded,
1400 /// Console Message: `EXIT: Maximum CPU time exceeded.`
1401 ///
1402 /// This indicates that IPOPT has exceeded the maximum number of CPU seconds as specified by
1403 /// the option
1404 /// [`max_cpu_time`](https://www.coin-or.org/Ipopt/documentation/node42.html#opt:max_cpu_time).
1405 MaximumCpuTimeExceeded,
1406 /// Console Message: `EXIT: Restoration Failed!`
1407 ///
1408 /// This indicates that the restoration phase failed to find a feasible point that was
1409 /// acceptable to the filter line search for the original problem. This could happen if the
1410 /// problem is highly degenerate, does not satisfy the constraint qualification, or if your NLP
1411 /// code provides incorrect derivative information.
1412 RestorationFailed,
1413 /// Console Output: `EXIT: Error in step computation (regularization becomes too large?)!`
1414 ///
1415 /// This messages is printed if IPOPT is unable to compute a search direction, despite several
1416 /// attempts to modify the iteration matrix. Usually, the value of the regularization parameter
1417 /// then becomes too large. One situation where this can happen is when values in the Hessian
1418 /// are invalid (NaN or Inf). You can check whether this is true by using the
1419 /// [`check_derivatives_for_naninf`
1420 /// ](https://www.coin-or.org/Ipopt/documentation/node44.html#opt:check_derivatives_for_naninf)
1421 /// option.
1422 ErrorInStepComputation,
1423 /// Console Message: (details about the particular error will be output to the console)
1424 ///
1425 /// This indicates that there was some problem specifying the options. See the specific message
1426 /// for details.
1427 InvalidOption,
1428 /// Console Message: `EXIT: Problem has too few degrees of freedom.`
1429 ///
1430 /// This indicates that your problem, as specified, has too few degrees of freedom. This can
1431 /// happen if you have too many equality constraints, or if you fix too many variables (IPOPT
1432 /// removes fixed variables by default, see also the [`fixed_variable_treatment`
1433 /// ](https://www.coin-or.org/Ipopt/documentation/node44.html#opt:fixed_variable_treatment)
1434 /// option).
1435 NotEnoughDegreesOfFreedom,
1436 /// Console Message: (no console message, this is a return code for the C and Fortran
1437 /// interfaces only.)
1438 ///
1439 /// This indicates that there was an exception of some sort when building the IpoptProblem
1440 /// structure in the C or Fortran interface. Likely there is an error in your model or the main
1441 /// routine.
1442 InvalidProblemDefinition,
1443 /// An invalid number like `NaN` was detected.
1444 InvalidNumberDetected,
1445 /// Console Message: (details about the particular error will be output to the console)
1446 ///
1447 /// This indicates that IPOPT has thrown an exception that does not have an internal return
1448 /// code. See the specific message for details.
1449 UnrecoverableException,
1450 /// Console Message: `Unknown Exception caught in Ipopt`
1451 ///
1452 /// An unknown exception was caught in IPOPT. This exception could have originated from your
1453 /// model or any linked in third party code.
1454 NonIpoptExceptionThrown,
1455 /// Console Message: `EXIT: Not enough memory.`
1456 ///
1457 /// An error occurred while trying to allocate memory. The problem may be too large for your
1458 /// current memory and swap configuration.
1459 InsufficientMemory,
1460 /// Console: `EXIT: INTERNAL ERROR: Unknown SolverReturn value - Notify IPOPT Authors.`
1461 ///
1462 /// An unknown internal error has occurred. Please notify the authors of IPOPT via the mailing
1463 /// list.
1464 InternalError,
1465 /// Unclassified error.
1466 UnknownError,
1467}
1468
1469impl Display for SolveStatus {
1470 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
1471 match *self {
1472 SolveStatus::SolveSucceeded => write!(f, "
1473 Console Message: `EXIT: Optimal Solution Found.`\n\n\
1474 This message indicates that IPOPT found a (locally) optimal point within the desired \
1475 tolerances."),
1476
1477 SolveStatus::SolvedToAcceptableLevel => write!(f, "
1478 Console Message: `EXIT: Solved To Acceptable Level.`\n\n\
1479 This indicates that the algorithm did not converge to the \"desired\" tolerances, but \
1480 that it was able to obtain a point satisfying the \"acceptable\" tolerance level as \
1481 specified by the `acceptable_*` options. This may happen if the desired tolerances \
1482 are too small for the current problem."),
1483
1484 SolveStatus::FeasiblePointFound => write!(f, "
1485 Console Message: `EXIT: Feasible point for square problem found.`\n\n\
1486 This message is printed if the problem is \"square\" (i.e., it has as many equality \
1487 constraints as free variables) and IPOPT found a feasible point."),
1488
1489 SolveStatus::InfeasibleProblemDetected => write!(f, "
1490 Console Message: `EXIT: Converged to a point of local infeasibility. Problem may be \
1491 infeasible.`\n\n\
1492 The restoration phase converged to a point that is a minimizer for the constraint \
1493 violation (in the l1-norm), but is not feasible for the original problem. This \
1494 indicates that the problem may be infeasible (or at least that the algorithm is stuck \
1495 at a locally infeasible point). The returned point (the minimizer of the constraint \
1496 violation) might help you to find which constraint is causing the problem. If you \
1497 believe that the NLP is feasible, it might help to start the optimization from a \
1498 different point."),
1499
1500 SolveStatus::SearchDirectionBecomesTooSmall => write!(f, "
1501 Console Message: `EXIT: Search Direction is becoming Too Small.`\n\n\
1502 This indicates that IPOPT is calculating very small step sizes and is making very \
1503 little progress. This could happen if the problem has been solved to the best numerical \
1504 accuracy possible given the current scaling."),
1505
1506 SolveStatus::DivergingIterates => write!(f, "
1507 Console Message: `EXIT: Iterates diverging; problem might be unbounded.`\n\n\
1508 This message is printed if the max-norm of the iterates becomes larger than the value \
1509 of the option `diverging_iterates_tol`. This can happen if the problem is unbounded \
1510 below and the iterates are diverging."),
1511
1512 SolveStatus::UserRequestedStop => write!(f, "
1513 Console Message: `EXIT: Stopping optimization at current point as requested by \
1514 user.`\n\n\
1515 This message is printed if the user call-back method intermediate_callback returned \
1516 false."),
1517
1518 SolveStatus::MaximumIterationsExceeded => write!(f, "
1519 Console Message: `EXIT: Maximum Number of Iterations Exceeded.`\n\n\
1520 This indicates that IPOPT has exceeded the maximum number of iterations as specified by \
1521 the option `max_iter`."),
1522
1523 SolveStatus::MaximumCpuTimeExceeded => write!(f, "
1524 Console Message: `EXIT: Maximum CPU time exceeded.`\n\n\
1525 This indicates that IPOPT has exceeded the maximum number of CPU seconds as specified \
1526 by the option `max_cpu_time`."),
1527
1528 SolveStatus::RestorationFailed => write!(f, "
1529 Console Message: `EXIT: Restoration Failed!`\n\n\
1530 This indicates that the restoration phase failed to find a feasible point that was \
1531 acceptable to the filter line search for the original problem. This could happen if the \
1532 problem is highly degenerate, does not satisfy the constraint qualification, or if \
1533 your NLP code provides incorrect derivative information."),
1534
1535 SolveStatus::ErrorInStepComputation => write!(f, "
1536 Console Output: `EXIT: Error in step computation (regularization becomes too large?)!`\n\n\
1537 This messages is printed if IPOPT is unable to compute a search direction, despite \
1538 several attempts to modify the iteration matrix. Usually, the value of the \
1539 regularization parameter then becomes too large. One situation where this can happen is \
1540 when values in the Hessian are invalid (NaN or Inf). You can check whether this is \
1541 true by using the `check_derivatives_for_naninf` option."),
1542
1543 SolveStatus::InvalidOption => write!(f, "
1544 Console Message: (details about the particular error will be output to the console)\n\n\
1545 This indicates that there was some problem specifying the options. See the specific \
1546 message for details."),
1547
1548 SolveStatus::NotEnoughDegreesOfFreedom => write!(f, "
1549 Console Message: `EXIT: Problem has too few degrees of freedom.`\n\n\
1550 This indicates that your problem, as specified, has too few degrees of freedom. This \
1551 can happen if you have too many equality constraints, or if you fix too many variables \
1552 (IPOPT removes fixed variables by default, see also the `fixed_variable_treatment` \
1553 option)."),
1554
1555 SolveStatus::InvalidProblemDefinition => write!(f, "
1556 Console Message: (no console message, this is a return code for the C and Fortran \
1557 interfaces only.)\n\n\
1558 This indicates that there was an exception of some sort when building the \
1559 IpoptProblem structure in the C or Fortran interface. Likely there is an error in \
1560 your model or the main routine."),
1561
1562 SolveStatus::InvalidNumberDetected => write!(f, "An invalid number like `NaN` was detected."),
1563
1564 SolveStatus::UnrecoverableException => write!(f, "
1565 Console Message: (details about the particular error will be output to the \
1566 console)\n\n\
1567 This indicates that IPOPT has thrown an exception that does not have an internal \
1568 return code. See the specific message for details."),
1569
1570 SolveStatus::NonIpoptExceptionThrown => write!(f, "
1571 Console Message: `Unknown Exception caught in Ipopt`\n\n\
1572 An unknown exception was caught in IPOPT. This exception could have originated from \
1573 your model or any linked in third party code."),
1574
1575 SolveStatus::InsufficientMemory => write!(f, "
1576 Console Message: `EXIT: Not enough memory.`\n\n\
1577 An error occurred while trying to allocate memory. The problem may be too large for \
1578 your current memory and swap configuration."),
1579
1580 SolveStatus::InternalError => write!(f, "
1581 Console: `EXIT: INTERNAL ERROR: Unknown SolverReturn value - Notify IPOPT Authors.`\n\n\
1582 An unknown internal error has occurred. Please notify the authors of IPOPT via the \
1583 mailing list."),
1584
1585 SolveStatus::UnknownError => write!(f, "Unclassified error."),
1586 }
1587 }
1588}
1589
1590#[allow(non_snake_case)]
1591impl SolveStatus {
1592 fn new(status: ffi::CNLP_ApplicationReturnStatus) -> Self {
1593 use crate::SolveStatus as RS;
1594 match status {
1595 ffi::CNLP_ApplicationReturnStatus_CNLP_SOLVE_SUCCEEDED => RS::SolveSucceeded,
1596 ffi::CNLP_ApplicationReturnStatus_CNLP_SOLVED_TO_ACCEPTABLE_LEVEL => {
1597 RS::SolvedToAcceptableLevel
1598 }
1599 ffi::CNLP_ApplicationReturnStatus_CNLP_INFEASIBLE_PROBLEM_DETECTED => {
1600 RS::InfeasibleProblemDetected
1601 }
1602 ffi::CNLP_ApplicationReturnStatus_CNLP_SEARCH_DIRECTION_BECOMES_TOO_SMALL => {
1603 RS::SearchDirectionBecomesTooSmall
1604 }
1605 ffi::CNLP_ApplicationReturnStatus_CNLP_DIVERGING_ITERATES => RS::DivergingIterates,
1606 ffi::CNLP_ApplicationReturnStatus_CNLP_USER_REQUESTED_STOP => RS::UserRequestedStop,
1607 ffi::CNLP_ApplicationReturnStatus_CNLP_FEASIBLE_POINT_FOUND => RS::FeasiblePointFound,
1608 ffi::CNLP_ApplicationReturnStatus_CNLP_MAXIMUM_ITERATIONS_EXCEEDED => {
1609 RS::MaximumIterationsExceeded
1610 }
1611 ffi::CNLP_ApplicationReturnStatus_CNLP_RESTORATION_FAILED => RS::RestorationFailed,
1612 ffi::CNLP_ApplicationReturnStatus_CNLP_ERROR_IN_STEP_COMPUTATION => {
1613 RS::ErrorInStepComputation
1614 }
1615 ffi::CNLP_ApplicationReturnStatus_CNLP_MAXIMUM_CPUTIME_EXCEEDED => {
1616 RS::MaximumCpuTimeExceeded
1617 }
1618 ffi::CNLP_ApplicationReturnStatus_CNLP_NOT_ENOUGH_DEGREES_OF_FREEDOM => {
1619 RS::NotEnoughDegreesOfFreedom
1620 }
1621 ffi::CNLP_ApplicationReturnStatus_CNLP_INVALID_PROBLEM_DEFINITION => {
1622 RS::InvalidProblemDefinition
1623 }
1624 ffi::CNLP_ApplicationReturnStatus_CNLP_INVALID_OPTION => RS::InvalidOption,
1625 ffi::CNLP_ApplicationReturnStatus_CNLP_INVALID_NUMBER_DETECTED => {
1626 RS::InvalidNumberDetected
1627 }
1628 ffi::CNLP_ApplicationReturnStatus_CNLP_UNRECOVERABLE_EXCEPTION => {
1629 RS::UnrecoverableException
1630 }
1631 ffi::CNLP_ApplicationReturnStatus_CNLP_NONIPOPT_EXCEPTION_THROWN => {
1632 RS::NonIpoptExceptionThrown
1633 }
1634 ffi::CNLP_ApplicationReturnStatus_CNLP_INSUFFICIENT_MEMORY => RS::InsufficientMemory,
1635 ffi::CNLP_ApplicationReturnStatus_CNLP_INTERNAL_ERROR => RS::InternalError,
1636 _ => RS::UnknownError,
1637 }
1638 }
1639}
1640
1641/// Problem create error type. This type is higher level than `CreateProblemStatus` and it captures
1642/// inconsistencies with the input before even calling `CreateIpoptProblem` internally adding
1643/// safety to this wrapper.
1644#[derive(Copy, Clone, Debug, PartialEq)]
1645pub enum CreateError {
1646 /// No optimization variables were provided.
1647 NoOptimizationVariablesSpecified,
1648 /// The number of Jacobian elements is non-zero, yet no constraints were provided or
1649 /// the number of constraints is non-zero, yet no Jacobian elements were provided.
1650 InvalidConstraintJacobian {
1651 /// Number of constraints set in the problem.
1652 num_constraints: usize,
1653 /// Number of constraint Jacobian entries specified for the problem.
1654 num_constraint_jac_nnz: usize,
1655 },
1656 /// Unexpected error occurred: None of the above. This is likely an internal bug.
1657 Unknown,
1658}
1659
1660impl Display for CreateError {
1661 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
1662 match *self {
1663 CreateError::NoOptimizationVariablesSpecified => {
1664 write!(f, "No optimization variables were provided.")
1665 }
1666 CreateError::InvalidConstraintJacobian {
1667 num_constraints,
1668 num_constraint_jac_nnz,
1669 } => write!(
1670 f,
1671 "The number of constraint Jacobian elements ({}) is inconsistent with the \
1672 number of constraints ({}).",
1673 num_constraint_jac_nnz, num_constraints
1674 ),
1675 CreateError::Unknown => write!(
1676 f,
1677 "Unexpected error occurred. This is likely an internal bug."
1678 ),
1679 }
1680 }
1681}
1682
1683impl std::error::Error for CreateError {
1684 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1685 None
1686 }
1687}
1688
1689impl From<CreateProblemStatus> for CreateError {
1690 fn from(_s: CreateProblemStatus) -> CreateError {
1691 CreateError::Unknown
1692 }
1693}
1694
1695/// Internal program create return status.
1696#[derive(Copy, Clone, Debug, PartialEq)]
1697enum CreateProblemStatus {
1698 /// Program creation was successful.
1699 ///
1700 /// This variant should never be returned, instead a successfully built instance is returned in
1701 /// a `Result` struct.
1702 Success,
1703 /// The initial guess callback is missing.
1704 MissingInitialGuess,
1705 /// Missing callback for evaluating variable and constraint bounds.
1706 MissingBounds,
1707 /// Missing callback for evaluating the objective: `eval_f`.
1708 MissingEvalF,
1709 /// Missing callback for evaluating the gradient of the objective: `eval_grad_f`.
1710 MissingEvalGradF,
1711 /// Inconsistent problem definition.
1712 InvalidProblemDefinition,
1713 /// Unexpected error occurred: None of the above. This is likely an internal bug.
1714 UnknownError,
1715}
1716
1717#[allow(non_snake_case)]
1718impl CreateProblemStatus {
1719 fn new(status: ffi::CNLP_CreateProblemStatus) -> Self {
1720 use crate::CreateProblemStatus as RS;
1721 match status {
1722 ffi::CNLP_CreateProblemStatus_CNLP_SUCCESS => RS::Success,
1723 ffi::CNLP_CreateProblemStatus_CNLP_MISSING_INITIAL_GUESS => RS::MissingInitialGuess,
1724 ffi::CNLP_CreateProblemStatus_CNLP_MISSING_BOUNDS => RS::MissingBounds,
1725 ffi::CNLP_CreateProblemStatus_CNLP_MISSING_EVAL_F => RS::MissingEvalF,
1726 ffi::CNLP_CreateProblemStatus_CNLP_MISSING_EVAL_GRAD_F => RS::MissingEvalGradF,
1727 ffi::CNLP_CreateProblemStatus_CNLP_INVALID_PROBLEM_DEFINITION_ON_CREATE => {
1728 RS::InvalidProblemDefinition
1729 }
1730 ffi::CNLP_CreateProblemStatus_CNLP_UNRECOVERABLE_EXCEPTION_ON_CREATE => {
1731 RS::UnknownError
1732 }
1733 _ => RS::UnknownError,
1734 }
1735 }
1736}
1737
1738#[cfg(test)]
1739mod tests {
1740 use super::*;
1741
1742 #[derive(Clone, Debug)]
1743 struct NlpUnconstrained {
1744 num_vars: usize,
1745 init_point: Vec<Number>,
1746 lower: Vec<Number>,
1747 upper: Vec<Number>,
1748 }
1749
1750 impl BasicProblem for NlpUnconstrained {
1751 fn num_variables(&self) -> usize {
1752 self.num_vars
1753 }
1754 fn bounds(&self, x_l: &mut [Number], x_u: &mut [Number]) -> bool {
1755 x_l.copy_from_slice(&self.lower);
1756 x_u.copy_from_slice(&self.upper);
1757 true
1758 }
1759 fn initial_point(&self, x: &mut [Number]) -> bool {
1760 x.copy_from_slice(&self.init_point);
1761 true
1762 }
1763 fn objective(&self, _: &[Number], _: bool, _: &mut Number) -> bool {
1764 true
1765 }
1766 fn objective_grad(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
1767 true
1768 }
1769 }
1770
1771 /// Test validation of new unconstrained ipopt problems.
1772 #[test]
1773 fn invalid_construction_unconstrained_test() {
1774 // Initialize a valid nlp.
1775 let nlp = NlpUnconstrained {
1776 num_vars: 2,
1777 init_point: vec![0.0, 0.0],
1778 lower: vec![-1e20; 2],
1779 upper: vec![1e20; 2],
1780 };
1781
1782 assert!(Ipopt::new_unconstrained(nlp.clone()).is_ok());
1783
1784 // Invalid number of variables
1785 let nlp4 = NlpUnconstrained {
1786 num_vars: 0,
1787 ..nlp.clone()
1788 };
1789 assert_eq!(
1790 Ipopt::new_unconstrained(nlp4).unwrap_err(),
1791 CreateError::NoOptimizationVariablesSpecified
1792 );
1793 }
1794
1795 #[derive(Debug, Clone)]
1796 struct NlpConstrained {
1797 num_vars: usize,
1798 num_constraints: usize,
1799 num_constraint_jac_nnz: usize,
1800 num_hess_nnz: usize,
1801 constraint_lower: Vec<Number>,
1802 constraint_upper: Vec<Number>,
1803 init_point: Vec<Number>,
1804 lower: Vec<Number>,
1805 upper: Vec<Number>,
1806 }
1807
1808 impl BasicProblem for NlpConstrained {
1809 fn num_variables(&self) -> usize {
1810 self.num_vars
1811 }
1812 fn bounds(&self, x_l: &mut [Number], x_u: &mut [Number]) -> bool {
1813 x_l.copy_from_slice(&self.lower);
1814 x_u.copy_from_slice(&self.upper);
1815 true
1816 }
1817 fn initial_point(&self, x: &mut [Number]) -> bool {
1818 x.copy_from_slice(&self.init_point.clone());
1819 true
1820 }
1821 fn objective(&self, _: &[Number], _: bool, _: &mut Number) -> bool {
1822 true
1823 }
1824 fn objective_grad(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
1825 true
1826 }
1827 }
1828
1829 impl ConstrainedProblem for NlpConstrained {
1830 fn num_constraints(&self) -> usize {
1831 self.num_constraints
1832 }
1833 fn num_constraint_jacobian_non_zeros(&self) -> usize {
1834 self.num_constraint_jac_nnz
1835 }
1836
1837 fn constraint_bounds(&self, g_l: &mut [Number], g_u: &mut [Number]) -> bool {
1838 g_l.copy_from_slice(&self.constraint_lower);
1839 g_u.copy_from_slice(&self.constraint_upper);
1840 true
1841 }
1842 fn constraint(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
1843 true
1844 }
1845 fn constraint_jacobian_indices(&self, _: &mut [Index], _: &mut [Index]) -> bool {
1846 true
1847 }
1848 fn constraint_jacobian_values(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
1849 true
1850 }
1851
1852 // Hessian Implementation
1853 fn num_hessian_non_zeros(&self) -> usize {
1854 self.num_hess_nnz
1855 }
1856 fn hessian_indices(&self, _: &mut [Index], _: &mut [Index]) -> bool {
1857 true
1858 }
1859 fn hessian_values(
1860 &self,
1861 _: &[Number],
1862 _: bool,
1863 _: Number,
1864 _: &[Number],
1865 _: &mut [Number],
1866 ) -> bool {
1867 true
1868 }
1869 }
1870
1871 /// Test validation of new constrained ipopt problems.
1872 #[test]
1873 fn invalid_construction_constrained_test() {
1874 // Initialize a valid nlp.
1875 let nlp = NlpConstrained {
1876 num_vars: 4,
1877 num_constraints: 2,
1878 num_constraint_jac_nnz: 8,
1879 num_hess_nnz: 10,
1880 constraint_lower: vec![25.0, 40.0],
1881 constraint_upper: vec![2.0e19, 40.0],
1882 init_point: vec![1.0, 5.0, 5.0, 1.0],
1883 lower: vec![1.0; 4],
1884 upper: vec![5.0; 4],
1885 };
1886
1887 assert!(Ipopt::new(nlp.clone()).is_ok());
1888
1889 // Invalid number of variables
1890 let nlp2 = NlpConstrained {
1891 num_vars: 0,
1892 ..nlp.clone()
1893 };
1894 assert_eq!(
1895 Ipopt::new(nlp2).unwrap_err(),
1896 CreateError::NoOptimizationVariablesSpecified
1897 );
1898
1899 // Invalid constraint jacobian
1900 let nlp3 = NlpConstrained {
1901 num_constraint_jac_nnz: 0,
1902 ..nlp.clone()
1903 };
1904 assert_eq!(
1905 Ipopt::new(nlp3).unwrap_err(),
1906 CreateError::InvalidConstraintJacobian {
1907 num_constraints: 2,
1908 num_constraint_jac_nnz: 0
1909 }
1910 );
1911
1912 let nlp4 = NlpConstrained {
1913 num_constraints: 0,
1914 constraint_lower: vec![],
1915 constraint_upper: vec![],
1916 ..nlp.clone()
1917 };
1918 assert_eq!(
1919 Ipopt::new(nlp4).unwrap_err(),
1920 CreateError::InvalidConstraintJacobian {
1921 num_constraints: 0,
1922 num_constraint_jac_nnz: 8
1923 }
1924 );
1925 }
1926
1927 /// Test validity of solver data before the first solve.
1928 /// Here we ensure that the necessary arrays are allocated at the time of creation.
1929 #[test]
1930 fn no_solve_validity_test() {
1931 // Initialize a valid nlp.
1932 let nlp = NlpConstrained {
1933 num_vars: 4,
1934 num_constraints: 2,
1935 num_constraint_jac_nnz: 8,
1936 num_hess_nnz: 10,
1937 constraint_lower: vec![25.0, 40.0],
1938 constraint_upper: vec![2.0e19, 40.0],
1939 init_point: vec![1.0, 5.0, 5.0, 1.0],
1940 lower: vec![1.0; 4],
1941 upper: vec![5.0; 4],
1942 };
1943
1944 let solver = Ipopt::new(nlp.clone()).expect("Failed to create Ipopt solver");
1945 let SolverData {
1946 solution:
1947 Solution {
1948 primal_variables,
1949 constraint_multipliers,
1950 lower_bound_multipliers,
1951 upper_bound_multipliers,
1952 },
1953 ..
1954 } = solver.solver_data();
1955
1956 // We expect that the solver data is initialized to what is provided by the initial_*
1957 // functions. Although they will be called again during the actual solve, the data
1958 // returned by solver_data must always be valid.
1959 assert_eq!(primal_variables, nlp.init_point.as_slice());
1960 assert_eq!(constraint_multipliers, vec![0.0; 2].as_slice());
1961 assert_eq!(lower_bound_multipliers, vec![0.0; 4].as_slice());
1962 assert_eq!(upper_bound_multipliers, vec![0.0; 4].as_slice());
1963 }
1964}