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}