Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ Manifest.toml
*.sublime-workspace
*.bat
*.sublime-project
*.mof.json
*.prt
*.txt
14 changes: 3 additions & 11 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
name = "QuadraticToBinary"
uuid = "014a38d5-7acb-4e20-b6c0-4fe5c2344fd1"
authors = ["Joaquim Garcia <joaquimgarcia@psr-inc.com>"]
version = "0.3.0"
version = "0.4.0"

[deps]
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"

[compat]
MathOptInterface = "0.10.6"
MathOptInterface = "1"
DataStructures = "0.17.10, 0.18.0"
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"

[targets]
test = ["Test", "Cbc"]

julia = "1.6"
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Non-convex quadratic programs are extremely hard to solve. This problem class ca
be solved by Global Solvers such as [Couenne](https://projects.coin-or.org/Couenne).
Another possibility is to rely on binary expasion of products terms that appear in the
problem, in this case the problem is *approximated* and can be solved by off-the-shelf
MIP solvers such as [Cbc](https://github.com/JuliaOpt/Cbc.jl), [CPLEX](https://github.com/JuliaOpt/CPLEX.jl), [GLPK](https://github.com/JuliaOpt/GLPK.jl), [Gurobi](https://github.com/JuliaOpt/Gurobi.jl), [Xpress](https://github.com/JuliaOpt/Xpress.jl).
MIP solvers such as [Cbc](https://github.com/jump-dev/Cbc.jl), [CPLEX](https://github.com/jump-dev/CPLEX.jl), [GLPK](https://github.com/jump-dev/GLPK.jl), [Gurobi](https://github.com/jump-dev/Gurobi.jl), [HiGHS](https://github.com/jump-dev/HiGHS.jl), [Xpress](https://github.com/jump-dev/Xpress.jl).

## Example

Expand Down Expand Up @@ -77,15 +77,15 @@ objective_value(model) # ≈ 9.0
@assert value(c) ≈ 4.0
```

### MathOptInterface with Cbc solver
### MathOptInterface with HiGHS solver

```julia
using MathOptInterface
using QuadraticToBinary
const MOI = MathOptInterface
using Cbc
using HiGHS

optimizer = MOI.instantiate(Cbc.Optimizer, with_bridge_type = Float64)
optimizer = MOI.instantiate(HiGHS.Optimizer, with_bridge_type = Float64)

model = QuadraticToBinary.Optimizer{Float64}(optimizer)

Expand Down Expand Up @@ -118,7 +118,8 @@ MOI.optimize!(model)
@assert MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 4.0
```

Note that duals are not available because the problem was approximated as a MIP.
Note:
that duals are not available because the problem was approximated as a MIP.

## QuadraticToBinary.Optimizer Attributes

Expand All @@ -140,6 +141,16 @@ MOI.set(model, QuadraticToBinary.VariablePrecision(), vi, val)
The precision for each varible will be `val * (UB - LB)`. Where `UB` and `LB` are,
respectively, the upper and lower bound of the variable.

Note:
binary expansion problem can be numerically challenging for high precision. You
might need to modify solver options accordingly. In the case of HiGHS:

```julia
tol = 1e-9
MOI.set(highs_high_tol, MOI.RawOptimizerAttribute("mip_feasibility_tolerance"), tol)
MOI.set(highs_high_tol, MOI.RawOptimizerAttribute("primal_feasibility_tolerance"), tol)
```

### Bounds

For the sake of simplicity, the following two attributes are made available:
Expand Down
53 changes: 16 additions & 37 deletions src/moi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ end

# MOI.supports(model::Optimizer, args...) = MOI.supports(model.optimizer, args...)

# TODO, call this on inner solver
# Q2B is always incremental because it uses add_constraint to cacehc onversions
MOI.supports_incremental_interface(::Optimizer) = true

function MOI.supports(model::Optimizer, attr::MOI.VariableName, tp::Type{MOI.VariableIndex})
Expand Down Expand Up @@ -270,6 +270,7 @@ function MOI.set(model::Optimizer, attr::Union{
MOI.Silent,
MOI.NumberOfThreads,
MOI.TimeLimitSec,
MOI.RawOptimizerAttribute,
}, val
)
return MOI.set(model.optimizer, attr, val)
Expand Down Expand Up @@ -307,8 +308,8 @@ quadratic_type(::Type{MOI.VectorAffineFunction{T}}) where T = MOI.VectorQuadrati


function MOI.get(model::Optimizer, ::MOI.SolverName)
return "Binary reformulation of quadratic model with solver " *
MOI.get(model.optimizer, MOI.SolverName()) * " attached"
return "QuadraticToBinary [" *
MOI.get(model.optimizer, MOI.SolverName()) * "]"
end

function MOI.supports_add_constrained_variables(
Expand Down Expand Up @@ -340,20 +341,8 @@ function MOI.supports_add_constrained_variables(
return MOI.supports_add_constrained_variables(model.optimizer, S)
end

# function MOI.set(model::Optimizer, param::MOI.RawOptimizerAttribute, value)
# # if in a subset of the q2b save it
# # otherwise pass it forward
# end

# function MOI.get(model::Optimizer, param::MOI.RawOptimizerAttribute)
# end

function MOI.Utilities.supports_default_copy_to(model::Optimizer, val::Bool)
return true # val # MOI.Utilities.supports_default_copy_to(model.optimizer, val)
end

function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kwargs...)
return MOI.Utilities.automatic_copy_to(dest, src; kwargs...)
function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
return MOI.Utilities.default_copy_to(dest, src)
end


Expand Down Expand Up @@ -989,25 +978,14 @@ end

function delete_additional_constraints(model)
ch = model.index_cache
if true
MOI.delete(model.optimizer, ch.sa_eq)
MOI.delete(model.optimizer, ch.sa_lt)
MOI.delete(model.optimizer, ch.sa_gt)
MOI.delete(model.optimizer, ch.vi)
else
for ci in ch.sa_eq
MOI.delete(model.optimizer, ci)
end
for ci in ch.sa_lt
MOI.delete(model.optimizer, ci)
end
for ci in ch.sa_gt
MOI.delete(model.optimizer, ci)
end
for vi in ch.vi
MOI.delete(model.optimizer, vi)
end
end
MOI.delete(model.optimizer, ch.sa_eq)
MOI.delete(model.optimizer, ch.sa_lt)
MOI.delete(model.optimizer, ch.sa_gt)
MOI.delete(model.optimizer, ch.sv_zo)
MOI.delete(model.optimizer, ch.sv_lt)
MOI.delete(model.optimizer, ch.sv_gt)
MOI.delete(model.optimizer, ch.vi)
return nothing
end

function lower(model, info)
Expand Down Expand Up @@ -1481,7 +1459,8 @@ function MOI.set(
::GlobalVariablePrecision,
value::Union{Nothing, Float64}
)
val = value === nothing ? NaN : value
# default global precision is 1e-4
val = value === nothing ? 1e-4 : value
@assert 0 < val <= 1
model.global_initial_precision = val
return
Expand Down
7 changes: 7 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[deps]
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
HiGHS = "1.1.0"
Loading