Skip to content
12 changes: 11 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,31 @@ BitBasis = "50ba71b6-fa0f-514d-ae9a-0916efc90dcf"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"

[weakdeps]
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"

[extensions]
IPSolverExt = "JuMP"

[compat]
BitBasis = "0.9"
DocStringExtensions = "0.9"
Graphs = "1"
InteractiveUtils = "1"
JuMP = "1"
LinearAlgebra = "1.11.0"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LinearAlgebra = "1.11.0"
LinearAlgebra = "1"

MLStyle = "0.4"
PrettyTables = "2"
julia = "1.10"

[extras]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
SCIP = "82193955-e24f-5292-bf16-6f2c5261a85f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Documenter"]
test = ["Test", "Documenter", "JuMP", "SCIP"]
104 changes: 104 additions & 0 deletions ext/IPSolverExt.jl
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)

Check warning on line 8 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L7-L8

Added lines #L7 - L8 were not covered by tests
end
function Base.findmax(problem::AbstractProblem, solver::IPSolver)
return _find(problem, solver,false)

Check warning on line 11 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L10-L11

Added lines #L10 - L11 were not covered by tests
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]

Check warning on line 20 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L15-L20

Added lines #L15 - L20 were not covered by tests

objs = objectives(problem)
@assert all(length(obj.variables) <= 1 for obj in objs) "IPSolver only supports objectives with at most 1 variables"

Check warning on line 23 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L22-L23

Added lines #L22 - L23 were not covered by tests

# IP by JuMP
model = JuMP.Model(solver.optimizer)
!solver.verbose && JuMP.set_silent(model)

Check warning on line 27 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L26-L27

Added lines #L26 - L27 were not covered by tests

JuMP.@variable(model, 0 <= x[i = 1:nsc] <= 1, Int)

Check warning on line 29 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L29

Added line #L29 was not covered by tests

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

Check warning on line 37 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L31-L37

Added lines #L31 - L37 were not covered by tests

obj_sum = 0
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unstable type.

for obj in objs
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Objectives can have more than one variables. The current code will scilently break for Spinglass.

obj_sum += (1-x[obj.variables[1]])*obj.specification[1] + x[obj.variables[1]]*obj.specification[2]
end

Check warning on line 42 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L39-L42

Added lines #L39 - L42 were not covered by tests

tag ? JuMP.@objective(model, Min, obj_sum) : JuMP.@objective(model, Max, obj_sum)

Check warning on line 44 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L44

Added line #L44 was not covered by tests

JuMP.optimize!(model)
@assert JuMP.is_solved_and_feasible(model)
return round.(Int, JuMP.value.(x))

Check warning on line 48 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L46-L48

Added lines #L46 - L48 were not covered by tests
end

function minimal_constraints(nflavor::Int, set::Vector)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is not tested.

subsets = [covering_items(c, totalset) for c in clauses]
return filter(set -> issubset(set, coverset), subsets)

Check warning on line 53 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L51-L53

Added lines #L51 - L53 were not covered by tests
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)

Check warning on line 70 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L70

Added line #L70 was not covered by tests
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need it anymore. Maybe we can dispatch it to set covering problem solver.

# 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)"

Check warning on line 72 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L72

Added line #L72 was not covered by tests

# Create a JuMP model for exact set cover
model = JuMP.Model(optimizer)
!verbose && JuMP.set_silent(model)

Check warning on line 76 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L75-L76

Added lines #L75 - L76 were not covered by tests

# Define binary variables for each subset (1 if selected, 0 if not)
n = length(subsets)
JuMP.@variable(model, x[1:n], Bin)

Check warning on line 80 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L79-L80

Added lines #L79 - L80 were not covered by tests

# Each element in the coverset must be covered exactly once
for element in coverset

Check warning on line 83 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L83

Added line #L83 was not covered by tests
# Find all subsets containing this element
covering_subsets = [i for i in 1:n if element in subsets[i]]

Check warning on line 85 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L85

Added line #L85 was not covered by tests

# Each element must be covered exactly once
JuMP.@constraint(model, sum(x[i] for i in covering_subsets) >= 1)
end

Check warning on line 89 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L88-L89

Added lines #L88 - L89 were not covered by tests

# Minimize the number of subsets used (optional objective)
JuMP.@objective(model, Min, sum(x))

Check warning on line 92 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L92

Added line #L92 was not covered by tests
# Solve the model
JuMP.optimize!(model)

Check warning on line 94 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L94

Added line #L94 was not covered by tests

# Return the solution if feasible
if JuMP.termination_status(model) == JuMP.MOI.OPTIMAL
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use @assert is_solved_and_feasible(model) "error message..."?

return [i for i in 1:n if JuMP.value(x[i]) > 0.5]

Check warning on line 98 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L97-L98

Added lines #L97 - L98 were not covered by tests
else
error("No solution found")

Check warning on line 100 in ext/IPSolverExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/IPSolverExt.jl#L100

Added line #L100 was not covered by tests
end
end

end
4 changes: 2 additions & 2 deletions src/ProblemReductions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export LogicGadget, truth_table
export ReductionSATTo3SAT
export ReductionCircuitToSpinGlass, ReductionMaxCutToSpinGlass, ReductionSpinGlassToMaxCut, ReductionVertexCoveringToSetCovering, ReductionSatToColoring,
ReductionSpinGlassToQUBO, ReductionQUBOToSpinGlass
export findbest, BruteForce
export findbest, BruteForce, AbstractSolver, IPSolver
export CNF
export ReductionSATToIndependentSet, ReductionSATToDominatingSet
export ReductionIndependentSetToSetPacking, ReductionSetPackingToIndependentSet
Expand All @@ -57,7 +57,7 @@ include("topology.jl")
include("bitvector.jl")
include("models/models.jl")
include("rules/rules.jl")
include("bruteforce.jl")
include("solvers.jl")
include("reduction_path.jl")
include("deprecated.jl")

Expand Down
11 changes: 10 additions & 1 deletion src/bruteforce.jl → src/solvers.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
abstract type AbstractSolver end

"""
$TYPEDEF

A brute force method to find the best configuration of a problem.
"""
Base.@kwdef struct BruteForce{T}
Base.@kwdef struct BruteForce{T} <: AbstractSolver
atol::T=eps(Float64)
rtol::T=eps(Float64)
end
Expand Down Expand Up @@ -38,3 +40,10 @@ function _find!(compare, best_configs, configs, sizes, initial, atol, rtol)
end
end
end

# Interface for IPSolver
Base.@kwdef struct IPSolver <: AbstractSolver
optimizer # e.g. HiGHS.Optimizer
max_itr::Int = 20
verbose::Bool = false
end
53 changes: 53 additions & 0 deletions test/IPSolverExt.jl
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
4 changes: 4 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ using Documenter
include("bitvector.jl")
end

@testset "solvers" begin
include("solvers.jl")
end

Copy link
Owner

Choose a reason for hiding this comment

The 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
Expand Down
12 changes: 12 additions & 0 deletions test/solvers.jl
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
Loading