Skip to content

Commit

Permalink
Add assert_is_solved_and_feasible (#3925)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Jan 28, 2025
1 parent 6e4427a commit 3b98ac1
Show file tree
Hide file tree
Showing 59 changed files with 295 additions and 179 deletions.
20 changes: 10 additions & 10 deletions docs/src/tutorials/algorithms/benders_decomposition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ set_silent(model)
@constraint(model, [i = 2:n-1], sum(y[i, :]) == sum(y[:, i]))
@objective(model, Min, 0.1 * sum(x) - sum(y[1, :]))
optimize!(model)
Test.@test is_solved_and_feasible(model) #src
assert_is_solved_and_feasible(model) #src
solution_summary(model)

# The optimal objective value is -5.1:
Expand Down Expand Up @@ -218,7 +218,7 @@ function solve_subproblem(x_bar)
@constraint(model, [i = 2:n-1], sum(y[i, :]) == sum(y[:, i]))
@objective(model, Min, -sum(y[1, :]))
optimize!(model)
@assert is_solved_and_feasible(model; dual = true)
assert_is_solved_and_feasible(model; dual = true)
return (obj = objective_value(model), y = value.(y), π = reduced_cost.(x))
end

Expand Down Expand Up @@ -249,7 +249,7 @@ ABSOLUTE_OPTIMALITY_GAP = 1e-6
println("Iteration Lower Bound Upper Bound Gap")
for k in 1:MAXIMUM_ITERATIONS
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
lower_bound = objective_value(model)
x_k = value.(x)
ret = solve_subproblem(x_k)
Expand All @@ -267,7 +267,7 @@ end
# Finally, we can obtain the optimal solution:

optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
x_optimal = value.(x)
optimal_ret = solve_subproblem(x_optimal)
iterative_solution = optimal_flows(optimal_ret.y)
Expand Down Expand Up @@ -338,7 +338,7 @@ if !HAS_GUROBI #hide
set_attribute(lazy_model, MOI.LazyConstraintCallback(), nothing) #hide
end #hide
optimize!(lazy_model)
@assert is_solved_and_feasible(lazy_model)
assert_is_solved_and_feasible(lazy_model)

# For this model, the callback algorithm required more solves of the subproblem:

Expand Down Expand Up @@ -396,7 +396,7 @@ subproblem
function solve_subproblem(model, x)
fix.(model[:x_copy], x)
optimize!(model)
@assert is_solved_and_feasible(model; dual = true)
assert_is_solved_and_feasible(model; dual = true)
return (
obj = objective_value(model),
y = value.(model[:y]),
Expand All @@ -409,7 +409,7 @@ end
println("Iteration Lower Bound Upper Bound Gap")
for k in 1:MAXIMUM_ITERATIONS
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
lower_bound = objective_value(model)
x_k = value.(x)
ret = solve_subproblem(subproblem, x_k)
Expand All @@ -427,7 +427,7 @@ end
# Finally, we can obtain the optimal solution:

optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
x_optimal = value.(x)
optimal_ret = solve_subproblem(subproblem, x_optimal)
inplace_solution = optimal_flows(optimal_ret.y)
Expand Down Expand Up @@ -504,7 +504,7 @@ end
println("Iteration Lower Bound Upper Bound Gap")
for k in 1:MAXIMUM_ITERATIONS
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
lower_bound = objective_value(model)
x_k = value.(x)
ret = solve_subproblem_with_feasibility(subproblem, x_k)
Expand All @@ -528,7 +528,7 @@ end
# Finally, we can obtain the optimal solution:

optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
x_optimal = value.(x)
optimal_ret = solve_subproblem(subproblem, x_optimal)
feasible_inplace_solution = optimal_flows(optimal_ret.y)
Expand Down
10 changes: 5 additions & 5 deletions docs/src/tutorials/algorithms/cutting_stock_column_generation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ set_silent(model)
@objective(model, Min, sum(x))
@constraint(model, demand[i in 1:I], patterns[i]' * x >= data.pieces[i].d)
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
solution_summary(model)

# This solution requires 421 rolls. This solution is sub-optimal because the
Expand All @@ -253,7 +253,7 @@ solution_summary(model)

unset_integer.(x)
optimize!(model)
@assert is_solved_and_feasible(model; dual = true)
assert_is_solved_and_feasible(model; dual = true)
π_13 = dual(demand[13])

# Using the economic interpretation of the dual variable, we can say that a one
Expand Down Expand Up @@ -284,7 +284,7 @@ function solve_pricing(data::Data, π::Vector{Float64})
@constraint(model, sum(data.pieces[i].w * y[i] for i in 1:I) <= data.W)
@objective(model, Max, sum(π[i] * y[i] for i in 1:I))
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
number_of_rolls_saved = objective_value(model)
if number_of_rolls_saved > 1 + 1e-8
## Benefit of pattern is more than the cost of a new roll plus some
Expand Down Expand Up @@ -315,7 +315,7 @@ solve_pricing(data, zeros(I))
while true
## Solve the linear relaxation
optimize!(model)
@assert is_solved_and_feasible(model; dual = true)
assert_is_solved_and_feasible(model; dual = true)
## Obtain a new dual vector
π = dual.(demand)
## Solve the pricing problem
Expand Down Expand Up @@ -366,7 +366,7 @@ sum(ceil.(Int, solution.rolls))

set_integer.(x)
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
solution = DataFrames.DataFrame([
(pattern = p, rolls = value(x_p)) for (p, x_p) in enumerate(x)
])
Expand Down
4 changes: 2 additions & 2 deletions docs/src/tutorials/algorithms/parallelism.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ julia> function a_good_way_to_use_threading()
@variable(model, x >= i)
@objective(model, Min, x)
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
Threads.lock(my_lock) do
push!(solutions, i => objective_value(model))
end
Expand Down Expand Up @@ -357,7 +357,7 @@ julia> Distributed.@everywhere begin
@objective(model, Min, x)
set_lower_bound(x, i)
optimize!(model)
@assert is_solved_and_feasible(sudoku)
assert_is_solved_and_feasible(sudoku)
return objective_value(model)
end
end
Expand Down
6 changes: 3 additions & 3 deletions docs/src/tutorials/algorithms/tsp_lazy_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ if !HAS_GUROBI #hide
end #hide
iterative_model = build_tsp_model(d, n, optimizer)
optimize!(iterative_model)
@assert is_solved_and_feasible(iterative_model)
assert_is_solved_and_feasible(iterative_model)
time_iterated = solve_time(iterative_model)
cycle = subtour(iterative_model[:x])
while 1 < length(cycle) < n
Expand All @@ -225,7 +225,7 @@ while 1 < length(cycle) < n
sum(iterative_model[:x][i, j] for (i, j) in S) <= length(cycle) - 1,
)
optimize!(iterative_model)
@assert is_solved_and_feasible(iterative_model)
assert_is_solved_and_feasible(iterative_model)
global time_iterated += solve_time(iterative_model)
global cycle = subtour(iterative_model[:x])
end
Expand Down Expand Up @@ -290,7 +290,7 @@ if !HAS_GUROBI #hide
set_attribute(lazy_model, MOI.LazyConstraintCallback(), nothing) #hide
end #hide
optimize!(lazy_model)
@assert is_solved_and_feasible(lazy_model)
assert_is_solved_and_feasible(lazy_model)
objective_value(lazy_model)

#-
Expand Down
8 changes: 4 additions & 4 deletions docs/src/tutorials/applications/optimal_power_flow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# This tutorial takes a matrix-oriented approach focused on network nodes
# that simplifies the construction of semidefinite programs.
# Another approach is to formulate the problem focusing on network lines
# (known as a _branch model_) where it is easier to work with flow
# (known as a _branch model_) where it is easier to work with flow
# constraints. A general approach is provided by
# [PowerModels.jl](https://lanl-ansi.github.io/PowerModels.jl/stable/),
# an open-source framework to a broad range of power flow model formulations
Expand Down Expand Up @@ -137,7 +137,7 @@ println("Objective value (basic lower bound) : $basic_lower_bound")

@constraint(model, sum(P_G) >= sum(P_Demand))
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
better_lower_bound = round(objective_value(model); digits = 2)
println("Objective value (better lower bound): $better_lower_bound")

Expand Down Expand Up @@ -281,7 +281,7 @@ P_G = real(S_G)
# We're finally ready to solve our nonlinear AC-OPF problem:

optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
Test.@test isapprox(objective_value(model), 3087.84; atol = 1e-2) #src
solution_summary(model)

Expand Down Expand Up @@ -420,7 +420,7 @@ optimize!(model)

#-

Test.@test is_solved_and_feasible(model; allow_almost = true)
assert_is_solved_and_feasible(model; allow_almost = true)
sdp_relaxation_lower_bound = round(objective_value(model); digits = 2)
Test.@test isapprox(sdp_relaxation_lower_bound, 2753.04; rtol = 1e-3) #src
println(
Expand Down
6 changes: 3 additions & 3 deletions docs/src/tutorials/applications/power_systems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function solve_economic_dispatch(generators::Vector, wind, scenario)
@constraint(model, sum(g[i] for i in 1:N) + w == scenario.demand)
## Solve statement
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
## return the optimal value of the objective function and its minimizers
return (
g = value.(g),
Expand Down Expand Up @@ -217,7 +217,7 @@ function solve_economic_dispatch_inplace(
wind.variable_cost * w,
)
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
push!(obj_out, objective_value(model))
push!(w_out, value(w))
push!(g1_out, value(g[1]))
Expand Down Expand Up @@ -528,7 +528,7 @@ function solve_nonlinear_economic_dispatch(
)
@constraint(model, sum(g[i] for i in 1:N) + sqrt(w) == scenario.demand)
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
return (
g = value.(g),
w = value(w),
Expand Down
6 changes: 3 additions & 3 deletions docs/src/tutorials/applications/two_stage_stochastic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ set_silent(model)
@expression(model, z[ω in Ω], 5y[ω] - 0.1 * (x - y[ω]))
@objective(model, Max, -2x + sum(P[ω] * z[ω] for ω in Ω))
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
solution_summary(model)

# The optimal number of pies to make is:
Expand Down Expand Up @@ -159,7 +159,7 @@ function CVaR(Z::Vector{Float64}, P::Vector{Float64}; γ::Float64)
@constraint(model, [i in 1:N], z[i] >= ξ - Z[i])
@objective(model, Max, ξ - 1 / γ * sum(P[i] * z[i] for i in 1:N))
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
return objective_value(model)
end

Expand Down Expand Up @@ -218,7 +218,7 @@ set_silent(model)
@constraint(model, [ω in Ω], z[ω] >= ξ - Z[ω])
@objective(model, Max, -2x + ξ - 1 / γ * sum(P[ω] * z[ω] for ω in Ω))
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)

# When ``\gamma = 0.4``, the optimal number of pies to bake is:

Expand Down
6 changes: 3 additions & 3 deletions docs/src/tutorials/conic/arbitrary_precision.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ print(model)
# Let's solve and inspect the solution:

optimize!(model)
@assert is_solved_and_feasible(model; dual = true)
assert_is_solved_and_feasible(model; dual = true)
solution_summary(model)

# The value of each decision variable is a `BigFloat`:
Expand All @@ -103,7 +103,7 @@ value.(x) .- [3 // 7, 3 // 14]
set_attribute(model, "tol_gap_abs", 1e-32)
set_attribute(model, "tol_gap_rel", 1e-32)
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
value.(x) .- [3 // 7, 3 // 14]

# ## Rational arithmetic
Expand Down Expand Up @@ -145,7 +145,7 @@ print(model)
# Let's solve and inspect the solution:

optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
solution_summary(model)

# The optimal values are given in exact rational arithmetic:
Expand Down
8 changes: 4 additions & 4 deletions docs/src/tutorials/conic/dualization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ print(model_dual)

set_optimizer(model_primal, SCS.Optimizer)
optimize!(model_primal)
@assert is_solved_and_feasible(model_primal; dual = true)
assert_is_solved_and_feasible(model_primal; dual = true)

# (There are five rows in the constraint matrix because SCS expects problems in
# geometric conic form, and so JuMP has reformulated the `X, PSD` variable
Expand All @@ -157,7 +157,7 @@ objective_value(model_primal)

set_optimizer(model_dual, SCS.Optimizer)
optimize!(model_dual)
@assert is_solved_and_feasible(model_dual; dual = true)
assert_is_solved_and_feasible(model_dual; dual = true)

# and the solution we obtain is:

Expand Down Expand Up @@ -187,7 +187,7 @@ objective_value(model_dual)

set_optimizer(model_primal, Dualization.dual_optimizer(SCS.Optimizer))
optimize!(model_primal)
@assert is_solved_and_feasible(model_primal; dual = true)
assert_is_solved_and_feasible(model_primal; dual = true)

# The performance is the same as if we solved `model_dual`, and the correct
# solution is returned to `X`:
Expand All @@ -203,7 +203,7 @@ dual.(primal_c)

set_optimizer(model_dual, Dualization.dual_optimizer(SCS.Optimizer))
optimize!(model_dual)
@assert is_solved_and_feasible(model_dual; dual = true)
assert_is_solved_and_feasible(model_dual; dual = true)

#-

Expand Down
6 changes: 3 additions & 3 deletions docs/src/tutorials/conic/ellipse_approx.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ m, n = size(S)
@constraint(model, [t; vec(Z)] in MOI.RootDetConeSquare(n))
@objective(model, Max, t)
optimize!(model)
Test.@test is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
solution_summary(model)

# ## Results
Expand Down Expand Up @@ -212,7 +212,7 @@ f = [1 - S[i, :]' * Z * S[i, :] + 2 * S[i, :]' * z - s for i in 1:m]
## The former @objective(model, Max, t)
@objective(model, Max, 1 * t + 0)
optimize!(model)
Test.@test is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
Test.@test isapprox(D, value.(Z); atol = 1e-3) #src
solve_time_1 = solve_time(model)

Expand All @@ -235,7 +235,7 @@ print_active_bridges(model)

remove_bridge(model, MOI.Bridges.Constraint.GeoMeanToPowerBridge)
optimize!(model)
Test.@test is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)

# This time, the solve took:

Expand Down
4 changes: 2 additions & 2 deletions docs/src/tutorials/conic/ellipse_fitting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ for (i, cluster) in enumerate(clusters)
)
@objective(model, Min, ζ)
optimize!(model)
@assert is_solved_and_feasible(model)
assert_is_solved_and_feasible(model)
Q, d, e = value.(model[:Q]), value.(model[:d]), value.(model[:e])
push!(ellipses_C1, Dict(:Q => Q, :d => d, :e => e))
end
Expand Down Expand Up @@ -356,7 +356,7 @@ for (i, cluster) in enumerate(clusters)
)
@objective(model, Min, ζ)
optimize!(model)
@assert is_solved_and_feasible(model; allow_almost = true)
assert_is_solved_and_feasible(model; allow_almost = true)
Q, d, e = value.(model[:Q]), value.(model[:d]), value.(model[:e])
push!(ellipses_C2, Dict(:Q => Q, :d => d, :e => e))
end
Expand Down
Loading

0 comments on commit 3b98ac1

Please sign in to comment.