Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ DataFrames = "1.8.1"
DifferentiationInterface = "0.7.13"
DimensionalData = "=0.29.25"
Distributions = "0.25.123"
Documenter = "1.16.1"
Documenter = "=1.15.0"
DocumenterCitations = "1.4.1"
Dualization = "0.7.1"
Enzyme = "0.13.112"
Expand Down
4 changes: 2 additions & 2 deletions docs/src/manual/containers.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ julia> DataFrames.DataFrame(table)
6 │ 2 3 (2, 3)
```

## DenseAxisArray
## ## [DenseAxisArray](@id manual_dense_axis_array)

A [`Containers.DenseAxisArray`](@ref) is created when the index sets are
rectangular, but not of the form `1:n`. The index sets can be of any type.
Expand Down Expand Up @@ -275,7 +275,7 @@ And data, a 2-element Vector{Tuple{Int64, Symbol}}:
(2, :B)
```

## SparseAxisArray
## [SparseAxisArray](@id manual_sparse_axis_array)

A [`Containers.SparseAxisArray`](@ref) is created when the index sets are
non-rectangular. This occurs in two circumstances:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/tutorials/algorithms/pdhg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ end

MOI.get(::Optimizer, ::MOI.SolverName) = "PDHG"

# ### GenericModel
# ### [GenericModel](@id tutorial_pdhg_generic_model)

# The simplest way to solve a problem with your optimizer is to implement the
# method `MOI.optimize!(dest::Optimizer, src::MOI.ModelLike)`, where `src` is an
Expand Down
216 changes: 214 additions & 2 deletions docs/src/tutorials/linear/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,217 @@ EditURL = "callbacks.jl"

# [Callbacks](@id callbacks_tutorial)

This page is a placeholder that appears only if the documentation is built from
a fork.
_This tutorial was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl)._
[_Download the source as a `.jl` file_](callbacks.jl).

The purpose of the tutorial is to demonstrate the various solver-independent
and solver-dependent callbacks that are supported by JuMP.

The tutorial uses the following packages:

````@example callbacks
using JuMP
import Gurobi
import Random
import Test
````

!!! info
This tutorial uses the [MathOptInterface](@ref moi_documentation) API.
By default, JuMP exports the `MOI` symbol as an alias for the
MathOptInterface.jl package. We recommend making this more explicit in
your code by adding the following lines:
```julia
import MathOptInterface as MOI
```

## Lazy constraints

An example using a lazy constraint callback.

````@example callbacks
function example_lazy_constraint()
model = Model(Gurobi.Optimizer)
set_silent(model)
@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)
lazy_called = false
function my_callback_function(cb_data)
lazy_called = true
x_val = callback_value(cb_data, x)
y_val = callback_value(cb_data, y)
println("Called from (x, y) = ($x_val, $y_val)")
status = callback_node_status(cb_data, model)
if status == MOI.CALLBACK_NODE_STATUS_FRACTIONAL
println(" - Solution is integer infeasible!")
elseif status == MOI.CALLBACK_NODE_STATUS_INTEGER
println(" - Solution is integer feasible!")
else
@assert status == MOI.CALLBACK_NODE_STATUS_UNKNOWN
println(" - I don't know if the solution is integer feasible :(")
end
if y_val - x_val > 1 + 1e-6
con = @build_constraint(y - x <= 1)
println("Adding $(con)")
MOI.submit(model, MOI.LazyConstraint(cb_data), con)
elseif y_val + x_val > 3 + 1e-6
con = @build_constraint(y + x <= 3)
println("Adding $(con)")
MOI.submit(model, MOI.LazyConstraint(cb_data), con)
end
return
end
set_attribute(model, MOI.LazyConstraintCallback(), my_callback_function)
optimize!(model)
assert_is_solved_and_feasible(model)
Test.@test lazy_called
Test.@test value(x) == 1
Test.@test value(y) == 2
println("Optimal solution (x, y) = ($(value(x)), $(value(y)))")
return
end

example_lazy_constraint()
````

## User-cuts

An example using a user-cut callback.

````@example callbacks
function example_user_cut_constraint()
Random.seed!(1)
N = 30
item_weights, item_values = rand(N), rand(N)
model = Model(Gurobi.Optimizer)
set_silent(model)
# Turn off "Cuts" parameter so that our new one must be called. In real
# models, you should leave "Cuts" turned on.
set_attribute(model, "Cuts", 0)
@variable(model, x[1:N], Bin)
@constraint(model, sum(item_weights[i] * x[i] for i in 1:N) <= 10)
@objective(model, Max, sum(item_values[i] * x[i] for i in 1:N))
callback_called = false
function my_callback_function(cb_data)
callback_called = true
x_vals = callback_value.(Ref(cb_data), x)
accumulated = sum(item_weights[i] for i in 1:N if x_vals[i] > 1e-4)
println("Called with accumulated = $(accumulated)")
n_terms = sum(1 for i in 1:N if x_vals[i] > 1e-4)
if accumulated > 10
con = @build_constraint(
sum(x[i] for i in 1:N if x_vals[i] > 0.5) <= n_terms - 1
)
println("Adding $(con)")
MOI.submit(model, MOI.UserCut(cb_data), con)
end
end
set_attribute(model, MOI.UserCutCallback(), my_callback_function)
optimize!(model)
assert_is_solved_and_feasible(model)
Test.@test callback_called
@show callback_called
return
end

example_user_cut_constraint()
````

## Heuristic solutions

An example using a heuristic solution callback.

````@example callbacks
function example_heuristic_solution()
Random.seed!(1)
N = 30
item_weights, item_values = rand(N), rand(N)
model = Model(Gurobi.Optimizer)
set_silent(model)
# Turn off "Heuristics" parameter so that our new one must be called. In
# real models, you should leave "Heuristics" turned on.
set_attribute(model, "Heuristics", 0)
@variable(model, x[1:N], Bin)
@constraint(model, sum(item_weights[i] * x[i] for i in 1:N) <= 10)
@objective(model, Max, sum(item_values[i] * x[i] for i in 1:N))
callback_called = false
function my_callback_function(cb_data)
callback_called = true
x_vals = callback_value.(Ref(cb_data), x)
ret =
MOI.submit(model, MOI.HeuristicSolution(cb_data), x, floor.(x_vals))
println("Heuristic solution status = $(ret)")
Test.@test ret in (
MOI.HEURISTIC_SOLUTION_ACCEPTED,
MOI.HEURISTIC_SOLUTION_REJECTED,
)
end
set_attribute(model, MOI.HeuristicCallback(), my_callback_function)
optimize!(model)
assert_is_solved_and_feasible(model)
Test.@test callback_called
return
end

example_heuristic_solution()
````

## Gurobi solver-dependent callback

An example using Gurobi's solver-dependent callback.

````@example callbacks
function example_solver_dependent_callback()
model = direct_model(Gurobi.Optimizer())
@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)
cb_calls = Cint[]
function my_callback_function(cb_data, cb_where::Cint)
# You can reference variables outside the function as normal
push!(cb_calls, cb_where)
# You can select where the callback is run
if cb_where == Gurobi.GRB_CB_MIPNODE
# You can query a callback attribute using GRBcbget
resultP = Ref{Cint}()
Gurobi.GRBcbget(
cb_data,
cb_where,
Gurobi.GRB_CB_MIPNODE_STATUS,
resultP,
)
if resultP[] != Gurobi.GRB_OPTIMAL
return # Solution is something other than optimal.
end
elseif cb_where != Gurobi.GRB_CB_MIPSOL
return
end
# Before querying `callback_value`, you must call:
Gurobi.load_callback_variable_primal(cb_data, cb_where)
x_val = callback_value(cb_data, x)
y_val = callback_value(cb_data, y)
# You can submit solver-independent MathOptInterface attributes such as
# lazy constraints, user-cuts, and heuristic solutions.
if y_val - x_val > 1 + 1e-6
con = @build_constraint(y - x <= 1)
MOI.submit(model, MOI.LazyConstraint(cb_data), con)
elseif y_val + x_val > 3 + 1e-6
con = @build_constraint(y + x <= 3)
MOI.submit(model, MOI.LazyConstraint(cb_data), con)
end
# You can terminate the callback as follows:
Gurobi.GRBterminate(backend(model))
return
end
# You _must_ set this parameter if using lazy constraints.
set_attribute(model, "LazyConstraints", 1)
set_attribute(model, Gurobi.CallbackFunction(), my_callback_function)
optimize!(model)
Test.@test termination_status(model) == MOI.INTERRUPTED
return
end

example_solver_dependent_callback()
````

Loading