From 5674fa3bab43fddb98ccdcac5434e54b34e8e87c Mon Sep 17 00:00:00 2001 From: mtfishman Date: Thu, 15 Dec 2022 22:20:55 -0500 Subject: [PATCH 1/6] Mincut, weights, flatten_networks --- examples/mincut.jl | 58 ++++++++++++++++++++++++++++ src/abstractindsnetwork.jl | 2 +- src/abstractitensornetwork.jl | 71 +++++++++++++++++++++++++++++++---- src/exports.jl | 12 +++--- src/imports.jl | 2 +- 5 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 examples/mincut.jl diff --git a/examples/mincut.jl b/examples/mincut.jl new file mode 100644 index 00000000..aceda576 --- /dev/null +++ b/examples/mincut.jl @@ -0,0 +1,58 @@ +using NamedGraphs +using ITensors +using ITensorNetworks +using ITensorUnicodePlots + +g = named_grid(5) +s = siteinds("S=1/2", g) + +ψ = ITensorNetwork(s; link_space=10) + +# ρ = flatten_networks(dag(ψ), ψ') + +# Or: + +ss = ∪(dag(s), s'; merge_data=union) +ρ = ITensorNetwork(ss; link_space=2) + +tn = ⊗(ρ', ρ, ψ) +tn_flattened = flatten_networks(ρ', ρ, ψ) +# tn = ρ' ⊗ ρ ⊗ ψ +@visualize tn + +@show center(tn) + +v = first(center(tn)) + +dijk_parents = dijkstra_parents(tn, v) +dijk_mst = dijkstra_mst(tn, v) +dijk_tree = dijkstra_tree(tn, v) + +bfs_tree_tn = bfs_tree(tn, v) + +@show eccentricity(tn, v) +@show radius(tn) +@show radius(tn) +@show diameter(tn) +@show periphery(tn) + +s = dijk_tree +t = bfs_tree_tn +@visualize s +@visualize t + +v1 = first(periphery(tn)) +nds = neighborhood_dists(tn, v1, nv(tn)) +d_and_i = findmax(vd -> vd[2], nds) +v2 = nds[d_and_i[2]][1] + +## p1, p2 = mincut_partitions(tn, (1, 1), (4, 1)) +## for v in p1 +## rem_vertex!(tn +## @show mincut_partitions(tn, (1, 1), (4, 1)) + +using SymRCM +# TODO: Implement for NamedGraph +# https://github.com/sbromberger/LightGraphs.jl/pull/1128 +p = symrcm(adjacency_matrix(tn_flattened)) +# TODO: Implement permute_vertices diff --git a/src/abstractindsnetwork.jl b/src/abstractindsnetwork.jl index bb0a46b9..63010c12 100644 --- a/src/abstractindsnetwork.jl +++ b/src/abstractindsnetwork.jl @@ -32,7 +32,7 @@ function union( tn2::AbstractIndsNetwork; kwargs..., ) - return IndsNetwork(union(data_graph(tn1), data_graph(tn2)); kwargs...) + return IndsNetwork(union(data_graph(tn1), data_graph(tn2); kwargs...)) end function rename_vertices( diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index d4460e7b..7fabede6 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -5,6 +5,18 @@ abstract type AbstractITensorNetwork{V} <: data_graph_type(::Type{<:AbstractITensorNetwork}) = not_implemented() data_graph(graph::AbstractITensorNetwork) = not_implemented() +# Graphs.jl overloads +function weights(graph::AbstractITensorNetwork) + V = vertextype(graph) + es = Tuple.(edges(graph)) + ws = Dictionary{Tuple{V,V},Float64}(es, undef) + for e in edges(graph) + w = log2(dim(commoninds(graph, e))) + ws[(src(e),dst(e))] = w + end + return ws +end + # Copy copy(tn::AbstractITensorNetwork) = not_implemented() @@ -21,8 +33,8 @@ end edge_data(graph::AbstractITensorNetwork, args...) = edge_data(data_graph(graph), args...) underlying_graph(tn::AbstractITensorNetwork) = underlying_graph(data_graph(tn)) -function vertex_to_parent_vertex(tn::AbstractITensorNetwork) - return vertex_to_parent_vertex(underlying_graph(tn)) +function vertex_to_parent_vertex(tn::AbstractITensorNetwork, vertex) + return vertex_to_parent_vertex(underlying_graph(tn), vertex) end # @@ -206,6 +218,7 @@ const map_inds_label_functions = [ :settags, :sim, :swaptags, + :dag, # :replaceind, # :replaceinds, # :swapind, @@ -226,19 +239,29 @@ dag(tn::AbstractITensorNetwork) = map_vertex_data(dag, tn) # TODO: should this make sure that internal indices # don't clash? -function ⊗(tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork; kwargs...) - return ⊔(tn1, tn2; kwargs...) +function ⊗( + tn1::AbstractITensorNetwork, + tn2::AbstractITensorNetwork, + tn_tail::AbstractITensorNetwork...; + kwargs..., +) + return ⊔(tn1, tn2, tn_tail...; kwargs...) end -function ⊗(tn1::Pair{<:Any,<:AbstractITensorNetwork}, tn2::Pair{<:Any,<:AbstractITensorNetwork}; kwargs...) - return ⊔(tn1, tn2; kwargs...) +function ⊗( + tn1::Pair{<:Any,<:AbstractITensorNetwork}, + tn2::Pair{<:Any,<:AbstractITensorNetwork}, + tn_tail::Pair{<:Any,<:AbstractITensorNetwork}...; + kwargs..., +) + return ⊔(tn1, tn2, tn_tail...; kwargs...) end # TODO: how to define this lazily? #norm(tn::AbstractITensorNetwork) = sqrt(inner(tn, tn)) function contract(tn::AbstractITensorNetwork; sequence=vertices(tn), kwargs...) - sequence_linear_index = deepmap(v -> vertex_to_parent_vertex(tn)[v], sequence) + sequence_linear_index = deepmap(v -> vertex_to_parent_vertex(tn, v), sequence) return contract(Vector{ITensor}(tn); sequence=sequence_linear_index, kwargs...) end @@ -432,6 +455,39 @@ function combine_linkinds(tn::AbstractITensorNetwork, combiners=linkinds_combine return combined_tn end +function flatten_networks( + tn1::AbstractITensorNetwork, + tn2::AbstractITensorNetwork; + map_bra_linkinds=sim, + flatten=true, + combine_linkinds=true, + kwargs..., +) + @assert issetequal(vertices(tn1), vertices(tn2)) + tn1 = map_bra_linkinds(tn1; sites=[]) + flattened_net = ⊗(tn1, tn2; kwargs...) + if flatten + for v in vertices(tn1) + flattened_net = contract(flattened_net, (v, 2) => (v, 1); merged_vertex=v) + end + end + if combine_linkinds + flattened_net = ITensorNetworks.combine_linkinds(flattened_net) + end + return flattened_net +end + +function flatten_networks( + tn1::AbstractITensorNetwork, + tn2::AbstractITensorNetwork, + tn3::AbstractITensorNetwork, + tn_tail::AbstractITensorNetwork...; + kwargs..., +) + return flatten_networks(flatten_networks(tn1, tn2; kwargs...), tn3, tn_tail...; kwargs...) +end + +# TODO: Use or replace with `flatten_networks` function inner_network( tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork; @@ -445,7 +501,6 @@ function inner_network( inner_net = ⊗(dag(tn1), tn2; kwargs...) if flatten for v in vertices(tn1) - # TODO: Combine the indices, optionally with `combine_linkinds` inner_net = contract(inner_net, (v, 2) => (v, 1); merged_vertex=v) end end diff --git a/src/exports.jl b/src/exports.jl index 2d5354ff..6d1da0f4 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -3,15 +3,15 @@ # export grid, + dst, edges, - vertices, - ne, - nv, src, - dst, neighbors, inneighbors, induced_subgraph, + mincut, + ne, + nv, outneighbors, has_edge, has_vertex, @@ -19,7 +19,8 @@ export grid, dfs_tree, edgetype, is_directed, - rem_vertex! + rem_vertex!, + vertices # # NamedGraphs @@ -61,6 +62,7 @@ export AbstractITensorNetwork, TreeTensorNetworkState, TTNS, data_graph, + flatten_networks, inner_network, norm_sqr_network, linkinds_combiners, diff --git a/src/imports.jl b/src/imports.jl index 0b8b9f1a..099bf86f 100644 --- a/src/imports.jl +++ b/src/imports.jl @@ -25,7 +25,7 @@ import .DataGraphs: edge_data, reverse_data_direction -import Graphs: SimpleGraph, is_directed +import Graphs: SimpleGraph, is_directed, weights import LinearAlgebra: svd, factorize, qr From 71dd61b65d4db7c32b627926e2c4970f4cdb5bb0 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Tue, 20 Dec 2022 18:14:29 -0500 Subject: [PATCH 2/6] Add examples of boundaries and steiner trees --- README.md | 178 +++++++++++++++++++-------------------- examples/boundary.jl | 20 +++++ examples/distances.jl | 17 ++++ examples/mincut.jl | 19 ++--- examples/steiner_tree.jl | 16 ++++ test/test_examples.jl | 4 + 6 files changed, 154 insertions(+), 100 deletions(-) create mode 100644 examples/boundary.jl create mode 100644 examples/distances.jl create mode 100644 examples/steiner_tree.jl diff --git a/README.md b/README.md index 40ae3b3c..7c1ac87a 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ with vertex data: 3 │ Index[(dim=2|id=683|"S=1/2,Site,n=3")] and edge data: -0-element Dictionaries.Dictionary{NamedGraphs.NamedEdge{Int64}, Vector{Index}} +0-element Dictionaries.Dictionary{NamedEdge{Int64}, Vector{Index}} julia> tn1 = ITensorNetwork(s; link_space=2) ITensorNetwork{Int64} with 3 vertices: @@ -198,78 +198,78 @@ with vertex data: 3 │ ((dim=2|id=683|"S=1/2,Site,n=3"), (dim=2|id=190|"2↔3")) julia> @visualize tn1; - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀tn11⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠈⠑⠢2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn12⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠉⠐⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠒⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn13⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀tn11⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠈⠑⠢2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn12⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠈⠑⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠒⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn13⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ julia> @visualize tn2; - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀tn21⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠈⠑⠢2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn22⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠉⠐⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠒⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn23⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀tn21⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠈⠑⠢2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn22⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠈⠑⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠒⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠢⢄⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀tn23⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ julia> Z = prime(tn1; sites=[]) ⊗ tn2; julia> @visualize Z; - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(1, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠊⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢2⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(2, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀Z(1, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠕⠀⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢2⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀(2)'⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⡠2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(3, 2)⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(2, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠁⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⡠⠊⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀(2)'⢀⡀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⡀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(3, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(1, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠊⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢2⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(2, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀Z(1, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢2⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀(2)'⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⡠2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(3, 2)⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(2, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠁⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⡠⠊⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀(2)'⢄⡀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⡀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z(3, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ julia> contraction_sequence(Z) 2-element Vector{Vector}: @@ -279,28 +279,28 @@ julia> contraction_sequence(Z) julia> Z̃ = contract(Z, (1, 1) => (2, 1)); julia> @visualize Z̃; - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z̃(2, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀(2)'⠤⠤⠔⠒⠒⠉⠉⠀⠀⢱⠀⠉⠈⠑⠒⠢⠤⢄⣀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⣀⣀⠤⠤⠔⠒⠊⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠒⠒⠤⠤⢄⣀⡀⠀⠀⠀⠀⠀ - ⠀Z̃(3, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z̃(1, 2)⠀⠀ - ⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⡠2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀2⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢀⠤⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z̃(2, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡠⠤⠒⠋⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠤2⠒⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⣀⡠⠤⠒⠊⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀Z̃(3, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z̃(2, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀(2)'⠤⠤⠔⠒⠒⠉⠉⠀⠀⢱⠀⠈⠉⠑⠒⠢⠤⢄⣀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⣀⣀⠤⠤⠔⠒⠊⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠒⠒⠤⠤⢄⣀⡀⠀⠀⠀⠀⠀ + ⠀Z̃(3, 1)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z̃(1, 2)⠀⠀ + ⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⡠2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀2⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢀⠤⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Z̃(2, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡠⠤⠒⠊⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠤2⠒⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⣀⡠⠤⠒⠊⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀Z̃(3, 2)⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ``` diff --git a/examples/boundary.jl b/examples/boundary.jl new file mode 100644 index 00000000..ef0dee0a --- /dev/null +++ b/examples/boundary.jl @@ -0,0 +1,20 @@ +using NamedGraphs +using ITensors +using ITensorNetworks +using ITensorUnicodePlots +using Metis + +tn = ITensorNetwork(named_grid((6, 3)); link_space=4) + +@visualize tn + +g = subgraphs(tn, nv(tn) ÷ 2) +sub_vs_1, sub_vs_2 = g[1], g[2] + +@show (1, 1) ∈ sub_vs_1 +@show (6, 3) ∈ sub_vs_2 + +@show boundary_edges(tn, sub_vs_1) +@show boundary_vertices(tn, sub_vs_1) +@show inner_boundary_vertices(tn, sub_vs_1) +@show outer_boundary_vertices(tn, sub_vs_1) diff --git a/examples/distances.jl b/examples/distances.jl new file mode 100644 index 00000000..6f678dea --- /dev/null +++ b/examples/distances.jl @@ -0,0 +1,17 @@ +using NamedGraphs +using ITensors +using ITensorNetworks +using ITensorUnicodePlots + +g = named_grid((3, 5)) +s = siteinds("S=1/2", g) +ψ = ITensorNetwork(s; link_space=4) +@visualize ψ +@show center(ψ) +@show periphery(ψ) +t = dijkstra_tree(ψ, only(center(ψ))) +@visualize t +@show a_star(ψ, (2, 1), (2, 5)) +@show mincut_partitions(ψ) +@show mincut_partitions(ψ, (1, 1), (3, 5)) +@show subgraphs(ψ, 2) diff --git a/examples/mincut.jl b/examples/mincut.jl index aceda576..298157cc 100644 --- a/examples/mincut.jl +++ b/examples/mincut.jl @@ -45,14 +45,11 @@ v1 = first(periphery(tn)) nds = neighborhood_dists(tn, v1, nv(tn)) d_and_i = findmax(vd -> vd[2], nds) v2 = nds[d_and_i[2]][1] - -## p1, p2 = mincut_partitions(tn, (1, 1), (4, 1)) -## for v in p1 -## rem_vertex!(tn -## @show mincut_partitions(tn, (1, 1), (4, 1)) - -using SymRCM -# TODO: Implement for NamedGraph -# https://github.com/sbromberger/LightGraphs.jl/pull/1128 -p = symrcm(adjacency_matrix(tn_flattened)) -# TODO: Implement permute_vertices +@show v1, v2 +p1, p2 = mincut_partitions(tn, v1, v2) +@show p1 +@show p2 + +display(adjacency_matrix(tn_flattened)) +tn_flattened_p = symrcm_permute(tn_flattened) +display(adjacency_matrix(tn_flattened_p)) diff --git a/examples/steiner_tree.jl b/examples/steiner_tree.jl new file mode 100644 index 00000000..934c008d --- /dev/null +++ b/examples/steiner_tree.jl @@ -0,0 +1,16 @@ +using NamedGraphs +using ITensors +using ITensorNetworks +using ITensorUnicodePlots + +tn = ITensorNetwork(named_grid((3, 5)); link_space=4) + +@visualize tn + +terminal_vertices = [(1, 2), (1, 4), (3, 4)] +st = steiner_tree(tn, terminal_vertices) + +@show has_edge(st, (1, 2) => (1, 3)) +@show has_edge(st, (1, 3) => (1, 4)) +@show has_edge(st, (1, 4) => (2, 4)) +@show has_edge(st, (2, 4) => (3, 4)) diff --git a/test/test_examples.jl b/test/test_examples.jl index 366b4df1..227620ba 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -8,6 +8,10 @@ using Test "examples.jl", "mps.jl", "peps.jl", + "distances.jl", + "mincut.jl", + "boundary.jl", + "steiner_tree.jl", joinpath("belief_propagation", "bpexample.jl"), joinpath("peps", "ising_tebd.jl"), joinpath("ttns", "comb_tree.jl"), From 03e197adf4292f545cb508b8244e74880cc5f4a7 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Thu, 22 Dec 2022 18:19:38 -0500 Subject: [PATCH 3/6] Improve graph partitioning interface --- README.md | 16 +-- examples/boundary.jl | 2 +- examples/distances.jl | 2 +- examples/group_partition.jl | 15 +++ examples/partition/kahypar_vs_metis.jl | 4 +- examples/partition/partitioning.jl | 2 +- src/ITensorNetworks.jl | 9 +- src/abstractindsnetwork.jl | 3 + src/abstractitensornetwork.jl | 3 + src/beliefpropagation.jl | 22 ++-- src/exports.jl | 2 +- src/imports.jl | 2 + src/partition.jl | 138 ++++++++++++++++++++++--- src/requires/kahypar.jl | 4 +- src/requires/metis.jl | 6 +- src/subgraphs.jl | 93 ----------------- test/test_examples.jl | 7 +- 17 files changed, 188 insertions(+), 142 deletions(-) create mode 100644 examples/group_partition.jl delete mode 100644 src/subgraphs.jl diff --git a/README.md b/README.md index 7c1ac87a..63766edb 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ and 3 edge(s): 3 => 4 with vertex data: -4-element Dictionaries.Dictionary{Int64, Any} +4-element Dictionary{Int64, Any} 1 │ ((dim=2|id=739|"1↔2"),) 2 │ ((dim=2|id=739|"1↔2"), (dim=2|id=920|"2↔3")) 3 │ ((dim=2|id=920|"2↔3"), (dim=2|id=761|"3↔4")) @@ -88,7 +88,7 @@ and 4 edge(s): (1, 2) => (2, 2) with vertex data: -4-element Dictionaries.Dictionary{Tuple{Int64, Int64}, Any} +4-element Dictionary{Tuple{Int64, Int64}, Any} (1, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=723|"1×1↔1×2")) (2, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=823|"2×1↔2×2")) (1, 2) │ ((dim=2|id=723|"1×1↔1×2"), (dim=2|id=712|"1×2↔2×2")) @@ -118,7 +118,7 @@ and 1 edge(s): (1, 1) => (1, 2) with vertex data: -2-element Dictionaries.Dictionary{Tuple{Int64, Int64}, Any} +2-element Dictionary{Tuple{Int64, Int64}, Any} (1, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=723|"1×1↔1×2")) (1, 2) │ ((dim=2|id=723|"1×1↔1×2"), (dim=2|id=712|"1×2↔2×2")) @@ -132,7 +132,7 @@ and 1 edge(s): (2, 1) => (2, 2) with vertex data: -2-element Dictionaries.Dictionary{Tuple{Int64, Int64}, Any} +2-element Dictionary{Tuple{Int64, Int64}, Any} (2, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=823|"2×1↔2×2")) (2, 2) │ ((dim=2|id=823|"2×1↔2×2"), (dim=2|id=712|"1×2↔2×2")) ``` @@ -155,13 +155,13 @@ and 2 edge(s): 2 => 3 with vertex data: -3-element Dictionaries.Dictionary{Int64, Vector{Index}} +3-element Dictionary{Int64, Vector{Index}} 1 │ Index[(dim=2|id=598|"S=1/2,Site,n=1")] 2 │ Index[(dim=2|id=457|"S=1/2,Site,n=2")] 3 │ Index[(dim=2|id=683|"S=1/2,Site,n=3")] and edge data: -0-element Dictionaries.Dictionary{NamedEdge{Int64}, Vector{Index}} +0-element Dictionary{NamedEdge{Int64}, Vector{Index}} julia> tn1 = ITensorNetwork(s; link_space=2) ITensorNetwork{Int64} with 3 vertices: @@ -175,7 +175,7 @@ and 2 edge(s): 2 => 3 with vertex data: -3-element Dictionaries.Dictionary{Int64, Any} +3-element Dictionary{Int64, Any} 1 │ ((dim=2|id=598|"S=1/2,Site,n=1"), (dim=2|id=123|"1↔2")) 2 │ ((dim=2|id=457|"S=1/2,Site,n=2"), (dim=2|id=123|"1↔2"), (dim=2|id=656|"2↔3… 3 │ ((dim=2|id=683|"S=1/2,Site,n=3"), (dim=2|id=656|"2↔3")) @@ -192,7 +192,7 @@ and 2 edge(s): 2 => 3 with vertex data: -3-element Dictionaries.Dictionary{Int64, Any} +3-element Dictionary{Int64, Any} 1 │ ((dim=2|id=598|"S=1/2,Site,n=1"), (dim=2|id=382|"1↔2")) 2 │ ((dim=2|id=457|"S=1/2,Site,n=2"), (dim=2|id=382|"1↔2"), (dim=2|id=190|"2↔3… 3 │ ((dim=2|id=683|"S=1/2,Site,n=3"), (dim=2|id=190|"2↔3")) diff --git a/examples/boundary.jl b/examples/boundary.jl index ef0dee0a..c864c696 100644 --- a/examples/boundary.jl +++ b/examples/boundary.jl @@ -8,7 +8,7 @@ tn = ITensorNetwork(named_grid((6, 3)); link_space=4) @visualize tn -g = subgraphs(tn, nv(tn) ÷ 2) +g = partition_vertices(tn; nvertices_per_partition=2) sub_vs_1, sub_vs_2 = g[1], g[2] @show (1, 1) ∈ sub_vs_1 diff --git a/examples/distances.jl b/examples/distances.jl index 6f678dea..82a87b24 100644 --- a/examples/distances.jl +++ b/examples/distances.jl @@ -14,4 +14,4 @@ t = dijkstra_tree(ψ, only(center(ψ))) @show a_star(ψ, (2, 1), (2, 5)) @show mincut_partitions(ψ) @show mincut_partitions(ψ, (1, 1), (3, 5)) -@show subgraphs(ψ, 2) +@show partition_vertices(ψ; npartitions=2) diff --git a/examples/group_partition.jl b/examples/group_partition.jl new file mode 100644 index 00000000..77bdf278 --- /dev/null +++ b/examples/group_partition.jl @@ -0,0 +1,15 @@ +using ITensors +using Graphs +using NamedGraphs +using ITensorNetworks +using SplitApplyCombine +using Metis + +s = siteinds("S=1/2", named_grid(8)) +tn = ITensorNetwork(s; link_space=2) +Z = prime(tn; sites=[]) ⊗ tn +vertex_groups = group(v -> v[1], vertices(Z)) +# Create two layers of partitioning +Z_p = partition(partition(Z, vertex_groups); nvertices_per_partition=2) +# Flatten the partitioned partitions +Z_verts = [reduce(vcat, (vertices(Z_p[vp][v]) for v in vertices(Z_p[vp]))) for vp in vertices(Z_p)] diff --git a/examples/partition/kahypar_vs_metis.jl b/examples/partition/kahypar_vs_metis.jl index 533159e7..05d08d8a 100644 --- a/examples/partition/kahypar_vs_metis.jl +++ b/examples/partition/kahypar_vs_metis.jl @@ -6,7 +6,7 @@ using ITensorNetworks g = grid((16,)) npartitions = 4 -kahypar_partitions = partition(g, npartitions; backend="KaHyPar") -metis_partitions = partition(g, npartitions; backend="Metis") +kahypar_partitions = partition(g; npartitions, backend="KaHyPar") +metis_partitions = partition(g; npartitions, backend="Metis") @show kahypar_partitions, length(unique(kahypar_partitions)) @show metis_partitions, length(unique(metis_partitions)) diff --git a/examples/partition/partitioning.jl b/examples/partition/partitioning.jl index ab8f327b..062178d7 100644 --- a/examples/partition/partitioning.jl +++ b/examples/partition/partitioning.jl @@ -24,7 +24,7 @@ neighbor_edges = [v => nv for nv in neighbors(ψ, v)] @show [e => linkinds(ψ, e) for e in neighbor_edges] npartitions = 4 -partitions = partition(ψ, npartitions) +partitions = partition(ψ; npartitions) @show partitions diff --git a/src/ITensorNetworks.jl b/src/ITensorNetworks.jl index 6dc31677..ff0e962f 100644 --- a/src/ITensorNetworks.jl +++ b/src/ITensorNetworks.jl @@ -21,11 +21,17 @@ using NamedGraphs: AbstractNamedGraph, parent_graph, vertex_to_parent_vertex, + parent_vertices_to_vertices, not_implemented -using DataGraphs: vertex_data_type +using DataGraphs: edge_data_type, vertex_data_type include("imports.jl") +# TODO: Move to `DataGraphs.jl` +edge_data_type(::AbstractNamedGraph) = Any +isassigned(::AbstractNamedGraph, ::Any) = false +iterate(::AbstractDataGraph) = error("Iterating data graphs is not yet defined. We may define it in the future as iterating through the vertex and edge data.") + include("utils.jl") include("visualize.jl") include("graphs.jl") @@ -47,7 +53,6 @@ include("itensornetwork.jl") include("specialitensornetworks.jl") include("renameitensornetwork.jl") include("boundarymps.jl") -include("subgraphs.jl") include("beliefpropagation.jl") include(joinpath("treetensornetwork", "treetensornetwork.jl")) diff --git a/src/abstractindsnetwork.jl b/src/abstractindsnetwork.jl index 63010c12..f416ed13 100644 --- a/src/abstractindsnetwork.jl +++ b/src/abstractindsnetwork.jl @@ -11,6 +11,9 @@ is_directed(::Type{<:AbstractIndsNetwork}) = false vertex_data(graph::AbstractIndsNetwork, args...) = vertex_data(data_graph(graph), args...) edge_data(graph::AbstractIndsNetwork, args...) = edge_data(data_graph(graph), args...) +# TODO: Define a generic fallback for `AbstractDataGraph`? +edge_data_type(::Type{<:AbstractIndsNetwork{V,I}}) where {V,I} = Vector{I} + # # Index access # diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index 7fabede6..45ec8567 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -5,6 +5,9 @@ abstract type AbstractITensorNetwork{V} <: data_graph_type(::Type{<:AbstractITensorNetwork}) = not_implemented() data_graph(graph::AbstractITensorNetwork) = not_implemented() +# TODO: Define a generic fallback for `AbstractDataGraph`? +edge_data_type(::Type{<:AbstractITensorNetwork}) = ITensor + # Graphs.jl overloads function weights(graph::AbstractITensorNetwork) V = vertextype(graph) diff --git a/src/beliefpropagation.jl b/src/beliefpropagation.jl index 4f2976e7..e153b71d 100644 --- a/src/beliefpropagation.jl +++ b/src/beliefpropagation.jl @@ -1,5 +1,5 @@ -function construct_initial_mts(tn::ITensorNetwork, nvertices_per_subgraph::Integer; subgraph_kwargs=(;), kwargs...) - return construct_initial_mts(tn, subgraphs(tn, nvertices_per_subgraph; subgraph_kwargs...); kwargs...) +function construct_initial_mts(tn::ITensorNetwork, nvertices_per_partition::Integer; partition_kwargs=(;), kwargs...) + return construct_initial_mts(tn, partition(tn; nvertices_per_partition, partition_kwargs...); kwargs...) end function construct_initial_mts(tn::ITensorNetwork, subgraphs::DataGraph; init) @@ -13,7 +13,7 @@ function construct_initial_mts(tn::ITensorNetwork, subgraphs::DataGraph; init) tns_to_contract = ITensor[] for subgraph_neighbor in neighbors(subgraphs, subgraph) edge_inds = Index[] - for vertex in subgraphs[subgraph] + for vertex in vertices(subgraphs[subgraph]) psiv = tn[vertex] for e in [edgetype(tn)(vertex => neighbor) for neighbor in neighbors(tn, vertex)] if (find_subgraph(dst(e), subgraphs) == subgraph_neighbor) @@ -42,6 +42,10 @@ function update_mt(tn::ITensorNetwork, subgraph_vertices::Vector, mts::Vector{IT return normalize!(new_mt) end +function update_mt(tn::ITensorNetwork, subgraph::ITensorNetwork, mts::Vector{ITensor}; kwargs...) + return update_mt(tn, vertices(subgraph), mts; kwargs...) +end + """ Do an update of all message tensors for a given flat ITensornetwork and its partition into sub graphs """ @@ -95,7 +99,7 @@ function get_single_site_expec( push!(num_tensors_to_contract, subgraphs[k => subgraph]) push!(denom_tensors_to_contract, subgraphs[k => subgraph]) end - for vertex in subgraphs[subgraph] + for vertex in vertices(subgraphs[subgraph]) push!(num_tensors_to_contract, tnO[vertex]) push!(denom_tensors_to_contract, tn[vertex]) end @@ -127,7 +131,7 @@ function get_two_site_expec( push!(denom_tensors_to_contract, subgraphs[k => subgraph1]) end - for vertex in subgraphs[subgraph1] + for vertex in vertices(subgraphs[subgraph1]) if (vertex != v1 && vertex != v2) push!(num_tensors_to_contract, deepcopy(psi[vertex])) else @@ -152,7 +156,7 @@ function get_two_site_expec( end end - for vertex in subgraphs[subgraph1] + for vertex in vertices(subgraphs[subgraph1]) if (vertex != v1) push!(num_tensors_to_contract, deepcopy(psi[vertex])) else @@ -162,7 +166,7 @@ function get_two_site_expec( push!(denom_tensors_to_contract, deepcopy(psi[vertex])) end - for vertex in subgraphs[subgraph2] + for vertex in vertices(subgraphs[subgraph2]) if (vertex != v2) push!(num_tensors_to_contract, deepcopy(psi[vertex])) else @@ -266,7 +270,7 @@ function two_site_rdm_bp( end end - for vertex in subgraphs[subgraph1] + for vertex in vertices(subgraphs[subgraph1]) if (vertex != v1 && vertex != v2) push!(tensors_to_contract, deepcopy(tn[vertex])) end @@ -280,7 +284,7 @@ function two_site_rdm_bp( end end - for vertex in subgraphs[subgraph2] + for vertex in vertices(subgraphs[subgraph2]) if (vertex != v2) push!(tensors_to_contract, deepcopy(tn[vertex])) end diff --git a/src/exports.jl b/src/exports.jl index 6d1da0f4..6d2486c8 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -86,4 +86,4 @@ export AbstractITensorNetwork, export hypercubic_lattice_graph, square_lattice_graph, chain_lattice_graph # partition.jl -export partition +export partition, partition_vertices diff --git a/src/imports.jl b/src/imports.jl index 099bf86f..a75837cd 100644 --- a/src/imports.jl +++ b/src/imports.jl @@ -9,6 +9,7 @@ import Base: setindex!, show, isassigned, + iterate, union import NamedGraphs: @@ -23,6 +24,7 @@ import .DataGraphs: underlying_graph_type, vertex_data, edge_data, + edge_data_type, reverse_data_direction import Graphs: SimpleGraph, is_directed, weights diff --git a/src/partition.jl b/src/partition.jl index 15f7354c..d8ed5a74 100644 --- a/src/partition.jl +++ b/src/partition.jl @@ -29,22 +29,6 @@ function set_partitioning_backend!(backend::Union{Missing,Backend,String}) return nothing end -function partition(g::Graph, npartitions::Integer; backend=current_partitioning_backend(), kwargs...) - return partition(Backend(backend), g, npartitions; kwargs...) -end - -function partition(g::NamedGraph, npartitions::Integer; kwargs...) - partitions = partition(parent_graph(g), npartitions; kwargs...) - #[inv(vertex_to_parent_vertex(g))[v] for v in partitions] - # TODO: output the reverse of this dictionary (a Vector of Vector - # of the vertices in each partition). - return Dictionary(vertices(g), partitions) -end - -function partition(g::AbstractDataGraph, npartitions::Integer; kwargs...) - return partition(underlying_graph(g), npartitions; kwargs...) -end - # KaHyPar configuration options # # configurations = readdir(joinpath(pkgdir(KaHyPar), "src", "config")) @@ -64,3 +48,125 @@ const kahypar_configurations = Dict([ # Metis configuration options const metis_algs = Dict(["kway" => :KWAY, "recursive" => :RECURSIVE]) + +function _npartitions(g::AbstractGraph, npartitions::Integer, nvertices_per_partition::Nothing) + return npartitions +end + +function _npartitions(g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Integer) + return nv(g) ÷ nvertices_per_partition +end + +function _npartitions(g::AbstractGraph, npartitions::Int, nvertices_per_partition::Int) + return error("Can't specify both `npartitions` and `nvertices_per_partition`") +end + +function _npartitions(g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Nothing) + return error("Must specify either `npartitions` or `nvertices_per_partition`") +end + +function partition_vertices(g::Graph; npartitions=nothing, nvertices_per_partition=nothing, backend=current_partitioning_backend(), kwargs...) + return partition_vertices(Backend(backend), g, _npartitions(g, npartitions, nvertices_per_partition); kwargs...) +end + +function partition_vertices(g::NamedGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs...) + vertex_partitions = partition_vertices(parent_graph(g); npartitions, nvertices_per_partition, kwargs...) + #[inv(vertex_to_parent_vertex(g))[v] for v in partitions] + # TODO: output the reverse of this dictionary (a Vector of Vector + # of the vertices in each partition). + # return Dictionary(vertices(g), partitions) + return [parent_vertices_to_vertices(g, vertex_partition) for vertex_partition in vertex_partitions] +end + +function partition_vertices(g::AbstractDataGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs...) + return partition_vertices(underlying_graph(g); npartitions, nvertices_per_partition, kwargs...) +end + +""" +Find all vertices `v` such that `f(graph[v]) == true` +""" +function findall_on_vertices(f::Function, graph::AbstractDataGraph) + return findall(f, vertex_data(graph)) +end + +""" +Find the vertex `v` such that `f(graph[v]) == true` +""" +function findfirst_on_vertices(f::Function, graph::AbstractDataGraph) + return findfirst(f, vertex_data(graph)) +end + +""" +Find all edges `e` such that `f(graph[e]) == true` +""" +function findall_on_edges(f::Function, graph::AbstractDataGraph) + return findall(f, edge_data(graph)) +end + +""" +Find the edge `e` such that `f(graph[e]) == true` +""" +function findfirst_on_edges(f::Function, graph::AbstractDataGraph) + return findfirst(f, edge_data(graph)) +end + +""" +Find the subgraph which contains the specified vertex. + +TODO: Rename something more general, like: + +findfirst_in_vertex_data(item, graph::AbstractDataGraph) +""" +function find_subgraph(vertex, subgraphs::DataGraph) + return findfirst_on_vertices(subgraph -> vertex ∈ vertices(subgraph), subgraphs) +end + +""" + subgraphs(g::AbstractGraph, subgraph_vertices) + +Return a collection of subgraphs of `g` defined by the subgraph +vertices `subgraph_vertices`. +""" +function subgraphs(g::AbstractGraph, subgraph_vertices) + return map(vs -> subgraph(g, vs), subgraph_vertices) +end + +""" + partition(g::AbstractGraph, subgraph_vertices::Vector) + +Given a graph `g` and groups of vertices defining subgraphs of that +graph `subgraph_vertices`, return a DataGraph storing the subgraphs +on the vertices of the graph and with edges denoting +which subgraphs of the original graph have edges, along with +edge data storing the edges original edges that were connecting the subgraphs +and the edge data if relavent. +""" +function partition(g::AbstractGraph, subgraph_vertices) + dg_subgraphs = DataGraph(NamedGraph(eachindex(subgraph_vertices)), subgraphs(g, Dictionary(subgraph_vertices))) + for e in edges(g) + s1 = findfirst_on_vertices(subgraph -> src(e) ∈ vertices(subgraph), dg_subgraphs) + s2 = findfirst_on_vertices(subgraph -> dst(e) ∈ vertices(subgraph), dg_subgraphs) + if (!has_edge(dg_subgraphs, s1, s2) && s1 ≠ s2) + add_edge!(dg_subgraphs, s1, s2) + dg_subgraphs[s1 => s2] = Dictionary([:edges, :edge_data], [Vector{edgetype(g)}(), Dictionary{edgetype(g),edge_data_type(g)}()]) + end + if has_edge(dg_subgraphs, s1, s2) + push!(dg_subgraphs[s1 => s2][:edges], e) + if isassigned(g, e) + set!(dg_subgraphs[s1 => s2][:edge_data], e, g[e]) + end + end + end + return dg_subgraphs +end + +""" + partition(g::AbstractGraph; npartitions::Integer, kwargs...) + +Given a graph g, partition the graph into 'npartitions' partitions. Try to keep all subgraphs the same size and minimise edges cut between them +Returns a datagraph where each vertex contains the list of vertices involved in that subgraph. The edges state which subgraphs are connected. +A graph partitioning backend such as Metis or KaHyPar needs to be installed for this function to work. +""" +function partition(g::AbstractGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs...) + return partition(g, partition_vertices(g; npartitions, nvertices_per_partition, kwargs...)) +end diff --git a/src/requires/kahypar.jl b/src/requires/kahypar.jl index 2bfbe606..4d120808 100644 --- a/src/requires/kahypar.jl +++ b/src/requires/kahypar.jl @@ -13,7 +13,7 @@ KaHyPar.HyperGraph(g::Graph) = incidence_matrix(g) """ function partition( ::Backend"KaHyPar", - g::Graph, + g::SimpleGraph, npartitions::Integer; objective="edge_cut", alg="kway", @@ -29,5 +29,5 @@ function partition( ) end partitions = @suppress KaHyPar.partition(g, npartitions; configuration, kwargs...) - return partitions .+ 1 + return groupfind(partitions .+ 1) end diff --git a/src/requires/metis.jl b/src/requires/metis.jl index a1c949ec..e9a5881e 100644 --- a/src/requires/metis.jl +++ b/src/requires/metis.jl @@ -1,17 +1,17 @@ set_partitioning_backend!(Backend"Metis"()) """ - partition(::Backend"Metis", g::AbstractGraph, npartitions::Integer; alg="recursive") + partition_vertices(::Backend"Metis", g::AbstractGraph, npartitions::Integer; alg="recursive") Partition the graph `G` in `n` parts. The partition algorithm is defined by the `alg` keyword: - :KWAY: multilevel k-way partitioning - :RECURSIVE: multilevel recursive bisection """ -function partition(::Backend"Metis", g::SimpleGraph, npartitions::Integer; alg="recursive", kwargs...) +function partition_vertices(::Backend"Metis", g::SimpleGraph, npartitions::Integer; alg="recursive", kwargs...) metis_alg = metis_algs[alg] partitions = Metis.partition(g, npartitions; alg=metis_alg, kwargs...) - return Int.(partitions) + return groupfind(Int.(partitions)) end ## #= diff --git a/src/subgraphs.jl b/src/subgraphs.jl deleted file mode 100644 index 8d4829e7..00000000 --- a/src/subgraphs.jl +++ /dev/null @@ -1,93 +0,0 @@ -function findall_on_vertices(f::Function, graph::AbstractDataGraph) - return findall(f, vertex_data(graph)) -end - -function findfirst_on_vertices(f::Function, graph::AbstractDataGraph) - return findfirst(f, vertex_data(graph)) -end - -function findall_on_edges(f::Function, graph::AbstractDataGraph) - return findall(f, edge_data(graph)) -end - -function findfirst_on_edges(f::Function, graph::AbstractDataGraph) - return findfirst(f, edge_data(graph)) -end - -""" -Find the subgraph which contains the specified vertex. - -TODO: Rename something more general, like: - -findfirst_in_vertex_data(item, graph::AbstractDataGraph) -""" -function find_subgraph(vertex, subgraphs::DataGraph) - return findfirst_on_vertices(subgraph_vertices -> vertex ∈ subgraph_vertices, subgraphs) -end - -""" -Given a graph g, form 'nv(g) ÷ nvertices_per_partition' subgraphs. Try to keep all subgraphs the same size and minimise edges cut between them -Returns a datagraph where each vertex contains the list of vertices involved in that subgraph. The edges state which subgraphs are connected -KaHyPar needs to be installed for this function to work -""" -function subgraphs(g::AbstractGraph, nvertices_per_partition::Integer; kwargs...) - nvertices_per_partition = min(nv(g), nvertices_per_partition) - npartitions = nv(g) ÷ nvertices_per_partition - vertex_to_partition = partition(g, npartitions; kwargs...) - partition_vertices = groupfind(vertex_to_partition) - if length(partition_vertices) ≠ npartitions - @warn "Requested $nvertices_per_partition vertices per partition for a graph with $(nv(g)) vertices. Attempting to partition the graph into $npartitions partitions but instead it was partitioned into $(length(partition_vertices)) partitions. Partitions are $partition_vertices." - end - if !issetequal(keys(partition_vertices), 1:length(partition_vertices)) - @warn "Vertex partioning is $partition_vertices, which may not be what you were hoping for. If not, try a different graph partitioning algorithm or backend." - end - dg_subgraphs = DataGraph(NamedGraph(keys(partition_vertices)), partition_vertices) - for e in edges(g) - s1 = findfirst_on_vertices(subgraph_vertices -> src(e) ∈ subgraph_vertices, dg_subgraphs) - s2 = findfirst_on_vertices(subgraph_vertices -> dst(e) ∈ subgraph_vertices, dg_subgraphs) - if (!has_edge(dg_subgraphs, s1, s2) && s1 != s2) - add_edge!(dg_subgraphs, s1, s2) - end - end - return dg_subgraphs -end - -## """ -## Given a graph g on a d-dimensional grid of size Ls[1] x Ls[2] x ..., form subgraphs of size ls[1] x ls[2] in a regular fashion -## Return the subgraphs (1... npartitions) with their contained vertices. Also return a dictionary of the subgraphs connected to each sgraph -## """ -## function subgraphs_grid(g::NamedGraph, Ls::Vector{Int64}, ls::Vector{Int64}) -## lengths = Ls ./ ls -## ps = Dict{Tuple,Int64}() -## for v in vertices(g) -## pos = [] -## count = 1 -## for i in 1:length(v) -## push!(pos, ceil(Int64, v[i] / ls[i])) -## if (pos[i] == 1) -## p = 1 -## else -## p = prod(lengths[1:(i - 1)]) -## end -## count = Int(count + (pos[i] - 1) * p) -## end -## ps[v] = count -## end -## -## nsubgraphs = Int(prod(lengths)) -## -## dg_subgraphs = DataGraph{Vector{Tuple},Any}(NamedGraph([(i,) for i in 1:nsubgraphs])) -## for s in 1:nsubgraphs -## dg_subgraphs[(s,)] = [v for v in vertices(g) if ps[v] == s] -## end -## -## for e in edges(g) -## v1, v2 = src(e), dst(e) -## s1, s2 = find_subgraph(v1, dg_subgraphs), find_subgraph(v2, dg_subgraphs) -## if (!has_edge(dg_subgraphs, s1, s2) && s1 != s2) -## add_edge!(dg_subgraphs, s1, s2) -## end -## end -## -## return dg_subgraphs -## end diff --git a/test/test_examples.jl b/test/test_examples.jl index 227620ba..07402940 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -5,12 +5,13 @@ using Test @testset "Test examples" begin example_files = [ "README.jl", + "boundary.jl", + "distances.jl", "examples.jl", + "group_partition.jl", + "mincut.jl", "mps.jl", "peps.jl", - "distances.jl", - "mincut.jl", - "boundary.jl", "steiner_tree.jl", joinpath("belief_propagation", "bpexample.jl"), joinpath("peps", "ising_tebd.jl"), From 4c76cd8c1793e9ddda9436b4411f5f8a70f68493 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Thu, 22 Dec 2022 18:34:02 -0500 Subject: [PATCH 4/6] Fix partition for AbstractSimpleGraphs --- examples/partition/kahypar_vs_metis.jl | 16 ++++++++++++---- src/ITensorNetworks.jl | 1 + src/partition.jl | 4 ++++ src/requires/kahypar.jl | 4 ++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/partition/kahypar_vs_metis.jl b/examples/partition/kahypar_vs_metis.jl index 05d08d8a..2c239464 100644 --- a/examples/partition/kahypar_vs_metis.jl +++ b/examples/partition/kahypar_vs_metis.jl @@ -6,7 +6,15 @@ using ITensorNetworks g = grid((16,)) npartitions = 4 -kahypar_partitions = partition(g; npartitions, backend="KaHyPar") -metis_partitions = partition(g; npartitions, backend="Metis") -@show kahypar_partitions, length(unique(kahypar_partitions)) -@show metis_partitions, length(unique(metis_partitions)) +kahypar_partitions = partition_vertices(g; npartitions, backend="KaHyPar") +metis_partitions = partition_vertices(g; npartitions, backend="Metis") +@show kahypar_partitions, length(kahypar_partitions) +@show metis_partitions, length(metis_partitions) + +g_parts = partition(g; npartitions) +@show nv(g_parts) == 4 +@show nv(g_parts[1]) == 4 +@show nv(g_parts[2]) == 4 +@show nv(g_parts[3]) == 4 +@show nv(g_parts[4]) == 4 +@show issetequal(metis_partitions[2], vertices(g_parts[2])) diff --git a/src/ITensorNetworks.jl b/src/ITensorNetworks.jl index ff0e962f..d2716b65 100644 --- a/src/ITensorNetworks.jl +++ b/src/ITensorNetworks.jl @@ -17,6 +17,7 @@ using Suppressor # TODO: export from ITensors using ITensors: commontags, @Algorithm_str, Algorithm using Graphs: AbstractEdge, AbstractGraph, Graph, add_edge! +using Graphs.SimpleGraphs # AbstractSimpleGraph using NamedGraphs: AbstractNamedGraph, parent_graph, diff --git a/src/partition.jl b/src/partition.jl index d8ed5a74..c93d6239 100644 --- a/src/partition.jl +++ b/src/partition.jl @@ -131,6 +131,10 @@ function subgraphs(g::AbstractGraph, subgraph_vertices) return map(vs -> subgraph(g, vs), subgraph_vertices) end +function partition(g::AbstractSimpleGraph, subgraph_vertices) + return partition(NamedGraph(g), subgraph_vertices) +end + """ partition(g::AbstractGraph, subgraph_vertices::Vector) diff --git a/src/requires/kahypar.jl b/src/requires/kahypar.jl index 4d120808..1bf9cb58 100644 --- a/src/requires/kahypar.jl +++ b/src/requires/kahypar.jl @@ -1,7 +1,7 @@ set_partitioning_backend!(Backend"KaHyPar"()) # https://github.com/kahypar/KaHyPar.jl/issues/20 -KaHyPar.HyperGraph(g::Graph) = incidence_matrix(g) +KaHyPar.HyperGraph(g::SimpleGraph) = incidence_matrix(g) """ partition(::Backend"KaHyPar", g::Graph, npartiations::Integer; objective="edge_cut", alg="kway", kwargs...) @@ -11,7 +11,7 @@ KaHyPar.HyperGraph(g::Graph) = incidence_matrix(g) - :connectivity => "km1_kKaHyPar_sea20.ini" - imbalance::Number=0.03 """ -function partition( +function partition_vertices( ::Backend"KaHyPar", g::SimpleGraph, npartitions::Integer; From 4e87b83af1d9c1f1be089cba75160586ea2d1a03 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Fri, 23 Dec 2022 11:12:54 -0500 Subject: [PATCH 5/6] Improve partitioning interface and format --- README.md | 16 +- docs/make.jl | 4 +- examples/README.jl | 6 +- examples/belief_propagation/bpexample.jl | 5 +- examples/boundary.jl | 2 +- examples/distances.jl | 2 +- examples/group_partition.jl | 4 +- examples/partition/kahypar_vs_metis.jl | 28 +++- examples/ttns/ttn_type.jl | 4 +- src/ITensorNetworks.jl | 6 +- src/abstractindsnetwork.jl | 14 +- src/abstractitensornetwork.jl | 20 +-- src/apply.jl | 7 +- src/beliefpropagation.jl | 101 +++++++----- src/exports.jl | 4 +- src/imports.jl | 6 +- src/indsnetwork.jl | 122 +++++++++----- src/itensornetwork.jl | 19 ++- src/partition.jl | 177 +++++++++++++++++---- src/renameitensornetwork.jl | 4 +- src/requires/kahypar.jl | 4 +- src/requires/metis.jl | 6 +- src/treetensornetwork/treetensornetwork.jl | 12 +- test/test_contraction_sequence.jl | 6 +- test/test_examples.jl | 3 +- 25 files changed, 397 insertions(+), 185 deletions(-) diff --git a/README.md b/README.md index 63766edb..9662d206 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ and 3 edge(s): 3 => 4 with vertex data: -4-element Dictionary{Int64, Any} +4-element Dictionaries.Dictionary{Int64, Any} 1 │ ((dim=2|id=739|"1↔2"),) 2 │ ((dim=2|id=739|"1↔2"), (dim=2|id=920|"2↔3")) 3 │ ((dim=2|id=920|"2↔3"), (dim=2|id=761|"3↔4")) @@ -88,7 +88,7 @@ and 4 edge(s): (1, 2) => (2, 2) with vertex data: -4-element Dictionary{Tuple{Int64, Int64}, Any} +4-element Dictionaries.Dictionary{Tuple{Int64, Int64}, Any} (1, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=723|"1×1↔1×2")) (2, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=823|"2×1↔2×2")) (1, 2) │ ((dim=2|id=723|"1×1↔1×2"), (dim=2|id=712|"1×2↔2×2")) @@ -118,7 +118,7 @@ and 1 edge(s): (1, 1) => (1, 2) with vertex data: -2-element Dictionary{Tuple{Int64, Int64}, Any} +2-element Dictionaries.Dictionary{Tuple{Int64, Int64}, Any} (1, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=723|"1×1↔1×2")) (1, 2) │ ((dim=2|id=723|"1×1↔1×2"), (dim=2|id=712|"1×2↔2×2")) @@ -132,7 +132,7 @@ and 1 edge(s): (2, 1) => (2, 2) with vertex data: -2-element Dictionary{Tuple{Int64, Int64}, Any} +2-element Dictionaries.Dictionary{Tuple{Int64, Int64}, Any} (2, 1) │ ((dim=2|id=74|"1×1↔2×1"), (dim=2|id=823|"2×1↔2×2")) (2, 2) │ ((dim=2|id=823|"2×1↔2×2"), (dim=2|id=712|"1×2↔2×2")) ``` @@ -155,13 +155,13 @@ and 2 edge(s): 2 => 3 with vertex data: -3-element Dictionary{Int64, Vector{Index}} +3-element Dictionaries.Dictionary{Int64, Vector{Index}} 1 │ Index[(dim=2|id=598|"S=1/2,Site,n=1")] 2 │ Index[(dim=2|id=457|"S=1/2,Site,n=2")] 3 │ Index[(dim=2|id=683|"S=1/2,Site,n=3")] and edge data: -0-element Dictionary{NamedEdge{Int64}, Vector{Index}} +0-element Dictionaries.Dictionary{NamedGraphs.NamedEdge{Int64}, Vector{Index}} julia> tn1 = ITensorNetwork(s; link_space=2) ITensorNetwork{Int64} with 3 vertices: @@ -175,7 +175,7 @@ and 2 edge(s): 2 => 3 with vertex data: -3-element Dictionary{Int64, Any} +3-element Dictionaries.Dictionary{Int64, Any} 1 │ ((dim=2|id=598|"S=1/2,Site,n=1"), (dim=2|id=123|"1↔2")) 2 │ ((dim=2|id=457|"S=1/2,Site,n=2"), (dim=2|id=123|"1↔2"), (dim=2|id=656|"2↔3… 3 │ ((dim=2|id=683|"S=1/2,Site,n=3"), (dim=2|id=656|"2↔3")) @@ -192,7 +192,7 @@ and 2 edge(s): 2 => 3 with vertex data: -3-element Dictionary{Int64, Any} +3-element Dictionaries.Dictionary{Int64, Any} 1 │ ((dim=2|id=598|"S=1/2,Site,n=1"), (dim=2|id=382|"1↔2")) 2 │ ((dim=2|id=457|"S=1/2,Site,n=2"), (dim=2|id=382|"1↔2"), (dim=2|id=190|"2↔3… 3 │ ((dim=2|id=683|"S=1/2,Site,n=3"), (dim=2|id=190|"2↔3")) diff --git a/docs/make.jl b/docs/make.jl index 29d98e9a..e95e397f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,7 +1,9 @@ using ITensorNetworks using Documenter -DocMeta.setdocmeta!(ITensorNetworks, :DocTestSetup, :(using ITensorNetworks); recursive=true) +DocMeta.setdocmeta!( + ITensorNetworks, :DocTestSetup, :(using ITensorNetworks); recursive=true +) makedocs(; modules=[ITensorNetworks], diff --git a/examples/README.jl b/examples/README.jl index 8bc7d4d5..755ca3ab 100644 --- a/examples/README.jl +++ b/examples/README.jl @@ -61,4 +61,8 @@ Z̃ = contract(Z, (1, 1) => (2, 1)); #+ eval=false using ITensorNetworks, Weave -weave(joinpath(pkgdir(ITensorNetworks), "examples", "README.jl"); doctype="github", out_path=pkgdir(ITensorNetworks)) +weave( + joinpath(pkgdir(ITensorNetworks), "examples", "README.jl"); + doctype="github", + out_path=pkgdir(ITensorNetworks), +) diff --git a/examples/belief_propagation/bpexample.jl b/examples/belief_propagation/bpexample.jl index eab46d7d..f8cb65d1 100644 --- a/examples/belief_propagation/bpexample.jl +++ b/examples/belief_propagation/bpexample.jl @@ -3,10 +3,7 @@ using ITensors using Metis using ITensorNetworks -using ITensorNetworks: - construct_initial_mts, - update_all_mts, - get_single_site_expec +using ITensorNetworks: construct_initial_mts, update_all_mts, get_single_site_expec n = 4 dims = (n, n) diff --git a/examples/boundary.jl b/examples/boundary.jl index c864c696..c10aecbf 100644 --- a/examples/boundary.jl +++ b/examples/boundary.jl @@ -8,7 +8,7 @@ tn = ITensorNetwork(named_grid((6, 3)); link_space=4) @visualize tn -g = partition_vertices(tn; nvertices_per_partition=2) +g = subgraph_vertices(tn; nvertices_per_partition=2) sub_vs_1, sub_vs_2 = g[1], g[2] @show (1, 1) ∈ sub_vs_1 diff --git a/examples/distances.jl b/examples/distances.jl index 82a87b24..38df7296 100644 --- a/examples/distances.jl +++ b/examples/distances.jl @@ -14,4 +14,4 @@ t = dijkstra_tree(ψ, only(center(ψ))) @show a_star(ψ, (2, 1), (2, 5)) @show mincut_partitions(ψ) @show mincut_partitions(ψ, (1, 1), (3, 5)) -@show partition_vertices(ψ; npartitions=2) +@show subgraph_vertices(ψ; npartitions=2) diff --git a/examples/group_partition.jl b/examples/group_partition.jl index 77bdf278..1a3c12ae 100644 --- a/examples/group_partition.jl +++ b/examples/group_partition.jl @@ -12,4 +12,6 @@ vertex_groups = group(v -> v[1], vertices(Z)) # Create two layers of partitioning Z_p = partition(partition(Z, vertex_groups); nvertices_per_partition=2) # Flatten the partitioned partitions -Z_verts = [reduce(vcat, (vertices(Z_p[vp][v]) for v in vertices(Z_p[vp]))) for vp in vertices(Z_p)] +Z_verts = [ + reduce(vcat, (vertices(Z_p[vp][v]) for v in vertices(Z_p[vp]))) for vp in vertices(Z_p) +] diff --git a/examples/partition/kahypar_vs_metis.jl b/examples/partition/kahypar_vs_metis.jl index 2c239464..36301a5b 100644 --- a/examples/partition/kahypar_vs_metis.jl +++ b/examples/partition/kahypar_vs_metis.jl @@ -6,8 +6,8 @@ using ITensorNetworks g = grid((16,)) npartitions = 4 -kahypar_partitions = partition_vertices(g; npartitions, backend="KaHyPar") -metis_partitions = partition_vertices(g; npartitions, backend="Metis") +kahypar_partitions = subgraph_vertices(g; npartitions, backend="KaHyPar") +metis_partitions = subgraph_vertices(g; npartitions, backend="Metis") @show kahypar_partitions, length(kahypar_partitions) @show metis_partitions, length(metis_partitions) @@ -18,3 +18,27 @@ g_parts = partition(g; npartitions) @show nv(g_parts[3]) == 4 @show nv(g_parts[4]) == 4 @show issetequal(metis_partitions[2], vertices(g_parts[2])) + +using ITensorNetworks +tn = ITensorNetwork(named_grid((4, 2)); link_space=3); + +# subgraph_vertices +tn_sv = subgraph_vertices(tn; npartitions=2) # Same as `partition_vertices(tn; nvertices_per_partition=4)` + +# partition_vertices +tn_pv = partition_vertices(tn; npartitions=2); +typeof(tn_pv) +tn_pv[1] +edges(tn_pv) +tn_pv[1 => 2] + +# subgraphs +tn_sg = subgraphs(tn; npartitions=2); +typeof(tn_sg) +tn_sg[1] + +# partition +tn_pg = partition(tn; npartitions=2); +typeof(tn_pg) +tn_pg[1] +tn_pg[1 => 2][:edges] diff --git a/examples/ttns/ttn_type.jl b/examples/ttns/ttn_type.jl index c57c0dc1..9184e452 100644 --- a/examples/ttns/ttn_type.jl +++ b/examples/ttns/ttn_type.jl @@ -101,7 +101,9 @@ for e in post_order_dfs_edges(ψ_ortho, root_vertex) end @show √( - contract(norm_sqr_network(ψ_ortho); sequence=contraction_sequence(norm_sqr_network(ψ_ortho)))[] + contract( + norm_sqr_network(ψ_ortho); sequence=contraction_sequence(norm_sqr_network(ψ_ortho)) + )[], ) @show √(contract(norm_sqr_network(ψ); sequence=contraction_sequence(norm_sqr_network(ψ)))[]) @show norm(ψ_ortho[root_vertex]) diff --git a/src/ITensorNetworks.jl b/src/ITensorNetworks.jl index d2716b65..cc7b2c0c 100644 --- a/src/ITensorNetworks.jl +++ b/src/ITensorNetworks.jl @@ -31,7 +31,11 @@ include("imports.jl") # TODO: Move to `DataGraphs.jl` edge_data_type(::AbstractNamedGraph) = Any isassigned(::AbstractNamedGraph, ::Any) = false -iterate(::AbstractDataGraph) = error("Iterating data graphs is not yet defined. We may define it in the future as iterating through the vertex and edge data.") +function iterate(::AbstractDataGraph) + return error( + "Iterating data graphs is not yet defined. We may define it in the future as iterating through the vertex and edge data.", + ) +end include("utils.jl") include("visualize.jl") diff --git a/src/abstractindsnetwork.jl b/src/abstractindsnetwork.jl index f416ed13..70de7710 100644 --- a/src/abstractindsnetwork.jl +++ b/src/abstractindsnetwork.jl @@ -1,5 +1,4 @@ -abstract type AbstractIndsNetwork{V,I} <: - AbstractDataGraph{V,Vector{I},Vector{I}} end +abstract type AbstractIndsNetwork{V,I} <: AbstractDataGraph{V,Vector{I},Vector{I}} end # Field access data_graph(graph::AbstractIndsNetwork) = not_implemented() @@ -30,18 +29,11 @@ function uniqueinds(is::AbstractIndsNetwork, edge::Pair) return uniqueinds(is, edgetype(is)(edge)) end -function union( - tn1::AbstractIndsNetwork, - tn2::AbstractIndsNetwork; - kwargs..., -) +function union(tn1::AbstractIndsNetwork, tn2::AbstractIndsNetwork; kwargs...) return IndsNetwork(union(data_graph(tn1), data_graph(tn2); kwargs...)) end -function rename_vertices( - f::Function, - tn::AbstractIndsNetwork, -) +function rename_vertices(f::Function, tn::AbstractIndsNetwork) return IndsNetwork(rename_vertices(f, data_graph(tn))) end diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index 45ec8567..de548201 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -1,5 +1,4 @@ -abstract type AbstractITensorNetwork{V} <: - AbstractDataGraph{V,ITensor,ITensor} end +abstract type AbstractITensorNetwork{V} <: AbstractDataGraph{V,ITensor,ITensor} end # Field access data_graph_type(::Type{<:AbstractITensorNetwork}) = not_implemented() @@ -15,7 +14,7 @@ function weights(graph::AbstractITensorNetwork) ws = Dictionary{Tuple{V,V},Float64}(es, undef) for e in edges(graph) w = log2(dim(commoninds(graph, e))) - ws[(src(e),dst(e))] = w + ws[(src(e), dst(e))] = w end return ws end @@ -27,7 +26,9 @@ copy(tn::AbstractITensorNetwork) = not_implemented() is_directed(::Type{<:AbstractITensorNetwork}) = false # Derived interface, may need to be overloaded -underlying_graph_type(G::Type{<:AbstractITensorNetwork}) = underlying_graph_type(data_graph_type(G)) +function underlying_graph_type(G::Type{<:AbstractITensorNetwork}) + return underlying_graph_type(data_graph_type(G)) +end # AbstractDataGraphs overloads function vertex_data(graph::AbstractITensorNetwork, args...) @@ -44,11 +45,7 @@ end # Iteration # -function union( - tn1::AbstractITensorNetwork, - tn2::AbstractITensorNetwork; - kwargs..., -) +function union(tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork; kwargs...) tn = ITensorNetwork(union(data_graph(tn1), data_graph(tn2)); kwargs...) # Add any new edges that are introduced during the union for v1 in vertices(tn1) @@ -61,10 +58,7 @@ function union( return tn end -function rename_vertices( - f::Function, - tn::AbstractITensorNetwork, -) +function rename_vertices(f::Function, tn::AbstractITensorNetwork) return ITensorNetwork(rename_vertices(f, data_graph(tn))) end diff --git a/src/apply.jl b/src/apply.jl index d3192a27..9924e79d 100644 --- a/src/apply.jl +++ b/src/apply.jl @@ -1,5 +1,10 @@ function ITensors.apply( - o::ITensor, ψ::AbstractITensorNetwork; cutoff=nothing, maxdim=nothing, normalize=false, ortho=false + o::ITensor, + ψ::AbstractITensorNetwork; + cutoff=nothing, + maxdim=nothing, + normalize=false, + ortho=false, ) ψ = copy(ψ) v⃗ = neighbor_vertices(ψ, o) diff --git a/src/beliefpropagation.jl b/src/beliefpropagation.jl index e153b71d..30c4108d 100644 --- a/src/beliefpropagation.jl +++ b/src/beliefpropagation.jl @@ -1,11 +1,17 @@ -function construct_initial_mts(tn::ITensorNetwork, nvertices_per_partition::Integer; partition_kwargs=(;), kwargs...) - return construct_initial_mts(tn, partition(tn; nvertices_per_partition, partition_kwargs...); kwargs...) +function construct_initial_mts( + tn::ITensorNetwork, nvertices_per_partition::Integer; partition_kwargs=(;), kwargs... +) + return construct_initial_mts( + tn, partition(tn; nvertices_per_partition, partition_kwargs...); kwargs... + ) end function construct_initial_mts(tn::ITensorNetwork, subgraphs::DataGraph; init) # TODO: This is dropping the vertex data for some reason. # mts = DataGraph{vertextype(subgraphs),vertex_data_type(subgraphs),ITensor}(subgraphs) - mts = DataGraph{vertextype(subgraphs),vertex_data_type(subgraphs),ITensor}(directed_graph(underlying_graph(subgraphs))) + mts = DataGraph{vertextype(subgraphs),vertex_data_type(subgraphs),ITensor}( + directed_graph(underlying_graph(subgraphs)) + ) for v in vertices(mts) mts[v] = subgraphs[v] end @@ -16,12 +22,17 @@ function construct_initial_mts(tn::ITensorNetwork, subgraphs::DataGraph; init) for vertex in vertices(subgraphs[subgraph]) psiv = tn[vertex] for e in [edgetype(tn)(vertex => neighbor) for neighbor in neighbors(tn, vertex)] - if (find_subgraph(dst(e), subgraphs) == subgraph_neighbor) + if (find_subgraph(dst(e), subgraphs) == subgraph_neighbor) append!(edge_inds, commoninds(tn, e)) end end end - mt = normalize!(itensor([init(Tuple(I)...) for I in CartesianIndices(tuple(dim.(edge_inds)...))], edge_inds)) + mt = normalize!( + itensor( + [init(Tuple(I)...) for I in CartesianIndices(tuple(dim.(edge_inds)...))], + edge_inds, + ), + ) mts[subgraph => subgraph_neighbor] = mt end end @@ -31,7 +42,11 @@ end """ DO a single update of a message tensor using the current subgraph and the incoming mts """ -function update_mt(tn::ITensorNetwork, subgraph_vertices::Vector, mts::Vector{ITensor}; contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal") +function update_mt( + tn::ITensorNetwork, + subgraph_vertices::Vector, + mts::Vector{ITensor}; + contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal"), ) contract_list = [mts; [tn[v] for v in subgraph_vertices]] new_mt = if isone(length(contract_list)) @@ -42,7 +57,9 @@ function update_mt(tn::ITensorNetwork, subgraph_vertices::Vector, mts::Vector{IT return normalize!(new_mt) end -function update_mt(tn::ITensorNetwork, subgraph::ITensorNetwork, mts::Vector{ITensor}; kwargs...) +function update_mt( + tn::ITensorNetwork, subgraph::ITensorNetwork, mts::Vector{ITensor}; kwargs... +) return update_mt(tn, vertices(subgraph), mts; kwargs...) end @@ -52,7 +69,7 @@ Do an update of all message tensors for a given flat ITensornetwork and its part function update_all_mts( tn::ITensorNetwork, mts::DataGraph; - contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal") + contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal"), ) update_mts = copy(mts) for e in edges(mts) @@ -73,7 +90,7 @@ function update_all_mts( tn::ITensorNetwork, mts::DataGraph, niters::Int; - contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal") + contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal"), ) for i in 1:niters mts = update_all_mts(tn, mts; contraction_sequence) @@ -90,7 +107,8 @@ function get_single_site_expec( subgraphs::DataGraph, tnO::ITensorNetwork, v; - contraction_sequence::Function=tn -> ITensorNetworks.contraction_sequence(tn; alg="optimal") + contraction_sequence::Function=tn -> + ITensorNetworks.contraction_sequence(tn; alg="optimal"), ) subgraph = find_subgraph(v, subgraphs) num_tensors_to_contract = ITensor[] @@ -103,8 +121,12 @@ function get_single_site_expec( push!(num_tensors_to_contract, tnO[vertex]) push!(denom_tensors_to_contract, tn[vertex]) end - numerator = ITensors.contract(num_tensors_to_contract; sequence=contraction_sequence(num_tensors_to_contract))[] - denominator = ITensors.contract(denom_tensors_to_contract; sequence=contraction_sequence(denom_tensors_to_contract))[] + numerator = ITensors.contract( + num_tensors_to_contract; sequence=contraction_sequence(num_tensors_to_contract) + )[] + denominator = ITensors.contract( + denom_tensors_to_contract; sequence=contraction_sequence(denom_tensors_to_contract) + )[] return numerator / denominator end @@ -117,7 +139,7 @@ function get_two_site_expec( psiO::ITensorNetwork, v1, v2; - contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal") + contraction_sequence::Function=tn -> contraction_sequence(tn; alg="optimal"), ) subgraph1 = find_subgraph(v1, subgraphs) subgraph2 = find_subgraph(v2, subgraphs) @@ -177,8 +199,12 @@ function get_two_site_expec( end end - numerator = ITensors.contract(num_tensors_to_contract; sequence=contraction_sequence(num_tensors_to_contract))[1] - denominator = ITensors.contract(denom_tensors_to_contract; sequence=contraction_sequence(denom_tensors_to_contract))[1] + numerator = ITensors.contract( + num_tensors_to_contract; sequence=contraction_sequence(num_tensors_to_contract) + )[1] + denominator = ITensors.contract( + denom_tensors_to_contract; sequence=contraction_sequence(denom_tensors_to_contract) + )[1] out = numerator / denominator return out @@ -188,11 +214,7 @@ end Starting with initial guess for messagetensors, monitor the convergence of an observable on a single site v (which is emedded in tnO) """ function iterate_single_site_expec( - tn::ITensorNetwork, - subgraphs::DataGraph, - niters::Int, - tnO::ITensorNetwork, - v, + tn::ITensorNetwork, subgraphs::DataGraph, niters::Int, tnO::ITensorNetwork, v ) println( "Initial Guess for Observable on site " * @@ -219,16 +241,13 @@ end Starting with initial guess for messagetensors, monitor the convergence of an observable on a pair of sites v1 and v2 (which is emedded in tnO) """ function iterate_two_site_expec( - tn::ITensorNetwork, - subgraphs::DataGraph, - niters::Int, - tnO::ITensorNetwork, - v1, - v2, + tn::ITensorNetwork, subgraphs::DataGraph, niters::Int, tnO::ITensorNetwork, v1, v2 ) println( "Initial Guess for Observable on sites " * - string(v1) * " and " *string(v2) * + string(v1) * + " and " * + string(v2) * " is " * string(get_two_site_expec(tn, subgraphs, tnO, v1, v2)), ) @@ -239,7 +258,9 @@ function iterate_two_site_expec( "After iteration " * string(i) * " Belief propagation gives observable on site " * - string(v1) * " and " * string(v2) * + string(v1) * + " and " * + string(v2) * " is " * string(approx_O), ) @@ -265,7 +286,7 @@ function two_site_rdm_bp( connected_subgraphs = neighbors(subgraphs, subgraph1) for k in connected_subgraphs - if(k != subgraph2) + if (k != subgraph2) push!(tensors_to_contract, subgraphs[k => subgraph1]) end end @@ -276,7 +297,7 @@ function two_site_rdm_bp( end end - if(subgraph2 != subgraph1) + if (subgraph2 != subgraph1) connected_subgraphs2 = neighbors(subgraphs, subgraph2) for k in connected_subgraphs2 if (k != subgraph1) @@ -291,17 +312,25 @@ function two_site_rdm_bp( end end - psi1 = deepcopy(psi[v1])*prime!(dag(deepcopy(psi[v1]))) + psi1 = deepcopy(psi[v1]) * prime!(dag(deepcopy(psi[v1]))) for v in neighbors(psi, v1) - C = haskey(combiners,NamedEdge(v => v1)) ? combiners[NamedEdge(v => v1)] : combiners[NamedEdge(v1 => v)] - psi1 = psi1*C + C = if haskey(combiners, NamedEdge(v => v1)) + combiners[NamedEdge(v => v1)] + else + combiners[NamedEdge(v1 => v)] + end + psi1 = psi1 * C end push!(tensors_to_contract, psi1) - psi2 = deepcopy(psi[v2])*prime!(dag(deepcopy(psi[v2]))) + psi2 = deepcopy(psi[v2]) * prime!(dag(deepcopy(psi[v2]))) for v in neighbors(psi, v2) - C = haskey(combiners,NamedEdge(v => v2)) ? combiners[NamedEdge(v => v2)] : combiners[NamedEdge(v2 => v)] - psi2 = psi2*C + C = if haskey(combiners, NamedEdge(v => v2)) + combiners[NamedEdge(v => v2)] + else + combiners[NamedEdge(v2 => v)] + end + psi2 = psi2 * C end push!(tensors_to_contract, psi2) diff --git a/src/exports.jl b/src/exports.jl index 6d2486c8..dcac2ab6 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -26,7 +26,7 @@ export grid, # NamedGraphs # -export named_binary_tree, +export named_binary_tree, named_grid, is_tree, parent_vertex, @@ -86,4 +86,4 @@ export AbstractITensorNetwork, export hypercubic_lattice_graph, square_lattice_graph, chain_lattice_graph # partition.jl -export partition, partition_vertices +export partition, partition_vertices, subgraphs, subgraph_vertices diff --git a/src/imports.jl b/src/imports.jl index a75837cd..01c2ccb3 100644 --- a/src/imports.jl +++ b/src/imports.jl @@ -13,11 +13,7 @@ import Base: union import NamedGraphs: - vertextype, - convert_vertextype, - vertex_to_parent_vertex, - rename_vertices, - disjoint_union + vertextype, convert_vertextype, vertex_to_parent_vertex, rename_vertices, disjoint_union import .DataGraphs: underlying_graph, diff --git a/src/indsnetwork.jl b/src/indsnetwork.jl index 7ee57704..57391a64 100644 --- a/src/indsnetwork.jl +++ b/src/indsnetwork.jl @@ -16,8 +16,7 @@ is_directed(::Type{<:IndsNetwork}) = false # When setting an edge with collections of `Index`, set the reverse direction # edge with the `dag`. function reverse_data_direction( - inds_network::IndsNetwork, - is::Union{Index,Tuple{Vararg{<:Index}},Vector{<:Index}}, + inds_network::IndsNetwork, is::Union{Index,Tuple{Vararg{<:Index}},Vector{<:Index}} ) return dag(is) end @@ -31,30 +30,18 @@ function IndsNetwork(data_graph::DataGraph) return IndsNetwork{vertextype(data_graph)}(data_graph) end -function IndsNetwork{V,I}( - g::AbstractNamedGraph, - link_space, - site_space, -) where {V,I} +function IndsNetwork{V,I}(g::AbstractNamedGraph, link_space, site_space) where {V,I} link_space_dictionary = link_space_map(V, I, g, link_space) site_space_dictionary = site_space_map(V, I, g, site_space) return IndsNetwork{V,I}(g, link_space_dictionary, site_space_dictionary) end -function IndsNetwork{V}( - g::AbstractNamedGraph, - link_space, - site_space, -) where {V} +function IndsNetwork{V}(g::AbstractNamedGraph, link_space, site_space) where {V} I = indtype(link_space, site_space) return IndsNetwork{V,I}(g, link_space, site_space) end -function IndsNetwork( - g::AbstractNamedGraph, - link_space, - site_space, -) +function IndsNetwork(g::AbstractNamedGraph, link_space, site_space) V = vertextype(g) return IndsNetwork{V}(g, links_space, site_space) end @@ -100,7 +87,6 @@ _indtype(::Type{Nothing}) = Index _indtype(T::Type{<:AbstractDictionary}) = _indtype(eltype(T)) _indtype(T::Type{<:AbstractVector}) = _indtype(eltype(T)) - function default_link_space(V::Type, g::AbstractNamedGraph) # TODO: Convert `g` to vertex type `V` E = edgetype(g) @@ -112,29 +98,27 @@ function default_site_space(V::Type, g::AbstractNamedGraph) end function IndsNetwork{V,I}( - g::AbstractNamedGraph; - link_space=nothing, - site_space=nothing, + g::AbstractNamedGraph; link_space=nothing, site_space=nothing ) where {V,I} return IndsNetwork{V,I}(g, link_space, site_space) end function IndsNetwork{V}( - g::AbstractNamedGraph; - link_space=nothing, - site_space=nothing, + g::AbstractNamedGraph; link_space=nothing, site_space=nothing ) where {V} return IndsNetwork{V}(g, link_space, site_space) end -function IndsNetwork( - g::AbstractNamedGraph; - kwargs..., -) +function IndsNetwork(g::AbstractNamedGraph; kwargs...) return IndsNetwork{vertextype(g)}(g; kwargs...) end -function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_spaces::Dictionary{<:Any,Vector{Int}}) +function link_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + link_spaces::Dictionary{<:Any,Vector{Int}}, +) # TODO: Convert `g` to vertex type `V` # @assert vertextype(g) == V E = edgetype(g) @@ -147,20 +131,34 @@ function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_s return linkinds_dictionary end -function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, linkinds::AbstractDictionary{<:Any,Vector{<:Index}}) +function link_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + linkinds::AbstractDictionary{<:Any,Vector{<:Index}}, +) E = edgetype(g) return convert(Dictionary{E,Vector{I}}, linkinds) end -function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, linkinds::AbstractDictionary{<:Any,<:Index}) +function link_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + linkinds::AbstractDictionary{<:Any,<:Index}, +) return link_space_map(V, I, g, map(l -> [l], linkinds)) end -function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_spaces::Dictionary{<:Any,Int}) +function link_space_map( + V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_spaces::Dictionary{<:Any,Int} +) return link_space_map(V, I, g, map(link_space -> [link_space], link_spaces)) end -function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_spaces::Vector{Int}) +function link_space_map( + V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_spaces::Vector{Int} +) return link_space_map(V, I, g, map(Returns(link_spaces), Indices(edges(g)))) end @@ -170,23 +168,47 @@ function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_s return link_space_map(V, I, g, [link_space]) end -function link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_space::Nothing) +function link_space_map( + V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_space::Nothing +) # TODO: Make sure `edgetype(g)` is consistent with vertex type `V` return Dictionary{edgetype(g),Vector{I}}() end # TODO: Convert the dictionary according to `V` and `I` -link_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, link_space::AbstractDictionary{<:Any,<:Vector{<:Index}}) = link_space +function link_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + link_space::AbstractDictionary{<:Any,<:Vector{<:Index}}, +) + return link_space +end -function site_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, siteinds::AbstractDictionary{<:Any,Vector{<:Index}}) +function site_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + siteinds::AbstractDictionary{<:Any,Vector{<:Index}}, +) return convert(Dictionary{V,Vector{I}}, siteinds) end -function site_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, siteinds::AbstractDictionary{<:Any,<:Index}) +function site_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + siteinds::AbstractDictionary{<:Any,<:Index}, +) return site_space_map(V, I, g, map(s -> [s], siteinds)) end -function site_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, site_spaces::AbstractDictionary{<:Any,Vector{Int}}) +function site_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + site_spaces::AbstractDictionary{<:Any,Vector{Int}}, +) siteinds_dictionary = Dictionary{V,Vector{I}}() for v in keys(site_spaces) s = [vertex_index(v, site_space) for site_space in site_spaces[v]] @@ -203,18 +225,34 @@ end # Multiple site indices per vertex # TODO: How to distinguish from block indices? -function site_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, site_spaces::Vector{Int}) +function site_space_map( + V::Type, I::Type{<:Index}, g::AbstractNamedGraph, site_spaces::Vector{Int} +) return site_space_map(V, I, g, map(Returns(site_spaces), Indices(vertices(g)))) end -function site_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, site_spaces::AbstractDictionary{<:Any,Int}) +function site_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + site_spaces::AbstractDictionary{<:Any,Int}, +) return site_space_map(V, I, g, map(site_space -> [site_space], site_spaces)) end # TODO: Convert the dictionary according to `V` and `I` -site_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, site_space::AbstractDictionary{<:Any,<:Vector{<:Index}}) = site_space +function site_space_map( + V::Type, + I::Type{<:Index}, + g::AbstractNamedGraph, + site_space::AbstractDictionary{<:Any,<:Vector{<:Index}}, +) + return site_space +end -function site_space_map(V::Type, I::Type{<:Index}, g::AbstractNamedGraph, site_space::Nothing) +function site_space_map( + V::Type, I::Type{<:Index}, g::AbstractNamedGraph, site_space::Nothing +) return Dictionary{V,Vector{I}}() end diff --git a/src/itensornetwork.jl b/src/itensornetwork.jl index 2892ff61..f1eec8a2 100644 --- a/src/itensornetwork.jl +++ b/src/itensornetwork.jl @@ -17,10 +17,16 @@ end data_graph(tn::ITensorNetwork) = getfield(tn, :data_graph) data_graph_type(TN::Type{<:ITensorNetwork}) = fieldtype(TN, :data_graph) -underlying_graph_type(TN::Type{<:ITensorNetwork}) = fieldtype(data_graph_type(TN), :underlying_graph) +function underlying_graph_type(TN::Type{<:ITensorNetwork}) + return fieldtype(data_graph_type(TN), :underlying_graph) +end -ITensorNetwork{V}(data_graph::DataGraph{V}) where {V} = ITensorNetwork{V}(Private(), copy(data_graph)) -ITensorNetwork{V}(data_graph::DataGraph) where {V} = ITensorNetwork{V}(Private(), DataGraph{V}(data_graph)) +function ITensorNetwork{V}(data_graph::DataGraph{V}) where {V} + return ITensorNetwork{V}(Private(), copy(data_graph)) +end +function ITensorNetwork{V}(data_graph::DataGraph) where {V} + return ITensorNetwork{V}(Private(), DataGraph{V}(data_graph)) +end ITensorNetwork(data_graph::DataGraph) = ITensorNetwork{vertextype(data_graph)}(data_graph) @@ -94,7 +100,10 @@ function ITensorNetwork{V}(inds_network::IndsNetwork; kwargs...) where {V} end for v in vertices(tn) siteinds = get(inds_network, v, indtype(inds_network)[]) - linkinds = [get(inds_network, edgetype(inds_network)(v, nv), indtype(inds_network)[]) for nv in neighbors(inds_network, v)] + linkinds = [ + get(inds_network, edgetype(inds_network)(v, nv), indtype(inds_network)[]) for + nv in neighbors(inds_network, v) + ] setindex_preserve_graph!(tn, ITensor(siteinds, linkinds...), v) end return tn @@ -122,4 +131,4 @@ function insert_links(ψ::ITensorNetwork, edges::Vector=edges(ψ); cutoff=1e-15) ψ[dst(e)] = ψᵥ₂ end return ψ -end \ No newline at end of file +end diff --git a/src/partition.jl b/src/partition.jl index c93d6239..3e02030e 100644 --- a/src/partition.jl +++ b/src/partition.jl @@ -49,11 +49,15 @@ const kahypar_configurations = Dict([ # Metis configuration options const metis_algs = Dict(["kway" => :KWAY, "recursive" => :RECURSIVE]) -function _npartitions(g::AbstractGraph, npartitions::Integer, nvertices_per_partition::Nothing) +function _npartitions( + g::AbstractGraph, npartitions::Integer, nvertices_per_partition::Nothing +) return npartitions end -function _npartitions(g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Integer) +function _npartitions( + g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Integer +) return nv(g) ÷ nvertices_per_partition end @@ -61,25 +65,94 @@ function _npartitions(g::AbstractGraph, npartitions::Int, nvertices_per_partitio return error("Can't specify both `npartitions` and `nvertices_per_partition`") end -function _npartitions(g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Nothing) +function _npartitions( + g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Nothing +) return error("Must specify either `npartitions` or `nvertices_per_partition`") end -function partition_vertices(g::Graph; npartitions=nothing, nvertices_per_partition=nothing, backend=current_partitioning_backend(), kwargs...) - return partition_vertices(Backend(backend), g, _npartitions(g, npartitions, nvertices_per_partition); kwargs...) +function subgraph_vertices( + g::Graph; + npartitions=nothing, + nvertices_per_partition=nothing, + backend=current_partitioning_backend(), + kwargs..., +) + return subgraph_vertices( + Backend(backend), g, _npartitions(g, npartitions, nvertices_per_partition); kwargs... + ) end -function partition_vertices(g::NamedGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs...) - vertex_partitions = partition_vertices(parent_graph(g); npartitions, nvertices_per_partition, kwargs...) +function subgraph_vertices( + g::NamedGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... +) + vertex_partitions = subgraph_vertices( + parent_graph(g); npartitions, nvertices_per_partition, kwargs... + ) #[inv(vertex_to_parent_vertex(g))[v] for v in partitions] # TODO: output the reverse of this dictionary (a Vector of Vector # of the vertices in each partition). # return Dictionary(vertices(g), partitions) - return [parent_vertices_to_vertices(g, vertex_partition) for vertex_partition in vertex_partitions] + return [ + parent_vertices_to_vertices(g, vertex_partition) for + vertex_partition in vertex_partitions + ] end -function partition_vertices(g::AbstractDataGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs...) - return partition_vertices(underlying_graph(g); npartitions, nvertices_per_partition, kwargs...) +function subgraph_vertices( + g::AbstractDataGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... +) + return subgraph_vertices( + underlying_graph(g); npartitions, nvertices_per_partition, kwargs... + ) +end + +""" + partition_vertices(g::AbstractGraph, subgraph_vertices::Vector) + +Given a graph (`g`) and groups of vertices defining subgraphs of that +graph (`subgraph_vertices`), return a DataGraph storing the subgraph +vertices on the vertices of the graph and with edges denoting +which subgraphs of the original graph have edges connecting them, along with +edge data storing the original edges that were connecting the subgraphs. +""" +function partition_vertices(g::AbstractGraph, subgraph_vertices) + partitioned_vertices = DataGraph( + NamedGraph(eachindex(subgraph_vertices)), Dictionary(subgraph_vertices) + ) + for e in edges(g) + s1 = findfirst_on_vertices( + subgraph_vertices -> src(e) ∈ subgraph_vertices, partitioned_vertices + ) + s2 = findfirst_on_vertices( + subgraph_vertices -> dst(e) ∈ subgraph_vertices, partitioned_vertices + ) + if (!has_edge(partitioned_vertices, s1, s2) && s1 ≠ s2) + add_edge!(partitioned_vertices, s1, s2) + partitioned_vertices[s1 => s2] = Vector{edgetype(g)}() + end + if has_edge(partitioned_vertices, s1, s2) + push!(partitioned_vertices[s1 => s2], e) + end + end + return partitioned_vertices +end + +""" + partition_vertices(g::AbstractGraph; npartitions, nvertices_per_partition, kwargs...) + +Given a graph `g`, partition the vertices of `g` into 'npartitions' partitions +or into partitions with `nvertices_per_partition` vertices per partition. +Try to keep all subgraphs the same size and minimise edges cut between them +Returns a datagraph where each vertex contains the list of vertices involved in that subgraph. The edges state which subgraphs are connected. +A graph partitioning backend such as Metis or KaHyPar needs to be installed for this function to work. +""" +function partition_vertices( + g::AbstractGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... +) + return partition_vertices( + g, subgraph_vertices(g; npartitions, nvertices_per_partition, kwargs...) + ) end """ @@ -121,6 +194,10 @@ function find_subgraph(vertex, subgraphs::DataGraph) return findfirst_on_vertices(subgraph -> vertex ∈ vertices(subgraph), subgraphs) end +function subgraphs(g::AbstractSimpleGraph, subgraph_vertices) + return subgraphs(NamedGraph(g), subgraph_vertices) +end + """ subgraphs(g::AbstractGraph, subgraph_vertices) @@ -131,46 +208,76 @@ function subgraphs(g::AbstractGraph, subgraph_vertices) return map(vs -> subgraph(g, vs), subgraph_vertices) end +""" + subgraphs(g::AbstractGraph; npartitions::Integer, kwargs...) + +Given a graph `g`, partition `g` into `npartitions` partitions +or into partitions with `nvertices_per_partition` vertices per partition, +returning a list of subgraphs. +Try to keep all subgraphs the same size and minimise edges cut between them. +A graph partitioning backend such as Metis or KaHyPar needs to be installed for this function to work. +""" +function subgraphs( + g::AbstractGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... +) + return subgraphs(g, subgraph_vertices(g; npartitions, nvertices_per_partition, kwargs...)) +end + function partition(g::AbstractSimpleGraph, subgraph_vertices) return partition(NamedGraph(g), subgraph_vertices) end -""" - partition(g::AbstractGraph, subgraph_vertices::Vector) - -Given a graph `g` and groups of vertices defining subgraphs of that -graph `subgraph_vertices`, return a DataGraph storing the subgraphs -on the vertices of the graph and with edges denoting -which subgraphs of the original graph have edges, along with -edge data storing the edges original edges that were connecting the subgraphs -and the edge data if relavent. -""" function partition(g::AbstractGraph, subgraph_vertices) - dg_subgraphs = DataGraph(NamedGraph(eachindex(subgraph_vertices)), subgraphs(g, Dictionary(subgraph_vertices))) + partitioned_graph = DataGraph( + NamedGraph(eachindex(subgraph_vertices)), subgraphs(g, Dictionary(subgraph_vertices)) + ) for e in edges(g) - s1 = findfirst_on_vertices(subgraph -> src(e) ∈ vertices(subgraph), dg_subgraphs) - s2 = findfirst_on_vertices(subgraph -> dst(e) ∈ vertices(subgraph), dg_subgraphs) - if (!has_edge(dg_subgraphs, s1, s2) && s1 ≠ s2) - add_edge!(dg_subgraphs, s1, s2) - dg_subgraphs[s1 => s2] = Dictionary([:edges, :edge_data], [Vector{edgetype(g)}(), Dictionary{edgetype(g),edge_data_type(g)}()]) + s1 = findfirst_on_vertices(subgraph -> src(e) ∈ vertices(subgraph), partitioned_graph) + s2 = findfirst_on_vertices(subgraph -> dst(e) ∈ vertices(subgraph), partitioned_graph) + if (!has_edge(partitioned_graph, s1, s2) && s1 ≠ s2) + add_edge!(partitioned_graph, s1, s2) + partitioned_graph[s1 => s2] = Dictionary( + [:edges, :edge_data], + [Vector{edgetype(g)}(), Dictionary{edgetype(g),edge_data_type(g)}()], + ) end - if has_edge(dg_subgraphs, s1, s2) - push!(dg_subgraphs[s1 => s2][:edges], e) + if has_edge(partitioned_graph, s1, s2) + push!(partitioned_graph[s1 => s2][:edges], e) if isassigned(g, e) - set!(dg_subgraphs[s1 => s2][:edge_data], e, g[e]) + set!(partitioned_graph[s1 => s2][:edge_data], e, g[e]) end end end - return dg_subgraphs + return partitioned_graph end """ partition(g::AbstractGraph; npartitions::Integer, kwargs...) + partition(g::AbstractGraph, subgraph_vertices) -Given a graph g, partition the graph into 'npartitions' partitions. Try to keep all subgraphs the same size and minimise edges cut between them -Returns a datagraph where each vertex contains the list of vertices involved in that subgraph. The edges state which subgraphs are connected. -A graph partitioning backend such as Metis or KaHyPar needs to be installed for this function to work. +Given a graph `g`, partition `g` into `npartitions` partitions +or into partitions with `nvertices_per_partition` vertices per partition. +The partitioning tries to keep all subgraphs the same size and minimize +edges cut between them. + +Alternatively, specify a desired partitioning with a collection of sugraph +vertices. + +Returns a data graph where each vertex contains the corresponding subgraph as vertex data. +The edges indicates which subgraphs are connected, and the edge data stores a dictionary +with two fields. The field `:edges` stores a list of the edges of the original graph +that were connecting the two subgraphs, and `:edge_data` stores a dictionary +mapping edges of the original graph to the data living on the edges of the original +graph, if it existed. + +Therefore, one should be able to extract that data and recreate the original +graph from the results of `partition`. + +A graph partitioning backend such as Metis or KaHyPar needs to be installed for this function to work +if the subgraph vertices aren't specified explicitly. """ -function partition(g::AbstractGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs...) - return partition(g, partition_vertices(g; npartitions, nvertices_per_partition, kwargs...)) +function partition( + g::AbstractGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... +) + return partition(g, subgraph_vertices(g; npartitions, nvertices_per_partition, kwargs...)) end diff --git a/src/renameitensornetwork.jl b/src/renameitensornetwork.jl index 8f0c0e45..da810c15 100644 --- a/src/renameitensornetwork.jl +++ b/src/renameitensornetwork.jl @@ -19,5 +19,7 @@ end function rename_vertices_itn(psi::ITensorNetwork, name_map::Function) original_vertices = vertices(psi) - return rename_vertices_itn(psi, Dictionary(original_vertices, name_map.(original_vertices))) + return rename_vertices_itn( + psi, Dictionary(original_vertices, name_map.(original_vertices)) + ) end diff --git a/src/requires/kahypar.jl b/src/requires/kahypar.jl index 1bf9cb58..fd4f7b38 100644 --- a/src/requires/kahypar.jl +++ b/src/requires/kahypar.jl @@ -4,14 +4,14 @@ set_partitioning_backend!(Backend"KaHyPar"()) KaHyPar.HyperGraph(g::SimpleGraph) = incidence_matrix(g) """ - partition(::Backend"KaHyPar", g::Graph, npartiations::Integer; objective="edge_cut", alg="kway", kwargs...) + subgraph_vertices(::Backend"KaHyPar", g::Graph, npartiations::Integer; objective="edge_cut", alg="kway", kwargs...) - default_configuration => "cut_kKaHyPar_sea20.ini" - :edge_cut => "cut_kKaHyPar_sea20.ini" - :connectivity => "km1_kKaHyPar_sea20.ini" - imbalance::Number=0.03 """ -function partition_vertices( +function subgraph_vertices( ::Backend"KaHyPar", g::SimpleGraph, npartitions::Integer; diff --git a/src/requires/metis.jl b/src/requires/metis.jl index e9a5881e..19985484 100644 --- a/src/requires/metis.jl +++ b/src/requires/metis.jl @@ -1,14 +1,16 @@ set_partitioning_backend!(Backend"Metis"()) """ - partition_vertices(::Backend"Metis", g::AbstractGraph, npartitions::Integer; alg="recursive") + subgraph_vertices(::Backend"Metis", g::AbstractGraph, npartitions::Integer; alg="recursive") Partition the graph `G` in `n` parts. The partition algorithm is defined by the `alg` keyword: - :KWAY: multilevel k-way partitioning - :RECURSIVE: multilevel recursive bisection """ -function partition_vertices(::Backend"Metis", g::SimpleGraph, npartitions::Integer; alg="recursive", kwargs...) +function subgraph_vertices( + ::Backend"Metis", g::SimpleGraph, npartitions::Integer; alg="recursive", kwargs... +) metis_alg = metis_algs[alg] partitions = Metis.partition(g, npartitions; alg=metis_alg, kwargs...) return groupfind(Int.(partitions)) diff --git a/src/treetensornetwork/treetensornetwork.jl b/src/treetensornetwork/treetensornetwork.jl index a7cb60ab..43d44120 100644 --- a/src/treetensornetwork/treetensornetwork.jl +++ b/src/treetensornetwork/treetensornetwork.jl @@ -1,7 +1,9 @@ # TODO: Replace `AbstractITensorNetwork` with a trait `IsTree`. abstract type AbstractTreeTensorNetwork{V} <: AbstractITensorNetwork{V} end -underlying_graph_type(G::Type{<:AbstractTreeTensorNetwork}) = underlying_graph_type(data_graph_type(G)) +function underlying_graph_type(G::Type{<:AbstractTreeTensorNetwork}) + return underlying_graph_type(data_graph_type(G)) +end function default_root_vertex(ϕ::AbstractTreeTensorNetwork, ψ::AbstractTreeTensorNetwork) return first(vertices(ψ)) @@ -70,7 +72,9 @@ struct TreeTensorNetworkState{V} <: AbstractTreeTensorNetwork{V} end end -data_graph_type(G::Type{<:TreeTensorNetworkState}) = data_graph_type(fieldtype(G, :itensor_network)) +function data_graph_type(G::Type{<:TreeTensorNetworkState}) + return data_graph_type(fieldtype(G, :itensor_network)) +end function copy(ψ::TreeTensorNetworkState) return TreeTensorNetworkState(copy(ψ.itensor_network), copy(ψ.ortho_center)) @@ -82,7 +86,9 @@ const TTNS = TreeTensorNetworkState itensor_network(ψ::TreeTensorNetworkState) = getfield(ψ, :itensor_network) # Constructor -TreeTensorNetworkState(tn::ITensorNetwork, args...) = TreeTensorNetworkState{vertextype(tn)}(tn, args...) +function TreeTensorNetworkState(tn::ITensorNetwork, args...) + return TreeTensorNetworkState{vertextype(tn)}(tn, args...) +end function TreeTensorNetworkState(inds_network::IndsNetwork, args...; kwargs...) return TreeTensorNetworkState(ITensorNetwork(inds_network; kwargs...), args...) diff --git a/test/test_contraction_sequence.jl b/test/test_contraction_sequence.jl index 30494637..fd732f3f 100644 --- a/test/test_contraction_sequence.jl +++ b/test/test_contraction_sequence.jl @@ -25,18 +25,14 @@ ITensors.disable_warn_order() res_sa_bipartite = contract(tn; sequence=seq_sa_bipartite)[] @test res_optimal ≈ res_greedy ≈ res_tree_sa ≈ res_sa_bipartite - if !Sys.iswindows() # KaHyPar doesn't work on Windows # https://github.com/kahypar/KaHyPar.jl/issues/9 using Pkg Pkg.add("KaHyPar") using KaHyPar - seq_kahypar_bipartite = contraction_sequence( - tn; alg="kahypar_bipartite", sc_target=200 - ) + seq_kahypar_bipartite = contraction_sequence(tn; alg="kahypar_bipartite", sc_target=200) res_kahypar_bipartite = contract(tn; sequence=seq_kahypar_bipartite)[] @test res_optimal ≈ res_kahypar_bipartite end end - diff --git a/test/test_examples.jl b/test/test_examples.jl index 07402940..1c31ca82 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -29,7 +29,8 @@ using Test joinpath("partition", "kahypar_vs_metis.jl"), joinpath("partition", "partitioning.jl"), ] - @testset "Test $example_file (using KaHyPar, so no Windows support)" for example_file in example_files + @testset "Test $example_file (using KaHyPar, so no Windows support)" for example_file in + example_files @suppress include(joinpath(pkgdir(ITensorNetworks), "examples", example_file)) end end From db6fea142e4dd4d2f5c51d3a44b2babbcb8c1cf8 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Fri, 23 Dec 2022 12:02:07 -0500 Subject: [PATCH 6/6] Add SplitApplyCombine as test dependency --- test/Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Project.toml b/test/Project.toml index e175f584..2393e7af 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -10,6 +10,7 @@ NamedGraphs = "678767b0-92e7-4007-89e4-4527a8725b19" OMEinsumContractionOrders = "6f22d1fd-8eed-4bb7-9776-e7d684900715" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"