From 144e12d767b06b1252f00746f8afb532ddd92665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 10 Jul 2019 15:17:59 +0200 Subject: [PATCH 1/5] Add Lazy/Heuristic MIP callbacks --- docs/src/apireference.md | 18 ++++++ src/attributes.jl | 132 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 48e01eec4b..58b46db27a 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -74,6 +74,13 @@ AbstractSubmittable submit ``` +List of submittables + +```@docs +LazyConstraint +HeuristicSolution +``` + ## Model Interface ```@docs @@ -121,6 +128,9 @@ SolverName Silent TimeLimitSec RawParameter +AbstractCallback +LazyCallback +HeuristicCallback ``` List of attributes useful for optimizers @@ -219,6 +229,8 @@ Calls to `get` and `set` should include as an argument a single `VariableIndex` VariableName VariablePrimalStart VariablePrimal +VariablePrimalAtIntegerNode +VariablePrimalAtFractionalNode ``` ### Constraints @@ -438,6 +450,12 @@ LowerBoundAlreadySet UpperBoundAlreadySet ``` +As discussed in [`AbstractCallback`](@ref), trying to [`get`](@ref) attributes +inside a callback may throw: +```@docs +OptimizeInProgress +``` + The rest of the errors defined in MOI fall in two categories represented by the following two abstract types: ```@docs diff --git a/src/attributes.jl b/src/attributes.jl index 930a894280..018f62aa20 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -377,6 +377,14 @@ function throw_set_error_fallback(model::ModelLike, end end +""" + SettingSingleVariableFunctionNotAllowed() + +Error type that should be thrown when the user [`set`](@ref) the +[`ConstraintFunction`](@ref) of a [`SingleVariable`](@ref) constraint. +""" +struct SettingSingleVariableFunctionNotAllowed <: Exception end + """ submit(optimizer::AbstractOptimizer, sub::AbstractSubmittable, values...)::Nothing @@ -397,13 +405,39 @@ function submit(model::ModelLike, sub::AbstractSubmittable, args...) end end +## Submittables + """ - SettingSingleVariableFunctionNotAllowed() + LazyConstraint(callback_data) -Error type that should be thrown when the user [`set`](@ref) the -[`ConstraintFunction`](@ref) of a [`SingleVariable`](@ref) constraint. +Lazy constraint `func`-in-`set` submitted as a tuple `(func, set)`. The optimal +solution returned by [`VariablePrimal`](@ref) will satisfy all lazy +constraints that have been submitted. + +This can be submitted only from the lazy callback (that is, the function set to +[`LazyCallback`](@ref)). The field `callback_data` is a solver-specific +callback type that is passed as the argument to the lazy callback. """ -struct SettingSingleVariableFunctionNotAllowed <: Exception end +struct LazyConstraint <: AbstractSubmittable + callback_data::Any +end + +""" + HeuristicSolution(callback_data) + +Heuristically obtained integer-feasible solutions. The solution is given by a +`Dict{VariableIndex, Float64}` mapping the variables to their solution value. + +This can be submitted only from the heuristic callback (that is, the function +set to [`HeuristicCallback`](@ref)). The field `callback_data` is a +solver-specific callback type that is passed as the argument to the heuristic +callback. + +Note that the solver may silently reject the provided solution. +""" +struct HeuristicSolution <: AbstractSubmittable + callback_data::Any +end ## Optimizer attributes @@ -461,6 +495,74 @@ struct RawParameter <: AbstractOptimizerAttribute name::Any end +### Callbacks + +""" + struct OptimizeInProgress{AttrType<:AnyAttribute} <: Exception + attr::AttrType + end + +Error thrown from optimizer when `MOI.get(optimizer, attr)` is called inside an +[`AbstractCallback`](@ref) while it is only defined once [`optimize!`](@ref) has +completed. This is only defined if `is_set_by_optimize(attr)` is `true`. +""" +struct OptimizeInProgress{AttrType<:AnyAttribute} <: Exception + attr::AttrType +end + +function Base.showerror(io::IO, err::OptimizeInProgress) + print(io, typeof(err), ": Cannot get result as the `MOI.optimize!` has not", + " finished.") +end + +""" + abstract type AbstractCallback <: AbstractOptimizerAttribute end + +Abstract type for optimizer attribute representing a callback functions. The +value set to subtypes of `AbstractCallback` is a function that may be called +during [`optimize!`](@ref). As [`optimize!`](@ref) is in progress, the result +attributes (i.e, the attributes `attr` such that `is_set_by_optimize(attr)`) +may not be accessible from the callback hence trying to get result attributes +might throw a [`OptimizeInProgress`](@ref) error. + +The value of the attribute should be a function taking only one argument, that +is commonly called `callback_data`, that can be used for instance in +[`LazyCallback`](@ref), [`HeuristicSolution`](@ref). +""" +abstract type AbstractCallback <: AbstractOptimizerAttribute end + +""" + LazyCallback() <: AbstractCallback + +The *lazy* callback can be used to submit [`LazyConstraint`](@ref) which are +added on-demand at integer nodes in the branch and bound tree. Note that there +is no guarantee that the callback is called at *every* integer primal solution. + +The solution at the integer node is accessed through +[`VariablePrimalAtIntegerNode`](@ref). Trying to access other result +attributes will throw [`OptimizeInProgress`](@ref) as discussed in +[`AbstractCallback`](@ref). +""" +struct LazyCallback <: AbstractCallback end + +""" + HeuristicCallback() <: AbstractCallback + +The *heuristic* callback can be used to submit [`HeuristicSolution`](@ref) at +fractional (i.e., non-integer) nodes in the branch and bound tree. Note that +there is not guarantee that the callback is called *everytime* the solver has a +fractional solution. + +The solution at the fractional node is accessed through +[`VariablePrimalAtFractionalNode`](@ref). Trying to access other result +attributes will throw [`OptimizeInProgress`](@ref) as discussed in +[`AbstractCallback`](@ref). + +Some solvers require a complete solution, others only partial solutions. It's up +to you to provide the appropriate one. If in doubt, give a complete solution. +""" +struct HeuristicCallback <: AbstractCallback end + ## Model attributes """ @@ -681,6 +783,28 @@ struct VariablePrimal <: AbstractVariableAttribute end VariablePrimal() = VariablePrimal(1) +""" + VariablePrimalAtIntegerNode(callback_data) + +A variable attribute for the assignment to some primal variable's value at the +integer node from which the [`LazyCallback`](@ref) corresponding to +`callback_data` was called. +""" +struct VariablePrimalAtIntegerNode <: AbstractVariableAttribute + callback_data::Any +end + +""" + VariablePrimalAtFractionalNode(callback_data) + +A variable attribute for the assignment to some primal variable's value at the +fractional node from which the [`HeuristicCallback`](@ref) corresponding to +`callback_data` was called. +""" +struct VariablePrimalAtFractionalNode <: AbstractVariableAttribute + callback_data::Any +end + """ BasisStatusCode From 81163c5ca7e98dfae3e0452caeb2faa69fa9cbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 18 Jul 2019 20:11:20 -0600 Subject: [PATCH 2/5] Feasible and Infeasible solution callbacks --- docs/src/apireference.md | 9 +- src/attributes.jl | 175 ++++++++++++++++++++++++++++----------- 2 files changed, 131 insertions(+), 53 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 58b46db27a..03fc758fc4 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -77,8 +77,10 @@ submit List of submittables ```@docs +RejectSolution LazyConstraint HeuristicSolution +UserCut ``` ## Model Interface @@ -129,8 +131,8 @@ Silent TimeLimitSec RawParameter AbstractCallback -LazyCallback -HeuristicCallback +FeasibleSolutionCallback +InfeasibleSolutionCallback ``` List of attributes useful for optimizers @@ -229,8 +231,7 @@ Calls to `get` and `set` should include as an argument a single `VariableIndex` VariableName VariablePrimalStart VariablePrimal -VariablePrimalAtIntegerNode -VariablePrimalAtFractionalNode +CallbackVariablePrimal ``` ### Constraints diff --git a/src/attributes.jl b/src/attributes.jl index 018f62aa20..2c2bc56aab 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -407,36 +407,71 @@ end ## Submittables +""" + RejectSolution(callback_data) + +Reject feasible solution provided by [`CallbackVariablePrimal`](@ref). + +This can be submitted only from the [`FeasibleSolutionCallback`](@ref). The +field `callback_data` is a solver-specific callback type that is passed as the +argument to the feasible solution callback. +""" +struct RejectSolution{CBDT} <: AbstractSubmittable + callback_data::CBDT +end + """ LazyConstraint(callback_data) -Lazy constraint `func`-in-`set` submitted as a tuple `(func, set)`. The optimal +Lazy constraint `func`-in-`set` submitted as `func, set`. The optimal solution returned by [`VariablePrimal`](@ref) will satisfy all lazy constraints that have been submitted. -This can be submitted only from the lazy callback (that is, the function set to -[`LazyCallback`](@ref)). The field `callback_data` is a solver-specific -callback type that is passed as the argument to the lazy callback. +This can be submitted only from the [`FeasibleSolutionCallback`](@ref). The +field `callback_data` is a solver-specific callback type that is passed as the +argument to the feasible solution callback. """ -struct LazyConstraint <: AbstractSubmittable - callback_data::Any +struct LazyConstraint{CBDT} <: AbstractSubmittable + callback_data::CBDT end """ HeuristicSolution(callback_data) -Heuristically obtained integer-feasible solutions. The solution is given by a -`Dict{VariableIndex, Float64}` mapping the variables to their solution value. +Heuristically obtained feasible solution. The solution is submitted as +`variables, values` where `values[i]` gives the value of `variables[i]`, +similarly to [`set`](@ref). -This can be submitted only from the heuristic callback (that is, the function -set to [`HeuristicCallback`](@ref)). The field `callback_data` is a -solver-specific callback type that is passed as the argument to the heuristic -callback. +This can be submitted only from the [`InfeasibleSolutionCallback`](@ref). The +field `callback_data` is a solver-specific callback type that is passed as the +argument to the infeasible solution callback. Note that the solver may silently reject the provided solution. """ -struct HeuristicSolution <: AbstractSubmittable - callback_data::Any +struct HeuristicSolution{CBDT} <: AbstractSubmittable + callback_data::CBDT +end + +""" + UserCut(callback_data) + +Constraint `func`-to-`set` suggested to render the solution given by +[`CallbackVariablePrimal`](@ref) trivially infeasible. The solution is submitted +as `func, set`. For instance, for a solution satisfying all constraints except +[`SingleVariable`](@ref)-in-[`Integer`](@ref) constraints, a +[`ScalarAffineFunction`](@ref)-in-[`LessThan`](@ref) may be submitted cut it +off. Note that, as opposed to [`LazyConstraint`](@ref), the provided constraint +cannot modify the feasible set, the constraint should be redundant, e.g., it +may be a consequence of affine and integrality constraints. + +This can be submitted only from the [`InfeasibleSolutionCallback`](@ref). The +field `callback_data` is a solver-specific callback type that is passed as the +argument to the infeasible solution callback. + +Note that the solver may silently ignore the provided constraint. +""" +struct UserCut{CBDT} <: AbstractSubmittable + callback_data::CBDT end ## Optimizer attributes @@ -525,43 +560,97 @@ attributes (i.e, the attributes `attr` such that `is_set_by_optimize(attr)`) may not be accessible from the callback hence trying to get result attributes might throw a [`OptimizeInProgress`](@ref) error. +Some solvers require a complete solution, others only partial solutions. It's up +to you to provide the appropriate one. If in doubt, give a complete solution. + The value of the attribute should be a function taking only one argument, that is commonly called `callback_data`, that can be used for instance in -[`LazyCallback`](@ref), [`HeuristicSolution`](@ref). +[`FeasibleSolutionCallback`](@ref), [`InfeasibleSolutionCallback`](@ref). """ abstract type AbstractCallback <: AbstractOptimizerAttribute end """ - LazyCallback() <: AbstractCallback + FeasibleSolutionCallback() <: AbstractCallback -The *lazy* callback can be used to submit [`LazyConstraint`](@ref) which are -added on-demand at integer nodes in the branch and bound tree. Note that there -is no guarantee that the callback is called at *every* integer primal solution. +The callback can be used to reject a feasible primal solution by submitting +[`RejectSolution`](@ref) and/or [`LazyConstraint`](@ref). Note that there +is no guarantee that the callback is called at *every* feasible primal solution. -The solution at the integer node is accessed through -[`VariablePrimalAtIntegerNode`](@ref). Trying to access other result +The feasible primal solution is accessed through +[`CallbackVariablePrimal`](@ref). Trying to access other result attributes will throw [`OptimizeInProgress`](@ref) as discussed in [`AbstractCallback`](@ref). + +## Examples + +```julia +x = MOI.add_variables(optimizer, 8) +MOI.submit(optimizer, MOI.FeasibleSolutionCallback(), callback_data -> begin + sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) + if MOI.supports(optimizer, MOI.RejectSolution(callback_data)) + # determine if it should be rejected only if it's supported as it may + # be time consuming. + if # should reject + MOI.submit(optimizer, MOI.RejectSolution(callback_data)) + end + end + if MOI.supports(optimizer, MOI.LazyConstraint(callback_data)) + # determine the lazy constraint only if it's supported as it may be time + # consuming. + if # should add a lazy constraint + func = # computes function + set = # computes set + MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set) + end + end +end +``` """ -struct LazyCallback <: AbstractCallback end +struct FeasibleSolutionCallback <: AbstractCallback end """ - HeuristicCallback() <: AbstractCallback + InfeasibleSolutionCallback() <: AbstractCallback -The *heuristic* callback can be used to submit [`HeuristicSolution`](@ref) at -fractional (i.e., non-integer) nodes in the branch and bound tree. Note that -there is not guarantee that the callback is called *everytime* the solver has a -fractional solution. +The callback can be used to submit [`HeuristicSolution`](@ref) and/or +[`UserCut`](@ref) given an infeasible primal solution. +For instance, it may be called at fractional (i.e., non-integer) nodes in the +branch and bound tree of a mixed-integer problem. Note that there is not +guarantee that the callback is called *everytime* the solver has an infeasible +solution. -The solution at the fractional node is accessed through -[`VariablePrimalAtFractionalNode`](@ref). Trying to access other result +The infeasible solution is accessed through +[`CallbackVariablePrimal`](@ref). Trying to access other result attributes will throw [`OptimizeInProgress`](@ref) as discussed in [`AbstractCallback`](@ref). -Some solvers require a complete solution, others only partial solutions. It's up -to you to provide the appropriate one. If in doubt, give a complete solution. +## Examples + +```julia +x = MOI.add_variables(optimizer, 8) +MOI.submit(optimizer, MOI.InfeasibleSolutionCallback(), callback_data -> begin + sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) + if MOI.supports(optimizer, MOI.HeuristicSolution(callback_data)) + # determine the heuristic solution only if it it's supported as it may + # be time consuming. + if # can find a heuristic solution + values = # computes heuristic solution + MOI.submit(optimizer, MOI.HeuristicSolution(callback_data), x, + values) + end + end + if MOI.supports(optimizer, MOI.UserCut(callback_data)) + # determine the user cut only if it's supported as it may be time + # consuming. + if # can find a user cut + func = # computes function + set = # computes set + MOI.submit(optimizer, MOI.UserCut(callback_data), func, set) + end + end +end +``` """ -struct HeuristicCallback <: AbstractCallback end +struct InfeasibleSolutionCallback <: AbstractCallback end ## Model attributes @@ -784,25 +873,13 @@ end VariablePrimal() = VariablePrimal(1) """ - VariablePrimalAtIntegerNode(callback_data) - -A variable attribute for the assignment to some primal variable's value at the -integer node from which the [`LazyCallback`](@ref) corresponding to -`callback_data` was called. -""" -struct VariablePrimalAtIntegerNode <: AbstractVariableAttribute - callback_data::Any -end - -""" - VariablePrimalAtFractionalNode(callback_data) + CallbackVariablePrimal(callback_data) -A variable attribute for the assignment to some primal variable's value at the -fractional node from which the [`HeuristicCallback`](@ref) corresponding to -`callback_data` was called. +A variable attribute for the assignment to some primal variable's value during +the callback identified by `callback_data`. """ -struct VariablePrimalAtFractionalNode <: AbstractVariableAttribute - callback_data::Any +struct CallbackVariablePrimal{CBDT} <: AbstractVariableAttribute + callback_data::CBDT end """ From 30c16b9e41ff5b581dd8ecb56a26d839a48f3f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 22 Sep 2019 18:23:07 +0200 Subject: [PATCH 3/5] [LazyConstraint|Heuristic|UserCut]Callback --- docs/src/apireference.md | 6 +- src/attributes.jl | 115 +++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 63 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 03fc758fc4..308f4f1594 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -77,7 +77,6 @@ submit List of submittables ```@docs -RejectSolution LazyConstraint HeuristicSolution UserCut @@ -131,8 +130,9 @@ Silent TimeLimitSec RawParameter AbstractCallback -FeasibleSolutionCallback -InfeasibleSolutionCallback +LazyConstraintCallback +HeuristicCallback +UserCutCallback ``` List of attributes useful for optimizers diff --git a/src/attributes.jl b/src/attributes.jl index 2c2bc56aab..83001f8ca2 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -407,19 +407,6 @@ end ## Submittables -""" - RejectSolution(callback_data) - -Reject feasible solution provided by [`CallbackVariablePrimal`](@ref). - -This can be submitted only from the [`FeasibleSolutionCallback`](@ref). The -field `callback_data` is a solver-specific callback type that is passed as the -argument to the feasible solution callback. -""" -struct RejectSolution{CBDT} <: AbstractSubmittable - callback_data::CBDT -end - """ LazyConstraint(callback_data) @@ -427,7 +414,7 @@ Lazy constraint `func`-in-`set` submitted as `func, set`. The optimal solution returned by [`VariablePrimal`](@ref) will satisfy all lazy constraints that have been submitted. -This can be submitted only from the [`FeasibleSolutionCallback`](@ref). The +This can be submitted only from the [`LazyConstraint`](@ref). The field `callback_data` is a solver-specific callback type that is passed as the argument to the feasible solution callback. """ @@ -442,9 +429,9 @@ Heuristically obtained feasible solution. The solution is submitted as `variables, values` where `values[i]` gives the value of `variables[i]`, similarly to [`set`](@ref). -This can be submitted only from the [`InfeasibleSolutionCallback`](@ref). The +This can be submitted only from the [`HeuristicCallback`](@ref). The field `callback_data` is a solver-specific callback type that is passed as the -argument to the infeasible solution callback. +argument to the heuristic callback. Note that the solver may silently reject the provided solution. """ @@ -464,7 +451,7 @@ off. Note that, as opposed to [`LazyConstraint`](@ref), the provided constraint cannot modify the feasible set, the constraint should be redundant, e.g., it may be a consequence of affine and integrality constraints. -This can be submitted only from the [`InfeasibleSolutionCallback`](@ref). The +This can be submitted only from the [`UserCutCallback`](@ref). The field `callback_data` is a solver-specific callback type that is passed as the argument to the infeasible solution callback. @@ -565,16 +552,18 @@ to you to provide the appropriate one. If in doubt, give a complete solution. The value of the attribute should be a function taking only one argument, that is commonly called `callback_data`, that can be used for instance in -[`FeasibleSolutionCallback`](@ref), [`InfeasibleSolutionCallback`](@ref). +[`LazyConstraintCallback`](@ref), [`HeuristicCallback`](@ref) and +[`UserCutCallback`](@ref). """ abstract type AbstractCallback <: AbstractOptimizerAttribute end """ - FeasibleSolutionCallback() <: AbstractCallback + LazyConstraintCallback() <: AbstractCallback -The callback can be used to reject a feasible primal solution by submitting -[`RejectSolution`](@ref) and/or [`LazyConstraint`](@ref). Note that there -is no guarantee that the callback is called at *every* feasible primal solution. +The callback can be used to reduce the feasible set given the current primal +solution by submitting a [`LazyConstraint`](@ref). For instance, it may be +called at an incumbent of a mixed-integer problem. Note that there is no +guarantee that the callback is called at *every* feasible primal solution. The feasible primal solution is accessed through [`CallbackVariablePrimal`](@ref). Trying to access other result @@ -585,34 +574,23 @@ attributes will throw [`OptimizeInProgress`](@ref) as discussed in ```julia x = MOI.add_variables(optimizer, 8) -MOI.submit(optimizer, MOI.FeasibleSolutionCallback(), callback_data -> begin +MOI.submit(optimizer, MOI.LazyConstraintCallback(), callback_data -> begin sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) - if MOI.supports(optimizer, MOI.RejectSolution(callback_data)) - # determine if it should be rejected only if it's supported as it may - # be time consuming. - if # should reject - MOI.submit(optimizer, MOI.RejectSolution(callback_data)) - end - end - if MOI.supports(optimizer, MOI.LazyConstraint(callback_data)) - # determine the lazy constraint only if it's supported as it may be time - # consuming. - if # should add a lazy constraint - func = # computes function - set = # computes set - MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set) - end + if # should add a lazy constraint + func = # computes function + set = # computes set + MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set) end end ``` """ -struct FeasibleSolutionCallback <: AbstractCallback end +struct LazyConstraintCallback <: AbstractCallback end """ - InfeasibleSolutionCallback() <: AbstractCallback + HeuristicCallback() <: AbstractCallback -The callback can be used to submit [`HeuristicSolution`](@ref) and/or -[`UserCut`](@ref) given an infeasible primal solution. +The callback can be used to submit [`HeuristicSolution`](@ref) given the +current primal solution. For instance, it may be called at fractional (i.e., non-integer) nodes in the branch and bound tree of a mixed-integer problem. Note that there is not guarantee that the callback is called *everytime* the solver has an infeasible @@ -627,30 +605,47 @@ attributes will throw [`OptimizeInProgress`](@ref) as discussed in ```julia x = MOI.add_variables(optimizer, 8) -MOI.submit(optimizer, MOI.InfeasibleSolutionCallback(), callback_data -> begin +MOI.submit(optimizer, MOI.HeuristicCallback(), callback_data -> begin sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) - if MOI.supports(optimizer, MOI.HeuristicSolution(callback_data)) - # determine the heuristic solution only if it it's supported as it may - # be time consuming. - if # can find a heuristic solution - values = # computes heuristic solution - MOI.submit(optimizer, MOI.HeuristicSolution(callback_data), x, - values) - end + if # can find a heuristic solution + values = # computes heuristic solution + MOI.submit(optimizer, MOI.HeuristicSolution(callback_data), x, + values) end - if MOI.supports(optimizer, MOI.UserCut(callback_data)) - # determine the user cut only if it's supported as it may be time - # consuming. - if # can find a user cut - func = # computes function - set = # computes set - MOI.submit(optimizer, MOI.UserCut(callback_data), func, set) - end +end +``` +""" +struct HeuristicCallback <: AbstractCallback end + +""" + UserCutCallback() <: AbstractCallback + +The callback can be used to submit [`UserCut`](@ref) given the current primal +solution. For instance, it may be called at fractional (i.e., non-integer) nodes +in the branch and bound tree of a mixed-integer problem. Note that there is not +guarantee that the callback is called *everytime* the solver has an infeasible +solution. + +The infeasible solution is accessed through +[`CallbackVariablePrimal`](@ref). Trying to access other result +attributes will throw [`OptimizeInProgress`](@ref) as discussed in +[`AbstractCallback`](@ref). + +## Examples + +```julia +x = MOI.add_variables(optimizer, 8) +MOI.submit(optimizer, MOI.UserCutCallback(), callback_data -> begin + sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) + if # can find a user cut + func = # computes function + set = # computes set + MOI.submit(optimizer, MOI.UserCut(callback_data), func, set) end end ``` """ -struct InfeasibleSolutionCallback <: AbstractCallback end +struct UserCutCallback <: AbstractCallback end ## Model attributes From 5bc053c78b94cdfb2ad4302f5de74c721cc33240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 22 Sep 2019 23:43:29 +0200 Subject: [PATCH 4/5] Address comments --- src/attributes.jl | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/attributes.jl b/src/attributes.jl index 83001f8ca2..53c1f401ce 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -433,6 +433,9 @@ This can be submitted only from the [`HeuristicCallback`](@ref). The field `callback_data` is a solver-specific callback type that is passed as the argument to the heuristic callback. +Some solvers require a complete solution, others only partial solutions. It's up +to you to provide the appropriate one. If in doubt, give a complete solution. + Note that the solver may silently reject the provided solution. """ struct HeuristicSolution{CBDT} <: AbstractSubmittable @@ -547,8 +550,9 @@ attributes (i.e, the attributes `attr` such that `is_set_by_optimize(attr)`) may not be accessible from the callback hence trying to get result attributes might throw a [`OptimizeInProgress`](@ref) error. -Some solvers require a complete solution, others only partial solutions. It's up -to you to provide the appropriate one. If in doubt, give a complete solution. +At most one callback of each type can be registered. If an optimizer already +has a function for a callback type, and the user registers a new function, +then the old one is erased, and the new one is registered. The value of the attribute should be a function taking only one argument, that is commonly called `callback_data`, that can be used for instance in @@ -574,7 +578,7 @@ attributes will throw [`OptimizeInProgress`](@ref) as discussed in ```julia x = MOI.add_variables(optimizer, 8) -MOI.submit(optimizer, MOI.LazyConstraintCallback(), callback_data -> begin +MOI.set(optimizer, MOI.LazyConstraintCallback(), callback_data -> begin sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) if # should add a lazy constraint func = # computes function @@ -596,7 +600,7 @@ branch and bound tree of a mixed-integer problem. Note that there is not guarantee that the callback is called *everytime* the solver has an infeasible solution. -The infeasible solution is accessed through +The current primal solution is accessed through [`CallbackVariablePrimal`](@ref). Trying to access other result attributes will throw [`OptimizeInProgress`](@ref) as discussed in [`AbstractCallback`](@ref). @@ -605,7 +609,7 @@ attributes will throw [`OptimizeInProgress`](@ref) as discussed in ```julia x = MOI.add_variables(optimizer, 8) -MOI.submit(optimizer, MOI.HeuristicCallback(), callback_data -> begin +MOI.set(optimizer, MOI.HeuristicCallback(), callback_data -> begin sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) if # can find a heuristic solution values = # computes heuristic solution @@ -635,7 +639,7 @@ attributes will throw [`OptimizeInProgress`](@ref) as discussed in ```julia x = MOI.add_variables(optimizer, 8) -MOI.submit(optimizer, MOI.UserCutCallback(), callback_data -> begin +MOI.set(optimizer, MOI.UserCutCallback(), callback_data -> begin sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x) if # can find a user cut func = # computes function From 620fae9f660a512d6ee5782ac3ac10d82634140b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 23 Sep 2019 11:48:54 +0200 Subject: [PATCH 5/5] Address comments --- docs/src/apireference.md | 1 + src/attributes.jl | 88 ++++++++++++++++++++++++++-------------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 308f4f1594..acbec37714 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -78,6 +78,7 @@ List of submittables ```@docs LazyConstraint +HeuristicSolutionStatus HeuristicSolution UserCut ``` diff --git a/src/attributes.jl b/src/attributes.jl index 53c1f401ce..cc013c6ed2 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -380,8 +380,8 @@ end """ SettingSingleVariableFunctionNotAllowed() -Error type that should be thrown when the user [`set`](@ref) the -[`ConstraintFunction`](@ref) of a [`SingleVariable`](@ref) constraint. +Error type that should be thrown when the user calls [`set`](@ref) to change +the [`ConstraintFunction`](@ref) of a [`SingleVariable`](@ref) constraint. """ struct SettingSingleVariableFunctionNotAllowed <: Exception end @@ -414,45 +414,73 @@ Lazy constraint `func`-in-`set` submitted as `func, set`. The optimal solution returned by [`VariablePrimal`](@ref) will satisfy all lazy constraints that have been submitted. -This can be submitted only from the [`LazyConstraint`](@ref). The +This can be submitted only from the [`LazyConstraintCallback`](@ref). The field `callback_data` is a solver-specific callback type that is passed as the argument to the feasible solution callback. + +## Examples + +Suppose `fx = MOI.SingleVariable(x)` and `fx = MOI.SingleVariable(y)` +where `x` and `y` are [`VariableIndex`](@ref)s of `optimizer`. To add a +`LazyConstraint` for `2x + 3y <= 1`, write +```julia +func = 2.0fx + 3.0fy +set = MOI.LessThan(1.0) +MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set) +``` +inside a [`LazyConstraintCallback`](@ref) of data `callback_data`. """ -struct LazyConstraint{CBDT} <: AbstractSubmittable - callback_data::CBDT +struct LazyConstraint{CallbackDataType} <: AbstractSubmittable + callback_data::CallbackDataType end +""" + HeuristicSolutionStatus + +An Enum of possible return values for [`submit`](@ref) with +[`HeuristicSolution`](@ref). +This informs whether the heuristic solution was accepted or rejected. +Possible values are: +* `HEURISTIC_SOLUTION_ACCEPTED`: The heuristic solution was accepted. +* `HEURISTIC_SOLUTION_REJECTED`: The heuristic solution was rejected. +* `HEURISTIC_SOLUTION_UNKNOWN`: No information available on the acceptance. +""" +@enum(HeuristicSolutionStatus, + HEURISTIC_SOLUTION_ACCEPTED, + HEURISTIC_SOLUTION_REJECTED, + HEURISTIC_SOLUTION_UNKNOWN) + """ HeuristicSolution(callback_data) Heuristically obtained feasible solution. The solution is submitted as `variables, values` where `values[i]` gives the value of `variables[i]`, -similarly to [`set`](@ref). +similarly to [`set`](@ref). The [`submit`](@ref) call returns a +[`HeuristicSolutionStatus`](@ref) indicating whether the provided solution +was accepted or rejected. This can be submitted only from the [`HeuristicCallback`](@ref). The field `callback_data` is a solver-specific callback type that is passed as the argument to the heuristic callback. -Some solvers require a complete solution, others only partial solutions. It's up -to you to provide the appropriate one. If in doubt, give a complete solution. - -Note that the solver may silently reject the provided solution. +Some solvers require a complete solution, others only partial solutions. """ -struct HeuristicSolution{CBDT} <: AbstractSubmittable - callback_data::CBDT +struct HeuristicSolution{CallbackDataType} <: AbstractSubmittable + callback_data::CallbackDataType end """ UserCut(callback_data) -Constraint `func`-to-`set` suggested to render the solution given by -[`CallbackVariablePrimal`](@ref) trivially infeasible. The solution is submitted -as `func, set`. For instance, for a solution satisfying all constraints except -[`SingleVariable`](@ref)-in-[`Integer`](@ref) constraints, a -[`ScalarAffineFunction`](@ref)-in-[`LessThan`](@ref) may be submitted cut it -off. Note that, as opposed to [`LazyConstraint`](@ref), the provided constraint -cannot modify the feasible set, the constraint should be redundant, e.g., it -may be a consequence of affine and integrality constraints. +Constraint `func`-to-`set` suggested to help the solver detect the solution +given by [`CallbackVariablePrimal`](@ref) as infeasible. The cut is submitted +as `func, set`. +Typically [`CallbackVariablePrimal`](@ref) will violate integrality constraints, +and a cut would be of the form [`ScalarAffineFunction`](@ref)-in-[`LessThan`](@ref) +or [`ScalarAffineFunction`](@ref)-in-[`GreaterThan`](@ref). Note that, as +opposed to [`LazyConstraint`](@ref), the provided constraint cannot modify the +feasible set, the constraint should be redundant, e.g., it may be a consequence +of affine and integrality constraints. This can be submitted only from the [`UserCutCallback`](@ref). The field `callback_data` is a solver-specific callback type that is passed as the @@ -460,8 +488,8 @@ argument to the infeasible solution callback. Note that the solver may silently ignore the provided constraint. """ -struct UserCut{CBDT} <: AbstractSubmittable - callback_data::CBDT +struct UserCut{CallbackDataType} <: AbstractSubmittable + callback_data::CallbackDataType end ## Optimizer attributes @@ -529,7 +557,7 @@ end Error thrown from optimizer when `MOI.get(optimizer, attr)` is called inside an [`AbstractCallback`](@ref) while it is only defined once [`optimize!`](@ref) has -completed. This is only defined if `is_set_by_optimize(attr)` is `true`. +completed. This can only happen when `is_set_by_optimize(attr)` is `true`. """ struct OptimizeInProgress{AttrType<:AnyAttribute} <: Exception attr::AttrType @@ -543,19 +571,19 @@ end """ abstract type AbstractCallback <: AbstractOptimizerAttribute end -Abstract type for optimizer attribute representing a callback functions. The +Abstract type for optimizer attribute representing a callback function. The value set to subtypes of `AbstractCallback` is a function that may be called during [`optimize!`](@ref). As [`optimize!`](@ref) is in progress, the result attributes (i.e, the attributes `attr` such that `is_set_by_optimize(attr)`) -may not be accessible from the callback hence trying to get result attributes +may not be accessible from the callback, hence trying to get result attributes might throw a [`OptimizeInProgress`](@ref) error. At most one callback of each type can be registered. If an optimizer already has a function for a callback type, and the user registers a new function, -then the old one is erased, and the new one is registered. +then the old one is replaced. -The value of the attribute should be a function taking only one argument, that -is commonly called `callback_data`, that can be used for instance in +The value of the attribute should be a function taking only one argument, +commonly called `callback_data`, that can be used for instance in [`LazyConstraintCallback`](@ref), [`HeuristicCallback`](@ref) and [`UserCutCallback`](@ref). """ @@ -877,8 +905,8 @@ VariablePrimal() = VariablePrimal(1) A variable attribute for the assignment to some primal variable's value during the callback identified by `callback_data`. """ -struct CallbackVariablePrimal{CBDT} <: AbstractVariableAttribute - callback_data::CBDT +struct CallbackVariablePrimal{CallbackDataType} <: AbstractVariableAttribute + callback_data::CallbackDataType end """