-
Notifications
You must be signed in to change notification settings - Fork 3
Integer programming solver #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
1b92bea
618343f
b958648
5962ccd
56cf027
6c8d2e8
e91925a
38dbad8
787ee94
b2ba950
dada6db
54ba718
ec38b9b
c335deb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| module IPSolverExt | ||
|
|
||
| import JuMP | ||
| using ProblemReductions | ||
| using LinearAlgebra | ||
|
|
||
| function Base.findmin(problem::AbstractProblem, solver::IPSolver) | ||
| return _find(problem, solver,true) | ||
GiggleLiu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| end | ||
| function Base.findmax(problem::AbstractProblem, solver::IPSolver) | ||
| return _find(problem, solver,false) | ||
| end | ||
|
|
||
| # tag: true for min, false for max | ||
| function _find(problem::AbstractProblem, solver::IPSolver,tag::Bool) | ||
| @assert num_flavors(problem) == 2 "IPSolver only supports boolean variables" | ||
| cons = constraints(problem) | ||
| nsc = ProblemReductions.num_variables(problem) | ||
| maxN = maximum([length(c.variables) for c in cons]) | ||
| combs = [ProblemReductions.combinations(2,i) for i in 1:maxN] | ||
|
|
||
| objs = objectives(problem) | ||
| @assert all(length(obj.variables) <= 1 for obj in objs) "IPSolver only supports objectives with at most 1 variables" | ||
|
|
||
| # IP by JuMP | ||
| model = JuMP.Model(solver.optimizer) | ||
| !solver.verbose && JuMP.set_silent(model) | ||
|
|
||
| JuMP.@variable(model, 0 <= x[i = 1:nsc] <= 1, Int) | ||
|
|
||
| for con in cons | ||
| f_vec = findall(!,con.specification) | ||
| num_vars = length(con.variables) | ||
| for f in f_vec | ||
| JuMP.@constraint(model, sum(j-> iszero(combs[num_vars][f][j]) ? (1 - x[con.variables[j]]) : x[con.variables[j]], 1:num_vars) <= num_vars -1) | ||
| end | ||
| end | ||
|
|
||
| obj_sum = 0 | ||
|
||
| for obj in objs | ||
|
||
| obj_sum += (1-x[obj.variables[1]])*obj.specification[1] + x[obj.variables[1]]*obj.specification[2] | ||
| end | ||
|
|
||
| tag ? JuMP.@objective(model, Min, obj_sum) : JuMP.@objective(model, Max, obj_sum) | ||
|
|
||
| JuMP.optimize!(model) | ||
| @assert JuMP.is_solved_and_feasible(model) | ||
| return round.(Int, JuMP.value.(x)) | ||
| end | ||
|
|
||
| function minimal_constraints(nflavor::Int, set::Vector) | ||
|
||
| subsets = [covering_items(c, totalset) for c in clauses] | ||
| return filter(set -> issubset(set, coverset), subsets) | ||
| end | ||
|
|
||
| """ | ||
| minimal_set_cover(coverset::Vector{Int}, subsets::Vector{Vector{Int}}, optimizer) | ||
| Solve the set cover problem: all elements in the coverset must be covered by the subsets. | ||
| The objective is to minimize the number of subsets used. | ||
| # Arguments | ||
| - `coverset::Vector{Int}`: The set of all elements to be covered. | ||
| - `subsets::Vector{Vector{Int}}`: The set of subsets to choose from. | ||
| - `optimizer`: The optimizer to use, e.g. SCIP.Optimizer or HiGHS.Optimizer | ||
| # Returns | ||
| - `Vector{Int}`: The indices of the subsets to choose. | ||
| """ | ||
| function minimal_set_cover(coverset::Vector{Int}, subsets::Vector{Vector{Int}}, optimizer, verbose::Bool=false) | ||
|
||
| # Remove subsets that cover not existing elements in the coverset | ||
| @assert all(set -> issubset(set, coverset), subsets) "subsets ($subsets) must not cover any elements absent in the coverset ($coverset)" | ||
|
|
||
| # Create a JuMP model for exact set cover | ||
| model = JuMP.Model(optimizer) | ||
| !verbose && JuMP.set_silent(model) | ||
|
|
||
| # Define binary variables for each subset (1 if selected, 0 if not) | ||
| n = length(subsets) | ||
| JuMP.@variable(model, x[1:n], Bin) | ||
|
|
||
| # Each element in the coverset must be covered exactly once | ||
| for element in coverset | ||
| # Find all subsets containing this element | ||
| covering_subsets = [i for i in 1:n if element in subsets[i]] | ||
|
|
||
| # Each element must be covered exactly once | ||
| JuMP.@constraint(model, sum(x[i] for i in covering_subsets) >= 1) | ||
| end | ||
|
|
||
| # Minimize the number of subsets used (optional objective) | ||
| JuMP.@objective(model, Min, sum(x)) | ||
| # Solve the model | ||
| JuMP.optimize!(model) | ||
|
|
||
| # Return the solution if feasible | ||
| if JuMP.termination_status(model) == JuMP.MOI.OPTIMAL | ||
|
||
| return [i for i in 1:n if JuMP.value(x[i]) > 0.5] | ||
| else | ||
| error("No solution found") | ||
| end | ||
| end | ||
|
|
||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| using Test | ||
| using JuMP | ||
| using ProblemReductions | ||
| using SCIP | ||
| using Graphs | ||
|
|
||
| @testset "IPSolverExt" begin | ||
| # Test exact_set_cover with HiGHS optimizer | ||
| optimizer = SCIP.Optimizer | ||
| nflavor = 5 | ||
| subsets = [[1, 2], [2, 3], [3, 4], [4, 5]] | ||
| coverset = [1, 2, 3, 4, 5] | ||
|
|
||
| # Test exact_set_cover with HiGHS optimizer | ||
| Ext = Base.get_extension(ProblemReductions, :IPSolverExt) | ||
| result = Ext.minimal_set_cover(coverset, subsets, optimizer) | ||
| @test result == [1, 2, 4] || result == [1, 3, 4] | ||
| end | ||
|
|
||
| @testset "IPSolver" begin | ||
| graph = smallgraph(:petersen) | ||
| problem = MaximalIS(graph) | ||
| @test findmin(problem, IPSolver(SCIP.Optimizer,20,false)) ∈ findmin(problem, BruteForce()) | ||
|
|
||
| problem = IndependentSet(graph) | ||
| @test findmax(problem, IPSolver(SCIP.Optimizer,20,false)) ∈ findmax(problem, BruteForce()) | ||
|
|
||
| fact3 = Factoring(2, 1, 3) | ||
| res3 = reduceto(CircuitSAT, fact3) | ||
| problem = CircuitSAT(res3.circuit.circuit; use_constraints=true) | ||
| @test findmin(problem, IPSolver(SCIP.Optimizer,20,false)) ∈ findmin(problem, BruteForce()) | ||
| best_config3 = findmin(problem, IPSolver(SCIP.Optimizer,20,false)) | ||
| assignment3 = Dict(zip(res3.circuit.symbols, best_config3)) | ||
| @test (2* assignment3[:p2]+ assignment3[:p1]) * assignment3[:q1] == 3 | ||
|
|
||
| m1 = Matching(graph) | ||
| @test findmax(m1, IPSolver(SCIP.Optimizer,20,false)) ∈ findbest(m1, BruteForce()) | ||
| end | ||
|
|
||
| @testset "Factoring" begin | ||
| function factoring(m,n,N,solver) | ||
| fact3 = Factoring(m, n, N) | ||
| res3 = reduceto(CircuitSAT, fact3) | ||
| problem = CircuitSAT(res3.circuit.circuit; use_constraints=true) | ||
| vals = findmin(problem, IPSolver(solver,20,true)) | ||
| return ProblemReductions.read_solution(fact3, [vals[res3.p]...,vals[res3.q]...]) | ||
| end | ||
| a,b = factoring(5,5,899,SCIP.Optimizer) | ||
| @test a*b == 899 | ||
|
|
||
| # using BenchmarkTools | ||
| # @btime factoring(10,10,1040399) # 37.499 ms (505559 allocations: 22.91 MiB) | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,10 @@ using Documenter | |
| include("bitvector.jl") | ||
| end | ||
|
|
||
| @testset "solvers" begin | ||
| include("solvers.jl") | ||
| end | ||
|
|
||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IPSolverExt.jl should be included. |
||
| @testset "models" begin | ||
| include("models/models.jl") | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| using Test, ProblemReductions, Graphs | ||
|
|
||
| @testset "BruteForce" begin | ||
| graph = smallgraph(:petersen) | ||
| problem = IndependentSet(graph) | ||
| solver = BruteForce() | ||
| res = findbest(problem, solver) | ||
| @test res == [[0, 0, 1, 0, 1, 1, 1, 0, 0, 0], [1, 0, 0, 1, 0, 0, 1, 1, 0, 0], [0, 1, 0, 0, 1, 0, 0, 1, 1, 0], [0, 1, 0, 1, 0, 1, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 0, 1, 1]] | ||
| solver = BruteForce() | ||
| res = findbest(problem, solver) | ||
| @test res == [[0, 0, 1, 0, 1, 1, 1, 0, 0, 0], [1, 0, 0, 1, 0, 0, 1, 1, 0, 0], [0, 1, 0, 0, 1, 0, 0, 1, 1, 0], [0, 1, 0, 1, 0, 1, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 0, 1, 1]] | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.