Skip to content

Commit

Permalink
New extension: ProblemReductionsExt (#50)
Browse files Browse the repository at this point in the history
* update

* port to ProblemReductions.jl, tests all pass

* port ProblemReductions

* make problemreductions an extension

* fix tests
  • Loading branch information
GiggleLiu authored Dec 13, 2024
1 parent 4ef1965 commit 4d04aec
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 3 deletions.
9 changes: 8 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ version = "0.4.0"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
LuxorGraphPlot = "1f49bdf2-22a7-4bc4-978b-948dc219fbbc"

[weakdeps]
ProblemReductions = "899c297d-f7d2-4ebf-8815-a35996def416"

[extensions]
ProblemReductionsExt = "ProblemReductions"

[compat]
Graphs = "1.6"
LuxorGraphPlot = "0.5"
ProblemReductions = "0.1.1"
julia = "1"

[extras]
Expand All @@ -19,4 +26,4 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Random", "GenericTensorNetworks", "LinearAlgebra"]
test = ["Test", "Random", "GenericTensorNetworks", "LinearAlgebra", "ProblemReductions"]
118 changes: 118 additions & 0 deletions ext/ProblemReductionsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
module ProblemReductionsExt

using UnitDiskMapping, UnitDiskMapping.Graphs
import ProblemReductions: reduceto, target_problem, extract_multiple_solutions
import ProblemReductions

function _to_gridgraph(g::UnitDiskMapping.GridGraph)
return ProblemReductions.GridGraph(getfield.(g.nodes, :loc), g.radius)
end
function _extract_weights(g::UnitDiskMapping.GridGraph{<:WeightedNode})
getfield.(g.nodes, :weight)
end

###### unweighted reduction
struct IndependentSetToKSG{NT, VT} <: ProblemReductions.AbstractReductionResult
mapres::MappingResult{NT}
weights::VT
end
function ProblemReductions.reduceto(::Type{ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Int, ProblemReductions.UnitWeight}}, problem::ProblemReductions.IndependentSet{GT, Int, ProblemReductions.UnitWeight} where GT<:SimpleGraph)
return IndependentSetToKSG(map_graph(UnWeighted(), problem.graph), problem.weights)
end

function ProblemReductions.target_problem(res::IndependentSetToKSG{<:UnWeightedNode})
return ProblemReductions.IndependentSet(_to_gridgraph(res.mapres.grid_graph))
end
function ProblemReductions.extract_solution(res::IndependentSetToKSG, sol)
return map_config_back(res.mapres, sol)
end

###### Weighted reduction
# TODO: rescale the weights to avoid errors
function ProblemReductions.reduceto(::Type{ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}}}, problem::ProblemReductions.IndependentSet{GT} where GT<:SimpleGraph)
return IndependentSetToKSG(map_graph(Weighted(), problem.graph), problem.weights)
end
function ProblemReductions.target_problem(res::IndependentSetToKSG{<:WeightedNode})
graph = _to_gridgraph(res.mapres.grid_graph)
weights = UnitDiskMapping.map_weights(res.mapres, res.weights)
return ProblemReductions.IndependentSet(graph, weights)
end

###### Factoring ######
struct FactoringToIndependentSet{NT} <: ProblemReductions.AbstractReductionResult
mapres::FactoringResult{NT}
raw_graph::ProblemReductions.GridGraph{2}
raw_weight::Vector{Int}
vmap::Vector{Int}
problem::ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Int, Vector{Int}}
end
function ProblemReductions.reduceto(::Type{ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Int, Vector{Int}}}, problem::ProblemReductions.Factoring)
mres = map_factoring(problem.m, problem.n)
g = _to_gridgraph(mres.grid_graph)
ws = getfield.(mres.grid_graph.nodes, :weight)
mg, vmap = UnitDiskMapping.set_target(g, [mres.pins_zeros..., mres.pins_output...], problem.input << length(mres.pins_zeros))
return FactoringToIndependentSet(mres, g, ws, vmap, ProblemReductions.IndependentSet(mg, ws[vmap]))
end

function ProblemReductions.target_problem(res::FactoringToIndependentSet)
return res.problem
end

function ProblemReductions.extract_solution(res::FactoringToIndependentSet, sol)
cfg = zeros(Int, nv(res.raw_graph))
cfg[res.vmap] .= sol
i1, i2 = map_config_back(res.mapres, cfg)
return vcat([i1>>(k-1) & 1 for k=1:length(res.mapres.pins_input1)], [i2>>(k-1) & 1 for k=1:length(res.mapres.pins_input2)])
end

###### Spinglass problem to MIS on KSG ######
# NOTE: I am not sure about the correctness of this reduction. If you encounter a bug, please question this function!
struct SpinGlassToIndependentSet{NT} <: ProblemReductions.AbstractReductionResult
mapres::QUBOResult{NT}
end
function ProblemReductions.reduceto(::Type{ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}}}, problem::ProblemReductions.SpinGlass{<:SimpleGraph})
n = length(problem.h)
M = similar(problem.h, n, n)
for (e, j) in zip(edges(problem.graph), problem.J)
M[e.src, e.dst] = M[e.dst, e.src] = j
end
return SpinGlassToIndependentSet(map_qubo(M, -problem.h))
end

function ProblemReductions.target_problem(res::SpinGlassToIndependentSet)
grid = _to_gridgraph(res.mapres.grid_graph)
ws = getfield.(res.mapres.grid_graph.nodes, :weight)
return ProblemReductions.IndependentSet(grid, ws)
end

function ProblemReductions.extract_solution(res::SpinGlassToIndependentSet, sol)
res = map_config_back(res.mapres, sol)
return 1 .- 2 .* Int.(res)
end

###### Spinglass problem on grid to MIS on KSG ######
# NOTE: the restricted layout is not implemented, since it is not often used
struct SquareSpinGlassToIndependentSet{NT} <: ProblemReductions.AbstractReductionResult
mapres::SquareQUBOResult{NT}
end
function ProblemReductions.reduceto(::Type{ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}}}, problem::ProblemReductions.SpinGlass{ProblemReductions.GridGraph{2}})
g = problem.graph
@assert 1 <= g.radius < sqrt(2) "Only support nearest neighbor interaction"
coupling = [(g.locations[e.src]..., g.locations[e.dst]..., J) for (e, J) in zip(edges(g), problem.J)]
onsite = [(i, j, -h) for ((i, j), h) in zip(g.locations, problem.h)]
# a vector coupling of `(i, j, i', j', J)`, s.t. (i', j') == (i, j+1) or (i', j') = (i+1, j).
# a vector of onsite term `(i, j, h)`.
return SquareSpinGlassToIndependentSet(map_qubo_square(coupling, onsite))
end

function ProblemReductions.target_problem(res::SquareSpinGlassToIndependentSet)
grid = _to_gridgraph(res.mapres.grid_graph)
ws = getfield.(res.mapres.grid_graph.nodes, :weight)
return ProblemReductions.IndependentSet(grid, ws)
end

function ProblemReductions.extract_solution(res::SquareSpinGlassToIndependentSet, sol)
res = map_config_back(res.mapres, sol)
return 1 .- 2 .* Int.(res)
end
end
6 changes: 6 additions & 0 deletions src/Core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ function Graphs.SimpleGraph(grid::GridGraph{Node{ONE}})
return unit_disk_graph(getfield.(grid.nodes, :loc), grid.radius)
end
coordinates(grid::GridGraph) = getfield.(grid.nodes, :loc)
function Graphs.neighbors(g::GridGraph, i::Int)
[j for j in 1:nv(g) if i != j && distance(g.nodes[i], g.nodes[j]) <= g.radius]
end
distance(n1::Node, n2::Node) = sqrt(sum(abs2, n1.loc .- n2.loc))
Graphs.nv(g::GridGraph) = length(g.nodes)
Graphs.vertices(g::GridGraph) = 1:nv(g)

# printing function for Grid graphs
function print_grid(io::IO, grid::GridGraph{Node{WT}}; show_weight=false) where WT
Expand Down
4 changes: 2 additions & 2 deletions src/multiplier.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function map_factoring(M::Int, N::Int)
[pinloc(1, j, 1) for j=1:N]...,
[pinloc(i, N, 4) for i=1:M]...,
]
return FactoringResult(gg, pp, pq, pm, p0)
return FactoringResult(gg, pq, pp, pm, p0)
end

struct FactoringResult{NT}
Expand Down Expand Up @@ -131,7 +131,7 @@ function solve_factoring(missolver, mres::FactoringResult, target::Int)
return map_config_back(mres, cfg)
end

function set_target(g::SimpleGraph, pins::AbstractVector, target::Int)
function set_target(g, pins::AbstractVector, target::Int)
vs = collect(vertices(g))
for (i, p) in enumerate(pins)
bitval = (target >> (i-1)) & 1
Expand Down
15 changes: 15 additions & 0 deletions test/Core.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Test, UnitDiskMapping, Graphs

@testset "GridGraph" begin
grid = GridGraph((5, 5), [Node(2, 3), Node(2, 4), Node(5, 5)], 1.2)
g = SimpleGraph(grid)
@test ne(g) == 1
@test vertices(grid) == vertices(g)
@test neighbors(grid, 2) == neighbors(g, 2)

grid = GridGraph((5, 5), [Node(2, 3), Node(2, 4), Node(5, 5)], 4.0)
g = SimpleGraph(grid)
@test ne(g) == 3
@test vertices(grid) == vertices(g)
@test neighbors(grid, 2) == neighbors(g, 2)
end
46 changes: 46 additions & 0 deletions test/reduceto.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Test, UnitDiskMapping, Graphs, GenericTensorNetworks
import ProblemReductions

@testset "reduction graph" begin
@test ProblemReductions.reduction_graph() isa ProblemReductions.ReductionGraph
end

@testset "rules" begin
graph = complete_graph(3) # triangle
fact = ProblemReductions.Factoring(2, 1, 2)
is = ProblemReductions.IndependentSet(graph)
wis = ProblemReductions.IndependentSet(graph, rand(nv(graph)) .* 0.2)
sg = ProblemReductions.SpinGlass(graph, [0.2, 0.4, -0.6], [0.1, 0.1, 0.1])
sg2 = ProblemReductions.SpinGlass(graph, [0.1, 0.1, -0.1], [0.1, 0.1, 0.1])
grid = ProblemReductions.GridGraph(ones(Bool, 2, 2), 1.2)
sg_square = ProblemReductions.SpinGlass(grid, [0.1, 0.3, -0.1, 0.4], [0.1, 0.1, 0.1, 0.2])
sg_square2 = ProblemReductions.SpinGlass(grid, [0.1, -0.3, 0.1, 0.4], [0.1, 0.1, 0.1, 0.2])
for (source, target_type) in [
sg_square => ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}},
sg_square2 => ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}},
sg => ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}},
sg2 => ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}},
is => ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Int, ProblemReductions.UnitWeight},
wis => ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Float64, Vector{Float64}},
fact => ProblemReductions.IndependentSet{ProblemReductions.GridGraph{2}, Int, Vector{Int}},
]
@info "Testing reduction from $(typeof(source)) to $(target_type)"
# directly solve
best_source = ProblemReductions.findbest(source, ProblemReductions.BruteForce())

# reduce and solve
result = ProblemReductions.reduceto(target_type, source)
target = ProblemReductions.target_problem(result)
@test target isa target_type
#best_target = findbest(target, BruteForce())
best_target = GenericTensorNetworks.solve(GenericTensorNetwork(GenericTensorNetworks.IndependentSet(SimpleGraph(target.graph), collect(target.weights))), ConfigsMax())[].c.data

# extract the solution
best_source_extracted_single = unique( ProblemReductions.extract_solution.(Ref(result), best_target) )
best_source_extracted_multiple = ProblemReductions.extract_multiple_solutions(result, best_target)

# check if the solutions are the same
@test best_source_extracted_single best_source
@test Set(best_source_extracted_multiple) == Set(best_source)
end
end
8 changes: 8 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using UnitDiskMapping
using Test

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

@testset "utils" begin
include("utils.jl")
end
Expand Down Expand Up @@ -48,3 +52,7 @@ end
@testset "visualize" begin
include("visualize.jl")
end

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

0 comments on commit 4d04aec

Please sign in to comment.