scirs2_optimize/constrained/
mod.rs

1//! Constrained optimization algorithms
2//!
3//! This module provides methods for constrained optimization of scalar
4//! functions of one or more variables.
5//!
6//! ## Example
7//!
8//! ```no_run
9//! use ndarray::{array, Array1};
10//! use scirs2_optimize::constrained::{minimize_constrained, Method, Constraint};
11//!
12//! // Define a simple function to minimize
13//! fn objective(x: &[f64]) -> f64 {
14//!     (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2)
15//! }
16//!
17//! // Define a constraint: x[0] + x[1] <= 3
18//! fn constraint(x: &[f64]) -> f64 {
19//!     3.0 - x[0] - x[1]  // Should be >= 0
20//! }
21//!
22//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! // Minimize the function starting at [0.0, 0.0]
24//! let initial_point = array![0.0, 0.0];
25//! let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
26//!
27//! let result = minimize_constrained(
28//!     objective,
29//!     &initial_point,
30//!     &constraints,
31//!     Method::SLSQP,
32//!     None
33//! )?;
34//!
35//! // The constrained minimum should be at [0.5, 2.5]
36//! # Ok(())
37//! # }
38//! ```
39//!
40//! Note: This function requires LAPACK libraries to be linked for certain optimization methods.
41
42use crate::error::OptimizeResult;
43use crate::result::OptimizeResults;
44use ndarray::{Array1, ArrayBase, Data, Ix1};
45use std::fmt;
46
47// Re-export optimization methods
48pub mod augmented_lagrangian;
49pub mod cobyla;
50pub mod interior_point;
51pub mod slsqp;
52pub mod trust_constr;
53
54// Re-export main functions
55pub use augmented_lagrangian::{
56    minimize_augmented_lagrangian, minimize_equality_constrained, minimize_inequality_constrained,
57    AugmentedLagrangianOptions, AugmentedLagrangianResult,
58};
59pub use cobyla::minimize_cobyla;
60pub use interior_point::{
61    minimize_interior_point, minimize_interior_point_constrained, InteriorPointOptions,
62    InteriorPointResult,
63};
64pub use slsqp::minimize_slsqp;
65pub use trust_constr::minimize_trust_constr;
66
67#[cfg(test)]
68mod tests;
69
70/// Type alias for constraint functions that take a slice of f64 and return f64
71pub type ConstraintFn = fn(&[f64]) -> f64;
72
73/// Optimization methods for constrained minimization.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum Method {
76    /// Sequential Least SQuares Programming
77    SLSQP,
78
79    /// Trust-region constrained algorithm
80    TrustConstr,
81
82    /// Linear programming using the simplex algorithm
83    COBYLA,
84
85    /// Interior point method
86    InteriorPoint,
87
88    /// Augmented Lagrangian method
89    AugmentedLagrangian,
90}
91
92impl fmt::Display for Method {
93    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94        match self {
95            Method::SLSQP => write!(f, "SLSQP"),
96            Method::TrustConstr => write!(f, "trust-constr"),
97            Method::COBYLA => write!(f, "COBYLA"),
98            Method::InteriorPoint => write!(f, "interior-point"),
99            Method::AugmentedLagrangian => write!(f, "augmented-lagrangian"),
100        }
101    }
102}
103
104/// Options for the constrained optimizer.
105#[derive(Debug, Clone)]
106pub struct Options {
107    /// Maximum number of iterations to perform
108    pub maxiter: Option<usize>,
109
110    /// Precision goal for the value in the stopping criterion
111    pub ftol: Option<f64>,
112
113    /// Precision goal for the gradient in the stopping criterion (relative)
114    pub gtol: Option<f64>,
115
116    /// Precision goal for constraint violation
117    pub ctol: Option<f64>,
118
119    /// Step size used for numerical approximation of the jacobian
120    pub eps: Option<f64>,
121
122    /// Whether to print convergence messages
123    pub disp: bool,
124
125    /// Return the optimization result after each iteration
126    pub return_all: bool,
127}
128
129impl Default for Options {
130    fn default() -> Self {
131        Options {
132            maxiter: None,
133            ftol: Some(1e-8),
134            gtol: Some(1e-8),
135            ctol: Some(1e-8),
136            eps: Some(1e-8),
137            disp: false,
138            return_all: false,
139        }
140    }
141}
142
143/// Constraint type for constrained optimization
144pub struct Constraint<F> {
145    /// The constraint function
146    pub fun: F,
147
148    /// The type of constraint (equality or inequality)
149    pub kind: ConstraintKind,
150
151    /// Lower bound for a box constraint
152    pub lb: Option<f64>,
153
154    /// Upper bound for a box constraint
155    pub ub: Option<f64>,
156}
157
158/// The kind of constraint
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum ConstraintKind {
161    /// Equality constraint: fun(x) = 0
162    Equality,
163
164    /// Inequality constraint: fun(x) >= 0
165    Inequality,
166}
167
168impl Constraint<fn(&[f64]) -> f64> {
169    /// Constant for equality constraint
170    pub const EQUALITY: ConstraintKind = ConstraintKind::Equality;
171
172    /// Constant for inequality constraint
173    pub const INEQUALITY: ConstraintKind = ConstraintKind::Inequality;
174
175    /// Create a new constraint
176    pub fn new(fun: fn(&[f64]) -> f64, kind: ConstraintKind) -> Self {
177        Constraint {
178            fun,
179            kind,
180            lb: None,
181            ub: None,
182        }
183    }
184
185    /// Create a new box constraint
186    pub fn new_bounds(lb: Option<f64>, ub: Option<f64>) -> Self {
187        Constraint {
188            fun: |_| 0.0, // Dummy function for box constraints
189            kind: ConstraintKind::Inequality,
190            lb,
191            ub,
192        }
193    }
194}
195
196impl<F> Constraint<F> {
197    /// Check if this is a box constraint
198    pub fn is_bounds(&self) -> bool {
199        self.lb.is_some() || self.ub.is_some()
200    }
201}
202
203/// Minimizes a scalar function of one or more variables with constraints.
204///
205/// # Arguments
206///
207/// * `func` - A function that takes a slice of values and returns a scalar
208/// * `x0` - The initial guess
209/// * `constraints` - Vector of constraints
210/// * `method` - The optimization method to use
211/// * `options` - Options for the optimizer
212///
213/// # Returns
214///
215/// * `OptimizeResults` containing the optimization results
216///
217/// # Example
218///
219/// ```no_run
220/// use ndarray::array;
221/// use scirs2_optimize::constrained::{minimize_constrained, Method, Constraint};
222///
223/// // Function to minimize
224/// fn objective(x: &[f64]) -> f64 {
225///     (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2)
226/// }
227///
228/// // Constraint: x[0] + x[1] <= 3
229/// fn constraint(x: &[f64]) -> f64 {
230///     3.0 - x[0] - x[1]  // Should be >= 0
231/// }
232///
233/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
234/// let initial_point = array![0.0, 0.0];
235/// let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
236///
237/// let result = minimize_constrained(
238///     objective,
239///     &initial_point,
240///     &constraints,
241///     Method::SLSQP,
242///     None
243/// )?;
244/// # Ok(())
245/// # }
246/// ```
247#[allow(dead_code)]
248pub fn minimize_constrained<F, S>(
249    func: F,
250    x0: &ArrayBase<S, Ix1>,
251    constraints: &[Constraint<ConstraintFn>],
252    method: Method,
253    options: Option<Options>,
254) -> OptimizeResult<OptimizeResults<f64>>
255where
256    F: Fn(&[f64]) -> f64 + Clone,
257    S: Data<Elem = f64>,
258{
259    let options = options.unwrap_or_default();
260
261    // Implementation of various methods will go here
262    match method {
263        Method::SLSQP => minimize_slsqp(func, x0, constraints, &options),
264        Method::TrustConstr => minimize_trust_constr(func, x0, constraints, &options),
265        Method::COBYLA => minimize_cobyla(func, x0, constraints, &options),
266        Method::InteriorPoint => {
267            // Convert constraints to interior point format
268            let x0_arr = Array1::from_vec(x0.to_vec());
269
270            // Create interior point options from general options
271            let ip_options = InteriorPointOptions {
272                max_iter: options.maxiter.unwrap_or(100),
273                tol: options.gtol.unwrap_or(1e-8),
274                feas_tol: options.ctol.unwrap_or(1e-8),
275                ..Default::default()
276            };
277
278            // Convert to OptimizeResults format
279            match minimize_interior_point_constrained(func, x0_arr, constraints, Some(ip_options)) {
280                Ok(result) => {
281                    let opt_result = OptimizeResults::<f64> {
282                        x: result.x,
283                        fun: result.fun,
284                        nit: result.nit,
285                        nfev: result.nfev,
286                        success: result.success,
287                        message: result.message,
288                        jac: None,
289                        hess: None,
290                        constr: None,
291                        njev: 0,  // Not tracked by interior point method
292                        nhev: 0,  // Not tracked by interior point method
293                        maxcv: 0, // Not applicable for interior point
294                        status: if result.success { 0 } else { 1 },
295                    };
296                    Ok(opt_result)
297                }
298                Err(e) => Err(e),
299            }
300        }
301        Method::AugmentedLagrangian => {
302            // Convert to augmented Lagrangian method format (simplified for now)
303            Err(crate::error::OptimizeError::NotImplementedError(
304                "Augmented Lagrangian method integration with minimize_constrained not yet implemented"
305                    .to_string(),
306            ))
307        }
308    }
309}