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

add support for constraint handlers #109

Merged
merged 35 commits into from
Aug 29, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
55e1f34
WIP: add conshdlr.jl with sketches
rschwarz Mar 24, 2019
790629c
WIP: add more interface methods and call them from the C callbacks
rschwarz May 17, 2019
4c4006c
fix missing type for parameter in signature
rschwarz Jul 4, 2019
4a07574
various fixes regarding Julia/C interface (and typos)
rschwarz Jul 4, 2019
c3b4335
add add_constraint for user's constraint handlers
rschwarz Jul 4, 2019
9ef250f
add minimal test with dummy constraint handler
rschwarz Jul 4, 2019
060ff85
move DummyConsHdlr into separate module (for Julia 1.0)
rschwarz Jul 5, 2019
f9a93a1
pass conshdlr parameters as keyword args
rschwarz Aug 4, 2019
5bcdfe5
register constraint handlers with ManagedSCIP
rschwarz Aug 5, 2019
f89343e
also register constraint data (for constraint handlers)
rschwarz Aug 5, 2019
432acff
add keyword args for non-simple SCIPcreateCons
rschwarz Aug 5, 2019
5059cf3
fix signature of enfo callbacks in @cfunction
rschwarz Aug 9, 2019
1f1ffeb
improve docstrings
rschwarz Aug 9, 2019
2815364
add more tests (never satisfied conshdlr)
rschwarz Aug 9, 2019
29ccbfc
pass all arguments from SCIP callbacks to Julia user functions
rschwarz Aug 9, 2019
c0848c3
fix argument type of locktype in conslock
rschwarz Aug 9, 2019
5e60627
implement consfree and consdelete
rschwarz Aug 9, 2019
11bafb9
add tests with naive all-different conshdlr
rschwarz Aug 9, 2019
e69515b
add convenience functions for constraint handlers
rschwarz Aug 9, 2019
88e3f2e
allow problem modification during solving stage (for callbacks)
rschwarz Aug 9, 2019
c4a99b9
add test with NoGoodCounter (adding constraints to enforce)
rschwarz Aug 9, 2019
587ef09
Replace array with tuple to avoid allocation.
rschwarz Aug 9, 2019
fd16ae5
add some comments and docstrings
rschwarz Aug 12, 2019
aa97504
add more comments and docstrings
rschwarz Aug 13, 2019
e1fbc07
Fix formatting of docstrings.
rschwarz Aug 13, 2019
e9e3619
Add comment about constraint (handler) maps.
rschwarz Aug 13, 2019
7f9ab4c
Add MOI wrappers for include_conshdlr, add_constraint.
rschwarz Aug 13, 2019
880b710
travis: drop Julia nightly
rschwarz Aug 14, 2019
3687471
convert SCIP_Bool to Bool in callbacks
rschwarz Aug 16, 2019
765f2dc
give more detail when checking SCIP stage
rschwarz Aug 22, 2019
335f162
relax assert_solved
rschwarz Aug 22, 2019
5ebc2c2
fix conversion from Cstring to String for MOI.VariableName
rschwarz Aug 23, 2019
f86c15c
create name for conshdlr, if not given
rschwarz Aug 23, 2019
69b67f8
Merge branch 'master' into rs/conshdlr
rschwarz Aug 26, 2019
b575101
update NEWS, README
rschwarz Aug 29, 2019
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
3 changes: 3 additions & 0 deletions src/SCIP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ include("managed_scip.jl")
# constraints from nonlinear expressions
include("nonlinear.jl")

# constraint handlers
include("conshdlr.jl")

# implementation of MOI
include("MOI_wrapper.jl")

Expand Down
250 changes: 250 additions & 0 deletions src/conshdlr.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# Current limitations:
# - use fixed values for some properties
# - EAGERFREQ = 100
# - NEEDSCONS = TRUE
# - SEPAPRIORITY = 0
# - SEPAFREQ = -1
# - DELAYSEPA = FALSE
# - PROPFREQ = -1
# - DELAYPROP = -1
# - PROP_TIMING = SCIP_PROPTIMING_BEFORELP
# - PRESOLTIMING = SCIP_PRESOLTIMING_MEDIUM
# - MAXPREROUNDS = -1
# - don't support optional methods: CONSHDLRCOPY, CONSFREE, CONSINIT, CONSEXIT,
# CONSINITPRE, CONSEXITPRE, CONSINITSOL, CONSEXITSOL, CONSDELETE, CONSTRANS,
# CONSINITLP, CONSSEPALP, CONSSEPASOL, CONSENFORELAX, CONSPROP, CONSPRESOL,
# CONSRESPROP, CONSACTIVE, CONSDEACTIVE, CONSENABLE, CONSDISABLE, CONSDELVARS,
# CONSPRINT, CONSCOPY, CONSPARSE, CONSGETVARS, CONSGETNVARS, CONSGETDIVEBDCHGS
# - don't support linear or nonlinear constraint upgrading
rschwarz marked this conversation as resolved.
Show resolved Hide resolved

# Abstract Types:

# (also: CONSHDLRDATA)
abstract type AbstractConstraintHandler end

# (also: CONSDATA)
abstract type AbstractConstraint{Handler} end

# Parameters:

abstract type AbstractConstraintHandlerParameter end
struct Name <: AbstractConstraintHandlerParameter end
struct Description <: AbstractConstraintHandlerParameter end
struct EnforcePriority <: AbstractConstraintHandlerParameter end
struct CheckPriority <: AbstractConstraintHandlerParameter end

# Interface methods

# Parameters have default values
get(::AbstractConstraintHandler, ::Name) = ""
get(::AbstractConstraintHandler, ::Description) = ""
# enfoprio: integral has 0, nonlinear: -30 or less
get(::AbstractConstraintHandler, ::EnforcePriority) = -15
# checkprio: integral has 0, SCIP's plugins have -6000000 or more
get(::AbstractConstraintHandler, ::CheckPriority) = -7000000

# For semantics of callback methods, see SCIP documentation at:
# https://scip.zib.de/doc-6.0.1/html/CONS.php#CONS_FUNDAMENTALCALLBACKS

# possible return values:
# SCIP_FEASIBLE: given solution is satisfies the constraint
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/is satisfies/satisfies/ (below as well)

# SCIP_INFEASIBLE: given solution is violates the constraint
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/is violates/violates/

function check end

# possible return values:
# SCIP_FEASIBLE: given solution is satisfies the constraint
# SCIP_CUTOFF: stating that the current subproblem is infeasible
# SCIP_CONSADDED: adding constraint that resolves the infeasibility
# SCIP_REDUCEDDOM: reducing the domain of a variable
# SCIP_SEPARATED: adding a cutting plane
# SCIP_BRANCHED: performing a branching
function enforce_lp_sol end

# possible return values:
# SCIP_FEASIBLE: given solution is satisfies the constraint
# SCIP_CUTOFF: stating that the current subproblem is infeasible
# SCIP_CONSADDED: adding constraint that resolves the infeasibility
# SCIP_REDUCEDDOM: reducing the domain of a variable
# SCIP_BRANCHED: performing a branching
# SCIP_SOLVELP: force solving of LP
function enforce_pseudo_sol end

function lock end

# Methods

# CONSCHECK
# (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss,
# SCIP_SOL* sol, SCIP_Bool checkintegrality, SCIP_Bool checklprows,
# SCIP_Bool printreason, SCIP_Bool completely, SCIP_RESULT* result
function _conscheck(scip::Ptr{SCIP_}, conshdlr::Ptr{SCIP_CONSHDLR},
conss::Ptr{Ptr{SCIP_CONS}}, nconss::Cint,
sol::Ptr{SCIP_SOL}, checkintegrality::SCIP_Bool,
checklprows::SCIP_Bool, printreason::SCIP_Bool,
completely::SCIP_Bool, result::Ptr{SCIP_RESULT})
# get Julia object out of constraint handler data
conshdlrdata::Ptr{SCIP_CONSHDLRDATA} = SCIPconshdlrGetData(conshdlr)
constraint_handler = unsafe_pointer_to_objref(conshdlrdata)

# get Julia array from C pointer
constraints = unsafe_wrap(Array{Ptr{SCIP_CONS}}, conss, nconss)

# TODO: fetch solution values
# TODO: document meaning of all parameters

# call user method via dispatch
res = check(constraint_handler, constraints)
unsafe_store!(result, res)

return SCIP_OKAY
end

# CONSENFOLP
# (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss,
# int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result)

function _consenfolp(scip::Ptr{SCIP_}, conshdlr::Ptr{SCIP_CONSHDLR},
conss::Ptr{Ptr{SCIP_CONS}}, nconss::Cint,
nusefulconss::Cint, solinfeasible::SCIP_Bool,
result::Ptr{SCIP_RESULT})
# get Julia object out of constraint handler data
conshdlrdata::Ptr{SCIP_CONSHDLRDATA} = SCIPconshdlrGetData(conshdlr)
constraint_handler = unsafe_pointer_to_objref(conshdlrdata)

# get Julia array from C pointer
constraints = unsafe_wrap(Array{Ptr{SCIP_CONS}}, conss, nconss)

# TODO: fetch solution values?
# TODO: document meaning of all parameters

# call user method via dispatch
res = enforce_lp_sol(constraint_handler, constraints, solinfeasible)
unsafe_store!(result, res)

return SCIP_OKAY
rschwarz marked this conversation as resolved.
Show resolved Hide resolved
end

# CONSENFOPS
# (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss,
# int nusefulconss, SCIP_Bool solinfeasible, SCIP_Bool objinfeasible,
# SCIP_RESULT* result)
function _consenfops(scip::Ptr{SCIP_}, conshdlr::Ptr{SCIP_CONSHDLR},
conss::Ptr{Ptr{SCIP_CONS}}, nconss::Cint,
nusefulconss::Cint, solinfeasible::SCIP_Bool,
objinfeasible::SCIP_Bool, result::Ptr{SCIP_RESULT})
# get Julia object out of constraint handler data
conshdlrdata::Ptr{SCIP_CONSHDLRDATA} = SCIPconshdlrGetData(conshdlr)
constraint_handler = unsafe_pointer_to_objref(conshdlrdata)

# get Julia array from C pointer
constraints = unsafe_wrap(Array{Ptr{SCIP_CONS}}, conss, nconss)

# TODO: fetch solution values?
# TODO: document meaning of all parameters

# call user method via dispatch
res = enforce_pseudo_sol(constraint_handler, constraints, solinfeasible, objinfeasible)
unsafe_store!(result, res)

return SCIP_OKAY
end

# CONSLOCK
# (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons,
# SCIP_LOCKTYPE locktype, int nlockspos, int nlocksneg)
function _conslock(scip::Ptr{SCIP_}, conshdlr::Ptr{SCIP_CONSHDLR},
cons::Ptr{SCIP_CONS}, locktype::Ptr{SCIP_LOCKTYPE},
nlockspos::Cint, nlocksneg::Cint)
# get Julia object out of constraint handler data
conshdlrdata::Ptr{SCIP_CONSHDLRDATA} = SCIPconshdlrGetData(conshdlr)
constraint_handler = unsafe_pointer_to_objref(conshdlrdata)

# TODO: document meaning of all parameters

# call user method via dispatch
lock(constraint_handler, cons, locktype, nlockspos, nlocksneg)

return SCIP_OKAY
end

# SCIPincludeConsXyz(SCIP* scip)
#
# - is called by users, not SCIP: no need to make it signature conformant.
# - set properties (NAME, DESC, ENFOPRIORITY, CHECKPRIORITY).
# - initialize conshdlrdata (== Julia object)
# - could be called from the constructor to constraint handler?
function include_conshdlr(mscip::ManagedSCIP, ch::CH) where CH <: AbstractConstraintHandler
# get C function pointers from Julia functions
_enfolp = @cfunction(_consenfolp, SCIP_RETCODE, (Ptr{SCIP_}, Ptr{SCIP_CONSHDLR}, Ptr{Ptr{SCIP_CONS}}, Cint, Cint, SCIP_Bool, SCIP_Bool, Ptr{SCIP_RESULT}))
_enfops = @cfunction(_consenfops, SCIP_RETCODE, (Ptr{SCIP_}, Ptr{SCIP_CONSHDLR}, Ptr{Ptr{SCIP_CONS}}, Cint, Cint, SCIP_Bool, SCIP_Bool, SCIP_Bool, Ptr{SCIP_RESULT}))
_check = @cfunction(_conscheck, SCIP_RETCODE, (Ptr{SCIP_}, Ptr{SCIP_CONSHDLR}, Ptr{Ptr{SCIP_CONS}}, Cint, Ptr{SCIP_SOL}, SCIP_Bool, SCIP_Bool, SCIP_Bool, SCIP_Bool, Ptr{SCIP_RESULT}))
_lock = @cfunction(_conslock, SCIP_RETCODE, (Ptr{SCIP_}, Ptr{SCIP_CONSHDLR}, Ptr{SCIP_CONS}, Ptr{SCIP_LOCKTYPE}, Cint, Cint))

# We don't need to store this, I guess.
conshdlr__ = Ref{Ptr{SCIP_CONSHDLR}}(C_NULL)

# Fixed values for some properties:
# TODO: allow to set them as optional parameters?
EAGERFREQ = 100
NEEDSCONS = TRUE

# Get other values from methods:
name = get(ch, Name())
name != "" || error("Constraint handler may not have empty name!")
desc = get(ch, Description())
enfopriority = get(ch, EnforcePriority())
chckpriority = get(ch, CheckPriority())

# Hand over Julia object as constraint handler data:
# TODO: make sure that `ch` is not garbage collected?
conshdlrdata_ = pointer_from_objref(ch)

# Register constraint handler with SCIP instance.
@SC SCIPincludeConshdlrBasic(mscip, conshdlr__,
name, desc, enfopriority, chckpriority,
EAGERFREQ, NEEDSCONS,
_enfolp, _enfops, _check, _lock,
conshdlrdata_)

# Sanity checks
@assert conshdlr__[] != C_NULL
end

# SCIPcreateConsBasicXyz(SCIP* scip, SCIP_CONS** cons, char* name, ...)
#
# - is called by users, not SCIP: no need to make it signature conformant.
# - add custom arguments as needed
# - store in consdata (== Julia object)
# - could be the same as the constructor to AbstractConstraint?

"""
Add constraint of user-defined type, returns cons ref.

# Arguments
- `ch`: Julia-side constraint handler (== SCIP-side conshdlrdata)
- `c`: Julia-side constraint (== SCIP-side consdata)
"""
function add_constraint(mscip::ManagedSCIP, ch::CH, c::C) where {CH <:AbstractConstraintHandler, C <: AbstractConstraint{CH}}
# Find matching SCIP constraint handler plugin.
# TODO: store mapping in Julia data, not depending on unique name?
name = get(ch, Name())
conshdlr_::Ptr{SCIP_CONSHDLR} = SCIPfindConshdlr(mscip, name)
conshdlr_ != C_NULL || error("Constraint handler $name not found!")

# Hand over Julia object as constraint data:
# TODO: make sure that `c` is not garbage collected?
consdata_ = pointer_from_objref(c)

# Create SCIP constraint (and attach constraint data).
# TODO: allow to set last 10 arguments as optional?
cons__ = Ref{Ptr{SCIP_CONS}}(C_NULL)
@SC SCIPcreateCons(mscip, cons__, "", conshdlr_, consdata_,
TRUE, TRUE, TRUE, TRUE, TRUE,
FALSE, FALSE, FALSE, FALSE, FALSE)

# Add constraint to problem.
@SC SCIPaddCons(mscip, cons__[])

# Register constraint and return reference.
return SCIP.store_cons!(mscip, cons__)
end
23 changes: 23 additions & 0 deletions test/conshdlr.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@testset "dummy conshdlr" begin
# create an empty problem
mscip = SCIP.ManagedSCIP()
SCIP.set_parameter(mscip, "display/verblevel", 0)

# add the constraint handler
ch = Dummy.DummyConsHdlr()
SCIP.include_conshdlr(mscip, ch)

# add dummy constraint
cr = SCIP.add_constraint(mscip, ch, Dummy.DummyCons())

# solve the problem
SCIP.@SC SCIP.SCIPsolve(mscip.scip[])

@test ch.name_called >= 1
@test ch.check_called >= 1
@test ch.enfo_called >= 0
@test ch.lock_called >= 1

# free the problem
finalize(mscip)
end
44 changes: 44 additions & 0 deletions test/conshdlr_support.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module Dummy

using SCIP

# Define a minimal no-op constraint handler.
# Needs to be mutable for `pointer_from_objref` to work.
mutable struct DummyConsHdlr <: SCIP.AbstractConstraintHandler
name_called::Int64
check_called::Int64
enfo_called::Int64
lock_called::Int64

DummyConsHdlr() = new(0, 0, 0, 0)
end

# Implement only the fundamental callbacks:
function SCIP.get(ch::DummyConsHdlr, ::SCIP.Name)
ch.name_called += 1
return "Dummy"
end

function SCIP.check(ch::DummyConsHdlr, constraints)
ch.check_called += 1
return SCIP.SCIP_FEASIBLE
end

function SCIP.enforce_lp_sol(ch::DummyConsHdlr, constraints, solinfeasible)
ch.enfo_called += 1
return SCIP.SCIP_FEASIBLE
end

function SCIP.enforce_pseudo_sol(ch::DummyConsHdlr, constraints, solinfeasible, objinfeasible)
ch.enfo_called += 1
return SCIP.SCIP_FEASIBLE
end

function SCIP.lock(ch::DummyConsHdlr, constraint, locktype, nlockspos, nlocksneg)
ch.lock_called += 1
end

# Corresponding, empty constraint (data) object
mutable struct DummyCons <: SCIP.AbstractConstraint{DummyConsHdlr} end

end # module Dummy
7 changes: 7 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ end
include("managed_scip.jl")
end

# new type definitions in module (needs top level)
include("conshdlr_support.jl")

@testset "constraint handlers" begin
include("conshdlr.jl")
end

@testset "MathOptInterface tests (bridged)" begin
include("MOI_wrapper.jl")
end
Expand Down