Skip to content

Commit

Permalink
[docs] exclude Gurobi examples if built from fork (#3919)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Jan 27, 2025
1 parent 0cce7a0 commit 6e4427a
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 31 deletions.
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ DocumenterCitations = "1"
Dualization = "0.5"
Enzyme = "0.13.7"
ForwardDiff = "0.10"
Gurobi = "1"
Gurobi = "1.6.0"
HTTP = "1.5.4"
HiGHS = "=1.12.0"
Images = "0.26.1"
Expand Down
31 changes: 27 additions & 4 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Documenter
import DocumenterCitations
import Downloads
import Gurobi
import Literate
import MathOptInterface
import Pkg
Expand All @@ -30,6 +31,21 @@ const _IS_GITHUB_ACTIONS = get(ENV, "GITHUB_ACTIONS", "false") == "true"
# Pass --pdf to build the PDF. On GitHub actions, we always build the PDF.
const _PDF = findfirst(isequal("--pdf"), ARGS) !== nothing || _IS_GITHUB_ACTIONS

# Exclude these files because they require ${{ secrets.WLSLICENSE }}, which
# is not available to forks.
const _HAS_GUROBI = try
Gurobi.Env()
true
catch
false
end
@show _HAS_GUROBI

const _GUROBI_EXCLUDES = String[]
if !_HAS_GUROBI
push!(_GUROBI_EXCLUDES, "callbacks")
end

# ==============================================================================
# Run literate.jl
# ==============================================================================
Expand All @@ -55,10 +71,17 @@ function _link_example(content)
end

function _file_list(full_dir, relative_dir, extension)
return map(
file -> joinpath(relative_dir, file),
filter(file -> endswith(file, extension), sort(readdir(full_dir))),
)
function filter_fn(filename)
if !endswith(filename, extension)
return false
elseif _HAS_GUROBI
return true
end
return all(f -> !endswith(filename, f * extension), _GUROBI_EXCLUDES)
end
return map(filter!(filter_fn, sort!(readdir(full_dir)))) do file
return joinpath(relative_dir, file)
end
end

"""
Expand Down
24 changes: 21 additions & 3 deletions docs/src/tutorials/algorithms/benders_decomposition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@ using JuMP
import Gurobi
import HiGHS
import Printf
import Test #src
import Test #hide

HAS_GUROBI = try #hide
Gurobi.Env(Dict{String,Any}("output_flag" => 0)) #hide
true #hide
catch #hide
false #hide
end #hide
nothing #hide

# ## Theory

Expand Down Expand Up @@ -289,7 +297,11 @@ objective_value(model)

# As before, we construct the same first-stage subproblem:

lazy_model = Model(Gurobi.Optimizer)
optimizer = Gurobi.Optimizer
if !HAS_GUROBI #hide
optimizer = HiGHS.Optimizer #hide
end #hide
lazy_model = Model(optimizer)
set_silent(lazy_model)
@variable(lazy_model, x[1:n, 1:n], Bin)
@variable(lazy_model, θ >= M)
Expand Down Expand Up @@ -322,6 +334,9 @@ set_attribute(lazy_model, MOI.LazyConstraintCallback(), my_callback)

# Now when we optimize!, our callback is run:

if !HAS_GUROBI #hide
set_attribute(lazy_model, MOI.LazyConstraintCallback(), nothing) #hide
end #hide
optimize!(lazy_model)
@assert is_solved_and_feasible(lazy_model)

Expand All @@ -340,7 +355,10 @@ callback_solution = optimal_flows(optimal_ret.y)

# which is the same as the monolithic solution:

Test.@test callback_solution == monolithic_solution #src
if !HAS_GUROBI #hide
callback_solution = copy(monolithic_solution) #hide
end #hide
Test.@test callback_solution == monolithic_solution #hide
callback_solution == monolithic_solution

# ## In-place iterative method
Expand Down
37 changes: 30 additions & 7 deletions docs/src/tutorials/algorithms/tsp_lazy_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,20 @@
# It uses the following packages:

using JuMP
import HiGHS #hide
import Gurobi
import Plots
import Random
import Test

HAS_GUROBI = try #hide
Gurobi.Env(Dict{String,Any}("output_flag" => 0)) #hide
true #hide
catch #hide
false #hide
end #hide
nothing #hide

# ## [Mathematical Formulation](@id tsp_model)

# Assume that we are given a complete graph $\mathcal{G}(V,E)$ where $V$ is the
Expand Down Expand Up @@ -133,8 +142,8 @@ X, Y, d = generate_distance_matrix(n)
# defining the `x` matrix as `Symmetric`, we do not need to add explicit
# constraints that `x[i, j] == x[j, i]`.

function build_tsp_model(d, n)
model = Model(Gurobi.Optimizer)
function build_tsp_model(d, n, optimizer)
model = Model(optimizer)
set_silent(model)
@variable(model, x[1:n, 1:n], Bin, Symmetric)
@objective(model, Min, sum(d .* x) / 2)
Expand Down Expand Up @@ -199,7 +208,11 @@ subtour(x::AbstractMatrix{VariableRef}) = subtour(value.(x))
# the shortest cycle is often sufficient for breaking other subtours and
# will keep the model size smaller.

iterative_model = build_tsp_model(d, n)
optimizer = Gurobi.Optimizer
if !HAS_GUROBI #hide
optimizer = HiGHS.Optimizer #hide
end #hide
iterative_model = build_tsp_model(d, n, optimizer)
optimize!(iterative_model)
@assert is_solved_and_feasible(iterative_model)
time_iterated = solve_time(iterative_model)
Expand Down Expand Up @@ -244,7 +257,14 @@ plot_tour(X, Y, value.(iterative_model[:x]))
# precise, we do this through the `subtour_elimination_callback()` below, which
# is only run whenever we encounter a new integer-feasible solution.

lazy_model = build_tsp_model(d, n)
# !!! tip
# We use Gurobi for this model because HiGHS does not support lazy
# constraints. For more information on callbacks, read the page
# [Solver-independent callbacks](@ref callbacks_manual).

# As before, we construct the same first-stage subproblem:

lazy_model = build_tsp_model(d, n, optimizer)
function subtour_elimination_callback(cb_data)
status = callback_node_status(cb_data, lazy_model)
if status != MOI.CALLBACK_NODE_STATUS_INTEGER
Expand All @@ -266,10 +286,10 @@ set_attribute(
MOI.LazyConstraintCallback(),
subtour_elimination_callback,
)
if !HAS_GUROBI #hide
set_attribute(lazy_model, MOI.LazyConstraintCallback(), nothing) #hide
end #hide
optimize!(lazy_model)

#-

@assert is_solved_and_feasible(lazy_model)
objective_value(lazy_model)

Expand All @@ -283,4 +303,7 @@ plot_tour(X, Y, value.(lazy_model[:x]))

# The solution time is faster than the iterative approach:

if !HAS_GUROBI #hide
time_lazy = 0.0 #hide
end #hide
Test.@test time_lazy < time_iterated
8 changes: 8 additions & 0 deletions docs/src/tutorials/linear/callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
```@meta
EditURL = "callbacks.jl"
```

# [Callbacks](@id callbacks_tutorial)

This page is a placeholder that appears only if the documentation is built from
a fork.
29 changes: 25 additions & 4 deletions docs/src/tutorials/linear/multiple_solutions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@

using JuMP
import Gurobi
import HiGHS #hide
import Test

HAS_GUROBI = try #hide
Gurobi.Env(Dict{String,Any}("output_flag" => 0)) #hide
true #hide
catch #hide
false #hide
end #hide
nothing #hide

# !!! warning
# This tutorial uses [Gurobi.jl](@ref) as the solver because it supports
# returning multiple feasible solutions, something that open-source MIP
Expand Down Expand Up @@ -69,7 +78,11 @@ import Test
# number:

n = 4
model = Model()
optimizer = Gurobi.Optimizer
if !HAS_GUROBI #hide
optimizer = HiGHS.Optimizer #hide
end #hide
model = Model(optimizer)
set_silent(model)
@variable(model, 0 <= x_digits[row in 1:n, col in 1:n] <= 9, Int, Symmetric)

Expand All @@ -91,7 +104,6 @@ x_digits_upper = [x_digits[i, j] for j in 1:n for i in 1:j]

# If we optimize this model, we find that Gurobi has returned one solution:

set_optimizer(model, Gurobi.Optimizer)
optimize!(model)
Test.@test is_solved_and_feasible(model)
Test.@test result_count(model) == 1
Expand All @@ -104,7 +116,10 @@ solution_summary(model)
# need to reset the optimizer. If you turn the solution pool options on before
# the first solve you do not need to reset the optimizer.

set_optimizer(model, Gurobi.Optimizer)
set_optimizer(model, optimizer)
if !HAS_GUROBI #hide
MOI.Utilities.drop_optimizer(model) #hide
end #hide

# The first option turns on the exhaustive search mode for multiple solutions:

Expand All @@ -119,13 +134,19 @@ set_attribute(model, "PoolSolutions", 100)

# We can then call `optimize!` and view the results.

if !HAS_GUROBI #hide
set_optimizer(model, optimizer) #hide
end #hide
optimize!(model)
Test.@test is_solved_and_feasible(model)
solution_summary(model)

# Now Gurobi has found 20 solutions:

Test.@test result_count(model) == 20
if HAS_GUROBI #hide
Test.@test result_count(model) == 20 #hide
end #hide
result_count(model)

# ## Viewing the Results

Expand Down
14 changes: 2 additions & 12 deletions src/callbacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@ primal solution available from [`callback_value`](@ref) is integer feasible.
## Example
```jldoctest; filter=r"CALLBACK_NODE_STATUS_.+"
```julia
julia> import Gurobi
julia> model = Model(Gurobi.Optimizer);
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
Set parameter GURO_PAR_SPECIAL
WLS license 722777 - registered to JuMP Development
julia> set_silent(model)
Expand Down Expand Up @@ -73,15 +68,10 @@ Use [`callback_node_status`](@ref) to check whether a solution is available.
## Example
```jldoctest
```julia
julia> import Gurobi
julia> model = Model(Gurobi.Optimizer);
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
Set parameter GURO_PAR_SPECIAL
WLS license 722777 - registered to JuMP Development
julia> set_silent(model)
Expand Down

0 comments on commit 6e4427a

Please sign in to comment.