Skip to content
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

New extension: ProblemReductionsExt #50

Merged
merged 5 commits into from
Dec 13, 2024
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
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
Loading