diff --git a/News.md b/News.md index ff85ec26..5dfa821e 100644 --- a/News.md +++ b/News.md @@ -4,10 +4,10 @@ - 4/12/2018: Initial release of combined EAGO packages v0.1.1. ## v0.1.2 -- 6/20/2018: [EAGO v0.1.2 has been tagged](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.1.2). Significant speed and functionality updates. +- 6/20/2018: [**EAGO v0.1.2 has been tagged**](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.1.2). Significant speed and functionality updates. ## v0.2.0 -- 6/14/2019: [EAGO v0.2.0 has been tagged](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.2.0). This update creates a number of breaking changes to the EAGO API. Please review the use cases provided in the documentation to update examples. +- 6/14/2019: [**EAGO v0.2.0 has been tagged**](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.2.0). This update creates a number of breaking changes to the EAGO API. Please review the use cases provided in the documentation to update examples. - Updated to support Julia 1.0+, MathOptInterface (MOI), and MOI construction of subproblems. - Additional domain reduction routines available. - Support for specialized handling of linear and quadratic terms. @@ -60,5 +60,12 @@ - Drops appveyor CI and Travis CI in favor of Github Actions. - 11/18/2020 [**EAGO v0.5.1 has been tagged**](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.5.1) - Support for Julia ~1 (with limited functionality for Julia 1.0, 1.1). -- 11/18/2020 **EAGO v0.5.2 has been tagged** +- 11/18/2020 [**EAGO v0.5.2 has been tagged**](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.5.2) - Fix user specified branching variables. + +## v0.8.0 +- 6/12/2023: [**EAGO v0.8.0 has been tagged**](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.8.0). + - Updated EAGO for compatibility with the nonlinear expression API changes introduced in JuMP v1.2: https://discourse.julialang.org/t/ann-upcoming-refactoring-of-jumps-nonlinear-api/83052 + - EAGO now uses the `MOI.Nonlinear` submodule instead of `JuMP._Derivatives`. + - Models, nodes, expressions, constraints, and operators are now compatible with MOI. + - Added logic and comparison operators to `EAGO.OperatorRegistry`. \ No newline at end of file diff --git a/Project.toml b/Project.toml index b49bfdc7..8ec063b6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "EAGO" uuid = "bb8be931-2a91-5aca-9f87-79e1cb69959a" authors = ["Matthew Wilhelm "] -version = "0.8.0" +version = "0.8.1" [deps] Cassette = "7057c7e9-c182-5462-911a-8362d720325c" @@ -41,7 +41,7 @@ MINLPTests = "0.5.2" MathOptInterface = "~1" McCormick = "0.13" NaNMath = "0.3.5, 1.0" -PrettyTables = "~1" +PrettyTables = "~2" Reexport = "~0.2, ~1" Requires = "~1" SpecialFunctions = "~1, ~2" diff --git a/README.md b/README.md index 05944724..43779b5e 100644 --- a/README.md +++ b/README.md @@ -99,11 +99,11 @@ As a global optimization platform, EAGO's solvers can be used to find solutions The EAGO package has numerous features: a solver accessible from JuMP/MathOptInterface (MOI), domain reduction routines, McCormick relaxations, and specialized non-convex semi-infinite program solvers. A full description of all EAGO features is available on the [**documentation website**](https://psorlab.github.io/EAGO.jl/dev/). A series of example have been provided in the form of Jupyter notebooks in the separate [**EAGO-notebooks**](https://github.com/PSORLab/EAGO-notebooks) repository. ## Recent News -- 6/12/2023: [EAGO v0.8.0 has been tagged](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.8.0). - - Updated EAGO for compatibility with the nonlinear expression API changes introduced in JuMP v1.2: https://discourse.julialang.org/t/ann-upcoming-refactoring-of-jumps-nonlinear-api/83052 - - EAGO now uses the `MOI.Nonlinear` submodule instead of `JuMP._Derivatives`. - - Models, nodes, expressions, constraints, and operators are now compatible with MOI. - - Added logic and comparison operators to `EAGO.OperatorRegistry`. +- 6/15/2023: [**EAGO v0.8.1 has been tagged**](https://github.com/PSORLab/EAGO.jl/releases/tag/v0.8.1). + - Resolved an issue where integer and binary variables would sometimes throw an `MathOptInterface.UpperBoundAlreadySet` error. + - Added the function `unbounded_check!` which warns users if they are missing variable bounds and sets them to +/- 1E10 by default. + - Added an EAGO parameter `unbounded_check` which defaults to `true` and enables `unbounded_check!`. + - Bumped requirement for PrettyTables.jl to v2+ to accomodate the latest version of DataFrames.jl. For a full list of EAGO release news, click [**here**](https://github.com/PSORLab/EAGO.jl/releases). diff --git a/src/eago_optimizer/functions/nonlinear/apriori_relax/affine_arithmetic.jl b/src/eago_optimizer/functions/nonlinear/apriori_relax/affine_arithmetic.jl index 93882618..238c5cd0 100644 --- a/src/eago_optimizer/functions/nonlinear/apriori_relax/affine_arithmetic.jl +++ b/src/eago_optimizer/functions/nonlinear/apriori_relax/affine_arithmetic.jl @@ -248,7 +248,7 @@ end MC(x::MCAffPnt{N,T}) where {N,T<:RelaxTag} = x.v MC(x::MC{N,T}) where {N, T<:RelaxTag} = x -relax_info(s::RelaxAA, n::Int, t::T) where {N,T} = MCAffPnt{n,T} +relax_info(s::RelaxAA, n::Int, t::T) where T = MCAffPnt{n,T} zero(::Type{MCAffPnt{N,T}}) where {N,T} = MCAffPnt{N,T}(zero(MC{N,T}), zero(AffineEAGO{N})) zero(x::MCAffPnt{N,T}) where {N,T} = MCAffPnt{N,T}(zero(x.v), zero(x.box)) @@ -297,7 +297,7 @@ function cut(x::MC{N,T}, z::MCAffPnt{N,T}, v::VariableValues, ϵ::Float64, s::Ve return x end -function varset(::Type{MCAffPnt{N,T}}, i, x_cv, x_cc, l, u) where {V,N,T<:RelaxTag} +function varset(::Type{MCAffPnt{N,T}}, i, x_cv, x_cc, l, u) where {N,T<:RelaxTag} v = seed_gradient(i, Val(N)) v_Intv = Interval{Float64}(l, u) v_mc = MC{N,T}(x_cv, x_cc, v_Intv, v, v, false) diff --git a/src/eago_optimizer/functions/nonlinear/composite_relax/composite_relax.jl b/src/eago_optimizer/functions/nonlinear/composite_relax/composite_relax.jl index 5daf617c..88c20336 100644 --- a/src/eago_optimizer/functions/nonlinear/composite_relax/composite_relax.jl +++ b/src/eago_optimizer/functions/nonlinear/composite_relax/composite_relax.jl @@ -284,5 +284,5 @@ function display_table!(g::DAT, b::RelaxCache{V,N,T}) where {V,N,T<:RelaxTag} val = [b._is_num[i] ? b._num[i] : b._set[i] for i in 1:nc] exr = [display_expr(g, i) for i in 1:nc] data = hcat(exr, b._is_num, val) - show(pretty_table(data, header = ["Expr", "Is Num", "Val"]; show_row_number = true)) + pretty_table(data, header = ["Expr", "Is Num", "Val"]; show_row_number = true) end \ No newline at end of file diff --git a/src/eago_optimizer/functions/nonlinear/composite_relax/forward_propagation.jl b/src/eago_optimizer/functions/nonlinear/composite_relax/forward_propagation.jl index 07ebf685..b1a0a1c7 100644 --- a/src/eago_optimizer/functions/nonlinear/composite_relax/forward_propagation.jl +++ b/src/eago_optimizer/functions/nonlinear/composite_relax/forward_propagation.jl @@ -15,7 +15,7 @@ xset_ynum(b, x, y) = !is_num(b, x) && is_num(b, y) xy_num(b, x, y) = is_num(b, x) && is_num(b, y) xyset(b, x, y) = !(is_num(b, x) || is_num(b, y)) -function varset(::Type{MC{N,T}}, i, x_cv, x_cc, l, u) where {V,N,T<:RelaxTag} +function varset(::Type{MC{N,T}}, i, x_cv, x_cc, l, u) where {N,T<:RelaxTag} v = seed_gradient(i, Val(N)) return MC{N,T}(x_cv, x_cc, Interval{Float64}(l, u), v, v, false) end diff --git a/src/eago_optimizer/functions/nonlinear/composite_relax/utilities.jl b/src/eago_optimizer/functions/nonlinear/composite_relax/utilities.jl index 66619ef3..c8787844 100644 --- a/src/eago_optimizer/functions/nonlinear/composite_relax/utilities.jl +++ b/src/eago_optimizer/functions/nonlinear/composite_relax/utilities.jl @@ -57,7 +57,7 @@ function expand_set(::Type{MC{N2,T}}, x::MC{N1,T}, fsparse::Vector{Int}, subspar return MC{N2,T}(x.cv, x.cc, x.Intv, cv_grad, cc_grad, x.cnst) end -function set_value_post(z::MC{N,T}, v::VariableValues{Float64}, s::Vector{Int}, ϵ::Float64) where {V,N,T<:RelaxTag} +function set_value_post(z::MC{N,T}, v::VariableValues{Float64}, s::Vector{Int}, ϵ::Float64) where {N,T<:RelaxTag} l = z.cv u = z.cc lower_refinement = true diff --git a/src/eago_optimizer/moi_wrapper.jl b/src/eago_optimizer/moi_wrapper.jl index 29d8aa8f..9190f4fd 100644 --- a/src/eago_optimizer/moi_wrapper.jl +++ b/src/eago_optimizer/moi_wrapper.jl @@ -176,7 +176,7 @@ MOI.get(m::Optimizer, ::MOI.DualStatus) = MOI.NO_SOLUTION MOI.get(m::Optimizer, ::MOI.ObjectiveBound) = m._objective_bound MOI.get(m::Optimizer, ::MOI.NumberOfVariables) = m._input_problem._variable_count MOI.get(m::Optimizer, ::MOI.SolverName) = "EAGO: Easy Advanced Global Optimization" -MOI.get(m::Optimizer, ::MOI.SolverVersion) = "0.7.1" +MOI.get(m::Optimizer, ::MOI.SolverVersion) = "0.8.1" MOI.get(m::Optimizer, ::MOI.TerminationStatus) = m._termination_status_code MOI.get(m::Optimizer, ::MOI.SolveTimeSec) = m._run_time MOI.get(m::Optimizer, ::MOI.NodeCount) = m._node_count diff --git a/src/eago_optimizer/optimize/nonconvex/display.jl b/src/eago_optimizer/optimize/nonconvex/display.jl index 4c5835f6..ec4cdf37 100644 --- a/src/eago_optimizer/optimize/nonconvex/display.jl +++ b/src/eago_optimizer/optimize/nonconvex/display.jl @@ -7,7 +7,7 @@ ############################################################################# # src/eago_optimizer/display.jl # Functions used to print information about solution routine to console. -# Printing is done with reference to the input problem is there is any +# Printing is done with reference to the input problem if there is any # ambiguity. ############################################################################# @@ -44,7 +44,7 @@ function print_solution!(m::GlobalOptimizer) println("LBD = $(MOI.get(m, MOI.ObjectiveBound()))") println("UBD = $(MOI.get(m, MOI.ObjectiveValue()))") end - println("Solution is :") + println("Solution is:") if m._feasible_solution_found for i = 1:m._input_problem._variable_count println(" X[$i] = $(m._continuous_solution[i])") diff --git a/src/eago_optimizer/optimize/nonconvex/relax.jl b/src/eago_optimizer/optimize/nonconvex/relax.jl index 64e34085..0f5e8fdd 100644 --- a/src/eago_optimizer/optimize/nonconvex/relax.jl +++ b/src/eago_optimizer/optimize/nonconvex/relax.jl @@ -73,7 +73,7 @@ Default routine for relaxing quadratic constraint `func < 0.0` on node `n`. Takes affine bounds of convex part at point `x0` and secant line bounds on concave parts. """ -function affine_relax_quadratic!(m::GlobalOptimizer, func::SQF, buffer::Dict{Int,Float64}, saf::SAF) where {R,S,Q<:ExtensionType} +function affine_relax_quadratic!(m::GlobalOptimizer, func::SQF, buffer::Dict{Int,Float64}, saf::SAF) quadratic_constant = func.constant diff --git a/src/eago_optimizer/optimize/nonconvex/stack_management.jl b/src/eago_optimizer/optimize/nonconvex/stack_management.jl index de682277..1c97daab 100644 --- a/src/eago_optimizer/optimize/nonconvex/stack_management.jl +++ b/src/eago_optimizer/optimize/nonconvex/stack_management.jl @@ -232,10 +232,43 @@ fathom!(m::GlobalOptimizer{R,S,Q}) where {R,S,Q<:ExtensionType} = fathom!(_ext(m """ $(TYPEDSIGNATURES) +Check the optimization problem for unbounded branching variables, which would interfere +with EAGO's branch-and-bound routine since there are no well-defined branching rules +for cases where the interval bounds contain `-Inf` or `Inf`. If any branching variables +are missing bounds, add the missing bound at +/- 1e10 and warn the user. +""" +function unbounded_check!(m::GlobalOptimizer) + if m._parameters.unbounded_check + unbounded_flag = false + wp = m._working_problem + epigraph_flag = _variable_num(FullVar(), m) != m._input_problem._variable_count + for i = 1:_variable_num(BranchVar(), m) - epigraph_flag #Not including epigraph reformulation variable + if !wp._variable_info[i].has_lower_bound + unbounded_flag = true + wp._variable_info[i] = VariableInfo(wp._variable_info[i], GT(-1e10)) + end + if !wp._variable_info[i].has_upper_bound + unbounded_flag = true + wp._variable_info[i] = VariableInfo(wp._variable_info[i], LT(1e10)) + end + end + unbounded_flag && @warn(""" + At least one branching variable is unbounded. This will interfere with EAGO's global + optimization routine and may cause unexpected results. Bounds have been automatically + generated at +/- 1e10 for all unbounded variables, but tighter user-defined bounds are + highly recommended. To disable this warning and the automatic generation of bounds, use + the option `unbounded_check = false`.""") + end +end + +""" +$(TYPEDSIGNATURES) + Prepare the stack for the branch-and-bound routine. By default, create an initial node with the variable bounds as box constraints and add it to the stack. """ function initialize_stack!(t::ExtensionType, m::GlobalOptimizer) + unbounded_check!(m) d = _working_variable_info.(m, m._branch_to_sol_map) push!(m._stack, NodeBB(lower_bound.(d), upper_bound.(d), is_integer.(d))) m._node_count = 1 diff --git a/src/eago_optimizer/parse.jl b/src/eago_optimizer/parse.jl index a0052765..1f1b2634 100644 --- a/src/eago_optimizer/parse.jl +++ b/src/eago_optimizer/parse.jl @@ -359,7 +359,10 @@ function label_branch_variables!(m::GlobalOptimizer) for i = 1:wp._variable_count if is_fixed(wp._variable_info[i]) m._branch_variables[i] = false - elseif m._branch_variables[i] || wp._variable_info[i].is_integer + elseif m._branch_variables[i] + push!(m._branch_to_sol_map, i) + elseif wp._variable_info[i].is_integer + m._branch_variables[i] = true push!(m._branch_to_sol_map, i) elseif i == wp._variable_count m._branch_variables[i] = false @@ -534,4 +537,4 @@ function parse_classify_problem!(m::GlobalOptimizer) end return -end \ No newline at end of file +end diff --git a/src/eago_optimizer/types/global_optimizer.jl b/src/eago_optimizer/types/global_optimizer.jl index a7c0369a..41ec0eff 100644 --- a/src/eago_optimizer/types/global_optimizer.jl +++ b/src/eago_optimizer/types/global_optimizer.jl @@ -222,7 +222,7 @@ Base.@kwdef mutable struct EAGOParameters cut_safe_b::Float64 = 1E9 "Solve upper problem for every node with depth less than `upper_bounding_depth`, - and otherwise solve upper problems with a probability of (1/2)^(depth-upper_bounding_depth) + and otherwise solve upper problems with a probability of `(1/2)^(depth-upper_bounding_depth)` (default = 8)" upper_bounding_depth::Int = 8 @@ -241,8 +241,10 @@ Base.@kwdef mutable struct EAGOParameters integer_rel_tol::Float64 = 1E-9 # Other forcing options - "Ignore EAGO's ability to parse problem types and force it to run global optimization" + "Ignore EAGO's ability to parse problem types and force it to run global optimization (default = false)" force_global_solve::Bool = false + "Check that all branching variables have finite bounds and set them to +/- 1E10 if not (default = true)" + unbounded_check::Bool = true end const EAGO_PARAMETERS = fieldnames(EAGOParameters)