diff --git a/docs/make.jl b/docs/make.jl index fa8c0f1..b0ef034 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -29,7 +29,8 @@ makedocs(; "Home" => "index.md", "Examples" => [ "generated/tutorial.md", - "generated/unweighted.md" + "generated/unweighted.md", + "generated/weighted_lattice_comparison.md" ], "Reference" => "ref.md", ], diff --git a/examples/weighted_lattice_comparison.jl b/examples/weighted_lattice_comparison.jl new file mode 100644 index 0000000..7539bef --- /dev/null +++ b/examples/weighted_lattice_comparison.jl @@ -0,0 +1,102 @@ +# # Weighted Mapping on Different Lattices + +# This page demonstrates weighted version of the Maximum Independent Set (MIS) or Maximum Weighted Independent Set (MWIS) mapping techniques from the paper "Embedding computationally hard problems in triangular Rydberg atom arrays". We compare two mapping approaches using the K₂,₃ bipartite graph as an example: +# +# 1. **King's Subgraph (KSG) mapping** on square lattices - the traditional approach +# 2. **Triangular lattice mapping** - a more experimental-promising alternative +# +# Both methods preserve the optimal solution while enabling implementation on quantum hardware with different connectivity constraints. + +using UnitDiskMapping, Graphs, GenericTensorNetworks + +# ## Problem Setup: K₂,₃ Graph + +# We use the complete bipartite graph K₂,₃ as our test case. This graph has two groups of vertices: {1,2} and {3,4,5}, where every vertex in the first group connects to every vertex in the second group. + +k23_graph = SimpleGraph(5) +add_edge!(k23_graph, 1, 3) +add_edge!(k23_graph, 1, 4) +add_edge!(k23_graph, 1, 5) +add_edge!(k23_graph, 2, 3) +add_edge!(k23_graph, 2, 4) +add_edge!(k23_graph, 2, 5) + +show_graph(k23_graph) + +# For the MIS problem, we assign equal weights to all vertices. +source_weights = [0.5, 0.5, 0.5, 0.5, 0.5] + +# ## Square Lattice Mapping (KSG) + +# The KSG-based approach creates a regular square grid where vertices can only connect to their nearest and next nearest neighbors (i.e. diagonals). + +square_result = map_graph(Weighted(), k23_graph; vertex_order=MinhThiTrick()) + +println("Square lattice grid size: ", square_result.grid_graph.size) +println("Number of vertices: ", nv(square_result.grid_graph)) +println("MIS overhead: ", square_result.mis_overhead) + +# Visualize the mapping +show_graph(square_result.grid_graph; show_number=false) +show_grayscale(square_result.grid_graph) # show weights in gray scale +show_pins(square_result) # show pins in red + +# Solve the mapped problem +square_mapped_weights = map_weights(square_result, source_weights) +square_grid_graph, _ = graph_and_weights(square_result.grid_graph) +square_solution = solve(GenericTensorNetwork(IndependentSet(square_grid_graph, square_mapped_weights)), + SingleConfigMax())[].c.data + +# Map back to source +square_source_solution = map_config_back(square_result, collect(Int, square_solution)) +println("Square solution: ", square_source_solution) + +# ## Triangular Lattice Mapping + +# While King's subgraphs provide a systematic approach for encoding problems on square lattices, they are not optimal for two-dimensional quantum hardware. The power-law decay of Rydberg interaction strengths in real devices leads to poor approximation of unit-disk graphs, requiring extensive post-processing that lacks explainability. +# +# The triangular lattice encoding scheme addresses these limitations by utilizing triangular Rydberg atom arrays which only blocks atoms in the nearest neighbors. This approach reduces independence-constraint violations by approximately two orders of magnitude compared to King's subgraphs, substantially alleviating the need for post-processing in experiments. +# +# The automatic embedding scheme generates graphs on triangular lattices with slightly larger overhead compared to KSG, but remains quadratic. For further improvement like removing dangling vertices, we need to manually optimize the embedding currently. +# + + +triangular_result = map_graph(TriangularWeighted(), k23_graph; vertex_order=MinhThiTrick()) + +println("Triangular lattice grid size: ", triangular_result.grid_graph.size) +println("Number of vertices: ", nv(triangular_result.grid_graph)) +println("MIS overhead: ", triangular_result.mis_overhead) + +# Visualize the mapping +show_graph(triangular_result.grid_graph; show_number=false) +show_grayscale(triangular_result.grid_graph) # show weights in gray scale +show_pins(triangular_result) # show pins in red + +# Solve the mapped problem +triangular_mapped_weights = map_weights(triangular_result, source_weights) +triangular_grid_graph, _ = graph_and_weights(triangular_result.grid_graph) +triangular_solution = solve(GenericTensorNetwork(IndependentSet(triangular_grid_graph, triangular_mapped_weights)), + SingleConfigMax())[].c.data +show_config(triangular_result.grid_graph, triangular_solution) + +# Map back to source +triangular_source_solution = map_config_back(triangular_result, collect(Int, triangular_solution)) +println("Triangular solution: ", triangular_source_solution) + +# ## Verification and Comparison + +# To verify correctness, we solve the original problem directly and compare with both mapping approaches. All three methods should yield the same optimal solution value. + +# Solve original problem directly +direct_solution = solve(GenericTensorNetwork(IndependentSet(k23_graph, source_weights)), + SingleConfigMax())[].c.data + +println("Direct solution: ", collect(Int, direct_solution)) + +# Check all give same optimal value +direct_value = sum(source_weights[i] for i in 1:5 if direct_solution[i] == 1) +square_value = sum(source_weights[i] for i in 1:5 if square_source_solution[i] == 1) +triangular_value = sum(source_weights[i] for i in 1:5 if triangular_source_solution[i] == 1) + +println("Optimal values - Direct: $direct_value, Square: $square_value, Triangular: $triangular_value") +println("All correct: ", direct_value ≈ square_value ≈ triangular_value) diff --git a/src/Core.jl b/src/Core.jl index 18049f2..e33c94d 100644 --- a/src/Core.jl +++ b/src/Core.jl @@ -56,30 +56,99 @@ offset(p::Node, xy) = chxy(p, getxy(p) .+ xy) const WeightedNode{T<:Real} = Node{T} const UnWeightedNode = Node{ONE} +############################ Grid Types ############################ +""" +Abstract base type for grid geometries. + +Grid types define how coordinates are mapped to physical positions for distance calculations. +""" +abstract type AbstractGridType end + +""" + SquareGrid <: AbstractGridType + +A square lattice grid where coordinates directly correspond to physical positions. +For a node at grid position (i, j), the physical position is (i, j). +""" +struct SquareGrid <: AbstractGridType end + +""" + TriangularGrid <: AbstractGridType + +A triangular lattice grid where nodes form an equilateral triangular pattern. + +# Fields +- `offset_even_cols::Bool`: Whether even-numbered columns are offset vertically. + - `false` (default): Odd columns are offset by 0.5 units vertically + - `true`: Even columns are offset by 0.5 units vertically + +# Physical positioning +For a node at grid position (i, j): +- y-coordinate: `j * (√3 / 2)` (maintains equilateral triangle geometry) +- x-coordinate: `i + offset` where offset depends on `offset_even_cols`: + - If `offset_even_cols = false`: offset = `0.5` if j is odd, `0.0` if j is even + - If `offset_even_cols = true`: offset = `0.5` if j is even, `0.0` if j is odd + +# Examples +```julia +# Default triangular grid (odd columns offset) +grid1 = TriangularGrid() # equivalent to TriangularGrid(false) + +# Even columns offset +grid2 = TriangularGrid(true) +``` +""" +struct TriangularGrid <: AbstractGridType + offset_even_cols::Bool + + """ + TriangularGrid(offset_even_cols::Bool=false) + + Create a triangular grid with specified column offset pattern. + + # Arguments + - `offset_even_cols`: If true, even columns are offset by 0.5; if false (default), odd columns are offset. + """ + TriangularGrid(offset_even_cols::Bool=false) = new(offset_even_cols) +end + ############################ GridGraph ############################ -# GridGraph -struct GridGraph{NT<:Node} +# Main definition +struct GridGraph{NT<:Node, GT<:AbstractGridType} + gridtype::GT size::Tuple{Int,Int} nodes::Vector{NT} radius::Float64 end + +# Base constructors (for any node/grid type) +GridGraph(size::Tuple{Int,Int}, nodes::Vector{NT}, radius::Real) where {NT<:Node} = + GridGraph(SquareGrid(), size, nodes, radius) + +GridGraph(gridtype::GT, size::Tuple{Int,Int}, nodes::Vector{NT}, radius::Real) where {NT<:Node, GT<:AbstractGridType} = + GridGraph{NT, GT}(gridtype, size, nodes, radius) + function Base.show(io::IO, grid::GridGraph) - println(io, "$(typeof(grid)) (radius = $(grid.radius))") + gridtype_name = grid.gridtype isa SquareGrid ? "Square" : "Triangular" + println(io, "$(gridtype_name)$(typeof(grid)) (radius = $(grid.radius))") print_grid(io, grid; show_weight=SHOW_WEIGHT[]) end Base.size(gg::GridGraph) = gg.size Base.size(gg::GridGraph, i::Int) = gg.size[i] + function graph_and_weights(grid::GridGraph) - return unit_disk_graph(getfield.(grid.nodes, :loc), grid.radius), getfield.(grid.nodes, :weight) + # Use physical positions for both grid types + physical_locs = [physical_position(node, grid.gridtype) for node in grid.nodes] + return unitdisk_graph(physical_locs, grid.radius), getfield.(grid.nodes, :weight) end -function Graphs.SimpleGraph(grid::GridGraph{Node{ONE}}) - return unit_disk_graph(getfield.(grid.nodes, :loc), grid.radius) + +function Graphs.SimpleGraph(grid::GridGraph{Node{ONE}, GT}) where GT + physical_locs = [physical_position(node, grid.gridtype) for node in grid.nodes] + return unitdisk_graph(physical_locs, 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.neighbors(g::GridGraph, i::Int) = [j for j in 1:nv(g) if i != j && distance(g.nodes[i], g.nodes[j], g.gridtype) <= g.radius] +distance(n1::Node, n2::Node, grid_type::AbstractGridType) = sqrt(sum(abs2, physical_position(n1, grid_type) .- physical_position(n2, grid_type))) Graphs.nv(g::GridGraph) = length(g.nodes) Graphs.vertices(g::GridGraph) = 1:nv(g) @@ -116,4 +185,4 @@ function GridGraph(m::AbstractMatrix{SimpleCell{WT}}, radius::Real) where WT end end return GridGraph(size(m), nodes, radius) -end \ No newline at end of file +end diff --git a/src/UnitDiskMapping.jl b/src/UnitDiskMapping.jl index 628f921..e6dfc04 100644 --- a/src/UnitDiskMapping.jl +++ b/src/UnitDiskMapping.jl @@ -7,7 +7,8 @@ using LuxorGraphPlot using LuxorGraphPlot.Luxor.Colors # Basic types -export UnWeighted, Weighted +export UnWeighted, Weighted, TriangularWeighted +export SquareGrid, TriangularGrid export Cell, AbstractCell, SimpleCell export Node, WeightedNode, UnWeightedNode export graph_and_weights, GridGraph, coordinates @@ -41,8 +42,8 @@ export pathwidth, PathDecompositionMethod, MinhThiTrick, Greedy @deprecate Branching MinhThiTrick -include("utils.jl") include("Core.jl") +include("utils.jl") include("pathdecomposition/pathdecomposition.jl") include("copyline.jl") include("dragondrop.jl") @@ -51,6 +52,7 @@ include("logicgates.jl") include("gadgets.jl") include("mapping.jl") include("weighted.jl") +include("triangular.jl") include("simplifiers.jl") include("extracting_results.jl") include("visualize.jl") diff --git a/src/mapping.jl b/src/mapping.jl index 1297393..a6003a9 100644 --- a/src/mapping.jl +++ b/src/mapping.jl @@ -1,7 +1,35 @@ -# UnWeighted mode +# Constants for grid parameters +const DEFAULT_SQUARE_SPACING = 4 +const DEFAULT_TRIANGULAR_SPACING = 6 +const DEFAULT_PADDING = 2 +const SQUARE_UNIT_RADIUS = 1.5 +const TRIANGULAR_UNIT_RADIUS = 1.1 + +# Mode types struct UnWeighted end -# Weighted mode struct Weighted end +struct TriangularWeighted end + +# Spacing structure to handle different spacing for different directions +struct GridSpacing + row_spacing::Int # N direction + col_spacing::Int # M direction +end + +# Constructor for uniform spacing +GridSpacing(s::Int) = GridSpacing(s, s) + +# Get spacing value based on mode +get_spacing(::Union{UnWeighted, Weighted}) = GridSpacing(DEFAULT_SQUARE_SPACING) +get_spacing(::TriangularWeighted) = GridSpacing(DEFAULT_TRIANGULAR_SPACING) + +# Helper functions to get individual spacing values +row_spacing(s::GridSpacing) = s.row_spacing +col_spacing(s::GridSpacing) = s.col_spacing + +# Add Base methods for GridSpacing +Base.:(==)(s1::GridSpacing, s2::GridSpacing) = s1.row_spacing == s2.row_spacing && s1.col_spacing == s2.col_spacing +Base.hash(s::GridSpacing, h::UInt) = hash((s.row_spacing, s.col_spacing), h) Base.@kwdef struct MCell{WT} <: AbstractCell{WT} occupied::Bool = true @@ -63,9 +91,10 @@ struct MappingGrid{CT<:AbstractCell} lines::Vector{CopyLine} padding::Int content::Matrix{CT} + spacing::GridSpacing end -Base.:(==)(ug::MappingGrid{CT}, ug2::MappingGrid{CT}) where CT = ug.lines == ug2.lines && ug.content == ug2.content +Base.:(==)(ug::MappingGrid{CT}, ug2::MappingGrid{CT}) where CT = ug.lines == ug2.lines && ug.content == ug2.content && ug.spacing == ug2.spacing Base.size(ug::MappingGrid, args...) = size(ug.content, args...) padding(ug::MappingGrid) = ug.padding coordinates(ug::MappingGrid) = [ci.I for ci in findall(!isempty, ug.content)] @@ -89,17 +118,21 @@ function Graphs.SimpleGraph(ug::MappingGrid) if any(x->x.doubled || x.connected, ug.content) error("This mapping is not done yet!") end - return unitdisk_graph(coordinates(ug), 1.5) + return unitdisk_graph(coordinates(ug), SQUARE_UNIT_RADIUS) end -function GridGraph(ug::MappingGrid) +function GridGraph(mode, ug::MappingGrid) if any(x->x.doubled || x.connected, ug.content) error("This mapping is not done yet!") end - return GridGraph(size(ug), [Node((i,j), ug.content[i,j].weight) for (i, j) in coordinates(ug)], 1.5) + if mode isa TriangularWeighted + return GridGraph(TriangularGrid(), size(ug), [Node((i,j), ug.content[i,j].weight) for (i, j) in coordinates(ug)], TRIANGULAR_UNIT_RADIUS) + else + return GridGraph(size(ug), [Node((i,j), ug.content[i,j].weight) for (i, j) in coordinates(ug)], SQUARE_UNIT_RADIUS) + end end Base.show(io::IO, ug::MappingGrid) = print_grid(io, ug.content) -Base.copy(ug::MappingGrid) = MappingGrid(ug.lines, ug.padding, copy(ug.content)) +Base.copy(ug::MappingGrid) = MappingGrid(ug.lines, ug.padding, copy(ug.content), ug.spacing) # TODO: # 1. check if the resulting graph is a unit-disk @@ -186,7 +219,7 @@ end function map_config_copyback!(ug::MappingGrid, c::AbstractMatrix) res = zeros(Int, length(ug.lines)) for line in ug.lines - locs = copyline_locations(nodetype(ug), line; padding=ug.padding) + locs = copyline_locations(nodetype(ug), line, ug.padding, ug.spacing) count = 0 for (iloc, loc) in enumerate(locs) gi, ci = ug.content[loc...], c[loc...] @@ -230,21 +263,26 @@ function remove_order(g::AbstractGraph, vertex_order::AbstractVector{Int}) return addremove end -function center_location(tc::CopyLine; padding::Int) - s = 4 - I = s*(tc.hslot-1)+padding+2 - J = s*(tc.vslot-1)+padding+1 +function center_location(tc::CopyLine, padding::Int, s::Union{Int, GridSpacing}) + spacing = s isa Int ? GridSpacing(s) : s + row_s = row_spacing(spacing) + col_s = col_spacing(spacing) + I = row_s*(tc.hslot-1)+padding+2 + J = col_s*(tc.vslot-1)+padding+1 return I, J end # NT is node type -function copyline_locations(::Type{NT}, tc::CopyLine; padding::Int) where NT - s = 4 +# Low-level API: explicit parameters required, no defaults [[memory:3852344]] +function copyline_locations(::Type{NT}, tc::CopyLine, padding::Int, s::Union{Int, GridSpacing}) where NT nline = 0 - I, J = center_location(tc; padding=padding) + spacing = s isa Int ? GridSpacing(s) : s + row_s = row_spacing(spacing) + col_s = col_spacing(spacing) + I, J = center_location(tc, padding, s) locations = NT[] # grow up - start = I+s*(tc.vstart-tc.hslot)+1 + start = I+row_s*(tc.vstart-tc.hslot)+1 if tc.vstart < tc.hslot nline += 1 end @@ -252,7 +290,7 @@ function copyline_locations(::Type{NT}, tc::CopyLine; padding::Int) where NT push!(locations, node(NT, i, J, 1+(i!=start))) # half weight on last node end # grow down - stop = I+s*(tc.vstop-tc.hslot)-1 + stop = I+row_s*(tc.vstop-tc.hslot)-1 if tc.vstop > tc.hslot nline += 1 end @@ -264,7 +302,7 @@ function copyline_locations(::Type{NT}, tc::CopyLine; padding::Int) where NT end end # grow right - stop = J+s*(tc.hstop-tc.vslot)-1 + stop = J+col_s*(tc.hstop-tc.vslot)-1 if tc.hstop > tc.vslot nline += 1 end @@ -278,25 +316,26 @@ nodetype(::MappingGrid{MCell{WT}}) where WT = Node{WT} cell_type(::Type{Node{WT}}) where WT = MCell{WT} nodetype(::UnWeighted) = UnWeightedNode +nodetype(::TriangularWeighted) = WeightedNode{Int} node(::Type{<:UnWeightedNode}, i, j, w) = Node(i, j) -function ugrid(mode, g::SimpleGraph, vertex_order::AbstractVector{Int}; padding=2, nrow=nv(g)) - @assert padding >= 2 +function ugrid(mode, g::SimpleGraph, vertex_order::AbstractVector{Int}; padding::Int=DEFAULT_PADDING, nrow::Int=nv(g)) + @assert padding >= DEFAULT_PADDING # create an empty canvas n = nv(g) - s = 4 - N = (n-1)*s+1+2*padding - M = nrow*s+1+2*padding - u = fill(empty(mode isa Weighted ? MCell{Int} : MCell{ONE}), M, N) + s = get_spacing(mode) + N = (n-1)*s.col_spacing+2+2*padding + M = nrow*s.row_spacing+2+2*padding + u = fill(empty(mode isa Union{Weighted, TriangularWeighted} ? MCell{Int} : MCell{ONE}), M, N) # add T-copies copylines = create_copylines(g, vertex_order) for tc in copylines - for loc in copyline_locations(nodetype(mode), tc; padding=padding) + for loc in copyline_locations(nodetype(mode), tc, padding, s) add_cell!(u, loc) end end - ug = MappingGrid(copylines, padding, u) + ug = MappingGrid(copylines, padding, u, s) for e in edges(g) I, J = crossat(ug, e.src, e.dst) connect_cell!(ug.content, I, J-1) @@ -313,8 +352,8 @@ function crossat(ug::MappingGrid, v, w) i, j = findfirst(x->x.vertex==v, ug.lines), findfirst(x->x.vertex==w, ug.lines) i, j = minmax(i, j) hslot = ug.lines[i].hslot - s = 4 - return (hslot-1)*s+2+ug.padding, (j-1)*s+1+ug.padding + s = ug.spacing + return (hslot-1)*s.row_spacing+2+ug.padding, (j-1)*s.col_spacing+1+ug.padding end """ @@ -335,24 +374,26 @@ function embed_graph(mode, g::SimpleGraph; vertex_order=MinhThiTrick()) end # we reverse the vertex order of the pathwidth result, # because this order corresponds to the vertex-seperation. - ug = ugrid(mode, g, L.vertices[end:-1:1]; padding=2, nrow=L.vsep+1) + ug = ugrid(mode, g, L.vertices[end:-1:1]; padding=DEFAULT_PADDING, nrow=L.vsep+1) return ug end function mis_overhead_copylines(ug::MappingGrid{WC}) where {WC} sum(ug.lines) do line - mis_overhead_copyline(WC <: WeightedMCell ? Weighted() : UnWeighted(), line) + mis_overhead_copyline(WC <: WeightedMCell ? Weighted() : UnWeighted(), line, ug.spacing) end end -function mis_overhead_copyline(w::W, line::CopyLine) where W - if W === Weighted - s = 4 - return (line.hslot - line.vstart) * s + - (line.vstop - line.hslot) * s + - max((line.hstop - line.vslot) * s - 2, 0) +function mis_overhead_copyline(w::W, line::CopyLine, s::Union{Int, GridSpacing}) where W + spacing = s isa Int ? GridSpacing(s) : s + if W === Weighted || W === TriangularWeighted + row_s = row_spacing(spacing) + col_s = col_spacing(spacing) + return (line.hslot - line.vstart) * row_s + + (line.vstop - line.hslot) * row_s + + max((line.hstop - line.vslot) * col_s - 2, 0) else - locs = copyline_locations(nodetype(w), line; padding=2) + locs = copyline_locations(nodetype(w), line, DEFAULT_PADDING, s) @assert length(locs) % 2 == 1 return length(locs) ÷ 2 end @@ -365,6 +406,7 @@ struct MappingResult{NT} padding::Int mapping_history::Vector{Tuple{Pattern,Int,Int}} mis_overhead::Int + spacing::GridSpacing end """ @@ -395,10 +437,12 @@ function map_graph(mode, g::SimpleGraph; vertex_order=MinhThiTrick(), ruleset=de ug = embed_graph(mode, g; vertex_order=vertex_order) mis_overhead0 = mis_overhead_copylines(ug) ug, tape = apply_crossing_gadgets!(mode, ug) + + # Note that apply_simplifier_gadgets! has not been specifically developed for the triangular lattice. However, the currently implemented components are also applicable to the triangular lattice and can provide a certain degree of simplification. Further development should take this into consideration. ug, tape2 = apply_simplifier_gadgets!(ug; ruleset=ruleset) mis_overhead1 = isempty(tape) ? 0 : sum(x->mis_overhead(x[1]), tape) mis_overhead2 = isempty(tape2) ? 0 : sum(x->mis_overhead(x[1]), tape2) - return MappingResult(GridGraph(ug), ug.lines, ug.padding, vcat(tape, tape2) , mis_overhead0 + mis_overhead1 + mis_overhead2) + return MappingResult(GridGraph(mode, ug), ug.lines, ug.padding, vcat(tape, tape2) , mis_overhead0 + mis_overhead1 + mis_overhead2, ug.spacing) end """ @@ -427,12 +471,13 @@ function map_config_back(res::MappingResult, cfg) end function _map_configs_back(r::MappingResult{UnWeightedNode}, configs::AbstractVector{<:AbstractMatrix}) cm = cell_matrix(r.grid_graph) - ug = MappingGrid(r.lines, r.padding, MCell.(cm)) + ug = MappingGrid(r.lines, r.padding, MCell.(cm), r.spacing) unapply_gadgets!(ug, r.mapping_history, copy.(configs))[2] end default_simplifier_ruleset(::UnWeighted) = vcat([rotated_and_reflected(rule) for rule in simplifier_ruleset]...) default_simplifier_ruleset(::Weighted) = weighted.(default_simplifier_ruleset(UnWeighted())) +default_simplifier_ruleset(::TriangularWeighted) = weighted.(default_simplifier_ruleset(UnWeighted())) print_config(mr::MappingResult, config::AbstractMatrix) = print_config(stdout, mr, config) function print_config(io::IO, mr::MappingResult, config::AbstractMatrix) diff --git a/src/triangular.jl b/src/triangular.jl new file mode 100644 index 0000000..dc51cdc --- /dev/null +++ b/src/triangular.jl @@ -0,0 +1,418 @@ +abstract type TriangularCrossPattern <: Pattern end + +struct TriCross{CON} <: TriangularCrossPattern end +iscon(::TriCross{CON}) where {CON} = CON +# ⋅ ◆ ⋅ · +# ◆ ◉ ● ● +# ⋅ ● · · +# · ● ⋅ ⋅ +# · ● ⋅ ⋅ +# ⋅ ● ⋅ ⋅ +function source_graph(::TriCross{true}) + locs = Node.([(2,1), (2,2), (2,3), (2,4), (1,2), (2,2), (3,2), (4,2), (5,2), (6,2)]) + g = simplegraph([(1,2), (2,3), (3,4), (5,6), (6,7), (7,8), (8,9), (9,10), (1,5)]) + return locs, g, [1,5,10,4] +end + +# · ● · ● +# ● ● ● · +# · · ● · +# · ● ● · +# ● · · · +# ● ● · · +function mapped_graph(::TriCross{true}) + locs = Node.([(1,2), (2,1), (2,2), (2,3), (1,4), (3,3), (4,2), (4,3), (5,1), (6,1), (6,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [2,1,11,5] +end +Base.size(::TriCross{true}) = (6, 4) +cross_location(::TriCross{true}) = (2, 2) +connected_nodes(::TriCross{true}) = [1,5] + +function weighted(p::TriCross{true}) + sw = [2,2,2,2,2,2,2,2,2,2] + mw = [3,2,3,3,2,2,2,2,2,2,2] + return weighted(p, sw, mw) +end + +# · ⋅ ⋅ ● ⋅ ⋅ +# ● ● ● ◉ ● ● +# · ⋅ ⋅ ● ⋅ ⋅ +# · ⋅ ⋅ ● ⋅ ⋅ +# · ⋅ ⋅ ● ⋅ ⋅ +# · ⋅ ⋅ ● ⋅ ⋅ +function source_graph(::TriCross{false}) + locs = Node.([(2,2), (2,3), (2,4), (2,5), (2,6), (1,4), (2,4), (3,4), (4,4), (5,4), (6,4), (2,1)]) + g = simplegraph([(1,2), (2,3), (3,4), (4,5), (6,7), (7,8), (8,9), (9,10), (10,11), (12,1)]) + return locs, g, [12,6,11,5] +end + +# · ⋅ ⋅ ● ⋅ ⋅ +# ● ● ● ● ● ● +# · ● ● ● ● · +# · ● ● · ⋅ ⋅ +# · ● ⋅ · ⋅ ⋅ +# · · ● ● ⋅ ⋅ +function mapped_graph(::TriCross{false}) + locs = Node.([(1,4), (2,2), (2,3), (2,4), (2,5), (2,6), (3,2), (3,3), (3,4), (3,5), (4,2), (4,3), (5,2), (6,3), (6,4), (2,1)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [16,1,15,6] +end +Base.size(::TriCross{false}) = (6, 6) +cross_location(::TriCross{false}) = (2, 4) + +function weighted(p::TriCross{false}) + sw = [2,2,2,2,2,2,2,2,2,2,2,2] + mw = [3,3,2,4,2,2,2,4,3,2,2,2,2,2,2,2] + return weighted(p, sw, mw) +end + +struct TriTCon_left <: TriangularCrossPattern end +# ⋅ ◆ ⋅ ⋅ · +# ◆ ● · · · +# ⋅ ● · ⋅ · +# ⋅ ● · · · +# · ● · · · +# · ● · · · +function source_graph(::TriTCon_left) + locs = Node.([(1,2), (2,1), (2,2), (3,2), (4,2), (5,2), (6,2)]) + g = simplegraph([(1,2), (1,3), (3,4), (4,5), (5,6), (6,7)]) + return locs, g, [1,2,7] +end +connected_nodes(::TriTCon_left) = [1, 2] + +# ⋅ ● ⋅ ⋅ · +# ● ● ● ● · +# ⋅ · ● ⋅ · +# ⋅ ● ● · · +# ● · · · · +# ● ● · · · +function mapped_graph(::TriTCon_left) + locs = Node.([(1,2), (2,1), (2,2), (2,3), (2,4), (3,3), (4,2), (4,3), (5,1), (6,1), (6,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1,2,11] +end +Base.size(::TriTCon_left) = (6,5) +cross_location(::TriTCon_left) = (2,2) +iscon(::TriTCon_left) = true + +function weighted(p::TriTCon_left) + sw = [2,1,2,2,2,2,2] + mw = [3,2,3,3,1,3,2,2,2,2,2] + return weighted(p, sw, mw) +end + +struct TriTCon_down <: TriangularCrossPattern end +# · · · +# ◆ ● ● +# ⋅ ◆ · +function source_graph(::TriTCon_down) + locs = Node.([(2,1), (2,2), (2,3), (3,2)]) + g = simplegraph([(1,2), (2,3), (1,4)]) + return locs, g, [1,4,3] +end +connected_nodes(::TriTCon_down) = [1, 4] + +# · · · +# · ● · +# ● ● ● +function mapped_graph(::TriTCon_down) + locs = Node.([(2,2), (3,1), (3,2), (3,3)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [2,3,4] +end +Base.size(::TriTCon_down) = (3,3) +cross_location(::TriTCon_down) = (2,2) +iscon(::TriTCon_down) = true + +function weighted(p::TriTCon_down) + sw = [2,2,2,1] + mw = [2,2,3,2] + return weighted(p, sw, mw) +end + +struct TriTCon_up <: TriangularCrossPattern end +# ⋅ ◆ · +# ◆ ● ● +# · · · +function source_graph(::TriTCon_up) + locs = Node.([(1,2), (2,1), (2,2), (2,3)]) + g = simplegraph([(1,2), (2,3), (3,4)]) + return locs, g, [2,1,4] +end +connected_nodes(::TriTCon_up) = [1, 2] + +# · ● · +# ● ● ● +# · · · +function mapped_graph(::TriTCon_up) + locs = Node.([(1,2), (2,1), (2,2), (2,3)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [2,1,4] +end +Base.size(::TriTCon_up) = (3,3) +cross_location(::TriTCon_up) = (2,2) +iscon(::TriTCon_up) = true + +function weighted(p::TriTCon_up) + sw = [1,2,2,2] + mw = [3,2,2,2] + return weighted(p, sw, mw) +end + +struct TriTrivialTurn_left <: TriangularCrossPattern end +# ⋅ ◆ +# ◆ ⋅ +function source_graph(::TriTrivialTurn_left) + locs = Node.([(1,2), (2,1)]) + g = simplegraph([(1,2)]) + return locs, g, [1,2] +end +# ⋅ ● +# ● ⋅ +function mapped_graph(::TriTrivialTurn_left) + locs = Node.([(1,2),(2,1)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1,2] +end +Base.size(::TriTrivialTurn_left) = (2,2) +cross_location(::TriTrivialTurn_left) = (2,2) +iscon(::TriTrivialTurn_left) = true +connected_nodes(::TriTrivialTurn_left) = [1, 2] + +function weighted(p::TriTrivialTurn_left) + sw = [1,1] + mw = [1,1] + return weighted(p, sw, mw) +end + +struct TriTrivialTurn_right <: TriangularCrossPattern end +# ◆ · +# · ◆ +function source_graph(::TriTrivialTurn_right) + locs = Node.([(1,1), (2,2)]) + g = simplegraph([(1,2)]) + return locs, g, [1,2] +end +# ⋅ · +# ● ● +function mapped_graph(::TriTrivialTurn_right) + locs = Node.([(2,1),(2,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1,2] +end +Base.size(::TriTrivialTurn_right) = (2,2) +cross_location(::TriTrivialTurn_right) = (1,2) +iscon(::TriTrivialTurn_right) = true +connected_nodes(::TriTrivialTurn_right) = [1, 2] + +function weighted(p::TriTrivialTurn_right) + sw = [1,1] + mw = [1,1] + return weighted(p, sw, mw) +end + +struct TriEndTurn <: TriangularCrossPattern end +# ⋅ ● ⋅ ⋅ +# ⋅ ● ● ⋅ +# ⋅ ⋅ ⋅ ⋅ +function source_graph(::TriEndTurn) + locs = Node.([(1,2), (2,2), (2,3)]) + g = simplegraph([(1,2), (2,3)]) + return locs, g, [1] +end +# ⋅ ● ⋅ ⋅ +# ⋅ ⋅ ⋅ ⋅ +# ⋅ ⋅ ⋅ ⋅ +function mapped_graph(::TriEndTurn) + locs = Node.([(1,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1] +end +Base.size(::TriEndTurn) = (3,4) +cross_location(::TriEndTurn) = (2,2) +iscon(::TriEndTurn) = false + +function weighted(p::TriEndTurn) + sw = [2,2,1] + mw = [1] + return weighted(p, sw, mw) +end + +struct TriTurn <: TriangularCrossPattern end +iscon(::TriTurn) = false +# ⋅ ● ⋅ ⋅ +# ⋅ ● ● ● +# ⋅ ⋅ ⋅ ⋅ +function source_graph(::TriTurn) + locs = Node.([(1,2), (2,2), (2,3), (2,4)]) + g = simplegraph([(1,2), (2,3), (3,4)]) + return locs, g, [1,4] +end + +# ⋅ ● · ⋅ +# · ● · ● +# · · ● ⋅ +function mapped_graph(::TriTurn) + locs = Node.([(1,2), (2,2), (3,3), (2,4)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1,4] +end +Base.size(::TriTurn) = (3, 4) +cross_location(::TriTurn) = (2,2) + +function weighted(p::TriTurn) + sw = [2,2,2,2] + mw = [2,2,2,2] + return weighted(p, sw, mw) +end + +struct TriWTurn <: TriangularCrossPattern end +# ⋅ ⋅ ⋅ ⋅ +# ⋅ ⋅ ● ● +# ⋅ ● ● ⋅ +# ⋅ ● ⋅ ⋅ +function source_graph(::TriWTurn) + locs = Node.([(2,3), (2,4), (3,2),(3,3),(4,2)]) + g = simplegraph([(1,2), (1,4), (3,4),(3,5)]) + return locs, g, [2, 5] +end +# ⋅ ⋅ ⋅ ● +# ⋅ ⋅ ● ⋅ +# ⋅ ● ● ⋅ +# ⋅ ● ⋅ ⋅ +function mapped_graph(::TriWTurn) + locs = Node.([(1,4), (2,3), (3,2), (3,3), (4,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1, 5] +end +Base.size(::TriWTurn) = (4, 4) +cross_location(::TriWTurn) = (2,2) +iscon(::TriWTurn) = false + +function weighted(p::TriWTurn) + sw = [2,2,2,2,2] + mw = [2,2,2,2,2] + return weighted(p, sw, mw) +end + +struct TriBranchFix <: TriangularCrossPattern end +# ⋅ ● ⋅ ⋅ +# ⋅ ● ● ⋅ +# ⋅ ● ● ⋅ +# ⋅ ● ⋅ ⋅ +function source_graph(::TriBranchFix) + locs = Node.([(1,2), (2,2), (2,3),(3,3),(3,2),(4,2)]) + g = simplegraph([(1,2), (2,3), (3,4),(4,5), (5,6)]) + return locs, g, [1, 6] +end +# ⋅ ● ⋅ ⋅ +# ⋅ ● ⋅ ⋅ +# ⋅ ● ⋅ ⋅ +# ⋅ ● ⋅ ⋅ +function mapped_graph(::TriBranchFix) + locs = Node.([(1,2),(2,2),(3,2),(4,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1, 4] +end +Base.size(::TriBranchFix) = (4, 4) +cross_location(::TriBranchFix) = (2,2) +iscon(::TriBranchFix) = false + +function weighted(p::TriBranchFix) + sw = [2,2,2,2,2,2] + mw = [2,2,2,2] + return weighted(p, sw, mw) +end + +struct TriBranchFixB <: TriangularCrossPattern end +# ⋅ ⋅ ⋅ ⋅ +# ⋅ ⋅ ● ⋅ +# ⋅ ● ● ⋅ +# ⋅ ● ⋅ ⋅ +function source_graph(::TriBranchFixB) + locs = Node.([(2,3),(3,2),(3,3),(4,2)]) + g = simplegraph([(1,3), (2,3), (2,4)]) + return locs, g, [1, 4] +end +# ⋅ ⋅ ⋅ ⋅ +# ⋅ ⋅ ⋅ ⋅ +# ⋅ ● ⋅ ⋅ +# ⋅ ● ⋅ ⋅ +function mapped_graph(::TriBranchFixB) + locs = Node.([(3,2),(4,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1, 2] +end +Base.size(::TriBranchFixB) = (4, 4) +cross_location(::TriBranchFixB) = (2,2) +iscon(::TriBranchFixB) = false + +function weighted(p::TriBranchFixB) + sw = [2,2,2,2] + mw = [2,2] + return weighted(p, sw, mw) +end + +struct TriBranch <: TriangularCrossPattern end +# ⋅ ● ⋅ ⋅ +# ⋅ ● ● ● +# ⋅ ● ● ⋅ +# ⋅ ● ⋅ ⋅ +# ⋅ ● · ⋅ +# ⋅ ● · ⋅ +function source_graph(::TriBranch) + locs = Node.([(1,2),(2,2),(2,3),(2,4),(3,3),(3,2),(4,2),(5,2),(6,2)]) + g = simplegraph([(1,2), (2,3), (3, 4), (3,5), (5,6), (6,7), (7,8), (8,9)]) + return locs, g, [1, 4, 9] +end +# ⋅ ● ⋅ ⋅ +# ⋅ ● · ● +# · · ● · +# ⋅ ● ● ⋅ +# ● · ⋅ ⋅ +# ● ● ⋅ ⋅ +function mapped_graph(::TriBranch) + locs = Node.([(1,2),(2,2),(2,4),(3,3),(4,2),(4,3),(5,1),(6,1),(6,2)]) + return locs, triangular_unitdisk_graph(locs, 1.1, TriangularGrid(true)), [1,3,9] +end +Base.size(::TriBranch) = (6, 4) +cross_location(::TriBranch) = (2,2) +iscon(::TriBranch) = false + +function weighted(p::TriBranch) + sw = [2,2,3,2,2,2,2,2,2] + mw = [2,2,2,3,2,2,2,2,2] + return weighted(p, sw, mw) +end + +const triangular_crossing_ruleset = ( + TriCross{false}(), + TriCross{true}(), + TriTCon_left(), + TriTCon_up(), + TriTCon_down(), + TriTrivialTurn_left(), + TriTrivialTurn_right(), + TriEndTurn(), + TriTurn(), + TriWTurn(), + TriBranchFix(), + TriBranchFixB(), + TriBranch(), + ) +const crossing_ruleset_triangular_weighted = weighted.(triangular_crossing_ruleset) +get_ruleset(::TriangularWeighted) = crossing_ruleset_triangular_weighted + +# Specialized mis_overhead for TriangularCrossPattern WeightedGadgets +# For triangular patterns, we don't use the *2 multiplier from the general WeightedGadget method +mis_overhead(w::WeightedGadget{<:TriangularCrossPattern}) = mis_overhead(w.gadget) + +# mis_overhead functions for TriangularCrossPattern types +# These values should be computed properly using compute_mis_overhead function from project/createmap.jl +mis_overhead(::TriCross{true}) = 1 +mis_overhead(::TriCross{false}) = 3 +mis_overhead(::TriTCon_left) = 4 +mis_overhead(::TriTCon_down) = 0 +mis_overhead(::TriTCon_up) = 0 +mis_overhead(::TriTrivialTurn_left) = 0 +mis_overhead(::TriTrivialTurn_right) = 0 +mis_overhead(::TriEndTurn) = -2 +mis_overhead(::TriTurn) = 0 +mis_overhead(::TriWTurn) = 0 +mis_overhead(::TriBranchFix) = -2 +mis_overhead(::TriBranchFixB) = -2 +mis_overhead(::TriBranch) = 0 + +for (T, centerloc) in [(:TriTurn, (1, 2)), (:TriBranch, (1,2)), (:TriBranchFix, (3, 2)), (:TriBranchFixB, (3, 2)), (:TriWTurn, (2, 3)), (:TriEndTurn, (1, 2))] + @eval source_centers(::WeightedGadget{<:$T}) = [cross_location($T()) .+ (0, 1)] + @eval mapped_centers(::WeightedGadget{<:$T}) = [$centerloc] +end \ No newline at end of file diff --git a/src/utils.jl b/src/utils.jl index 1fa4cda..1134514 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -41,6 +41,61 @@ function unitdisk_graph(locs::AbstractVector, unit::Real) return g end +function triangular_unitdisk_graph(locs::AbstractVector, unit::Real, grid_type::TriangularGrid=TriangularGrid()) + n = length(locs) + g = SimpleGraph(n) + physical_locs = [physical_position(node, grid_type) for node in locs] + for i=1:n, j=i+1:n + if sum(abs2, physical_locs[i] .- physical_locs[j]) < unit ^ 2 + add_edge!(g, i, j) + end + end + return g +end + +""" + physical_position(node, grid_type) + +Convert grid coordinates to physical coordinates for distance calculations. + +# Arguments +- `node`: A `Node` instance +- `grid_type`: A subtype of `AbstractGridType` specifying the grid geometry + +# Returns +- Tuple `(x, y)` representing physical coordinates + +# Grid type behaviors +- `SquareGrid`: Physical position equals grid position +- `TriangularGrid`: Maps to equilateral triangular lattice with appropriate column offsets + +# Examples +```julia +node = Node((3, 4)) +square_pos = physical_position(node, SquareGrid()) # (3.0, 4.0) +tri_pos = physical_position(node, TriangularGrid()) # (3.0, 3.464...) +tri_pos_alt = physical_position(node, TriangularGrid(true)) # (3.5, 3.464...) +``` +""" +function physical_position(node::Node, ::SquareGrid) + # For square grids, coordinates are already physical positions + return float.(node.loc) +end + +function physical_position(node::Node, grid::TriangularGrid) + # For triangular grids, use the grid's offset setting to create equilateral triangles + i, j = node.loc + y = j * (√3 / 2) # Vertical spacing for equilateral triangles + if grid.offset_even_cols + # add offset to even columns + x = i + (iseven(j) ? 0.5 : 0.0) + else + # add offset to odd columns (default behavior) + x = i + (isodd(j) ? 0.5 : 0.0) + end + return (x, y) +end + function is_independent_set(g::SimpleGraph, config) for e in edges(g) if config[e.src] == config[e.dst] == 1 diff --git a/src/visualize.jl b/src/visualize.jl index 4f46e1c..3ec7931 100644 --- a/src/visualize.jl +++ b/src/visualize.jl @@ -1,4 +1,17 @@ # normalized to minimum weight and maximum weight + +""" + plot_coordinates(gg::GridGraph, i::Int, j::Int, unit::Float64) + +Map grid coordinates to plot coordinates using the physical position +for the grid type, then rotate to plotting frame. +""" +function plot_coordinates(gg::GridGraph, i::Int, j::Int, unit::Float64) + # Use unified physical coordinates for both grid types + x, y = physical_position(Node(i, j), gg.gridtype) + return (y * unit, -x * unit) +end + function LuxorGraphPlot.show_graph(gg::GridGraph; format = :svg, filename = nothing, @@ -18,10 +31,14 @@ function LuxorGraphPlot.show_graph(gg::GridGraph; xmin, xmax = extrema(first.(coos)) ymin, ymax = extrema(last.(coos)) nodestore() do ns - filledlocs = map(coo->circle!((unit * (coo[2] - 1), -unit * (coo[1] - 1)), config.vertex_size), coos) + # Use the new coordinate transformation for filled locations + filledlocs = map(coo->circle!(plot_coordinates(gg, coo[1], coo[2], unit), config.vertex_size), coos) emptylocs, edges = [], [] for i=xmin:xmax, j=ymin:ymax - (i, j) ∉ coos && push!(emptylocs, circle!(((j-1) * unit, -(i-1) * unit), config.vertex_size/10)) + if (i, j) ∉ coos + plot_pos = plot_coordinates(gg, i, j, unit) + push!(emptylocs, circle!(plot_pos, config.vertex_size/10)) + end end for e in Graphs.edges(graph_and_weights(gg)[1]) i, j = e.src, e.dst diff --git a/src/weighted.jl b/src/weighted.jl index 37df58a..96bb26f 100644 --- a/src/weighted.jl +++ b/src/weighted.jl @@ -95,9 +95,9 @@ function move_center(w::WeightedGadgetTypes, nodexy, offset) error("center not found, source center = $(source_centers(w)), while offset = $(offset)") end -trace_centers(r::MappingResult) = trace_centers(r.lines, r.padding, r.mapping_history) -function trace_centers(lines, padding, tape) - center_locations = map(x->center_location(x; padding) .+ (0, 1), lines) +trace_centers(r::MappingResult) = trace_centers(r.lines, r.padding, r.mapping_history, r.spacing) +function trace_centers(lines, padding, tape, spacing=4) + center_locations = map(x->center_location(x, padding, spacing) .+ (0, 1), lines) for (gadget, i, j) in tape m, n = size(gadget) for (k, centerloc) in enumerate(center_locations) @@ -122,12 +122,17 @@ function _map_configs_back(r::MappingResult{<:WeightedNode}, configs::AbstractVe end # simple rules for crossing gadgets -for (GT, s1, m1, s3, m3) in [(:(Cross{true}), [], [], [], []), (:(Cross{false}), [], [], [], []), - (:(WTurn), [], [], [], []), (:(BranchFix), [], [], [], []), (:(Turn), [], [], [], []), - (:(TrivialTurn), [1, 2], [1, 2], [], []), (:(BranchFixB), [1], [1], [], []), - (:(EndTurn), [3], [1], [], []), (:(TCon), [2], [2], [], []), - (:(Branch), [], [], [4], [2]), - ] +for (GT, s1, m1, s3, m3) in [ + (:(Cross{true}), [], [], [], []), + (:(Cross{false}), [], [], [], []), + (:(WTurn), [], [], [], []), + (:(BranchFix), [], [], [], []), + (:(Turn), [], [], [], []), + (:(TrivialTurn), [1, 2], [1, 2], [], []), + (:(BranchFixB), [1], [1], [], []), + (:(EndTurn), [3], [1], [], []), + (:(TCon), [2], [2], [], []), + (:(Branch), [], [], [4], [2]),] @eval function weighted(g::$GT) slocs, sg, spins = source_graph(g) mlocs, mg, mpins = mapped_graph(g) diff --git a/test/mapping.jl b/test/mapping.jl index 1d5a699..583e6c1 100644 --- a/test/mapping.jl +++ b/test/mapping.jl @@ -41,7 +41,7 @@ end mis_overhead1 = sum(x->mis_overhead(x[1]), tape) mis_overhead2 = sum(x->mis_overhead(x[1]), tape2) @show mis_overhead2 - gp = GenericTensorNetwork(IndependentSet(SimpleGraph(ug3)); optimizer=GreedyMethod(nrepeat=10)) + gp = GenericTensorNetwork(IndependentSet(SimpleGraph(ug3))) missize_map = solve(gp, SizeMax())[].n missize = solve(GenericTensorNetwork(IndependentSet(g)), SizeMax())[].n @test mis_overhead0 + mis_overhead1 + mis_overhead2 + missize == missize_map diff --git a/test/runtests.jl b/test/runtests.jl index 2350059..6c9c7ed 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -55,4 +55,8 @@ end @testset "reduceto" begin include("reduceto.jl") +end + +@testset "triangular" begin + include("triangular.jl") end \ No newline at end of file diff --git a/test/triangular.jl b/test/triangular.jl new file mode 100644 index 0000000..68656e1 --- /dev/null +++ b/test/triangular.jl @@ -0,0 +1,104 @@ +using Test, UnitDiskMapping, Graphs, GenericTensorNetworks +using GenericTensorNetworks: TropicalF64, content +using Random +using UnitDiskMapping: is_independent_set + +@testset "triangular gadgets" begin + for s in UnitDiskMapping.crossing_ruleset_triangular_weighted + println("Testing triangular gadget:\n$s") + locs1, g1, pins1 = source_graph(s) + locs2, g2, pins2 = mapped_graph(s) + @assert length(locs1) == nv(g1) + w1 = getfield.(locs1, :weight) + w2 = getfield.(locs2, :weight) + w1[pins1] .-= 1 + w2[pins2] .-= 1 + gp1 = GenericTensorNetwork(IndependentSet(g1, w1), openvertices=pins1) + gp2 = GenericTensorNetwork(IndependentSet(g2, w2), openvertices=pins2) + m1 = solve(gp1, SizeMax()) + m2 = solve(gp2, SizeMax()) + mm1 = maximum(m1) + mm2 = maximum(m2) + @test nv(g1) == length(locs1) && nv(g2) == length(locs2) + if !(all((mm1 .== m1) .== (mm2 .== m2))) + @show m1 + @show m2 + end + @test all((mm1 .== m1) .== (mm2 .== m2)) + @test content(mm1 / mm2) == -mis_overhead(s) + end +end + +@testset "triangular copy lines" begin + for (vstart, vstop, hstop) in [ + (3, 7, 8), (3, 5, 8), (5, 9, 8), (5, 5, 8), + (1, 7, 5), (5, 8, 5), (1, 5, 5), (5, 5, 5)] + tc = UnitDiskMapping.CopyLine(1, 5, 5, vstart, vstop, hstop) + locs = UnitDiskMapping.copyline_locations(UnitDiskMapping.WeightedNode, tc, 2, UnitDiskMapping.get_spacing(TriangularWeighted())) + g = SimpleGraph(length(locs)) + weights = getfield.(locs, :weight) + for i=1:length(locs)-1 + if i==1 || locs[i-1].weight == 1 # starting point + add_edge!(g, length(locs), i) + else + add_edge!(g, i, i-1) + end + end + gp = GenericTensorNetwork(IndependentSet(g, weights)) + @test solve(gp, SizeMax())[].n == UnitDiskMapping.mis_overhead_copyline(TriangularWeighted(), tc, UnitDiskMapping.get_spacing(TriangularWeighted())) + end +end + +@testset "triangular map configurations back" begin + Random.seed!(2) + for graphname in [:bull, :petersen, :cubical, :house, :diamond, :tutte] + @show graphname + g = smallgraph(graphname) + weights = fill(0.2, nv(g)) + r = map_graph(TriangularWeighted(), g) + mapped_weights = UnitDiskMapping.map_weights(r, weights) + mgraph, _ = graph_and_weights(r.grid_graph) + gp = GenericTensorNetwork(IndependentSet(mgraph, round.(Int, mapped_weights .* 10))) + missize_map = solve(gp, CountingMax())[] + + missize = solve(GenericTensorNetwork(IndependentSet(g, round.(Int, weights .* 10))), CountingMax())[] + @test r.mis_overhead + missize.n / 10 ≈ missize_map.n / 10 + @test missize.c == missize_map.c + + T = GenericTensorNetworks.sampler_type(nv(mgraph), 2) + misconfig = solve(gp, SingleConfigMax())[].c + c = zeros(Int, size(r.grid_graph)) + for (i, n) in enumerate(r.grid_graph.nodes) + c[n.loc...] = misconfig.data[i] + end + + center_locations = trace_centers(r) + indices = CartesianIndex.(center_locations) + sc = c[indices] + @test count(isone, sc) ≈ (missize.n / 10) * 5 + @test is_independent_set(g, sc) + end +end + +@testset "triangular interface" begin + Random.seed!(2) + g = smallgraph(:petersen) + res = map_graph(TriangularWeighted(), g) + + # checking size + mgraph, _ = graph_and_weights(res.grid_graph) + ws = rand(nv(g)) + weights = UnitDiskMapping.map_weights(res, ws) + + gp = GenericTensorNetwork(IndependentSet(mgraph, weights); optimizer=TreeSA(ntrials=1, niters=10)) + missize_map = solve(gp, SizeMax())[].n + missize = solve(GenericTensorNetwork(IndependentSet(g, ws)), SizeMax())[].n + @test res.mis_overhead + missize ≈ missize_map + + # checking mapping back + T = GenericTensorNetworks.sampler_type(nv(mgraph), 2) + misconfig = solve(gp, SingleConfigMax())[].c + original_configs = map_config_back(res, collect(misconfig.data)) + @test count(isone, original_configs) == solve(GenericTensorNetwork(IndependentSet(g)), SizeMax())[].n + @test is_independent_set(g, original_configs) +end diff --git a/test/utils.jl b/test/utils.jl index 39c2bf6..a63e023 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -1,4 +1,5 @@ -using UnitDiskMapping: rotate90, reflectx, reflecty, reflectdiag, reflectoffdiag +using UnitDiskMapping: rotate90, reflectx, reflecty, reflectdiag, reflectoffdiag, physical_position +using UnitDiskMapping: Node, SquareGrid, TriangularGrid using Test @testset "symmetry operations" begin @@ -9,4 +10,12 @@ using Test @test reflecty(loc, center) == (0,3) @test reflectdiag(loc, center) == (1,0) @test reflectoffdiag(loc, center) == (3,4) +end + +@testset "physical position" begin + node = Node((2,3)) + @test physical_position(node, SquareGrid()) == (2,3) + @test physical_position(node, TriangularGrid()) == (2.5, 3 * (√3 / 2)) + @test physical_position(node, TriangularGrid(false)) == (2.5, 3 * (√3 / 2)) + @test physical_position(node, TriangularGrid(true)) == (2, 3 * (√3 / 2)) end \ No newline at end of file diff --git a/test/weighted.jl b/test/weighted.jl index b8331a9..cb591ef 100644 --- a/test/weighted.jl +++ b/test/weighted.jl @@ -34,7 +34,7 @@ end (3, 7, 8), (3, 5, 8), (5, 9, 8), (5, 5, 8), (1, 7, 5), (5, 8, 5), (1, 5, 5), (5, 5, 5)] tc = UnitDiskMapping.CopyLine(1, 5, 5, vstart, vstop, hstop) - locs = UnitDiskMapping.copyline_locations(UnitDiskMapping.WeightedNode, tc; padding=2) + locs = UnitDiskMapping.copyline_locations(UnitDiskMapping.WeightedNode, tc, 2, UnitDiskMapping.get_spacing(Weighted())) g = SimpleGraph(length(locs)) weights = getfield.(locs, :weight) for i=1:length(locs)-1 @@ -45,7 +45,7 @@ end end end gp = GenericTensorNetwork(IndependentSet(g, weights)) - @test solve(gp, SizeMax())[].n == UnitDiskMapping.mis_overhead_copyline(Weighted(), tc) + @test solve(gp, SizeMax())[].n == UnitDiskMapping.mis_overhead_copyline(Weighted(), tc, UnitDiskMapping.get_spacing(Weighted())) end end @@ -64,12 +64,13 @@ end # trace back configurations mgraph = SimpleGraph(ug3) weights = fill(0.5, nv(g)) - r = UnitDiskMapping.MappingResult(GridGraph(ug3), ug3.lines, ug3.padding, [tape..., tape2...], mis_overhead0+mis_overhead1+mis_overhead2) + r = UnitDiskMapping.MappingResult(GridGraph(Weighted(), ug3), ug3.lines, ug3.padding, [tape..., tape2...], mis_overhead0+mis_overhead1+mis_overhead2, ug.spacing) mapped_weights = UnitDiskMapping.map_weights(r, weights) - gp = GenericTensorNetwork(IndependentSet(mgraph, mapped_weights); optimizer=GreedyMethod(nrepeat=10)) + gp = GenericTensorNetwork(IndependentSet(mgraph, round.(Int, mapped_weights .* 10))) missize_map = solve(gp, CountingMax())[] - missize = solve(GenericTensorNetwork(IndependentSet(g, weights)), CountingMax())[] - @test mis_overhead0 + mis_overhead1 + mis_overhead2 + missize.n == missize_map.n + + missize = solve(GenericTensorNetwork(IndependentSet(g, round.(Int, weights .* 10))), CountingMax())[] + @test mis_overhead0 + mis_overhead1 + mis_overhead2 + missize.n / 10 ≈ missize_map.n / 10 @test missize.c == missize_map.c T = GenericTensorNetworks.sampler_type(nv(mgraph), 2) @@ -82,7 +83,7 @@ end center_locations = trace_centers(r) indices = CartesianIndex.(center_locations) sc = c[indices] - @test count(isone, sc) == missize.n * 2 + @test count(isone, sc) == (missize.n / 10) * 2 @test is_independent_set(g, sc) end end