Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/OptimizationBase/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "OptimizationBase"
uuid = "bca83a33-5cc9-4baa-983d-23429ab6bcbb"
version = "5.1.3"
version = "5.2.0"
authors = ["Vaibhav Dixit <vaibhavyashdixit@gmail.com> and contributors"]

[deps]
Expand Down
11 changes: 9 additions & 2 deletions lib/OptimizationBase/src/cache.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ end

struct OptimizationCache{
O, IIP, F <: SciMLBase.AbstractOptimizationFunction{IIP},
RC, LB, UB, LC, UC, S, P, C, M, V,
RC, LB, UB, LC, UC, S, P, C, M, V, PR,
} <:
SciMLBase.AbstractOptimizationCache
opt::O
Expand All @@ -24,6 +24,13 @@ struct OptimizationCache{
analysis_results::AnalysisResults
solver_args::NamedTuple
verbose::V
# Original `OptimizationProblem` the cache was built from. Unlike `f`, which
# holds the instantiated objective/constraints with `p` baked into closures,
# `prob` keeps the user's untouched problem so the original objective,
# constraints, parameters, and bounds remain recoverable from the solution.
# Not updated by `reinit!` (which only mutates `reinit_cache`); use
# `cache.u0`/`cache.p` for the current state.
prob::PR
end

function OptimizationCache(
Expand Down Expand Up @@ -111,7 +118,7 @@ function OptimizationCache(
prob.ucons, prob.sense,
progress, callback, manifold, AnalysisResults(obj_res, cons_res),
merge((; maxiters, maxtime, abstol, reltol), NamedTuple(kwargs)),
processed_verbose
processed_verbose, prob
)
end

Expand Down
55 changes: 55 additions & 0 deletions lib/OptimizationBase/test/solve_internals_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,24 @@ function OptimizationBase.__solve(
)
end

# Mock solver with has_init = true that also allows constraints, so a constrained
# problem routes through the real OptimizationCache (not DefaultOptimizationCache).
struct MockSolverWithInitCons end
SciMLBase.has_init(::MockSolverWithInitCons) = true
OptimizationBase.allowsconstraints(::MockSolverWithInitCons) = true

function OptimizationBase.__solve(
cache::OptimizationBase.OptimizationCache{MockSolverWithInitCons}
)
stats = SciMLBase.OptimizationStats(; iterations = 1, time = 0.0, fevals = 1)
u = cache.reinit_cache.u0
return SciMLBase.build_solution(
cache, cache.opt, u, cache.f.f(u, cache.reinit_cache.p);
retcode = ReturnCode.Success,
stats = stats
)
end

# Mock solver that allows constraints (for _check_opt_alg tests)
struct MockAlgWithCons end
OptimizationBase.allowsconstraints(::MockAlgWithCons) = true
Expand Down Expand Up @@ -329,3 +347,40 @@ end
@test SciMLBase.successful_retcode(sol)
@test _mock_ncalls[] == 1
end

# ============================================================
# OptimizationCache retains the original OptimizationProblem (issue #1191)
# ============================================================

@testset "cache retains the original problem" begin
objfun = (x, p) -> sum(abs2, x) + sum(p)
# Raw, un-instantiated constraint: takes p explicitly and is not p-baked.
consfun = (res, x, p) -> (res .= x[1]^2 + x[2]^2)
f = OptimizationFunction(objfun, SciMLBase.NoAD(); cons = consfun)
p0 = [3.0, 5.0]
prob = OptimizationProblem(f, [0.5, 0.5], p0; lcons = [0.0], ucons = [2.0])

# `init` does not remake the problem, so the cache holds it by identity.
cache = init(prob, MockSolverWithInitCons())
@test cache.prob === prob

sol = solve!(cache)
# The original problem is recoverable from the solution via the cache.
@test sol.cache.prob === prob
@test sol.cache.prob.p === p0
@test sol.cache.prob.f.f === objfun
# The original constraint is the raw user function, not the p-baked closure
# that the instantiated cache function carries.
@test sol.cache.prob.f.cons === consfun
@test sol.cache.f.cons !== consfun
@test sol.cache.prob.lcons == [0.0]
@test sol.cache.prob.ucons == [2.0]

# The end-to-end `solve` path remakes the problem, but the stored problem is
# still an OptimizationProblem carrying the original objective and parameters.
sol2 = solve(prob, MockSolverWithInitCons())
@test sol2.cache.prob isa OptimizationProblem
@test sol2.cache.prob.f.f === objfun
@test sol2.cache.prob.f.cons === consfun
@test sol2.cache.prob.p == p0
end
4 changes: 2 additions & 2 deletions lib/OptimizationPRIMA/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "OptimizationPRIMA"
uuid = "72f8369c-a2ea-4298-9126-56167ce9cbc2"
authors = ["Vaibhav Dixit <vaibhavyashdixit@gmail.com> and contributors"]
version = "0.3.8"
version = "0.3.9"
[deps]
OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb"
PRIMA = "0a7d04aa-8ac2-47b3-b7a7-9dbd6ad661ed"
Expand All @@ -16,7 +16,7 @@ ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"

[compat]
julia = "1.10"
OptimizationBase = "5.1"
OptimizationBase = "5.2"
PRIMA = "0.2.0"
SciMLBase = "2.122.1, 3"
Reexport = "1"
Expand Down
2 changes: 1 addition & 1 deletion lib/OptimizationPRIMA/src/OptimizationPRIMA.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function OptimizationBase.OptimizationCache(
progress, callback, nothing,
OptimizationBase.OptimizationBase.AnalysisResults(nothing, nothing),
merge((; maxiters, maxtime, abstol, reltol), NamedTuple(kwargs)),
processed_verbose
processed_verbose, prob
)
end

Expand Down
Loading