diff --git a/test/Derivative/DerivativeExample.jl b/test/Derivative/DerivativeExample.jl deleted file mode 100644 index 9e577c5e..00000000 --- a/test/Derivative/DerivativeExample.jl +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright (c) 2016: Joaquim Garcia, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using MathOptInterface -using Xpress -using Test - -const MOI = MathOptInterface - -#Example of simple Thermal Generation Dispatch and the derivatives of the generation by the demand (dg/dd) -mutable struct DispachModel - optimizer::MOI.AbstractOptimizer # Optimizer - g::Vector{MOI.VariableIndex} # Generation Variable Indexes - Df::MOI.VariableIndex # Decifit Variable Index - c_limit_lower # Constraint of lower limit for variables - c_limit_upper # Constraint of upper limite for variables - c_demand # Constraint of the generation by the demand -end - -#Simple Model of Thermal Generation Dispatch by Constraints with MOI.VariableIndex -function GenerateModel_VariableIndex() - # Parameters - d = 45.0 # Demand - I = 3 # Number of generators - g_sup = [10.0, 20.0, 30.0] # Upper limit of generation for each generator - c_g = [1.0, 3.0, 5.0] # Cost of generation for each generator - c_Df = 10.0 # Cost of deficit - - # Creation of the Optimizer - optimizer = Xpress.Optimizer(PRESOLVE=0, logfile = "outputXpress_SV.log") - # Variables - g = MOI.add_variables(optimizer, I) # Generation for each generator - Df = MOI.add_variable(optimizer) # Deficit - - # Constraints - c_limit_inf = Vector{Any}(undef, I + 1) # Lower limit constraints - c_limit_sup = Vector{Any}(undef, I) # Upper limit constraints - for i in 1:I - c_limit_inf[i] = MOI.add_constraint(optimizer, g[i], MOI.GreaterThan(0.0)) - c_limit_sup[i] = MOI.add_constraint(optimizer, g[i], MOI.LessThan(g_sup[i])) - end - c_limit_inf[I+1] = MOI.add_constraint(optimizer, Df, MOI.GreaterThan(0.0)) - - c_demand = MOI.add_constraint(optimizer, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(I+1),[g;Df]), 0.0), - MOI.EqualTo(d) - ) # Constraint of the Demand - - # Objectives - objective_function = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([c_g; c_Df], [g; Df]), 0.0) # Total cost function - MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), objective_function) # Pass the objective function to the optimizer - MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) # Set the objective sense to MIN - - # Solve the optimizer - MOI.optimize!(optimizer) - - # Return the solved optimizer with interest variables and constraints - return DispachModel(optimizer, g, Df, c_limit_inf, c_limit_sup, c_demand) -end - -#Simple Model of Thermal Generation Dispatch by Constraints with MOI.ScalarAffineFunction -function GenerateModel_ScalarAffineFunction() - # Parameters - d = 45.0 # Demand - I = 3 # Number of generators - g_sup = [10.0, 20.0, 30.0] # Upper limit of generation for each generator - c_g = [1.0, 3.0, 5.0] # Cost of generation for each generator - c_Df = 10.0 # Cost of deficit - - # Creation of the Optimizer - optimizer = Xpress.Optimizer(PRESOLVE=0, logfile = "outputXpress_SAF.log") - # Variables - g = MOI.add_variables(optimizer, I) # Generation for each generator - Df = MOI.add_variable(optimizer) # Deficit - - # Constraints - c_limit_inf = Vector{Any}(undef, I + 1) # Lower limit constraints - c_limit_sup = Vector{Any}(undef, I) # Upper limit constraints - vectAux = zeros(I + 1) # Auxiliar Vector - for i in 1:I - vectAux[i] = 1.0 - c_limit_inf[i] = MOI.add_constraint(optimizer, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(vectAux,[g;Df]), 0.0), - MOI.GreaterThan(0.0) - ) - c_limit_sup[i] = MOI.add_constraint(optimizer, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(vectAux,[g;Df]), 0.0), - MOI.LessThan(g_sup[i]) - ) - vectAux[i] = 0.0 - end - vectAux[I+1] = 1.0 - c_limit_inf[I+1] = MOI.add_constraint(optimizer, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(vectAux,[g;Df]),0.0), MOI.GreaterThan(0.0)) - vectAux[I+1] = 0.0 - - c_demand = MOI.add_constraint(optimizer, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(I+1),[g;Df]), 0.0), - MOI.EqualTo(d) - ) # Constraint of the Demand - - # Objectives - objective_function = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([c_g; c_Df], [g; Df]), 0.0) # Total cost function - MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), objective_function) # Pass the objective function to the optimizer - MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) # Set the objective sense to MIN - - # Solve the optimizer - MOI.optimize!(optimizer) - - # Return the solved optimizer with interest variables and constraints - return DispachModel(optimizer, g, Df, c_limit_inf, c_limit_sup, c_demand) -end - -#It uses a solved (optimized) Dispach Model created by the function GenerateModel -function Forward(model::DispachModel, ϵ::Float64 = 1.0) - #Initialization of parameters and references to simplify the notation - optimizer = model.optimizer - vectRef = [model.g; model.Df] - I = length(model.g) - - #Get the primal solution of the model - vect = MOI.get.(optimizer, MOI.VariablePrimal(), vectRef) - - #Pass the perturbation to the Xpress Framework and set the context to Forward - MOI.set(optimizer, Xpress.ForwardSensitivityInputConstraint(), model.c_demand, ϵ) - - #Get the derivative of the model - dvect = MOI.get.(optimizer, Xpress.ForwardSensitivityOutputVariable(), vectRef) - - #Return the values as a vector - return [vect;dvect] -end - -function Backward(model::DispachModel, ϵ::Float64 = 1.0) - #Initialization of parameters and references to simplify the notation - optimizer = model.optimizer - vectRef = [model.g; model.Df] - I = length(model.g) - - #Get the primal solution of the model - vect = MOI.get.(optimizer, MOI.VariablePrimal(), vectRef) - - #Set variables needed for the Xpress Backward Framework - dvect = zeros(I + 1) - - #Loop for each primal variable - for i in 1:I + 1 - #Set the perturbation in the Primal Variables and set the context to Backward - MOI.set(optimizer, Xpress.BackwardSensitivityInputVariable(), vectRef[i], ϵ) - - #Get the value of the derivative of the model - dvect[i] = MOI.get(optimizer, Xpress.BackwardSensitivityOutputConstraint(), model.c_demand) - - #Reset perturbation - MOI.set(optimizer, Xpress.BackwardSensitivityInputVariable(), vectRef[i], 0.0) - end - - #Return the values as a vector - return [vect;dvect] -end - -#Initialization of Parameters -@testset "Results" begin - @testset "Single_Variable" begin - #Generate model by VariableIndex - model1 = GenerateModel_VariableIndex() - #Get the results of models with the DiffOpt Forward context - @testset "Forward" begin - resultForward = Forward(model1) - @test resultForward[1:4] == [10.0;20.0;15.0;0.0] - @test resultForward[5:8] == [0.0;0.0;1.0;0.0] - end - #Get the results of models with the DiffOpt Backward context - @testset "Backward" begin - resultBackward = Backward(model1) - @test resultBackward[1:4] == [10.0;20.0;15.0;0.0] - @test resultBackward[5:8] == [0.0;0.0;1.0;0.0] - end - end - @testset "Scalar_Affine_Function" begin - #Generate model by ScalarAffineFunction - model2 = GenerateModel_ScalarAffineFunction() - #Get the results of models with the DiffOpt Forward context - @testset "Forward" begin - resultForward = Forward(model2) - @test resultForward[1:4] == [10.0;20.0;15.0;0.0] - @test resultForward[5:8] == [0.0;0.0;1.0;0.0] - end - #Get the results of models with the DiffOpt Backward context - @testset "Backward" begin - resultBackward = Backward(model2) - @test resultBackward[1:4] == [10.0;20.0;15.0;0.0] - @test resultBackward[5:8] == [0.0;0.0;1.0;0.0] - end - end -end - -;#END diff --git a/test/MathOptInterface/MOI_callbacks.jl b/test/MathOptInterface/MOI_callbacks.jl deleted file mode 100644 index e4d0921e..00000000 --- a/test/MathOptInterface/MOI_callbacks.jl +++ /dev/null @@ -1,425 +0,0 @@ -# Copyright (c) 2016: Joaquim Garcia, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Xpress -using MathOptInterface -using Random -using Test - -const MOI = MathOptInterface - -function callback_simple_model() - model = Xpress.Optimizer( - HEURSTRATEGY = 0, # before v41 - HEUREMPHASIS = 0, - OUTPUTLOG = 0, - ) - - MOI.Utilities.loadfromstring!(model, """ - variables: x, y - maxobjective: y - c1: x in Integer() - c2: y in Integer() - c3: x in Interval(0.0, 2.5) - c4: y in Interval(0.0, 2.5) - """) - x = MOI.get(model, MOI.VariableIndex, "x") - y = MOI.get(model, MOI.VariableIndex, "y") - return model, x, y -end - -function callback_knapsack_model() - model = Xpress.Optimizer( - OUTPUTLOG = 0, - HEURSTRATEGY = 0, # before v41 - HEUREMPHASIS = 0, - CUTSTRATEGY = 0, - PRESOLVE = 0, - MIPPRESOLVE = 0, - PRESOLVEOPS = 0, - MIPTHREADS = 1, - THREADS = 1, - ) - MOI.set(model, MOI.NumberOfThreads(), 2) - - N = 30 - x = MOI.add_variables(model, N) - MOI.add_constraints(model, x, MOI.ZeroOne()) - MOI.set.(model, MOI.VariablePrimalStart(), x, 0.0) - Random.seed!(1) - item_weights, item_values = rand(N), rand(N) - MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(item_weights, x), 0.0), - MOI.LessThan(10.0) - ) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(item_values, x), 0.0) - ) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - return model, x, item_weights -end - -@testset "LazyConstraintCallback" begin - @testset "LazyConstraint" begin - model, x, y = callback_simple_model() - global lazy_called = false - MOI.set(model, MOI.LazyConstraintCallback(), cb_data -> begin - global lazy_called = true - x_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), x) - y_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), y) - status = MOI.get(model, MOI.CallbackNodeStatus(cb_data))::MOI.CallbackNodeStatusCode - if round.(Int, [x_val, y_val]) ≈ [x_val, y_val] atol=1e-6 - @test status == MOI.CALLBACK_NODE_STATUS_INTEGER - else - @test status == MOI.CALLBACK_NODE_STATUS_FRACTIONAL - end - @test MOI.supports(model, MOI.LazyConstraint(cb_data)) - if y_val - x_val > 1 + 1e-6 - MOI.submit( - model, - MOI.LazyConstraint(cb_data), - MOI.ScalarAffineFunction{Float64}( - MOI.ScalarAffineTerm.([-1.0, 1.0], [x, y]), - 0.0 - ), - MOI.LessThan{Float64}(1.0) - ) - elseif y_val + x_val > 3 + 1e-6 - MOI.submit( - model, - MOI.LazyConstraint(cb_data), - MOI.ScalarAffineFunction{Float64}( - MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), - 0.0 - ), MOI.LessThan{Float64}(3.0) - ) - end - end) - @test MOI.supports(model, MOI.LazyConstraintCallback()) - MOI.optimize!(model) - @test lazy_called - @test MOI.get(model, MOI.VariablePrimal(), x) == 1 - @test MOI.get(model, MOI.VariablePrimal(), y) == 2 - end - @testset "OptimizeInProgress" begin - model, x, y = callback_simple_model() - MOI.set(model, MOI.LazyConstraintCallback(), cb_data -> begin - @test_throws( - MOI.OptimizeInProgress(MOI.VariablePrimal()), - MOI.get(model, MOI.VariablePrimal(), x) - ) - @test_throws( - MOI.OptimizeInProgress(MOI.ObjectiveValue()), - MOI.get(model, MOI.ObjectiveValue()) - ) - @test_throws( - MOI.OptimizeInProgress(MOI.ObjectiveBound()), - MOI.get(model, MOI.ObjectiveBound()) - ) - end) - MOI.optimize!(model) - end - - @testset "HeuristicSolution" begin - model, x, y = callback_simple_model() - cb = nothing - MOI.set(model, MOI.LazyConstraintCallback(), cb_data -> begin - cb = cb_data - MOI.submit( - model, - MOI.HeuristicSolution(cb_data), - [x], - [2.0] - ) - end) - @test_throws( - MOI.InvalidCallbackUsage( - MOI.LazyConstraintCallback(), - MOI.HeuristicSolution(cb) - ), - MOI.optimize!(model) - ) - end -end - -@testset "UserCutCallback" begin - @testset "UserCut" begin - model, x, item_weights = callback_knapsack_model() - global user_cut_submitted = false - MOI.set(model, MOI.UserCutCallback(), cb_data -> begin - terms = MOI.ScalarAffineTerm{Float64}[] - accumulated = 0.0 - for (i, xi) in enumerate(x) - if MOI.get(model, MOI.CallbackVariablePrimal(cb_data), xi) > 0.0 - push!(terms, MOI.ScalarAffineTerm(1.0, xi)) - accumulated += item_weights[i] - end - end - @test MOI.supports(model, MOI.UserCut(cb_data)) - if accumulated > 10.0 - MOI.submit( - model, - MOI.UserCut(cb_data), - MOI.ScalarAffineFunction{Float64}(terms, 0.0), - MOI.LessThan{Float64}(length(terms) - 1) - ) - global user_cut_submitted = true - end - end) - @test MOI.supports(model, MOI.UserCutCallback()) - MOI.optimize!(model) - @test user_cut_submitted - end - @testset "HeuristicSolution" begin - model, x, item_weights = callback_knapsack_model() - cb = nothing - MOI.set(model, MOI.UserCutCallback(), cb_data -> begin - cb = cb_data - MOI.submit( - model, - MOI.HeuristicSolution(cb_data), - [x[1]], - [0.0] - ) - end) - @test_throws( - MOI.InvalidCallbackUsage( - MOI.UserCutCallback(), - MOI.HeuristicSolution(cb) - ), - MOI.optimize!(model) - ) - end -end - -@testset "HeuristicCallback" begin - @testset "HeuristicSolution" begin - model, x, item_weights = callback_knapsack_model() - global callback_called = false - MOI.set(model, MOI.HeuristicCallback(), cb_data -> begin - x_vals = MOI.get.(model, MOI.CallbackVariablePrimal(cb_data), x) - status = MOI.get(model, MOI.CallbackNodeStatus(cb_data))::MOI.CallbackNodeStatusCode - if round.(Int, x_vals) ≈ x_vals atol=1e-6 - @test status == MOI.CALLBACK_NODE_STATUS_INTEGER - else - @test status == MOI.CALLBACK_NODE_STATUS_FRACTIONAL - end - @test MOI.supports(model, MOI.HeuristicSolution(cb_data)) - @test MOI.submit( - model, - MOI.HeuristicSolution(cb_data), - x, - floor.(x_vals) - ) == MOI.HEURISTIC_SOLUTION_UNKNOWN - global callback_called = true - end) - @test MOI.supports(model, MOI.HeuristicCallback()) - MOI.optimize!(model) - @test callback_called - end - @testset "LazyConstraint" begin - model, x, item_weights = callback_knapsack_model() - cb = nothing - MOI.set(model, MOI.HeuristicCallback(), cb_data -> begin - cb = cb_data - MOI.submit( - model, - MOI.LazyConstraint(cb_data), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), - MOI.LessThan(5.0) - ) - end) - @test_throws( - MOI.InvalidCallbackUsage( - MOI.HeuristicCallback(), - MOI.LazyConstraint(cb) - ), - MOI.optimize!(model) - ) - end - @testset "UserCut" begin - model, x, item_weights = callback_knapsack_model() - cb = nothing - MOI.set(model, MOI.HeuristicCallback(), cb_data -> begin - cb = cb_data - MOI.submit( - model, - MOI.UserCut(cb_data), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), - MOI.LessThan(5.0) - ) - end) - @test_throws( - MOI.InvalidCallbackUsage( - MOI.HeuristicCallback(), - MOI.UserCut(cb) - ), - MOI.optimize!(model) - ) - end -end - -@testset "Xpress.CallbackFunction" begin - @testset "OptimizeInProgress" begin - model, x, y = callback_simple_model() - MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin - @test_throws( - MOI.OptimizeInProgress(MOI.VariablePrimal()), - MOI.get(model, MOI.VariablePrimal(), x) - ) - @test_throws( - MOI.OptimizeInProgress(MOI.ObjectiveValue()), - MOI.get(model, MOI.ObjectiveValue()) - ) - @test_throws( - MOI.OptimizeInProgress(MOI.ObjectiveBound()), - MOI.get(model, MOI.ObjectiveBound()) - ) - end) - @test MOI.supports(model, Xpress.CallbackFunction()) - MOI.optimize!(model) - end - @testset "LazyConstraint" begin - model, x, y = callback_simple_model() - cb_calls = Int32[] - global generic_lazy_called = false - function callback_function(cb_data) - push!(cb_calls, 1) - Xpress.get_cb_solution(model, cb_data.model) - x_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), x) - y_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), y) - if y_val - x_val > 1 + 1e-6 - MOI.submit(model, MOI.LazyConstraint(cb_data), - MOI.ScalarAffineFunction{Float64}( - MOI.ScalarAffineTerm.([-1.0, 1.0], [x, y]), - 0.0 - ), - MOI.LessThan{Float64}(1.0) - ) - elseif y_val + x_val > 3 + 1e-6 - MOI.submit(model, MOI.LazyConstraint(cb_data), - MOI.ScalarAffineFunction{Float64}( - MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), - 0.0 - ), - MOI.LessThan{Float64}(3.0) - ) - end - end - MOI.set(model, Xpress.CallbackFunction(), callback_function) - MOI.optimize!(model) - @test MOI.get(model, MOI.VariablePrimal(), x) == 1 - @test MOI.get(model, MOI.VariablePrimal(), y) == 2 - @test length(cb_calls) > 0 - end - @testset "UserCut" begin - model, x, item_weights = callback_knapsack_model() - user_cut_submitted = false - cb_calls = Int32[] - MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin - push!(cb_calls) - - if Xpress.get_control_or_attribute(cb_data.model, Xpress.Lib.XPRS_CALLBACKCOUNT_OPTNODE) > 1 - return - end - Xpress.get_cb_solution(model, cb_data.model) - terms = MOI.ScalarAffineTerm{Float64}[] - accumulated = 0.0 - for (i, xi) in enumerate(x) - if MOI.get(model, MOI.CallbackVariablePrimal(cb_data), xi) > 0.0 - push!(terms, MOI.ScalarAffineTerm(1.0, xi)) - accumulated += item_weights[i] - end - end - if accumulated > 10.0 - MOI.submit( - model, - MOI.UserCut(cb_data), - MOI.ScalarAffineFunction{Float64}(terms, 0.0), - MOI.LessThan{Float64}(length(terms) - 1) - ) - user_cut_submitted = true - end - return - end) - MOI.optimize!(model) - @test user_cut_submitted - end - @testset "HeuristicSolution" begin - model, x, item_weights = callback_knapsack_model() - callback_called = false - cb_calls = Int32[] - MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin - if Xpress.get_control_or_attribute(cb_data.model, Xpress.Lib.XPRS_CALLBACKCOUNT_OPTNODE) > 1 - return - end - Xpress.get_cb_solution(model, cb_data.model) - x_vals = MOI.get.(model, MOI.CallbackVariablePrimal(cb_data), x) - @test MOI.submit( - model, - MOI.HeuristicSolution(cb_data), - x, - floor.(x_vals) - ) == MOI.HEURISTIC_SOLUTION_UNKNOWN - callback_called = true - return - end) - MOI.optimize!(model) - @test callback_called - end -end - -@testset "Xpress.CallbackFunction.CallbackNodeStatus" begin - model, x, item_weights = callback_knapsack_model() - global unknown_reached = false - MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin - if MOI.get(model, MOI.CallbackNodeStatus(cb_data)) == MOI.CALLBACK_NODE_STATUS_UNKNOWN - global unknown_reached = true - end - end) - MOI.optimize!(model) - @test unknown_reached -end - -function test_lazy_constraint_dual_reductions() - model = Xpress.Optimizer() - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variables(model, 3) - MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) - MOI.add_constraint.(model, x[1:2], MOI.Integer()) - MOI.add_constraint( - model, - 1.0 * x[1] + 1.0 * x[2] - 1.0 * x[3], - MOI.EqualTo(0.0), - ) - MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(220.0)) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - f = 1.0 * x[3] - MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) - function lazy_flow_constraints(cb_data) - x_val = MOI.get.(model, MOI.CallbackVariablePrimal(cb_data), x) - if x_val[1] + x_val[2] > 10 - MOI.submit( - model, - MOI.LazyConstraint(cb_data), - 1.0 * x[1] + 1.0 * x[2], - MOI.LessThan(10.0), - ) - end - end - MOI.set(model, MOI.LazyConstraintCallback(), lazy_flow_constraints) - MOI.optimize!(model) - x_val = MOI.get(model, MOI.VariablePrimal(), x) - @test x_val[1] + x_val[2] <= 10.0 + 1e-4 - @test x_val[1] + x_val[2] ≈ x_val[3] - return -end - -@testset "Xpress.test_lazy_constraint_dual_reductions" begin - test_lazy_constraint_dual_reductions() -end diff --git a/test/runtests.jl b/test/runtests.jl index 99d4909d..5fd5af18 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,22 +9,13 @@ using Xpress println(Xpress.get_banner()) println("Optimizer version: $(Xpress.get_version())") -@testset "$(folder)" for folder in [ - "MathOptInterface", - "xprs_callbacks", - "Derivative", -] - @testset "$(file)" for file in readdir(folder) - include(joinpath(folder, file)) - end +@testset "$f" for f in filter!(f -> startswith(f, "test_"), readdir(@__DIR__)) + include(f) end @testset "Xpress tests" begin - prob = Xpress.XpressProblem() - @test Xpress.getcontrol(prob, "HEURTHREADS") == 0 - vXpress_major = Int(Xpress.get_version().major) file_extension = ifelse(vXpress_major <= 38, ".mps","") msg = "Xpress internal error:\n\n85 Error: File not found: $(file_extension).\n" diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/test_MOI_wrapper.jl similarity index 64% rename from test/MathOptInterface/MOI_wrapper.jl rename to test/test_MOI_wrapper.jl index f2e2fc6a..d509592f 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -1,15 +1,16 @@ -# Copyright (c) 2016: Joaquim Garcia, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - +# Copyright (c) 2016: Joaquim Garcia, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + module TestMOIWrapper using Xpress -using MathOptInterface using Test -const MOI = MathOptInterface +import LinearAlgebra +import MathOptInterface as MOI +import Random function runtests() for name in names(@__MODULE__; all = true) @@ -19,6 +20,7 @@ function runtests() end end end + return end function test_Basic_Parameters() @@ -1010,6 +1012,601 @@ function test_multiple_modifications2() return end +function callback_simple_model() + model = Xpress.Optimizer( + HEURSTRATEGY = 0, # before v41 + HEUREMPHASIS = 0, + OUTPUTLOG = 0, + ) + MOI.Utilities.loadfromstring!(model, """ + variables: x, y + maxobjective: y + c1: x in Integer() + c2: y in Integer() + c3: x in Interval(0.0, 2.5) + c4: y in Interval(0.0, 2.5) + """) + x = MOI.get(model, MOI.VariableIndex, "x") + y = MOI.get(model, MOI.VariableIndex, "y") + return model, x, y +end + +function callback_knapsack_model() + model = Xpress.Optimizer( + OUTPUTLOG = 0, + HEURSTRATEGY = 0, # before v41 + HEUREMPHASIS = 0, + CUTSTRATEGY = 0, + PRESOLVE = 0, + MIPPRESOLVE = 0, + PRESOLVEOPS = 0, + MIPTHREADS = 1, + THREADS = 1, + ) + MOI.set(model, MOI.NumberOfThreads(), 2) + N = 30 + x = MOI.add_variables(model, N) + MOI.add_constraints(model, x, MOI.ZeroOne()) + MOI.set.(model, MOI.VariablePrimalStart(), x, 0.0) + Random.seed!(1) + item_weights, item_values = rand(N), rand(N) + MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(item_weights, x), 0.0), + MOI.LessThan(10.0) + ) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(item_values, x), 0.0) + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + return model, x, item_weights +end + +function test_lazy_constraint_callback_lazy_constraint() + model, x, y = callback_simple_model() + global lazy_called = false + MOI.set(model, MOI.LazyConstraintCallback(), cb_data -> begin + global lazy_called = true + x_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), x) + y_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), y) + status = MOI.get(model, MOI.CallbackNodeStatus(cb_data))::MOI.CallbackNodeStatusCode + if round.(Int, [x_val, y_val]) ≈ [x_val, y_val] atol=1e-6 + @test status == MOI.CALLBACK_NODE_STATUS_INTEGER + else + @test status == MOI.CALLBACK_NODE_STATUS_FRACTIONAL + end + @test MOI.supports(model, MOI.LazyConstraint(cb_data)) + if y_val - x_val > 1 + 1e-6 + MOI.submit( + model, + MOI.LazyConstraint(cb_data), + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm.([-1.0, 1.0], [x, y]), + 0.0 + ), + MOI.LessThan{Float64}(1.0) + ) + elseif y_val + x_val > 3 + 1e-6 + MOI.submit( + model, + MOI.LazyConstraint(cb_data), + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), + 0.0 + ), MOI.LessThan{Float64}(3.0) + ) + end + end) + @test MOI.supports(model, MOI.LazyConstraintCallback()) + MOI.optimize!(model) + @test lazy_called + @test MOI.get(model, MOI.VariablePrimal(), x) == 1 + @test MOI.get(model, MOI.VariablePrimal(), y) == 2 + return +end + +function test_lazy_constraint_callback_OptimizeInProgress() + model, x, y = callback_simple_model() + MOI.set(model, MOI.LazyConstraintCallback(), cb_data -> begin + @test_throws( + MOI.OptimizeInProgress(MOI.VariablePrimal()), + MOI.get(model, MOI.VariablePrimal(), x) + ) + @test_throws( + MOI.OptimizeInProgress(MOI.ObjectiveValue()), + MOI.get(model, MOI.ObjectiveValue()) + ) + @test_throws( + MOI.OptimizeInProgress(MOI.ObjectiveBound()), + MOI.get(model, MOI.ObjectiveBound()) + ) + end) + MOI.optimize!(model) + return +end + +function test_lazy_constraint_callback_HeuristicSolution() + model, x, y = callback_simple_model() + cb = nothing + MOI.set(model, MOI.LazyConstraintCallback(), cb_data -> begin + cb = cb_data + MOI.submit( + model, + MOI.HeuristicSolution(cb_data), + [x], + [2.0] + ) + end) + @test_throws( + MOI.InvalidCallbackUsage( + MOI.LazyConstraintCallback(), + MOI.HeuristicSolution(cb) + ), + MOI.optimize!(model) + ) + return end +function test_user_cut_callback_UserCut() + model, x, item_weights = callback_knapsack_model() + global user_cut_submitted = false + MOI.set(model, MOI.UserCutCallback(), cb_data -> begin + terms = MOI.ScalarAffineTerm{Float64}[] + accumulated = 0.0 + for (i, xi) in enumerate(x) + if MOI.get(model, MOI.CallbackVariablePrimal(cb_data), xi) > 0.0 + push!(terms, MOI.ScalarAffineTerm(1.0, xi)) + accumulated += item_weights[i] + end + end + @test MOI.supports(model, MOI.UserCut(cb_data)) + if accumulated > 10.0 + MOI.submit( + model, + MOI.UserCut(cb_data), + MOI.ScalarAffineFunction{Float64}(terms, 0.0), + MOI.LessThan{Float64}(length(terms) - 1) + ) + global user_cut_submitted = true + end + end) + @test MOI.supports(model, MOI.UserCutCallback()) + MOI.optimize!(model) + @test user_cut_submitted + return +end + +function test_user_cut_callback_HeuristicSolution() + model, x, item_weights = callback_knapsack_model() + cb = nothing + MOI.set(model, MOI.UserCutCallback(), cb_data -> begin + cb = cb_data + MOI.submit( + model, + MOI.HeuristicSolution(cb_data), + [x[1]], + [0.0] + ) + end) + @test_throws( + MOI.InvalidCallbackUsage( + MOI.UserCutCallback(), + MOI.HeuristicSolution(cb) + ), + MOI.optimize!(model) + ) + return +end + +function test_heuristic_callback_HeuristicSolution() + model, x, item_weights = callback_knapsack_model() + global callback_called = false + MOI.set(model, MOI.HeuristicCallback(), cb_data -> begin + x_vals = MOI.get.(model, MOI.CallbackVariablePrimal(cb_data), x) + status = MOI.get(model, MOI.CallbackNodeStatus(cb_data))::MOI.CallbackNodeStatusCode + if round.(Int, x_vals) ≈ x_vals atol=1e-6 + @test status == MOI.CALLBACK_NODE_STATUS_INTEGER + else + @test status == MOI.CALLBACK_NODE_STATUS_FRACTIONAL + end + @test MOI.supports(model, MOI.HeuristicSolution(cb_data)) + @test MOI.submit( + model, + MOI.HeuristicSolution(cb_data), + x, + floor.(x_vals) + ) == MOI.HEURISTIC_SOLUTION_UNKNOWN + global callback_called = true + end) + @test MOI.supports(model, MOI.HeuristicCallback()) + MOI.optimize!(model) + @test callback_called + return +end + +function test_heuristic_callback_LazyConstraint() + model, x, item_weights = callback_knapsack_model() + cb = nothing + MOI.set(model, MOI.HeuristicCallback(), cb_data -> begin + cb = cb_data + MOI.submit( + model, + MOI.LazyConstraint(cb_data), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), + MOI.LessThan(5.0) + ) + end) + @test_throws( + MOI.InvalidCallbackUsage( + MOI.HeuristicCallback(), + MOI.LazyConstraint(cb) + ), + MOI.optimize!(model) + ) + return +end + +function test_heuristic_callback_UserCut() + model, x, item_weights = callback_knapsack_model() + cb = nothing + MOI.set(model, MOI.HeuristicCallback(), cb_data -> begin + cb = cb_data + MOI.submit( + model, + MOI.UserCut(cb_data), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), + MOI.LessThan(5.0) + ) + end) + @test_throws( + MOI.InvalidCallbackUsage( + MOI.HeuristicCallback(), + MOI.UserCut(cb) + ), + MOI.optimize!(model) + ) + return +end + +function test_callback_function_OptimizeInProgress() + model, x, y = callback_simple_model() + MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin + @test_throws( + MOI.OptimizeInProgress(MOI.VariablePrimal()), + MOI.get(model, MOI.VariablePrimal(), x) + ) + @test_throws( + MOI.OptimizeInProgress(MOI.ObjectiveValue()), + MOI.get(model, MOI.ObjectiveValue()) + ) + @test_throws( + MOI.OptimizeInProgress(MOI.ObjectiveBound()), + MOI.get(model, MOI.ObjectiveBound()) + ) + end) + @test MOI.supports(model, Xpress.CallbackFunction()) + MOI.optimize!(model) + return +end + +function test_callback_function_LazyConstraint() + model, x, y = callback_simple_model() + cb_calls = Int32[] + global generic_lazy_called = false + function callback_function(cb_data) + push!(cb_calls, 1) + Xpress.get_cb_solution(model, cb_data.model) + x_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), x) + y_val = MOI.get(model, MOI.CallbackVariablePrimal(cb_data), y) + if y_val - x_val > 1 + 1e-6 + MOI.submit(model, MOI.LazyConstraint(cb_data), + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm.([-1.0, 1.0], [x, y]), + 0.0 + ), + MOI.LessThan{Float64}(1.0) + ) + elseif y_val + x_val > 3 + 1e-6 + MOI.submit(model, MOI.LazyConstraint(cb_data), + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), + 0.0 + ), + MOI.LessThan{Float64}(3.0) + ) + end + end + MOI.set(model, Xpress.CallbackFunction(), callback_function) + MOI.optimize!(model) + @test MOI.get(model, MOI.VariablePrimal(), x) == 1 + @test MOI.get(model, MOI.VariablePrimal(), y) == 2 + @test length(cb_calls) > 0 + return +end + +function test_callback_function_UserCut() + model, x, item_weights = callback_knapsack_model() + user_cut_submitted = false + cb_calls = Int32[] + MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin + push!(cb_calls) + + if Xpress.get_control_or_attribute(cb_data.model, Xpress.Lib.XPRS_CALLBACKCOUNT_OPTNODE) > 1 + return + end + Xpress.get_cb_solution(model, cb_data.model) + terms = MOI.ScalarAffineTerm{Float64}[] + accumulated = 0.0 + for (i, xi) in enumerate(x) + if MOI.get(model, MOI.CallbackVariablePrimal(cb_data), xi) > 0.0 + push!(terms, MOI.ScalarAffineTerm(1.0, xi)) + accumulated += item_weights[i] + end + end + if accumulated > 10.0 + MOI.submit( + model, + MOI.UserCut(cb_data), + MOI.ScalarAffineFunction{Float64}(terms, 0.0), + MOI.LessThan{Float64}(length(terms) - 1) + ) + user_cut_submitted = true + end + return + end) + MOI.optimize!(model) + @test user_cut_submitted + return +end + +function test_callback_function_HeuristicSolution() + model, x, item_weights = callback_knapsack_model() + callback_called = false + cb_calls = Int32[] + MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin + if Xpress.get_control_or_attribute(cb_data.model, Xpress.Lib.XPRS_CALLBACKCOUNT_OPTNODE) > 1 + return + end + Xpress.get_cb_solution(model, cb_data.model) + x_vals = MOI.get.(model, MOI.CallbackVariablePrimal(cb_data), x) + @test MOI.submit( + model, + MOI.HeuristicSolution(cb_data), + x, + floor.(x_vals) + ) == MOI.HEURISTIC_SOLUTION_UNKNOWN + callback_called = true + return + end) + MOI.optimize!(model) + @test callback_called + return +end + +function test_callback_CallbackNodeStatus() + model, x, item_weights = callback_knapsack_model() + global unknown_reached = false + MOI.set(model, Xpress.CallbackFunction(), (cb_data) -> begin + if MOI.get(model, MOI.CallbackNodeStatus(cb_data)) == MOI.CALLBACK_NODE_STATUS_UNKNOWN + global unknown_reached = true + end + end) + MOI.optimize!(model) + @test unknown_reached + return +end + +function test_callback_lazy_constraint_dual_reductions() + model = Xpress.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 3) + MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) + MOI.add_constraint.(model, x[1:2], MOI.Integer()) + MOI.add_constraint( + model, + 1.0 * x[1] + 1.0 * x[2] - 1.0 * x[3], + MOI.EqualTo(0.0), + ) + MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(220.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * x[3] + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + function lazy_flow_constraints(cb_data) + x_val = MOI.get.(model, MOI.CallbackVariablePrimal(cb_data), x) + if x_val[1] + x_val[2] > 10 + MOI.submit( + model, + MOI.LazyConstraint(cb_data), + 1.0 * x[1] + 1.0 * x[2], + MOI.LessThan(10.0), + ) + end + end + MOI.set(model, MOI.LazyConstraintCallback(), lazy_flow_constraints) + MOI.optimize!(model) + x_val = MOI.get(model, MOI.VariablePrimal(), x) + @test x_val[1] + x_val[2] <= 10.0 + 1e-4 + @test x_val[1] + x_val[2] ≈ x_val[3] + return +end + +function callback_simple_model() + model = Xpress.Optimizer( + PRESOLVE = 0, + CUTSTRATEGY = 0, + HEURSTRATEGY = 0, + SYMMETRY = 0, + OUTPUTLOG = 0 + ) + MOI.Utilities.loadfromstring!(model, """ + variables: x, y + maxobjective: y + c1: x in Integer() + c2: y in Integer() + c3: x in Interval(0.0, 2.5) + c4: y in Interval(0.0, 2.5) + """) + x = MOI.get(model, MOI.VariableIndex, "x") + y = MOI.get(model, MOI.VariableIndex, "y") + return model, x, y +end + +function test_callback_preintsol() + model, x, y = callback_simple_model() + data = 1.0 * LinearAlgebra.I(3) + function foo(cb::Xpress.CallbackData) + cb.data[1] = 98 + cols = Xpress.get_control_or_attribute(cb.model, Xpress.Lib.XPRS_COLS) + rows = Xpress.get_control_or_attribute(cb.model, Xpress.Lib.XPRS_ROWS) + Xpress.get_control_or_attribute(cb.model, Xpress.Lib.XPRS_BESTBOUND) + ans_variable_primal = Vector{Float64}(undef,Int(cols)) + ans_linear_primal = Vector{Float64}(undef,Int(cols)) + Xpress.Lib.XPRSgetlpsol( + cb.model, + ans_variable_primal, + ans_linear_primal, + C_NULL, + C_NULL, + ) + return + end + func_ptr, data_ptr = Xpress.set_callback_preintsol!(model.inner, foo, data) + @test data[1] == 1 + MOI.optimize!(model) + @test data[1] == 98 + @test func_ptr isa Ptr{Cvoid} + return +end + +mutable struct DispatchModel + optimizer::MOI.AbstractOptimizer + g::Vector{MOI.VariableIndex} + Df::MOI.VariableIndex + c_limit_lower + c_limit_upper + c_demand +end + +function GenerateModel_VariableIndex() + d = 45.0 + I = 3 + g_sup = [10.0, 20.0, 30.0] + c_g = [1.0, 3.0, 5.0] + c_Df = 10.0 + model = Xpress.Optimizer(; PRESOLVE = 0, logfile = "outputXpress_SV.log") + g = MOI.add_variables(model, I) + Df = MOI.add_variable(model) + c_limit_inf = MOI.add_constraint.(model, g, MOI.GreaterThan(0.0)) + push!(c_limit_inf, MOI.add_constraint(model, Df, MOI.GreaterThan(0.0))) + c_limit_sup = MOI.add_constraint.(model, g, MOI.LessThan.(g_sup)) + c_demand = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(I+1),[g;Df]), 0.0), + MOI.EqualTo(d), + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + obj = sum(c_g .* g) + c_Df * Df + MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj) + MOI.optimize!(model) + return DispatchModel(model, g, Df, c_limit_inf, c_limit_sup, c_demand) +end + +function GenerateModel_ScalarAffineFunction() + d = 45.0 + I = 3 + g_sup = [10.0, 20.0, 30.0] + c_g = [1.0, 3.0, 5.0] + c_Df = 10.0 + model = Xpress.Optimizer(; PRESOLVE = 0, logfile = "outputXpress_SAF.log") + g = MOI.add_variables(model, I) + Df = MOI.add_variable(model) + c_limit_inf = MOI.add_constraint.(model, 1.0 .* g, MOI.GreaterThan(0.0)) + push!( + c_limit_inf, + MOI.add_constraint(model, 1.0 * Df, MOI.GreaterThan(0.0)), + ) + c_limit_sup = MOI.add_constraint.(model, 1.0 .* g, MOI.LessThan.(g_sup)) + c_demand = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(I+1),[g;Df]), 0.0), + MOI.EqualTo(d) + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + obj = sum(c_g .* g) + c_Df * Df + MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj) + MOI.optimize!(model) + return DispatchModel(model, g, Df, c_limit_inf, c_limit_sup, c_demand) +end + +function Forward(model::DispatchModel, ϵ::Float64 = 1.0) + variables = [model.g; model.Df] + primal = MOI.get.(model.optimizer, MOI.VariablePrimal(), variables) + MOI.set( + model.optimizer, + Xpress.ForwardSensitivityInputConstraint(), + model.c_demand, + ϵ, + ) + dual = MOI.get.( + model.optimizer, + Xpress.ForwardSensitivityOutputVariable(), + variables, + ) + return vcat(primal, dual) +end + +function Backward(model::DispatchModel, ϵ::Float64 = 1.0) + variables = [model.g; model.Df] + primal = MOI.get.(model.optimizer, MOI.VariablePrimal(), variables) + dual = zeros(length(variables)) + for (i, xi) in enumerate(variables) + MOI.set( + model.optimizer, + Xpress.BackwardSensitivityInputVariable(), + xi, + ϵ, + ) + dual[i] = MOI.get( + model.optimizer, + Xpress.BackwardSensitivityOutputConstraint(), + model.c_demand, + ) + MOI.set( + model.optimizer, + Xpress.BackwardSensitivityInputVariable(), + xi, + 0.0, + ) + end + return vcat(primal, dual) +end + +function test_variable_index_forward() + model = GenerateModel_VariableIndex() + @test Forward(model) == [10.0, 20.0, 15.0, 0.0, 0.0, 0.0, 1.0, 0.0] + return +end + +function test_variable_index_backward() + model = GenerateModel_VariableIndex() + @test Backward(model) == [10.0, 20.0, 15.0, 0.0, 0.0, 0.0, 1.0, 0.0] + return +end + +function test_scalar_affine_index_forward() + model = GenerateModel_ScalarAffineFunction() + @test Forward(model) == [10.0, 20.0, 15.0, 0.0, 0.0, 0.0, 1.0, 0.0] + return +end + +function test_scalar_affine_index_backward() + model = GenerateModel_ScalarAffineFunction() + @test Backward(model) == [10.0, 20.0, 15.0, 0.0, 0.0, 0.0, 1.0, 0.0] + return +end + +end # TestMOIWrapper + TestMOIWrapper.runtests() diff --git a/test/xprs_callbacks/cb_preintsol.jl b/test/xprs_callbacks/cb_preintsol.jl deleted file mode 100644 index ca20ccf8..00000000 --- a/test/xprs_callbacks/cb_preintsol.jl +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (c) 2016: Joaquim Garcia, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Xpress -using LinearAlgebra -using MathOptInterface - -const MOI = MathOptInterface - -function callback_simple_model() - model = Xpress.Optimizer( - PRESOLVE = 0, - CUTSTRATEGY = 0, - HEURSTRATEGY = 0, - SYMMETRY = 0, - OUTPUTLOG = 0 - ) - - MOI.Utilities.loadfromstring!(model, """ - variables: x, y - maxobjective: y - c1: x in Integer() - c2: y in Integer() - c3: x in Interval(0.0, 2.5) - c4: y in Interval(0.0, 2.5) - """) - x = MOI.get(model, MOI.VariableIndex, "x") - y = MOI.get(model, MOI.VariableIndex, "y") - return model, x, y -end - -model, x, y = callback_simple_model() - -data = Matrix(1.0I, 3, 3) - -function foo(cb::Xpress.CallbackData) - - cb.data[1] = 98 - - cols = Xpress.get_control_or_attribute(cb.model, Xpress.Lib.XPRS_COLS) - rows = Xpress.get_control_or_attribute(cb.model, Xpress.Lib.XPRS_ROWS) - Xpress.get_control_or_attribute(cb.model, Xpress.Lib.XPRS_BESTBOUND) - - ans_variable_primal = Vector{Float64}(undef,Int(cols)) - ans_linear_primal = Vector{Float64}(undef,Int(cols)) - - Xpress.Lib.XPRSgetlpsol(cb.model, - ans_variable_primal, - ans_linear_primal, - C_NULL, C_NULL) - - return -end - -func_ptr, data_ptr = Xpress.set_callback_preintsol!(model.inner, foo, data) - -@test data[1] == 1 -MOI.optimize!(model) -@test data[1] == 98 - -@test typeof(data_ptr) <: Any -@test typeof(func_ptr) <: Ptr{Cvoid} \ No newline at end of file