Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splits MOI solve into init and solve #400

Closed

Conversation

ValentinKaisermayer
Copy link
Contributor

@ValentinKaisermayer ValentinKaisermayer commented Oct 20, 2022

using ModelingToolkit
import Optimization
import OptimizationMOI
import Ipopt

@variables x[1:2] = [0.0, 0.0] [bounds = (0.0, Inf)]
@parameters a = 1.0
@parameters b = 1.0
@named sys = OptimizationSystem(
    x[1] + x[2], [x...], [a, b]; 
    constraints=[1.0  a * x[1]^2 * b + x[2]^2, x[1]^2 + x[2]^2  2.0]
)
prob = OptimizationProblem(sys, [x[1] => 2.0, x[2] => 0.0], []; grad=true, hess=true)
cache = Optimization.init(prob)
cache = OptimizationMOI.set_p(cache, [1.0, 100.0])
Optimization.solve!(cache, Ipopt.Optimizer(); print_level=0)

J::JT
H::HT
cons_H::Vector{CHT}
lcons::Vector{T}
ucons::Vector{T}
sense::S
prob::PR # TODO: this is dangerous since the cache and problem might get out of sync after reinit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is not good to have the problem in here but it is needed for the construction of the solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's store the cache in the solution object as you suggested below, to make full use of the memory efficiency the abstraction offers? (again this would have to be done in SciMLBase)

@@ -336,12 +372,19 @@ function SciMLBase.__solve(prob::OptimizationProblem,
minimum = NaN
opt_ret = :Default
end
return SciMLBase.build_solution(prob,
return SciMLBase.build_solution(cache.prob,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a specific interface for optimization solutions is needed. From the problem only p and observed will be used at any point, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be even better is to avoid all those nested field access in the solution interfaces, like A.prob.p and use something like get_p(A), so that anything that implements this interface can be used, e.g. a cache.

@codecov
Copy link

codecov bot commented Oct 20, 2022

Codecov Report

Merging #400 (9b669f7) into master (11f16bc) will decrease coverage by 0.37%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##           master     #400      +/-   ##
==========================================
- Coverage   82.02%   81.64%   -0.38%     
==========================================
  Files           9        9              
  Lines         267      267              
==========================================
- Hits          219      218       -1     
- Misses         48       49       +1     
Impacted Files Coverage Δ
src/function/finitediff.jl 90.69% <0.00%> (-2.33%) ⬇️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@ValentinKaisermayer
Copy link
Contributor Author

#352

struct MOIOptimizationProblem{T, F <: OptimizationFunction, uType, P,
JT <: DenseOrSparse{T}, HT <: DenseOrSparse{T},
CHT <: DenseOrSparse{T}} <:
struct MOIOptimizationCache{T, F <: OptimizationFunction, uType, P, LB, UB, I,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be a more general, OptimizationCache right? (and moved to SciMLBase) and then, exactly like how you have done here, we'd define init for it for each backend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would such an OptimizationCache hold? Right now it is kind of big. It holds the problem, which holds the function which hold the system. Convinient but a nightmare in terms of interface.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all the fields here, except remove the prob field for OptimizationProblem from this (since as I commented below we can also make the build_solution work directly with OptimizationCache then as you had suggested)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be a more general, OptimizationCache right? (and moved to SciMLBase) and then, exactly like how you have done here, we'd define init for it for each backend.

Why not stick the OptimizationCache into Optimization.jl?

@ChrisRackauckas pointed out that a per-solver cache maybe needed in #352. I'm not quite sure yet.

Since reinit(cache; p) or something like set_p(cache, p) is the only real reason for a cache, it has to be possible to change the parameters. All nonlinear sovers use prob.f.f and prob.f.cons which simply take p as an argument - easy! But what if the actual box constraints bounds are defined via parameters?
I would like to implement an interface for linear solvers as well. The only way I see to implement this, is directly via the objective and constraints expressions and using Symbolics.jl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now the OptimizationSolution does not support access of parameters, however it would be nice to provide this. Than we need paramsyms.

For the MOI case I think you added the expr and cons_expr to the OptimizationFunction, at least if the parameters are not replaced it might be usefull for the linear solver interface.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not stick the OptimizationCache into Optimization.jl?

Because afaik we keep all such structs in SciMLBase, so it avoids any recursive dependencies for these. For example in https://github.com/SciML/SciMLBase.jl/pull/286/files if instead of C you want to use OptimizationCache then it would have to be there since SciMLBase can't take Optimization as a dependency.

@ChrisRackauckas pointed out that a per-solver cache may be needed in #352. I'm not quite sure yet.

Yeah me too, from what I can see, OptimizationCache seems like a union of OptimizationFunction and OptimizationProblem, such that it can store the complete state of the problem, hence it would end up needing all of the possible fields and we just initialize everything to nothing and fill it up as relevant for the backend.

Since reinit(cache; p) or something like set_p(cache, p) is the only real reason for a cache, it has to be possible to change the parameters. All nonlinear sovers use prob.f.f and prob.f.cons which simply take p as an argument - easy! But what if the actual box constraints bounds are defined via parameters?

Sorry, not sure I understood this. Why would that not be possible? 🤔

Regarding the expressions, yeah that would go into it as well, and replace any constant values interpolated in there if needed (but this can be punted for later depending on how much work it ends up being)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because afaik we keep all such structs in SciMLBase, so it avoids any recursive dependencies for these.

LinearCache would be a counterexample but i can see that it might be beneficial.

If ub or lb are defined in terms of parameters of an MTK model, only the system knows how to compute this.

@parameters c
@variables x [bounds = (c, Inf)]

It is stored in the bounds metadata of each variable. But accessing the metadata is only possible easily via MTK. We can of course forbid this in MTK.

J::JT
H::HT
cons_H::Vector{CHT}
lcons::Vector{T}
ucons::Vector{T}
sense::S
prob::PR # TODO: this is dangerous since the cache and problem might get out of sync after reinit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's store the cache in the solution object as you suggested below, to make full use of the memory efficiency the abstraction offers? (again this would have to be done in SciMLBase)

@Vaibhavdixit02
Copy link
Member

This PR looks good, but I got a feeling that a lot of it can be just copy pasted into SciMLBase and then used here. I would be okay with merging it here and taking up the move if that's what you'd prefer @ValentinKaisermayer. But I think it might be more impactful to move the changes into SciMLBase and then follow up here?

@ValentinKaisermayer
Copy link
Contributor Author

closing this in favor of #381

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants