diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 48e01eec4b..acbec37714 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -74,6 +74,15 @@ AbstractSubmittable submit ``` +List of submittables + +```@docs +LazyConstraint +HeuristicSolutionStatus +HeuristicSolution +UserCut +``` + ## Model Interface ```@docs @@ -121,6 +130,10 @@ SolverName Silent TimeLimitSec RawParameter +AbstractCallback +LazyConstraintCallback +HeuristicCallback +UserCutCallback ``` List of attributes useful for optimizers @@ -219,6 +232,7 @@ Calls to `get` and `set` should include as an argument a single `VariableIndex` VariableName VariablePrimalStart VariablePrimal +CallbackVariablePrimal ``` ### Constraints @@ -438,6 +452,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..cc013c6ed2 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 calls [`set`](@ref) to change +the [`ConstraintFunction`](@ref) of a [`SingleVariable`](@ref) constraint. +""" +struct SettingSingleVariableFunctionNotAllowed <: Exception end + """ submit(optimizer::AbstractOptimizer, sub::AbstractSubmittable, values...)::Nothing @@ -397,13 +405,92 @@ function submit(model::ModelLike, sub::AbstractSubmittable, args...) end end +## Submittables + """ - SettingSingleVariableFunctionNotAllowed() + LazyConstraint(callback_data) + +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 [`LazyConstraintCallback`](@ref). The +field `callback_data` is a solver-specific callback type that is passed as the +argument to the feasible solution callback. + +## Examples -Error type that should be thrown when the user [`set`](@ref) the -[`ConstraintFunction`](@ref) of a [`SingleVariable`](@ref) constraint. +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 SettingSingleVariableFunctionNotAllowed <: Exception end +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). 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. +""" +struct HeuristicSolution{CallbackDataType} <: AbstractSubmittable + callback_data::CallbackDataType +end + +""" + UserCut(callback_data) + +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 +argument to the infeasible solution callback. + +Note that the solver may silently ignore the provided constraint. +""" +struct UserCut{CallbackDataType} <: AbstractSubmittable + callback_data::CallbackDataType +end ## Optimizer attributes @@ -461,6 +548,137 @@ 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 can only happen when `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 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 +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 replaced. + +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). +""" +abstract type AbstractCallback <: AbstractOptimizerAttribute end + +""" + LazyConstraintCallback() <: AbstractCallback + +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 +attributes will throw [`OptimizeInProgress`](@ref) as discussed in +[`AbstractCallback`](@ref). + +## Examples + +```julia +x = MOI.add_variables(optimizer, 8) +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 + set = # computes set + MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set) + end +end +``` +""" +struct LazyConstraintCallback <: AbstractCallback end + +""" + HeuristicCallback() <: AbstractCallback + +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 +solution. + +The current 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.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 + MOI.submit(optimizer, MOI.HeuristicSolution(callback_data), x, + values) + 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.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 + set = # computes set + MOI.submit(optimizer, MOI.UserCut(callback_data), func, set) + end +end +``` +""" +struct UserCutCallback <: AbstractCallback end + ## Model attributes """ @@ -681,6 +899,16 @@ struct VariablePrimal <: AbstractVariableAttribute end VariablePrimal() = VariablePrimal(1) +""" + CallbackVariablePrimal(callback_data) + +A variable attribute for the assignment to some primal variable's value during +the callback identified by `callback_data`. +""" +struct CallbackVariablePrimal{CallbackDataType} <: AbstractVariableAttribute + callback_data::CallbackDataType +end + """ BasisStatusCode