diff --git a/Project.toml b/Project.toml index 94e3b7e2..71015667 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorNetworksNext" uuid = "302f2e75-49f0-4526-aef7-d8ba550cb06c" -version = "0.11.2" +version = "0.12.0" authors = ["ITensor developers and contributors"] [workspace] @@ -23,6 +23,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" TensorAlgebra = "68bd88dc-f39d-4e12-b2ca-f046b68fcc6a" +UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [weakdeps] OMEinsumContractionOrders = "6f22d1fd-8eed-4bb7-9776-e7d684900715" @@ -48,4 +49,5 @@ Random = "1.10" SimpleTraits = "0.9.5" SplitApplyCombine = "1.2.3" TensorAlgebra = "0.23.1" +UUIDs = "1.10" julia = "1.10" diff --git a/docs/Project.toml b/docs/Project.toml index eee8bc4e..c8846666 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -10,5 +10,5 @@ path = ".." [compat] Documenter = "1" ITensorFormatter = "0.2.27" -ITensorNetworksNext = "0.11" +ITensorNetworksNext = "0.12" Literate = "2" diff --git a/examples/Project.toml b/examples/Project.toml index 0fa22c56..52c95335 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -5,4 +5,4 @@ ITensorNetworksNext = "302f2e75-49f0-4526-aef7-d8ba550cb06c" path = ".." [compat] -ITensorNetworksNext = "0.11" +ITensorNetworksNext = "0.12" diff --git a/src/ITensorNetworkGenerators/ising_network.jl b/src/ITensorNetworkGenerators/ising_network.jl index 702b8787..c1d4b769 100644 --- a/src/ITensorNetworkGenerators/ising_network.jl +++ b/src/ITensorNetworkGenerators/ising_network.jl @@ -1,6 +1,6 @@ using ..ITensorNetworksNext using Graphs: degree, dst, edges, src -using ITensorBase: NamedTensor, name, uniquename +using ITensorBase: NamedTensor, inds, name, uniquename using LinearAlgebra: Diagonal, eigen using NamedGraphs: vertextype @@ -33,7 +33,7 @@ function ising_network( fp(e) = get(() -> l̃[reverse(e)], l̃, e) tn = delta_network(fp, elt, g) for v in sz_vertices - tn[v] = diagonaltensor(elt[1, -1], axes(tn[v])) + tn[v] = diagonaltensor(elt[1, -1], axes(tn, v)) end for e in edges(g) diff --git a/src/abstracttensornetwork.jl b/src/abstracttensornetwork.jl index eabe39ba..0fe4148b 100644 --- a/src/abstracttensornetwork.jl +++ b/src/abstracttensornetwork.jl @@ -3,8 +3,8 @@ using DataGraphs: DataGraphs, AbstractDataGraph, AbstractVertexDataGraph, edge_d set_vertex_data!, underlying_graph, underlying_graph_type, vertex_data using Graphs: Graphs, AbstractEdge, AbstractGraph, add_edge!, add_vertex!, dst, edges, edgetype, ne, neighbors, nv, rem_edge!, src, vertices -using ITensorBase: ITensorOperator, NamedUnitRange, inds, inputnames, name, names, nametype, - prime, uniquename, unnamedtype +using ITensorBase: ITensorBase, ITensorOperator, NamedUnitRange, inds, inputnames, name, + names, nametype, prime, uniquename, unnamedtype using LinearAlgebra: LinearAlgebra using MacroTools: @capture using NamedGraphs: @@ -37,13 +37,17 @@ end # ====================================== interface ======================================= # +ITensorBase.inds(tn::AbstractITensorNetwork, v) = inds(tn[v]) +ITensorBase.axes(tn::AbstractITensorNetwork, v) = axes(tn[v]) +ITensorBase.names(tn::AbstractITensorNetwork, v) = names(tn[v]) + linkinds(tn::AbstractGraph, edge::Pair) = linkinds(tn, edgetype(tn)(edge)) # Pick the link indices from the `src` side, identified by name match with `dst`. # A range-strict intersection (`inds(src) ∩ inds(dst)`) would drop graded links # whose two endpoints carry dual-related ranges. function linkinds(tn::AbstractGraph, edge::AbstractEdge) ln = linknames(tn, edge) - return [i for i in inds(tn[src(edge)]) if name(i) in ln] + return [i for i in inds(tn, src(edge)) if name(i) in ln] end function linkaxes(tn::AbstractGraph, edge::Pair) @@ -51,53 +55,47 @@ function linkaxes(tn::AbstractGraph, edge::Pair) end function linkaxes(tn::AbstractGraph, edge::AbstractEdge) ln = linknames(tn, edge) - return [ax for ax in axes(tn[src(edge)]) if name(ax) in ln] + return Tuple(ax for ax in axes(tn, src(edge)) if name(ax) in ln) end function linknames(tn::AbstractGraph, edge::Pair) return linknames(tn, edgetype(tn)(edge)) end function linknames(tn::AbstractGraph, edge::AbstractEdge) - return names(tn[src(edge)]) ∩ names(tn[dst(edge)]) + dstnames = names(tn, dst(edge)) + return [n for n in names(tn, src(edge)) if n in dstnames] end +eachlinkname(tn::AbstractGraph) = Iterators.flatten(linknames(tn, e) for e in edges(tn)) function siteinds(tn::AbstractGraph, v) - s = inds(tn[v]) + s = inds(tn, v) for v′ in neighbors(tn, v) - s = setdiff(s, inds(tn[v′])) + s = setdiff(s, inds(tn, v′)) end return s end function siteaxes(tn::AbstractGraph, v) - s = axes(tn[v]) + s = axes(tn, v) for v′ in neighbors(tn, v) - s = setdiff(s, axes(tn[v′])) + s = setdiff(s, axes(tn, v′)) end - return s + return Tuple(s) end function sitenames(tn::AbstractGraph, v) - s = names(tn[v]) + s = names(tn, v) for v′ in neighbors(tn, v) - s = setdiff(s, names(tn[v′])) + s = setdiff(s, names(tn, v′)) end return s end # Return the non-link vertices associated with an dim name function dimnamevertices(tn::AbstractGraph, name) - sites = vertextype(tn)[] - - for v in vertices(tn) - if name ∈ names(tn[v]) - push!(sites, v) - end - end - - return sites + return Indices(filter(v -> name ∈ names(tn, v), vertices(tn))) end function has_dimname(tn::AbstractGraph, name) for v in vertices(tn) - if name ∈ names(tn[v]) + if name ∈ names(tn, v) return true end end @@ -107,7 +105,7 @@ end has_ind(tn::AbstractGraph, ind) = has_dimname(tn, name(ind)) function insertlink!(tn::AbstractGraph, e) - T = eltype(inds(tn[src(e)])) + T = eltype(inds(tn, src(e))) linkind = NamedUnitRange(trivialrange(unnamedtype(T)), uniquename(nametype(T))) diff --git a/src/beliefpropagation/beliefpropagation.jl b/src/beliefpropagation/beliefpropagation.jl index fa387253..df39650a 100644 --- a/src/beliefpropagation/beliefpropagation.jl +++ b/src/beliefpropagation/beliefpropagation.jl @@ -272,9 +272,9 @@ end # the message is positive semidefinite and its trace is a positive normalization. function message_update!(algorithm::SimpleMessageUpdate, cache, factors::NormNetwork, edge) new_tensor = updated_message(algorithm, cache, factors, edge) - new_message = operator( - new_tensor, linknames(BraView(factors), edge), linknames(KetView(factors), edge) - ) + branames = linknames(branetwork(factors), edge) + ketnames = linknames(ketnetwork(factors), edge) + new_message = operator(new_tensor, branames, ketnames) if algorithm.normalize message_norm = tr(new_message) iszero(message_norm) || (new_message /= message_norm) diff --git a/src/beliefpropagation/messagecache.jl b/src/beliefpropagation/messagecache.jl index ec91cd58..ebbfd759 100644 --- a/src/beliefpropagation/messagecache.jl +++ b/src/beliefpropagation/messagecache.jl @@ -191,16 +191,16 @@ bethe_free_energy(factors, messages) = -bethe_free_entropy(factors, messages) function similar_message_environment(nn::NormNetwork) messages = mapmany(vertices(nn)) do vertex return map(in_incident_edges(nn, vertex)) do edge - braview = BraView(nn) - ketview = KetView(nn) + bra = branetwork(nn) + ket = ketnetwork(nn) - ketnames = linknames(ketview, edge) - branames = linknames(braview, edge) - braaxis = unnamed.(linkaxes(braview, edge)) + ketnames = linknames(ket, edge) + branames = linknames(bra, edge) + braaxis = unnamed.(linkaxes(bra, edge)) # Bra leg = operator output, ket leg = input, the bipartition in which the message # is positive semidefinite. - message = similar_operator(ketview[vertex], braaxis, branames, ketnames) + message = similar_operator(ket[vertex], braaxis, branames, ketnames) return edge => message end diff --git a/src/normnetwork.jl b/src/normnetwork.jl index d735b22f..46308a8f 100644 --- a/src/normnetwork.jl +++ b/src/normnetwork.jl @@ -1,5 +1,5 @@ -using Dictionaries: Dictionary, dictionary -using ITensorBase: rename, setname, similar_operator, uniquename +using Dictionaries: Dictionary, Indices, dictionary +using ITensorBase: ITensorBase, conj, nametype, rename, similar_operator using ITensorNetworksNext """ @@ -16,14 +16,24 @@ struct NormNetwork{T, V, I} <: AbstractITensorNetwork{T, V} ket::ITensorNetwork{T, V, I}, map::Dictionary{I, I} ) where {T, V, I} - braname = Dictionary{I, I}() - for (name, vertices) in pairs(ket.dimname_vertices) - if length(vertices) == 2 - insert!(braname, name, map[name]) - end + check_input(NormNetwork, ket, map) + return new{T, V, I}(ket, map) + end +end + +function check_input(::Type{<:NormNetwork}, ket, braname) + if !issubset(eachlinkname(ket), keys(braname)) + error("every link name of the ket layer must have a bra name.") + end + if !allunique(braname) + error("the bra names of a norm network must be distinct.") + end + for name in braname + if has_dimname(ket, name) + error("bra name $name is already an index name of the ket layer.") end - return new{T, V, I}(ket, braname) end + return nothing end function Base.eltype(::Type{<:NormNetwork}) @@ -32,20 +42,20 @@ function Base.eltype(::Type{<:NormNetwork}) ) end -function NormNetwork(tn::ITensorNetwork) - return NormNetwork(tn, map(uniquename, keys(tn.dimname_vertices))) -end - # ====================================== Graphs.jl ======================================= # -Graphs.edges(nn::NormNetwork) = edges(nn.ket) -Graphs.vertices(nn::NormNetwork) = vertices(nn.ket) +Graphs.edges(nn::NormNetwork) = edges(ketnetwork(nn)) +Graphs.vertices(nn::NormNetwork) = vertices(ketnetwork(nn)) # ==================================== NamedGraphs.jl ==================================== # -NamedGraphs.encoded_vertex(nn::NormNetwork, vertex) = encoded_vertex(nn.ket, vertex) -NamedGraphs.decoded_vertex(nn::NormNetwork, code::Integer) = decoded_vertex(nn.ket, code) -NamedGraphs.encoded_graph(nn::NormNetwork) = encoded_graph(nn.ket) +function NamedGraphs.encoded_vertex(nn::NormNetwork, vertex) + return encoded_vertex(ketnetwork(nn), vertex) +end +function NamedGraphs.decoded_vertex(nn::NormNetwork, code::Integer) + return decoded_vertex(ketnetwork(nn), code) +end +NamedGraphs.encoded_graph(nn::NormNetwork) = encoded_graph(ketnetwork(nn)) # ==================================== DataGraphs.jl ===================================== # @@ -56,8 +66,9 @@ function DataGraphs.get_vertex_data(nn::NormNetwork, vertex) end function DataGraphs.is_vertex_assigned(nn::NormNetwork, vertex) - return isassigned(nn.ket, vertex) + return isassigned(ketnetwork(nn), vertex) end + # =================================== Dictionaries.jl ==================================== # Dictionaries.issettable(::NormNetwork) = false @@ -65,22 +76,64 @@ Dictionaries.isinsertable(::NormNetwork) = false # ====================================== interface ======================================= # +# The name type of the indices, the same as the ket network's. +ITensorBase.nametype(::Type{<:NormNetwork{T, V, I}}) where {T, V, I} = I +ITensorBase.nametype(nn::NormNetwork) = nametype(typeof(nn)) + +""" + ketnetwork(nn::NormNetwork) + +The ket-layer network of `nn`. +""" +ketnetwork(nn::NormNetwork) = nn.ket + +""" + branetwork(nn::NormNetwork) + +The bra-layer network of `nn`, a `BraView` whose tensors are built by [`bratensor`](@ref) when +accessed. +""" +branetwork(nn::NormNetwork) = BraView(nn) + +""" + branamemap(nn::NormNetwork) + +The ket→bra name map, holding a bra name for each ket index name that has a separate bra copy. +""" +branamemap(nn::NormNetwork) = nn.braname + +""" + braname(nn::NormNetwork, name) + +The bra-layer index name corresponding to the ket-layer index name `name`. +""" function braname(nn::NormNetwork, name) - if !has_dimname(nn.ket, name) + if !has_dimname(ketnetwork(nn), name) error("index name $name not found underlying tensor network.") end - # The indices not stored in `nn.braname` are precisely the site indices, which - # get mapped to themselves. - return get(nn.braname, name, name) + # The names absent from the map are the site indices, which map to themselves. + return get(branamemap(nn), name, name) end -indmap(nn::NormNetwork, ind) = setname(conj(ind), braname(nn, name(ind))) +# The default ket→bra name map +derive_braname(name) = prime(name) + +""" + kettensor(nn::NormNetwork, vertex) + +The ket-layer tensor at `vertex`. +""" +kettensor(nn::NormNetwork, vertex) = ketnetwork(nn)[vertex] -kettensor(nn::NormNetwork, vertex) = nn.ket[vertex] function conj_bratensor(nn::NormNetwork, vertex) return rename(n -> braname(nn, n), kettensor(nn, vertex)) end +""" + bratensor(nn::NormNetwork, vertex) + +The bra-layer tensor at `vertex`. +""" bratensor(nn::NormNetwork, vertex) = conj(conj_bratensor(nn, vertex)) """ @@ -102,12 +155,22 @@ function flatten_network(nn::NormNetwork) end """ - normnetwork(tn::ITensorNetwork, [braname]) -> NormNetwork - -Build the double-layer norm network `⟨tn|tn⟩`, represented lazily as a `NomnNetwork` object. -The optional second argument `braname` should implement `braname[ketdimname] = bradimname` for -every link dimension name `ketdimname` in `tn`. If this is not specified, then a name is -generated via the `ITensorBase.uniquename` function. + normnetwork([f::Function], tn::ITensorNetwork, [dangling]) -> NormNetwork + normnetwork(tn::ITensorNetwork, braname::Dictionary) -> NormNetwork + +Build the double-layer norm network `⟨tn|tn⟩`, represented lazily as a `NormNetwork` object. +Each ket index name in `union(dangling, eachlinkname(tn))` gets the bra name `f(name)`, +where `f` defaults to `derive_braname`, which primes the name, and `dangling` is empty by +default. +Alternatively pass the ket→bra name map `braname` directly, which must include every link +name of `tn` and may include others. Either way an error is thrown if a link name is missing, +two bra names coincide or a bra name is already an index name of `tn`. """ -normnetwork(tn::ITensorNetwork) = NormNetwork(tn) -normnetwork(tn::ITensorNetwork, braname) = NormNetwork(tn, braname) +normnetwork(tn::ITensorNetwork, braname::Dictionary) = NormNetwork(tn, braname) +function normnetwork(f::Function, tn::ITensorNetwork, dangling = nametype(tn)[]) + names = Indices{nametype(tn)}(union(eachlinkname(tn), dangling)) + return normnetwork(tn, narrow_map(f, names)) +end +function normnetwork(tn::ITensorNetwork, dangling = nametype(tn)[]) + return normnetwork(derive_braname, tn, dangling) +end diff --git a/src/normnetworkview.jl b/src/normnetworkview.jl index 75c57926..8b675089 100644 --- a/src/normnetworkview.jl +++ b/src/normnetworkview.jl @@ -1,58 +1,50 @@ using DataGraphs: DataGraphs, get_vertex_data, is_vertex_assigned using Dictionaries: Dictionaries, isinsertable, issettable using Graphs: Graphs, edges, vertices +using ITensorBase: ITensorBase, conj, inds using NamedGraphs: NamedGraphs, decoded_vertex, encoded_graph, encoded_vertex -struct KetView{T, V, I} <: AbstractITensorNetwork{T, V} - parent::NormNetwork{T, V, I} -end +""" + struct BraView{T, V, I} <: AbstractITensorNetwork{T, V} +The bra layer of the norm network `parent(view)`, with each vertex tensor built by +[`bratensor`](@ref) when accessed. Its graph structure and mutability are those of the parent. +""" struct BraView{T, V, I} <: AbstractITensorNetwork{T, V} parent::NormNetwork{T, V, I} end -# ====================================== Graphs.jl ======================================= # +Base.parent(nnv::BraView) = nnv.parent -for View in (:KetView, :BraView) - @eval begin - Graphs.edges(nnv::$View) = edges(nnv.parent) - Graphs.vertices(nnv::$View) = vertices(nnv.parent) - end +# ==================================== DataGraphs.jl ===================================== # + +DataGraphs.get_vertex_data(nnv::BraView, vertex) = bratensor(parent(nnv), vertex) +function DataGraphs.is_vertex_assigned(nnv::BraView, vertex) + return is_vertex_assigned(parent(nnv), vertex) end -# ==================================== NamedGraphs.jl ==================================== # +# ==================================== ITensorBase.jl ==================================== # -for View in (:KetView, :BraView) - @eval begin - function NamedGraphs.encoded_vertex(nnv::$View, vertex) - return encoded_vertex(nnv.parent, vertex) - end - function NamedGraphs.decoded_vertex(nnv::$View, code::Integer) - return decoded_vertex(nnv.parent, code) - end - - NamedGraphs.encoded_graph(nnv::$View) = encoded_graph(nnv.parent) - end +function ITensorBase.inds(nnv::BraView, vertex) + return conj.(inds(conj_bratensor(parent(nnv), vertex))) end -# ==================================== DataGraphs.jl ===================================== # +# ====================================== Graphs.jl ======================================= # + +Graphs.edges(nnv::BraView) = edges(parent(nnv)) +Graphs.vertices(nnv::BraView) = vertices(parent(nnv)) -DataGraphs.get_vertex_data(nn::KetView, vertex) = kettensor(nn.parent, vertex) -DataGraphs.get_vertex_data(nn::BraView, vertex) = bratensor(nn.parent, vertex) +# ==================================== NamedGraphs.jl ==================================== # -for View in (:KetView, :BraView) - @eval begin - function DataGraphs.is_vertex_assigned(nnv::$View, vertex) - return isassigned(nnv.parent.ket, vertex) - end - end +function NamedGraphs.encoded_vertex(nnv::BraView, vertex) + return encoded_vertex(parent(nnv), vertex) +end +function NamedGraphs.decoded_vertex(nnv::BraView, code::Integer) + return decoded_vertex(parent(nnv), code) end +NamedGraphs.encoded_graph(nnv::BraView) = encoded_graph(parent(nnv)) # =================================== Dictionaries.jl ==================================== # -for View in (:KetView, :BraView) - @eval begin - Dictionaries.issettable(nnv::$View) = issettable(nnv.parent) - Dictionaries.isinsertable(nnv::$View) = isinsertable(nnv.parent) - end -end +Dictionaries.issettable(nnv::BraView) = issettable(parent(nnv)) +Dictionaries.isinsertable(nnv::BraView) = isinsertable(parent(nnv)) diff --git a/src/tensornetwork.jl b/src/tensornetwork.jl index 57ad5b2b..5742d749 100644 --- a/src/tensornetwork.jl +++ b/src/tensornetwork.jl @@ -11,11 +11,11 @@ using SplitApplyCombine: mapview struct ITensorNetwork{T, V, I} <: AbstractITensorNetwork{T, V} tensors::Dictionary{V, T} - dimname_vertices::Dictionary{I, Set{V}} + dimname_vertices::Dictionary{I, Indices{V}} underlying_graph::NamedGraph{V} function ITensorNetwork{T, V, I}(::UndefInitializer, vertices) where {T, V, I} tensors = Dictionary{V, T}() - dimname_vertices = Dictionary{I, Set{V}}() + dimname_vertices = Dictionary{I, Indices{V}}() underlying_graph = NamedGraph(vertices) return new{T, V, I}(tensors, dimname_vertices, underlying_graph) end @@ -118,7 +118,7 @@ DataGraphs.get_vertex_data(tn::ITensorNetwork, v) = tn.tensors[v] function check_input(::typeof(set_vertex_data!), tn, tensor, vertex) for name in names(tensor) - vertices = get(tn.dimname_vertices, name, Set()) + vertices = get(tn.dimname_vertices, name, Indices{vertextype(tn)}()) if length(setdiff(vertices, Set([vertex]))) > 1 throw( ArgumentError( @@ -146,7 +146,7 @@ function DataGraphs.set_vertex_data!(tn::ITensorNetwork, tensor, vertex) end function update_tensornetwork_metadata!(tn, vertex, tensor) - oldnames = isassigned(tn, vertex) ? names(tn[vertex]) : Set{nametype(tn)}() + oldnames = isassigned(tn, vertex) ? names(tn, vertex) : Set{nametype(tn)}() newnames = names(tensor) update_tensornetwork_metadata!(tn, vertex, oldnames, newnames) @@ -165,7 +165,7 @@ function update_tensornetwork_metadata!(tn, vertex, oldnames, newnames) # Now `name` must be a new index that's not in `oldinds` - vertex_list = get!(tn.dimname_vertices, name, Set()) + vertex_list = get!(tn.dimname_vertices, name, Indices{vertextype(tn)}()) if length(vertex_list) > 1 throw( ArgumentError( @@ -173,7 +173,7 @@ function update_tensornetwork_metadata!(tn, vertex, oldnames, newnames) ) ) end - push!(vertex_list, vertex) + insert!(vertex_list, vertex) # Add an edge if the index is now shared between two vertices. if length(vertex_list) == 2 @@ -197,12 +197,19 @@ Graphs.add_edge!(::ITensorNetwork, _edge) = false # PERF: fast lookup compared to `AbstractITensorNetwork` fallback. function dimnamevertices(tn::ITensorNetwork, name) - return get(tn.dimname_vertices, name, Set{vertextype(tn)}()) + return copy(get(tn.dimname_vertices, name, Indices{vertextype(tn)}())) end # PERF: fast lookup compared to `AbstractITensorNetwork` fallback. has_dimname(tn::ITensorNetwork, name) = haskey(tn.dimname_vertices, name) +# PERF: still linear compared to generic method, but in practice 5x faster +function eachlinkname(tn::ITensorNetwork) + return ( + name for (name, vertices) in pairs(tn.dimname_vertices) if length(vertices) == 2 + ) +end + function NamedGraphs.similar_graph( T::Type{<:ITensorNetwork}, vertices = vertextype(T)[] diff --git a/src/utils.jl b/src/utils.jl index abf666e4..5113f317 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,6 +1,21 @@ using Dictionaries: AbstractIndices, Dictionary +using ITensorBase: ITensorBase, IndexName, settags +using UUIDs: uuid5 # `map` does something clever to figure out the element type of the output when not # inferable, but the `map` overload on `Dictionary` does not, so we fix this here. narrow_map(f, v) = map(f, v) narrow_map(f, v::AbstractIndices) = Dictionary(v, [f(x) for x in v]) + +""" + derivedname(name::IndexName, key) -> IndexName + +Return a name derived deterministically from `name` and `key`: the identifier is replaced by +the name-based UUID `uuid5(uuid(name), string(key))`, `key` is added as a bare-label tag, and +the other tags and the prime level are kept. Every process derives the same name from the same +`name` and `key`, and the result differs from `name` and from any independently minted name. +""" +function derivedname(name::IndexName, key) + derived = ITensorBase.setuuid(name, uuid5(ITensorBase.uuid(name), string(key))) + return settags(derived, string(key)) +end diff --git a/test/Project.toml b/test/Project.toml index 9abc495b..8845e97c 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -32,7 +32,7 @@ Dictionaries = "0.4.5" GradedArrays = "0.17" Graphs = "1.13.1" ITensorBase = "0.16.1" -ITensorNetworksNext = "0.11" +ITensorNetworksNext = "0.12" ITensorPkgSkeleton = "0.3.42" MatrixAlgebraKit = "0.6" NamedGraphs = "0.14" diff --git a/test/test_apply_operator.jl b/test/test_apply_operator.jl index 8c2048a9..00e4e855 100644 --- a/test/test_apply_operator.jl +++ b/test/test_apply_operator.jl @@ -1,8 +1,8 @@ using GradedArrays: U1, gradedrange using Graphs: dst, edges, src, vertices using ITensorBase: Index, apply, name, operator, setname, uniquename -using ITensorNetworksNext: NormNetwork, apply_operator, apply_operators, insertlink!, - message_environment, tensornetwork +using ITensorNetworksNext: apply_operator, apply_operators, insertlink!, + message_environment, normnetwork, tensornetwork using MatrixAlgebraKit: svd_trunc, truncrank using NamedGraphs: named_cycle_graph, named_path_graph using Random: AbstractRNG @@ -35,7 +35,7 @@ function random_state(rng::AbstractRNG, elt::Type, g, site_axes; nlayers, trunc) insertlink!(network, edge) end - env = message_environment(one, NormNetwork(network)) + env = message_environment(one, normnetwork(network)) for _ in 1:nlayers, e in edges(g) gate = randn_operator(rng, elt, (site_axes[src(e)], site_axes[dst(e)])) network, env = apply_operator(gate, network, env; trunc) diff --git a/test/test_basics.jl b/test/test_basics.jl index a975170c..2c9318bc 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -18,6 +18,7 @@ using Test: @test, @testset @test issetequal(vertices(tn), vertices(g)) for v in vertices(tn) @test issetequal(siteinds(tn, v), [s[v]]) + @test axes(tn, v) == (s[v],) end end @testset "Construct ITensorNetwork partition function" begin @@ -35,6 +36,8 @@ using Test: @test, @testset @test issetequal(arranged_edges(tn), arranged_edges(g)) for v in vertices(tn) @test isempty(siteinds(tn, v)) + @test axes(tn, v) == axes(tn[v]) + @test issetequal(axes(tn, v), [l[e] for e in incident_edges(g, v)]) end for v1 in vertices(tn) for v2 in vertices(tn) diff --git a/test/test_beliefpropagation.jl b/test/test_beliefpropagation.jl index 1591e26f..67ac8555 100644 --- a/test/test_beliefpropagation.jl +++ b/test/test_beliefpropagation.jl @@ -6,10 +6,10 @@ using Graphs: AbstractGraph, add_vertex!, dst, edges, has_edge, has_vertex, ne, rem_edge!, src, vertices using ITensorBase: ITensor, Index, apply, inds, name, noprime, outputnames, prime, state using ITensorNetworksNext: ITensorNetworksNext, ContractionTreeAlgorithm, Greedy, - ITensorNetwork, MessageCache, NormNetwork, SimpleMessageUpdate, StopWhenConverged, - beliefpropagation, bethe_free_energy, bethe_free_entropy, bratensor, contraction_tree, - edge_scalar, edge_scalars, factor_tensors, incoming_messages, insertlink!, kettensor, - linkaxes, linkinds, message_environment, messagecache, prod_tensors, region_scalar, + ITensorNetwork, MessageCache, SimpleMessageUpdate, StopWhenConverged, beliefpropagation, + bethe_free_energy, bethe_free_entropy, bratensor, contraction_tree, edge_scalar, + edge_scalars, factor_tensors, incoming_messages, insertlink!, kettensor, linkaxes, + linkinds, message_environment, messagecache, normnetwork, prod_tensors, region_scalar, subgraph, tensornetwork, updated_message, vertex_scalar, vertex_scalars using LinearAlgebra: LinearAlgebra, norm, tr using NamedGraphs: NamedEdge, all_edges, incident_edges, named_comb_tree, named_grid, @@ -312,7 +312,7 @@ end for edge in edges(g) insertlink!(network, edge) end - nn = NormNetwork(network) + nn = normnetwork(network) cache = beliefpropagation( nn, message_environment(one, nn); @@ -364,7 +364,7 @@ end for edge in edges(g) insertlink!(network, edge) end - nn = NormNetwork(network) + nn = normnetwork(network) v = (2, 2) # A doubled vertex splits into its two layers. diff --git a/test/test_free_fermions.jl b/test/test_free_fermions.jl index c4450b67..686f8060 100644 --- a/test/test_free_fermions.jl +++ b/test/test_free_fermions.jl @@ -1,8 +1,8 @@ using GradedArrays: U1 using Graphs: dst, edges, src, vertices using ITensorBase: Index, apply, inds, operator, prime -using ITensorNetworksNext: NormNetwork, apply_operator, apply_operators, insertlink!, - message_environment, tensornetwork +using ITensorNetworksNext: apply_operator, apply_operators, insertlink!, + message_environment, normnetwork, tensornetwork using NamedGraphs: named_comb_tree, named_cycle_graph using TensorAlgebra: project, project_aux using TensorKitSectors: FermionNumber @@ -71,7 +71,7 @@ function bp_evolve(sector, g, occupied, bonds) for e in edges(g) insertlink!(ψ, e) end - env = message_environment(one, NormNetwork(ψ)) + env = message_environment(one, normnetwork(ψ)) for v in occupied ψ, env = apply_operator(cdag(sites[v]), ψ, env) end diff --git a/test/test_itensornetworkgenerators.jl b/test/test_itensornetworkgenerators.jl index 0dc9f4b7..1c47b326 100644 --- a/test/test_itensornetworkgenerators.jl +++ b/test/test_itensornetworkgenerators.jl @@ -51,6 +51,24 @@ using Test: @test, @testset f_analytic = TestUtils.f_1d_ising(β, 4; periodic) @test f ≈ f_analytic end + @testset "1D Ising spin correlator" begin + N = 6 + β = 0.4 + g = named_grid((N,); periodic = true) + ldict = DataGraph(g) + for e in edges(g) + ldict[e] = Index(2) + end + l = e -> ldict[e] + z = prod_tensors(ising_network(l, β, g), Greedy())[] + + # On a periodic chain ⟨σᵢσᵢ₊d⟩ = (tᵈ + tᴺ⁻ᵈ) / (1 + tᴺ) with t = tanh(β). + t = tanh(β) + for d in 1:3 + tn = ising_network(l, β, g; sz_vertices = [(1,), (1 + d,)]) + @test prod_tensors(tn, Greedy())[] / z ≈ (t^d + t^(N - d)) / (1 + t^N) + end + end @testset "2D Ising" begin dims = (4, 4) β = TestUtils.βc_2d_ising() diff --git a/test/test_normnetwork.jl b/test/test_normnetwork.jl index 4528b220..7861aec3 100644 --- a/test/test_normnetwork.jl +++ b/test/test_normnetwork.jl @@ -1,10 +1,12 @@ using DataGraphs: is_vertex_assigned -using Dictionaries: isinsertable, issettable -using Graphs: edges, vertices -using ITensorBase: ITensor, Index, IndexName, conj, inds, name, setname, uniquename -using ITensorNetworksNext: BraView, Greedy, ITensorNetwork, KetView, NormNetwork, braname, - bratensor, conj_bratensor, contraction_tree, flatten_network, indmap, kettensor, - normnetwork, prod_tensors, tensornetwork +using Dictionaries: Dictionary, Indices, isinsertable, issettable +using Graphs: edges, has_edge, neighbors, vertices +using ITensorBase: + ITensor, Index, IndexName, inds, name, nametype, plev, prime, setname, tags, uniquename +using ITensorNetworksNext: BraView, Greedy, ITensorNetwork, NormNetwork, braname, + branetwork, bratensor, conj_bratensor, contraction_tree, derivedname, dimnamevertices, + eachlinkname, flatten_network, has_dimname, ketnetwork, kettensor, normnetwork, + prod_tensors, sitenames, tensornetwork using LinearAlgebra: norm using NamedGraphs: NamedEdge, incident_edges, named_grid, named_path_graph using Test: @test, @test_throws, @testset @@ -30,10 +32,7 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] @testset "Basics" begin g = named_path_graph(3) tn, l, s = random_state(Float64, g) - nn = NormNetwork(tn) - - # `normnetwork` is the public constructor and agrees with `NormNetwork`. - @test normnetwork(tn) isa NormNetwork + nn = normnetwork(tn) @test nn isa NormNetwork # The norm network shares the graph structure of the underlying network. @@ -44,6 +43,7 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] # `eltype` is defined. @test_throws ErrorException nn[1] @test_throws ErrorException eltype(nn) + @test nametype(nn) === nametype(tn) === IndexName # Vertex data is assigned wherever the underlying network is. @test is_vertex_assigned(nn, 1) @@ -56,7 +56,7 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] @testset "kettensor / bratensor / conj_bratensor and the name map" begin g = named_path_graph(3) tn, l, s = random_state(Float64, g) - nn = NormNetwork(tn) + nn = normnetwork(tn) # `kettensor` returns the underlying tensor untouched. @test kettensor(nn, 2) === tn[2] @@ -79,11 +79,6 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] # `bra` is the elementwise conjugate of `conj_bratensor` and carries the same indices. @test inds(bratensor(nn, 2)) == inds(conj_bratensor(nn, 2)) - # `indmap` conjugates an index and renames it according to the name map. - ind = only(i for i in inds(kettensor(nn, 2)) if name(i) == lname) - @test name(indmap(nn, ind)) == braname(nn, name(ind)) - @test indmap(nn, ind) == setname(conj(ind), braname(nn, name(ind))) - # Querying the name map with an index name absent from the network errors. @test_throws ErrorException braname(nn, name(Index(2))) end @@ -93,7 +88,7 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] tn, l, s = random_state(Float64, g) # A user-supplied map dictates the bra-layer name for each link. - custom = map(uniquename, keys(tn.dimname_vertices)) + custom = map(uniquename, Indices(eachlinkname(tn))) nn = normnetwork(tn, custom) lname = name(l[NamedEdge(1 => 2)]) @@ -101,40 +96,132 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] @test braname(nn, lname) in name.(inds(conj_bratensor(nn, 2))) end - @testset "`KetView` / `BraView`" begin + @testset "`derivedname`" begin + n = prime(name(Index(2; tags = "Link"))) + + # The derived name depends only on the name and key, keeps the decoration and is tagged + # with the key. + @test derivedname(n, :bra) == derivedname(n, :bra) + @test derivedname(n, :bra) != n + @test derivedname(n, :bra) != derivedname(n, :other) + @test derivedname(n, :bra) != prime(n) + @test tags(derivedname(n, :bra)) == merge(tags(n), Dict("bra" => "")) + @test plev(derivedname(n, :bra)) == plev(n) + end + + @testset "default and function name maps" begin + g = named_path_graph(3) + tn, l, s = random_state(Float64, g) + lname = name(l[NamedEdge(1 => 2)]) + + # Two independently constructed norm networks agree on every bra name. + @test braname(normnetwork(tn), lname) == prime(lname) + @test braname(normnetwork(tn), lname) == braname(normnetwork(tn), lname) + f = n -> derivedname(n, :bra) + @test braname(normnetwork(f, tn), lname) == derivedname(lname, :bra) + + # A bra name that is already an index name of the network is rejected. + taken = setname(Index(2), prime(lname)) + tn[1] = tn[1] * ITensor(ones(2), (taken,)) + @test_throws ErrorException normnetwork(tn) + end + + @testset "rejected name maps" begin + g = named_path_graph(3) + tn, l, s = random_state(Float64, g) + l12 = name(l[NamedEdge(1 => 2)]) + l23 = name(l[NamedEdge(2 => 3)]) + + # A map missing the bra name of the link 2 => 3 is rejected. + @test_throws ErrorException normnetwork(tn, Dictionary([l12], [uniquename(l12)])) + + # A map giving two links the same bra name is rejected. + shared = uniquename(l12) + @test_throws ErrorException normnetwork( + tn, + Dictionary([l12, l23], [shared, shared]) + ) + end + + @testset "bond to a vertex outside the network" begin + g = named_path_graph(3) + tn, l, s = random_state(Float64, g) + + # The part of `tn` on vertices 1 and 2, as one rank of a distributed network holds it. + part = ITensorNetwork(Dict(v => tn[v] for v in (1, 2))) + nn = normnetwork(part, map(uniquename, Indices(eachlinkname(tn)))) + + # The bond to vertex 3 is not a link of `part`, but it is in the map, so it gets its + # own bra name and the two layers at vertex 2 share only the site index. + lname = name(l[NamedEdge(2 => 3)]) + @test braname(nn, lname) != lname + shared = + intersect(name.(inds(kettensor(nn, 2))), name.(inds(conj_bratensor(nn, 2)))) + @test issetequal(shared, [name(s[2])]) + end + + @testset "`ketnetwork` / `branetwork`" begin g = named_path_graph(3) tn, l, s = random_state(Float64, g) - nn = NormNetwork(tn) + nn = normnetwork(tn) - kv = KetView(nn) - bv = BraView(nn) + # The ket layer is the network the norm network was built from. + @test ketnetwork(nn) === tn - # Views share the graph structure of the underlying network. - @test issetequal(vertices(kv), vertices(tn)) + # The bra layer is not stored, so it is a view sharing the ket layer's graph structure. + bv = branetwork(nn) + @test bv isa BraView @test issetequal(vertices(bv), vertices(tn)) - @test issetequal(edges(kv), edges(tn)) @test issetequal(edges(bv), edges(tn)) - - # The ket view exposes the bare ket tensors; the bra view exposes the bra tensors. for v in vertices(tn) - @test kv[v] === kettensor(nn, v) @test inds(bv[v]) == inds(bratensor(nn, v)) end - - @test is_vertex_assigned(kv, 1) @test is_vertex_assigned(bv, 1) - # Views inherit the (non-)mutability of their parent norm network. - @test !issettable(kv) - @test !isinsertable(kv) + # The view inherits the (non-)mutability of its parent norm network. @test !issettable(bv) @test !isinsertable(bv) end + @testset "`BraView` index lookups" begin + g = named_path_graph(3) + tn, l, s = random_state(Float64, g) + nn = normnetwork(tn) + bv = branetwork(nn) + lname = name(l[NamedEdge(1 => 2)]) + bname = braname(nn, lname) + + # The bra layer has the bra link names and the same site names as the ket layer. + @test inds(bv, 2) == inds(bv[2]) + @test issetequal(dimnamevertices(bv, bname), [1, 2]) + @test issetequal(dimnamevertices(bv, name(s[2])), [2]) + @test isempty(dimnamevertices(bv, lname)) + @test has_dimname(bv, bname) + @test !has_dimname(bv, lname) + @test issetequal(eachlinkname(bv), [braname(nn, name(l[e])) for e in edges(g)]) + @test sitenames(bv, 2) == [name(s[2])] + end + + @testset "graph structure on a grid" begin + g = named_grid((2, 3)) + tn, l, s = random_state(Float64, g) + nn = normnetwork(tn) + bv = branetwork(nn) + + # The norm network and its bra layer have the same neighbors and edges as the grid. + for v in vertices(g) + @test issetequal(neighbors(nn, v), neighbors(g, v)) + @test issetequal(neighbors(bv, v), neighbors(g, v)) + end + @test has_edge(nn, (1, 1) => (1, 2)) + @test has_edge(bv, (1, 1) => (1, 2)) + @test !has_edge(nn, (1, 1) => (2, 2)) + end + @testset "`flatten_network`" begin g = named_path_graph(3) tn, l, s = random_state(Float64, g) - nn = NormNetwork(tn) + nn = normnetwork(tn) flat = flatten_network(nn) # One vertex per layer, so the vertex count doubles and each carries its layer's tensor. @@ -160,7 +247,7 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] v = randn(s) v = v / norm(v) tn = ITensorNetwork(Dict(1 => v)) - nn = NormNetwork(tn) + nn = normnetwork(tn) # ⟨ψ|ψ⟩ for a single normalized site tensor is 1. @test contract_norm(nn) ≈ 1 @@ -171,18 +258,18 @@ contract_norm(nn) = prod_tensors(flatten_network(nn), Greedy())[] tn, l, s = random_state(T, g) # The norm network contracts to ⟨tn|tn⟩ = ‖prod(tn)‖², a real nonnegative number. - z = contract_norm(NormNetwork(tn)) + z = contract_norm(normnetwork(tn)) @test z ≈ norm(prod(tn))^2 @test imag(z) ≈ 0 atol = 1.0e-12 * abs(z) @test real(z) > 0 # Rescaling a single tensor by 1/√z normalizes the state, so ⟨tn|tn⟩ = 1. tn[first(vertices(tn))] = tn[first(vertices(tn))] / sqrt(real(z)) - @test contract_norm(NormNetwork(tn)) ≈ 1 + @test contract_norm(normnetwork(tn)) ≈ 1 # The contracted norm does not depend on the chosen bra-layer name map. - custom = map(uniquename, keys(tn.dimname_vertices)) - @test contract_norm(normnetwork(tn, custom)) ≈ contract_norm(NormNetwork(tn)) + custom = map(uniquename, Indices(eachlinkname(tn))) + @test contract_norm(normnetwork(tn, custom)) ≈ contract_norm(normnetwork(tn)) end end end diff --git a/test/test_tensornetwork.jl b/test/test_tensornetwork.jl index 0beaad01..77384c9d 100644 --- a/test/test_tensornetwork.jl +++ b/test/test_tensornetwork.jl @@ -2,9 +2,9 @@ using DataGraphs: DataGraph, assigned_edge_data, assigned_vertex_data, underlying_graph, vertex_data using Graphs: add_edge!, add_vertex!, dst, edges, edgetype, has_edge, has_vertex, is_directed, ne, nv, rem_edge!, rem_vertex!, src, vertices -using ITensorBase: Index, inds, operator -using ITensorNetworksNext: ITensorNetwork, has_ind, linkaxes, linkinds, linknames, - operator_support, siteaxes, siteinds, sitenames, tensornetwork +using ITensorBase: Index, inds, name, operator +using ITensorNetworksNext: ITensorNetwork, eachlinkname, has_ind, linkaxes, linkinds, + linknames, operator_support, siteaxes, siteinds, sitenames, tensornetwork using NamedGraphs: convert_vertextype, incident_edges, named_grid, named_path_graph, similar_graph, subgraph, vertextype using Test: @test, @test_throws, @testset @@ -114,15 +114,16 @@ using Test: @test, @test_throws, @testset @test linkinds(tn, 1 => 2) == [l[E(1 => 2)]] @test linkinds(tn, E(1 => 2)) == [l[E(1 => 2)]] - @test linkaxes(tn, 1 => 2) == [l[E(1 => 2)]] - @test linkaxes(tn, E(1 => 2)) == [l[E(1 => 2)]] + @test linkaxes(tn, 1 => 2) == (l[E(1 => 2)],) + @test linkaxes(tn, E(1 => 2)) == (l[E(1 => 2)],) - @test linknames(tn, 1 => 2) == [l[E(1 => 2)].name] - @test linknames(tn, E(1 => 2)) == [l[E(1 => 2)].name] + @test linknames(tn, 1 => 2) == [name(l[E(1 => 2)])] + @test linknames(tn, E(1 => 2)) == [name(l[E(1 => 2)])] + @test issetequal(eachlinkname(tn), [name(l[e]) for e in edges(g)]) @test siteinds(tn, 1) == [s[1]] - @test siteaxes(tn, 2) == [s[2]] - @test sitenames(tn, 3) == [s[3].name] + @test siteaxes(tn, 2) == (s[2],) + @test sitenames(tn, 3) == [name(s[3])] end @testset "`operator_support`" begin