├── docs ├── src │ ├── expression.md │ ├── hacking.md │ ├── algorithm.md │ ├── installation.md │ ├── functions.md │ ├── intro.md │ ├── choosingsolver.md │ └── parameters.md └── make.jl ├── REQUIRE ├── test ├── REQUIRE ├── solver.jl ├── runtests.jl └── algorithm.jl ├── benchmark ├── playground.jl ├── run_benchmark.jl ├── nlp1_amp.jl ├── nlp3_amp.jl ├── nlp1_btamp.jl ├── nlp1_pbtamp.jl ├── nlp3_btamp.jl └── nlp3_pbtamp.jl ├── deps └── fixipopt.jl ├── src ├── POD.jl ├── log.jl ├── solver.jl ├── presolve.jl ├── tmc.jl ├── bounds.jl ├── multi.jl ├── amp.jl ├── nlexpr.jl └── utility.jl ├── examples ├── specialopts.jl ├── linearlift.jl ├── div.jl ├── circle.jl ├── convex.jl ├── nlp.jl ├── castro2m2.jl ├── multi.jl ├── blend029.jl └── exprstest.jl ├── .travis.yml ├── LICENSE.md └── README.md /docs/src/expression.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.6 2 | JuMP 0.17.1 3 | MathProgBase 4 | Compat 5 | -------------------------------------------------------------------------------- /test/REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.5 2 | Ipopt 3 | JuMP 4 | Pajarito 5 | Gurobi 6 | Cbc 7 | AmplNLWriter 8 | CoinOptServices 9 | -------------------------------------------------------------------------------- /benchmark/playground.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase 2 | using Gurobi, Ipopt 3 | using POD 4 | 5 | include("../examples/nlp1.jl") 6 | include("../examples/nlp3.jl") 7 | -------------------------------------------------------------------------------- /docs/src/hacking.md: -------------------------------------------------------------------------------- 1 | # Hacking-Solver 2 | 3 | ```@meta 4 | CurrentModule = POD 5 | ``` 6 | 7 | This page give more detailed tutorial on how to hack this solver for different behaviors... 8 | -------------------------------------------------------------------------------- /benchmark/run_benchmark.jl: -------------------------------------------------------------------------------- 1 | # @time include("nlp1_amp.jl") 2 | # @time include("nlp1_btamp.jl") 3 | # @time include("nlp1_pbtamp.jl") 4 | # @time include("nlp3_amp.jl") 5 | # @time include("nlp3_btamp.jl") 6 | @time include("nlp3_pbtamp.jl") 7 | # 8 | -------------------------------------------------------------------------------- /docs/src/algorithm.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | ```@meta 4 | CurrentModule = POD 5 | ``` 6 | 7 | Consider the following [problem](https://link.springer.com/article/10.1007/s10898-012-0022-1) as an example. 8 | 9 | ```math 10 | \min_{x}\, &c^Tx\\ 11 | s.t. &a_i^Tx \text{ sense}_i \, b_i \forall\,\, i\\ 12 | &l \leq x \leq u\\ 13 | ``` 14 | -------------------------------------------------------------------------------- /benchmark/nlp1_amp.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase 2 | using Gurobi, Ipopt 3 | using POD 4 | 5 | include("../examples/nlp1.jl") 6 | benchmark_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | log_level=1) 9 | m = nlp1(solver=benchmark_solver) 10 | status = solve(m) 11 | println("Final POD Status is $status") 12 | -------------------------------------------------------------------------------- /deps/fixipopt.jl: -------------------------------------------------------------------------------- 1 | # Used for Windows 2 | # https://github.com/JuliaOpt/Ipopt.jl/issues/77 3 | using WinRPM 4 | pkgs = String[] 5 | for pkg in eachline(WinRPM.installedlist) 6 | name = match(Regex("$(WinRPM.OS_ARCH)-(.*)"), last(split(pkg))) 7 | if name !== nothing && !isempty(WinRPM.lookup(name[1])) 8 | push!(pkgs, name[1]) 9 | end 10 | end 11 | WinRPM.install(pkgs) 12 | -------------------------------------------------------------------------------- /benchmark/nlp3_amp.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase 2 | using Gurobi, Ipopt 3 | using POD 4 | 5 | include("../examples/nlp3.jl") 6 | benchmark_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | maxiter=3, log_level=1) 9 | 10 | m = nlp3(solver=benchmark_solver) 11 | status = solve(m) 12 | println("Final POD Status is $status") 13 | -------------------------------------------------------------------------------- /docs/src/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ```@meta 4 | CurrentModule = POD 5 | ``` 6 | 7 | Currently, AMP is a private repository that is used by LANL-ANSI group, the proposed publication is unknown given the upcoming changes in JuMP and MathProgBase that AMP needs to adapt to. To install AMP, 8 | 9 | `Pkg.clone("https://github.com/lanl-ansi/POD.git")` 10 | 11 | For developers, it is highly recommend that any further development on POD is conducted on a new branch or a forked repo. 12 | -------------------------------------------------------------------------------- /benchmark/nlp1_btamp.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase 2 | using Gurobi, Ipopt 3 | using POD 4 | 5 | include("../examples/nlp1.jl") 6 | benchmark_solver = PODSolver(nlp_local_solver=IpoptSolver(), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | log_level=1, 9 | presolve_bt_width_tol=1e-3, 10 | presolve_bt_output_tol=1e-1, 11 | presolve_bound_tightening=true, 12 | presolve_bound_tightening_algo=1) 13 | 14 | m = nlp1(solver=benchmark_solver) 15 | status = solve(m) 16 | println("Final POD Status is $status") 17 | -------------------------------------------------------------------------------- /benchmark/nlp1_pbtamp.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase 2 | using Gurobi, Ipopt 3 | using POD 4 | 5 | include("../examples/nlp1.jl") 6 | benchmark_solver = PODSolver(nlp_local_solver=IpoptSolver(), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | log_level=1, 9 | presolve_bt_width_tol=1e-3, 10 | presolve_bt_output_tol=1e-1, 11 | presolve_bound_tightening=true, 12 | presolve_bound_tightening_algo=2) 13 | 14 | m = nlp1(solver=benchmark_solver) 15 | status = solve(m) 16 | println("Final POD Status is $status") 17 | -------------------------------------------------------------------------------- /benchmark/nlp3_btamp.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase 2 | using Gurobi, Ipopt 3 | using POD 4 | 5 | include("../examples/nlp3.jl") 6 | benchmark_solver = PODSolver(nlp_local_solver=IpoptSolver(), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | log_level=1,maxiter=3, 9 | presolve_bt_width_tol=1e-3, 10 | presolve_bt_output_tol=1e-1, 11 | presolve_bound_tightening=true, 12 | presolve_bound_tightening_algo=1) 13 | 14 | m = nlp3(solver=benchmark_solver) 15 | status = solve(m) 16 | println("Final POD Status is $status") 17 | -------------------------------------------------------------------------------- /benchmark/nlp3_pbtamp.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase 2 | using Gurobi, Ipopt 3 | using POD 4 | 5 | include("../examples/nlp3.jl") 6 | benchmark_solver = PODSolver(nlp_local_solver=IpoptSolver(), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | log_level=100, 9 | presolve_bt_width_tol=1e-2, 10 | presolve_bt_output_tol=1e-1, 11 | presolve_bound_tightening=true, 12 | presolve_mip_relaxation=true, 13 | presolve_bound_tightening_algo=2) 14 | 15 | m = nlp3(solver=benchmark_solver) 16 | status = solve(m) 17 | println("Final POD Status is $status") 18 | -------------------------------------------------------------------------------- /src/POD.jl: -------------------------------------------------------------------------------- 1 | module POD 2 | 3 | using JuMP 4 | using MathProgBase 5 | using Compat 6 | 7 | # Engine for High-level Algorithmic Control and User-interface 8 | include("algorithm.jl") 9 | include("solver.jl") 10 | 11 | # Engine for expression handling 12 | include("nlexpr.jl") 13 | include("operators.jl") 14 | 15 | # Main Algorithmic Process 16 | include("presolve.jl") 17 | include("amp.jl") 18 | 19 | # Convexification method 20 | include("tmc.jl") 21 | include("multi.jl") 22 | 23 | # Model Manipulation and utilities 24 | include("bounds.jl") 25 | include("utility.jl") 26 | 27 | # Othes 28 | include("log.jl") 29 | 30 | end # module 31 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, POD 2 | 3 | makedocs( 4 | format = :html, 5 | sitename = "POD", 6 | pages = [ 7 | "Introduction" => "intro.md", 8 | "How to Use" => "installation.md", 9 | "Choosing Sub-Solvers" => "choosingsolver.md", 10 | "Algorithm" => "algorithm.md", 11 | "Expression Guideline" => "expression.md", 12 | "Parameters" => "parameters.md", 13 | "Methods" => "functions.md", 14 | "Hacking POD" => "hacking.md" 15 | ] 16 | ) 17 | 18 | 19 | deploydocs( 20 | repo = "github.com/lanl-ansi/POD.git", 21 | target = "build", 22 | branch = "monomial", 23 | osname = "linux", 24 | julia = "0.6", 25 | deps = nothing, 26 | make = nothing 27 | ) 28 | -------------------------------------------------------------------------------- /test/solver.jl: -------------------------------------------------------------------------------- 1 | @testset "Solver Function Tests" begin 2 | 3 | using POD 4 | 5 | @testset "PODNonlinearModel loading tests" begin 6 | # Random Model 1 7 | m = operator_c() 8 | setsolver(m, PODSolver(nlp_local_solver=IpoptSolver(), 9 | mip_solver=CbcSolver(OutputFlag=0),log_level=0)) 10 | status = JuMP.build(m) 11 | @test isa(m.internalModel, POD.PODNonlinearModel) 12 | 13 | # Expression Model 1 14 | m = exprstest() 15 | setsolver(m, PODSolver(nlp_local_solver=IpoptSolver(), 16 | mip_solver=CbcSolver(OutputFlag=0),log_level=0)) 17 | status = JuMP.build(m) 18 | @test isa(m.internalModel, POD.PODNonlinearModel) 19 | 20 | end 21 | 22 | @testset "Basic global solve check" begin 23 | # Dummy test 24 | @test 3 == 3 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /docs/src/functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | ```@meta 4 | CurrentModule = POD 5 | ``` 6 | 7 | ## High-level Algorithmic Operations 8 | These are the high-level algorithmic methods: 9 | ```@docs 10 | presolve 11 | global_solve 12 | local_solve 13 | bounding_solve 14 | ``` 15 | 16 | ## Adapative Partitioning Methods 17 | ```@docs 18 | create_bounding_mip 19 | pick_vars_discretization 20 | fix_domains 21 | min_vertex_cover 22 | max_cover 23 | ``` 24 | 25 | ## Presolve Methods 26 | ```@docs 27 | bound_tightening 28 | minmax_bound_tightening 29 | create_bound_tightening_model 30 | solve_bound_tightening_model 31 | resolve_lifted_var_bounds 32 | ``` 33 | 34 | ## Utility Methods 35 | ```@docs 36 | update_var_bounds 37 | discretization_to_bounds 38 | initialize_discretization 39 | to_discretization 40 | flatten_discretization 41 | add_adpative_partition 42 | update_mip_time_limit 43 | fetch_timeleft_symbol 44 | ``` 45 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using Base.Test 2 | using JuMP, MathProgBase 3 | using Pajarito, Ipopt, Cbc, Gurobi, AmplNLWriter 4 | using POD 5 | 6 | # Expression Testing Instances 7 | include("$(Pkg.dir())/POD/examples/exprstest.jl") 8 | 9 | # NLP Testing Instances 10 | include("$(Pkg.dir())/POD/examples/nlp.jl") 11 | include("$(Pkg.dir())/POD/examples/castro2m2.jl") 12 | 13 | # Multilinear Testing Instances 14 | include("$(Pkg.dir())/POD/examples/blend029.jl") 15 | include("$(Pkg.dir())/POD/examples/multi.jl") 16 | 17 | # Special Operator 18 | include("$(Pkg.dir())/POD/examples/div.jl") 19 | include("$(Pkg.dir())/POD/examples/circle.jl") 20 | include("$(Pkg.dir())/POD/examples/convex.jl") 21 | include("$(Pkg.dir())/POD/examples/linearlift.jl") 22 | 23 | # Performe Tests 24 | include("$(Pkg.dir())/POD/test/solver.jl") 25 | include("$(Pkg.dir())/POD/test/expression.jl") 26 | include("$(Pkg.dir())/POD/test/algorithm.jl") 27 | -------------------------------------------------------------------------------- /examples/specialopts.jl: -------------------------------------------------------------------------------- 1 | using POD, JuMP, Ipopt, Cbc, MathProgBase 2 | 3 | function specialopts(;verbose=false, solver=nothing) 4 | 5 | if solver == nothing 6 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 7 | mip_solver=CbcSolver(), 8 | log_level=0)) 9 | else 10 | m = Model(solver=solver) 11 | end 12 | 13 | @variable(m, x[i=1:6]) # At some point if an initial value is given, keep them 14 | 15 | @NLconstraint(m, sin(x[1]) + cos(x[2]) >= 1) 16 | @NLconstraint(m, sin(x[1]) * x[2] >= 1) 17 | @NLconstraint(m, sin(x[1]) * cos(x[2]) >= 1) 18 | # 19 | @NLconstraint(m, sin(x[1]*x[2]) >= 0.5) 20 | @NLconstraint(m, cos(x[1]+x[2]) >= 0.5) 21 | @NLconstraint(m, cos(x[1]/2) >= 0.5) 22 | @NLconstraint(m, cos(x[1]*x[2]/5) >= 0.5) 23 | @NLconstraint(m, sin(x[1]/5+x[2]/5) >= 0.5) 24 | # 25 | 26 | 27 | if verbose 28 | print(m) 29 | end 30 | 31 | return m 32 | end 33 | -------------------------------------------------------------------------------- /examples/linearlift.jl: -------------------------------------------------------------------------------- 1 | # Contains a basic model with various expressions for testing 2 | using POD, JuMP, Ipopt, Cbc, MathProgBase 3 | 4 | function basic_linear_lift(;verbose=false, solver=nothing) 5 | 6 | if solver == nothing 7 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(), 8 | mip_solver=CbcSolver(), 9 | log_level=0)) 10 | else 11 | m = Model(solver=solver) 12 | end 13 | 14 | @variable(m, x[i=1:3]>=1) # At some point if an initial value is given, keep them 15 | 16 | @constraint(m, (x[1]-x[2])*(3*x[2]-x[3]) >= 111) 17 | @NLconstraint(m, (x[1]-x[2])*(3*x[2]-x[3]) >= 111) 18 | @NLconstraint(m, (x[1]-x[2])^2 >= 100) 19 | @NLconstraint(m, (x[1]-x[2])*(3*x[2]-x[3])*(x[1]+x[3]) >= 111) 20 | @NLconstraint(m, (3+x[1]+x[2]+x[3])*(x[1]+x[2])^2 >= 111) 21 | 22 | # Next level goal 23 | # @NLconstraint(m, (9-x[1]-x[2]-x[3])^2 >= 111) 24 | 25 | @objective(m, Min, x[1]+x[2]+x[3]) 26 | 27 | verbose && print(m) 28 | 29 | return m 30 | end 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | julia: 3 | - 0.5 4 | # - 0.4 5 | # - release 6 | # - nightly 7 | 8 | # this will save build time, once it is working 9 | cache: 10 | directories: 11 | - /home/travis/.julia 12 | 13 | before_script: 14 | # install dependencies 15 | # ideally these should be installed from a REQUIRE file in the root of the repository 16 | - julia -e 'Pkg.add("JuMP")' 17 | # - julia -e 'Pkg.add("Coverage")' # Not until coverage is set to be tested 18 | # much easiers to test with an open source solver 19 | - julia -e 'Pkg.add("Cbc")' 20 | - julia -e 'Pkg.add("Ipopt")' 21 | 22 | script: # core code tests 23 | - julia --code-coverage=user --inline=no -e 'include("test/runtests.jl")' 24 | 25 | after_success: # post-processing, if the script passes with no errors 26 | - echo $TRAVIS_JULIA_VERSION # just useful to know what version of Julia was used 27 | # submits the code coverage report 28 | # - julia -e 'using Coverage; cd("src"); Codecov.submit(process_folder("."), codecov_url="https://cov.lanlytics.com", token="aad3f5c6-a4c4-4216-ac42-fe9250f5f226");' 29 | # - julia -e 'Pkg.rm("Cbc")' 30 | # - julia -e 'Pkg.rm("Ipopt")' 31 | # - julia -e 'Pkg.rm("JuMP")' 32 | -------------------------------------------------------------------------------- /docs/src/intro.md: -------------------------------------------------------------------------------- 1 | # POD 2 | 3 | [POD.jl](https://github.com/lanl-ansi/POD) is a two-stage approach to strengthen piecewise convex relaxations for mixed-integer nonlinear programs (MINLP) with multi-linear terms. In the first stage, we exploit Constraint Programing techniques to contract the variable bounds. We apply feasibility-based bound contraction methods iteratively until a fixed point with respect to the bounds are achieved. In the second stage, we partition the variables domains using an adaptive multivariate partitioning scheme. Instead of equally partitioning the domains of variables appearing in multi-linear terms (predominantly common in the literature), we construct sparser partitions yet tighter relaxations by iteratively partitioning the variable domains in regions of interest (a parametrized partition around the current solution of lower-bounding MILP). This approach decouples the number of partitions from the size of the variable domains, leads to a significant reduction in computation time, and limits the number of binary variables that are introduced by the partitioning. We further apply polyhedral cutting plane methods to handle convex relaxations of higher-order monomial terms. 4 | 5 | To use POD, external sub-solvers must be installed. See [Choosing Sub-Solvers](@ref) for more information. 6 | -------------------------------------------------------------------------------- /examples/div.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase, Gurobi, Ipopt, POD 2 | 3 | function div(;verbose=false,solver=nothing) 4 | 5 | if solver == nothing 6 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | presolve_bound_tightening=true, 9 | presolve_bound_tightening_algo=2, 10 | presolve_bt_output_tol=1e-1, 11 | log_level=1)) 12 | else 13 | m = Model(solver=solver) 14 | end 15 | 16 | @variable(m, 1<=x[1:2]<=10) 17 | 18 | @NLobjective(m, Min, 6*x[1]^2 + 4*x[2]^2 - 2.5/5*x[1]*x[2]) 19 | 20 | @constraint(m, x[1]*(-1*3/5*5) >= 0) 21 | @constraint(m, x[2]*5/20 >= 0) 22 | @constraint(m, x[2]*(5*15/15) >= 0) 23 | @constraint(m, x[2]*(120*2/-2) >= 0) 24 | @constraint(m, 1*2*3*4*5*6*x[2]/0.01>=0) 25 | @constraint(m, x[1]*1*2*3*4*5*6/0.01>=0) 26 | @NLconstraint(m, (3/5)*x[1]*(60/60)*x[2] >= 8) 27 | @NLconstraint(m, (1*2*3*4/5/6*7)*x[2]-x[1]*1*2*3*4*5*6*x[2]/0.01>=0) 28 | @NLconstraint(m, (1*2*3*4/5/6*7)*x[2]-0.5(x[1]*1*2*3*4*5*6*x[2]/0.01)>=0) 29 | @NLconstraint(m, (1*2*3*4/5/6*7)*x[2]-0.5(x[1]*2*3*x[2]/0.01+5*7/10*x[2])>=0) 30 | @NLconstraint(m, (1*2*3*4/5/6*7)*x[2]-0.5*(x[1]-x[2]x[1])>=0) 31 | 32 | if verbose 33 | print(m) 34 | end 35 | 36 | return m 37 | end 38 | -------------------------------------------------------------------------------- /examples/circle.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase, Gurobi, Ipopt, POD 2 | 3 | function circle(;verbose=false, solver=nothing) 4 | 5 | if solver == nothing 6 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | discretization_abs_width_tol=1e-2, 9 | discretization_ratio=4, 10 | presolve_bound_tightening=false, 11 | presolve_bound_tightening_algo=1, 12 | presolve_bt_output_tol=1e-1, 13 | log_level=1)) 14 | else 15 | m = Model(solver=solver) 16 | end 17 | 18 | @variable(m, 0<=x[1:2]<=2) 19 | @NLconstraint(m, x[1]^2 + x[2]^2 >= 2) 20 | @objective(m, Min, x[1]+x[2]) 21 | 22 | if verbose 23 | print(m) 24 | end 25 | 26 | return m 27 | end 28 | 29 | function circleN(;verbose=false, solver=nothing, convhull=false, N=2, uniform=-1, delta=16) 30 | 31 | if solver == nothing 32 | if uniform > 0 33 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 34 | mip_solver=GurobiSolver(OutputFlag=0), 35 | monomial_convexhull=convhull, 36 | discretization_abs_width_tol=1e-2, 37 | maxiter=1, 38 | discretization_add_partition_method="uniform", 39 | discretization_uniform_rate=uniform, 40 | presolve_bound_tightening=false, 41 | log_level=1)) 42 | else 43 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 44 | mip_solver=GurobiSolver(OutputFlag=0), 45 | monomial_convexhull=convhull, 46 | discretization_abs_width_tol=1e-2, 47 | presolve_bound_tightening=false, 48 | log_level=100)) 49 | end 50 | else 51 | m = Model(solver=solver) 52 | end 53 | 54 | @variable(m, 0<=x[1:N]<=N) 55 | @NLconstraint(m, sum(x[i]^2 for i in 1:N) >= N) 56 | @objective(m, Min, sum(x)) 57 | 58 | if verbose 59 | print(m) 60 | end 61 | 62 | return m 63 | end 64 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Los Alamos National Security, LLC 2 | All rights reserved. 3 | Copyright 2017. Los Alamos National Security, LLC. This software was produced under U.S. Government contract DE-AC52-06NA25396 for Los Alamos National Laboratory (LANL), which is operated by Los Alamos National Security, LLC for the U.S. Department of Energy. The U.S. Government has rights to use, reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR LOS ALAMOS NATIONAL SECURITY, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is modified to produce derivative works, such modified software should be clearly marked, so as not to confuse it with the version available from LANL. 4 | 5 | Additionally, redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 3. Neither the name of Los Alamos National Security, LLC, Los Alamos National Laboratory, LANL, the U.S. Government, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY LOS ALAMOS NATIONAL SECURITY, LLC AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LOS ALAMOS NATIONAL SECURITY, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | 12 | -------------------------------------------------------------------------------- /docs/src/choosingsolver.md: -------------------------------------------------------------------------------- 1 | # Choosing Sub-Solvers 2 | 3 | ```@meta 4 | CurrentModule = POD 5 | ``` 6 | 7 | The design of the AMP solver requires a variety of programming problems to be solved underneath the surface. For algorithmic performance, it's recommend that dedicated solvers to be used for these operations. The design of AMP takes advantage of MathProgBase to allow a majority of optimization softwares to be utilized easily with simple development. Currently, the following sub-solvers with Julia Interface is supported by AMP: 8 | 9 | | Solver | Julia Package | 10 | |--------------------------------------------------------------------------------|--------------------------------------------------------------| 11 | | [CPLEX](http://www-01.ibm.com/software/commerce/optimization/cplex-optimizer/) | [CPLEX.jl](https://github.com/JuliaOpt/CPLEX.jl) | 12 | | [Cbc](https://projects.coin-or.org/Cbc) | [Cbc.jl](https://github.com/JuliaOpt/Clp.jl) | 13 | | [Gurobi](http://gurobi.com/) | [Gurobi.jl](https://github.com/JuliaOpt/Gurobi.jl) | 14 | | [Ipopt](https://projects.coin-or.org/Ipopt) | [Ipopt.jl](https://github.com/JuliaOpt/Ipopt.jl) | 15 | | [Bonmin](https://projects.coin-or.org/Bonmin) | [Bonmin.jl](https://github.com/JackDunnNZ/AmplNLWriter.jl) | 16 | | [Artelys KNITRO](http://artelys.com/en/optimization-tools/knitro) | [KNITRO.jl](https://github.com/JuliaOpt/KNITRO.jl) | 17 | 18 | As the development of AMP contineous, supports fo [Mosek](http://www.mosek.com/), [GLPK](http://www.gnu.org/software/glpk/), [NLopt](http://ab-initio.mit.edu/wiki/index.php/NLopt), [Xpress](http://www.fico.com/en/products/fico-xpress-optimization-suite) is already scheduled for the roadmap. 19 | 20 | To use different sub-solvers, here is an example: 21 | 22 | ```julia 23 | using JuMP 24 | using POD 25 | using Gurobi, Ipopt 26 | m = Model() 27 | # Here goes the building of your model... 28 | setsolver(m, PODSolver(nlp_local_solver=IpoptSolver(print_level=0), mip_solver=GurobiSolver(OutputFlag=0))) 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/src/parameters.md: -------------------------------------------------------------------------------- 1 | # Parameters 2 | 3 | ```@meta 4 | CurrentModule = POD 5 | ``` 6 | 7 | ## General 8 | 9 | There are some general paremters that controls the behavior of the AMP: 10 | 11 | * `log_level(default=0)`: verbosity of the algorihtm, set to `1` for turning on logging, `100` for detailed debugging mode 12 | 13 | * `timeout(default=Inf)`: total time regulated for the solver in seconds 14 | 15 | * `maxiter(default=999)`: total iteration allowed in the [`global_solve`](@ref) 16 | 17 | * `rel_gap(default=1e-4)`: relative gap considered for optimality during [`global_solve`](@ref). Bounds are evaluated using ``\frac{UB-LB}{UB} 100 \times \%`` 18 | 19 | * `tol(default=1e-6)`: numerical tol used furing the process of [`global_solve`](@ref) 20 | 21 | ## Discretization-based parameters 22 | 23 | * `discretization_ratio(default=4)`: used during [`add_adpative_partition`](@ref) for measuring the radius of new partitioning relative to the active domain 24 | 25 | * `discretization_var_pick_algo(default=0)`: controls algorithm/methods used for selecting variables for discretization, `0` is for max-cover, `1` is for minimum-vertex-cover. This parameter allows functional inputs. 26 | 27 | * `discretization_add_partition_method`: allows functional input on how new partitions should be constructed. This paremeter is not fully stable. 28 | 29 | ## Presolve Parameters 30 | 31 | * `presolve_track_time(default=false)`: consier presolve time as the total time or not 32 | 33 | * `presolve_bound_tightening(default=false)`: perform built-in bound tightening presolve procedure 34 | 35 | * `presolve_maxiter(default=9999)`: maximum iteration allowed using presolve process 36 | 37 | * `presolve_bt_width_tol(default=1e-3)`: independent numerical tol used in presolve for bound tightening procedure. Note that this procedure is more sensitive to the tol in here. Small tol is more likely to results in strange presolve behvaior. 38 | 39 | * `presolve_bound_tightening_algo(default=1)`: method used to do built-in bound tightening, choose `1` for regular bounding tightening, `2` for Tighten McCormick bound tightening. 40 | 41 | * `presolve_mip_relaxation(default=false)`: whether to relax the bounding tightening MILP solved or not 42 | 43 | * `presolve_mip_timelimit(default=Inf)`: time limit used for invidiual MILP solved during bound tightening presolve 44 | 45 | More parameter descriptions to come... 46 | -------------------------------------------------------------------------------- /examples/convex.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase, Gurobi, Ipopt, POD 2 | 3 | function convex_test(;verbose=false, solver=nothing, recognize=true) 4 | 5 | if solver == nothing 6 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 7 | mip_solver=GurobiSolver(OutputFlag=1), 8 | discretization_ratio=4, 9 | recognize_convex=recognize, 10 | presolve_bound_tightening=false, 11 | log_level=200)) 12 | else 13 | m = Model(solver=solver) 14 | end 15 | 16 | @variable(m, 0<=x[1:5]<=2) 17 | 18 | @constraint(m, 3*x[1]*x[1] + 4*x[2]*x[2] <= 25) # true 19 | @constraint(m, 3*x[1]*x[1] - 25 + 4*x[2]*x[2] <= 0) # true 20 | @constraint(m, 3(x[1]x[1]) + 4*x[2]*x[2] <= -5) # false 21 | @constraint(m, 3(x[1]x[1]) + 4*x[2]^2 <= 10) # true 22 | @constraint(m, 3x[1]^2 + 4x[2]^2 + 6x[3]^2 <= 10) # true 23 | 24 | @NLconstraint(m, 3x[1]^0.5 + 4x[2]^0.5 + 5x[5]^0.5 <= 100) # true | type-C 25 | @NLconstraint(m, -3x[1]^0.5 -4x[2]^0.5 >= -100) # true | type-C 26 | 27 | @NLconstraint(m, 3*x[1]*x[1] + 4*x[2]*x[2] <= 25) # true 28 | 29 | @NLconstraint(m, (3*x[1]*x[1] + 4*x[2]*x[2]) <= 25) # true 30 | @NLconstraint(m, 3*x[1]*x[1] + 4*x[2]*x[2] - 25 <= 0) # true 31 | @NLconstraint(m, -3*x[1]*x[1] -4*x[2]*x[2] >= -25) # true 32 | @NLconstraint(m, 3*x[1]*x[1] + 5x[2]*x[2] <= 25) # true 33 | 34 | @NLconstraint(m, 4*x[1]^2 + 5x[2]^2 <= 25) # Pass 35 | @NLconstraint(m, 3*x[1]*x[1] - 25 + 4*x[2]*x[2] <= 0) # false (unsupported when with @NLconstraint) 36 | @NLconstraint(m, 3*x[1]*x[1] + 4*x[2]*x[1] <= 25) # false 37 | @NLconstraint(m, 3*x[1]*x[1] + 16*x[2]^2 <= 40) # true 38 | @NLconstraint(m, 3*x[1]^2 + 16*x[2]^2 + 17 <= 16) # false 39 | 40 | @NLconstraint(m, 3*x[1]^3 + 16*x[2]^2 <= 20 - 20) # false 41 | @NLconstraint(m, 3*x[1]*x[1] + 4*x[2]*x[2] + 5*x[3]*x[3] + 6x[4]x[4] <= 15) # true 42 | @NLconstraint(m, 3x[1]x[1] + 4x[2]x[2] + 5x[3]^2 <= -15) # false 43 | @NLconstraint(m, 3x[1]^2 + 4x[2]^2 >= 15) # false 44 | @NLconstraint(m, - 3x[1]^2 - 4x[3]^2 >= -15) # true 45 | 46 | @NLconstraint(m, 3x[1]^4 + 4x[2]^4 <= 200) # true 47 | @NLconstraint(m, 3x[1]^4 + 4x[2]x[2]x[2]x[2] - 200 <= 0) # true 48 | @NLconstraint(m, 3x[1]^4 + 4x[2]^2*x[2]*x[2] <= 200) # true 49 | @NLconstraint(m, 3x[1]^4 + 4x[2]^3 <= 200) # false 50 | @NLconstraint(m, 3x[1]^8 + 16*25*x[2]^8 - 30x[3]^8 <= 50) # false 51 | 52 | 53 | @objective(m, Max, x[1]^2+x[3]^2) 54 | 55 | if verbose 56 | print(m) 57 | end 58 | 59 | return m 60 | end 61 | 62 | function convex(;verbose=false, solver=nothing, recognize=true) 63 | 64 | return 65 | end 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POD, a novel MINLP solver that is awesome 2 | 3 | Polyhedral Outer-Approximation and dynamic Discretization POD is based on a novel global algorithm that uses an adaptive convexification scheme and constraints programming methods to solve Mixed-Integer Non-Linear Programming problems (MINLPs) efficiently. MINLPs are famously known as the "hard" programming problems that exist in many applications (see this [Library](http://www.gamsworld.org/minlp/minlplib2/html/) for problem instances). POD is also a good fit for subsets of the MINLP family, e.g., Mixed-Integer Quadradic Convex Programming (MIQCP), Non-Linear Programming (NLP), etc. 4 | 5 | Unlike many other state-of-the-art MINLP solvers, POD is entirely built upon [JuMP](https://github.com/JuliaOpt/JuMP.jl) and [MathProgBase](https://github.com/JuliaOpt/MathProgBase.jl) Interface in Julia, which provides incredible flexibility for usage and further development. 6 | 7 | POD solves MINLP by: 8 | 9 | * analyzing the problem expressions (objective & constraints) to obtain a convexifyable formulation 10 | 11 | * performing bound tightening to contract variables' feasible domains 12 | 13 | * performing adaptive partitioning-based convexification to improve problem bounds for global convergence 14 | 15 | 16 | # Installation 17 | 18 | Currently, POD is a private repository that is used by the LANL-ANSI group, the proposed publication is unknown given the upcoming changes in JuMP and MathProgBase that POD needs to adapt to. To install POD, 19 | 20 | `Pkg.clone("https://github.com/lanl-ansi/POD.git")` 21 | 22 | For developers, it is highly recommended that any further development on POD is conducted on a new branch or a forked repo. 23 | 24 | # Usage 25 | 26 | The design of the POD solver requires a variety of programming problems to be solved under the hood. For algorithmic performance, it is recommended that dedicated solvers be used. The design of POD takes advantage of MathProgBase to allow a majority of optimization softwares to be utilized easily with simple development. Currently, the following solvers with Julia Interface are supported by POD: 27 | 28 | | Solver | Julia Package | 29 | |--------------------------------------------------------------------------------|--------------------------------------------------------------| 30 | | [CPLEX](http://www-01.ibm.com/software/commerce/optimization/cplex-optimizer/) | [CPLEX.jl](https://github.com/JuliaOpt/CPLEX.jl) | 31 | | [Cbc](https://projects.coin-or.org/Cbc) | [Cbc.jl](https://github.com/JuliaOpt/Clp.jl) | 32 | | [Gurobi](http://gurobi.com/) | [Gurobi.jl](https://github.com/JuliaOpt/Gurobi.jl) | 33 | | [Ipopt](https://projects.coin-or.org/Ipopt) | [Ipopt.jl](https://github.com/JuliaOpt/Ipopt.jl) | 34 | | [Bonmin](https://projects.coin-or.org/Bonmin) | [Bonmin.jl](https://github.com/JackDunnNZ/PODlNLWriter.jl) | 35 | | [Artelys KNITRO](http://artelys.com/en/optimization-tools/knitro) | [KNITRO.jl](https://github.com/JuliaOpt/KNITRO.jl) | 36 | 37 | As the development of POD continues, supports fo [Mosek](http://www.mosek.com/), [Pajarito](https://github.com/JuliaOpt/Pajarito.jl), [GLPK](http://www.gnu.org/software/glpk/), [NLopt](http://ab-initio.mit.edu/wiki/index.php/NLopt), [Xpress](http://www.fico.com/en/products/fico-xpress-optimization-suite) are already scheduled on the roadmap. 38 | 39 | # Citation 40 | 41 | If you find POD useful in your work, we kindly request your citation. 42 | -------------------------------------------------------------------------------- /examples/nlp.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase, Gurobi, Ipopt, POD 2 | 3 | function nlp1(;verbose=false,solver=nothing, convhull=false, presolve=0) 4 | 5 | if solver == nothing 6 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 7 | mip_solver=GurobiSolver(OutputFlag=0), 8 | bilinear_convexhull=convhull, 9 | monomial_convexhull=convhull, 10 | presolve_bound_tightening=(presolve>0), 11 | presolve_bound_tightening_algo=presolve, 12 | presolve_bt_output_tol=1e-1, 13 | log_level=100)) 14 | else 15 | m = Model(solver=solver) 16 | end 17 | 18 | @variable(m, 1<=x[1:2]<=10) 19 | 20 | @NLconstraint(m, x[1]*x[2] >= 8) 21 | @NLobjective(m, Min, 6*x[1]^2 + 4*x[2]^2 - 2.5*x[1]*x[2]) 22 | 23 | if verbose 24 | print(m) 25 | end 26 | return m 27 | end 28 | 29 | function nlp2(;verbose=false,solver=nothing, convhull=false, presolve=0) 30 | 31 | if solver == nothing 32 | m = Model(solver=PODSolver(colorful_pod="warmer", 33 | nlp_local_solver=IpoptSolver(print_level=0), 34 | mip_solver=GurobiSolver(OutputFlag=0), 35 | bilinear_convexhull=convhull, 36 | monomial_convexhull=convhull, 37 | presolve_bound_tightening=(presolve>0), 38 | presolve_bound_tightening_algo=presolve, 39 | presolve_bt_output_tol=1e-1, 40 | log_level=100)) 41 | else 42 | m = Model(solver=solver) 43 | end 44 | 45 | @variable(m, -500<=x[1:2]<=500) 46 | 47 | @NLobjective(m, Min, sum((x[i]^2 - i)^2 for i in 1:2)) 48 | 49 | if verbose 50 | print(m) 51 | end 52 | 53 | return m 54 | end 55 | 56 | function max_cover_var_picker(m::POD.PODNonlinearModel) 57 | nodes = Set() 58 | for pair in keys(m.nonlinear_terms) 59 | for i in pair 60 | @assert isa(i.args[2], Int) 61 | push!(nodes, i.args[2]) 62 | end 63 | end 64 | nodes = collect(nodes) 65 | m.num_var_discretization_mip = length(nodes) 66 | m.var_discretization_mip = nodes 67 | return 68 | end 69 | 70 | 71 | function nlp3(;verbose=false, solver=nothing, convhull=true, sos2=true, sos2_alter=false, presolve=0, delta=16) 72 | 73 | if solver == nothing 74 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 75 | mip_solver=GurobiSolver(OutputFlag=0), 76 | log_level=1, 77 | rel_gap=0.0001, 78 | bilinear_convexhull=convhull, 79 | convhull_formulation_sos2=sos2, 80 | convhull_formulation_sos2aux=sos2_alter, 81 | discretization_ratio=delta, 82 | presolve_bt_width_tol=1, 83 | presolve_bt_output_tol=1e-1, 84 | presolve_bound_tightening=(presolve>0), 85 | presolve_bound_tightening_algo=1, 86 | discretization_var_pick_algo=0)) 87 | else 88 | m = Model(solver=solver) 89 | end 90 | 91 | @variable(m, x[1:8]) 92 | 93 | setlowerbound(x[1], 100) 94 | setlowerbound(x[2], 1000) 95 | setlowerbound(x[3], 1000) 96 | setlowerbound(x[4], 10) 97 | setlowerbound(x[5], 10) 98 | setlowerbound(x[6], 10) 99 | setlowerbound(x[7], 10) 100 | setlowerbound(x[8], 10) 101 | 102 | setupperbound(x[1], 10000) 103 | setupperbound(x[2], 10000) 104 | setupperbound(x[3], 10000) 105 | setupperbound(x[4], 1000) 106 | setupperbound(x[5], 1000) 107 | setupperbound(x[6], 1000) 108 | setupperbound(x[7], 1000) 109 | setupperbound(x[8], 1000) 110 | 111 | @constraint(m, 0.0025*(x[4] + x[6]) <= 1) 112 | @constraint(m, 0.0025*(x[5] - x[4] + x[7]) <= 1) 113 | @constraint(m, 0.01(x[8]-x[5]) <= 1) 114 | @NLconstraint(m, 100*x[1] - x[1]*x[6] + 833.33252*x[4] <= 83333.333) 115 | @NLconstraint(m, x[2]*x[4] - x[2]*x[7] - 1250*x[4] + 1250*x[5] <= 0) 116 | @NLconstraint(m, x[3]*x[5] - x[3]*x[8] - 2500*x[5] + 1250000 <= 0) 117 | 118 | @objective(m, Min, x[1]+x[2]+x[3]) 119 | 120 | if verbose 121 | print(m) 122 | end 123 | 124 | return m 125 | end 126 | -------------------------------------------------------------------------------- /examples/castro2m2.jl: -------------------------------------------------------------------------------- 1 | function castro2m2(;verbose=false, solver=nothing, convhull=true, sos2=true, sos2_alter=false, presolve=0, delta=16) 2 | 3 | if solver == nothing 4 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 5 | mip_solver=GurobiSolver(OutputFlag=0), 6 | log_level=100, 7 | rel_gap=0.0001, 8 | bilinear_convexhull=convhull, 9 | convhull_formulation_sos2=sos2, 10 | convhull_formulation_sos2aux=sos2_alter, 11 | discretization_ratio=delta, 12 | presolve_bt_width_tol=1e-3, 13 | presolve_bt_output_tol=1e-1, 14 | presolve_bound_tightening=(presolve>0), 15 | presolve_bound_tightening_algo=1, 16 | discretization_var_pick_algo=0)) 17 | else 18 | m = Model(solver=solver) 19 | end 20 | 21 | @variable(m, x[1:42]) 22 | 23 | setlowerbound(x[36], 0.0) 24 | setlowerbound(x[4], 0.0) 25 | setlowerbound(x[16], 0.0) 26 | setlowerbound(x[6], 0.0) 27 | setlowerbound(x[27], 0.0) 28 | setlowerbound(x[14], 0.0) 29 | setlowerbound(x[32], 0.0) 30 | setlowerbound(x[17], 0.0) 31 | setlowerbound(x[3], 0.0) 32 | setlowerbound(x[25], 0.0) 33 | setlowerbound(x[38], 0.0) 34 | setlowerbound(x[30], 0.0) 35 | setlowerbound(x[26], 0.0) 36 | setlowerbound(x[23], 0.0) 37 | setlowerbound(x[34], 0.0) 38 | setlowerbound(x[11], 0.0) 39 | setlowerbound(x[29], 0.0) 40 | setlowerbound(x[22], 0.0) 41 | setlowerbound(x[12], 0.0) 42 | setlowerbound(x[5], 0.0) 43 | setlowerbound(x[19], 0.0) 44 | setlowerbound(x[37], 0.0) 45 | setlowerbound(x[40], 0.0) 46 | setlowerbound(x[2], 0.0) 47 | setlowerbound(x[20], 0.0) 48 | setlowerbound(x[24], 0.0) 49 | setlowerbound(x[41], 0.0) 50 | setlowerbound(x[39], 0.0) 51 | setlowerbound(x[31], 0.0) 52 | setlowerbound(x[18], 0.0) 53 | setlowerbound(x[9], 0.0) 54 | setlowerbound(x[15], 0.0) 55 | setlowerbound(x[1], 0.0) 56 | setlowerbound(x[7], 0.0) 57 | setlowerbound(x[8], 0.0) 58 | setlowerbound(x[13], 0.0) 59 | setlowerbound(x[33], 0.0) 60 | setlowerbound(x[21], 0.0) 61 | setlowerbound(x[28], 0.0) 62 | setlowerbound(x[35], 0.0) 63 | setlowerbound(x[10], 0.0) 64 | setupperbound(x[1],1.0e6) 65 | setupperbound(x[2],1.0e6) 66 | setupperbound(x[3],1.0e6) 67 | setupperbound(x[4],1.0e6) 68 | setupperbound(x[5],1.0e6) 69 | setupperbound(x[6],1.0e6) 70 | setupperbound(x[7],1.0e6) 71 | setupperbound(x[8],1.0e6) 72 | setupperbound(x[9],1.0e6) 73 | setupperbound(x[10],1.0e6) 74 | setupperbound(x[11],1.0e6) 75 | setupperbound(x[12],1.0e6) 76 | setupperbound(x[13],1.0e6) 77 | setupperbound(x[14],1.0e6) 78 | setupperbound(x[15],1.0e6) 79 | setupperbound(x[16],1.0e6) 80 | setupperbound(x[17],1.0e6) 81 | setupperbound(x[18],1.0e6) 82 | setupperbound(x[19],1.0e6) 83 | setupperbound(x[20],1.0e6) 84 | setupperbound(x[21],1.0e6) 85 | setupperbound(x[22],1.0e6) 86 | setupperbound(x[23],1.0e6) 87 | setupperbound(x[24],1.0e6) 88 | setupperbound(x[25],1.0e6) 89 | setupperbound(x[26],1.0e6) 90 | setupperbound(x[27],1.0e6) 91 | setupperbound(x[28],1.0e6) 92 | setupperbound(x[29],1.0e6) 93 | setupperbound(x[30],1.0e6) 94 | setupperbound(x[31],1.0e6) 95 | setupperbound(x[32],1.0e6) 96 | setupperbound(x[33],1.0e6) 97 | setupperbound(x[34],1.0e6) 98 | setupperbound(x[35],1.0e6) 99 | setupperbound(x[36],1.0e6) 100 | setupperbound(x[37],1.0e6) 101 | setupperbound(x[38],1.0e6) 102 | setupperbound(x[39],1.0e6) 103 | setupperbound(x[40],1.0e6) 104 | setupperbound(x[41],1.0e6) 105 | 106 | @objective(m, Min, x[42]) 107 | 108 | @NLconstraint(m, e31,x[28]*x[30]-x[16]==0.0) 109 | @NLconstraint(m, e32,x[28]*x[31]-x[17]==0.0) 110 | @NLconstraint(m, e33,x[29]*x[32]-x[18]==0.0) 111 | @NLconstraint(m, e34,x[29]*x[33]-x[19]==0.0) 112 | @NLconstraint(m, e35,x[28]*x[36]-x[22]==0.0) 113 | @NLconstraint(m, e36,x[29]*x[37]-x[23]==0.0) 114 | @NLconstraint(m, e37,x[14]*x[30]-x[1]==0.0) 115 | @NLconstraint(m, e38,x[14]*x[31]-x[2]==0.0) 116 | @NLconstraint(m, e39,x[15]*x[32]-x[3]==0.0) 117 | @NLconstraint(m, e40,x[15]*x[33]-x[4]==0.0) 118 | @NLconstraint(m, e41,x[14]*x[36]-x[7]==0.0) 119 | @NLconstraint(m, e42,x[15]*x[37]-x[8]==0.0) 120 | 121 | @constraint(m, e1, -x[14]-x[15]+x[42]==0.0) 122 | @constraint(m, e2, -x[5]-x[9]-x[10]==-60.0) 123 | @constraint(m, e3, -x[6]-x[11]-x[12]==-20.0) 124 | @constraint(m, e4, -x[1]-x[3]-x[9]-x[11]+x[14]==0.0) 125 | @constraint(m, e5, -x[2]-x[4]-x[10]-x[12]+x[15]==0.0) 126 | @constraint(m, e6, -x[1]-x[2]-x[7]+x[14]==0.0) 127 | @constraint(m, e7, -x[3]-x[4]-x[8]+x[15]==0.0) 128 | @constraint(m, e8, -x[5]-x[6]-x[7]-x[8]+x[13]==0.0) 129 | @constraint(m, e9, -x[20]-x[24]-x[25]==-24000.0) 130 | @constraint(m, e10, -x[21]-x[26]-x[27]==-16000.0) 131 | @constraint(m, e11, -x[24]+24000*x[38]==0.0) 132 | @constraint(m, e12, -x[25]+24000*x[39]==0.0) 133 | @constraint(m, e13, -x[26]+16000*x[40]==0.0) 134 | @constraint(m, e14, -x[27]+16000*x[41]==0.0) 135 | @constraint(m, e15, -x[20]+24000*x[34]==0.0) 136 | @constraint(m, e16, -x[21]+16000*x[35]==0.0) 137 | @constraint(m, e17, -x[9]+60*x[38]==0.0) 138 | @constraint(m, e18, -x[10]+60*x[39]==0.0) 139 | @constraint(m, e19, -x[11]+20*x[40]==0.0) 140 | @constraint(m, e20, -x[12]+20*x[41]==0.0) 141 | @constraint(m, e21, -x[5]+60*x[34]==0.0) 142 | @constraint(m, e22, -x[6]+20*x[35]==0.0) 143 | @constraint(m, e23, x[34]+x[38]+x[39]==1.0) 144 | @constraint(m, e24, x[35]+x[40]+x[41]==1.0) 145 | @constraint(m, e25, -200*x[14]+x[16]+x[18]+x[24]+x[26]<=0.0) 146 | @constraint(m, e26, -1000*x[15]+x[17]+x[19]+x[25]+x[27]<=0.0) 147 | @constraint(m, e27, 0.01*x[16]+0.01*x[18]+0.01*x[24]+0.01*x[26]-x[28]==0.0) 148 | @constraint(m, e28, 0.2*x[17]+0.2*x[19]+0.2*x[25]+0.2*x[27]-x[29]==0.0) 149 | @constraint(m, e29, -x[16]-x[17]-x[22]+x[28]==0.0) 150 | @constraint(m, e30, -x[18]-x[19]-x[23]+x[29]==0.0) 151 | @constraint(m, e43, x[30]+x[31]+x[36]==1.0) 152 | @constraint(m, e44, x[32]+x[33]+x[37]==1.0) 153 | @constraint(m, e45, -10*x[13]+x[20]+x[21]+x[22]+x[23]<=0.0) 154 | 155 | if verbose 156 | print(m) 157 | end 158 | 159 | return m 160 | end 161 | -------------------------------------------------------------------------------- /src/log.jl: -------------------------------------------------------------------------------- 1 | # Create dictionary of logs for timing and iteration counts 2 | function create_logs!(m) 3 | 4 | logs = Dict{Symbol,Any}() 5 | 6 | # Timers 7 | logs[:presolve_time] = 0. # Total presolve-time of the algorithm 8 | logs[:total_time] = 0. # Total run-time of the algorithm 9 | logs[:time_left] = m.timeout # Total remaining time of the algorithm if timeout is specified 10 | 11 | # Values 12 | logs[:obj] = [] # Iteration based objective 13 | logs[:bound] = [] # Iteration based objective 14 | 15 | # Counters 16 | logs[:n_iter] = 0 # Number of iterations in iterative 17 | logs[:n_feas] = 0 # Number of times get a new feasible solution 18 | logs[:ub_incumb_cnt] = 0 # Number of incumbents detected on upper bound 19 | logs[:lb_incumb_cnt] = 0 # Number of incumebnts detected on lower bound 20 | logs[:bt_iter] = 0 21 | 22 | m.logs = logs 23 | end 24 | 25 | function reset_timer(m::PODNonlinearModel) 26 | m.logs[:total_time] = 0. 27 | m.logs[:time_left] = m.timeout 28 | return m 29 | end 30 | 31 | function logging_summary(m::PODNonlinearModel) 32 | 33 | if m.log_level > 0 34 | print_with_color(:light_yellow, "full problem loaded into POD\n") 35 | println("problen sense $(m.sense_orig)") 36 | @printf "number of constraints = %d\n" m.num_constr_orig 37 | @printf "number of non-linear constraints = %d\n" m.num_nlconstr_orig 38 | @printf "number of linear constraints = %d\n" m.num_lconstr_orig 39 | @printf "number of variables = %d\n" m.num_var_orig 40 | 41 | println("NLP solver = ", split(string(m.nlp_local_solver),".")[1]) 42 | println("MIP solver = ", split(string(m.mip_solver),".")[1]) 43 | 44 | println("maximum solution time = ", m.timeout) 45 | println("maximum iterations = ", m.maxiter) 46 | @printf "relative optimality gap criteria = %.5f (%.4f %%)\n" m.rel_gap (m.rel_gap*100) 47 | println("detected nonlinear terms = $(length(m.nonlinear_terms))") 48 | println("number of variables involved in nonlinear terms = $(length(m.all_nonlinear_vars))") 49 | println("number of selected variables to discretize = $(length(m.var_discretization_mip))") 50 | 51 | m.bilinear_convexhull && println("bilinear treatment = convex hull formulation") 52 | m.monomial_convexhull && println("monomial treatment = convex hull formulation") 53 | 54 | m.convhull_formulation_facet && println("using convex hull : facet formulation") 55 | m.convhull_formulation_sos2 && println("using convex hull : sos2 formulation") 56 | 57 | (m.discretization_add_partition_method == "adpative") && println("adaptively adding discretization ratio = $(m.discretization_ratio)") 58 | (m.discretization_add_partition_method == "uniform") && println("uniform discretization rate = $(m.discretization_uniform_rate)") 59 | 60 | end 61 | 62 | # Additional warnings 63 | m.mip_solver_identifier == "Gurobi" && warn("POD support Gurobi solver 7.0+ ...") 64 | end 65 | 66 | function logging_head(m::PODNonlinearModel) 67 | @show m.logs[:time_left] 68 | if m.logs[:time_left] < Inf 69 | print_with_color(:light_yellow, " | NLP | MIP || Objective | Bound | GAP\% | CLOCK | TIME LEFT | Iter \n") 70 | else 71 | print_with_color(:light_yellow, " | NLP | MIP || Objective | Bound | GAP\% | CLOCK | | Iter \n") 72 | end 73 | 74 | return 75 | end 76 | 77 | function logging_row_entry(m::PODNonlinearModel; kwargs...) 78 | 79 | options = Dict(kwargs) 80 | 81 | b_len = 14 82 | if isa(m.logs[:obj][end], Float64) 83 | UB_block = string(" ", round(m.logs[:obj][end],4), " " ^ (b_len - length(string(round(m.logs[:obj][end], 4))))) 84 | else 85 | UB_block = string(" ", string(m.logs[:obj][end]), " " ^ (b_len - length(string(m.logs[:obj][end])))) 86 | end 87 | LB_block = string(" ", round(m.logs[:bound][end],4), " " ^ (b_len - length(string(round(m.logs[:bound][end], 4))))) 88 | incumb_UB_block = string(" ", round(m.best_obj,4), " " ^ (b_len - length(string(round(m.best_obj, 4))))) 89 | incumb_LB_block = string(" ", round(m.best_bound,4), " " ^ (b_len - length(string(round(m.best_bound, 4))))) 90 | GAP_block = string(" ", round(m.best_rel_gap*100,5), " " ^ (b_len - length(string(round(m.best_rel_gap*100,5))))) 91 | UTIME_block = string(" ", round(m.logs[:total_time],2), "s", " " ^ (b_len - 1 - length(string(round(m.logs[:total_time],2))))) 92 | if m.logs[:time_left] < Inf 93 | LTIME_block = string(" ", round(m.logs[:time_left],2), "s", " " ^ (b_len - 1 - length(string(round(m.logs[:time_left],2))))) 94 | else 95 | LTIME_block = " " 96 | end 97 | haskey(options, :finsih_entry) ? (ITER_block = string(" ", "finish")) : (ITER_block = string(" ", m.logs[:n_iter])) 98 | 99 | if m.colorful_pod == "random" 100 | colors = [:blue, :cyan, :green, :red, :light_red, :light_blue, :light_cyan, :light_green, :light_magenta, :light_re, :light_yellow, :white, :yellow] 101 | print(" |") 102 | print_with_color(rand(colors),UB_block) 103 | print("|") 104 | print_with_color(rand(colors),LB_block) 105 | print("||") 106 | print_with_color(rand(colors),incumb_UB_block) 107 | print("|") 108 | print_with_color(rand(colors),incumb_LB_block) 109 | print("|") 110 | print_with_color(rand(colors),GAP_block) 111 | print("|") 112 | print_with_color(rand(colors),UTIME_block) 113 | print("|") 114 | print_with_color(rand(colors),LTIME_block) 115 | print("|") 116 | print_with_color(rand(colors),ITER_block) 117 | print("\n") 118 | elseif m.colorful_pod == "solarized" 119 | print_with_color(m.logs[:n_iter]+21, " |$(UB_block)|$(LB_block)||$(incumb_UB_block)|$(incumb_LB_block)|$(GAP_block)|$(UTIME_block)|$(LTIME_block)|$(ITER_block)\n") 120 | elseif m.colorful_pod == "warmer" 121 | print_with_color(max(20,170-m.logs[:n_iter]), " |$(UB_block)|$(LB_block)||$(incumb_UB_block)|$(incumb_LB_block)|$(GAP_block)|$(UTIME_block)|$(LTIME_block)|$(ITER_block)\n") 122 | elseif m.colorful_pod == false 123 | println(" |",UB_block,"|",LB_block,"||",incumb_UB_block,"|",incumb_LB_block,"|",GAP_block,"|",UTIME_block,"|",LTIME_block,"|",ITER_block) 124 | end 125 | 126 | return 127 | end 128 | 129 | 130 | #========================================================= 131 | Logging and printing functions 132 | =========================================================# 133 | 134 | # Create dictionary of statuses for POD algorithm 135 | function create_status!(m) 136 | 137 | status = Dict{Symbol,Symbol}() 138 | 139 | status[:presolve] = :none # Status of presolve 140 | status[:local_solve] = :none # Status of local solve 141 | status[:bounding_solve] = :none # Status of bounding solve 142 | status[:lower_bounding_solve] = :none # Status of lower bonding solve 143 | status[:upper_bounding_solve] = :none # Status of bounding solve 144 | status[:feasible_solution] = :none # Status of whether a upper bound is detected or not 145 | status[:upper_bound] = :none # Status of whether a upper bound has been detected 146 | status[:lower_bound] = :none # Status of whether a lower bound has been detected 147 | status[:bound] = :none # Status of whether a bound has been detected 148 | status[:bound_tightening_solve] = :none # Status of bound-tightening solve 149 | 150 | m.status = status 151 | end 152 | 153 | function summary_status(m::PODNonlinearModel) 154 | 155 | if m.status[:bound] == :Detected && m.status[:feasible_solution] == :Detected 156 | if m.best_rel_gap > m.rel_gap 157 | m.pod_status = :UserLimits 158 | else 159 | m.pod_status = :Optimal 160 | end 161 | elseif m.status[:bound] == :Detected && m.status[:feasible_solution] == :none 162 | m.pod_status = :Infeasible 163 | elseif m.status[:bound] == :none && m.status[:feasible_solution] == :Detected 164 | m.pod_status = :Heuristic 165 | else 166 | error("[UNEXPECTED] Missing bound and feasible solution during status summary.") 167 | end 168 | 169 | print_with_color(:green, "\n POD ended with status $(m.pod_status)\n") 170 | 171 | return 172 | end 173 | -------------------------------------------------------------------------------- /src/solver.jl: -------------------------------------------------------------------------------- 1 | export PODSolver 2 | 3 | # Dummy solver 4 | type UnsetSolver <: MathProgBase.AbstractMathProgSolver 5 | end 6 | 7 | type PODSolver <: MathProgBase.AbstractMathProgSolver 8 | dev_debug::Bool 9 | dev_test::Bool 10 | colorful_pod::Any 11 | 12 | log_level::Int 13 | timeout::Float64 14 | maxiter::Int 15 | rel_gap::Float64 16 | tol::Float64 17 | 18 | nlp_local_solver::MathProgBase.AbstractMathProgSolver 19 | minlp_local_solver::MathProgBase.AbstractMathProgSolver 20 | mip_solver::MathProgBase.AbstractMathProgSolver 21 | 22 | recognize_convex::Bool 23 | bilinear_mccormick::Bool 24 | bilinear_convexhull::Bool 25 | monomial_convexhull::Bool 26 | 27 | method_convexification::Array{Function} 28 | term_patterns::Array{Function} 29 | constr_patterns::Array{Function} 30 | 31 | discretization_var_pick_algo::Any 32 | discretization_ratio::Any 33 | discretization_uniform_rate::Int 34 | discretization_add_partition_method::Any 35 | discretization_abs_width_tol::Float64 36 | discretization_rel_width_tol::Float64 37 | discretization_consecutive_forbid::Int 38 | 39 | convexhull_sweep_limit::Int 40 | convhull_formulation_sos2::Bool 41 | convhull_formulation_sos2aux::Bool 42 | convhull_formulation_facet::Bool 43 | convhull_formulation_minib::Bool 44 | 45 | presolve_track_time::Bool 46 | presolve_bound_tightening::Bool 47 | presolve_maxiter::Int 48 | presolve_bt_width_tol::Float64 49 | presolve_bt_output_tol::Float64 50 | presolve_bound_tightening_algo::Any 51 | presolve_mip_relaxation::Bool 52 | presolve_mip_timelimit::Float64 53 | 54 | bound_basic_propagation::Bool 55 | 56 | user_parameters::Dict 57 | 58 | # other options to be added later on 59 | end 60 | 61 | function PODSolver(; 62 | dev_debug = false, 63 | dev_test = false, 64 | colorful_pod = false, 65 | 66 | log_level = 1, 67 | timeout = Inf, 68 | maxiter = 99, 69 | rel_gap = 1e-4, 70 | tol = 1e-6, 71 | 72 | nlp_local_solver = UnsetSolver(), 73 | minlp_local_solver = UnsetSolver(), 74 | mip_solver = UnsetSolver(), 75 | 76 | recognize_convex = true, 77 | bilinear_mccormick = false, 78 | bilinear_convexhull = true, 79 | monomial_convexhull = true, 80 | 81 | method_convexification = Array{Function}(0), 82 | term_patterns = Array{Function}(0), 83 | constr_patterns = Array{Function}(0), 84 | 85 | discretization_var_pick_algo = 0, # By default pick all variables 86 | discretization_ratio = 4, 87 | discretization_uniform_rate = 2, 88 | discretization_add_partition_method = "adaptive", 89 | discretization_abs_width_tol = 1e-4, 90 | discretization_rel_width_tol = 1e-6, 91 | discretization_consecutive_forbid = 0, 92 | 93 | convexhull_sweep_limit = 1, 94 | convhull_formulation_sos2 = true, 95 | convhull_formulation_sos2aux = false, 96 | convhull_formulation_facet = false, 97 | convhull_formulation_minib = false, 98 | 99 | presolve_track_time = false, 100 | presolve_bound_tightening = false, 101 | presolve_maxiter = 9999, 102 | presolve_bt_width_tol = 1e-3, 103 | presolve_bt_output_tol = 1e-5, 104 | presolve_bound_tightening_algo = 1, 105 | presolve_mip_relaxation = false, 106 | presolve_mip_timelimit = Inf, 107 | 108 | bound_basic_propagation = false, 109 | user_parameters = Dict(), 110 | kwargs... 111 | ) 112 | 113 | unsupport_opts = Dict(kwargs) 114 | !isempty(keys(unsupport_opts)) && warn("Detected unsupported/experimental arguments = $(keys(unsupport_opts))") 115 | 116 | if nlp_local_solver == UnsetSolver() 117 | error("No NLP local solver specified (set nlp_local_solver)\n") 118 | end 119 | 120 | if mip_solver == UnsetSolver() 121 | error("NO MIP solver specififed (set mip_solver)\n") 122 | end 123 | 124 | # Deepcopy the solvers because we may change option values inside POD 125 | PODSolver(dev_debug, dev_test, colorful_pod, 126 | log_level, timeout, maxiter, rel_gap, tol, 127 | deepcopy(nlp_local_solver), 128 | deepcopy(minlp_local_solver), 129 | deepcopy(mip_solver), 130 | recognize_convex, 131 | bilinear_mccormick, 132 | bilinear_convexhull, 133 | monomial_convexhull, 134 | method_convexification, 135 | term_patterns, 136 | constr_patterns, 137 | discretization_var_pick_algo, 138 | discretization_ratio, 139 | discretization_uniform_rate, 140 | discretization_add_partition_method, 141 | discretization_abs_width_tol, 142 | discretization_rel_width_tol, 143 | discretization_consecutive_forbid, 144 | convexhull_sweep_limit, 145 | convhull_formulation_sos2, 146 | convhull_formulation_sos2aux, 147 | convhull_formulation_facet, 148 | convhull_formulation_minib, 149 | presolve_track_time, 150 | presolve_bound_tightening, 151 | presolve_maxiter, 152 | presolve_bt_width_tol, 153 | presolve_bt_output_tol, 154 | presolve_bound_tightening_algo, 155 | presolve_mip_relaxation, 156 | presolve_mip_timelimit, 157 | bound_basic_propagation, 158 | user_parameters) 159 | end 160 | 161 | # Create POD nonlinear model: can solve with nonlinear algorithm only 162 | function MathProgBase.NonlinearModel(s::PODSolver) 163 | if !applicable(MathProgBase.NonlinearModel, s.nlp_local_solver) 164 | error("NLP local solver $(s.nlp_local_solver) specified is not a NLP solver recognized by POD\n") 165 | end 166 | 167 | # Translate options into old nonlinearmodel.jl fields 168 | dev_test = s.dev_test 169 | dev_debug = s.dev_debug 170 | colorful_pod = s.colorful_pod 171 | 172 | log_level = s.log_level 173 | timeout = s.timeout 174 | maxiter = s.maxiter 175 | rel_gap = s.rel_gap 176 | tol = s.tol 177 | 178 | recognize_convex = s.recognize_convex 179 | bilinear_mccormick = s.bilinear_mccormick 180 | bilinear_convexhull = s.bilinear_convexhull 181 | monomial_convexhull = s.monomial_convexhull 182 | 183 | method_convexification = s.method_convexification 184 | term_patterns = s.term_patterns 185 | constr_patterns = s.constr_patterns 186 | 187 | nlp_local_solver = s.nlp_local_solver 188 | minlp_local_solver = s.minlp_local_solver 189 | mip_solver = s.mip_solver 190 | 191 | discretization_var_pick_algo = s.discretization_var_pick_algo 192 | discretization_ratio = s.discretization_ratio 193 | discretization_uniform_rate = s.discretization_uniform_rate 194 | discretization_add_partition_method = s.discretization_add_partition_method 195 | discretization_abs_width_tol = s.discretization_abs_width_tol 196 | discretization_rel_width_tol = s.discretization_rel_width_tol 197 | discretization_consecutive_forbid = s.discretization_consecutive_forbid 198 | 199 | convexhull_sweep_limit = s.convexhull_sweep_limit 200 | convhull_formulation_sos2 = s.convhull_formulation_sos2 201 | convhull_formulation_sos2aux = s.convhull_formulation_sos2aux 202 | convhull_formulation_facet = s.convhull_formulation_facet 203 | convhull_formulation_minib = s.convhull_formulation_minib 204 | 205 | presolve_track_time = s.presolve_track_time 206 | presolve_bound_tightening = s.presolve_bound_tightening 207 | presolve_maxiter = s.presolve_maxiter 208 | presolve_bt_width_tol = s.presolve_bt_width_tol 209 | presolve_bt_output_tol = s.presolve_bt_output_tol 210 | presolve_bound_tightening_algo = s.presolve_bound_tightening_algo 211 | presolve_mip_relaxation = s.presolve_mip_relaxation 212 | presolve_mip_timelimit = s.presolve_mip_timelimit 213 | 214 | bound_basic_propagation = s.bound_basic_propagation 215 | 216 | user_parameters = s.user_parameters 217 | 218 | return PODNonlinearModel(dev_debug, dev_test, colorful_pod, 219 | log_level, timeout, maxiter, rel_gap, tol, 220 | nlp_local_solver, 221 | minlp_local_solver, 222 | mip_solver, 223 | recognize_convex, 224 | bilinear_mccormick, 225 | bilinear_convexhull, 226 | monomial_convexhull, 227 | method_convexification, 228 | term_patterns, 229 | constr_patterns, 230 | discretization_var_pick_algo, 231 | discretization_ratio, 232 | discretization_uniform_rate, 233 | discretization_add_partition_method, 234 | discretization_abs_width_tol, 235 | discretization_rel_width_tol, 236 | discretization_consecutive_forbid, 237 | convexhull_sweep_limit, 238 | convhull_formulation_sos2, 239 | convhull_formulation_sos2aux, 240 | convhull_formulation_facet, 241 | convhull_formulation_minib, 242 | presolve_track_time, 243 | presolve_bound_tightening, 244 | presolve_maxiter, 245 | presolve_bt_width_tol, 246 | presolve_bt_output_tol, 247 | presolve_bound_tightening_algo, 248 | presolve_mip_relaxation, 249 | presolve_mip_timelimit, 250 | bound_basic_propagation, 251 | user_parameters) 252 | end 253 | -------------------------------------------------------------------------------- /src/presolve.jl: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | bound_tightening(m::PODNonlinearModel) 4 | 5 | Entry point for the bound-tightening algorithm. The aim of the bound-tightening algorithm 6 | is to tighten the variable bounds, if possible. 7 | 8 | Currently, two bounding tightening method is implemented [`minmax_bound_tightening`](@ref). 9 | 10 | * Bound-tightening with basic McCormick 11 | * Bound-tightening with McCormick partitions: (3 partitions around the local feasible solution) 12 | If no local feasible solution is obtained, the algorithm defaults to bound-tightening with basic McCormick 13 | 14 | """ 15 | function bound_tightening(m::PODNonlinearModel; use_bound = true, kwargs...) 16 | 17 | if m.presolve_bound_tightening == false # User choose not to do bound tightening 18 | return 19 | end 20 | 21 | if m.presolve_bound_tightening_algo == 1 22 | minmax_bound_tightening(m, use_bound=use_bound) 23 | elseif m.presolve_bound_tightening_algo == 2 24 | minmax_bound_tightening(m, use_bound=use_bound, use_tmc=true) 25 | elseif isa(m.presolve_bound_tightening_algo, Function) 26 | eval(m.presolve_bound_tightening_algo)(m) 27 | else 28 | error("Unrecognized bound-tightening algorithm") 29 | end 30 | 31 | return 32 | end 33 | 34 | """ 35 | 36 | minmax_bound_tightening(m:PODNonlinearModel; use_bound::Bool=true, use_tmc::Bool) 37 | 38 | This function implements the bound-tightening algorithm to tighten the variable bounds. 39 | It utilizes either the basic McCormick relaxation or the Tightened McCormick relaxation (TMC) 40 | to tighten the bounds. The TMC has additional binary variables for partitioning. 41 | 42 | The algorithm as two main parameters. The first is the `use_tmc`, which when set to `true` 43 | invokes the algorithm on the TMC relaxation. The second parameter `use_bound` takes in the 44 | objective value of the local solve solution stored in `best_sol`. The `use_bound` option is set 45 | to `true` when the local solve is successful is obtaining a feasible solution and the bound-tightening 46 | is to be performed using the objective value of the feasible solution, else this parameter 47 | is set to `false` 48 | 49 | Several other parameters are available for the presolve algorithm tuning. 50 | For more details, see [Parameters](@ref). 51 | 52 | """ 53 | function minmax_bound_tightening(m::PODNonlinearModel; use_bound = true, kwargs...) 54 | 55 | # Some functinal constants 56 | both_senses = [:Min, :Max] # Senses during bound tightening procedures 57 | tell_side = Dict(:Min=>1, :Max=>2) # Positional information 58 | tell_round = Dict(:Min=>floor, :Max=>ceil) 59 | status_pass = [:Optimal] 60 | status_reroute = [:UserLimits] 61 | 62 | options = Dict(kwargs) 63 | 64 | # Regulating Special Input Conditions: default use best feasible solution objective value 65 | (use_bound == true) ? bound = m.best_obj : bound = Inf 66 | discretization = to_discretization(m, m.l_var_tight, m.u_var_tight) 67 | if use_bound == false && haskey(options, :use_tmc) 68 | (m.log_level > 0) && warn("[BOUND TIGHTENING ALGO] TMC chosen by the user, but local solve infeasible; defaulting to doing bound-tightening without TMC.") 69 | end 70 | if use_bound == true && haskey(options, :use_tmc) 71 | discretization = add_adaptive_partition(m, use_solution=m.best_sol, use_discretization=discretization) 72 | end 73 | discretization = resolve_lifted_var_bounds(m.nonlinear_terms, m.linear_terms, discretization) # recomputation of bounds for lifted_variables 74 | 75 | (m.log_level > 0) && println("starting the bound-tightening algorithm ...") 76 | (m.log_level > 99) && [println("[DEBUG] VAR $(var_idx) Original Bound [$(round(m.l_var_tight[var_idx],4)) < - > $(round(m.u_var_tight[var_idx],4))]") for var_idx in m.all_nonlinear_vars] 77 | 78 | # start of the solve 79 | keeptightening = true 80 | while keeptightening && (m.logs[:time_left] > m.tol) && (m.logs[:bt_iter] < m.presolve_maxiter) # Stopping criteria 81 | 82 | keeptightening = false 83 | m.logs[:bt_iter] += 1 84 | (m.log_level > 99) && println("[DEBUG] Iteration - $(m.logs[:bt_iter])") 85 | temp_bounds = Dict() 86 | 87 | # Perform Bound Contraction 88 | for var_idx in m.all_nonlinear_vars # why only all nonlinear vars? 89 | temp_bounds[var_idx] = [discretization[var_idx][1], discretization[var_idx][end]] 90 | if abs(discretization[var_idx][1] - discretization[var_idx][end]) > m.presolve_bt_width_tol 91 | create_bound_tightening_model(m, discretization, bound) 92 | for sense in both_senses 93 | @objective(m.model_mip, sense, Variable(m.model_mip, var_idx)) 94 | status = solve_bound_tightening_model(m) 95 | if status in status_pass 96 | temp_bounds[var_idx][tell_side[sense]] = eval(tell_round[sense])(getobjectivevalue(m.model_mip)/m.presolve_bt_output_tol)*m.presolve_bt_output_tol # Objective truncation for numerical issues 97 | elseif status in status_reroute 98 | temp_bounds[var_idx][tell_side[sense]] = eval(tell_round[sense])(getobjbound(m.model_mip)/m.presolve_bt_output_tol)*m.presolve_bt_output_tol 99 | else 100 | print("!") 101 | temp_bounds[var_idx][tell_side[sense]] = temp_bounds[var_idx][tell_side[sense]] 102 | end 103 | m.log_level > 99 && println("[DEBUG] contracting VAR $(var_idx) $(sense) problem, results in $(temp_bounds[var_idx][tell_side[sense]])") 104 | end 105 | end 106 | end 107 | 108 | # Updates the discretization structure 109 | for var_idx in m.all_nonlinear_vars 110 | if abs((temp_bounds[var_idx][1] - discretization[var_idx][1])/discretization[var_idx][1]) > m.presolve_bt_width_tol 111 | (m.log_level > 0) && print("+") 112 | keeptightening = true # Continue to perform the next iteration 113 | discretization[var_idx][1] = temp_bounds[var_idx][1] 114 | end 115 | if abs((discretization[var_idx][end]-temp_bounds[var_idx][end])/discretization[var_idx][end]) > m.presolve_bt_width_tol 116 | (m.log_level > 0) && print("+") 117 | keeptightening = true 118 | discretization[var_idx][end] = temp_bounds[var_idx][end] 119 | end 120 | end 121 | 122 | discretization = resolve_lifted_var_bounds(m.nonlinear_terms, m.linear_terms, discretization) 123 | haskey(options, :use_tmc) ? discretization = add_adaptive_partition(m, use_solution=m.best_sol, use_discretization=flatten_discretization(discretization)) : discretization = discretization 124 | end 125 | 126 | (m.log_level > 0) && println("\nfinished bound tightening in $(m.logs[:bt_iter]) iterations, applying tighten bounds") 127 | 128 | # Update the bounds with the tightened ones 129 | # @show discretization 130 | m.l_var_tight, m.u_var_tight = update_var_bounds(discretization) 131 | m.discretization = add_adaptive_partition(m, use_solution=m.best_sol) 132 | 133 | (m.log_level > 99) && [println("[DEBUG] VAR $(i) BOUND contracted |$(round(m.l_var_orig[i],4)) --> | $(round(m.l_var_tight[i],4)) - * - $(round(m.u_var_tight[i],4)) | <-- $(round(m.u_var_orig[i],4)) |") for i in 1:m.num_var_orig] 134 | (m.log_level > 0) && print("\n") 135 | return 136 | end 137 | 138 | """ 139 | create_bound_tightening_model(m::PODNonlinearModel, discretization::Dict, bound::Float64) 140 | 141 | This function takes in the initial discretization information and builds a bound-tightening model. 142 | It is an algorithm specific function called by [`minmax_bound_tightening`](@ref) 143 | 144 | """ 145 | function create_bound_tightening_model(m::PODNonlinearModel, discretization, bound; kwargs...) 146 | 147 | options = Dict(kwargs) 148 | 149 | start_build = time() 150 | m.model_mip = Model(solver=m.mip_solver) # Construct JuMP model 151 | amp_post_vars(m, use_discretization=discretization) 152 | amp_post_lifted_constraints(m) 153 | amp_post_convexification(m, use_discretization=discretization) # Convexify problem 154 | 155 | if bound != Inf 156 | post_obj_bounds(m, bound) 157 | end 158 | 159 | cputime_build = time() - start_build 160 | m.logs[:total_time] += cputime_build 161 | m.logs[:time_left] = max(0.0, m.timeout - m.logs[:total_time]) 162 | 163 | return 164 | end 165 | 166 | """ 167 | 168 | solve_bound_tightening_model(m::PODNonlinearModel) 169 | 170 | A function that solves the min and max bound-tightening model. 171 | 172 | """ 173 | function solve_bound_tightening_model(m::PODNonlinearModel; kwargs...) 174 | 175 | # ========= MILP Solve ========= # 176 | if m.presolve_mip_timelimit < Inf 177 | update_mip_time_limit(m, timelimit = max(0.0, min(m.presolve_mip_timelimit, m.timeout - m.logs[:total_time]))) 178 | else 179 | update_mip_time_limit(m, timelimit = max(0.0, m.timeout - m.logs[:total_time])) 180 | end 181 | 182 | start_solve = time() 183 | status = solve(m.model_mip, suppress_warnings=true, relaxation=m.presolve_mip_relaxation) #TODO Double check here 184 | cputime_solve = time() - start_solve 185 | m.logs[:total_time] += cputime_solve 186 | m.logs[:time_left] = max(0.0, m.timeout - m.logs[:total_time]) 187 | # ========= MILP Solve ========= # 188 | 189 | return status 190 | end 191 | 192 | """ 193 | TODO: docstring 194 | """ 195 | function post_obj_bounds(m::PODNonlinearModel, bound::Float64; kwargs...) 196 | if m.sense_orig == :Max 197 | @constraint(m.model_mip, 198 | sum(m.bounding_obj_mip[:coefs][j]*Variable(m.model_mip, m.bounding_obj_mip[:vars][j].args[2]) for j in 1:m.bounding_obj_mip[:cnt]) >= bound) 199 | elseif m.sense_orig == :Min 200 | @constraint(m.model_mip, 201 | sum(m.bounding_obj_mip[:coefs][j]*Variable(m.model_mip, m.bounding_obj_mip[:vars][j].args[2]) for j in 1:m.bounding_obj_mip[:cnt]) <= bound) 202 | end 203 | 204 | return 205 | end 206 | -------------------------------------------------------------------------------- /examples/multi.jl: -------------------------------------------------------------------------------- 1 | using JuMP, MathProgBase, Gurobi, Ipopt, POD 2 | 3 | println("--------------------------------------------------------------------------") 4 | println("Multi4/N - exprmode 1 -> X1 * X2 * X3 * X4") 5 | println("Multi4/N - exprmode 2 -> (X1*X2) * (X3*X4)") 6 | println("Multi4/N - exprmode 3 -> (X1*X2) * X3 * X4") 7 | println("Multi4/N - exprmode 4 -> X1 * X2 * (X3*X4)") 8 | println("Multi4/N - exprmode 5 -> ((X1*X2) * X3) * X4") 9 | println("Multi4/N - exprmode 6 -> (X1*X2*X3) *X4") 10 | println("Multi4/N - exprmode 7 -> X1 * (X2 * (X3*X4))") 11 | println("Multi4/N - exprmode 8 -> X1 * (X2*X3) * X4") 12 | println("Multi4/N - exprmode 9 -> X1 * (X2*X3*X4)") 13 | println("Multi4/N - exprmode 10 -> X1 * ((X2*X3) * X4)") 14 | println("Multi4/N - exprmode 11 -> (X1 * (X2*X3)) * X4") 15 | println("--------------------------------------------------------------------------") 16 | println("Multi3/N - exprmode 1 -> X1 * X2 * X3") 17 | println("Multi3/N - exprmode 2 -> (X1*X2) * X3") 18 | println("Multi3/N - exprmode 3 -> X1 * (X2*X3)") 19 | println("--------------------------------------------------------------------------") 20 | println(" Options") 21 | println("N | K | convhull | exprmode | unifrom | randomub | sos2 | presolve | delta") 22 | println("--------------------------------------------------------------------------") 23 | 24 | function multi4(;verbose=false,solver=nothing, exprmode=1) 25 | 26 | if solver == nothing 27 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 28 | mip_solver=GurobiSolver(OutputFlag=0), 29 | rel_gap=0.0001, 30 | bilinear_convexhull=true, 31 | convexhull_sweep_limit=1, 32 | # discretization_var_pick_algo=pick_my_var, 33 | presolve_bound_tightening=false, 34 | presolve_track_time=true, 35 | presolve_bound_tightening_algo=1, 36 | log_level=100)) 37 | else 38 | m = Model(solver=solver) 39 | end 40 | 41 | srand(1) 42 | @variable(m, 0.1<=x[1:4]<=rand()*100) 43 | if exprmode == 1 44 | @NLobjective(m, Max, x[1]*x[2]*x[3]*x[4]) 45 | elseif exprmode == 2 46 | @NLobjective(m, Max, (x[1]*x[2])*(x[3]*x[4])) 47 | elseif exprmode == 3 48 | @NLobjective(m, Max, (x[1]*x[2])*x[3]*x[4]) 49 | elseif exprmode == 4 50 | @NLobjective(m, Max, x[1]*x[2]*(x[3]*x[4])) 51 | elseif exprmode == 5 52 | @NLobjective(m, Max, (((x[1]*x[2])*x[3])*x[4])) 53 | elseif exprmode == 6 54 | @NLobjective(m, Max, (x[1]*x[2]*x[3])*x[4]) 55 | elseif exprmode == 7 56 | @NLobjective(m, Max, x[1]*(x[2]*(x[3]*x[4]))) 57 | elseif exprmode == 8 58 | @NLobjective(m, Max, x[1]*(x[2]*x[3])*x[4]) 59 | elseif exprmode == 9 60 | @NLobjective(m, Max, x[1]*(x[2]*x[3]*x[4])) 61 | elseif exprmode == 10 62 | @NLobjective(m, Max, x[1]*((x[2]*x[3])*x[4])) 63 | elseif exprmode == 11 64 | @NLobjective(m, Max, (x[1]*(x[2]*x[3]))*x[4]) 65 | else 66 | error("exprmode argument only taks from 1-7") 67 | end 68 | @constraint(m, sum(x[i] for i in 1:4) <= 4) 69 | 70 | if verbose 71 | print(m) 72 | end 73 | 74 | return m 75 | end 76 | 77 | function multi3(;verbose=false, solver=nothing, exprmode=1, convhull=false) 78 | 79 | if solver == nothing 80 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 81 | mip_solver=GurobiSolver(OutputFlag=1, Presolve=0), 82 | maxiter=1, 83 | bilinear_convexhull=convhull, 84 | discretization_add_partition_method="uniform", 85 | discretization_uniform_rate=5, 86 | presolve_bound_tightening=false, 87 | log_level=1)) 88 | else 89 | m = Model(solver=solver) 90 | end 91 | 92 | srand(1) 93 | ub = rand() 94 | @variable(m, 0.1<=x[1:3]<=ub*1000) 95 | if exprmode == 1 96 | @NLobjective(m, Max, x[1]*x[2]*x[3]) 97 | elseif exprmode == 2 98 | @NLobjective(m, Max, (x[1]*x[2])*x[3]) 99 | elseif exprmode == 3 100 | @NLobjective(m, Max, x[1]*(x[2]*x[3])) 101 | else 102 | error("exprmode argument only taks from 1-7") 103 | end 104 | 105 | @constraint(m, sum(x[i] for i in 1:3) <= 3) 106 | 107 | if verbose 108 | print(m) 109 | end 110 | 111 | return m 112 | end 113 | 114 | function multi2(;verbose=false,solver=nothing) 115 | 116 | if solver == nothing 117 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 118 | mip_solver=GurobiSolver(OutputFlag=0), 119 | bilinear_convexhull=true, 120 | discretization_add_partition_method="uniform", 121 | presolve_bound_tightening=false, 122 | log_level=100)) 123 | else 124 | m = Model(solver=solver) 125 | end 126 | 127 | srand(1) 128 | @variable(m, 0.1<=x[1:2]<=rand()*10) 129 | @NLobjective(m, Max, x[1]*x[2]) 130 | @constraint(m, sum(x[i] for i in 1:2) <= 2) 131 | 132 | if verbose 133 | print(m) 134 | end 135 | 136 | return m 137 | end 138 | 139 | function multi4N(;verbose=false, solver=nothing, N=1, convhull=false, exprmode=1, uniform=-1, randomub=true, sos2=true, delta=4, presolve=0) 140 | 141 | if solver == nothing 142 | if uniform >0 143 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 144 | mip_solver=GurobiSolver(OutputFlag=1), 145 | bilinear_convexhull=convhull, 146 | convhull_formulation_sos2=sos2, 147 | discretization_add_partition_method="uniform", 148 | discretization_uniform_rate=uniform, 149 | maxiter=1, 150 | log_level=100)) 151 | else 152 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 153 | mip_solver=GurobiSolver(OutputFlag=0), 154 | discretization_ratio=delta, 155 | convhull_formulation_sos2=sos2, 156 | bilinear_convexhull=convhull, 157 | presolve_bound_tightening=(presolve>0), 158 | presolve_bound_tightening_algo=presolve, 159 | log_level=100)) 160 | end 161 | else 162 | m = Model(solver=solver) 163 | end 164 | 165 | M = 1+3*N 166 | srand(100) 167 | isa(randomub, Bool) ? @variable(m, 0.1 <= x[1:M] <= 100*rand()) : @variable(m, 0.1 <= x[1:M] <= randomub) 168 | 169 | if exprmode == 1 170 | @NLobjective(m, Max, sum(x[i]*x[i+1]*x[i+2]*x[i+3] for i in 1:3:(M-1))) 171 | elseif exprmode == 2 172 | @NLobjective(m, Max, sum((x[i]*x[i+1])*(x[i+2]*x[i+3]) for i in 1:3:(M-1))) 173 | elseif exprmode == 3 174 | @NLobjective(m, Max, sum((x[i]*x[i+1])*x[i+2]*x[i+3] for i in 1:3:(M-1))) 175 | elseif exprmode == 4 176 | @NLobjective(m, Max, sum(x[i]*x[i+1]*(x[i+2]*x[i+3]) for i in 1:3:(M-1))) 177 | elseif exprmode == 5 178 | @NLobjective(m, Max, sum((((x[i]*x[i+1])*x[i+2])*x[i+3]) for i in 1:3:(M-1))) 179 | elseif exprmode == 6 180 | @NLobjective(m, Max, sum((x[i]*x[i+1]*x[i+2])*x[i+3] for i in 1:3:(M-1))) 181 | elseif exprmode == 7 182 | @NLobjective(m, Max, sum(x[i]*(x[i+1]*(x[i+2]*x[i+3])) for i in 1:3:(M-1))) 183 | elseif exprmode == 8 184 | @NLobjective(m, Max, sum(x[i]*(x[i+1]*x[i+2])*x[i+3] for i in 1:3:(M-1))) 185 | elseif exprmode == 9 186 | @NLobjective(m, Max, sum(x[i]*(x[i+1]*x[i+2]*x[i+3]) for i in 1:3:(M-1))) 187 | elseif exprmode == 10 188 | @NLobjective(m, Max, sum(x[i]*((x[i+1]*x[i+2])*x[i+3]) for i in 1:3:(M-1))) 189 | elseif exprmode == 11 190 | @NLobjective(m, Max, sum((x[i]*(x[i+1]*x[i+2]))*x[i+3] for i in 1:3:(M-1))) 191 | else 192 | error("exprmode argument only taks from 1-7") 193 | end 194 | 195 | @constraint(m, [i in 1:3:(M-1)], x[i]+x[i+1]+x[i+2]+x[i+3]<=4) 196 | 197 | if verbose 198 | print(m) 199 | end 200 | 201 | return m 202 | end 203 | 204 | function multi3N(;verbose=false, solver=nothing, exprmode=1, convhull=false, uniform=-1, randomub=true, N=1, delta=4, sos2=true, sos2_alter=false, presolve=0) 205 | 206 | if solver == nothing 207 | if uniform > 0.0 208 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 209 | mip_solver=GurobiSolver(OutputFlag=1), 210 | convhull_formulation_sos2=sos2, 211 | convhull_formulation_sos2aux=sos2_alter, 212 | maxiter=1, 213 | bilinear_convexhull=convhull, 214 | discretization_add_partition_method="uniform", 215 | discretization_uniform_rate=uniform, 216 | presolve_bound_tightening=false, 217 | log_level=1)) 218 | else 219 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 220 | mip_solver=GurobiSolver(OutputFlag=0), 221 | convhull_formulation_sos2=sos2, 222 | convhull_formulation_sos2aux=sos2_alter, 223 | discretization_ratio=delta, 224 | bilinear_convexhull=convhull, 225 | presolve_bound_tightening=(presolve>0), 226 | presolve_bound_tightening_algo=presolve, 227 | discretization_var_pick_algo=0, 228 | log_level=1)) 229 | end 230 | else 231 | m = Model(solver=solver) 232 | end 233 | 234 | M = 1+2*N 235 | srand(100) 236 | isa(randomub, Int) ? @variable(m, 0.1<=x[1:M]<=randomub) : @variable(m, 0.1<=x[1:M]<=rand()*100) 237 | if exprmode == 1 238 | @NLobjective(m, Max, sum(x[i]*x[i+1]*x[i+2] for i in 1:2:(M-1))) 239 | elseif exprmode == 2 240 | @NLobjective(m, Max, sum((x[i]*x[i+1])*x[i+2] for i in 1:2:(M-1))) 241 | elseif exprmode == 3 242 | @NLobjective(m, Max, sum(x[i]*(x[i+1]*x[i+2]) for i in 1:2:(M-1))) 243 | else 244 | error("exprmode argument only taks from 1-7") 245 | end 246 | 247 | @constraint(m, [i in 1:2:(M-1)], x[i] + x[i+1] + x[i+2] <= 3) 248 | 249 | if verbose 250 | print(m) 251 | end 252 | 253 | return m 254 | end 255 | 256 | function multiKND(;verbose=false, solver=nothing, exprmode=1, convhull=false, uniform=-1, sos2=false, facet=false, randomub=true, N=1, K=2, D=1, delta=4, presolve=0) 257 | 258 | if solver == nothing 259 | if uniform > 0.0 260 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 261 | mip_solver=GurobiSolver(OutputFlag=1), 262 | maxiter=1, 263 | bilinear_convexhull=convhull, 264 | convhull_formulation_sos2=sos2, 265 | convhull_formulation_facet=facet, 266 | discretization_add_partition_method="uniform", 267 | discretization_uniform_rate=uniform, 268 | presolve_bound_tightening=false, 269 | log_level=100)) 270 | else 271 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 272 | mip_solver=GurobiSolver(OutputFlag=1), 273 | convhull_formulation_sos2=sos2, 274 | convhull_formulation_facet=facet, 275 | discretization_ratio=delta, 276 | bilinear_convexhull=convhull, 277 | presolve_bound_tightening=(presolve>0), 278 | presolve_bound_tightening=presolve, 279 | log_level=100)) 280 | end 281 | else 282 | m = Model(solver=solver) 283 | end 284 | 285 | M = K+(K-D)*(N-1) 286 | srand(100) 287 | isa(randomub, Int) ? @variable(m, 0.1<=x[1:M]<=randomub) : @variable(m, 0.1<=x[1:M]<=rand()*100) 288 | @NLobjective(m, Max, sum(prod(x[i+k] for k in 0:(K-1)) for i in 1:(K-D):(M-D))) 289 | @constraint(m, [i in 1:(K-D):(M-D)], sum(x[i+k] for k in 0:(K-1)) <= K) 290 | 291 | verbose && print(m) 292 | 293 | return m 294 | end 295 | -------------------------------------------------------------------------------- /src/tmc.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | TODO: docstring 4 | """ 5 | function amp_post_mccormick(m::PODNonlinearModel; kwargs...) 6 | 7 | options = Dict(kwargs) 8 | 9 | # detect whether to use specific discretization information 10 | haskey(options, :use_discretization) ? discretization = options[:use_discretization] : discretization = m.discretization 11 | 12 | λ = Dict() 13 | λλ = Dict() 14 | λX = Dict() 15 | lb = Dict() 16 | ub = Dict() 17 | 18 | for bi in keys(m.nonlinear_terms) 19 | nl_type = m.nonlinear_terms[bi][:nonlinear_type] 20 | if ((!m.monomial_convexhull)*(nl_type == :monomial) || (!m.bilinear_convexhull)*(nl_type == :bilinear)) && (m.nonlinear_terms[bi][:convexified] == false) 21 | @assert length(bi) == 2 22 | m.nonlinear_terms[bi][:convexified] = true # Bookeeping the examined terms 23 | idx_a = bi[1].args[2] 24 | idx_b = bi[2].args[2] 25 | idx_ab = m.nonlinear_terms[bi][:lifted_var_ref].args[2] 26 | 27 | part_cnt_a = length(discretization[idx_a]) - 1 28 | part_cnt_b = length(discretization[idx_b]) - 1 29 | 30 | lb[idx_a] = discretization[idx_a][1:(end-1)] 31 | ub[idx_a] = discretization[idx_a][2:end] 32 | lb[idx_b] = discretization[idx_b][1:(end-1)] 33 | ub[idx_b] = discretization[idx_b][2:end] 34 | 35 | (lb[idx_a][1] == -Inf) && error("Infinite lower bound detected on VAR$idx_a, resolve it please") 36 | (lb[idx_b][1] == -Inf) && error("Infinite lower bound detected on VAR$idx_b, resolve it please") 37 | (ub[idx_a][end] == +Inf) && error("Infinite upper bound detected on VAR$idx_a, resolve it please") 38 | (ub[idx_b][end] == +Inf) && error("Infinite upper bound detected on VAR$idx_b, resolve it please") 39 | 40 | if (length(lb[idx_a]) == 1) && (length(lb[idx_b]) == 1) # Basic McCormick 41 | if m.nonlinear_terms[bi][:nonlinear_type] == :monomial 42 | mccormick_monomial(m.model_mip, Variable(m.model_mip, idx_ab), Variable(m.model_mip,idx_a), lb[idx_a][1], ub[idx_a][1]) 43 | elseif m.nonlinear_terms[bi][:nonlinear_type] == :bilinear 44 | mccormick(m.model_mip, Variable(m.model_mip, idx_ab), Variable(m.model_mip, idx_a), Variable(m.model_mip, idx_b), 45 | lb[idx_a][1], ub[idx_a][1], lb[idx_b][1], ub[idx_b][1]) 46 | end 47 | else # Tighten McCormick 48 | # if m.nonlinear_terms[bi][:monomial_satus] 49 | if m.nonlinear_terms[bi][:nonlinear_type] == :monomial 50 | λ = amp_post_tmc_λ(m.model_mip, λ, lb, ub, part_cnt_a, idx_a) 51 | λX = amp_post_tmc_λX(m.model_mip, λX, part_cnt_a, idx_a, idx_b) 52 | amp_post_tmc_λxX_mc(m.model_mip, λX, λ, lb, ub, idx_a, idx_b) 53 | amp_post_tmc_monomial_mc(m.model_mip, idx_ab, λ, λX, lb, ub, part_cnt_a, idx_a) 54 | # else 55 | elseif m.nonlinear_terms[bi][:nonlinear_type] == :bilinear 56 | # Partitioning on left 57 | if (idx_a in m.var_discretization_mip) && !(idx_b in m.var_discretization_mip) 58 | λ = amp_post_tmc_λ(m.model_mip, λ, lb, ub, part_cnt_a, idx_a) 59 | λX = amp_post_tmc_λX(m.model_mip, λX, part_cnt_a, idx_a, idx_b) 60 | λX[(idx_b,idx_a)] = [Variable(m.model_mip, idx_a)] 61 | λλ = amp_post_tmc_λλ(m.model_mip, λλ, λ, idx_a, idx_b) 62 | amp_post_tmc_λxX_mc(m.model_mip, λX, λ, lb, ub, idx_a, idx_b) 63 | amp_post_tmc_XX_mc(m.model_mip, idx_ab, λX, λλ, lb, ub, idx_a, idx_b) 64 | end 65 | 66 | # Partitioning of right 67 | if !(idx_a in m.var_discretization_mip) && (idx_b in m.var_discretization_mip) 68 | λ = amp_post_tmc_λ(m.model_mip, λ, lb, ub, part_cnt_b, idx_b) 69 | λX = amp_post_tmc_λX(m.model_mip, λX, part_cnt_b, idx_b, idx_a) 70 | λX[(idx_a,idx_b)] = [Variable(m.model_mip, idx_b)] 71 | λλ = amp_post_tmc_λλ(m.model_mip, λλ, λ, idx_b, idx_a) 72 | amp_post_tmc_λxX_mc(m.model_mip, λX, λ, lb, ub, idx_b, idx_a) 73 | amp_post_tmc_XX_mc(m.model_mip,idx_ab, λX, λλ, lb, ub, idx_b, idx_a) 74 | end 75 | 76 | # Partitioning on both variables 77 | if (idx_a in m.var_discretization_mip) && (idx_b in m.var_discretization_mip) 78 | λ = amp_post_tmc_λ(m.model_mip, λ, lb, ub, part_cnt_a, idx_a) 79 | λ = amp_post_tmc_λ(m.model_mip, λ, lb, ub, part_cnt_b, idx_b) 80 | λX = amp_post_tmc_λX(m.model_mip, λX, part_cnt_a, idx_a, idx_b) 81 | λX = amp_post_tmc_λX(m.model_mip, λX, part_cnt_b, idx_b, idx_a) 82 | λλ = amp_post_tmc_λλ(m.model_mip, λλ, part_cnt_a, part_cnt_b, idx_a, idx_b) 83 | amp_post_tmc_λxX_mc(m.model_mip, λX, λ, lb, ub, idx_a, idx_b) 84 | amp_post_tmc_λxX_mc(m.model_mip, λX, λ, lb, ub, idx_b, idx_a) 85 | amp_post_tmc_λxλ_mc(m.model_mip, λλ, λ, idx_a, idx_b) 86 | amp_post_tmc_XX_mc(m.model_mip, idx_ab, λX, λλ, lb, ub, idx_a, idx_b) 87 | end 88 | 89 | # Error condition 90 | if !(idx_a in m.var_discretization_mip) && !(idx_b in m.var_discretization_mip) 91 | error("Error case. At least one term should show up in discretization choices.") 92 | end 93 | end 94 | end 95 | end 96 | end 97 | return 98 | end 99 | 100 | function amp_post_tmc_λ(m::JuMP.Model, λ::Dict, lb::Dict, ub::Dict, dim::Int, idx::Int, relax=false) 101 | if !haskey(λ, idx) 102 | λ[idx] = @variable(m, [1:dim], Bin, basename=string("L",idx)) 103 | @constraint(m, sum(λ[idx]) == 1) # The SOS-1 Constraints, not all MIP solver has SOS feature 104 | @constraint(m, Variable(m, idx) >= dot(lb[idx], λ[idx])) 105 | @constraint(m, Variable(m, idx) <= dot(ub[idx], λ[idx])) 106 | end 107 | return λ 108 | end 109 | 110 | function amp_post_tmc_monomial_mc(m::JuMP.Model, idx_aa::Int, λ::Dict, λX::Dict, LB::Dict, UB::Dict, dim::Int, idx_a::Int) 111 | @constraint(m, Variable(m, idx_aa) >= Variable(m, idx_a)^(2)) 112 | @constraint(m, Variable(m, idx_aa) <= dot(λX[(idx_a,idx_a)],LB[idx_a]) + dot(λX[(idx_a,idx_a)],UB[idx_a]) - dot(λ[idx_a], *(diagm(LB[idx_a]), UB[idx_a]))) 113 | return 114 | end 115 | 116 | function amp_post_tmc_λX(m::JuMP.Model, λX::Dict, dim::Int, idx_a::Int, idx_b::Int) 117 | if !haskey(λX, (idx_a,idx_b)) 118 | λX[(idx_a,idx_b)] = @variable(m, [1:dim], basename=string("L",idx_a,"X",idx_b)) 119 | end 120 | return λX 121 | end 122 | 123 | function amp_post_tmc_λλ(m::JuMP.Model, λλ::Dict, dim_a::Int, dim_b::Int, idx_a::Int, idx_b::Int) 124 | if !haskey(λλ, (idx_a, idx_b)) 125 | λλ[(idx_a,idx_b)] = @variable(m, [1:dim_a, 1:dim_b], basename=string("L",idx_a,"L",idx_b)) 126 | for i in 1:dim_a 127 | for j in 1:dim_b 128 | setlowerbound(λλ[(idx_a, idx_b)][i,j], 0); 129 | setupperbound(λλ[(idx_a, idx_b)][i,j], 1); 130 | end 131 | end 132 | λλ[(idx_b,idx_a)] = λλ[(idx_a,idx_b)]' 133 | end 134 | return λλ 135 | end 136 | 137 | function amp_post_tmc_λλ(m::JuMP.Model, λλ::Dict, λ::Dict, idx_a::Int, idx_b::Int) 138 | if !haskey(λλ, (idx_a, idx_b)) 139 | λλ[(idx_a,idx_b)] = λ[idx_a] 140 | λλ[(idx_a,idx_b)] = reshape(λλ[(idx_a,idx_b)], (length(λ[idx_a]), 1)) 141 | λλ[(idx_b,idx_a)] = λλ[(idx_a,idx_b)]' 142 | end 143 | return λλ 144 | end 145 | 146 | function amp_post_tmc_XX_mc(m, ab, λX, λλ, LB, UB, a, b) 147 | @assert length(LB[a]) == length(UB[a]) 148 | @assert length(LB[b]) == length(UB[b]) 149 | dim_A = length(LB[a]) 150 | dim_B = length(LB[b]) 151 | @constraint(m, Variable(m, ab) .>= dot(λX[(a,b)],LB[a]) + dot(λX[(b,a)],LB[b]) - reshape(LB[a], (1, dim_A))*λλ[(a,b)]*reshape(LB[b], (dim_B, 1))) 152 | @constraint(m, Variable(m, ab) .>= dot(λX[(a,b)],UB[a]) + dot(λX[(b,a)],UB[b]) - reshape(UB[a], (1, dim_A))*λλ[(a,b)]*reshape(UB[b], (dim_B, 1))) 153 | @constraint(m, Variable(m, ab) .<= dot(λX[(a,b)],LB[a]) + dot(λX[(b,a)],UB[b]) - reshape(LB[a], (1, dim_A))*λλ[(a,b)]*reshape(UB[b], (dim_B, 1))) 154 | @constraint(m, Variable(m, ab) .<= dot(λX[(a,b)],UB[a]) + dot(λX[(b,a)],LB[b]) - reshape(UB[a], (1, dim_A))*λλ[(a,b)]*reshape(LB[b], (dim_B, 1))) 155 | return 156 | end 157 | 158 | function amp_post_tmc_λxX_mc(m::JuMP.Model, λX::Dict, λ::Dict, lb::Dict, ub::Dict, ind_λ::Int, ind_X::Int) 159 | 160 | # X_u and λ here are vectors, and X is one variable, 161 | # After the product, we have a polynomial to multiply X, 162 | # forming |λ| new variables stored in λX dictionary 163 | 164 | dim_λ = length(λ[ind_λ]) # This is how many new variables to be generated 165 | for i in 1:dim_λ 166 | lb_X = getlowerbound(Variable(m, ind_X)) 167 | ub_X = getupperbound(Variable(m, ind_X)) 168 | lb_λ = getlowerbound(λ[ind_λ][i]) 169 | ub_λ = getupperbound(λ[ind_λ][i]) 170 | @assert (lb_λ == 0.0) && (ub_λ == 1.0) 171 | mccormick(m, λX[(ind_λ,ind_X)][i], λ[ind_λ][i], Variable(m, ind_X), lb_λ, ub_λ, lb_X, ub_X) 172 | end 173 | return 174 | end 175 | 176 | function amp_post_tmc_λxλ_mc(m::JuMP.Model, λλ::Dict, λ::Dict, ind_A::Int, ind_B::Int) 177 | 178 | #= 179 | A 3 x 2 example, some new variables are formed 180 | | y11y21 y11y22 | | y11 | | y21 |T 181 | | y12y21 y12y22 | = | y12 | x | | 182 | | y13y21 y13y22 | | y13 | | y22 | 183 | =# 184 | 185 | # Prepare the new variable, these two counts track the total count of new variables 186 | dim_A = length(λ[ind_A]) 187 | dim_B = length(λ[ind_B]) 188 | for i in 1:dim_A 189 | for j in 1:dim_B 190 | setlowerbound(λλ[(ind_A,ind_B)][i,j],0) 191 | setupperbound(λλ[(ind_A,ind_B)][i,j],1) 192 | end 193 | end 194 | 195 | for i in 1:dim_A 196 | for j in 1:dim_B 197 | mccormick_bin(m, λλ[(ind_A,ind_B)][i,j], λ[ind_A][i], λ[ind_B][j]) 198 | end 199 | end 200 | 201 | return 202 | end 203 | 204 | """ 205 | mccormick(m::JuMP.Model, xy, x, y, x_l, x_u, y_l, y_u) 206 | 207 | Generic function to add a McCormick convex envelop, where `xy=x*y` and `x_l, x_u, y_l, y_u` are variable bounds. 208 | """ 209 | function mccormick(m,xy,x,y,xˡ,xᵘ,yˡ,yᵘ) 210 | @constraint(m, xy >= xˡ*y + yˡ*x - xˡ*yˡ) 211 | @constraint(m, xy >= xᵘ*y + yᵘ*x - xᵘ*yᵘ) 212 | @constraint(m, xy <= xˡ*y + yᵘ*x - xˡ*yᵘ) 213 | @constraint(m, xy <= xᵘ*y + yˡ*x - xᵘ*yˡ) 214 | return 215 | end 216 | 217 | function mccormick_bin(m,xy,x,y) 218 | 219 | @constraint(m, xy <= x) 220 | @constraint(m, xy <= y) 221 | @constraint(m, xy >= x+y-1) 222 | 223 | return 224 | end 225 | 226 | function mccormick_monomial(m,xy,x,xˡ,xᵘ) 227 | @constraint(m, xy >= x^2) 228 | @constraint(m, xy <= (xˡ+xᵘ)*x - (xˡ*xᵘ)) 229 | return 230 | end 231 | 232 | 233 | 234 | function tightmccormick_monomial(m,x_p,x,xz,xˡ,xᵘ,z,p,lazy,quad) # if p=2, tightened_lazycuts = tightmccormick_quad 235 | if lazy == 1 236 | function GetLazyCuts_quad(cb) 237 | TOL1 = 1e-6 238 | if (getvalue(x)^p > (getvalue(x_p) + TOL1)) 239 | a = p*getvalue(x)^(p-1) 240 | b = (1-p)*getvalue(x)^p 241 | @lazyconstraint(cb, a*x + b <= x_p) 242 | end 243 | end 244 | addlazycallback(m, GetLazyCuts_quad) 245 | elseif p == 2 && quad == 1 246 | @constraint(m, x_p >= x^2) 247 | else 248 | x0_vec = sort(union(xˡ, xᵘ)) 249 | for x0 in x0_vec 250 | @constraint(m, x_p >= (1-p)*(x0)^p + p*(x0)^(p-1)*x) 251 | end 252 | end 253 | 254 | A = ((xᵘ).^p-(xˡ).^p)./(xᵘ-xˡ) 255 | @constraint(m, x_p .<= A'*xz - (A.*xˡ)'*z + ((xˡ).^p)'*z) 256 | 257 | return 258 | end 259 | -------------------------------------------------------------------------------- /examples/blend029.jl: -------------------------------------------------------------------------------- 1 | using POD, JuMP, CPLEX, Ipopt, MathProgBase, AmplNLWriter, CoinOptServices 2 | 3 | function blend029(;verbose=false, solver=nothing, convhull=true, exprmode=1, sos2=true, presolve=0) 4 | 5 | if solver == nothing 6 | m = Model(solver=PODSolver(nlp_local_solver=BonminNLSolver(["bonmin.iteration_limit=100"; "bonmin.nlp_log_level=0"; "bonmin.bb_log_level=0"]), 7 | mip_solver=CplexSolver(CPX_PARAM_SCRIND=1), 8 | presolve_bound_tightening=(presolve>0), 9 | presolve_bound_tightening_algo=presolve, 10 | bilinear_convexhull=false, 11 | monomial_convexhull=convhull, 12 | convhull_formulation_sos2=sos2, 13 | discretization_ratio=8, 14 | # discretization_var_pick_algo="min_vertex_cover", 15 | log_level=1, 16 | rel_gap=0.0001)) 17 | else 18 | m = Model(solver=solver) 19 | end 20 | 21 | @variable(m, x[1:102]) 22 | for i=67:102 23 | setcategory(x[i], :Bin) 24 | end 25 | 26 | for i=1:48 27 | setlowerbound(x[i], 0) 28 | setupperbound(x[i], 1) 29 | end 30 | for i=49:66 31 | setlowerbound(x[i], 0) 32 | setupperbound(x[i], 2) 33 | end 34 | 35 | @objective(m, Max, - 1.74*x[1] - 1.74*x[2] - 1.74*x[3] - 1.45*x[4] - 1.45*x[5] - 1.45*x[6] + 7.38*x[7] + 7.38*x[8] + 7.38*x[9] + 5.6*x[10] + 5.6*x[11] + 5.6*x[12] - 1.7*x[13] - 1.7*x[14] - 1.7*x[15] - 1.18*x[16] - 1.18*x[17] - 1.18*x[18] + 7.21*x[19] + 7.21*x[20] + 7.21*x[21] + 5.45*x[22] + 5.45*x[23] + 5.45*x[24] - 0.3*x[25] - 0.3*x[26] - 0.3*x[27] + 7.71*x[28] + 7.71*x[29] + 7.71*x[30] + 6.28*x[31] + 6.28*x[32] + 6.28*x[33] + 7.74*x[34] + 7.74*x[35] + 7.74*x[36] - 0.84*x[67] - 0.84*x[68] - 0.84*x[69] - 0.05*x[70] - 0.05*x[71] - 0.05*x[72] - 0.94*x[73] - 0.94*x[74] - 0.94*x[75] - 0.81*x[76] - 0.81*x[77] - 0.81*x[78] - 0.79*x[79] - 0.79*x[80] - 0.79*x[81] - 0.05*x[82] - 0.05*x[83] - 0.05*x[84] - 0.65*x[85] - 0.65*x[86] - 0.65*x[87] - 0.97*x[88] - 0.97*x[89] - 0.97*x[90] - 0.57*x[91] - 0.57*x[92] - 0.57*x[93] - 0.26*x[94] - 0.26*x[95] - 0.26*x[96] - 0.45*x[97] - 0.45*x[98] - 0.45*x[99] - 0.1*x[100] - 0.1*x[101] - 0.1*x[102]) 36 | 37 | @NLconstraint(m, x[37]*x[55]-0.6*x[1]-0.2*x[13]+0.2*x[25]+0.2*x[28]+0.2*x[31] ==0.04) #= e8: =# 38 | @NLconstraint(m, x[40]*x[58]-0.6*x[4]-0.2*x[16]-0.2*x[25]+0.7*x[34] ==0.07) #= e9: =# 39 | @NLconstraint(m, x[43]*x[55]-0.4*x[1]-0.4*x[13]+0.5*x[25]+0.5*x[28]+0.5*x[31] ==0.1) #= e10: =# 40 | @NLconstraint(m, x[46]*x[58]-0.4*x[4]-0.4*x[16]-0.5*x[25]+0.6*x[34] ==0.06) #= e11: =# 41 | @NLconstraint(m, x[38]*x[56]-(x[37]*x[55]-(x[37]*x[26]+x[37]*x[29]+x[37]*x[32]))-0.6*x[2]-0.2*x[14] ==0) #= e24: =# 42 | @NLconstraint(m, x[39]*x[57]-(x[38]*x[56]-(x[38]*x[27]+x[38]*x[30]+x[38]*x[33]))-0.6*x[3]-0.2*x[15] ==0) #= e25: =# 43 | @NLconstraint(m, x[41]*x[59]-(x[40]*x[58]+x[37]*x[26]-x[40]*x[35])-0.6*x[5]-0.2*x[17] ==0) #= e26: =# 44 | @NLconstraint(m, x[42]*x[60]-(x[41]*x[59]+x[38]*x[27]-x[41]*x[36])-0.6*x[6]-0.2*x[18] ==0) #= e27: =# 45 | @NLconstraint(m, x[44]*x[56]-(x[43]*x[55]-(x[43]*x[26]+x[43]*x[29]+x[43]*x[32]))-0.4*x[2]-0.4*x[14] ==0) #= e28: =# 46 | @NLconstraint(m, x[45]*x[57]-(x[44]*x[56]-(x[44]*x[27]+x[44]*x[30]+x[44]*x[33]))-0.4*x[3]-0.4*x[15] ==0) #= e29: =# 47 | @NLconstraint(m, x[47]*x[59]-(x[46]*x[58]+x[43]*x[26]-x[46]*x[35])-0.4*x[5]-0.4*x[17] ==0) #= e30: =# 48 | @NLconstraint(m, x[48]*x[60]-(x[47]*x[59]+x[44]*x[27]-x[47]*x[36])-0.4*x[6]-0.4*x[18] ==0) #= e31: =# 49 | 50 | @constraint(m, x[1]+x[4]+x[7]+x[10]+x[49] ==1) #= e2: =# 51 | @constraint(m, x[13]+x[16]+x[19]+x[22]+x[52] ==1.1) #= e3: =# 52 | @constraint(m, -x[1]-x[13]+x[25]+x[28]+x[31]+x[55] ==0.2) #= e4: =# 53 | @constraint(m, -x[4]-x[16]-x[25]+x[34]+x[58] ==0.1) #= e5: =# 54 | @constraint(m, -x[7]-x[19]-x[28]-x[34]+x[61] ==1.55) #= e6: =# 55 | @constraint(m, -x[10]-x[22]-x[31]+x[64] ==0.49) #= e7: =# 56 | @constraint(m, x[2]+x[5]+x[8]+x[11]-x[49]+x[50] ==1) #= e12: =# 57 | @constraint(m, x[3]+x[6]+x[9]+x[12]-x[50]+x[51] ==0) #= e13: =# 58 | @constraint(m, x[14]+x[17]+x[20]+x[23]-x[52]+x[53] ==0.1) #= e14: =# 59 | @constraint(m, x[15]+x[18]+x[21]+x[24]-x[53]+x[54] ==0.9) #= e15: =# 60 | @constraint(m, -x[2]-x[14]+x[26]+x[29]+x[32]-x[55]+x[56] ==0) #= e16: =# 61 | @constraint(m, -x[3]-x[15]+x[27]+x[30]+x[33]-x[56]+x[57] ==0) #= e17: =# 62 | @constraint(m, -x[5]-x[17]-x[26]+x[35]-x[58]+x[59] ==0) #= e18: =# 63 | @constraint(m, -x[6]-x[18]-x[27]+x[36]-x[59]+x[60] ==0) #= e19: =# 64 | @constraint(m, -x[8]-x[20]-x[29]-x[35]-x[61]+x[62] ==-0.81) #= e20: =# 65 | @constraint(m, -x[9]-x[21]-x[30]-x[36]-x[62]+x[63] ==-0.88) #= e21: =# 66 | @constraint(m, -x[11]-x[23]-x[32]-x[64]+x[65] ==-0.14) #= e22: =# 67 | @constraint(m, -x[12]-x[24]-x[33]-x[65]+x[66] ==-0.1) #= e23: =# 68 | @constraint(m, x[1]-x[67]<=0) #= e32: =# 69 | @constraint(m, x[2]-x[68]<=0) #= e33: =# 70 | @constraint(m, x[3]-x[69]<=0) #= e34: =# 71 | @constraint(m, x[4]-x[70]<=0) #= e35: =# 72 | @constraint(m, x[5]-x[71]<=0) #= e36: =# 73 | @constraint(m, x[6]-x[72]<=0) #= e37: =# 74 | @constraint(m, x[7]-x[73]<=0) #= e38: =# 75 | @constraint(m, x[8]-x[74]<=0) #= e39: =# 76 | @constraint(m, x[9]-x[75]<=0) #= e40: =# 77 | @constraint(m, x[10]-x[76]<=0) #= e41: =# 78 | @constraint(m, x[11]-x[77]<=0) #= e42: =# 79 | @constraint(m, x[12]-x[78]<=0) #= e43: =# 80 | @constraint(m, x[13]-x[79]<=0) #= e44: =# 81 | @constraint(m, x[14]-x[80]<=0) #= e45: =# 82 | @constraint(m, x[15]-x[81]<=0) #= e46: =# 83 | @constraint(m, x[16]-x[82]<=0) #= e47: =# 84 | @constraint(m, x[17]-x[83]<=0) #= e48: =# 85 | @constraint(m, x[18]-x[84]<=0) #= e49: =# 86 | @constraint(m, x[19]-x[85]<=0) #= e50: =# 87 | @constraint(m, x[20]-x[86]<=0) #= e51: =# 88 | @constraint(m, x[21]-x[87]<=0) #= e52: =# 89 | @constraint(m, x[22]-x[88]<=0) #= e53: =# 90 | @constraint(m, x[23]-x[89]<=0) #= e54: =# 91 | @constraint(m, x[24]-x[90]<=0) #= e55: =# 92 | @constraint(m, x[25]-x[91]<=0) #= e56: =# 93 | @constraint(m, x[26]-x[92]<=0) #= e57: =# 94 | @constraint(m, x[27]-x[93]<=0) #= e58: =# 95 | @constraint(m, x[28]-x[94]<=0) #= e59: =# 96 | @constraint(m, x[29]-x[95]<=0) #= e60: =# 97 | @constraint(m, x[30]-x[96]<=0) #= e61: =# 98 | @constraint(m, x[31]-x[97]<=0) #= e62: =# 99 | @constraint(m, x[32]-x[98]<=0) #= e63: =# 100 | @constraint(m, x[33]-x[99]<=0) #= e64: =# 101 | @constraint(m, x[34]-x[100]<=0) #= e65: =# 102 | @constraint(m, x[35]-x[101]<=0) #= e66: =# 103 | @constraint(m, x[36]-x[102]<=0) #= e67: =# 104 | @constraint(m, x[1]>=0) #= e68: =# 105 | @constraint(m, x[2]>=0) #= e69: =# 106 | @constraint(m, x[3]>=0) #= e70: =# 107 | @constraint(m, x[4]>=0) #= e71: =# 108 | @constraint(m, x[5]>=0) #= e72: =# 109 | @constraint(m, x[6]>=0) #= e73: =# 110 | @constraint(m, x[7]>=0) #= e74: =# 111 | @constraint(m, x[8]>=0) #= e75: =# 112 | @constraint(m, x[9]>=0) #= e76: =# 113 | @constraint(m, x[10]>=0) #= e77: =# 114 | @constraint(m, x[11]>=0) #= e78: =# 115 | @constraint(m, x[12]>=0) #= e79: =# 116 | @constraint(m, x[13]>=0) #= e80: =# 117 | @constraint(m, x[14]>=0) #= e81: =# 118 | @constraint(m, x[15]>=0) #= e82: =# 119 | @constraint(m, x[16]>=0) #= e83: =# 120 | @constraint(m, x[17]>=0) #= e84: =# 121 | @constraint(m, x[18]>=0) #= e85: =# 122 | @constraint(m, x[19]>=0) #= e86: =# 123 | @constraint(m, x[20]>=0) #= e87: =# 124 | @constraint(m, x[21]>=0) #= e88: =# 125 | @constraint(m, x[22]>=0) #= e89: =# 126 | @constraint(m, x[23]>=0) #= e90: =# 127 | @constraint(m, x[24]>=0) #= e91: =# 128 | @constraint(m, x[25]>=0) #= e92: =# 129 | @constraint(m, x[26]>=0) #= e93: =# 130 | @constraint(m, x[27]>=0) #= e94: =# 131 | @constraint(m, x[28]>=0) #= e95: =# 132 | @constraint(m, x[29]>=0) #= e96: =# 133 | @constraint(m, x[30]>=0) #= e97: =# 134 | @constraint(m, x[31]>=0) #= e98: =# 135 | @constraint(m, x[32]>=0) #= e99: =# 136 | @constraint(m, x[33]>=0) #= e100: =# 137 | @constraint(m, x[34]>=0) #= e101: =# 138 | @constraint(m, x[35]>=0) #= e102: =# 139 | @constraint(m, x[36]>=0) #= e103: =# 140 | @constraint(m, x[73]<=1.5) #= e104: =# 141 | @constraint(m, x[74]<=1.5) #= e105: =# 142 | @constraint(m, x[75]<=1.5) #= e106: =# 143 | @constraint(m, x[76]<=0.6) #= e107: =# 144 | @constraint(m, x[77]<=0.6) #= e108: =# 145 | @constraint(m, x[78]<=0.6) #= e109: =# 146 | @constraint(m, x[85]<=1.1) #= e110: =# 147 | @constraint(m, x[86]<=1.1) #= e111: =# 148 | @constraint(m, x[87]<=1.1) #= e112: =# 149 | @constraint(m, x[88]<=0.2) #= e113: =# 150 | @constraint(m, x[89]<=0.2) #= e114: =# 151 | @constraint(m, x[90]<=0.2) #= e115: =# 152 | @constraint(m, x[73]<=1) #= e116: =# 153 | @constraint(m, x[74]<=1) #= e117: =# 154 | @constraint(m, x[75]<=1) #= e118: =# 155 | @constraint(m, x[76]<=0.8) #= e119: =# 156 | @constraint(m, x[77]<=0.8) #= e120: =# 157 | @constraint(m, x[78]<=0.8) #= e121: =# 158 | @constraint(m, x[85]<=1) #= e122: =# 159 | @constraint(m, x[86]<=1) #= e123: =# 160 | @constraint(m, x[87]<=1) #= e124: =# 161 | @constraint(m, x[88]<=0.8) #= e125: =# 162 | @constraint(m, x[89]<=0.8) #= e126: =# 163 | @constraint(m, x[90]<=0.8) #= e127: =# 164 | @constraint(m, -x[73]>=-1.3) #= e128: =# 165 | @constraint(m, -x[74]>=-1.3) #= e129: =# 166 | @constraint(m, -x[75]>=-1.3) #= e130: =# 167 | @constraint(m, -x[76]>=-1.4) #= e131: =# 168 | @constraint(m, -x[77]>=-1.4) #= e132: =# 169 | @constraint(m, -x[78]>=-1.4) #= e133: =# 170 | @constraint(m, -x[85]>=-1.7) #= e134: =# 171 | @constraint(m, -x[86]>=-1.7) #= e135: =# 172 | @constraint(m, -x[87]>=-1.7) #= e136: =# 173 | @constraint(m, -x[88]>=-1.8) #= e137: =# 174 | @constraint(m, -x[89]>=-1.8) #= e138: =# 175 | @constraint(m, -x[90]>=-1.8) #= e139: =# 176 | @constraint(m, -x[73]>=-1) #= e140: =# 177 | @constraint(m, -x[74]>=-1) #= e141: =# 178 | @constraint(m, -x[75]>=-1) #= e142: =# 179 | @constraint(m, -x[76]>=-1.4) #= e143: =# 180 | @constraint(m, -x[77]>=-1.4) #= e144: =# 181 | @constraint(m, -x[78]>=-1.4) #= e145: =# 182 | @constraint(m, -x[85]>=-1) #= e146: =# 183 | @constraint(m, -x[86]>=-1) #= e147: =# 184 | @constraint(m, -x[87]>=-1) #= e148: =# 185 | @constraint(m, -x[88]>=-1.4) #= e149: =# 186 | @constraint(m, -x[89]>=-1.4) #= e150: =# 187 | @constraint(m, -x[90]>=-1.4) #= e151: =# 188 | @constraint(m, -x[37]+x[95]<=0.9) #= e152: =# 189 | @constraint(m, -x[38]+x[96]<=0.9) #= e153: =# 190 | @constraint(m, -x[37]+x[98]<=0) #= e154: =# 191 | @constraint(m, -x[38]+x[99]<=0) #= e155: =# 192 | @constraint(m, -x[40]+x[101]<=0.9) #= e156: =# 193 | @constraint(m, -x[41]+x[102]<=0.9) #= e157: =# 194 | @constraint(m, -x[43]+x[95]<=0.6) #= e158: =# 195 | @constraint(m, -x[44]+x[96]<=0.6) #= e159: =# 196 | @constraint(m, -x[43]+x[98]<=0.4) #= e160: =# 197 | @constraint(m, -x[44]+x[99]<=0.4) #= e161: =# 198 | @constraint(m, -x[46]+x[101]<=0.6) #= e162: =# 199 | @constraint(m, -x[47]+x[102]<=0.6) #= e163: =# 200 | @constraint(m, -x[37]-x[95]>=-1.9) #= e164: =# 201 | @constraint(m, -x[38]-x[96]>=-1.9) #= e165: =# 202 | @constraint(m, -x[37]-x[98]>=-2) #= e166: =# 203 | @constraint(m, -x[38]-x[99]>=-2) #= e167: =# 204 | @constraint(m, -x[40]-x[101]>=-1.9) #= e168: =# 205 | @constraint(m, -x[41]-x[102]>=-1.9) #= e169: =# 206 | @constraint(m, -x[43]-x[95]>=-1.4) #= e170: =# 207 | @constraint(m, -x[44]-x[96]>=-1.4) #= e171: =# 208 | @constraint(m, -x[43]-x[98]>=-1.8) #= e172: =# 209 | @constraint(m, -x[44]-x[99]>=-1.8) #= e173: =# 210 | @constraint(m, -x[46]-x[101]>=-1.4) #= e174: =# 211 | @constraint(m, -x[47]-x[102]>=-1.4) #= e175: =# 212 | @constraint(m, x[94]<=1.1) #= e176: =# 213 | @constraint(m, x[97]<=0.2) #= e177: =# 214 | @constraint(m, x[100]<=1.6) #= e178: =# 215 | @constraint(m, x[94]<=1.1) #= e179: =# 216 | @constraint(m, x[97]<=0.9) #= e180: =# 217 | @constraint(m, x[100]<=1.2) #= e181: =# 218 | @constraint(m, -x[94]>=-1.7) #= e182: =# 219 | @constraint(m, -x[97]>=-1.8) #= e183: =# 220 | @constraint(m, -x[100]>=-1.2) #= e184: =# 221 | @constraint(m, -x[94]>=-0.9) #= e185: =# 222 | @constraint(m, -x[97]>=-1.3) #= e186: =# 223 | @constraint(m, -x[100]>=-0.8) #= e187: =# 224 | @constraint(m, x[67]+x[91]<=1) #= e188: =# 225 | @constraint(m, x[68]+x[92]<=1) #= e189: =# 226 | @constraint(m, x[69]+x[93]<=1) #= e190: =# 227 | @constraint(m, x[67]+x[94]<=1) #= e191: =# 228 | @constraint(m, x[68]+x[95]<=1) #= e192: =# 229 | @constraint(m, x[69]+x[96]<=1) #= e193: =# 230 | @constraint(m, x[67]+x[97]<=1) #= e194: =# 231 | @constraint(m, x[68]+x[98]<=1) #= e195: =# 232 | @constraint(m, x[69]+x[99]<=1) #= e196: =# 233 | @constraint(m, x[79]+x[91]<=1) #= e197: =# 234 | @constraint(m, x[80]+x[92]<=1) #= e198: =# 235 | @constraint(m, x[81]+x[93]<=1) #= e199: =# 236 | @constraint(m, x[79]+x[94]<=1) #= e200: =# 237 | @constraint(m, x[80]+x[95]<=1) #= e201: =# 238 | @constraint(m, x[81]+x[96]<=1) #= e202: =# 239 | @constraint(m, x[79]+x[97]<=1) #= e203: =# 240 | @constraint(m, x[80]+x[98]<=1) #= e204: =# 241 | @constraint(m, x[81]+x[99]<=1) #= e205: =# 242 | @constraint(m, x[70]+x[100]<=1) #= e206: =# 243 | @constraint(m, x[71]+x[101]<=1) #= e207: =# 244 | @constraint(m, x[72]+x[102]<=1) #= e208: =# 245 | @constraint(m, x[82]+x[100]<=1) #= e209: =# 246 | @constraint(m, x[83]+x[101]<=1) #= e210: =# 247 | @constraint(m, x[84]+x[102]<=1) #= e211: =# 248 | @constraint(m, x[91]+x[100]<=1) #= e212: =# 249 | @constraint(m, x[92]+x[101]<=1) #= e213: =# 250 | @constraint(m, x[93]+x[102]<=1) #= e214: =# 251 | 252 | if verbose 253 | print(m) 254 | end 255 | 256 | return m 257 | end 258 | -------------------------------------------------------------------------------- /src/bounds.jl: -------------------------------------------------------------------------------- 1 | """ 2 | initialize_tight_bounds(m::PODNonlinearModel) 3 | 4 | Initial tight bound vectors for later operations 5 | """ 6 | function initialize_tight_bounds(m::PODNonlinearModel) 7 | 8 | m.l_var_tight = [m.l_var_orig,fill(-Inf, m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip);] 9 | m.u_var_tight = [m.u_var_orig,fill(Inf, m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip);] 10 | for i in 1:m.num_var_orig 11 | if m.var_type_orig[i] == :Bin 12 | m.l_var_tight[i] = 0.0 13 | m.u_var_tight[i] = 1.0 14 | end 15 | end 16 | 17 | return 18 | end 19 | 20 | 21 | """ 22 | detect_bound_from_aff(m::PODNonlinearModel) 23 | 24 | Detect bounds from parse affine constraint. This function examines the one variable constraints such as 25 | x >= 5, x <= 5 or x == 5 and fetch the information to m.l_var_tight and m.u_var_tight. 26 | This function can potential grow to be smarter. 27 | """ 28 | function bounds_propagation(m::PODNonlinearModel) 29 | 30 | exhausted = false 31 | while !exhausted 32 | exhausted = true 33 | for aff in m.bounding_constr_mip 34 | for i in 1:length(aff[:vars]) 35 | var_idx = aff[:vars][i].args[2] 36 | var_coef = aff[:coefs][i] 37 | @assert (isa(var_idx, Float64) || isa(var_idx, Int)) 38 | if aff[:sense] == :(==) && var_coef > 0.0 39 | eval_l_bound = aff[:rhs] / var_coef 40 | eval_u_bound = aff[:rhs] / var_coef 41 | for j in 1:length(aff[:vars]) 42 | if j != i && aff[:coefs][j]*var_coef > 0.0 # same sign 43 | (eval_l_bound != -Inf) && (eval_l_bound -= abs(aff[:coefs][j]/var_coef)*m.u_var_tight[aff[:vars][j].args[2]]) 44 | (eval_u_bound != Inf) && (eval_u_bound -= abs(aff[:coefs][j]/var_coef)*m.l_var_tight[aff[:vars][j].args[2]]) 45 | elseif j!= i && aff[:coefs][j]*var_coef < 0.0 # different sign 46 | (eval_l_bound != -Inf) && (eval_l_bound += abs(aff[:coefs][j]/var_coef)*m.l_var_tight[aff[:vars][j].args[2]]) 47 | (eval_u_bound != Inf) && (eval_u_bound += abs(aff[:coefs][j]/var_coef)*m.u_var_tight[aff[:vars][j].args[2]]) 48 | end 49 | end 50 | if eval_l_bound > m.l_var_tight[var_idx] + m.tol 51 | exhausted = false 52 | m.l_var_tight[var_idx] = eval_l_bound 53 | (m.log_level > 99) && println("[VAR$(var_idx)] Lower bound $(m.l_var_tight[var_idx]) evaluated from constraints") 54 | end 55 | if eval_u_bound < m.u_var_tight[var_idx] - m.tol 56 | exhausted = false 57 | m.u_var_tight[var_idx] = eval_u_bound 58 | (m.log_level > 99) && println("[VAR$(var_idx)] Upper bound $(m.u_var_tight[var_idx]) evaluated from constraints") 59 | end 60 | elseif aff[:sense] == :(>=) && var_coef > 0.0 # a($) - by + cz >= 100, y∈[1,10], z∈[2,50], a,b,c > 0 61 | eval_bound = aff[:rhs] / var_coef 62 | for j in 1:length(aff[:vars]) 63 | if j != i && aff[:coefs][j] > 0.0 64 | eval_bound -= abs(aff[:coefs][j]/var_coef) * m.u_var_tight[aff[:vars][j].args[2]] 65 | elseif j !=i aff[:coefs][j] < 0.0 66 | eval_bound += abs(aff[:coefs][j]/var_coef) * m.l_var_tight[aff[:vars][j].args[2]] 67 | end 68 | (eval_bound == -Inf) && break 69 | end 70 | if eval_bound > m.l_var_tight[var_idx] + m.tol 71 | exhausted = false 72 | m.l_var_tight[var_idx] = eval_bound 73 | (m.log_level > 99) && println("[VAR$(var_idx)] Lower bound $(m.l_var_tight[var_idx]) evaluated from constraints") 74 | end 75 | elseif var_coef < 0.0 && aff[:sense] == :(>=) # -a($) - by + cz >= 100, y∈[1,10], z∈[2,50], a,b,c > 0 76 | eval_bound = aff[:rhs] / var_coef 77 | for j in 1:length(aff[:vars]) 78 | if j != i && aff[:coefs][j] > 0.0 79 | eval_bound += abs(aff[:coefs][j]/var_coef) * m.u_var_tight[aff[:vars][j].args[2]] 80 | elseif j != i && aff[:coefs][j] < 0.0 81 | eval_bound -= abs(aff[:coefs][j]/var_coef) * m.l_var_tight[aff[:vars][j].args[2]] 82 | end 83 | (eval_bound == Inf) && break 84 | end 85 | if eval_bound < m.u_var_tight[var_idx] - m.tol 86 | exhausted = false 87 | m.u_var_tight[var_idx] = eval_bound 88 | (m.log_level > 99) && println("[VAR$(var_idx)] Upper bound $(m.u_var_tight[var_idx]) evaluated from constraints") 89 | end 90 | elseif (aff[:sense] == :(<=) && aff[:coefs][i] > 0.0) # a($) - by + cz <= 100, y∈[1,10], z∈[2,50], a,b,c > 0 91 | eval_bound = aff[:rhs] / var_coef 92 | for j in 1:length(aff[:vars]) 93 | if j != i && aff[:coefs][j] > 0.0 94 | eval_bound -= abs(aff[:coefs][j]/var_coef) * m.l_var_tight[aff[:vars][j].args[2]] 95 | elseif j != i && aff[:coefs][j] < 0.0 96 | eval_bound += abs(aff[:coefs][j]/var_coef) * m.u_var_tight[aff[:vars][j].args[2]] 97 | end 98 | (eval_bound == Inf) && break 99 | end 100 | if eval_bound < m.u_var_tight[var_idx] - m.tol 101 | exhausted = false 102 | m.u_var_tight[var_idx] = eval_bound 103 | (m.log_level > 99) && println("[VAR$(var_idx)] Upper bound $(m.u_var_tight[var_idx]) evaluated from constraints") 104 | end 105 | elseif (aff[:sense] == :(<=) && aff[:coefs][i] < 0.0) # -a($) - by + cz <= 100, y∈[1,10], z∈[2,50], a,b,c > 0 106 | eval_bound = aff[:rhs] / var_coef 107 | for j in 1:length(aff[:vars]) 108 | if j != i && aff[:coefs][j] > 0.0 109 | eval_bound += abs(aff[:coefs][j]/var_coef) * m.l_var_tight[aff[:vars][j].args[2]] 110 | elseif j != i && aff[:coefs][j] < 0.0 111 | eval_bound -= abs(aff[:coefs][j]/var_coef) * m.u_var_tight[aff[:vars][j].args[2]] 112 | end 113 | (eval_bound == -Inf) && break 114 | end 115 | if eval_bound > m.l_var_tight[var_idx] + m.tol 116 | exhausted = false 117 | m.l_var_tight[var_idx] = eval_bound 118 | (m.log_level > 99) && println("[VAR$(var_idx)] Lower bound $(m.l_var_tight[var_idx]) evaluated from constraints") 119 | end 120 | end 121 | end 122 | end 123 | (exhausted == true && m.log_level > 99) && println("Initial constraint-based bound evaluation exhausted...") 124 | end 125 | 126 | return 127 | end 128 | 129 | 130 | """ 131 | resolve_lifted_var_bounds(m::PODNonlinearModel) 132 | 133 | Resolve the bounds of the lifted variable using the information in l_var_tight and u_var_tight. This method only takes 134 | in known or trivial bounds information to reason lifted variable bound to avoid the cases of infinity bounds. 135 | """ 136 | function resolve_lifted_var_bounds(m::PODNonlinearModel) 137 | 138 | # Added sequential bound resolving process base on DFS process, which ensures all bounds are secured. 139 | # Increased complexity from linear to square but a reasonable amount 140 | # Potentially, additional mapping can be applied to reduce the complexity 141 | # TODO: need to consider negative values 142 | for i in 1:length(m.nonlinear_terms) 143 | for bi in keys(m.nonlinear_terms) 144 | if (m.nonlinear_terms[bi][:id]) == i && (m.nonlinear_terms[bi][:nonlinear_type] in [:bilinear, :monomial, :multilinear]) 145 | lifted_idx = m.nonlinear_terms[bi][:lifted_var_ref].args[2] 146 | cnt = 0 147 | bound = [] 148 | for var in bi 149 | cnt += 1 150 | var_idx = var.args[2] 151 | var_bounds = [m.l_var_tight[var_idx], m.u_var_tight[var_idx]] 152 | if cnt == 1 153 | bound = copy(var_bounds) 154 | elseif cnt == 2 155 | bound = bound * var_bounds' 156 | else 157 | bound = diag(bound) * var_bounds' 158 | end 159 | end 160 | if minimum(bound) > m.l_var_tight[lifted_idx] + m.tol 161 | m.l_var_tight[lifted_idx] = minimum(bound) 162 | end 163 | if maximum(bound) < m.u_var_tight[lifted_idx] - m.tol 164 | m.u_var_tight[lifted_idx] = maximum(bound) 165 | end 166 | end 167 | end 168 | end 169 | 170 | # Resolve bounds for lifted linear terms 171 | for i in keys(m.linear_terms) 172 | lifted_idx = m.linear_terms[i][:y_idx] 173 | ub = 0.0 174 | lb = 0.0 175 | for j in m.linear_terms[i][:ref][:coef_var] 176 | (j[1] > 0.0) ? ub += abs(j[1])*m.u_var_tight[j[2]] : ub -= abs(j[1])*m.l_var_tight[j[2]] 177 | (j[1] > 0.0) ? lb += abs(j[1])*m.l_var_tight[j[2]] : lb -= abs(j[1])*m.u_var_tight[j[2]] 178 | end 179 | lb += m.linear_terms[i][:ref][:scalar] 180 | ub += m.linear_terms[i][:ref][:scalar] 181 | (lb > m.l_var_tight[lifted_idx] + m.tol) && (m.l_var_tight[lifted_idx] = lb) 182 | (ub < m.u_var_tight[lifted_idx] - m.tol) && (m.u_var_tight[lifted_idx] = ub) 183 | end 184 | 185 | return 186 | end 187 | 188 | """ 189 | resolve_lifted_var_bounds(nonlinear_terms::Dict, discretization::Dict) 190 | 191 | For discretization to be performed, we do not allow for a variable being discretized to have infinite bounds. 192 | The lifted variables will have infinite bounds and the function infers bounds on these variables. This process 193 | can help speed up the subsequent solve in subsequent iterations. 194 | """ 195 | function resolve_lifted_var_bounds(nonlinear_terms::Dict, linear_terms::Dict, discretization::Dict; kwargs...) 196 | 197 | # Added sequential bound resolving process base on DFS process, which ensures all bounds are secured. 198 | # Increased complexity from linear to square but a reasonable amount 199 | # Potentially, additional mapping can be applied to reduce the complexity 200 | for i in 1:length(nonlinear_terms) 201 | for bi in keys(nonlinear_terms) 202 | if (nonlinear_terms[bi][:id] == i) && (nonlinear_terms[bi][:nonlinear_type] in [:bilinear, :monomial, :multilinear]) 203 | lifted_idx = nonlinear_terms[bi][:lifted_var_ref].args[2] 204 | cnt = 0 205 | bound = [] 206 | for var in bi 207 | cnt += 1 208 | var_idx = var.args[2] 209 | var_bounds = [discretization[var_idx][1], discretization[var_idx][end]] 210 | if cnt == 1 211 | bound = copy(var_bounds) 212 | elseif cnt == 2 213 | bound = bound * var_bounds' 214 | else 215 | bound = diag(bound) * var_bounds' 216 | end 217 | end 218 | if minimum(bound) > discretization[lifted_idx][1] 219 | discretization[lifted_idx][1] = minimum(bound) 220 | end 221 | if maximum(bound) < discretization[lifted_idx][end] 222 | discretization[lifted_idx][end] = maximum(bound) 223 | end 224 | end 225 | end 226 | end 227 | 228 | # Resolve bounds for lifted linear terms 229 | for i in keys(linear_terms) 230 | lifted_idx = linear_terms[i][:y_idx] 231 | ub = 0.0 232 | lb = 0.0 233 | for j in linear_terms[i][:ref][:coef_var] 234 | (j[1] > 0.0) ? ub += abs(j[1])*discretization[j[2]][end] : ub -= abs(j[1])*discretization[j[2]][1] 235 | (j[1] > 0.0) ? lb += abs(j[1])*discretization[j[2]][1] : lb -= abs(j[1])*discretization[j[2]][end] 236 | end 237 | lb += linear_terms[i][:ref][:scalar] 238 | ub += linear_terms[i][:ref][:scalar] 239 | (lb > discretization[lifted_idx][1] + m.tol) && (discretization[lifted_idx][1] = lb) 240 | (ub < discretization[lifted_idx][end] - m.tol) && (discretization[lifted_idx][end] = ub) 241 | end 242 | 243 | return discretization 244 | end 245 | 246 | """ 247 | resolve_closed_var_bounds(m::PODNonlinearModel) 248 | 249 | This function seeks variable with tight bounds (by presolve_bt_width_tol) by checking .l_var_tight and .u_var_tight. 250 | If a variable is found to be within a sufficiently small interval then no discretization will be performed on this variable 251 | and the .discretization will be cleared with the tight bounds for basic McCormick operation if necessary. 252 | 253 | """ 254 | function resolve_closed_var_bounds(m::PODNonlinearModel; kwargs...) 255 | 256 | for var in m.all_nonlinear_vars 257 | if abs(m.l_var_tight[var] - m.u_var_tight[var]) < m.presolve_bt_width_tol # Closed Bound Criteria 258 | deleteat!(m.var_discretization_mip, findfirst(m.var_discretization_mip, var)) # Clean nonlinear_terms by deleting the info 259 | m.discretization[var] = [m.l_var_tight[var], m.u_var_tight[var]] # Clean up the discretization for basic McCormick if necessary 260 | end 261 | end 262 | 263 | return 264 | end 265 | 266 | """ 267 | update_var_bounds(m::PODNonlinearModel, discretization::Dict; len::Float64=length(keys(discretization))) 268 | 269 | This function take in a dictionary-based discretization information and convert them into two bounds vectors (l_var, u_var) by picking the smallest and largest numbers. User can specify a certain length that may contains variables that is out of the scope of discretization. 270 | 271 | Output:: 272 | 273 | l_var::Vector{Float64}, u_var::Vector{Float64} 274 | """ 275 | function update_var_bounds(discretization; kwargs...) 276 | 277 | options = Dict(kwargs) 278 | 279 | haskey(options, :len) ? len = options[:len] : len = length(keys(discretization)) 280 | 281 | l_var = fill(-Inf, len) 282 | u_var = fill(Inf, len) 283 | 284 | for var_idx in keys(discretization) 285 | l_var[var_idx] = discretization[var_idx][1] 286 | u_var[var_idx] = discretization[var_idx][end] 287 | end 288 | 289 | return l_var, u_var 290 | end 291 | -------------------------------------------------------------------------------- /src/multi.jl: -------------------------------------------------------------------------------- 1 | #=====================----- Developer's note's -----=====================# 2 | # Code used to use convexhull representation to convexify the non-convex model. 3 | #========================================================================# 4 | function amp_post_convhull(m::PODNonlinearModel; kwargs...) 5 | 6 | options = Dict(kwargs) 7 | haskey(options, :use_discretization) ? discretization = options[:use_discretization] : discretization = m.discretization 8 | 9 | # Variable holders 10 | λ = Dict() # Extreme points and multipliers 11 | α = Dict() # Partitioning Variables 12 | 13 | # Construct λ variable space 14 | for bi in keys(m.nonlinear_terms) 15 | nl_type = m.nonlinear_terms[bi][:nonlinear_type] 16 | if ((nl_type == :multilinear) || (nl_type == :bilinear)) && (m.nonlinear_terms[bi][:convexified] == false) 17 | m.nonlinear_terms[bi][:convexified] = true # Bookeeping the examined terms 18 | ml_indices, dim, extreme_point_cnt = amp_convhull_prepare(discretization, bi) # convert key to easy read mode 19 | λ = amp_convhull_λ(m, bi, ml_indices, λ, extreme_point_cnt, dim) 20 | λ = populate_convhull_extreme_values(m, discretization, ml_indices, λ, dim, ones(Int,length(dim))) 21 | α = amp_convhull_α(m, ml_indices, α, dim, discretization) 22 | amp_post_convhull_constrs(m, λ, α, ml_indices, dim, extreme_point_cnt, discretization) 23 | elseif (nl_type == :monomial) && (m.nonlinear_terms[bi][:convexified] == false) 24 | m.nonlinear_terms[bi][:convexified] = true 25 | monomial_index, dim, extreme_point_cnt = amp_convhull_prepare(discretization, bi, monomial=true) 26 | λ = amp_convhull_λ(m, bi, monomial_index, λ, extreme_point_cnt, dim) 27 | λ = populate_convhull_extreme_values(m, discretization, monomial_index, λ) 28 | α = amp_convhull_α(m, [monomial_index], α, dim, discretization) 29 | amp_post_convhull_constrs(m, λ, α, monomial_index, dim, discretization) 30 | end 31 | end 32 | 33 | return 34 | end 35 | 36 | """ 37 | TODO: docstring 38 | This function is very important. 39 | """ 40 | function amp_convhull_prepare(discretization::Dict, nonlinear_key::Any; monomial=false) 41 | 42 | id = Set() # Coverting the nonlinear indices into a different space 43 | for var in nonlinear_key # This output regulates the sequence of how composing variable should be arranged 44 | push!(id, var.args[2]) 45 | end 46 | 47 | if length(id) < length(nonlinear_key) # Got repeating terms, now the sequence matters 48 | id = [] 49 | for var in nonlinear_key 50 | push!(id, var.args[2]) 51 | end 52 | end 53 | 54 | dim = [] 55 | for i in id 56 | push!(dim, length(discretization[i])) 57 | end 58 | 59 | monomial && return id[1], tuple(dim[1]), dim[1] # One less dimension is required 60 | 61 | return id, tuple([i for i in dim]...), prod(dim) 62 | end 63 | 64 | function amp_convhull_λ(m::PODNonlinearModel, nonlinear_key::Any, ml_indices::Any, λ::Dict, extreme_point_cnt::Int, dim::Tuple; kwargs...) 65 | 66 | lifted_var_idx = m.nonlinear_terms[nonlinear_key][:lifted_var_ref].args[2] 67 | @assert !(lifted_var_idx in keys(λ)) 68 | λ[ml_indices] = Dict(:dim=>dim, 69 | :lifted_var_idx=>lifted_var_idx, 70 | :indices=>reshape([1:extreme_point_cnt;], dim), 71 | :vars=>@variable(m.model_mip, [1:extreme_point_cnt], lowerbound=0, upperbound=1, basename="L$(lifted_var_idx)"), 72 | :vals=>ones(dim)) 73 | 74 | return λ 75 | end 76 | 77 | """ 78 | TODO: docstring 79 | method for monomial 80 | """ 81 | function populate_convhull_extreme_values(m::PODNonlinearModel, discretization::Dict, monomial_index::Int, λ::Dict) 82 | λ[monomial_index][:vals] = [discretization[monomial_index][i]^2 for i in 1:length(discretization[monomial_index])] 83 | return λ 84 | end 85 | 86 | """ 87 | TODO: docstring 88 | method for general multilinear term 89 | """ 90 | function populate_convhull_extreme_values(m::PODNonlinearModel, discretization::Dict, ml_indices::Any, λ::Dict, dim::Tuple, locator::Array, level::Int=1) 91 | 92 | if level > length(dim) 93 | @assert length(ml_indices) == length(dim) 94 | @assert length(ml_indices) == length(locator) 95 | val = 1.0 96 | k = 0 97 | for i in ml_indices 98 | k += 1 99 | val *= discretization[i][locator[k]] # Calculate extreme point z-value 100 | end 101 | λ[ml_indices][:vals][CartesianIndex(tuple([i for i in locator]...))] = val # Value assignment 102 | return λ # finished with last dimension 103 | else 104 | for i in 1:dim[level] 105 | locator[level] = i 106 | λ = populate_convhull_extreme_values(m, discretization, ml_indices, λ, dim, locator, level+1) 107 | end 108 | end 109 | 110 | return λ 111 | end 112 | 113 | """ 114 | Less memeory & time efficient, but a easier implementation 115 | """ 116 | function _populate_convhull_extreme_values(discretization::Dict, ml_indices::Any, λ::Dict, extreme_point_cnt::Int) 117 | 118 | var_indices = collect(ml_indices) 119 | for i in 1:extreme_point_cnt 120 | sub = ind2sub(λ[ml_indices][:indices], i) 121 | k = 0 122 | for var in ml_indices 123 | k += 1 124 | @assert var == var_indices[k] 125 | λ[ml_indices][:vals][CartesianIndex(sub)] *= discretization[var][sub[k]] 126 | end 127 | end 128 | 129 | return λ 130 | end 131 | 132 | function amp_convhull_α(m::PODNonlinearModel, ml_indices::Any, α::Dict, dim::Tuple, discretization::Dict; kwargs...) 133 | 134 | for i in ml_indices 135 | if !(i in keys(α)) 136 | if m.convhull_formulation_sos2aux 137 | intersect_cnt = length(discretization[i]) 138 | α[i] = @variable(m.model_mip, [1:intersect_cnt], lowerbound=0.0, upperbound=1.0, basename="A$(i)") 139 | addSOS2(m.model_mip, α[i]) 140 | else 141 | partition_cnt = length(discretization[i]) - 1 142 | α[i] = @variable(m.model_mip, [1:partition_cnt], Bin, basename="A$(i)") 143 | @constraint(m.model_mip, sum(α[i]) == 1) 144 | end 145 | end 146 | end 147 | 148 | return α 149 | end 150 | 151 | function amp_post_convhull_constrs(m::PODNonlinearModel, λ::Dict, α::Dict, ml_indices::Any, dim::Tuple, extreme_point_cnt::Int, discretization::Dict) 152 | 153 | # Adding λ constraints 154 | @constraint(m.model_mip, sum(λ[ml_indices][:vars]) == 1) 155 | @constraint(m.model_mip, Variable(m.model_mip, λ[ml_indices][:lifted_var_idx]) == dot(λ[ml_indices][:vars], reshape(λ[ml_indices][:vals], extreme_point_cnt))) 156 | 157 | cnt = 0 158 | for i in ml_indices 159 | cnt += 1 160 | valid_inequalities(m, discretization, λ, α, ml_indices, dim, i, cnt) # Add links between λ and α 161 | if m.convhull_formulation_sos2aux 162 | @constraint(m.model_mip, Variable(m.model_mip, i) == dot(α[i], discretization[i])) 163 | else 164 | partition_cnt = length(α[i]) 165 | lambda_cnt = length(discretization[i]) 166 | @assert lambda_cnt == partition_cnt + 1 167 | sliced_indices = [collect_indices(λ[ml_indices][:indices], cnt, [k], dim) for k in 1:lambda_cnt] # Add x = f(λ) for convex representation of x value 168 | @constraint(m.model_mip, Variable(m.model_mip, i) == sum(dot(repmat([discretization[i][k]],length(sliced_indices[k])), λ[ml_indices][:vars][sliced_indices[k]]) for k in 1:lambda_cnt)) 169 | 170 | @constraint(m.model_mip, Variable(m.model_mip, i) >= sum(α[i][j]*discretization[i][j] for j in 1:lambda_cnt-1)) # Add x = f(α) for regulating the domains 171 | @constraint(m.model_mip, Variable(m.model_mip, i) <= sum(α[i][j-1]*discretization[i][j] for j in 2:lambda_cnt)) 172 | end 173 | end 174 | 175 | return 176 | end 177 | 178 | function amp_post_convhull_constrs(m::PODNonlinearModel, λ::Dict, α::Dict, monomial_idx::Int, dim::Tuple, discretization::Dict) 179 | 180 | partition_cnt = length(α[monomial_idx]) 181 | lambda_cnt = length(discretization[monomial_idx]) 182 | @assert lambda_cnt == partition_cnt + 1 183 | 184 | # Adding λ constraints 185 | @constraint(m.model_mip, sum(λ[monomial_idx][:vars]) == 1) 186 | @constraint(m.model_mip, Variable(m.model_mip, λ[monomial_idx][:lifted_var_idx]) <= dot(λ[monomial_idx][:vars], λ[monomial_idx][:vals])) 187 | @constraint(m.model_mip, Variable(m.model_mip, λ[monomial_idx][:lifted_var_idx]) >= Variable(m.model_mip, monomial_idx)^2) 188 | 189 | # Add SOS-2 Constraints with basic encoding 190 | for i in 1:lambda_cnt 191 | if i == 1 192 | @constraint(m.model_mip, λ[monomial_idx][:vars][i] <= α[monomial_idx][i]) 193 | elseif i == lambda_cnt 194 | @constraint(m.model_mip, λ[monomial_idx][:vars][i] <= α[monomial_idx][i-1]) 195 | else 196 | @constraint(m.model_mip, λ[monomial_idx][:vars][i] <= α[monomial_idx][i-1] + α[monomial_idx][i]) 197 | end 198 | end 199 | 200 | # Equalivent SOS-2 : A different encoding (solwer performance) 201 | # for i in 1:partition_cnt 202 | # @constraint(m.model_mip, α[monomial_idx][i] <= λ[monomial_idx][:vars][i] + λ[monomial_idx][:vars][i+1]) 203 | # end 204 | # @constraint(m.model_mip, α[monomial_idx][1] >= λ[monomial_idx][:vars][1]) 205 | # @constraint(m.model_mip, α[monomial_idx][end] >= λ[monomial_idx][:vars][end]) 206 | 207 | # Add x = f(λ) for convex representation 208 | @constraint(m.model_mip, Variable(m.model_mip, monomial_idx) == dot(λ[monomial_idx][:vars], discretization[monomial_idx])) 209 | 210 | # Add x = f(α) for regulating the domains 211 | @constraint(m.model_mip, Variable(m.model_mip, monomial_idx) >= sum(α[monomial_idx][j]*discretization[monomial_idx][j] for j in 1:lambda_cnt-1)) 212 | @constraint(m.model_mip, Variable(m.model_mip, monomial_idx) <= sum(α[monomial_idx][j-1]*discretization[monomial_idx][j] for j in 2:lambda_cnt)) 213 | 214 | (m.convhull_formulation_sos2aux) && warn("Not considering alternative SOS2-Formulation for monomial terms") 215 | 216 | return 217 | end 218 | 219 | # Valid inequalities proposed when Jeff L. was here 220 | function valid_inequalities(m::PODNonlinearModel, discretization::Dict, λ::Dict, α::Dict, ml_indices::Any, dim::Tuple, var_ind::Int, cnt::Int) 221 | 222 | partition_cnt = length(α[var_ind]) 223 | lambda_cnt = length(discretization[var_ind]) 224 | 225 | if m.convhull_formulation_sos2aux 226 | for j in 1:lambda_cnt 227 | sliced_indices = collect_indices(λ[ml_indices][:indices], cnt, [j], dim) 228 | @constraint(m.model_mip, α[var_ind][j] == sum(λ[ml_indices][:vars][sliced_indices])) 229 | end 230 | return 231 | end 232 | 233 | if m.convhull_formulation_facet 234 | # Constraint cluster of α >= f(λ) 235 | for j in 1:(partition_cnt-1) # Construct cuts by sweeping in both directions 236 | sliced_indices = collect_indices(λ[ml_indices][:indices], cnt, [1:j;], dim) 237 | @constraint(m.model_mip, sum(α[var_ind][1:j]) >= sum(λ[ml_indices][:vars][sliced_indices])) 238 | end 239 | 240 | # Constriant cluster of α <= f(λ) 241 | for j in 1:(partition_cnt-1) 242 | sliced_indices = collect_indices(λ[ml_indices][:indices], cnt, [1:(j+1);], dim) 243 | @constraint(m.model_mip, sum(α[var_ind][1:j]) <= sum(λ[ml_indices][:vars][sliced_indices])) 244 | end 245 | return 246 | end 247 | 248 | 249 | if m.convhull_formulation_sos2 250 | # Encoding of λ -> α goes here 251 | for j in 1:lambda_cnt 252 | sliced_indices = collect_indices(λ[ml_indices][:indices], cnt, [j], dim) 253 | if (j == 1) 254 | @constraint(m.model_mip, sum(λ[ml_indices][:vars][sliced_indices]) <= α[var_ind][j]) 255 | elseif (j == lambda_cnt) 256 | @constraint(m.model_mip, sum(λ[ml_indices][:vars][sliced_indices]) <= α[var_ind][partition_cnt]) 257 | else 258 | @constraint(m.model_mip, sum(λ[ml_indices][:vars][sliced_indices]) <= sum(α[var_ind][(j-1):j])) 259 | end 260 | end 261 | return 262 | end 263 | 264 | if m.convhull_formulation_minib 265 | # Constraint cluster of α >= f(λ) 266 | for j in 1:min(partition_cnt, m.convexhull_sweep_limit) # Construct cuts by sweeping in both directions 267 | sliced_indices = collect_indices(λ[ml_indices][:indices], cnt, [1:j;], dim) 268 | @constraint(m.model_mip, sum(α[var_ind][1:j]) >= sum(λ[ml_indices][:vars][sliced_indices])) 269 | sliced_indices = collect_indices(λ[ml_indices][:indices], cnt, [(lambda_cnt-j+1):(lambda_cnt);], dim) 270 | @constraint(m.model_mip, sum(α[var_ind][(dim[cnt]-j):(dim[cnt]-1)]) >= sum(λ[ml_indices][:vars][sliced_indices])) 271 | end 272 | 273 | # Constriant cluster of α <= f(λ) 274 | for j in 1:partition_cnt 275 | for i in 1:max(1, min(partition_cnt-j+1, m.convexhull_sweep_limit)) # At least one 276 | sliced_indices = collect_indices(λ[ml_indices][:indices], cnt, [j:(j+i);], dim) 277 | @constraint(m.model_mip, sum(α[var_ind][j:(j+i-1)]) <= sum(λ[ml_indices][:vars][sliced_indices])) 278 | end 279 | end 280 | return 281 | end 282 | 283 | error("Must indicate a choice of convex hull formulation. ?(minib, sos2, sos2aux, facet)") 284 | return 285 | end 286 | 287 | function collect_indices(l::Array, locator::Tuple, dim::Tuple) 288 | 289 | k = 0 290 | indices = Vector{Int}(2^length(dim)) 291 | for i in 1:prod(dim) 292 | ind = ind2sub(l, i) 293 | diff = [((ind[i] - locator[i] == 0) || (ind[i] - locator[i] == 1)) for i in 1:length(dim)] 294 | if prod(diff) 295 | k +=1 296 | indices[k] = i 297 | end 298 | end 299 | 300 | return indices 301 | end 302 | 303 | function collect_indices(l::Array, fixed_dim::Int, fixed_partition::Array, dim::Tuple) 304 | 305 | k = 0 306 | indices = Vector{Int}(Int(prod(dim)/dim[fixed_dim]*length(fixed_partition))) 307 | for i in 1:prod(dim) 308 | ind = ind2sub(l, i) 309 | if ind[fixed_dim] in fixed_partition 310 | k += 1 311 | indices[k] = i 312 | end 313 | end 314 | 315 | return indices 316 | end 317 | 318 | function resolve_lifted_var_value(m::PODNonlinearModel, sol_vec::Array) 319 | 320 | @assert length(sol_vec) == m.num_var_orig 321 | sol_vec = [sol_vec; fill(NaN, m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip)] 322 | 323 | for i in 1:length(m.nonlinear_terms) 324 | for bi in keys(m.nonlinear_terms) 325 | if m.nonlinear_terms[bi][:id] == i 326 | lvar_idx = m.num_var_orig + i 327 | if haskey(m.nonlinear_terms[bi], :evaluator) 328 | sol_vec[lvar_idx] = m.nonlinear_terms[bi][:evaluator](m.nonlinear_terms[bi], sol_vec) 329 | else 330 | sol_vec[lvar_idx] = 0.5*m.discretization[lvar_idx][1] + 0.5*m.discretization[lvar_idx][end] 331 | end 332 | end 333 | end 334 | end 335 | 336 | return sol_vec 337 | end 338 | -------------------------------------------------------------------------------- /src/amp.jl: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | create_bounding_mip(m::PODNonlinearModel; use_discretization::Dict) 4 | 5 | Set up a JuMP MILP bounding model base on variable domain partitioning information stored in `use_discretization`. 6 | By default, if `use_discretization is` not provided, it will use `m.discretizations` store in the POD model. 7 | The basic idea of this MILP bounding model is to use Tighten McCormick to convexify the original Non-convex region. 8 | Among all presented partitionings, the bounding model will choose one specific partition as the lower bound solution. 9 | The more partitions there are, the better or finer bounding model relax the original MINLP while the more 10 | efforts required to solve this MILP is required. 11 | 12 | This function is implemented in the following manner: 13 | 14 | * [`amp_post_vars`](@ref): post original and lifted variables 15 | * [`amp_post_lifted_constraints`](@ref): post original and lifted constraints 16 | * [`amp_post_lifted_obj`](@ref): post original or lifted objective function 17 | * [`amp_post_tmc_mccormick`](@ref): post Tighen McCormick variables and constraints base on `discretization` information 18 | 19 | More specifically, the Tightening McCormick used here can be genealized in the following mathematcial formulation. Consider a nonlinear term 20 | ```math 21 | \\begin{subequations} 22 | \\begin{align} 23 | &\\widehat{x_{ij}} \\geq (\\mathbf{x}_i^l\\cdot\\hat{\\mathbf{y}}_i) x_j + (\\mathbf{x}_j^l\\cdot\\hat{\\mathbf{y}}_j) x_i - (\\mathbf{x}_i^l\\cdot\\hat{\\mathbf{y}}_i)(\\mathbf{x}_j^l\\cdot\\hat{\\mathbf{y}}_j) \\\\ 24 | &\\widehat{x_{ij}} \\geq (\\mathbf{x}_i^u\\cdot\\hat{\\mathbf{y}}_i) x_j + (\\mathbf{x}_j^u\\cdot\\hat{\\mathbf{y}}_j) x_i - (\\mathbf{x}_i^u\\cdot\\hat{\\mathbf{y}}_i)(\\mathbf{x}_j^u\\cdot\\hat{\\mathbf{y}}_j) \\\\ 25 | &\\widehat{x_{ij}} \\leq (\\mathbf{x}_i^l\\cdot\\hat{\\mathbf{y}}_i) x_j + (\\mathbf{x}_j^u\\cdot\\hat{\\mathbf{y}}_j) x_i - (\\mathbf{x}_i^l\\cdot\\hat{\\mathbf{y}}_i)(\\mathbf{x}_j^u\\cdot\\hat{\\mathbf{y}}_j) \\\\ 26 | &\\widehat{x_{ij}} \\leq (\\mathbf{x}_i^u\\cdot\\hat{\\mathbf{y}}_i) x_j + (\\mathbf{x}_j^l\\cdot\\hat{\\mathbf{y}}_j) x_i - (\\mathbf{x}_i^u\\cdot\\hat{\\mathbf{y}}_i)(\\mathbf{x}_j^l\\cdot\\hat{\\mathbf{y}}_j) \\\\ 27 | & \\mathbf{x}_i^u\\cdot\\hat{\\mathbf{y}}_i) \\geq x_{i} \\geq \\mathbf{x}_i^l\\cdot\\hat{\\mathbf{y}}_i) \\\\ 28 | & \\mathbf{x}_j^u\\cdot\\hat{\\mathbf{y}}_j) \\geq x_{j} \\geq \\mathbf{x}_j^l\\cdot\\hat{\\mathbf{y}}_j) \\\\ 29 | &\\sum \\hat{\\mathbf{y}_i} = 1, \\ \\ \\sum \\hat{\\mathbf{y}_j}_k = 1 \\\\ 30 | &\\hat{\\mathbf{y}}_i \\in \\{0,1\\}, \\hat{\\mathbf{y}}_j \\in \\{0,1\\} 31 | \\end{align} 32 | \\end{subequations} 33 | ``` 34 | 35 | """ 36 | function create_bounding_mip(m::PODNonlinearModel; kwargs...) 37 | 38 | options = Dict(kwargs) 39 | 40 | haskey(options, :use_discretization) ? discretization = options[:use_discretization] : discretization = m.discretization 41 | 42 | m.model_mip = Model(solver=m.mip_solver) # Construct JuMP Model 43 | start_build = time() 44 | # ------- Model Construction ------ # 45 | amp_post_vars(m) # Post original and lifted variables 46 | amp_post_lifted_constraints(m) # Post lifted constraints 47 | amp_post_lifted_objective(m) # Post objective 48 | amp_post_convexification(m, use_discretization=discretization) # Convexify problem 49 | # --------------------------------- # 50 | cputime_build = time() - start_build 51 | m.logs[:total_time] += cputime_build 52 | m.logs[:time_left] = max(0.0, m.timeout - m.logs[:total_time]) 53 | 54 | return 55 | end 56 | 57 | """ 58 | amp_post_convexification(m::PODNonlinearModel; kwargs...) 59 | 60 | warpper function to convexify the problem for a bounding model. This function talks to nonlinear_terms and convexification methods 61 | to finish the last step required during the construction of bounding model. 62 | """ 63 | function amp_post_convexification(m::PODNonlinearModel; kwargs...) 64 | 65 | options = Dict(kwargs) 66 | 67 | haskey(options, :use_discretization) ? discretization = options[:use_discretization] : discretization = m.discretization 68 | 69 | for i in 1:length(m.method_convexification) # Additional user-defined convexification method 70 | eval(m.method_convexification[i])(m) 71 | end 72 | 73 | amp_post_mccormick(m, use_discretization=discretization) # handles all bi-linear and monomial convexificaitons 74 | amp_post_convhull(m, use_discretization=discretization) # convex hull representation 75 | 76 | # Exam to see if all non-linear terms have been convexificed 77 | convexification_exam(m) 78 | 79 | return 80 | end 81 | 82 | function amp_post_vars(m::PODNonlinearModel; kwargs...) 83 | 84 | options = Dict(kwargs) 85 | 86 | if haskey(options, :use_discretization) 87 | l_var = [options[:use_discretization][i][1] for i in 1:(m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip)] 88 | u_var = [options[:use_discretization][i][end] for i in 1:(m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip)] 89 | else 90 | l_var = m.l_var_tight 91 | u_var = m.u_var_tight 92 | end 93 | 94 | @variable(m.model_mip, x[i=1:(m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip)]) 95 | for i in 1:(m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip) 96 | (i <= m.num_var_orig) && setcategory(x[i], m.var_type_orig[i]) 97 | (l_var[i] > -Inf) && (setlowerbound(x[i], l_var[i])) # Changed to tight bound, if no bound tightening is performed, will be just .l_var_orig 98 | (u_var[i] < Inf) && (setupperbound(x[i], u_var[i])) # Changed to tight bound, if no bound tightening is performed, will be just .u_var_orig 99 | end 100 | 101 | return 102 | end 103 | 104 | 105 | function amp_post_lifted_constraints(m::PODNonlinearModel) 106 | 107 | for i in 1:m.num_constr_orig 108 | if m.structural_constr[i] == :affine 109 | amp_post_affine_constraint(m.model_mip, m.bounding_constr_mip[i]) 110 | elseif m.structural_constr[i] == :convex 111 | amp_post_convex_constraint(m.model_mip, m.bounding_constr_mip[i]) 112 | else 113 | error("Unknown structural_constr type $(m.structural_constr[i])") 114 | end 115 | end 116 | 117 | for i in keys(m.linear_terms) 118 | amp_post_linear_lift_constraints(m.model_mip, m.linear_terms[i]) 119 | end 120 | 121 | return 122 | end 123 | 124 | function amp_post_affine_constraint(model_mip::JuMP.Model, affine::Dict) 125 | 126 | if affine[:sense] == :(>=) 127 | @constraint(model_mip, 128 | sum(affine[:coefs][j]*Variable(model_mip, affine[:vars][j].args[2]) for j in 1:affine[:cnt]) >= affine[:rhs]) 129 | elseif affine[:sense] == :(<=) 130 | @constraint(model_mip, 131 | sum(affine[:coefs][j]*Variable(model_mip, affine[:vars][j].args[2]) for j in 1:affine[:cnt]) <= affine[:rhs]) 132 | elseif affine[:sense] == :(==) 133 | @constraint(model_mip, 134 | sum(affine[:coefs][j]*Variable(model_mip, affine[:vars][j].args[2]) for j in 1:affine[:cnt]) == affine[:rhs]) 135 | end 136 | 137 | return 138 | end 139 | 140 | function amp_post_convex_constraint(model_mip::JuMP.Model, convex::Dict) 141 | 142 | if convex[:sense] == :(>=) 143 | @constraint(model_mip, 144 | sum(convex[:coefs][j]*Variable(model_mip, convex[:vars][j].args[2])^2 for j in 1:convex[:cnt]) >= convex[:rhs]) 145 | elseif convex[:sense] == :(<=) 146 | @constraint(model_mip, 147 | sum(convex[:coefs][j]*Variable(model_mip, convex[:vars][j].args[2])^2 for j in 1:convex[:cnt]) <= convex[:rhs]) 148 | elseif convex[:sense] == :(==) 149 | @constraint(model_mip, 150 | sum(convex[:coefs][j]*Variable(model_mip, convex[:vars][j].args[2])^2 for j in 1:convex[:cnt]) == convex[:rhs]) 151 | end 152 | 153 | return 154 | end 155 | 156 | function amp_post_linear_lift_constraints(model_mip::JuMP.Model, l::Dict) 157 | 158 | @assert l[:ref][:sign] == :+ 159 | @constraint(model_mip, Variable(model_mip, l[:y_idx]) == sum(i[1]*Variable(model_mip, i[2]) for i in l[:ref][:coef_var]) + l[:ref][:scalar]) 160 | return 161 | end 162 | 163 | function amp_post_lifted_objective(m::PODNonlinearModel) 164 | 165 | if m.structural_obj == :affine 166 | @objective(m.model_mip, m.sense_orig, m.bounding_obj_mip[:rhs] + sum(m.bounding_obj_mip[:coefs][i]*Variable(m.model_mip, m.bounding_obj_mip[:vars][i].args[2]) for i in 1:m.bounding_obj_mip[:cnt])) 167 | elseif m.structural_obj == :convex 168 | @objective(m.model_mip, m.sense_orig, m.bounding_obj_mip[:rhs] + sum(m.bounding_obj_mip[:coefs][i]*Variable(m.model_mip, m.bounding_obj_mip[:vars][i].args[2])^2 for i in 1:m.bounding_obj_mip[:cnt])) 169 | else 170 | error("Unknown structural obj type $(m.structural_obj)") 171 | end 172 | 173 | return 174 | end 175 | 176 | function add_partition(m::PODNonlinearModel; kwargs...) 177 | 178 | options = Dict(kwargs) 179 | haskey(options, :use_discretization) ? discretization = options[:use_discretization] : discretization = m.discretization 180 | haskey(options, :use_solution) ? point_vec = options[:use_solution] : point_vec = m.best_bound_sol 181 | 182 | if isa(m.discretization_add_partition_method, Function) 183 | m.discretization = eval(m.discretization_add_partition_method)(m, use_discretization=discretization, use_solution=point_vec) 184 | elseif m.discretization_add_partition_method == "adaptive" 185 | m.discretization = add_adaptive_partition(m, use_discretization=discretization, use_solution=point_vec) 186 | elseif m.discretization_add_partition_method == "uniform" 187 | m.discretization = add_uniform_partition(m, use_discretization=discretization) 188 | else 189 | error("Unknown input on how to add partitions.") 190 | end 191 | 192 | return 193 | end 194 | 195 | """ 196 | add_discretization(m::PODNonlinearModel; use_discretization::Dict, use_solution::Vector) 197 | 198 | Basic built-in method used to add a new partition on feasible domains of discretizing variables. 199 | This method make modification in .discretization 200 | 201 | Consider original partition [0, 3, 7, 9], where LB/any solution is 4. 202 | Use ^ as the new partition, "|" as the original partition 203 | 204 | A case when discretize ratio = 4 205 | | -------- | - ^ -- * -- ^ ---- | -------- | 206 | 0 3 3.5 4 4.5 7 9 207 | 208 | A special case when discretize ratio = 2 209 | | -------- | ---- * ---- ^ ---- | -------- | 210 | 0 3 4 5 7 9 211 | 212 | There are two options for this function, 213 | 214 | * `use_discretization(default=m.discretization)`:: to regulate which is the base to add new partitions on 215 | * `use_solution(default=m.best_bound_sol)`:: to regulate which solution to use when adding new partitions on 216 | 217 | TODO: also need to document the speical diverted cases when new partition touches both sides 218 | 219 | This function belongs to the hackable group, which means it can be replaced by the user to change the behvaior of the solver. 220 | """ 221 | function add_adaptive_partition(m::PODNonlinearModel; kwargs...) 222 | 223 | options = Dict(kwargs) 224 | 225 | haskey(options, :use_discretization) ? discretization = options[:use_discretization] : discretization = m.discretization 226 | haskey(options, :use_solution) ? point_vec = copy(options[:use_solution]) : point_vec = copy(m.best_bound_sol) 227 | 228 | (length(point_vec) < m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip) && (point_vec = resolve_lifted_var_value(m, point_vec)) # Update the solution vector for lifted variable 229 | 230 | # ? Perform discretization base on type of nonlinear terms 231 | for i in m.var_discretization_mip 232 | point = point_vec[i] # Original Variable 233 | #@show i, point, discretization[i] 234 | if (i <= m.num_var_orig) && (m.var_type_orig[i] in [:Bin, :Int]) # DO not add partitions to discrete variables 235 | continue 236 | end 237 | if point < discretization[i][1] - m.tol || point > discretization[i][end] + m.tol 238 | warn("Soluiton VAR$(i)=$(point) out of bounds [$(discretization[i][1]),$(discretization[i][end])]. Taking middle point...") 239 | point = 0.5*(discretization[i][1]+discretization[i][end]) 240 | end 241 | # Safety Scheme 242 | (abs(point - discretization[i][1]) <= m.tol) && (point = discretization[i][1]) 243 | (abs(point - discretization[i][end]) <= m.tol) && (point = discretization[i][end]) 244 | for j in 1:length(discretization[i]) 245 | if point >= discretization[i][j] && point <= discretization[i][j+1] # Locating the right location 246 | 247 | @assert j < length(m.discretization[i]) 248 | lb_local = discretization[i][j] 249 | ub_local = discretization[i][j+1] 250 | distance = ub_local - lb_local 251 | 252 | if isa(m.discretization_ratio, Float64) || isa(m.discretization_ratio, Int) 253 | radius = distance / m.discretization_ratio 254 | elseif isa(m.discretization_ratio, Function) 255 | radius = distance / m.discretization_ratio(m) 256 | else 257 | error("Undetermined discretization_ratio") 258 | end 259 | 260 | lb_new = max(point - radius, lb_local) 261 | ub_new = min(point + radius, ub_local) 262 | ub_touch = true 263 | lb_touch = true 264 | if ub_new < ub_local && !isapprox(ub_new, ub_local; atol=m.discretization_abs_width_tol) && abs(ub_new-ub_local)/(1e-8+abs(ub_local)) > m.discretization_rel_width_tol # Insert new UB-based partition 265 | insert!(discretization[i], j+1, ub_new) 266 | ub_touch = false 267 | end 268 | if lb_new > lb_local && !isapprox(lb_new, lb_local; atol=m.discretization_abs_width_tol) && abs(lb_new-lb_local)/(1e-8+abs(lb_local)) > m.discretization_rel_width_tol # Insert new LB-based partition 269 | insert!(discretization[i], j+1, lb_new) 270 | lb_touch = false 271 | end 272 | # @show i, ub_touch, lb_touch, check_solution_history(m, i) 273 | if (ub_touch && lb_touch) || (m.discretization_consecutive_forbid>0 && check_solution_history(m, i)) 274 | distance = -1.0 275 | pos = -1 276 | for j in 2:length(discretization[i]) # it is made sure there should be at least two partitions 277 | if (discretization[i][j] - discretization[i][j-1]) > distance 278 | lb_local = discretization[i][j-1] 279 | ub_local = discretization[i][j] 280 | distance = ub_local - lb_local 281 | point = lb_local + (ub_local - lb_local) / 2 # reset point 282 | pos = j 283 | end 284 | end 285 | chunk = (ub_local - lb_local)/2 286 | insert!(discretization[i], pos, lb_local + chunk) 287 | # insert!(discretization[i], pos+1, lb_local + chunk*2) 288 | (m.log_level > 99) && println("[DEBUG] !DIVERT! VAR$(i): |$(lb_local) | 2 SEGMENTS | $(ub_local)|") 289 | else 290 | m.log_level > 99 && println("[DEBUG] VAR$(i): SOL=$(round(point,4)) RATIO=$(m.discretization_ratio), PARTITIONS=$(length(discretization[i])-1) |$(round(lb_local,4)) |$(round(lb_new,6)) <- * -> $(round(ub_new,6))| $(round(ub_local,4))|") 291 | end 292 | break 293 | end 294 | end 295 | end 296 | 297 | return discretization 298 | end 299 | 300 | function add_uniform_partition(m::PODNonlinearModel; kwargs...) 301 | 302 | options = Dict(kwargs) 303 | haskey(options, :use_discretization) ? discretization = options[:use_discretization] : discretization = m.discretization 304 | 305 | for i in m.var_discretization_mip # Only construct when discretized 306 | lb_local = discretization[i][1] 307 | ub_local = discretization[i][end] 308 | distance = ub_local - lb_local 309 | chunk = distance / ((m.logs[:n_iter]+1)*m.discretization_uniform_rate) 310 | discretization[i] = [lb_local+chunk*(j-1) for j in 1:(m.logs[:n_iter]+1)*m.discretization_uniform_rate] 311 | push!(discretization[i], ub_local) # Safety Scheme 312 | (m.log_level > 99) && println("[DEBUG] VAR$(i): RATE=$(m.discretization_uniform_rate), PARTITIONS=$(length(discretization[i])) |$(round(lb_local,4)) | $(m.discretization_uniform_rate*(1+m.logs[:n_iter])) SEGMENTS | $(round(ub_local,4))|") 313 | end 314 | 315 | return discretization 316 | end 317 | -------------------------------------------------------------------------------- /examples/exprstest.jl: -------------------------------------------------------------------------------- 1 | # Contains a basic model with various expressions for testing 2 | using POD, JuMP, Ipopt, CPLEX, MathProgBase 3 | 4 | function exprstest(;verbose=false, solver=nothing) 5 | 6 | if solver == nothing 7 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 8 | mip_solver=CplexSolver(), 9 | log_level=0)) 10 | else 11 | m = Model(solver=solver) 12 | end 13 | 14 | @variable(m, px[i=1:6]>=1) # At some point if an initial value is given, keep them 15 | 16 | @NLconstraint(m, sum(3*px[i]^2 for i=1:4) >= 111) 17 | @NLconstraint(m, -px[1] * px[2] + 4*5*px[3]*px[4] >= 222) 18 | @NLconstraint(m, -px[1] * px[2] <= 115) 19 | @NLconstraint(m, -px[1] * -px[2] >= 115) 20 | @NLconstraint(m, px[1] * -px[2] <= 115) 21 | @constraint(m, -px[1] + (-5) - 4 <= 100) 22 | @NLconstraint(m, px[1]+ px[2]*px[3] >= 555) # => px[1] + x23 >= 555 && x23 == px[2]*px[3] 23 | @NLconstraint(m, px[1]^2 - 7*px[2]^2 + px[3]^2 + px[4] <= 6666) 24 | @NLconstraint(m, 13*px[1] - px[2] + 5*px[3]*6 + px[4] >= 77) 25 | 26 | @NLobjective(m, Min, 7*px[1]*6*px[4]*2+5+17+px[1]+px[2]+px[3]+8+3*5*px[1]^2*4) 27 | 28 | if verbose 29 | print(m) 30 | end 31 | 32 | return m 33 | end 34 | 35 | function meyer4_expr(;verbose=false, solver=nothing) 36 | 37 | if solver == nothing 38 | m = Model(solver=PODSolver(nlp_local_solver=BonminNLSolver(["bonmin.iteration_limit=100"]), 39 | mip_solver=GurobiSolver(OutputFlag=0), 40 | discretization_ratio=32, 41 | log_level=100, 42 | rel_gap=0.001)) 43 | else 44 | m = Model(solver=solver) 45 | end 46 | 47 | # ----- Variables ----- # 48 | xIdx = Int64[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119] 49 | @assert length(xIdx) == 119 50 | @variable(m, x[1:119]) # Set up all variables 51 | 52 | # ----> BINARY VARIABLES (IF ANY) 53 | binaryIdx = Int64[64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118] 54 | for i in binaryIdx 55 | setcategory(x[i], :Bin) 56 | end 57 | 58 | # ----> ALL BOUNDS 59 | setlowerbound(x[16], 0.0) 60 | setlowerbound(x[14], 0.0) 61 | setlowerbound(x[62], 0.0) 62 | setlowerbound(x[38], 0.0) 63 | setlowerbound(x[42], 0.0) 64 | setlowerbound(x[56], 0.0) 65 | setlowerbound(x[22], 0.0) 66 | setlowerbound(x[59], 0.0) 67 | setlowerbound(x[2], 0.0) 68 | setlowerbound(x[9], 0.0) 69 | setlowerbound(x[8], 0.0) 70 | setlowerbound(x[43], 0.0) 71 | setlowerbound(x[36], 0.0) 72 | setlowerbound(x[4], 0.0) 73 | setlowerbound(x[32], 0.0) 74 | setlowerbound(x[54], 0.0) 75 | setlowerbound(x[27], 0.0) 76 | setlowerbound(x[3], 0.0) 77 | setlowerbound(x[25], 0.0) 78 | setlowerbound(x[30], 0.0) 79 | setlowerbound(x[58], 0.0) 80 | setlowerbound(x[11], 0.0) 81 | setlowerbound(x[29], 0.0) 82 | setlowerbound(x[53], 0.0) 83 | setlowerbound(x[5], 0.0) 84 | setlowerbound(x[37], 0.0) 85 | setlowerbound(x[63], 0.0) 86 | setlowerbound(x[57], 0.0) 87 | setlowerbound(x[55], 0.0) 88 | setlowerbound(x[24], 0.0) 89 | setlowerbound(x[41], 0.0) 90 | setlowerbound(x[18], 0.0) 91 | setlowerbound(x[52], 0.0) 92 | setlowerbound(x[1], 0.0) 93 | setlowerbound(x[7], 0.0) 94 | setlowerbound(x[13], 0.0) 95 | setlowerbound(x[49], 0.0) 96 | setlowerbound(x[21], 0.0) 97 | setlowerbound(x[10], 0.0) 98 | setlowerbound(x[26], 0.0) 99 | setlowerbound(x[45], 0.0) 100 | setlowerbound(x[12], 0.0) 101 | setlowerbound(x[40], 0.0) 102 | setlowerbound(x[44], 0.0) 103 | setlowerbound(x[61], 0.0) 104 | setlowerbound(x[50], 0.0) 105 | setlowerbound(x[31], 0.0) 106 | setlowerbound(x[33], 0.0) 107 | setlowerbound(x[47], 0.0) 108 | setlowerbound(x[28], 0.0) 109 | setlowerbound(x[35], 0.0) 110 | setlowerbound(x[6], 0.0) 111 | setlowerbound(x[60], 0.0) 112 | setlowerbound(x[17], 0.0) 113 | setlowerbound(x[23], 0.0) 114 | setlowerbound(x[34], 0.0) 115 | setlowerbound(x[46], 0.0) 116 | setlowerbound(x[51], 0.0) 117 | setlowerbound(x[19], 0.0) 118 | setlowerbound(x[48], 0.0) 119 | setlowerbound(x[20], 0.0) 120 | setlowerbound(x[39], 0.0) 121 | setlowerbound(x[15], 0.0) 122 | setupperbound(x[1],20.0) 123 | setupperbound(x[2],20.0) 124 | setupperbound(x[3],20.0) 125 | setupperbound(x[4],20.0) 126 | setupperbound(x[5],50.0) 127 | setupperbound(x[6],50.0) 128 | setupperbound(x[7],50.0) 129 | setupperbound(x[8],50.0) 130 | setupperbound(x[9],47.5) 131 | setupperbound(x[10],47.5) 132 | setupperbound(x[11],47.5) 133 | setupperbound(x[12],47.5) 134 | setupperbound(x[13],28.0) 135 | setupperbound(x[14],28.0) 136 | setupperbound(x[15],28.0) 137 | setupperbound(x[16],28.0) 138 | setupperbound(x[17],100.0) 139 | setupperbound(x[18],100.0) 140 | setupperbound(x[19],100.0) 141 | setupperbound(x[20],100.0) 142 | setupperbound(x[21],30.0) 143 | setupperbound(x[22],30.0) 144 | setupperbound(x[23],30.0) 145 | setupperbound(x[24],30.0) 146 | setupperbound(x[25],25.0) 147 | setupperbound(x[26],25.0) 148 | setupperbound(x[27],25.0) 149 | setupperbound(x[28],25.0) 150 | setupperbound(x[29],300.5) 151 | setupperbound(x[30],300.5) 152 | setupperbound(x[31],300.5) 153 | setupperbound(x[32],300.5) 154 | setupperbound(x[33],300.5) 155 | setupperbound(x[34],300.5) 156 | setupperbound(x[35],300.5) 157 | setupperbound(x[36],300.5) 158 | setupperbound(x[37],300.5) 159 | setupperbound(x[38],300.5) 160 | setupperbound(x[39],300.5) 161 | setupperbound(x[40],300.5) 162 | setupperbound(x[41],300.5) 163 | setupperbound(x[42],300.5) 164 | setupperbound(x[43],300.5) 165 | setupperbound(x[44],300.5) 166 | setupperbound(x[45],20.0) 167 | setupperbound(x[46],50.0) 168 | setupperbound(x[47],47.5) 169 | setupperbound(x[48],28.0) 170 | setupperbound(x[49],100.0) 171 | setupperbound(x[50],30.0) 172 | setupperbound(x[51],25.0) 173 | setupperbound(x[52],12.0) 174 | setupperbound(x[53],175.0) 175 | setupperbound(x[54],100.0) 176 | setupperbound(x[55],1200.0) 177 | setupperbound(x[56],227.5) 178 | setupperbound(x[57],200.0) 179 | setupperbound(x[58],1080.0) 180 | setupperbound(x[59],17.5) 181 | setupperbound(x[60],2000.0) 182 | setupperbound(x[61],360.0) 183 | setupperbound(x[62],1400.0) 184 | setupperbound(x[63],1400.0) 185 | 186 | # ----> OBJECTIVE FUNCTION 187 | @objective(m, Min, x[119]) 188 | 189 | # ----> NON-LINEAR CONSTRAINTS 190 | @NLconstraint(m, e26,0.01*(x[55]*x[36]+x[58]*x[39]+x[61]*x[42])-(x[52]*x[29]+x[52]*x[33]+x[52]*x[34]+x[52]*x[35])+x[1]+8.00000000000001*x[5]+4*x[9]+12*x[13]+5*x[17]+0.5*x[21]+10*x[25]==0.0) 191 | @NLconstraint(m, e27,800*(0.1*(x[56]*x[36]+x[59]*x[39]+x[62]*x[42])-(x[53]*x[29]+x[53]*x[33]+x[53]*x[34]+x[53]*x[35]))+50*x[1]+175*x[5]+8*x[9]+100*x[13]+70*x[17]+10*x[21]+5*x[25]==0.0) 192 | @NLconstraint(m, e28,0.05*(x[57]*x[36]+x[60]*x[39]+x[63]*x[42])-0.02*(x[54]*x[29]+x[54]*x[33]+x[54]*x[34]+x[54]*x[35])+25*x[1]+100*x[5]+5*x[9]+20*x[13]+12.5*x[17]+2.5*x[21]+7.50000000000001*x[25]==0.0) 193 | @NLconstraint(m, e38,x[52]*x[29]+x[55]*x[30]+x[58]*x[31]+x[61]*x[32]-5*x[29]-5*x[30]-5*x[31]-5*x[32]+95*x[45]+795*x[46]+395*x[47]+1195*x[48]+495*x[49]+45*x[50]+995*x[51]<=0.0) 194 | 195 | @constraint(m, e1,-75.0708333333333*x[1]-150.141666666667*x[2]-280.264444444444*x[3]-245.231388888889*x[4]-55.0519444444444*x[5]-125.118055555556*x[6]-260.245555555556*x[7]-215.203055555556*x[8]-30.0283333333333*x[9]-115.108611111111*x[10]-240.226666666667*x[11]-220.207777777778*x[12]-55.0519444444444*x[13]-140.132222222222*x[14]-245.231388888889*x[15]-245.231388888889*x[16]-55.0519444444444*x[17]-40.0377777777778*x[18]-150.141666666667*x[19]-150.141666666667*x[20]-40.0377777777778*x[21]-120.113333333333*x[22]-230.217222222222*x[23]-230.217222222222*x[24]-30.0283333333333*x[25]-60.0566666666667*x[26]-175.165277777778*x[27]-165.155833333333*x[28]-1177.97083333333*x[29]-2975.27555555556*x[30]-1263.05111111111*x[31]-1293.07944444444*x[32]-1182.97555555556*x[33]-1313.09833333333*x[34]-1293.07944444444*x[35]-2975.27555555556*x[36]-3025.32277777778*x[37]-2995.29444444444*x[38]-1313.09833333333*x[39]-1233.02277777778*x[40]-1213.00388888889*x[41]-1293.07944444444*x[42]-1202.99444444444*x[43]-1213.00388888889*x[44]-150.141666666667*x[45]-135.1275*x[46]-100.094444444444*x[47]-90.085*x[48]-40.0377777777778*x[49]-70.0661111111111*x[50]-45.0425*x[51]-9345*x[64]-18690*x[65]-34888*x[66]-30527*x[67]-6853*x[68]-15575*x[69]-32396*x[70]-26789*x[71]-3738*x[72]-14329*x[73]-29904*x[74]-27412*x[75]-6853*x[76]-17444*x[77]-30527*x[78]-30527*x[79]-6853*x[80]-4984*x[81]-18690*x[82]-18690*x[83]-4984*x[84]-14952*x[85]-28658*x[86]-28658*x[87]-3738*x[88]-7476*x[89]-21805*x[90]-20559*x[91]-9345*x[92]-9968*x[93]-19936*x[94]-23674*x[95]-9968*x[96]-26166*x[97]-23674*x[98]-9968*x[99]-16198*x[100]-12460*x[101]-26166*x[102]-16198*x[103]-13706*x[104]-23674*x[105]-12460*x[106]-13706*x[107]-18690*x[108]-16821*x[109]-12460*x[110]-11214*x[111]-4984*x[112]-8722*x[113]-5607*x[114]-13972*x[115]-36676*x[116]-13972*x[117]-13972*x[118]+x[119]==0.0) 196 | @constraint(m, e2,-x[1]-x[2]-x[3]-x[4]-x[45]<=-20.0) 197 | @constraint(m, e140,x[35]-300.5*x[98]<=0.0) 198 | 199 | if verbose 200 | print(m) 201 | end 202 | 203 | return m 204 | end 205 | 206 | function operator_b(;verbose=false, solver=nothing) 207 | 208 | if solver == nothing 209 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(), 210 | mip_solver=CbcSolver(OutputFlag=0), 211 | presolve_bound_tightening=true, 212 | presolve_bound_tightening_algo=2, 213 | presolve_bt_output_tol=1e-1, 214 | log_level=1)) 215 | else 216 | m = Model(solver=solver) 217 | end 218 | 219 | @variable(m, x[1:4]>=0) 220 | @variable(m, y[1:3]<=0) 221 | # PARSING TARGETS # 222 | @constraint(m, x[1] + x[2] + -y[1] >= 1) # None 223 | @constraint(m, 5x[1] + 8x[2] + 9y[2] <= 2) # None 224 | @constraint(m, 4*x[1] + 5*x[2] + -3*y[1] >= 3) # None 225 | @constraint(m, 3 <= x[1] + x[2] <= 4) # None 226 | 227 | @NLconstraint(m, x[1]*x[2] + 3*x[2]*x[3] >= 5) # x[1]*x[2] and 3*x[2]*x[3] 228 | @NLconstraint(m, x[1]*(x[2]+4) + 10*44*x[2]*5*x[3] <= 6) # x[2]*x[3] 229 | @NLconstraint(m, x[1]*4 + x[2]*(x[2]+x[3]) >= 7) # None 230 | @NLconstraint(m, 3*x[1]*x[2] + x[2]*-x[3] <= 8) # x[1]*x[2] 231 | 232 | @NLconstraint(m, -1*x[1]^2 - x[2]^2 >= 9) # x[1]^2 and x[2]^2 233 | @NLconstraint(m, x[1]^(1+1) + 5*x[2]^2 + 3*x[3]^3 <= 10) # x[1]^2 and x[2]^2 234 | @NLconstraint(m, (x[1]+5)^2 >= 11) # None 235 | @NLconstraint(m, x[1]^3 + x[3]^99 >= 12) # None 236 | 237 | @NLconstraint(m, x[1]*x[2]*x[3] <= 13) # x[1]*x[2]*x[3] 238 | @NLconstraint(m, (x[1]*x[2])*x[3] >= 14) # x[1]*x[2] ****** 239 | @NLconstraint(m, x[1]*(x[2]*x[3]) <= 15) # x[2]*x[3] ****** 240 | @NLconstraint(m, x[1]*x[2]*x[3]*x[4] >= 16) # x[1]*x[2]*x[3]*x[4] 241 | @NLconstraint(m, (x[1]*x[2])*(x[3]*x[4]) <= 17) # x[1]*x[2] and x[3]*x[4] 242 | @NLconstraint(m, x[1]*(x[2]*x[3])*x[4] >= 18) # x[2]*x[3] ****** 243 | @NLconstraint(m, (x[1]*x[2])*x[3]*x[4] <= 19) # x[1]*x[2] ****** 244 | @NLconstraint(m, ((x[1]*x[2])*x[3])*x[4] >= 20) # x[1]*x[2] ****** 245 | @NLconstraint(m, (x[1]*(x[2]*x[3])*x[4]) <= 21) # x[2]*x[3] ****** 246 | 247 | @NLconstraint(m, 4*5*6*x[1]*x[2]*x[3]*x[4] >= 22) # x[1]*x[2]*x[3]*x[4] 248 | @NLconstraint(m, x[1]^2*x[2]^2*x[3]^2*x[4] <= 23) # None 249 | @NLconstraint(m, x[1]*x[1]*x[2]*x[2]*x[3]*x[3] >= 24) # x[1]*x[1]*x[2]*x[2]*x[3]*x[3] 250 | @NLconstraint(m, (x[1]+1)*(x[2]+2)*(x[3]+3)*(x[4]+4) <= 25) # None 251 | 252 | @NLconstraint(m, 50sin(x[1]) - 32*cos(x[2]) >= 26) # sin(x[1]), cos(x[2]) 253 | @NLconstraint(m, sin(x[1]+x[2]) - cos(x[2]-x[3]) + sin(-x[2]+x[3]) <= 27) 254 | @NLconstraint(m, sin(4*x[1]*x[2]) + cos(x[2]*-1*x[3]) >= 28)# x[1]*x[2] and x[2]*-1*x[3] ****** 255 | @NLconstraint(m, sin(x[1]*x[2]*x[3]) <= 29) # x[1]*x[2]*x[3] 256 | 257 | if verbose 258 | print(m) 259 | end 260 | 261 | return m 262 | end 263 | 264 | function operator_basic(;verbose=false, solver=nothing) 265 | 266 | if solver == nothing 267 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(), 268 | mip_solver=CbcSolver(), 269 | log_level=1)) 270 | else 271 | m = Model(solver=solver) 272 | end 273 | 274 | @variable(m, x[1:4]>=0) 275 | 276 | @NLconstraint(m, x[1]^2 >= 1) 277 | @NLconstraint(m, x[1]*x[2] <= 1) 278 | @NLconstraint(m, x[1]^2 + x[2]*x[3] <= 1) 279 | 280 | @NLconstraint(m, x[1] * (x[2]*x[3]) >= 1) 281 | @NLconstraint(m, x[1]^2 * (x[2]^2 * x[3]^2) <= 1) 282 | 283 | @NLconstraint(m, (x[1] * x[2]) * x[3] >= 1) 284 | @NLconstraint(m, (x[1]^2 * x[2]^2) * x[3]^2 <= 1) 285 | 286 | @NLconstraint(m, x[1] * (x[2]^2 * x[3]^2) >= 1) 287 | @NLconstraint(m, (x[1]^2 * x[2]) * x[3]^2 <= 1) 288 | @NLconstraint(m, x[1]^2 * (x[2] * x[3]) >= 1) 289 | @NLconstraint(m, (x[1] * x[2]^2) * x[3] <= 1) 290 | 291 | @NLconstraint(m, ((x[1]*x[2])*x[3])*x[4] >= 1) 292 | @NLconstraint(m, ((x[1]^2*x[2])*x[3])*x[4] <= 1) 293 | @NLconstraint(m, ((x[1]*x[2]^2)*x[3])*x[4] >= 1) 294 | @NLconstraint(m, ((x[1]*x[2])*x[3]^2)*x[4] <= 1) 295 | @NLconstraint(m, ((x[1]*x[2])*x[3])*x[4]^2 >= 1) 296 | @NLconstraint(m, ((x[1]^2*x[2]^2)*x[3]^2)*x[4]^2 <=1) 297 | 298 | @NLconstraint(m, x[1]*(x[2]*(x[3]*x[4])) >= 1) 299 | @NLconstraint(m, x[1]^2*(x[2]*(x[3]*x[4])) <= 1) 300 | @NLconstraint(m, x[1]*(x[2]^2*(x[3]*x[4])) >= 1) 301 | @NLconstraint(m, x[1]*(x[2]*(x[3]^2*x[4])) <= 1) 302 | @NLconstraint(m, x[1]*(x[2]*(x[3]*x[4]^2)) >= 1) 303 | @NLconstraint(m, x[1]^2*(x[2]^2*(x[3]^2*x[4]^2)) <= 1) 304 | 305 | @NLconstraint(m, x[1]*x[2]*x[3] >= 1) 306 | @NLconstraint(m, x[1]^2*x[2]*x[3] >= 1) 307 | @NLconstraint(m, x[1]*x[2]^2*x[3] >= 1) 308 | @NLconstraint(m, x[1]*x[2]*x[3]^2 >= 1) 309 | @NLconstraint(m, x[1]^2*x[2]^2*x[3] >= 1) 310 | @NLconstraint(m, x[1]*x[2]^2*x[3]^2 >= 1) 311 | @NLconstraint(m, x[1]^2*x[2]*x[3]^2 >= 1) 312 | @NLconstraint(m, x[1]^2*x[2]^2*x[3]^2 >= 1) 313 | 314 | @NLconstraint(m, (x[1]*x[2])*(x[3]*x[4]) >= 1) 315 | @NLconstraint(m, (x[1]^2*x[2])*(x[3]*x[4]) >= 1) 316 | @NLconstraint(m, (x[1]*x[2]^2)*(x[3]*x[4]) >= 1) 317 | @NLconstraint(m, (x[1]*x[2])*(x[3]^2*x[4]) >= 1) 318 | @NLconstraint(m, (x[1]*x[2])*(x[3]^2*x[4]^2) >= 1) 319 | @NLconstraint(m, (x[1]^2*x[2])*(x[3]*x[4]^2) >= 1) 320 | @NLconstraint(m, (x[1]^2*x[2])*(x[3]^2*x[4]) >= 1) 321 | 322 | @NLconstraint(m, x[1]*x[2]*x[3]*x[4] >= 1) 323 | @NLconstraint(m, x[1]^2*x[2]*x[3]*x[4] >= 1) 324 | @NLconstraint(m, x[1]*x[2]^2*x[3]*x[4] >= 1) 325 | @NLconstraint(m, x[1]*x[2]*x[3]^2*x[4]^2 >= 1) 326 | @NLconstraint(m, x[1]*x[2]^2*x[3]^2*x[4] >= 1) 327 | @NLconstraint(m, x[1]^2*x[2]*x[3]*x[4]^2 >= 1) 328 | @NLconstraint(m, x[1]^2*x[2]^2*x[3]^2*x[4]^2 >= 1) 329 | 330 | @NLconstraint(m, (x[1]*x[2]*x[3])*x[4] >= 1) 331 | @NLconstraint(m, (x[1]^2*x[2]*x[3])*x[4] >= 1) 332 | @NLconstraint(m, (x[1]*x[2]^2*x[3])*x[4] >= 1) 333 | @NLconstraint(m, (x[1]*x[2]*x[3]^2)*x[4] >= 1) 334 | @NLconstraint(m, (x[1]*x[2]*x[3])*x[4]^2 >= 1) 335 | @NLconstraint(m, (x[1]^2*x[2]^2*x[3]^2)*x[4]^2 >= 1) 336 | 337 | @NLconstraint(m, x[1]*(x[2]*x[3])*x[4] >= 1) 338 | @NLconstraint(m, x[1]^2*(x[2]*x[3])*x[4] >= 1) 339 | @NLconstraint(m, x[1]*(x[2]^2*x[3])*x[4] >= 1) 340 | @NLconstraint(m, x[1]*(x[2]*x[3]^2)*x[4] >= 1) 341 | @NLconstraint(m, x[1]*(x[2]*x[3])*x[4]^2 >= 1) 342 | @NLconstraint(m, x[1]^2*(x[2]*x[3])*x[4]^2 >= 1) 343 | @NLconstraint(m, x[1]*(x[2]^2*x[3]^2)*x[4] >= 1) 344 | @NLconstraint(m, x[1]^2*(x[2]^2*x[3])*x[4] >= 1) 345 | @NLconstraint(m, x[1]*(x[2]*x[3]^2)*x[4]^2 >= 1) 346 | 347 | @NLconstraint(m, x[1]*(x[2]*x[3]*x[4]) >= 1) 348 | @NLconstraint(m, x[1]^2*(x[2]*x[3]*x[4]) >= 1) 349 | @NLconstraint(m, x[1]*(x[2]^2*x[3]*x[4]) >= 1) 350 | @NLconstraint(m, x[1]*(x[2]*x[3]^2*x[4]) >= 1) 351 | @NLconstraint(m, x[1]*(x[2]*x[3]*x[4]^2) >= 1) 352 | @NLconstraint(m, x[1]^2*(x[2]*x[3]^2*x[4]) >= 1) 353 | @NLconstraint(m, x[1]^2*(x[2]^2*x[3]^2*x[4]^2) >= 1) 354 | 355 | @NLconstraint(m, x[1]*x[2]*(x[3]*x[4]) >= 1) 356 | @NLconstraint(m, x[1]^2*x[2]*(x[3]*x[4]) >= 1) 357 | @NLconstraint(m, x[1]*x[2]^2*(x[3]*x[4]) >= 1) 358 | @NLconstraint(m, x[1]*x[2]*(x[3]^2*x[4]) >= 1) 359 | @NLconstraint(m, x[1]*x[2]*(x[3]*x[4]^2) >= 1) 360 | @NLconstraint(m, x[1]^2*x[2]^2*(x[3]^2*x[4]^2) >= 1) 361 | 362 | @NLconstraint(m, (x[1]*x[2])*x[3]*x[4] >= 1) 363 | @NLconstraint(m, (x[1]^2*x[2])*x[3]*x[4] >= 1) 364 | @NLconstraint(m, (x[1]*x[2]^2)*x[3]*x[4] >= 1) 365 | @NLconstraint(m, (x[1]*x[2])*x[3]^2*x[4] >= 1) 366 | @NLconstraint(m, (x[1]*x[2])*x[3]*x[4]^2 >= 1) 367 | @NLconstraint(m, (x[1]*x[2])*x[3]^2*x[4]^2 >= 1) 368 | @NLconstraint(m, (x[1]^2*x[2]^2)*x[3]^2*x[4]^2 >= 1) 369 | 370 | if verbose 371 | print(m) 372 | end 373 | 374 | return m 375 | end 376 | 377 | function operator_c(;verbose=false, solver=nothing) 378 | 379 | if solver == nothing 380 | m = Model(solver=PODSolver(nlp_local_solver=IpoptSolver(), 381 | mip_solver=CplexSolver())) 382 | else 383 | m = Model(solver=solver) 384 | end 385 | 386 | @variable(m, px[i=1:6]>=1) # At some point if an initial value is given, keep them 387 | 388 | @NLconstraint(m, sum(3*px[i]^2 for i=1:4) >= 111) 389 | @NLconstraint(m, -1 * px[1]*px[2] + 4*5*px[3]*px[4] >= 222) 390 | @NLconstraint(m, px[1]+ px[2] * px[3] >= 555) # => px[1] + x23 >= 555 && x23 == px[2]*px[3] 391 | @NLconstraint(m, px[1]^2 - 7 * px[2]^2 + px[3]^2 + px[4] <= 6666) 392 | @NLconstraint(m, 13*px[1] - px[2] + 5*px[3]*6 + px[4] >= 77) 393 | 394 | @NLobjective(m, Min, 7*px[1]*6*px[4]*2+5+17+px[1]+px[2]+px[3]+8+3*5*px[1]^2*4) 395 | if verbose 396 | print(m) 397 | end 398 | 399 | return m 400 | end 401 | -------------------------------------------------------------------------------- /src/nlexpr.jl: -------------------------------------------------------------------------------- 1 | function expr_strip_const(expr, subs=[], rhs=0.0) 2 | 3 | exhaust_const = [!(expr.args[1] in [:+, :-]) || !(isa(expr.args[i], Float64) || isa(expr.args[i], Int)) for i in 2:length(expr.args)] 4 | if prod(exhaust_const) 5 | push!(subs, expr) 6 | return subs, rhs 7 | end 8 | 9 | for i in 2:length(expr.args) 10 | if (isa(expr.args[i], Float64) || isa(expr.args[i], Int) || isa(expr.args[i], Symbol)) 11 | (expr.args[1] == :+) && (rhs -= expr.args[i]) 12 | (expr.args[1] == :-) && ((i == 2) ? rhs -= expr.args[i] : rhs += expr.args[i]) 13 | elseif expr.args[i].head == :ref 14 | continue 15 | elseif expr.args[i].head == :call 16 | subs, rhs = expr_strip_const(expr.args[i], subs, rhs) 17 | end 18 | end 19 | return subs, rhs 20 | end 21 | 22 | function preprocess_expression(expr) 23 | 24 | for i in 1:length(expr.args) 25 | expr.args[i] = expr_resolve_simple_tree(expr.args[i]) 26 | if (isa(expr.args[i], Float64) || isa(expr.args[i], Int) || isa(expr.args[i], Symbol)) 27 | continue 28 | elseif (expr.args[i].head == :ref) 29 | continue 30 | elseif (expr.args[i].head == :call) 31 | preprocess_expression(expr.args[i]) 32 | end 33 | end 34 | 35 | return 36 | end 37 | 38 | """ 39 | expr_constr_parsing(expr, m::PODNonlinearModel) 40 | 41 | Recognize structural constraints. 42 | """ 43 | function expr_constr_parsing(expr, m::PODNonlinearModel, idx::Int=0) 44 | 45 | # First process user-defined structures in-cases of over-ride 46 | for i in 1:length(m.constr_patterns) 47 | is_strucural = eval(m.constr_patterns[i])(expr, m, idx) 48 | return 49 | end 50 | 51 | # Recognize built-in special structural pattern 52 | if m.recognize_convex 53 | is_convex = resolve_convex_constr(expr, m, idx) 54 | is_convex && return true 55 | end 56 | 57 | # More patterns goes here 58 | 59 | return false 60 | end 61 | 62 | function expr_is_axn(expr, scalar=1.0, var_idxs=[], power=[]; N=nothing) 63 | 64 | # println("inner recursive input ", expr) 65 | !(expr.args[1] in [:*,:^]) && return nothing, nothing, nothing # Limited Area 66 | 67 | if expr.args[1] == :* 68 | for i in 2:length(expr.args) 69 | if isa(expr.args[i], Float64) || isa(expr.args[i], Int) 70 | scalar *= expr.args[i] 71 | elseif (expr.args[i].head == :ref) 72 | @assert isa(expr.args[i].args[2], Int) 73 | push!(var_idxs, expr.args[i]) 74 | push!(power, 1) 75 | elseif (expr.args[i].head == :call) 76 | scalar, var_idxs, power = expr_is_axn(expr.args[i], scalar, var_idxs, power) 77 | end 78 | end 79 | elseif expr.args[1] == :^ 80 | for i in 2:length(expr.args) 81 | if isa(expr.args[i], Float64) || isa(expr.args[i], Int) 82 | push!(power, expr.args[i]) 83 | continue 84 | elseif (expr.args[i].head == :ref) 85 | push!(var_idxs, expr.args[i]) 86 | continue 87 | elseif (expr.args[i].head == :call) 88 | return nothing, nothing, nothing 89 | end 90 | end 91 | end 92 | 93 | # If the user wants a specific N 94 | !(N == nothing) && !(length(var_idxs) == N) && return nothing, nothing, nothing 95 | (var_idxs == nothing) && (scalar == nothing) && (power == nothing) && return nothing, nothing, nothing # Unrecognized sub-structure 96 | 97 | # println("inner recursive ", scalar, " ", var_idxs) 98 | @assert length(var_idxs) == length(power) 99 | return scalar, var_idxs, power 100 | end 101 | 102 | """ 103 | This function takes a constraint/objective expression and converts it into a affine expression data structure 104 | Use the function to traverse linear expressions traverse_expr_linear_to_affine() 105 | """ 106 | function expr_linear_to_affine(expr) 107 | 108 | # The input should follow :(<=, LHS, RHS) 109 | affdict = Dict() 110 | if expr.args[1] in [:(==), :(>=), :(<=)] # For a constraint expression 111 | @assert isa(expr.args[3], Float64) || isa(expr.args[3], Int) 112 | @assert isa(expr.args[2], Expr) 113 | # non are buffer spaces, not used anywhere 114 | lhscoeff, lhsvars, rhs, non, non = traverse_expr_linear_to_affine(expr.args[2]) 115 | rhs = -rhs + expr.args[3] 116 | affdict[:sense] = expr.args[1] 117 | elseif expr.head == :ref # For single variable objective expression 118 | lhscoeff = [1.0] 119 | lhsvars = [expr] 120 | rhs = 0 121 | affdict[:sense] = nothing 122 | else # For an objective expression 123 | lhscoeff, lhsvars, rhs, non, non = traverse_expr_linear_to_affine(expr) 124 | affdict[:sense] = nothing 125 | end 126 | 127 | affdict[:coefs] = lhscoeff 128 | affdict[:vars] = lhsvars 129 | affdict[:rhs] = rhs 130 | @assert length(affdict[:coefs]) == length(affdict[:vars]) 131 | affdict[:cnt] = length(affdict[:coefs]) 132 | 133 | return affdict 134 | end 135 | 136 | """ 137 | 138 | traverse_expr_linear_to_affine(expr, lhscoeffs=[], lhsvars=[], rhs=0.0, bufferVal=0.0, bufferVar=nothing, sign=1.0, level=0) 139 | 140 | This function traverse a left hand side tree to collect affine terms. 141 | Updated status : possible to handle (x-(x+y(t-z))) cases where signs are handled properly 142 | """ 143 | function traverse_expr_linear_to_affine(expr, lhscoeffs=[], lhsvars=[], rhs=0.0, bufferVal=0.0, bufferVar=nothing, sign=1.0, coef=1.0, level=0) 144 | 145 | # @show expr, coef, bufferVal 146 | 147 | reversor = Dict(true => -1.0, false => 1.0) 148 | function sign_convertor(subexpr, pos) 149 | if length(subexpr.args) == 2 && subexpr.args[1] == :- 150 | return -1.0 151 | elseif length(subexpr.args) > 2 && subexpr.args[1] == :- && pos > 2 152 | return -1.0 153 | end 154 | return 1.0 155 | end 156 | 157 | if isa(expr, Float64) || isa(expr, Int) # Capture any coefficients or right hand side 158 | (bufferVal > 0.0) ? bufferVal *= expr : bufferVal = expr * coef 159 | return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar 160 | elseif expr in [:+, :-] 161 | if bufferVal != 0.0 && bufferVar != nothing 162 | push!(lhscoeffs, bufferVal) 163 | push!(lhsvars, bufferVar) 164 | bufferVal = 0.0 165 | bufferVar = nothing 166 | end 167 | return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar 168 | elseif expr in [:*] 169 | return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar 170 | elseif expr in [:(<=), :(==), :(>=)] 171 | return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar 172 | elseif expr in [:/, :^] 173 | error("Unsupported operators $expr, it is suppose to be affine function") 174 | elseif expr.head == :ref 175 | bufferVar = expr 176 | return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar 177 | end 178 | 179 | # HOTPATCH : Special Structure Recognization 180 | start_pos = 1 181 | if (expr.args[1] == :*) && (length(expr.args) == 3) 182 | if (isa(expr.args[2], Float64) || isa(expr.args[2], Int)) && (expr.args[3].head == :call) 183 | (coef != 0.0) ? coef = expr.args[2] * coef : coef = expr.args[2] # Patch 184 | # coef = expr.args[2] 185 | start_pos = 3 186 | warn("Speicial expression structure detected [*, coef, :call, ...]. Currently handling using a beta fix...", once=true) 187 | end 188 | end 189 | 190 | for i in start_pos:length(expr.args) 191 | lhscoeff, lhsvars, rhs, bufferVal, bufferVar = traverse_expr_linear_to_affine(expr.args[i], lhscoeffs, lhsvars, rhs, bufferVal, bufferVar, sign*sign_convertor(expr, i), coef, level+1) 192 | if expr.args[1] in [:+, :-] # Term segmentation [:-, :+], see this and wrap-up the current (linear) term 193 | if bufferVal != 0.0 && bufferVar != nothing # (sign) * (coef) * (var) => linear term 194 | push!(lhscoeffs, sign*sign_convertor(expr, i)*bufferVal) 195 | push!(lhsvars, bufferVar) 196 | bufferVal = 0.0 197 | bufferVar = nothing 198 | end 199 | if bufferVal != 0.0 && bufferVar == nothing # (sign) * (coef) => right-hand-side term 200 | rhs += sign*sign_convertor(expr, i)*bufferVal 201 | bufferVal = 0.0 202 | end 203 | if bufferVal == 0.0 && bufferVar != nothing && expr.args[1] == :+ 204 | push!(lhscoeffs, sign*1.0*coef) 205 | push!(lhsvars, bufferVar) 206 | bufferVar = nothing 207 | end 208 | if bufferVal == 0.0 && bufferVar != nothing && expr.args[1] == :- 209 | push!(lhscoeffs, sign*sign_convertor(expr, i)*coef) 210 | push!(lhsvars, bufferVar) 211 | bufferVar = nothing 212 | end 213 | elseif expr.args[1] in [:(<=), :(==), :(>=)] 214 | rhs = expr.args[end] 215 | end 216 | end 217 | 218 | if level == 0 219 | if bufferVal != 0.0 && bufferVar != nothing 220 | push!(lhscoeffs, bufferVal) 221 | push!(lhsvars, bufferVar) 222 | bufferVal = 0.0 223 | bufferVar = nothing 224 | end 225 | end 226 | 227 | return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar 228 | end 229 | 230 | """ 231 | Recursive dereferencing of variables in expression graph to overcome JuMP.addNLconstraints() difficulties 232 | """ 233 | function expr_dereferencing(expr, m) 234 | 235 | for i in 2:length(expr.args) 236 | if isa(expr.args[i], Float64) 237 | k = 0 238 | elseif expr.args[i].head == :ref 239 | @assert isa(expr.args[i].args[2], Int) 240 | expr.args[i] = Variable(m, expr.args[i].args[2]) 241 | elseif expr.args[i].head == :call 242 | expr_dereferencing(expr.args[i], m) 243 | else 244 | error("expr_dereferencing :: Unexpected term in expression tree.") 245 | end 246 | end 247 | end 248 | 249 | 250 | """ 251 | This function is trates the sign in expressions trees by cleaning the following structure: 252 | args 253 | ::+ || ::- 254 | ::Call || ::ref 255 | By separating the structure with some dummy treatments 256 | """ 257 | function expr_resolve_sign(expr, level=0; kwargs...) 258 | 259 | resolver = Dict(:- => -1, :+ => 1) 260 | for i in 2:length(expr.args) 261 | if !isa(expr.args[i], Float64) && !isa(expr.args[i], Int) # Skip the coefficients 262 | if length(expr.args[i].args) == 2 && expr.args[i].head == :call 263 | if expr.args[i].args[1] == :- # Treatment for :(-x), replace with :(x*-1) 264 | if expr.args[1] in [:*] # Only perform the treatment when connected with * 265 | push!(expr.args, resolver[expr.args[i].args[1]]) 266 | expr.args[i] = expr.args[i].args[2] 267 | elseif expr.args[1] in [:+, :-, :(==), :(<=), :(>=)] 268 | expr.args[i] = expr.args[i] 269 | else 270 | error("Unexpected operator $(expr.args[i]) during resolving sign") 271 | end 272 | elseif expr.args[i].args[1] == :+ # Treatement for :(+x) replace with :x 273 | expr.args[i] = expr.args[i].args[2] 274 | end 275 | elseif expr.args[i].head == :call 276 | expr_resolve_sign(expr.args[i], level+1) 277 | end 278 | end 279 | end 280 | 281 | return 282 | end 283 | 284 | # This can be a cleaner version of the above function 285 | function expr_resolve_simple_tree(expr) 286 | (isa(expr, Float64) || isa(expr, Int) || isa(expr, Symbol)) && return expr 287 | (expr.head == :ref) && return expr 288 | 289 | if ((expr.args[1] == :-) && (length(expr.args) == 2)) 290 | (isa(expr.args[2], Float64) || isa(expr.args[2], Int)) && return -expr.args[2] 291 | if (expr.args[2].head in [:ref, :call]) 292 | return Expr(:call, :*, -1, expr.args[2]) 293 | end 294 | end 295 | return expr 296 | end 297 | 298 | """ 299 | Recursivly pre-process expression by treating the coefficients 300 | TODO: this function requires a lot of refining. 301 | Most issues can be caused by this function. 302 | """ 303 | function expr_flatten(expr, level=0; kwargs...) 304 | if level > 0 # No trivial constraint is allowed "3>5" 305 | flat = expr_arrangeargs(expr.args) 306 | if isa(flat, Float64) 307 | return flat 308 | else 309 | expr.args = flat 310 | end 311 | end 312 | 313 | for i in 2:length(expr.args) 314 | if isa(expr.args[i], Expr) && expr.args[i].head == :call 315 | expr.args[i] = expr_flatten(expr.args[i], level+1) 316 | end 317 | end 318 | 319 | if level > 0 #Root level process, no additional processes 320 | expr.args = expr_arrangeargs(expr.args) 321 | end 322 | 323 | return expr 324 | end 325 | 326 | """ 327 | Re-arrange childrens by their type. 328 | Only consider 3 types: coefficients(Int64, Float64), :ref, :calls 329 | Sequence arrangement is dependent on operator 330 | """ 331 | function expr_arrangeargs(args::Array; kwargs...) 332 | # A mapping used for operator treatments 333 | reverter = Dict(true=>:-, false=>:+) 334 | operator_coefficient_base = Dict{Symbol, Float64}( 335 | :+ => 0.0, 336 | :- => 0.0, 337 | :/ => 1.0, 338 | :* => 1.0, 339 | :^ => 1.0 340 | ) 341 | 342 | # Treatment => Flatten pure number sub-tree 343 | allFloats = [i for i in 2:length(args) if isa(args[i], Float64)] 344 | if length(allFloats) == length(args)-1 345 | val = args[2] 346 | for i in 3:length(args) 347 | val = eval(args[1])(val, args[i]) 348 | end 349 | return val 350 | end 351 | 352 | if args[1] in [:^, :sin, :cos] 353 | return args 354 | elseif args[1] in [:/] 355 | warn("Partially supported operator $(args[1]) in $(args). Trying to resolve the expression...") 356 | args = expr_resolve_divdenominator(args) 357 | elseif args[1] in [:*, :+, :-] 358 | # Such generic operators can be handled 359 | else 360 | error("Unkown/unspported operator $(args[1])") 361 | end 362 | 363 | if args[1] in [:*, :+, :-] 364 | val = operator_coefficient_base[args[1]] # initialize 365 | exprs = [] 366 | refs = [] 367 | 368 | valinit = false # what is first children : needs consideration with :(-) 369 | refinit = false 370 | callinit = false 371 | 372 | for i in 2:length(args) 373 | if isa(args[i],Float64) || isa(args[i], Int) 374 | valinit += (i==2) 375 | val = eval(args[1])(val, eval(reverter[(i==2) && (args[1]==:-)])(args[i])) # Revert the first sigh if (-) 376 | elseif typeof(args[i]) == Expr 377 | if args[i].head == :ref 378 | refinit += (i==2) 379 | push!(refs, args[i]) 380 | elseif args[i].head == :call 381 | callinit += (i==2) 382 | push!(exprs, args[i]) 383 | else 384 | error("Unkown expression head") 385 | end 386 | else 387 | error("Unkown argument type") 388 | end 389 | end 390 | 391 | # Treatment for 0.0 coefficients 392 | if val == operator_coefficient_base[args[1]] && val == 0.0 393 | val = [] 394 | end 395 | 396 | # Re-arrange children :: without actually doing anything on them 397 | if args[1] in [:*] 398 | return[args[1];val;refs;exprs] 399 | elseif args[1] in [:+, :-] 400 | if Bool(valinit) 401 | return [args[1];val;refs;exprs] 402 | elseif Bool(refinit) 403 | return [args[1];refs;val;exprs] 404 | elseif Bool(callinit) 405 | return [args[1];exprs;refs;val] 406 | else 407 | error("Unexpected condition encountered...") 408 | end 409 | end 410 | else 411 | error("Unsupported expression arguments $args") 412 | end 413 | 414 | return 415 | end 416 | 417 | 418 | """ 419 | Mark expression with "dimensions" of the variable. Augment the tree. 420 | """ 421 | function expr_mark(expr; kwargs...) 422 | 423 | p = 0 424 | for i in 2:length(expr.args) 425 | if isa(expr.args[i], Expr) && expr.args[i].head == :ref 426 | if expr.args[1] in [:+, :-] 427 | p = max(p, 1) 428 | expr.args[i].typ = 1 429 | elseif expr.args[1] in [:*] 430 | p = +(p, 1) 431 | expr.args[i].typ = 1 432 | elseif expr.args[1] in [:^] # Consider leaf when encounter :^ 433 | p = expr_dim_enquiry(expr) 434 | elseif expr.args[1] in [:/] 435 | error("Does not support :/ and :^ yet.") 436 | end 437 | elseif isa(expr.args[i], Expr) && expr.args[i].head == :call 438 | if expr.args[1] in [:+,:-] 439 | innerp = expr_mark(expr.args[i]) 440 | p = max(p, innerp) 441 | expr.args[i].typ = innerp 442 | elseif expr.args[1] in [:*] 443 | innerp = expr_mark(expr.args[i]) 444 | p = +(p, innerp) 445 | expr.args[i].typ = innerp 446 | elseif expr.args[1] in [:/, :^] 447 | error("Does not support :/ and :^ yet.") 448 | end 449 | elseif isa(expr.args[i],Float64) || isa(expr.args[i],Int) 450 | p = p 451 | else 452 | error("Unexpected expression node encouter $(expr.args[i])") 453 | end 454 | end 455 | 456 | return p 457 | end 458 | 459 | """ 460 | A special check when encouter :^. Cleaner function 461 | """ 462 | function expr_dim_enquiry(expr) 463 | @assert expr.args[1] == :^ 464 | @assert length(expr.args) == 3 # Don't take cray :call with :^ 465 | if isa(expr.args[2], Float64) 466 | return expr.args[2] 467 | else 468 | return expr.args[3] 469 | end 470 | end 471 | 472 | """ 473 | Check if a sub-tree is linear or not 474 | """ 475 | function expr_islinear(expr) 476 | if expr_mark(expr) > 1 477 | return False 478 | else 479 | return True 480 | end 481 | end 482 | 483 | """ 484 | Check if a sub-tree is pure constant or not 485 | """ 486 | function expr_resolve_const(expr) 487 | 488 | for i in 1:length(expr.args) 489 | if isa(expr.args[i], Float64) || isa(expr.args[i], Int) || isa(expr.args[i], Symbol) 490 | continue 491 | elseif expr.args[i].head == :call 492 | (expr_isconst(expr.args[i])) && (expr.args[i] = eval(expr.args[i])) 493 | if isa(expr.args[i], Float64) || isa(expr.args[i], Int) || isa(expr.args[i], Symbol) 494 | continue 495 | else 496 | expr_resolve_const(expr.args[i]) 497 | end 498 | end 499 | end 500 | 501 | return 502 | end 503 | 504 | """ 505 | Check if a division's denominator is pure numerical and replace it with a multiply 506 | """ 507 | function expr_resolve_divdenominator(args) 508 | 509 | @assert args[1] == :/ 510 | @assert length(args) == 3 511 | resolvable = true 512 | 513 | for i in 3:length(args) 514 | resolvable *= expr_isconst(args[i]) 515 | end 516 | 517 | if resolvable 518 | for i in 3:length(args) 519 | args[i]=1/eval(args[i]) 520 | end 521 | args[1] = :* 522 | end 523 | 524 | return args 525 | end 526 | 527 | """ 528 | Check if a sub-tree(:call) is totally composed of constant values 529 | """ 530 | function expr_isconst(expr) 531 | 532 | (isa(expr, Float64) || isa(expr, Int) || isa(expr, Symbol)) && return true 533 | 534 | const_tree = true 535 | for i in 1:length(expr.args) 536 | if isa(expr.args[i], Float64) || isa(expr.args[i], Int) || isa(expr.args[i], Symbol) 537 | continue 538 | elseif expr.args[i].head == :call 539 | const_tree *= expr_isconst(expr.args[i]) 540 | elseif expr.args[i].head == :ref 541 | return false 542 | end 543 | end 544 | 545 | return const_tree 546 | end 547 | -------------------------------------------------------------------------------- /test/algorithm.jl: -------------------------------------------------------------------------------- 1 | @testset "Solving algorithm tests" begin 2 | 3 | @testset " Validation Test || AMP-TMC || basic solve || exampls/nlp1.jl" begin 4 | 5 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 6 | mip_solver=GurobiSolver(OutputFlag=0), 7 | bilinear_convexhull=false, 8 | monomial_convexhull=false, 9 | presolve_bound_tightening=false, 10 | presolve_bt_output_tol=1e-1, 11 | log_level=0) 12 | m = nlp1(solver=test_solver) 13 | status = solve(m) 14 | 15 | @test status == :Optimal 16 | @test isapprox(m.objVal, 58.38367169858795; atol=1e-4) 17 | @test m.internalModel.logs[:n_iter] == 7 18 | 19 | # Out-dated tests base on initial implementation [PASSED] 20 | # @test isapprox(m.internalModel.logs[:obj][1], 58.38367118523376; atol=1e-3) 21 | # @test isapprox(m.internalModel.logs[:obj][2], 58.38367118523376; atol=1e-3) 22 | # @test isapprox(m.internalModel.logs[:obj][3], 58.38367118523376; atol=1e-3) 23 | # @test isapprox(m.internalModel.logs[:obj][4], 58.38367118523376; atol=1e-3) 24 | # @test isapprox(m.internalModel.logs[:obj][5], 58.38367118523376; atol=1e-3) 25 | # @test isapprox(m.internalModel.logs[:obj][6], 58.38367118523376; atol=1e-3) 26 | # @test isapprox(m.internalModel.logs[:obj][7], 58.38367118523376; atol=1e-3) 27 | # 28 | # @test isapprox(m.internalModel.logs[:bound][1], 25.7091; atol=1e-3) 29 | # @test isapprox(m.internalModel.logs[:bound][2], 51.5131; atol=1e-3) 30 | # @test isapprox(m.internalModel.logs[:bound][3], 56.5857; atol=1e-3) 31 | # @test isapprox(m.internalModel.logs[:bound][4], 58.2394; atol=1e-3) 32 | # @test isapprox(m.internalModel.logs[:bound][5], 58.3611; atol=1e-3) 33 | # @test isapprox(m.internalModel.logs[:bound][6], 58.3681; atol=1e-3) 34 | # @test isapprox(m.internalModel.logs[:bound][7], 58.3824; atol=1e-3) 35 | 36 | # Out-dated tests base on initial implementation [PASSED] 37 | # Testing discretizations for x1 and x2 38 | # ans = Dict() 39 | # ans[1] = [1.0,1.12035,2.10076,2.43359,2.51991,2.52774,2.55118,2.60884,2.64305,2.75053,2.89483,3.02324,4.80577, 10.0] 40 | # ans[2] = [1.0,1.13852,2.26689,2.64995,2.89107,3.02633,3.06707,3.13215,3.15649,3.18081,3.3286,5.38017, 10.0] 41 | # 42 | # @test length(ans[1]) == length(m.internalModel.discretization[1]) 43 | # @test length(ans[2]) == length(m.internalModel.discretization[2]) 44 | # for i in 1:length(ans[1]) 45 | # @test isapprox(ans[1][i], m.internalModel.discretization[1][i]; atol=1e-1) 46 | # end 47 | # for i in 1:length(ans[2]) 48 | # @test isapprox(ans[2][i], m.internalModel.discretization[2][i]; atol=1e-1) 49 | # end 50 | end 51 | 52 | @testset " Validation Test || AMP-TMC || basic solve || examples/nlp3.jl (3 iterations)" begin 53 | 54 | test_solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 55 | mip_solver=CbcSolver(logLevel=0), 56 | bilinear_convexhull=false, 57 | monomial_convexhull=false, 58 | log_level=0, 59 | maxiter=3, 60 | presolve_bt_width_tol=1e-3, 61 | presolve_bound_tightening=false, 62 | discretization_var_pick_algo=0) 63 | m = nlp3(solver=test_solver) 64 | status = solve(m) 65 | 66 | @test status == :UserLimits 67 | 68 | @test isapprox(m.objVal, 7049.247897696512; atol=1e-4) 69 | @test m.internalModel.logs[:n_iter] == 3 70 | 71 | # Out-dated tests base on initial implementation [PASSED] 72 | # @test isapprox(m.internalModel.logs[:bound][1], 3183.6748; atol=1e-3) 73 | # @test isapprox(m.internalModel.logs[:bound][2], 5105.0043; atol=1e-3) 74 | # @test isapprox(m.internalModel.logs[:bound][3], 6485.8829; atol=1e-3) 75 | # 76 | # ans = Dict() 77 | # ans[1] = [100.0, 424.721, 838.577, 3054.31, 10000.0] 78 | # ans[2] = [1000.0, 1216.0, 1864.0, 3609.97, 10000.0] 79 | # ans[3] = [1000.0, 1407.17, 2337.16, 2859.97, 4989.93, 7359.97, 10000.0] 80 | # ans[4] = [10.0, 87.0177, 132.916, 168.929, 277.018, 390.0] 81 | # ans[5] = [10.0, 103.101, 185.828, 214.871, 311.121, 378.328, 488.101, 780.0] 82 | # ans[6] = [10.0, 122.982, 231.071, 267.084, 312.982, 390.0] 83 | # ans[7] = [10.0, 93.9165, 143.1, 242.272, 335.6, 478.917, 780.0] 84 | # ans[8] = [10.0, 178.101, 273.328, 308.621, 417.371, 490.828, 613.101, 880.0] 85 | # 86 | # for i in 1:8 87 | # for j in 1:length(ans[i]) 88 | # @test length(ans[i]) == length(m.internalModel.discretization[i]) 89 | # @test isapprox(ans[i][j], m.internalModel.discretization[i][j]; atol=1e-1) 90 | # end 91 | # end 92 | end 93 | 94 | @testset " Validation Test || BT-AMP-TMC || basic solve || examples/nlp1.jl " begin 95 | 96 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 97 | mip_solver=GurobiSolver(OutputFlag=0), 98 | bilinear_convexhull=false, 99 | monomial_convexhull=false, 100 | presolve_bound_tightening=true, 101 | presolve_bound_tightening_algo=1, 102 | presolve_bt_output_tol=1e-1, 103 | log_level=0) 104 | 105 | m = nlp1(solver=test_solver) 106 | status = solve(m) 107 | 108 | @test status == :Optimal 109 | @test isapprox(m.objVal, 58.38367169858795; atol=1e-4) 110 | @test m.internalModel.logs[:n_iter] == 3 111 | 112 | # Out-dated tests base on initial implementation [PASSED] 113 | # @test isapprox(m.internalModel.l_var_tight[1], 2.4; atol=1e-2) 114 | # @test isapprox(m.internalModel.l_var_tight[2], 3.0; atol=1e-2) 115 | # @test isapprox(m.internalModel.l_var_tight[3], 5.76; atol=1e-2) 116 | # @test isapprox(m.internalModel.l_var_tight[4], 9.0; atol=1e-2) 117 | # @test isapprox(m.internalModel.l_var_tight[5], 8.0; atol=1e-2) 118 | # 119 | # @test isapprox(m.internalModel.u_var_tight[1], 2.7; atol=1e-2) 120 | # @test isapprox(m.internalModel.u_var_tight[2], 3.3; atol=1e-2) 121 | # @test isapprox(m.internalModel.u_var_tight[3], 7.29; atol=1e-2) 122 | # @test isapprox(m.internalModel.u_var_tight[4], 10.89; atol=1e-2) 123 | # @test isapprox(m.internalModel.u_var_tight[5], 8.91; atol=1e-2) 124 | end 125 | 126 | @testset " Validation Test || PBT-AMP-TMC || basic solve || exampls/nlp1.jl" begin 127 | 128 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 129 | mip_solver=GurobiSolver(OutputFlag=0), 130 | bilinear_convexhull=false, 131 | monomial_convexhull=false, 132 | presolve_bound_tightening=true, 133 | presolve_bound_tightening_algo=2, 134 | presolve_bt_output_tol=1e-1, 135 | log_level=0) 136 | m = nlp1(solver=test_solver) 137 | status = solve(m) 138 | 139 | # @show m.internalModel.l_var_tight 140 | # @show m.internalModel.u_var_tight 141 | 142 | @test status == :Optimal 143 | @test isapprox(m.objVal, 58.38367169858795; atol=1e-4) 144 | @test m.internalModel.logs[:n_iter] == 2 145 | 146 | # Out-dated tests base on initial implementation [PASSED] 147 | # @show m.internalModel.l_var_tight 148 | # @test isapprox(m.internalModel.l_var_tight[1], 2.5; atol=1e-2) 149 | # @test isapprox(m.internalModel.l_var_tight[2], 3.1; atol=1e-2) 150 | # @test isapprox(m.internalModel.l_var_tight[3], 6.25; atol=1e-2) 151 | # @test isapprox(m.internalModel.l_var_tight[4], 9.61; atol=1e-2) 152 | # @test isapprox(m.internalModel.l_var_tight[5], 8.0; atol=1e-2) 153 | # 154 | # @test isapprox(m.internalModel.u_var_tight[1], 2.6; atol=1e-2) 155 | # @test isapprox(m.internalModel.u_var_tight[2], 3.2; atol=1e-2) 156 | # @test isapprox(m.internalModel.u_var_tight[3], 6.76; atol=1e-2) 157 | # @test isapprox(m.internalModel.u_var_tight[4], 10.24; atol=1e-2) 158 | # @test isapprox(m.internalModel.u_var_tight[5], 8.32; atol=1e-2) 159 | end 160 | 161 | @testset " Validation Test || BT-AMP-TMC || basic solve || examples/nlp3.jl" begin 162 | 163 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 164 | mip_solver=CbcSolver(logLevel=0), 165 | bilinear_convexhull=false, 166 | log_level=0, 167 | maxiter=3, 168 | presolve_bt_width_tol=1e-3, 169 | presolve_bt_output_tol=1e-1, 170 | presolve_bound_tightening=true, 171 | presolve_bound_tightening_algo=1, 172 | presolve_maxiter=2, 173 | discretization_var_pick_algo=max_cover_var_picker) 174 | m = nlp3(solver=test_solver) 175 | 176 | status = solve(m) 177 | 178 | @test status == :UserLimits 179 | @show m.objVal 180 | @test m.internalModel.logs[:n_iter] == 3 181 | @test m.internalModel.logs[:bt_iter] == 2 182 | 183 | # @test isapprox(m.internalModel.u_var_tight[1], 4640.4; atol=1e-2) 184 | # @test isapprox(m.internalModel.u_var_tight[2], 5634.2; atol=1e-2) 185 | # @test isapprox(m.internalModel.u_var_tight[3], 5920.1; atol=1e-2) 186 | # @test isapprox(m.internalModel.u_var_tight[4], 334.2; atol=1e-2) 187 | # @test isapprox(m.internalModel.u_var_tight[5], 588.4; atol=1e-2) 188 | # @test isapprox(m.internalModel.u_var_tight[6], 390.0; atol=1e-2) 189 | # @test isapprox(m.internalModel.u_var_tight[7], 626.3; atol=1e-2) 190 | # @test isapprox(m.internalModel.u_var_tight[8], 688.4; atol=1e-2) 191 | # 192 | # @test isapprox(m.internalModel.l_var_tight[1], 100.0; atol=1e-2) 193 | # @test isapprox(m.internalModel.l_var_tight[2], 1000.0; atol=1e-2) 194 | # @test isapprox(m.internalModel.l_var_tight[3], 1000.0; atol=1e-2) 195 | # @test isapprox(m.internalModel.l_var_tight[4], 10.0; atol=1e-2) 196 | # @test isapprox(m.internalModel.l_var_tight[5], 107.8; atol=1e-2) 197 | # @test isapprox(m.internalModel.l_var_tight[6], 10.0; atol=1e-2) 198 | # @test isapprox(m.internalModel.l_var_tight[7], 16.9; atol=1e-2) 199 | # @test isapprox(m.internalModel.l_var_tight[8], 89.2; atol=1e-2) 200 | end 201 | 202 | @testset " Validation Test || PBT-AMP-TMC || basic solve || examples/nlp3.jl" begin 203 | 204 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 205 | mip_solver=CbcSolver(logLevel=0), 206 | bilinear_convexhull=false, 207 | log_level=0, 208 | maxiter=2, 209 | presolve_bound_tightening=true, 210 | presolve_bt_width_tol=1e-3, 211 | presolve_bt_output_tol=1e-1, 212 | presolve_bound_tightening_algo=2, 213 | presolve_maxiter=2, 214 | discretization_var_pick_algo=max_cover_var_picker) 215 | 216 | m = nlp3(solver=test_solver) 217 | status = solve(m) 218 | @test status == :UserLimits 219 | @test m.internalModel.logs[:n_iter] == 2 220 | @test m.internalModel.logs[:bt_iter] == 2 221 | 222 | # @test isapprox(m.internalModel.u_var_tight[1], 3011.9; atol=1e-1) 223 | # @test isapprox(m.internalModel.u_var_tight[2], 3951.3; atol=1e-1) 224 | # @test isapprox(m.internalModel.u_var_tight[3], 5836.4; atol=1e-1) 225 | # @test isapprox(m.internalModel.u_var_tight[4], 262.1; atol=1e-1) 226 | # @test isapprox(m.internalModel.u_var_tight[5], 390.5; atol=1e-1) 227 | # @test isapprox(m.internalModel.u_var_tight[6], 388.0; atol=1e-1) 228 | # @test isapprox(m.internalModel.u_var_tight[7], 370.9; atol=1e-1) 229 | # @test isapprox(m.internalModel.u_var_tight[8], 490.5; atol=1e-1) 230 | # 231 | # @test isapprox(m.internalModel.l_var_tight[1], 100.0; atol=1e-1) 232 | # @test isapprox(m.internalModel.l_var_tight[2], 1000.0; atol=1e-1) 233 | # @test isapprox(m.internalModel.l_var_tight[3], 2241.3; atol=1e-1) 234 | # @test isapprox(m.internalModel.l_var_tight[4], 12.0; atol=1e-1) 235 | # @test isapprox(m.internalModel.l_var_tight[5], 252.0; atol=1e-1) 236 | # @test isapprox(m.internalModel.l_var_tight[6], 10.0; atol=1e-1) 237 | # @test isapprox(m.internalModel.l_var_tight[7], 100.2; atol=1e-1) 238 | # @test isapprox(m.internalModel.l_var_tight[8], 352.0; atol=1e-1) 239 | end 240 | 241 | @testset " Validation Test || AMP-CONV || basic solve || examples/nlp1.jl" begin 242 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 243 | mip_solver=GurobiSolver(OutputFlag=0), 244 | bilinear_convexhull=true, 245 | monomial_convexhull=true, 246 | presolve_bound_tightening=false, 247 | log_level=0) 248 | m = nlp1(solver=test_solver) 249 | status = solve(m) 250 | 251 | @test status == :Optimal 252 | @test isapprox(m.objVal, 58.38367169858795; atol=1e-4) 253 | @test m.internalModel.logs[:n_iter] == 7 254 | end 255 | 256 | @testset " Validation Test || BT-AMP-CONV || basic solve || examples/nlp1.jl" begin 257 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 258 | mip_solver=GurobiSolver(OutputFlag=0), 259 | bilinear_convexhull=true, 260 | monomial_convexhull=true, 261 | presolve_bound_tightening=true, 262 | presolve_bound_tightening_algo=1, 263 | log_level=0) 264 | m = nlp1(solver=test_solver) 265 | status = solve(m) 266 | 267 | @test status == :Optimal 268 | @test isapprox(m.objVal, 58.38367169858795; atol=1e-4) 269 | @test m.internalModel.logs[:n_iter] == 1 270 | end 271 | 272 | @testset " Validation Test || PBT-AMP-CONV || basic solve || examples/nlp1.jl" begin 273 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 274 | mip_solver=GurobiSolver(OutputFlag=0), 275 | bilinear_convexhull=true, 276 | monomial_convexhull=true, 277 | presolve_bound_tightening=true, 278 | presolve_bound_tightening_algo=2, 279 | log_level=0) 280 | m = nlp1(solver=test_solver) 281 | status = solve(m) 282 | 283 | @test status == :Optimal 284 | @test isapprox(m.objVal, 58.38367169858795; atol=1e-4) 285 | @test m.internalModel.logs[:n_iter] == 1 286 | end 287 | 288 | @testset " Validation Test || AMP-CONV || basic solve || examples/nlp3.jl" begin 289 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 290 | mip_solver=GurobiSolver(OutputFlag=0), 291 | bilinear_convexhull=true, 292 | monomial_convexhull=true, 293 | presolve_bound_tightening=false, 294 | log_level=0) 295 | m = nlp3(solver=test_solver) 296 | status = solve(m) 297 | 298 | @test status == :Optimal 299 | @test isapprox(m.objVal, 7049.247897696188; atol=1e-4) 300 | @test m.internalModel.logs[:n_iter] == 9 301 | end 302 | 303 | @testset " Validation Test || BT-AMP-CONV || basic solve || examples/nlp3.jl" begin 304 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 305 | mip_solver=GurobiSolver(OutputFlag=0), 306 | bilinear_convexhull=true, 307 | monomial_convexhull=true, 308 | presolve_bound_tightening=true, 309 | presolve_bound_tightening_algo=1, 310 | log_level=0) 311 | m = nlp3(solver=test_solver) 312 | status = solve(m) 313 | 314 | @test status == :Optimal 315 | @test isapprox(m.objVal, 7049.247897696188; atol=1e-4) 316 | @test m.internalModel.logs[:n_iter] == 9 317 | end 318 | 319 | @testset "Validation Test || PBT-AMP-CONV || basic solve || examples/nlp3.jl" begin 320 | test_solver = PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 321 | mip_solver=GurobiSolver(OutputFlag=0), 322 | bilinear_convexhull=true, 323 | monomial_convexhull=true, 324 | presolve_bound_tightening=true, 325 | presolve_bound_tightening_algo=2, 326 | log_level=0) 327 | m = nlp3(solver=test_solver) 328 | status = solve(m) 329 | 330 | @test status == :Optimal 331 | @test isapprox(m.objVal, 7049.247897696188; atol=1e-4) 332 | @test m.internalModel.logs[:n_iter] == 1 333 | end 334 | 335 | @testset " Validation Test || AMP || special problem || ... " begin 336 | test_solver=PODSolver(nlp_local_solver=IpoptSolver(print_level=0), 337 | mip_solver=GurobiSolver(OutputFlag=0), 338 | discretization_abs_width_tol=1e-2, 339 | discretization_ratio=8, 340 | presolve_bound_tightening = false, 341 | presolve_bound_tightening_algo = 1, 342 | presolve_bt_output_tol = 1e-1, 343 | log_level=0) 344 | 345 | m = circle(solver=test_solver) 346 | solve(m) 347 | @test isapprox(m.objVal, 1.4142135534556992; atol=1e-3) 348 | end 349 | end 350 | -------------------------------------------------------------------------------- /src/utility.jl: -------------------------------------------------------------------------------- 1 | """ 2 | update_rel_gap(m::PODNonlinearModel) 3 | 4 | Update POD model relative & absolute optimality gap. 5 | 6 | The relative gap calculation is 7 | 8 | ```math 9 | \\textbf{Gap} = \\frac{|UB-LB|}{ϵ+|UB|} 10 | ``` 11 | 12 | The absolute gap calculation is 13 | ``` 14 | |UB-LB| 15 | ``` 16 | """ 17 | function update_opt_gap(m::PODNonlinearModel) 18 | 19 | if m.best_obj in [Inf, -Inf] 20 | m.best_rel_gap = Inf 21 | return 22 | else 23 | p = round(abs(log(10,m.rel_gap))) 24 | n = round(abs(m.best_obj-m.best_bound), Int(p)) 25 | dn = round(abs(1e-12+abs(m.best_obj)), Int(p)) 26 | if (n == 0.0) && (dn == 0.0) 27 | m.best_rel_gap = 0.0 28 | return 29 | end 30 | m.best_rel_gap = abs(m.best_obj - m.best_bound)/(m.tol+abs(m.best_obj)) 31 | end 32 | 33 | # absoluate or anyother bound calculation shows here... 34 | 35 | return 36 | end 37 | 38 | """ 39 | discretization_to_bounds(d::Dict, l::Int) 40 | 41 | Same as [`update_var_bounds`](@ref) 42 | """ 43 | discretization_to_bounds(d::Dict, l::Int) = update_var_bounds(d, len=l) 44 | 45 | 46 | """ 47 | Utility function for debugging. 48 | """ 49 | function show_solution(m::JuMP.Model) 50 | for i in 1:length(m.colNames) 51 | println("$(m.colNames[i])=$(m.colVal[i])") 52 | end 53 | return 54 | end 55 | 56 | """ 57 | initialize_discretization(m::PODNonlinearModel) 58 | 59 | This function initialize the dynamic discretization used for any bounding models. By default, it takes (.l_var_orig, .u_var_orig) as the base information. User is allowed to use alternative bounds for initializing the discretization dictionary. 60 | The output is a dictionary with MathProgBase variable indices keys attached to the :PODNonlinearModel.discretization. 61 | """ 62 | function initialize_discretization(m::PODNonlinearModel; kwargs...) 63 | 64 | options = Dict(kwargs) 65 | 66 | for var in 1:(m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip) 67 | lb = m.l_var_tight[var] 68 | ub = m.u_var_tight[var] 69 | m.discretization[var] = [lb, ub] 70 | end 71 | 72 | return 73 | end 74 | 75 | """ 76 | 77 | to_discretization(m::PODNonlinearModel, lbs::Vector{Float64}, ubs::Vector{Float64}) 78 | 79 | Utility functions to convert bounds vectors to Dictionary based structures that is more suitable for 80 | partition operations. 81 | 82 | """ 83 | function to_discretization(m::PODNonlinearModel, lbs::Vector{Float64}, ubs::Vector{Float64}; kwargs...) 84 | 85 | options = Dict(kwargs) 86 | 87 | @assert length(lbs) == length(ubs) 88 | var_discretization = Dict() 89 | for var in 1:m.num_var_orig 90 | lb = lbs[var] 91 | ub = ubs[var] 92 | var_discretization[var] = [lb, ub] 93 | end 94 | 95 | if length(lbs) == (m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip) 96 | for var in (1+m.num_var_orig):(m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip) 97 | lb = lbs[var] 98 | ub = ubs[var] 99 | var_discretization[var] = [lb, ub] 100 | end 101 | else 102 | for var in (1+m.num_var_orig):(m.num_var_orig+m.num_var_linear_lifted_mip+m.num_var_nonlinear_lifted_mip) 103 | lb = -Inf 104 | ub = Inf 105 | var_discretization[var] = [lb, ub] 106 | end 107 | end 108 | 109 | return var_discretization 110 | end 111 | 112 | """ 113 | flatten_discretization(discretization::Dict) 114 | 115 | Utility functions to eliminate all partition on discretizing variable and keep the loose bounds. 116 | 117 | """ 118 | function flatten_discretization(discretization::Dict; kwargs...) 119 | 120 | flatten_discretization = Dict() 121 | for var in keys(discretization) 122 | flatten_discretization[var] = [discretization[var][1],discretization[var][end]] 123 | end 124 | 125 | return flatten_discretization 126 | end 127 | 128 | """ 129 | 130 | update_mip_time_limit(m::PODNonlinearModel) 131 | 132 | An utility function used to dynamically regulate MILP solver time limits to fit POD solver time limits. 133 | """ 134 | function update_mip_time_limit(m::PODNonlinearModel; kwargs...) 135 | 136 | options = Dict(kwargs) 137 | haskey(options, :timelimit) ? timelimit = options[:timelimit] : timelimit = max(0.0, m.timeout-m.logs[:total_time]) 138 | 139 | if m.mip_solver_identifier == "CPLEX" 140 | insert_timeleft_symbol(m.mip_solver.options,timelimit,:CPX_PARAM_TILIM,m.timeout) 141 | elseif m.mip_solver_identifier == "Gurobi" 142 | insert_timeleft_symbol(m.mip_solver.options,timelimit,:TimeLimit,m.timeout) 143 | elseif m.mip_solver_identifier == "Cbc" 144 | insert_timeleft_symbol(m.mip_solver.options,timelimit,:seconds,m.timeout) 145 | elseif m.mip_solver_identifier == "GLPK" 146 | insert_timeleft_symbol(m.mip_solver.opt) 147 | elseif m.mip_solver_identifier == "Pajarito" 148 | (timelimit < Inf) && (m.mip_solver.timeout = timelimit) 149 | else 150 | error("Needs support for this MIP solver") 151 | end 152 | 153 | return 154 | end 155 | 156 | """ 157 | 158 | update_mip_time_limit(m::PODNonlinearModel) 159 | 160 | An utility function used to dynamically regulate MILP solver time limits to fit POD solver time limits. 161 | """ 162 | function update_nlp_time_limit(m::PODNonlinearModel; kwargs...) 163 | 164 | options = Dict(kwargs) 165 | haskey(options, :timelimit) ? timelimit = options[:timelimit] : timelimit = max(0.0, m.timeout-m.logs[:total_time]) 166 | 167 | if m.nlp_local_solver_identifier == "Ipopt" 168 | insert_timeleft_symbol(m.nlp_local_solver.options,timelimit,:CPX_PARAM_TILIM,m.timeout) 169 | elseif m.nlp_local_solver_identifier == "Pajarito" 170 | (timeout < Inf) && (m.nlp_local_solver.timeout = timelimit) 171 | elseif m.nlp_local_solver_identifier == "AmplNL" 172 | insert_timeleft_symbol(m.nlp_local_solver.options,timelimit,:seconds,m.timeout, options_string_type=2) 173 | elseif m.nlp_local_solver_identifier == "Knitro" 174 | error("You never tell me anything about knitro. Probably because they charge everything they own.") 175 | elseif m.nlp_local_solver_identifier == "NLopt" 176 | m.nlp_local_solver.maxtime = timelimit 177 | else 178 | error("Needs support for this MIP solver") 179 | end 180 | 181 | return 182 | end 183 | 184 | """ 185 | 186 | update_mip_time_limit(m::PODNonlinearModel) 187 | 188 | An utility function used to dynamically regulate MILP solver time limits to fit POD solver time limits. 189 | """ 190 | function update_minlp_time_limit(m::PODNonlinearModel; kwargs...) 191 | 192 | options = Dict(kwargs) 193 | haskey(options, :timelimit) ? timelimit = options[:timelimit] : timelimit = max(0.0, m.timeout-m.logs[:total_time]) 194 | 195 | if m.minlp_local_solver_identifier == "Pajarito" 196 | (timeout < Inf) && (m.minlp_local_solver.timeout = timelimit) 197 | elseif m.minlp_local_solver_identifier == "AmplNL" 198 | insert_timeleft_symbol(m.minlp_local_solver.options,timelimit,:seconds,m.timeout,options_string_type=2) 199 | elseif m.minlp_local_solver_identifier == "Knitro" 200 | error("You never tell me anything about knitro. Probably because they charge everything they own.") 201 | elseif m.minlp_local_solver_identifier == "NLopt" 202 | m.minlp_local_solver.maxtime = timelimit 203 | else 204 | error("Needs support for this MIP solver") 205 | end 206 | 207 | return 208 | end 209 | 210 | """ 211 | @docstring 212 | """ 213 | function insert_timeleft_symbol(options, val::Float64, keywords::Symbol, timeout; options_string_type=1) 214 | for i in 1:length(options) 215 | if options_string_type == 1 216 | if keywords in collect(options[i]) 217 | deleteat!(options, i) 218 | end 219 | elseif options_string_type == 2 220 | if keywords == split(options[i],"=")[1] 221 | deleteat!(options, i) 222 | end 223 | end 224 | end 225 | 226 | if options_string_type == 1 227 | (timeout != Inf) && push!(options, (keywords, val)) 228 | elseif options_string_type == 2 229 | (timeout != Inf) && push!(options, "$(keywords)=$(val)") 230 | end 231 | return 232 | end 233 | 234 | """ 235 | fetch_boundstop_symbol(m::PODNonlinearModel) 236 | 237 | An utility function used to recongize different sub-solvers and return the bound stop option key words 238 | """ 239 | function update_boundstop_options(m::PODNonlinearModel) 240 | 241 | 242 | # # Calculation of the bound 243 | # if m.sense_orig == :Min 244 | # stopbound = (1-m.rel_gap+m.tol) * m.best_obj 245 | # elseif m.sense_orig == :Max 246 | # stopbound = (1+m.rel_gap-m.tol) * m.best_obj 247 | # end 248 | # 249 | # for i in 1:length(m.mip_solver.options) 250 | # if m.mip_solver.options[i][1] == :BestBdStop 251 | # deleteat!(m.mip_solver.options, i) 252 | # if string(m.mip_solver)[1:6] == "Gurobi" 253 | # push!(m.mip_solver.options, (:BestBdStop, stopbound)) 254 | # else 255 | # return 256 | # end 257 | # end 258 | # end 259 | # 260 | # if string(m.mip_solver)[1:6] == "Gurobi" 261 | # push!(m.mip_solver.options, (:BestBdStop, stopbound)) 262 | # else 263 | # return 264 | # end 265 | 266 | return 267 | end 268 | 269 | 270 | """ 271 | check_solution_history(m::PODNonlinearModel, ind::Int) 272 | 273 | Check if the solution is alwasy the same within the last discretization_consecutive_forbid iterations. Return true if suolution in invariant. 274 | """ 275 | function check_solution_history(m::PODNonlinearModel, ind::Int) 276 | 277 | (m.logs[:n_iter] < m.discretization_consecutive_forbid) && return false 278 | 279 | sol_val = m.bound_sol_history[mod(m.logs[:n_iter]-1, m.discretization_consecutive_forbid)+1][ind] 280 | for i in 1:(m.discretization_consecutive_forbid-1) 281 | search_pos = mod(m.logs[:n_iter]-1-i, m.discretization_consecutive_forbid)+1 282 | !isapprox(sol_val, m.bound_sol_history[search_pos][ind]; atol=m.discretization_rel_width_tol) && return false 283 | end 284 | return true 285 | end 286 | 287 | """ 288 | 289 | fix_domains(m::PODNonlinearModel) 290 | 291 | This function is used to fix variables to certain domains during the local solve process in the [`global_solve`](@ref). 292 | More specifically, it is used in [`local_solve`](@ref) to fix binary and integer variables to lower bound solutions 293 | and discretizing varibles to the active domain according to lower bound solution. 294 | """ 295 | function fix_domains(m::PODNonlinearModel; kwargs...) 296 | 297 | l_var = copy(m.l_var_orig) 298 | u_var = copy(m.u_var_orig) 299 | for i in 1:m.num_var_orig 300 | if i in m.var_discretization_mip 301 | point = m.sol_incumb_lb[i] 302 | for j in 1:length(m.discretization[i]) 303 | if point >= (m.discretization[i][j] - m.tol) && (point <= m.discretization[i][j+1] + m.tol) 304 | @assert j < length(m.discretization[i]) 305 | l_var[i] = m.discretization[i][j] 306 | u_var[i] = m.discretization[i][j+1] 307 | break 308 | end 309 | end 310 | elseif m.var_type_orig[i] == :Bin || m.var_type_orig[i] == :Int 311 | l_var[i] = round(m.sol_incumb_lb[i]) 312 | u_var[i] = round(m.sol_incumb_lb[i]) 313 | end 314 | end 315 | 316 | return l_var, u_var 317 | end 318 | 319 | """ 320 | convexification_exam(m::PODNonlinearModel) 321 | """ 322 | function convexification_exam(m::PODNonlinearModel) 323 | 324 | # Other more advanced convexification check goes here 325 | for term in keys(m.nonlinear_terms) 326 | if !m.nonlinear_terms[term][:convexified] 327 | warn("Detected terms that is not convexified $(term[:lifted_constr_ref]), bounding model solver may report a error due to this") 328 | return 329 | else 330 | m.nonlinear_terms[term][:convexified] = false # Reset status for next iteration 331 | end 332 | end 333 | 334 | return 335 | end 336 | 337 | """ 338 | pick_vars_discretization(m::PODNonlinearModel) 339 | 340 | This function helps pick the variables for discretization. The method chosen depends on user-inputs. 341 | In case when `indices::Int` is provided, the method is chosen as built-in method. Currently, 342 | there exist two built-in method: 343 | 344 | * `max-cover(m.discretization_var_pick_algo=0, default)`: pick all variables involved in the non-linear term for discretization 345 | * `min-vertex-cover(m.discretization_var_pick_algo=1)`: pick a minimum vertex cover for variables involved in non-linear terms so that each non-linear term is at least convexified 346 | 347 | For advance usage, `m.discretization_var_pick_algo` allows `::Function` inputs. User is required to perform flexible methods in choosing the non-linear variable. 348 | For more information, read more details at [Hacking Solver](@ref). 349 | 350 | """ 351 | function pick_vars_discretization(m::PODNonlinearModel) 352 | 353 | if isa(m.discretization_var_pick_algo, Function) 354 | (m.log_level > 0) && println("using method $(m.discretization_var_pick_algo) for picking discretization variable...") 355 | eval(m.discretization_var_pick_algo)(m) 356 | (length(m.var_discretization_mip) == 0 && length(m.nonlinear_terms) > 0) && error("[USER FUNCTION] must select at least one variable to perform discretization for convexificiation purpose") 357 | elseif isa(m.discretization_var_pick_algo, Int) || isa(m.discretization_var_pick_algo, String) 358 | if m.discretization_var_pick_algo == 0 || m.discretization_var_pick_algo == "max_cover" 359 | max_cover(m) 360 | elseif m.discretization_var_pick_algo == 1 || m.discretization_var_pick_algo == "min_vertex_cover" 361 | min_vertex_cover(m) 362 | else 363 | error("Unsupported default indicator for picking variables for discretization") 364 | end 365 | else 366 | error("Input for parameter :discretization_var_pick_algo is illegal. Should be either a Int for default methods indexes or functional inputs.") 367 | end 368 | 369 | return 370 | end 371 | 372 | """ 373 | 374 | min_vertex_cover(m:PODNonlinearModel) 375 | 376 | A built-in method for selecting variables for discretization. 377 | 378 | """ 379 | function min_vertex_cover(m::PODNonlinearModel) 380 | 381 | # Collect the information for arcs and nodes 382 | nodes = Set() 383 | arcs = Set() 384 | for pair in keys(m.nonlinear_terms) 385 | arc = [] 386 | if length(pair) > 2 387 | warn("min_vertex_cover discretizing variable selection method only support bi-linear problems, enfocing thie method may produce mistakes...") 388 | end 389 | for i in pair 390 | @assert isa(i.args[2], Int) 391 | push!(nodes, i.args[2]) 392 | push!(arc, i.args[2]) 393 | end 394 | push!(arcs, arc) 395 | end 396 | nodes = collect(nodes) 397 | arcs = collect(arcs) 398 | 399 | # Set up minimum vertex cover problem 400 | minvertex = Model(solver=m.mip_solver) 401 | @variable(minvertex, x[nodes], Bin) 402 | for arc in arcs 403 | @constraint(minvertex, x[arc[1]] + x[arc[2]] >= 1) 404 | end 405 | @objective(minvertex, Min, sum(x)) 406 | status = solve(minvertex, suppress_warnings=true) 407 | 408 | xVal = getvalue(x) 409 | 410 | # Getting required information 411 | m.num_var_discretization_mip = Int(sum(xVal)) 412 | m.var_discretization_mip = [i for i in nodes if xVal[i] > 1e-5] 413 | 414 | return 415 | end 416 | 417 | """ 418 | 419 | max_cover(m:PODNonlinearModel) 420 | 421 | A built-in method for selecting variables for discretization. It selects all variables in the nonlinear terms. 422 | 423 | """ 424 | function max_cover(m::PODNonlinearModel; kwargs...) 425 | 426 | nodes = Set() 427 | for k in keys(m.nonlinear_terms) 428 | # Assumption Max cover is always safe 429 | if m.nonlinear_terms[k][:nonlinear_type] in [:monomial, :bilinear, :multilinear] 430 | for i in k 431 | @assert isa(i.args[2], Int) 432 | push!(nodes, i.args[2]) 433 | end 434 | elseif m.nonlinear_terms[k][:nonlinear_type] in [:sin, :cos] 435 | for i in k[:vars] 436 | @assert isa(i, Int) 437 | push!(nodes, i) 438 | end 439 | end 440 | end 441 | nodes = collect(nodes) 442 | m.num_var_discretization_mip = length(nodes) 443 | m.var_discretization_mip = nodes 444 | 445 | return 446 | end 447 | 448 | function fetch_mip_solver_identifier(m::PODNonlinearModel) 449 | 450 | if string(m.mip_solver)[1:6] == "Gurobi" 451 | m.mip_solver_identifier = "Gurobi" 452 | elseif string(m.mip_solver)[1:5] == "CPLEX" 453 | m.mip_solver_identifier = "CPLEX" 454 | elseif string(m.mip_solver)[1:3] == "Cbc" 455 | m.mip_solver_identifier = "Cbc" 456 | elseif string(m.mip_solver)[1:4] == "GLPK" 457 | m.mip_solver_identifier = "GLPK" 458 | elseif string(m.mip_solver)[1:8] == "Pajarito" 459 | m.mip_solver_identifier = "Pajarito" 460 | else 461 | error("Unsupported mip solver name. Using blank") 462 | end 463 | 464 | return 465 | end 466 | 467 | function fetch_nlp_solver_identifier(m::PODNonlinearModel) 468 | 469 | if string(m.nlp_local_solver)[1:5] == "Ipopt" 470 | m.nlp_local_solver_identifier = "Ipopt" 471 | elseif string(m.nlp_local_solver)[1:6] == "AmplNL" 472 | m.nlp_local_solver_identifier = "Bonmin" 473 | elseif string(m.nlp_local_solver)[1:6] == "Knitro" 474 | m.nlp_local_solver_identifier = "Knitro" 475 | elseif string(m.nlp_local_solver)[1:8] == "Pajarito" 476 | m.nlp_local_solver_identifier = "Pajarito" 477 | elseif string(m.nlp_local_solver)[1:5] == "NLopt" 478 | m.nlp_local_solver_identifier = "NLopt" 479 | else 480 | error("Unsupported nlp solver name. Using blank") 481 | end 482 | 483 | return 484 | end 485 | 486 | function fetch_minlp_solver_identifier(m::PODNonlinearModel) 487 | 488 | (m.minlp_local_solver == UnsetSolver()) && return 489 | if string(m.minlp_local_solver)[1:6] == "AmplNL" 490 | m.minlp_local_solver_identifier = "Bonmin" 491 | elseif string(m.minlp_local_solver)[1:6] == "Knitro" 492 | m.minlp_local_solver_identifier = "Knitro" 493 | elseif string(m.minlp_local_solver)[1:8] == "Pajarito" 494 | m.minlp_local_solver_identifier = "Pajarito" 495 | elseif string(m.nlp_local_solver)[1:5] == "NLopt" 496 | m.nlp_local_solver_identifier = "NLopt" 497 | else 498 | error("Unsupported nlp solver name. Using blank") 499 | end 500 | 501 | return 502 | end 503 | 504 | 505 | function print_iis_gurobi(m::JuMP.Model) 506 | 507 | grb = MathProgBase.getrawsolver(internalmodel(m)) 508 | Gurobi.computeIIS(grb) 509 | numconstr = Gurobi.num_constrs(grb) 510 | numvar = Gurobi.num_vars(grb) 511 | 512 | iisconstr = Gurobi.get_intattrarray(grb, "IISConstr", 1, numconstr) 513 | iislb = Gurobi.get_intattrarray(grb, "IISLB", 1, numvar) 514 | iisub = Gurobi.get_intattrarray(grb, "IISUB", 1, numvar) 515 | 516 | info("Irreducible Inconsistent Subsystem (IIS)") 517 | info("Variable bounds:") 518 | for i in 1:numvar 519 | v = Variable(m, i) 520 | if iislb[i] != 0 && iisub[i] != 0 521 | println(getlowerbound(v), " <= ", getname(v), " <= ", getupperbound(v)) 522 | elseif iislb[i] != 0 523 | println(getname(v), " >= ", getlowerbound(v)) 524 | elseif iisub[i] != 0 525 | println(getname(v), " <= ", getupperbound(v)) 526 | end 527 | end 528 | 529 | info("Constraints:") 530 | for i in 1:numconstr 531 | if iisconstr[i] != 0 532 | println(m.linconstr[i]) 533 | end 534 | end 535 | 536 | return 537 | end 538 | --------------------------------------------------------------------------------