From 4d04aec33d1a802a3988e42ae46cbd328480f0ce Mon Sep 17 00:00:00 2001 From: Jinguo Liu Date: Fri, 13 Dec 2024 22:56:20 +0800 Subject: [PATCH] New extension: ProblemReductionsExt (#50) * update * port to ProblemReductions.jl, tests all pass * port ProblemReductions * make problemreductions an extension * fix tests --- Project.toml | 9 ++- ext/ProblemReductionsExt.jl | 118 ++++++++++++++++++++++++++++++++++++ src/Core.jl | 6 ++ src/multiplier.jl | 4 +- test/Core.jl | 15 +++++ test/reduceto.jl | 46 ++++++++++++++ test/runtests.jl | 8 +++ 7 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 ext/ProblemReductionsExt.jl create mode 100644 test/Core.jl create mode 100644 test/reduceto.jl diff --git a/Project.toml b/Project.toml index dfe5a96..c7690b6 100644 --- a/Project.toml +++ b/Project.toml @@ -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] @@ -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"] diff --git a/ext/ProblemReductionsExt.jl b/ext/ProblemReductionsExt.jl new file mode 100644 index 0000000..1ebcdeb --- /dev/null +++ b/ext/ProblemReductionsExt.jl @@ -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 diff --git a/src/Core.jl b/src/Core.jl index 90b8c8d..18049f2 100644 --- a/src/Core.jl +++ b/src/Core.jl @@ -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 diff --git a/src/multiplier.jl b/src/multiplier.jl index dfd60d1..f1814f7 100644 --- a/src/multiplier.jl +++ b/src/multiplier.jl @@ -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} @@ -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 diff --git a/test/Core.jl b/test/Core.jl new file mode 100644 index 0000000..b1376a3 --- /dev/null +++ b/test/Core.jl @@ -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 \ No newline at end of file diff --git a/test/reduceto.jl b/test/reduceto.jl new file mode 100644 index 0000000..319ef6c --- /dev/null +++ b/test/reduceto.jl @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index b983e25..2350059 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,10 @@ using UnitDiskMapping using Test +@testset "Core" begin + include("Core.jl") +end + @testset "utils" begin include("utils.jl") end @@ -48,3 +52,7 @@ end @testset "visualize" begin include("visualize.jl") end + +@testset "reduceto" begin + include("reduceto.jl") +end \ No newline at end of file