From 82ce1b8d98a5d16437d093b5d18f20872d0bf061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 Sep 2022 17:46:33 +0200 Subject: [PATCH 1/6] Add option to use starting value as local solution for presolve --- docs/src/parameters.md | 22 ++++++++++++---------- src/main_algorithm.jl | 11 ++++++++++- src/solver_options.jl | 4 ++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/docs/src/parameters.md b/docs/src/parameters.md index 71a5facc..36ace37c 100644 --- a/docs/src/parameters.md +++ b/docs/src/parameters.md @@ -12,16 +12,16 @@ Here are a few general solver options which control the performance of Alpine: * `time_limit (default = Inf)`: total limit on run time for Alpine in seconds. -* `max_iter (default = 999)`: total number of iterations allowed in the [`global_solve`](@ref). +* `max_iter (default = 999)`: total number of iterations allowed in the [`global_solve`](@ref). * `rel_gap (default = 1e-4)`: relative gap considered for global convergence during [`global_solve`](@ref). Bounds are evaluated using ``\frac{UB-LB}{UB} \cdot 100 \%``. -* `tol (default = 1e-6)`: numerical tolerance used during the process of [`global_solve`](@ref). +* `tol (default = 1e-6)`: numerical tolerance used during the process of [`global_solve`](@ref). ## Adaptive Partitioning Options * `apply_partitioning (default = true)`: applies Alpine's built-in MIP-based partitioning algorithm only when activated; else terminates with the presolve solution. -* `partition_scaling_factor (default = 10)`: used during [`add_adaptive_partition`](@ref) for scaling the width of new partitions relative to the active partition chosen in the sequentially-solved lower-bounding MIP models. This value can substantially affect the run time for global convergence; this value can be set to different integer values (>= 4) for various classes of problems. +* `partition_scaling_factor (default = 10)`: used during [`add_adaptive_partition`](@ref) for scaling the width of new partitions relative to the active partition chosen in the sequentially-solved lower-bounding MIP models. This value can substantially affect the run time for global convergence; this value can be set to different integer values (>= 4) for various classes of problems. * `disc_var_pick (default = 0)`: controls Alpine's algorithm used for selecting variables for partitioning; `0` is for max-cover, `1` is for minimum-vertex-cover. This parameter allows functional inputs. @@ -29,18 +29,20 @@ Here are a few general solver options which control the performance of Alpine: ## Presolve Options -* `presolve_track_time (default = true)`: includes/excludes presolve run time in the total Alpine's run time. +* `presolve_track_time (default = true)`: includes/excludes presolve run time in the total Alpine's run time. -* `presolve_bt (default = false)`: performs sequential, optimization-based bound tightening (OBBT) at the presolve step. +* `presolve_bt (default = false)`: performs sequential, optimization-based bound tightening (OBBT) at the presolve step. -* `presolve_bt_max_iter (default = 9999)`: maximum number of iterations allowed using the sequential OBBT step. +* `presolve_bt_max_iter (default = 9999)`: maximum number of iterations allowed using the sequential OBBT step. -* `presolve_bt_width_tol (default = 1e-3)`: numerical tolerance value used in the OBBT step. Note that smaller values of this tolerance can lead to inaccurate solutions. +* `presolve_bt_width_tol (default = 1e-3)`: numerical tolerance value used in the OBBT step. Note that smaller values of this tolerance can lead to inaccurate solutions. * `presolve_bt_algo (default = 1)`: method chosen to perform the presolve step; choose `1` for the built-in OBBT, else `2` for user-input functions for presolve. -* `presolve_bt_relax_integrality (default = false)`: relaxes the integrality of the existing integer variables in the OBBT step, if the input problem is an MINLP. +* `presolve_bt_relax_integrality (default = false)`: relaxes the integrality of the existing integer variables in the OBBT step, if the input problem is an MINLP. -* `presolve_bt_mip_time_limit (default = Inf)`: time limit for individual MILPs solved during the sequential OBBT procedure. +* `presolve_bt_mip_time_limit (default = Inf)`: time limit for individual MILPs solved during the sequential OBBT procedure. -Note that the above-mentioned list of solver options is not comprehensive, but can be found in [solver.jl](https://github.com/lanl-ansi/Alpine.jl/blob/master/src/solver.jl). +* `use_start_as_local (default = false)`: if `true`, Alpine does not perform any local optimization during the presolve and use the starting value instead (*warning*: Alpine assumes the feasibility of the starting value and does not check it). + +Note that the above-mentioned list of solver options is not comprehensive, but can be found in [solver.jl](https://github.com/lanl-ansi/Alpine.jl/blob/master/src/solver.jl). diff --git a/src/main_algorithm.jl b/src/main_algorithm.jl index 1e410bd1..630ba31a 100644 --- a/src/main_algorithm.jl +++ b/src/main_algorithm.jl @@ -203,7 +203,16 @@ function presolve(m::Optimizer) start_presolve = time() Alp.get_option(m, :log_level) > 0 && printstyled("PRESOLVE \n", color = :cyan) Alp.get_option(m, :log_level) > 0 && println(" Doing local search") - Alp.local_solve(m, presolve = true) + if Alp.get_option(m, :use_start_as_local) + obj_warmval = if m.has_nl_objective + MOI.eval_variables(vi -> m.initial_warmval[vi.value], m.objective_function) + else + MOI.eval_objective(m.d_orig, m.initial_warmval) + end + update_incumb_objective(m, obj_warmval, m.initial_warmval) + else + Alp.local_solve(m, presolve = true) + end # Solver status if m.status[:local_solve] in STATUS_OPT || m.status[:local_solve] in STATUS_LIMIT diff --git a/src/solver_options.jl b/src/solver_options.jl index 8ec2bbc1..d4721801 100644 --- a/src/solver_options.jl +++ b/src/solver_options.jl @@ -60,6 +60,8 @@ mutable struct OptimizerOptions # Domain Reduction presolve_bp::Bool # Conduct basic bound propagation + + use_start_as_local # Use starting value as local solution for presolve end function get_default_options() @@ -109,6 +111,7 @@ function get_default_options() presolve_bt_relax_integrality = false presolve_bt_mip_time_limit = Inf presolve_bp = false + use_start_as_local = false return OptimizerOptions( log_level, @@ -152,5 +155,6 @@ function get_default_options() presolve_bt_relax_integrality, presolve_bt_mip_time_limit, presolve_bp, + use_start_as_local, ) end From 19c399f6b5e04b4d69c9c555bc9430e6882d44b5 Mon Sep 17 00:00:00 2001 From: harshangrjn Date: Fri, 9 Sep 2022 11:48:22 -0600 Subject: [PATCH 2/6] minor renaming changes --- CHANGELOG.md | 1 + docs/src/parameters.md | 2 +- examples/MINLPs/blend.jl | 2 +- examples/MINLPs/nlp.jl | 6 ++++-- examples/run_examples.jl | 1 + src/MOI_wrapper/MOI_wrapper.jl | 10 +++++----- src/heuristics.jl | 2 +- src/log.jl | 8 ++++---- src/main_algorithm.jl | 12 ++++++------ src/nlexpr.jl | 4 ++-- src/solver_options.jl | 9 ++++----- src/utility.jl | 4 ++-- 12 files changed, 32 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84adadd3..ddd0b9e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Re-org and clean-up in partition additions within bounding MIP model - Updated to cheaper `AffExpr` evaluation within linking constraints - Minor update in gap evaluataion after OBBT termination with fixed iterations +- Added option to use warm start value as also the intial local solution in presolve without invoking a local solver (#218) ## v0.5.0 - New feature: Linking constraints for multilinear terms with uniform and adaptive partitions (significant speed up in run times for multilinear problems: http://www.optimization-online.org/DB_HTML/2022/07/8974.html) (@jongeunkim) diff --git a/docs/src/parameters.md b/docs/src/parameters.md index 36ace37c..dffb7189 100644 --- a/docs/src/parameters.md +++ b/docs/src/parameters.md @@ -43,6 +43,6 @@ Here are a few general solver options which control the performance of Alpine: * `presolve_bt_mip_time_limit (default = Inf)`: time limit for individual MILPs solved during the sequential OBBT procedure. -* `use_start_as_local (default = false)`: if `true`, Alpine does not perform any local optimization during the presolve and use the starting value instead (*warning*: Alpine assumes the feasibility of the starting value and does not check it). +* `use_start_as_local_solution (default = false)`: if `true`, Alpine does not perform any local optimization during the presolve and uses the starting value instead (*warning*: Alpine assumes the feasibility of the starting value and does not check it). Note that the above-mentioned list of solver options is not comprehensive, but can be found in [solver.jl](https://github.com/lanl-ansi/Alpine.jl/blob/master/src/solver.jl). diff --git a/examples/MINLPs/blend.jl b/examples/MINLPs/blend.jl index a4e8fbbf..3c3d694f 100644 --- a/examples/MINLPs/blend.jl +++ b/examples/MINLPs/blend.jl @@ -616,7 +616,7 @@ function blend029_gl(; solver = nothing) 0.0, ] for i in 1:102 - set_start_value(x[i], warmstarter[i]) + JuMP.set_start_value(x[i], warmstarter[i]) end @constraint(m, x[1] + x[4] + x[7] + x[10] + x[49] == 1) #= e2: =# diff --git a/examples/MINLPs/nlp.jl b/examples/MINLPs/nlp.jl index 3be3a468..b6e99daf 100644 --- a/examples/MINLPs/nlp.jl +++ b/examples/MINLPs/nlp.jl @@ -1,7 +1,8 @@ function nlp1(; solver = nothing) m = JuMP.Model(solver) - @variable(m, 1 <= x[1:2] <= 4) + sol = [2.55577, 3.13017] + @variable(m, 1 <= x[i = 1:2] <= 4, start = sol[i]) @NLconstraint(m, x[1] * x[2] >= 8) @NLobjective(m, Min, 6 * x[1]^2 + 4 * x[2]^2 - 2.5 * x[1] * x[2]) @@ -37,7 +38,8 @@ function nlp3(; solver = nothing) LB = [100, 1000, 1000, 10, 10, 10, 10, 10] UB = [10000, 10000, 10000, 1000, 1000, 1000, 1000, 1000] - @variable(m, LB[i] <= x[i = 1:8] <= UB[i]) + sol = [579.30669, 1359.97066, 5109.97054, 182.0177, 295.60118, 217.9823, 286.41653, 395.60118] + @variable(m, LB[i] <= x[i = 1:8] <= UB[i], start = sol[i]) @constraint(m, 0.0025 * (x[4] + x[6]) <= 1) @constraint(m, 0.0025 * (x[5] - x[4] + x[7]) <= 1) diff --git a/examples/run_examples.jl b/examples/run_examples.jl index 792cf0a4..1540de92 100644 --- a/examples/run_examples.jl +++ b/examples/run_examples.jl @@ -35,6 +35,7 @@ const alpine = JuMP.optimizer_with_attributes( "presolve_bt" => true, "apply_partitioning" => true, "partition_scaling_factor" => 10, + "use_start_as_local_solution" => true ) m = nlp3(solver = alpine) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 634e97a6..355e8f61 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -86,7 +86,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer presolve_infeasible::Bool # Presolve infeasibility detection flag best_bound::Float64 # Best bound from MIP best_obj::Float64 # Best feasible objective value - initial_warmval::Vector{Float64} # Warmstart values set to Alpine + warm_start_value::Vector{Float64} # Warmstart values set to Alpine best_sol::Vector{Float64} # Best feasible solution best_bound_sol::Vector{Float64} # Best bound solution (arg-min) presolve_best_rel_gap::Float64 # Post-OBBT relative optimality gap = |best_obj - best_bound|/|best_obj|*100 @@ -100,7 +100,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer # Logging information and status logs::Dict{Symbol,Any} # Logging information - detected_feasible_solution::Bool + detected_incumbent::Bool detected_bound::Bool status::Dict{Symbol,MOI.TerminationStatusCode} # Detailed status of every iteration in the algorithm alpine_status::MOI.TerminationStatusCode # Current Alpine's status @@ -226,7 +226,7 @@ function MOI.empty!(m::Optimizer) Vector{Vector{Float64}}(undef, m.options.disc_consecutive_forbid) m.best_obj = Inf - m.initial_warmval = Float64[] + m.warm_start_value = Float64[] m.best_sol = Float64[] m.best_bound = -Inf m.presolve_best_rel_gap = Inf @@ -264,7 +264,7 @@ function MOI.add_variable(model::Optimizer) push!(model.l_var_orig, -Inf) push!(model.u_var_orig, Inf) push!(model.var_type_orig, :Cont) - push!(model.initial_warmval, 0.0) + push!(model.warm_start_value, 0.0) push!(model.best_sol, 0.0) return MOI.VariableIndex(model.num_var_orig) end @@ -278,7 +278,7 @@ function MOI.set( vi::MOI.VariableIndex, value::Union{Real,Nothing}, ) - model.best_sol[vi.value] = model.initial_warmval[vi.value] = something(value, 0.0) + model.best_sol[vi.value] = model.warm_start_value[vi.value] = something(value, 0.0) return end diff --git a/src/heuristics.jl b/src/heuristics.jl index e354b1dd..32d68274 100644 --- a/src/heuristics.jl +++ b/src/heuristics.jl @@ -7,7 +7,7 @@ function update_disc_cont_var(m::Optimizer) length(m.candidate_disc_vars) <= 15 && return # Algorithm Separation Point # If no feasible solution is found, do NOT update - if !m.detected_feasible_solution + if !m.detected_incumbent println("no feasible solution detected. No update disc var selection.") return end diff --git a/src/log.jl b/src/log.jl index a779db29..f891a65a 100644 --- a/src/log.jl +++ b/src/log.jl @@ -218,7 +218,7 @@ function create_status!(m) status[:local_solve] = MOI.OPTIMIZE_NOT_CALLED # Status of local solve status[:bounding_solve] = MOI.OPTIMIZE_NOT_CALLED # Status of bounding solve - m.detected_feasible_solution = false + m.detected_incumbent = false m.detected_bound = false return m.status = status @@ -240,14 +240,14 @@ function summary_status(m::Optimizer) # happens when lower bound problem is extremely hard to solve # :Unknown : termination with no exception recorded - if m.detected_bound && m.detected_feasible_solution + if m.detected_bound && m.detected_incumbent m.alpine_status = m.best_rel_gap > Alp.get_option(m, :rel_gap) ? MOI.OTHER_LIMIT : MOI.OPTIMAL elseif m.status[:bounding_solve] == MOI.INFEASIBLE m.alpine_status = MOI.INFEASIBLE - elseif m.detected_bound && !m.detected_feasible_solution + elseif m.detected_bound && !m.detected_incumbent m.alpine_status = MOI.OTHER_LIMIT - elseif !m.detected_bound && m.detected_feasible_solution + elseif !m.detected_bound && m.detected_incumbent m.alpine_status = MOI.LOCALLY_SOLVED else @warn " [EXCEPTION] Indefinite Alpine status. Please report your instance (& solver configuration) as an issue (https://github.com/lanl-ansi/Alpine.jl/issues) to help us make Alpine better." diff --git a/src/main_algorithm.jl b/src/main_algorithm.jl index 630ba31a..369d1a02 100644 --- a/src/main_algorithm.jl +++ b/src/main_algorithm.jl @@ -203,13 +203,13 @@ function presolve(m::Optimizer) start_presolve = time() Alp.get_option(m, :log_level) > 0 && printstyled("PRESOLVE \n", color = :cyan) Alp.get_option(m, :log_level) > 0 && println(" Doing local search") - if Alp.get_option(m, :use_start_as_local) + if Alp.get_option(m, :use_start_as_local_solution) obj_warmval = if m.has_nl_objective - MOI.eval_variables(vi -> m.initial_warmval[vi.value], m.objective_function) + MOI.eval_variables(vi -> m.warm_start_value[vi.value], m.objective_function) else - MOI.eval_objective(m.d_orig, m.initial_warmval) + MOI.eval_objective(m.d_orig, m.warm_start_value) end - update_incumb_objective(m, obj_warmval, m.initial_warmval) + Alp.update_incumbent(m, obj_warmval, m.warm_start_value) else Alp.local_solve(m, presolve = true) end @@ -402,7 +402,7 @@ function local_solve(m::Optimizer; presolve = false) if !presolve warmval = m.best_sol[1:m.num_var_orig] else - warmval = m.initial_warmval[1:m.num_var_orig] + warmval = m.warm_start_value[1:m.num_var_orig] end MOI.set(local_solve_model, MOI.VariablePrimalStart(), x, warmval) @@ -454,7 +454,7 @@ function local_solve(m::Optimizer; presolve = false) end @assert length(candidate_sol) == length(sol_temp) - Alp.update_incumb_objective(m, candidate_obj, candidate_sol) + Alp.update_incumbent(m, candidate_obj, candidate_sol) m.status[:local_solve] = local_nlp_status return diff --git a/src/nlexpr.jl b/src/nlexpr.jl index 414248a0..3f1e3715 100644 --- a/src/nlexpr.jl +++ b/src/nlexpr.jl @@ -353,7 +353,7 @@ function traverse_expr_linear_to_affine( return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar end - # HOT-PATCH : Special Structure Recognition + # PATCH : Special Structure Recognition start_pos = 1 if (expr.args[1] == :*) && (length(expr.args) == 3) if (isa(expr.args[2], Float64) || isa(expr.args[2], Int)) && @@ -361,7 +361,7 @@ function traverse_expr_linear_to_affine( (coef != 0.0) ? coef = expr.args[2] * coef : coef = expr.args[2]# Patch # coef = expr.args[2] start_pos = 3 - @warn "Special expression structure detected [*, coef, :call, ...]. Currently using a beta fix..." + # @warn "Special expression structure detected [*, coef, :call, ...]. Currently using a beta fix..." end end diff --git a/src/solver_options.jl b/src/solver_options.jl index d4721801..0f962b5c 100644 --- a/src/solver_options.jl +++ b/src/solver_options.jl @@ -45,7 +45,7 @@ mutable struct OptimizerOptions linking_constraints::Bool # Multilinear linking constraint feature linking_constraints_degree_limit::Int64 # Degree of shared multilinear terms up to which the linking constraints are built and added - # Bound-tightening parameters + # Presolve and bound-tightening parameters presolve_track_time::Bool # Account presolve time for total time usage presolve_bt::Bool # Perform bound tightening procedure before the main algorithm (default: true) presolve_bt_time_limit::Float64 # Time limit for presolving (seconds) @@ -57,11 +57,10 @@ mutable struct OptimizerOptions presolve_bt_algo::Any # Method used for bound tightening procedures, can either be an index of default methods or functional inputs presolve_bt_relax_integrality::Bool # Relax the MIP solved in built-in relaxation scheme for time performance presolve_bt_mip_time_limit::Float64 # Time limit for a single MIP solved in the built-in bound tightening algorithm (with partitions) + use_start_as_local_solution::Bool # Use starting value as local solution for presolve without invoking a local solver # Domain Reduction presolve_bp::Bool # Conduct basic bound propagation - - use_start_as_local # Use starting value as local solution for presolve end function get_default_options() @@ -110,8 +109,8 @@ function get_default_options() presolve_bt_algo = 1 presolve_bt_relax_integrality = false presolve_bt_mip_time_limit = Inf + use_start_as_local_solution = false presolve_bp = false - use_start_as_local = false return OptimizerOptions( log_level, @@ -154,7 +153,7 @@ function get_default_options() presolve_bt_algo, presolve_bt_relax_integrality, presolve_bt_mip_time_limit, + use_start_as_local_solution, presolve_bp, - use_start_as_local, ) end diff --git a/src/utility.jl b/src/utility.jl index e52a3f54..c5e32cae 100644 --- a/src/utility.jl +++ b/src/utility.jl @@ -104,13 +104,13 @@ discretization_to_bounds(d::Dict, l::Int) = Alp.update_var_bounds(d, len = l) """ Update the data structure with feasible solution and its associated objective (if better) """ -function update_incumb_objective(m::Optimizer, objval::Float64, sol::Vector) +function update_incumbent(m::Optimizer, objval::Float64, sol::Vector) convertor = Dict(MOI.MAX_SENSE => :>, MOI.MIN_SENSE => :<) push!(m.logs[:obj], objval) if eval(convertor[m.sense_orig])(objval, m.best_obj) #&& !eval(convertor[m.sense_orig])(objval, m.best_bound) m.best_obj = objval m.best_sol = sol - m.detected_feasible_solution = true + m.detected_incumbent = true end return From ff9e84a58c13067e6d4193389c21be3683bbd92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 12 Sep 2022 13:33:27 +0200 Subject: [PATCH 3/6] Fix evalutation --- src/main_algorithm.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main_algorithm.jl b/src/main_algorithm.jl index 369d1a02..7f6ba493 100644 --- a/src/main_algorithm.jl +++ b/src/main_algorithm.jl @@ -205,9 +205,9 @@ function presolve(m::Optimizer) Alp.get_option(m, :log_level) > 0 && println(" Doing local search") if Alp.get_option(m, :use_start_as_local_solution) obj_warmval = if m.has_nl_objective - MOI.eval_variables(vi -> m.warm_start_value[vi.value], m.objective_function) - else MOI.eval_objective(m.d_orig, m.warm_start_value) + else + MOI.eval_variables(vi -> m.warm_start_value[vi.value], m.objective_function) end Alp.update_incumbent(m, obj_warmval, m.warm_start_value) else From c86d72b9e36a1528c56d85fa1a068e1140a06682 Mon Sep 17 00:00:00 2001 From: harshangrjn Date: Sat, 24 Sep 2022 15:53:22 -0600 Subject: [PATCH 4/6] unit tests added --- CHANGELOG.md | 5 ++-- docs/src/functions.md | 4 ++-- docs/src/parameters.md | 2 +- examples/MINLPs/nlp.jl | 6 ++--- examples/run_examples.jl | 1 - src/bounding_model.jl | 2 +- src/main_algorithm.jl | 47 ++++++++++++++++++++++++-------------- src/presolve.jl | 22 ++++++++---------- src/solver_options.jl | 6 ++--- test/test_algorithm.jl | 49 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 101 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd0b9e3..92632d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ - Re-org and clean-up in partition additions within bounding MIP model - Updated to cheaper `AffExpr` evaluation within linking constraints - Minor update in gap evaluataion after OBBT termination with fixed iterations -- Added option to use warm start value as also the intial local solution in presolve without invoking a local solver (#218) +- Added solver option `use_start_as_incumbent` to use warm start value as also an intial incumbent solution in presolve without invoking a local solver. (#218) +- Added unit tests for testing warm start point ## v0.5.0 - New feature: Linking constraints for multilinear terms with uniform and adaptive partitions (significant speed up in run times for multilinear problems: http://www.optimization-online.org/DB_HTML/2022/07/8974.html) (@jongeunkim) @@ -50,7 +51,7 @@ - Bug fix in `relax_integrality` under presolve - Added Rosenbrock function - Added user-definable `presolve_bt_improv_tol` for variable range reductions in OBBT -- Minor update on finite values for obj-bound in `create_bound_tightening_model` +- Minor update on finite values for obj-bound in `create_obbt_model` - NLP solution rounding issue resolved (now compatible with default tol, 1e-6), to avoid the variable solution outside discretization (#190) - `circleN` instance updated to `circle_MINLPLib` to eliminate Pavito's mixed-integer cycling in unit tests (#190) - Clean-up in printing variable solutions (`variable_values`) diff --git a/docs/src/functions.md b/docs/src/functions.md index 2126c46b..ab0d924a 100644 --- a/docs/src/functions.md +++ b/docs/src/functions.md @@ -25,8 +25,8 @@ min_vertex_cover ```@docs bound_tightening optimization_based_bound_tightening -create_bound_tightening_model -solve_bound_tightening_model +create_obbt_model +solve_obbt_model resolve_var_bounds ``` diff --git a/docs/src/parameters.md b/docs/src/parameters.md index dffb7189..6f364fab 100644 --- a/docs/src/parameters.md +++ b/docs/src/parameters.md @@ -43,6 +43,6 @@ Here are a few general solver options which control the performance of Alpine: * `presolve_bt_mip_time_limit (default = Inf)`: time limit for individual MILPs solved during the sequential OBBT procedure. -* `use_start_as_local_solution (default = false)`: if `true`, Alpine does not perform any local optimization during the presolve and uses the starting value instead (*warning*: Alpine assumes the feasibility of the starting value and does not check it). +* `use_start_as_incumbent (default = false)`: if `true`, Alpine does not perform any local optimization during the presolve and uses the starting value instead (*warning*: Alpine assumes the feasibility of the starting value and does not check it). Note that the above-mentioned list of solver options is not comprehensive, but can be found in [solver.jl](https://github.com/lanl-ansi/Alpine.jl/blob/master/src/solver.jl). diff --git a/examples/MINLPs/nlp.jl b/examples/MINLPs/nlp.jl index b6e99daf..3be3a468 100644 --- a/examples/MINLPs/nlp.jl +++ b/examples/MINLPs/nlp.jl @@ -1,8 +1,7 @@ function nlp1(; solver = nothing) m = JuMP.Model(solver) - sol = [2.55577, 3.13017] - @variable(m, 1 <= x[i = 1:2] <= 4, start = sol[i]) + @variable(m, 1 <= x[1:2] <= 4) @NLconstraint(m, x[1] * x[2] >= 8) @NLobjective(m, Min, 6 * x[1]^2 + 4 * x[2]^2 - 2.5 * x[1] * x[2]) @@ -38,8 +37,7 @@ function nlp3(; solver = nothing) LB = [100, 1000, 1000, 10, 10, 10, 10, 10] UB = [10000, 10000, 10000, 1000, 1000, 1000, 1000, 1000] - sol = [579.30669, 1359.97066, 5109.97054, 182.0177, 295.60118, 217.9823, 286.41653, 395.60118] - @variable(m, LB[i] <= x[i = 1:8] <= UB[i], start = sol[i]) + @variable(m, LB[i] <= x[i = 1:8] <= UB[i]) @constraint(m, 0.0025 * (x[4] + x[6]) <= 1) @constraint(m, 0.0025 * (x[5] - x[4] + x[7]) <= 1) diff --git a/examples/run_examples.jl b/examples/run_examples.jl index 1540de92..792cf0a4 100644 --- a/examples/run_examples.jl +++ b/examples/run_examples.jl @@ -35,7 +35,6 @@ const alpine = JuMP.optimizer_with_attributes( "presolve_bt" => true, "apply_partitioning" => true, "partition_scaling_factor" => 10, - "use_start_as_local_solution" => true ) m = nlp3(solver = alpine) diff --git a/src/bounding_model.jl b/src/bounding_model.jl index a147c1f5..13e01cb3 100644 --- a/src/bounding_model.jl +++ b/src/bounding_model.jl @@ -11,7 +11,7 @@ this MILP is required. """ function create_bounding_mip(m::Optimizer; use_disc = nothing) if (use_disc === nothing) - if (m.logs[:n_iter] == 1) && (m.status[:local_solve] in STATUS_OPT || m.status[:local_solve] in STATUS_LIMIT) + if (m.logs[:n_iter] == 1) && (m.status[:local_solve] in union(STATUS_OPT, STATUS_LIMIT, STATUS_WARM_START)) # Setting up an initial partition Alp.add_partition(m, use_solution = m.best_sol) elseif m.logs[:n_iter] >= 2 diff --git a/src/main_algorithm.jl b/src/main_algorithm.jl index 7f6ba493..81f04192 100644 --- a/src/main_algorithm.jl +++ b/src/main_algorithm.jl @@ -10,6 +10,9 @@ const STATUS_LIMIT = [ ] const STATUS_OPT = [MOI.OPTIMAL, MOI.LOCALLY_SOLVED, MOI.ALMOST_OPTIMAL, MOI.ALMOST_LOCALLY_SOLVED] + +const STATUS_WARM_START = [MOI.OPTIMIZE_NOT_CALLED] + const STATUS_INF = [MOI.INFEASIBLE, MOI.LOCALLY_INFEASIBLE] function features_available(m::Optimizer) @@ -42,7 +45,7 @@ function load!(m::Optimizer) elseif m.objective_function isa Nothing m.obj_expr_orig = Expr(:call, :+) else - m.obj_expr_orig = _moi_function_to_expr(m.objective_function) + m.obj_expr_orig = Alp._moi_function_to_expr(m.objective_function) end # Collect original variable type and build dynamic variable type space @@ -159,8 +162,6 @@ function MOI.optimize!(m::Optimizer) println( "====================================================================================================", ) - else - println(" Presolve terminated with a global optimal solution") end Alp.summary_status(m) @@ -203,23 +204,33 @@ function presolve(m::Optimizer) start_presolve = time() Alp.get_option(m, :log_level) > 0 && printstyled("PRESOLVE \n", color = :cyan) Alp.get_option(m, :log_level) > 0 && println(" Doing local search") - if Alp.get_option(m, :use_start_as_local_solution) + + if Alp.get_option(m, :use_start_as_incumbent) + # Check for bound feasibility of the warm start value + if !(m.l_var_orig <= m.warm_start_value <= m.u_var_orig) + error("Provide a valid, feasible, warm starting point. Else, set use_start_as_incumbent to false") + end + obj_warmval = if m.has_nl_objective MOI.eval_objective(m.d_orig, m.warm_start_value) else - MOI.eval_variables(vi -> m.warm_start_value[vi.value], m.objective_function) + MOI.Utilities.eval_variables(vi -> m.warm_start_value[vi.value], m.objective_function) end Alp.update_incumbent(m, obj_warmval, m.warm_start_value) + m.status[:local_solve] = MOI.OPTIMIZE_NOT_CALLED + Alp.get_option(m, :log_level) > 0 && println(" Using warm starting point as a local incumbent solution with value $(round(m.best_obj, digits=4))") else Alp.local_solve(m, presolve = true) end - + # Solver status - if m.status[:local_solve] in STATUS_OPT || m.status[:local_solve] in STATUS_LIMIT - Alp.get_option(m, :log_level) > 0 && println( - " Local solver returns a feasible point with value $(round(m.best_obj, digits=4))", - ) - Alp.bound_tightening(m, use_bound = true) # performs bound-tightening with the local solve objective value + if m.status[:local_solve] in union(STATUS_OPT, STATUS_LIMIT, STATUS_WARM_START) + if !Alp.get_option(m, :use_start_as_incumbent) + Alp.get_option(m, :log_level) > 0 && println( + " Local solver returns a feasible point with value $(round(m.best_obj, digits=4))", + ) + end + Alp.bound_tightening_wrapper(m, use_bound = true) # performs bound-tightening with the local solve objective value Alp.get_option(m, :presolve_bt) && Alp.init_disc(m) # Re-initialize discretization dictionary on tight bounds Alp.get_option(m, :partition_scaling_factor_branch) && (Alp.set_option( m, @@ -230,7 +241,7 @@ function presolve(m::Optimizer) elseif m.status[:local_solve] in STATUS_INF (Alp.get_option(m, :log_level) > 0) && println(" Bound tightening without objective bounds (OBBT)") - Alp.bound_tightening(m, use_bound = false) # do bound tightening without objective value + Alp.bound_tightening_wrapper(m, use_bound = false) # do bound tightening without objective value (Alp.get_option(m, :partition_scaling_factor_branch)) && (Alp.set_option( m, :partition_scaling_factor, @@ -239,9 +250,8 @@ function presolve(m::Optimizer) Alp.get_option(m, :presolve_bt) && Alp.init_disc(m) elseif m.status[:local_solve] == MOI.INVALID_MODEL - @warn " Warning: Presolve ends with local solver yielding $(m.status[:local_solve]). \n This may come from Ipopt's `:Not_Enough_Degrees_Of_Freedom`. \n Consider more replace equality constraints with >= and <= to resolve this." - - else + @warn " Warning: Presolve ends with local solver yielding $(m.status[:local_solve]). \n This may come from Ipopt's `:Not_Enough_Degrees_Of_Freedom`." + elseif !Alp.get_option(m, :use_start_as_incumbent) @warn " Warning: Presolve ends with local solver yielding $(m.status[:local_solve])." end @@ -335,8 +345,11 @@ function load_nonlinear_model(m::Optimizer, model::MOI.ModelLike, l_var, u_var) m.objective_function, ) end - block = MOI.NLPBlockData(m.nl_constraint_bounds_orig, m.d_orig, m.has_nl_objective) - MOI.set(model, MOI.NLPBlock(), block) + + if m.d_orig !== nothing + block = MOI.NLPBlockData(m.nl_constraint_bounds_orig, m.d_orig, m.has_nl_objective) + MOI.set(model, MOI.NLPBlock(), block) + end return x end diff --git a/src/presolve.jl b/src/presolve.jl index 037699af..e26e10a2 100644 --- a/src/presolve.jl +++ b/src/presolve.jl @@ -1,5 +1,5 @@ """ - bound_tightening(m::Optimizer) + bound_tightening_wrapper(m::Optimizer) Entry point to the optimization-based bound-tightening (OBBT) algorithm. The aim of the OBBT algorithm is to sequentially tighten the variable bounds until a fixed point is reached. @@ -10,7 +10,7 @@ Currently, two OBBT methods are implemented in [`optimization_based_bound_tighte * Bound-tightening with piecewise polyhedral relaxations: (with three partitions around the local feasible solution) If no local feasible solution is obtained, the algorithm defaults to OBBT without partitions """ -function bound_tightening(m::Optimizer; use_bound = true, kwargs...) +function bound_tightening_wrapper(m::Optimizer; use_bound = true, kwargs...) Alp.get_option(m, :presolve_bt) || return if Alp.get_option(m, :presolve_bt_algo) == 1 @@ -115,14 +115,14 @@ function optimization_based_bound_tightening( temp_bounds[var_idx] = [discretization[var_idx][1], discretization[var_idx][end]] if (discretization[var_idx][end] - discretization[var_idx][1]) > width_tol - Alp.create_bound_tightening_model(m, discretization, bound) + Alp.create_obbt_model(m, discretization, bound) for sense in both_senses JuMP.@objective( m.model_mip, sense, _index_to_variable_ref(m.model_mip, var_idx) ) - stats = Alp.solve_bound_tightening_model(m) + stats = Alp.solve_obbt_model(m) if stats["status"] in STATUS_OPT temp_bounds[var_idx][tell_side[sense]] = tell_round[sense]( @@ -278,12 +278,12 @@ function optimization_based_bound_tightening( end """ - create_bound_tightening_model(m::Optimizer, discretization::Dict, bound::Float64) + create_obbt_model(m::Optimizer, discretization::Dict, bound::Float64) This function takes in the initial discretization information and builds the OBBT model. It is an algorithm specific function called by [`optimization_based_bound_tightening`](@ref). """ -function create_bound_tightening_model( +function create_obbt_model( m::Optimizer, discretization, bound::Number; @@ -311,7 +311,7 @@ function create_bound_tightening_model( end function relaxation_model_obbt(m::Optimizer, discretization, bound::Number) - Alp.create_bound_tightening_model(m, discretization, bound) + Alp.create_obbt_model(m, discretization, bound) obj_expr = sum( m.bounding_obj_mip[:coefs][j] * @@ -325,17 +325,15 @@ function relaxation_model_obbt(m::Optimizer, discretization, bound::Number) JuMP.@objective(m.model_mip, Max, obj_expr) end - stats = Alp.solve_bound_tightening_model(m) - - return stats + return Alp.solve_obbt_model(m) end """ - solve_bound_tightening_model(m::Optimizer) + solve_obbt_model(m::Optimizer) A function that solves the min and max OBBT model. """ -function solve_bound_tightening_model(m::Optimizer; kwargs...) +function solve_obbt_model(m::Optimizer; kwargs...) stats = Dict() # ========= MILP Solve ========= # diff --git a/src/solver_options.jl b/src/solver_options.jl index 0f962b5c..acaade82 100644 --- a/src/solver_options.jl +++ b/src/solver_options.jl @@ -57,7 +57,7 @@ mutable struct OptimizerOptions presolve_bt_algo::Any # Method used for bound tightening procedures, can either be an index of default methods or functional inputs presolve_bt_relax_integrality::Bool # Relax the MIP solved in built-in relaxation scheme for time performance presolve_bt_mip_time_limit::Float64 # Time limit for a single MIP solved in the built-in bound tightening algorithm (with partitions) - use_start_as_local_solution::Bool # Use starting value as local solution for presolve without invoking a local solver + use_start_as_incumbent::Bool # Use starting value as a local incumbent solution for presolve without invoking a local solver # Domain Reduction presolve_bp::Bool # Conduct basic bound propagation @@ -109,7 +109,7 @@ function get_default_options() presolve_bt_algo = 1 presolve_bt_relax_integrality = false presolve_bt_mip_time_limit = Inf - use_start_as_local_solution = false + use_start_as_incumbent = false presolve_bp = false return OptimizerOptions( @@ -153,7 +153,7 @@ function get_default_options() presolve_bt_algo, presolve_bt_relax_integrality, presolve_bt_mip_time_limit, - use_start_as_local_solution, + use_start_as_incumbent, presolve_bp, ) end diff --git a/test/test_algorithm.jl b/test/test_algorithm.jl index 890c8e9f..b2d330ff 100644 --- a/test/test_algorithm.jl +++ b/test/test_algorithm.jl @@ -970,3 +970,52 @@ end @test in(Set{Any}([1, 2, 4]), alp.linking_constraints_info[[1, 4]]) @test in(Set{Any}([1, 3, 4]), alp.linking_constraints_info[[1, 4]]) end + +@testset "Warm start value used as the incumbent solution" begin + + test_solver = JuMP.optimizer_with_attributes( + Alpine.Optimizer, + "nlp_solver" => IPOPT, + "mip_solver" => HIGHS, + "presolve_bt" => false, + "max_iter" => 1, + "use_start_as_incumbent" => false + ) + sol = [579.30669, 1359.97066, 5109.97054, 182.0177, 295.60118, 217.9823, 286.41653, 395.60118] + + m = nlp3(solver = test_solver) + for i=1:length(m[:x]) + JuMP.set_start_value(m[:x][i], sol[i]) + end + + JuMP.optimize!(m) + alp = JuMP.backend(m).optimizer.model + + @test termination_status(m) == MOI.OTHER_LIMIT + @test isapprox(objective_value(m), 7049.247897696512, atol = 1e-6) + @test isapprox(alp.best_bound, 3834.978106740535, atol = 1E-6) + @test alp.options.use_start_as_incumbent == false + + test_solver = JuMP.optimizer_with_attributes( + Alpine.Optimizer, + "nlp_solver" => IPOPT, + "mip_solver" => HIGHS, + "presolve_bt" => true, + "max_iter" => 1, + "use_start_as_incumbent" => true + ) + + m = nlp3(solver = test_solver) + for i=1:length(m[:x]) + JuMP.set_start_value(m[:x][i], sol[i]) + end + + JuMP.optimize!(m) + alp = JuMP.backend(m).optimizer.model + + @test termination_status(m) == MOI.OTHER_LIMIT + @test isapprox(alp.best_bound, 4810.212866711817, atol = 1E-6) + @test alp.warm_start_value == sol + @test alp.options.use_start_as_incumbent == true + +end \ No newline at end of file From 1fb1053f9f73c3e79662e8b3fd660b6250d6f69a Mon Sep 17 00:00:00 2001 From: harshangrjn Date: Sat, 24 Sep 2022 15:55:55 -0600 Subject: [PATCH 5/6] code formatting --- src/bounding_model.jl | 17 +++++++++-------- src/main_algorithm.jl | 20 ++++++++++++++------ src/multilinear.jl | 3 ++- src/presolve.jl | 9 ++------- src/utility.jl | 2 +- src/variable_bounds.jl | 2 +- test/test_algorithm.jl | 23 +++++++++++++++-------- 7 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/bounding_model.jl b/src/bounding_model.jl index 13e01cb3..191e9115 100644 --- a/src/bounding_model.jl +++ b/src/bounding_model.jl @@ -9,17 +9,18 @@ will choose one specific partition as the lower bound solution. The more partiti better or finer bounding model relax the original MINLP while the more efforts required to solve this MILP is required. """ -function create_bounding_mip(m::Optimizer; use_disc = nothing) +function create_bounding_mip(m::Optimizer; use_disc = nothing) if (use_disc === nothing) - if (m.logs[:n_iter] == 1) && (m.status[:local_solve] in union(STATUS_OPT, STATUS_LIMIT, STATUS_WARM_START)) + if (m.logs[:n_iter] == 1) && + (m.status[:local_solve] in union(STATUS_OPT, STATUS_LIMIT, STATUS_WARM_START)) # Setting up an initial partition - Alp.add_partition(m, use_solution = m.best_sol) + Alp.add_partition(m, use_solution = m.best_sol) elseif m.logs[:n_iter] >= 2 # Add subsequent iteration partitions Alp.add_partition(m) end discretization = m.discretization - else + else discretization = use_disc end @@ -59,11 +60,11 @@ function amp_post_vars(m::Optimizer; kwargs...) if haskey(options, :use_disc) l_var = [ options[:use_disc][i][1] for - i in 1:(m.num_var_orig + m.num_var_linear_mip + m.num_var_nonlinear_mip) + i in 1:(m.num_var_orig+m.num_var_linear_mip+m.num_var_nonlinear_mip) ] u_var = [ options[:use_disc][i][end] for - i in 1:(m.num_var_orig + m.num_var_linear_mip + m.num_var_nonlinear_mip) + i in 1:(m.num_var_orig+m.num_var_linear_mip+m.num_var_nonlinear_mip) ] else l_var = m.l_var_tight @@ -72,10 +73,10 @@ function amp_post_vars(m::Optimizer; kwargs...) JuMP.@variable( m.model_mip, - x[i = 1:(m.num_var_orig + m.num_var_linear_mip + m.num_var_nonlinear_mip)] + x[i = 1:(m.num_var_orig+m.num_var_linear_mip+m.num_var_nonlinear_mip)] ) - for i in 1:(m.num_var_orig + m.num_var_linear_mip + m.num_var_nonlinear_mip) + for i in 1:(m.num_var_orig+m.num_var_linear_mip+m.num_var_nonlinear_mip) # Interestingly, not enforcing category of lifted variables is able to improve performance if i <= m.num_var_orig if m.var_type_orig[i] == :Bin diff --git a/src/main_algorithm.jl b/src/main_algorithm.jl index 81f04192..74b24b33 100644 --- a/src/main_algorithm.jl +++ b/src/main_algorithm.jl @@ -208,21 +208,28 @@ function presolve(m::Optimizer) if Alp.get_option(m, :use_start_as_incumbent) # Check for bound feasibility of the warm start value if !(m.l_var_orig <= m.warm_start_value <= m.u_var_orig) - error("Provide a valid, feasible, warm starting point. Else, set use_start_as_incumbent to false") + error( + "Provide a valid, feasible, warm starting point. Else, set use_start_as_incumbent to false", + ) end obj_warmval = if m.has_nl_objective MOI.eval_objective(m.d_orig, m.warm_start_value) else - MOI.Utilities.eval_variables(vi -> m.warm_start_value[vi.value], m.objective_function) + MOI.Utilities.eval_variables( + vi -> m.warm_start_value[vi.value], + m.objective_function, + ) end Alp.update_incumbent(m, obj_warmval, m.warm_start_value) m.status[:local_solve] = MOI.OPTIMIZE_NOT_CALLED - Alp.get_option(m, :log_level) > 0 && println(" Using warm starting point as a local incumbent solution with value $(round(m.best_obj, digits=4))") + Alp.get_option(m, :log_level) > 0 && println( + " Using warm starting point as a local incumbent solution with value $(round(m.best_obj, digits=4))", + ) else Alp.local_solve(m, presolve = true) end - + # Solver status if m.status[:local_solve] in union(STATUS_OPT, STATUS_LIMIT, STATUS_WARM_START) if !Alp.get_option(m, :use_start_as_incumbent) @@ -345,9 +352,10 @@ function load_nonlinear_model(m::Optimizer, model::MOI.ModelLike, l_var, u_var) m.objective_function, ) end - + if m.d_orig !== nothing - block = MOI.NLPBlockData(m.nl_constraint_bounds_orig, m.d_orig, m.has_nl_objective) + block = + MOI.NLPBlockData(m.nl_constraint_bounds_orig, m.d_orig, m.has_nl_objective) MOI.set(model, MOI.NLPBlock(), block) end diff --git a/src/multilinear.jl b/src/multilinear.jl index 969a66c7..d7539383 100644 --- a/src/multilinear.jl +++ b/src/multilinear.jl @@ -583,7 +583,8 @@ function amp_post_inequalities_cont( return elseif Alp.get_option(m, :convhull_formulation) == "facet" for j in 1:(partition_cnt-1) # Constraint cluster of α >= f(λ) - sliced_indices = Alp.collect_indices(λ[ml_indices][:indices], cnt, [1:j;], dim) + sliced_indices = + Alp.collect_indices(λ[ml_indices][:indices], cnt, [1:j;], dim) JuMP.@constraint( m.model_mip, sum(α[var_ind][1:j]) >= sum(λ[ml_indices][:vars][sliced_indices]) diff --git a/src/presolve.jl b/src/presolve.jl index e26e10a2..bda4d1fe 100644 --- a/src/presolve.jl +++ b/src/presolve.jl @@ -255,7 +255,7 @@ function optimization_based_bound_tightening( println(" Actual iterations (OBBT): ", (m.logs[:bt_iter])) m.l_var_tight, m.u_var_tight = Alp.update_var_bounds(discretization) - + if haskey(options, :use_tmc) m.discretization = Alp.add_adaptive_partition(m, use_solution = m.best_sol) end @@ -283,12 +283,7 @@ end This function takes in the initial discretization information and builds the OBBT model. It is an algorithm specific function called by [`optimization_based_bound_tightening`](@ref). """ -function create_obbt_model( - m::Optimizer, - discretization, - bound::Number; - kwargs..., -) +function create_obbt_model(m::Optimizer, discretization, bound::Number; kwargs...) # options = Dict(kwargs) start_build = time() diff --git a/src/utility.jl b/src/utility.jl index c5e32cae..5ffeab7c 100644 --- a/src/utility.jl +++ b/src/utility.jl @@ -170,7 +170,7 @@ Check if the solution is always the same within the last disc_consecutive_forbid """ function check_solution_history(m::Optimizer, ind::Int) Alp.get_option(m, :disc_consecutive_forbid) == 0 && return false - ((m.logs[:n_iter]-1) < Alp.get_option(m, :disc_consecutive_forbid)) && return false + ((m.logs[:n_iter] - 1) < Alp.get_option(m, :disc_consecutive_forbid)) && return false sol_val = m.bound_sol_history[mod( m.logs[:n_iter] - 2, diff --git a/src/variable_bounds.jl b/src/variable_bounds.jl index a6d6f1ce..04d6a2fd 100644 --- a/src/variable_bounds.jl +++ b/src/variable_bounds.jl @@ -456,7 +456,7 @@ Output:: l_var::Vector{Float64}, u_var::Vector{Float64} """ -function update_var_bounds(discretization::Dict{Any, Any}; kwargs...) +function update_var_bounds(discretization::Dict{Any,Any}; kwargs...) options = Dict(kwargs) haskey(options, :len) ? len = options[:len] : len = length(keys(discretization)) diff --git a/test/test_algorithm.jl b/test/test_algorithm.jl index b2d330ff..e2a915a3 100644 --- a/test/test_algorithm.jl +++ b/test/test_algorithm.jl @@ -972,19 +972,27 @@ end end @testset "Warm start value used as the incumbent solution" begin - test_solver = JuMP.optimizer_with_attributes( Alpine.Optimizer, "nlp_solver" => IPOPT, "mip_solver" => HIGHS, "presolve_bt" => false, "max_iter" => 1, - "use_start_as_incumbent" => false + "use_start_as_incumbent" => false, ) - sol = [579.30669, 1359.97066, 5109.97054, 182.0177, 295.60118, 217.9823, 286.41653, 395.60118] + sol = [ + 579.30669, + 1359.97066, + 5109.97054, + 182.0177, + 295.60118, + 217.9823, + 286.41653, + 395.60118, + ] m = nlp3(solver = test_solver) - for i=1:length(m[:x]) + for i in 1:length(m[:x]) JuMP.set_start_value(m[:x][i], sol[i]) end @@ -1002,11 +1010,11 @@ end "mip_solver" => HIGHS, "presolve_bt" => true, "max_iter" => 1, - "use_start_as_incumbent" => true + "use_start_as_incumbent" => true, ) m = nlp3(solver = test_solver) - for i=1:length(m[:x]) + for i in 1:length(m[:x]) JuMP.set_start_value(m[:x][i], sol[i]) end @@ -1017,5 +1025,4 @@ end @test isapprox(alp.best_bound, 4810.212866711817, atol = 1E-6) @test alp.warm_start_value == sol @test alp.options.use_start_as_incumbent == true - -end \ No newline at end of file +end From 7765919fb2b00a77bc9784ed0b3a120ec64b1acb Mon Sep 17 00:00:00 2001 From: harshangrjn Date: Sat, 24 Sep 2022 17:31:19 -0600 Subject: [PATCH 6/6] docs renamed func [ci skip] --- docs/src/functions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/functions.md b/docs/src/functions.md index ab0d924a..9d4e7fa4 100644 --- a/docs/src/functions.md +++ b/docs/src/functions.md @@ -23,11 +23,12 @@ min_vertex_cover ## Presolve Methods ```@docs -bound_tightening +bound_tightening_wrapper optimization_based_bound_tightening create_obbt_model solve_obbt_model resolve_var_bounds +post_objective_bound ``` ## Utility Methods