Skip to content

Commit

Permalink
clear internal model if we change something we're not supposed to
Browse files Browse the repository at this point in the history
  • Loading branch information
joehuchette committed Jul 3, 2014
1 parent 437151f commit a4bfa71
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 32 deletions.
51 changes: 44 additions & 7 deletions src/JuMP.jl
Expand Up @@ -234,6 +234,8 @@ end
Base.zero(v::Type{Variable}) = AffExpr(Variable[],Float64[],0.0)
Base.zero(v::Variable) = zero(typeof(v))

verify_ownership(m::Model, vec::Vector{Variable}) = all(v->isequal(v.m,m), vec)

###############################################################################
# Generic affine expression class
# Holds a vector of tuples (Var, Coeff)
Expand Down Expand Up @@ -318,6 +320,17 @@ end

function setObjective(m::Model, sense::Symbol, q::QuadExpr)
m.obj = q
if m.internalModelLoaded
try
verify_ownership(m, m.obj.qvars1)
verify_ownership(m, m.obj.qvars2)
MathProgBase.setquadobjterms!(m.internalModel, Cint[v.col for v in m.obj.qvars1], Cint[v.col for v in m.obj.qvars2], m.obj.qcoeffs)
catch
# we don't (yet) support hot-starting QCQP solutions
Base.warn_once("JuMP does not yet support adding a quadratic objective to an existing model. Hot-start is disabled.")
m.internalModelLoaded = false
end
end
setObjectiveSense(m, sense)
end

Expand Down Expand Up @@ -419,7 +432,7 @@ type SOSConstraint <: JuMPConstraint
sostype::Symbol
end

function constructSOS(coll::Vector{AffExpr})
function constructSOS(m::Model, coll::Vector{AffExpr})
nvar = length(coll)
vars = Array(Variable, nvar)
weight = Array(Float64, nvar)
Expand All @@ -428,6 +441,7 @@ function constructSOS(coll::Vector{AffExpr})
error("Must specify collection in terms of single variables")
end
vars[i] = coll[i].vars[1]
vars[i].m == m || error("Variable in constraint is not owned by the model")
weight[i] = coll[i].coeffs[1]
end
return vars, weight
Expand All @@ -436,7 +450,7 @@ end
addSOS1(m::Model, coll) = addSOS1(m, convert(Vector{AffExpr}, coll))

function addSOS1(m::Model, coll::Vector{AffExpr})
vars, weight = constructSOS(coll)
vars, weight = constructSOS(m,coll)
push!(m.sosconstr, SOSConstraint(vars, weight, :SOS1))
if m.internalModelLoaded
idx = Int[v.col for v in vars]
Expand All @@ -453,7 +467,7 @@ end
addSOS2(m::Model, coll) = addSOS2(m, convert(Vector{AffExpr}, coll))

function addSOS2(m::Model, coll::Vector{AffExpr})
vars, weight = constructSOS(coll)
vars, weight = constructSOS(m,coll)
push!(m.sosconstr, SOSConstraint(vars, weight, :SOS2))
if m.internalModelLoaded
idx = Int[v.col for v in vars]
Expand Down Expand Up @@ -482,10 +496,33 @@ typealias QuadConstraint GenericQuadConstraint{QuadExpr}

function addConstraint(m::Model, c::QuadConstraint)
push!(m.quadconstr,c)
if m.internalModelLoaded
# we don't (yet) support hot-starting QCQP solutions
Base.warn_once("JuMP does not yet support adding quadratic constraints to an existing model. Hot-start is disabled.")
m.internalModelLoaded = false
if m.internalModelLoaded
if method_exists(MathProgBase.addquadconstr!, (typeof(m.internalModel),
Vector{Cint},
Vector{Float64},
Vector{Cint},
Vector{Cint},
Vector{Float64},
Char,
Float64))
if !((s = string(c.sense)[1]) in ['<', '>', '='])
error("Invalid sense for quadratic constraint")
end
terms = c.terms
verify_ownership(m, terms.qvars1)
verify_ownership(m, terms.qvars2)
MathProgBase.addquadconstr!(m.internalModel,
Cint[v.col for v in c.terms.aff.vars],
c.terms.aff.coeffs,
Cint[v.col for v in c.terms.qvars1],
Cint[v.col for v in c.terms.qvars2],
c.terms.qcoeffs,
s,
-c.terms.aff.constant)
else
Base.warn_once("Solver does not appear to support adding quadratic constraints to an existing model. Hot-start is disabled.")
m.internalModelLoaded = false
end
end
return ConstraintRef{QuadConstraint}(m,length(m.quadconstr))
end
Expand Down
16 changes: 13 additions & 3 deletions src/callbacks.jl
@@ -1,9 +1,19 @@
export setLazyCallback, setCutCallback, setHeuristicCallback
export setlazycallback
@Base.deprecate setlazycallback setLazyCallback
setLazyCallback(m::Model, f::Function) = (m.lazycallback = f)
setCutCallback(m::Model, f::Function) = (m.cutcallback = f)
setHeuristicCallback(m::Model, f::Function) = (m.heurcallback = f)
function setLazyCallback(m::Model, f::Function)
m.internalModelLoaded = false
m.lazycallback = f
end
function setCutCallback(m::Model, f::Function)
m.internalModelLoaded = false
m.cutcallback = f
end
function setHeuristicCallback(m::Model, f::Function)
m.internalModelLoaded = false
m.heurcallback = f
end


function registercallbacks(m::Model)
if isa(m.lazycallback, Function)
Expand Down
19 changes: 11 additions & 8 deletions src/solvers.jl
Expand Up @@ -45,6 +45,8 @@ function addQuadratics(m::Model)

if length(m.obj.qvars1) != 0
assert_isfinite(m.obj)
verify_ownership(m, m.obj.qvars1)
verify_ownership(m, m.obj.qvars2)
MathProgBase.setquadobjterms!(m.internalModel, Cint[v.col for v in m.obj.qvars1], Cint[v.col for v in m.obj.qvars2], m.obj.qcoeffs)
end

Expand Down Expand Up @@ -82,6 +84,7 @@ function prepProblemBounds(m::Model)

objaff::AffExpr = m.obj.aff
assert_isfinite(objaff)
verify_ownership(m, objaff.vars)

# We already have dense column lower and upper bounds

Expand Down Expand Up @@ -157,7 +160,7 @@ function solveLP(m::Model; load_model_only=false, suppress_warnings=false)
f, rowlb, rowub = prepProblemBounds(m)

# Ready to solve
hasQuads = (length(m.quadconstr) > 0) || (length(m.obj.qvars1) > 0)
noQuads = (length(m.quadconstr) == 0) && (length(m.obj.qvars1) == 0)
if m.internalModelLoaded
if applicable(MathProgBase.setvarLB!, m.internalModel, m.colLower) &&
applicable(MathProgBase.setvarUB!, m.internalModel, m.colUpper) &&
Expand Down Expand Up @@ -201,17 +204,17 @@ function solveLP(m::Model; load_model_only=false, suppress_warnings=false)
m.colVal = fill(NaN, m.numCols)
m.objVal = NaN
if stat == :Infeasible
if !hasQuads && applicable(MathProgBase.getinfeasibilityray, m.internalModel)
if noQuads && applicable(MathProgBase.getinfeasibilityray, m.internalModel)
m.linconstrDuals = MathProgBase.getinfeasibilityray(m.internalModel)
else
!suppress_warnings && warn("Infeasibility ray (Farkas proof) not available")
noQuads && !suppress_warnings && warn("Infeasibility ray (Farkas proof) not available")
m.linconstrDuals = fill(NaN, length(m.linconstr))
end
elseif stat == :Unbounded
if !hasQuads && applicable(MathProgBase.getunboundedray, m.internalModel)
if noQuads && applicable(MathProgBase.getunboundedray, m.internalModel)
m.colVal = MathProgBase.getunboundedray(m.internalModel)
else
!suppress_warnings && warn("Unbounded ray not available")
noQuads && !suppress_warnings && warn("Unbounded ray not available")
m.colVal = fill(NaN, numCols)
end
else
Expand All @@ -233,12 +236,12 @@ function solveLP(m::Model; load_model_only=false, suppress_warnings=false)
m.objVal = MathProgBase.getobjval(m.internalModel)
m.objVal += m.obj.aff.constant
m.colVal = MathProgBase.getsolution(m.internalModel)
if !hasQuads && applicable(MathProgBase.getreducedcosts, m.internalModel) &&
applicable(MathProgBase.getconstrduals, m.internalModel)
if noQuads && applicable(MathProgBase.getreducedcosts, m.internalModel) &&
applicable(MathProgBase.getconstrduals, m.internalModel)
m.redCosts = MathProgBase.getreducedcosts(m.internalModel)
m.linconstrDuals = MathProgBase.getconstrduals(m.internalModel)
else
!suppress_warnings && warn("Dual solutions not available")
noQuads && !suppress_warnings && warn("Dual solutions not available")
m.redCosts = fill(NaN, length(m.linconstr))
m.linconstrDuals = fill(NaN, length(m.linconstr))
end
Expand Down
6 changes: 1 addition & 5 deletions test/model.jl
Expand Up @@ -13,8 +13,6 @@ let
@test_throws getDual(errVar)
end



###############################################################################
# Test Model A
#####################################################################
Expand Down Expand Up @@ -161,7 +159,6 @@ println(" !!TODO: test external solvers for reading LP and MPS files")
setObjectiveSense(modA, :Min)
@test getObjectiveSense(modA) == :Min


#####################################################################
# Test binary variable handling
let
Expand All @@ -174,7 +171,6 @@ let
@test_approx_eq_eps getValue(x) 1.0 1e-6
end


#####################################################################
# Test model copying
let
Expand All @@ -201,7 +197,7 @@ let
# Constraints
source.linconstr[1].ub = 5.0
@test dest.linconstr[1].ub == 6.0
source.quadconstr[1].sense == :(>=)
source.quadconstr[1].sense = :(>=)
@test dest.quadconstr[1].sense == :(<=)
end

Expand Down
15 changes: 8 additions & 7 deletions test/probmod.jl
Expand Up @@ -130,7 +130,8 @@ function methods_test(solvername, solverobj, supp)
end

# test there were no regressions in applicable
const mpb_methods = [(MathProgBase.addconstr!, ([1],[1.0],1.0,1.0)),
const mpb_methods = [(MathProgBase.addquadconstr!, (Cint[1],Float64[1.0],Cint[1],Cint[1],Float64[1],'>',1.0)),
(MathProgBase.addconstr!, ([1],[1.0],1.0,1.0)),
(MathProgBase.addsos1!, ([1],[1.0])),
(MathProgBase.addsos2!, ([1],[1.0])),
(MathProgBase.addvar!, ([1],[1.0],1.0,1.0,1.0)),
Expand All @@ -149,33 +150,33 @@ const mpb_methods = [(MathProgBase.addconstr!, ([1],[1.0],1.0,1.0)),

if Pkg.installed("Gurobi") != nothing
using Gurobi
supp = (true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true)
supp = (true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true)
methods_test("Gurobi", GurobiSolver(), supp)
end

if Pkg.installed("CPLEX") != nothing
using CPLEX
supp = (true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true)
supp = (true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true)
methods_test("CPLEX", CplexSolver(), supp)
end
if Pkg.installed("Clp") != nothing
using Clp
supp = (true,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false)
supp = (false,true,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false)
methods_test("Clp", ClpSolver(), supp)
end
if Pkg.installed("Cbc") != nothing
# no-op, since Cbc doesn't support any
end
if Pkg.installed("GLPK") != nothing
using GLPKMathProgInterface
supp = (true,false,false,true,true,true,true,true,true,true,false,true,true,true,true,false)
supp = (false,true,false,false,true,true,true,true,true,true,true,false,true,true,true,true,false)
methods_test("GLPK", GLPKSolverLP(), supp)
supp = (true,false,false, true,true,true,true,true,true,true,true,false,false,false,false,false)
supp = (false,true,false,false, true,true,true,true,true,true,true,true,false,false,false,false,false)
methods_test("GLPK", GLPKSolverMIP(), supp)
end
if Pkg.installed("Mosek") != nothing
using Mosek
supp = (true,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false)
supp = (true,true,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false)
methods_test("Mosek", MosekSolver(), supp)
end

Expand Down
36 changes: 34 additions & 2 deletions test/quadmodel.jl
@@ -1,5 +1,5 @@
# Test models with quadratic objectives
function qp_test(solvername, solverobj)
function qp_test(solvername, solverobj; prob_mod=true)
println(string(" Running ", solvername))
let
println(" Test 1")
Expand Down Expand Up @@ -50,6 +50,38 @@ function qp_test(solvername, solverobj)
@test_approx_eq_eps modQ.objVal 1.0 1e-6
@test_approx_eq_eps (getValue(x) + getValue(y)) 1.0 1e-6
end

# test problem modification (quad constraint)
let
println(" Test 4")
modQ = Model(solver=solverobj)
@defVar(modQ, x >= 0)
addConstraint(modQ, x*x <= 1)
@setObjective(modQ, Max, x)
stat = solve(modQ)
@test_approx_eq_eps getObjectiveValue(modQ) 1.0 1e-5

addConstraint(modQ, 2x*x <= 1)
@test modQ.internalModelLoaded
stat = solve(modQ)
@test_approx_eq getObjectiveValue(modQ) sqrt(0.5)
end

# test problem modification (quad objective)
let
println(" Test 5")
modQ = Model(solver=solverobj)
@defVar(modQ, 0 <= x <= 1)
@defVar(modQ, 1/2 <= y <= 1)
setObjective(modQ, :Min, x*x - y)
stat = solve(modQ)
@test getObjectiveValue(modQ) == -1.0

setObjective(modQ, :Min, y*y - x)
@test modQ.internalModelLoaded == true
stat = solve(modQ)
@test getObjectiveValue(modQ) == -0.75
end
end


Expand All @@ -64,5 +96,5 @@ if Pkg.installed("CPLEX") != nothing
end
if Pkg.installed("Mosek") != nothing
using Mosek
qp_test("Mosek", MosekSolver())
qp_test("Mosek", MosekSolver(); prob_mod=false) # weird issues with probmod to revisit
end

0 comments on commit a4bfa71

Please sign in to comment.