Skip to content

Commit 8f8e4ff

Browse files
Merge pull request #915 from SebastianM-C/smc/ipopt
Add initial version of OptimizationIpopt
2 parents 5c2a226 + f410ed5 commit 8f8e4ff

File tree

10 files changed

+1640
-1
lines changed

10 files changed

+1640
-1
lines changed

.github/workflows/CI.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
- OptimizationCMAEvolutionStrategy
2424
- OptimizationEvolutionary
2525
- OptimizationGCMAES
26+
- OptimizationIpopt
2627
- OptimizationManopt
2728
- OptimizationMetaheuristics
2829
- OptimizationMOI
@@ -65,7 +66,7 @@ jobs:
6566
GROUP: ${{ matrix.group }}
6667
- uses: julia-actions/julia-processcoverage@v1
6768
with:
68-
directories: src,lib/OptimizationBBO/src,lib/OptimizationCMAEvolutionStrategy/src,lib/OptimizationEvolutionary/src,lib/OptimizationGCMAES/src,lib/OptimizationManopt/src,lib/OptimizationMOI/src,lib/OptimizationMetaheuristics/src,lib/OptimizationMultistartOptimization/src,lib/OptimizationNLopt/src,lib/OptimizationNOMAD/src,lib/OptimizationOptimJL/src,lib/OptimizationOptimisers/src,lib/OptimizationPolyalgorithms/src,lib/OptimizationQuadDIRECT/src,lib/OptimizationSpeedMapping/src
69+
directories: src,lib/OptimizationBBO/src,lib/OptimizationCMAEvolutionStrategy/src,lib/OptimizationEvolutionary/src,lib/OptimizationGCMAES/src,lib/OptimizationIpopt/src,lib/OptimizationManopt/src,lib/OptimizationMOI/src,lib/OptimizationMetaheuristics/src,lib/OptimizationMultistartOptimization/src,lib/OptimizationNLopt/src,lib/OptimizationNOMAD/src,lib/OptimizationOptimJL/src,lib/OptimizationOptimisers/src,lib/OptimizationPolyalgorithms/src,lib/OptimizationQuadDIRECT/src,lib/OptimizationSpeedMapping/src
6970
- uses: codecov/codecov-action@v5
7071
with:
7172
file: lcov.info

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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.10.0"
17+
ModelingToolkit = "10.20"
18+
Optimization = "4.3.0"
19+
SciMLBase = "2.90.0"
20+
SparseArrays = "1.10.0"
21+
SymbolicIndexingInterface = "0.3.40"
22+
Zygote = "0.7"
23+
julia = "1.10"
24+
25+
[extras]
26+
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
27+
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
28+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
29+
ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267"
30+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
31+
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
32+
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
33+
34+
[targets]
35+
test = ["Aqua", "ModelingToolkit", "Random", "ReverseDiff", "Test", "Symbolics", "Zygote"]
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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(reltol)
110+
Ipopt.AddIpoptNumOption(prob, "tol", reltol)
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+
for kw in pairs(kwargs)
120+
if kw[2] isa Int
121+
Ipopt.AddIpoptIntOption(prob, string(kw[1]), kw[2])
122+
elseif kw[2] isa Float64
123+
Ipopt.AddIpoptNumOption(prob, string(kw[1]), kw[2])
124+
elseif kw[2] isa String
125+
Ipopt.AddIpoptStrOption(prob, string(kw[1]), kw[2])
126+
else
127+
error("Keyword argument type $(typeof(kw[2])) not recognized")
128+
end
129+
end
130+
131+
return prob
132+
end
133+
134+
function map_retcode(solvestat)
135+
status = Ipopt.ApplicationReturnStatus(solvestat)
136+
if status in [
137+
Ipopt.Solve_Succeeded,
138+
Ipopt.Solved_To_Acceptable_Level,
139+
Ipopt.User_Requested_Stop,
140+
Ipopt.Feasible_Point_Found
141+
]
142+
return ReturnCode.Success
143+
elseif status in [
144+
Ipopt.Infeasible_Problem_Detected,
145+
Ipopt.Search_Direction_Becomes_Too_Small,
146+
Ipopt.Diverging_Iterates
147+
]
148+
return ReturnCode.Infeasible
149+
elseif status == Ipopt.Maximum_Iterations_Exceeded
150+
return ReturnCode.MaxIters
151+
elseif status in [Ipopt.Maximum_CpuTime_Exceeded
152+
Ipopt.Maximum_WallTime_Exceeded]
153+
return ReturnCode.MaxTime
154+
else
155+
return ReturnCode.Failure
156+
end
157+
end
158+
159+
function SciMLBase.__solve(cache::IpoptCache)
160+
maxiters = Optimization._check_and_convert_maxiters(cache.solver_args.maxiters)
161+
maxtime = Optimization._check_and_convert_maxtime(cache.solver_args.maxtime)
162+
163+
opt_setup = __map_optimizer_args(cache,
164+
cache.opt;
165+
abstol = cache.solver_args.abstol,
166+
reltol = cache.solver_args.reltol,
167+
maxiters = maxiters,
168+
maxtime = maxtime,
169+
cache.solver_args...)
170+
171+
opt_setup.x .= cache.reinit_cache.u0
172+
173+
start_time = time()
174+
status = Ipopt.IpoptSolve(opt_setup)
175+
176+
opt_ret = map_retcode(status)
177+
178+
if cache.progress
179+
# Set progressbar to 1 to finish it
180+
Base.@logmsg(Base.LogLevel(-1), "", progress=1, _id=:OptimizationIpopt)
181+
end
182+
183+
minimum = opt_setup.obj_val
184+
minimizer = opt_setup.x
185+
186+
stats = Optimization.OptimizationStats(; time = time() - start_time,
187+
iterations = cache.iterations, fevals = cache.f_calls, gevals = cache.f_grad_calls)
188+
189+
finalize(opt_setup)
190+
191+
return SciMLBase.build_solution(cache,
192+
cache.opt,
193+
minimizer,
194+
minimum;
195+
original = opt_setup,
196+
retcode = opt_ret,
197+
stats = stats)
198+
end
199+
200+
function SciMLBase.__init(prob::OptimizationProblem,
201+
opt::IpoptOptimizer;
202+
maxiters::Union{Number, Nothing} = nothing,
203+
maxtime::Union{Number, Nothing} = nothing,
204+
abstol::Union{Number, Nothing} = nothing,
205+
reltol::Union{Number, Nothing} = nothing,
206+
kwargs...)
207+
cache = IpoptCache(prob, opt;
208+
maxiters,
209+
maxtime,
210+
abstol,
211+
reltol,
212+
kwargs...
213+
)
214+
cache.reinit_cache.u0 .= prob.u0
215+
216+
return cache
217+
end
218+
219+
end # OptimizationIpopt

0 commit comments

Comments
 (0)