Skip to content

Commit e9e35f6

Browse files
SebastianM-CVaibhavdixit02ValentinKaisermayerbaggepinnenodow
committed
feat: add initial version of OptimizationIpopt
This pacakge directly uses the C interface in Ipopt.jl. The implementation is based on OptimizationMOI, but it also adds Ipopt specific elements, such as the callback handling. Co-authored-by: Vaibhav Dixit <[email protected]> Co-authored-by: Valentin Kaisermayer <[email protected]> Co-authored-by: Fredrik Bagge Carlson <[email protected]> Co-authored-by: Oscar Dowson <[email protected]>
1 parent ca06e9a commit e9e35f6

File tree

6 files changed

+807
-0
lines changed

6 files changed

+807
-0
lines changed

lib/OptimizationIpopt/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Sebastian Micluța-Câmpeanu <[email protected]> and contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

lib/OptimizationIpopt/Project.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name = "OptimizationIpopt"
2+
uuid = "43fad042-7963-4b32-ab19-e2a4f9a67124"
3+
authors = ["Sebastian Micluța-Câmpeanu <[email protected]> and contributors"]
4+
version = "0.1.0"
5+
6+
[deps]
7+
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
8+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
9+
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
10+
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
11+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
12+
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
13+
14+
[compat]
15+
Ipopt = "1.10.3"
16+
LinearAlgebra = "1.11.0"
17+
Optimization = "4.3.0"
18+
SciMLBase = "2.90.0"
19+
SparseArrays = "1.11.0"
20+
SymbolicIndexingInterface = "0.3.40"
21+
julia = "1.10"
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
module OptimizationIpopt
2+
3+
using Optimization
4+
using Ipopt
5+
using LinearAlgebra
6+
using SparseArrays
7+
using SciMLBase
8+
using SymbolicIndexingInterface
9+
10+
export IpoptOptimizer
11+
12+
const DenseOrSparse{T} = Union{Matrix{T}, SparseMatrixCSC{T}}
13+
14+
struct IpoptOptimizer end
15+
16+
function SciMLBase.supports_opt_cache_interface(alg::IpoptOptimizer)
17+
true
18+
end
19+
20+
function SciMLBase.requiresgradient(opt::IpoptOptimizer)
21+
true
22+
end
23+
function SciMLBase.requireshessian(opt::IpoptOptimizer)
24+
true
25+
end
26+
function SciMLBase.requiresconsjac(opt::IpoptOptimizer)
27+
true
28+
end
29+
function SciMLBase.requiresconshess(opt::IpoptOptimizer)
30+
true
31+
end
32+
33+
function SciMLBase.allowsbounds(opt::IpoptOptimizer)
34+
true
35+
end
36+
function SciMLBase.allowsconstraints(opt::IpoptOptimizer)
37+
true
38+
end
39+
40+
include("cache.jl")
41+
include("callback.jl")
42+
43+
function __map_optimizer_args(cache,
44+
opt::IpoptOptimizer;
45+
maxiters::Union{Number, Nothing} = nothing,
46+
maxtime::Union{Number, Nothing} = nothing,
47+
abstol::Union{Number, Nothing} = nothing,
48+
reltol::Union{Number, Nothing} = nothing,
49+
hessian_approximation = "exact",
50+
verbose = false,
51+
progress = false,
52+
callback = nothing,
53+
kwargs...)
54+
jacobian_sparsity = jacobian_structure(cache)
55+
hessian_sparsity = hessian_lagrangian_structure(cache)
56+
57+
eval_f(x) = eval_objective(cache, x)
58+
eval_grad_f(x, grad_f) = eval_objective_gradient(cache, grad_f, x)
59+
eval_g(x, g) = eval_constraint(cache, g, x)
60+
function eval_jac_g(x, rows, cols, values)
61+
if values === nothing
62+
for i in 1:length(jacobian_sparsity)
63+
rows[i], cols[i] = jacobian_sparsity[i]
64+
end
65+
else
66+
eval_constraint_jacobian(cache, values, x)
67+
end
68+
return
69+
end
70+
function eval_h(x, rows, cols, obj_factor, lambda, values)
71+
if values === nothing
72+
for i in 1:length(hessian_sparsity)
73+
rows[i], cols[i] = hessian_sparsity[i]
74+
end
75+
else
76+
eval_hessian_lagrangian(cache, values, x, obj_factor, lambda)
77+
end
78+
return
79+
end
80+
81+
lb = isnothing(cache.lb) ? fill(-Inf, cache.n) : cache.lb
82+
ub = isnothing(cache.ub) ? fill(Inf, cache.n) : cache.ub
83+
84+
prob = Ipopt.CreateIpoptProblem(
85+
cache.n,
86+
lb,
87+
ub,
88+
cache.num_cons,
89+
cache.lcons,
90+
cache.ucons,
91+
length(jacobian_structure(cache)),
92+
length(hessian_lagrangian_structure(cache)),
93+
eval_f,
94+
eval_g,
95+
eval_grad_f,
96+
eval_jac_g,
97+
eval_h
98+
)
99+
progress_callback = IpoptProgressLogger(cache.progress, cache, prob)
100+
intermediate = (args...) -> progress_callback(args...)
101+
Ipopt.SetIntermediateCallback(prob, intermediate)
102+
103+
if !isnothing(maxiters)
104+
Ipopt.AddIpoptIntOption(prob, "max_iter", maxiters)
105+
end
106+
if !isnothing(maxtime)
107+
Ipopt.AddIpoptNumOption(prob, "max_cpu_time", maxtime)
108+
end
109+
if !isnothing(abstol)
110+
Ipopt.AddIpoptNumOption(prob, "tol", abstol)
111+
end
112+
if verbose isa Bool
113+
Ipopt.AddIpoptIntOption(prob, "print_level", verbose * 5)
114+
else
115+
Ipopt.AddIpoptIntOption(prob, "print_level", verbose)
116+
end
117+
Ipopt.AddIpoptStrOption(prob, "hessian_approximation", hessian_approximation)
118+
119+
return prob
120+
end
121+
122+
function map_retcode(solvestat)
123+
status = Ipopt.ApplicationReturnStatus(solvestat)
124+
if status in [
125+
Ipopt.Solve_Succeeded,
126+
Ipopt.Solved_To_Acceptable_Level,
127+
Ipopt.User_Requested_Stop,
128+
Ipopt.Feasible_Point_Found
129+
]
130+
return ReturnCode.Success
131+
elseif status in [
132+
Ipopt.Infeasible_Problem_Detected,
133+
Ipopt.Search_Direction_Becomes_Too_Small,
134+
Ipopt.Diverging_Iterates
135+
]
136+
return ReturnCode.Infeasible
137+
elseif status == Ipopt.Maximum_Iterations_Exceeded
138+
return ReturnCode.MaxIters
139+
elseif status in [Ipopt.Maximum_CpuTime_Exceeded
140+
Ipopt.Maximum_WallTime_Exceeded]
141+
return ReturnCode.MaxTime
142+
else
143+
return ReturnCode.Failure
144+
end
145+
end
146+
147+
function SciMLBase.__solve(cache::IpoptCache)
148+
maxiters = Optimization._check_and_convert_maxiters(cache.solver_args.maxiters)
149+
maxtime = Optimization._check_and_convert_maxtime(cache.solver_args.maxtime)
150+
151+
opt_setup = __map_optimizer_args(cache,
152+
cache.opt;
153+
abstol = cache.solver_args.abstol,
154+
reltol = cache.solver_args.reltol,
155+
maxiters = maxiters,
156+
maxtime = maxtime,
157+
cache.solver_args...)
158+
159+
opt_setup.x .= cache.reinit_cache.u0
160+
161+
start_time = time()
162+
status = Ipopt.IpoptSolve(opt_setup)
163+
164+
opt_ret = map_retcode(status)
165+
166+
if cache.progress
167+
# Set progressbar to 1 to finish it
168+
Base.@logmsg(Base.LogLevel(-1), "", progress=1, _id=:OptimizationIpopt)
169+
end
170+
171+
minimum = opt_setup.obj_val
172+
minimizer = opt_setup.x
173+
174+
stats = Optimization.OptimizationStats(; time = time() - start_time,
175+
iterations = cache.iterations, fevals = cache.f_calls, gevals = cache.f_grad_calls)
176+
177+
finalize(opt_setup)
178+
179+
return SciMLBase.build_solution(cache,
180+
cache.opt,
181+
minimizer,
182+
minimum;
183+
original = opt_setup,
184+
retcode = opt_ret,
185+
stats = stats)
186+
end
187+
188+
function SciMLBase.__init(prob::OptimizationProblem,
189+
opt::IpoptOptimizer;
190+
maxiters::Union{Number, Nothing} = nothing,
191+
maxtime::Union{Number, Nothing} = nothing,
192+
abstol::Union{Number, Nothing} = nothing,
193+
reltol::Union{Number, Nothing} = nothing,
194+
mtkize = false,
195+
kwargs...)
196+
cache = IpoptCache(prob, opt;
197+
maxiters,
198+
maxtime,
199+
abstol,
200+
reltol,
201+
mtkize,
202+
kwargs...
203+
)
204+
cache.reinit_cache.u0 .= prob.u0
205+
206+
return cache
207+
end
208+
209+
end # OptimizationIpopt

0 commit comments

Comments
 (0)