I’d like to solve a JuMP model, use primal_feasibility_report to check which constraints are violated, tweak the variable values until the constraints are all strictly respected, and then compute the new objective value with the new variables. Is it possible to do so?
I can pass a Dict to primal_feasiblity_report with the new values. That’s a bit inconvenient as it seems I have to do scalar by scalar, I can’t pass a vector or a matrix at once, but it works. I see no way to recompute the objective value, though.
If the solver says it’s OPTIMAL or it has a suboptimal solution, then that solution is already deemed feasible.
It’s fairly easy and you don’t have to resort to primal_feasibility_report. You only need to fix the variable’s lower_bound and upper_bound to your candidate solution and call optimize! again, the solver will help you compute the objective value, and you fetch that value through objective_value(model).
But the bigger issue is that, just as you’ve used the word “tinker”—it may not be helpful to tweak the solution returned from the solver (What’s the purpose? You typically get inferior objective values).
That doesn’t work, solvers can’t impose constraints exactly, they only work up to some tolerance. What I need is to compute an objective value for a solution that respects the constraints exactly (up to numerical precision).
Amazing, that works! value(f, objective_function(model)) is what I needed. I also didn’t know about the broadcast syntax for building dictionaries, that’s very convenient, and it works even for symmetric matrices.
using JuMP, LinearAlgebra
import Hypatia
function feasibility_test()
model = Model(Hypatia.Optimizer)
@variable(model, x[1:3])
@constraint(model, x in JuMP.SecondOrderCone())
@variable(model, ρ[1:2,1:2] in JuMP.PSDCone())
@constraint(model, x[1] ≤ 1)
@constraint(model, tr(ρ) == 1)
@objective(model, Max, x[2] + x[3] + ρ[1, 2])
optimize!(model)
display(objective_value(model))
display(primal_feasibility_report(model))
newρ = value(ρ)
newx = value(x)
newx[1] = 1
newx[2:3] .-= 1e-5
d = merge(Dict(ρ .=> newρ), Dict(x .=> newx))
display(primal_feasibility_report(model, d))
f = x -> d[x]
display(value(f, objective_function(model)))
end
Ignore this if you knew already, but there is also a technique called ‘Modelling to Generate Alternatives’ (MGA) which seems to be close to what you are trying to do? This is commonly used in energy system optimization, because there are often many solutions that differe only a little bit in the final system cost. There is even a package for it: NearOptimalAlternatives.jl