From 1f42ff7998438b1601ad1a984f20fb9ff7be88ca Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 11 May 2026 19:19:14 -0400 Subject: [PATCH 1/5] Trim the ITensorsExtensions submodule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-codebase usage audit found ~110 of the submodule's 258 lines uncalled. Three groupings of changes: **Dead-code deletions:** - `sqrt_diag` / `inv_diag` / `invsqrt_diag` / `pinv_diag` / `pinvsqrt_diag` (five convenience wrappers around `map_diag`, zero callers). - `diagblocks` (both `Tensor` and `ITensor` methods, zero callers) along with its supporting `DenseTensor` overloads of `NDTensors.blockoffsets` / `nzblocks` / `blockdim` (only consumer was the now-dead `diagblocks`). - `LinearAlgebra.isdiag(::ITensor)` (zero callers; `apply.jl` and the internal `map_eigvals` reference resolve to `ITensors.isdiag`). - `is_delta` (three methods, zero callers). - `ITensors._contract(::Key)` dispatch patch (bypassed because `contract.jl:18` unwraps `Key`-wrapped sequences via `deepmap` before reaching `_contract`). - `ITensors.replaceinds(::ITensor, ::Dict{<:Index, <:Index})` performance patch (zero callers in src/, test/, or ext/). **Type-system block transitively dead — entry chain `trivial_space(x)` → `promote_indtypeof(x)` had only test callers:** - `make_bosonic` and the `using_auto_fermion` / `has_fermionic_subspaces` fermion branch in `map_eigvals`. The corresponding "Fermionic map eigvals tests" testset in `test_itensorsextensions.jl` is dropped. The fermion-agnostic "Fermionic eigendecomp" testset (which tests `eigendecomp` directly) stays. - `promote_indtypeof(::AbstractITensorNetwork)`, `promote_indtypeof (::AbstractIndsNetwork)`, and the `function promote_indtypeof end` declaration. - `indtype(::ITensor)`, `indtype(::IndsNetwork)`, and `indtype(::Type{ <:IndsNetwork{V, I}})`. The user-facing `indtype(link_space, site_space)` 4-form (used by the `IndsNetwork{V,I}` constructor to infer `I`) stays. - `promote_indtype(::Vararg{Type{<:Index}})`, `promote_indtype_rule`, `promote_spacetype_rule` (four methods), `spacetype` — all only reached through the deleted `promote_indtypeof` chain. - `trivial_space` (all four methods) — replaced at its single test callsite by an inline `hasqns`-conditional helper in `test/utils.jl`. **Test-only relocations:** - `replace_vertices` and its internal `set_sites` helper, used only by `test/test_opsum_to_ttn_mpo_cross_check.jl`, move to `test/utils.jl`. - `group_terms`, used only by `test/test_tebd.jl`, moves to `test/utils.jl`. - `src/lib/ITensorsExtensions/src/opsum.jl` is deleted outright. **Doc cleanup:** - Drop the `promote_indtypeof(is::AbstractIndsNetwork)` and `indtype (inds_network::IndsNetwork)` entries in `docs/src/experimental_methods .md`. Final state: the submodule keeps `tensor_sum`, `eigendecomp` + `map_eigvals` (now fermion-free), and the focused type-system chain supporting the `IndsNetwork{V,I}` constructor — `IsIndexSpace`, `indtype(link_space, site_space)` family, `indtype(space)`, `_indtype`. About 60 LOC instead of 258. --- docs/src/experimental_methods.md | 7 -- src/abstractindsnetwork.jl | 15 +-- src/abstractitensornetwork.jl | 6 +- src/indsnetwork.jl | 2 - .../src/ITensorsExtensions.jl | 1 - src/lib/ITensorsExtensions/src/itensor.jl | 90 +---------------- .../ITensorsExtensions/src/itensor_more.jl | 97 +++---------------- src/lib/ITensorsExtensions/src/opsum.jl | 39 -------- test/test_itensorsextensions.jl | 52 ---------- test/test_opsum_to_ttn_mpo_cross_check.jl | 1 - test/test_tebd.jl | 1 - test/utils.jl | 60 +++++++++++- 12 files changed, 76 insertions(+), 295 deletions(-) delete mode 100644 src/lib/ITensorsExtensions/src/opsum.jl diff --git a/docs/src/experimental_methods.md b/docs/src/experimental_methods.md index cd8a5abd..8f83db88 100644 --- a/docs/src/experimental_methods.md +++ b/docs/src/experimental_methods.md @@ -204,11 +204,6 @@ Methods which still need to be discussed, modified, or deprecated. rename_vertices(f::Function, tn::AbstractIndsNetwork) ``` -* Promoted index type across all site and link indices in the network (`abstractindsnetwork.jl`): - ```julia - promote_indtypeof(is::AbstractIndsNetwork) - ``` - * Build an `IndsNetwork` whose site/link indices at each vertex/edge are the union of the corresponding indices from each input network (graphs must match) (`abstractindsnetwork.jl`): ```julia @@ -219,8 +214,6 @@ Methods which still need to be discussed, modified, or deprecated. * Type-parameter accessors and graph-type metadata (`indsnetwork.jl`): ```julia - indtype(inds_network::IndsNetwork) - indtype(::Type{<:IndsNetwork{V, I}}) where {V, I} data_graph(is::IndsNetwork) underlying_graph(is::IndsNetwork) vertextype(::Type{<:IndsNetwork{V}}) where {V} diff --git a/src/abstractindsnetwork.jl b/src/abstractindsnetwork.jl index f00b2a56..3253cc63 100644 --- a/src/abstractindsnetwork.jl +++ b/src/abstractindsnetwork.jl @@ -1,9 +1,8 @@ -using .ITensorsExtensions: ITensorsExtensions, promote_indtype, trivial_space using DataGraphs: DataGraphs, AbstractDataGraph, DataGraph, IsUnderlyingGraph, edge_data, get_edge_data, get_vertex_data, is_edge_assigned, is_vertex_assigned, map_data, set_edge_data!, set_vertex_data!, underlying_graph_type, vertex_data using Graphs: Graphs, AbstractEdge -using ITensors: ITensors, unioninds +using ITensors: ITensors, Index, unioninds using NamedGraphs.GraphsExtensions: GraphsExtensions, directed_graph, incident_edges, rename_vertices using NamedGraphs: NamedGraphs @@ -62,18 +61,6 @@ end # Convenience functions # -function ITensorsExtensions.promote_indtypeof(is::AbstractIndsNetwork) - sitetype = mapreduce(promote_indtype, vertices(is); init = Index{Int}) do v - # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. - return mapreduce(typeof, promote_indtype, get(is, v, Index[]); init = Index{Int}) - end - linktype = mapreduce(promote_indtype, edges(is); init = Index{Int}) do e - # TODO: Replace with `is[e]` once `getindex(::IndsNetwork, ...)` is smarter. - return mapreduce(typeof, promote_indtype, get(is, e, Index[]); init = Index{Int}) - end - return promote_indtype(sitetype, linktype) -end - function union_all_inds(is_in::AbstractIndsNetwork...) @assert all(map(ug -> ug == underlying_graph(is_in[1]), underlying_graph.(is_in))) is_out = IndsNetwork(underlying_graph(is_in[1])) diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index 3b51701c..93d27ab9 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -1,4 +1,4 @@ -using .ITensorsExtensions: ITensorsExtensions, indtype, promote_indtype, trivial_space +using .ITensorsExtensions: ITensorsExtensions using Adapt: Adapt, adapt, adapt_structure using DataGraphs: DataGraphs, edge_data, get_vertex_data, is_vertex_assigned, set_vertex_data!, underlying_graph, underlying_graph_type, vertex_data @@ -210,10 +210,6 @@ end # Promotion and conversion # -function ITensorsExtensions.promote_indtypeof(tn::AbstractITensorNetwork) - return mapreduce(v -> indtype(tn[v]), promote_indtype, vertices(tn)) -end - function NDTensors.scalartype(tn::AbstractITensorNetwork) return mapreduce(v -> eltype(tn[v]), promote_type, vertices(tn); init = Bool) end diff --git a/src/indsnetwork.jl b/src/indsnetwork.jl index c7f6c851..0c465770 100644 --- a/src/indsnetwork.jl +++ b/src/indsnetwork.jl @@ -16,8 +16,6 @@ struct IndsNetwork{V, I} <: AbstractIndsNetwork{V, I} return new{V, I}(g) end end -ITensorsExtensions.indtype(inds_network::IndsNetwork) = indtype(typeof(inds_network)) -ITensorsExtensions.indtype(::Type{<:IndsNetwork{V, I}}) where {V, I} = I data_graph(is::IndsNetwork) = is.data_graph NamedGraphs.vertextype(::Type{<:IndsNetwork{V}}) where {V} = V Graphs.is_directed(::Type{<:IndsNetwork}) = false diff --git a/src/lib/ITensorsExtensions/src/ITensorsExtensions.jl b/src/lib/ITensorsExtensions/src/ITensorsExtensions.jl index 1346b65a..d0b5bbbe 100644 --- a/src/lib/ITensorsExtensions/src/ITensorsExtensions.jl +++ b/src/lib/ITensorsExtensions/src/ITensorsExtensions.jl @@ -1,5 +1,4 @@ module ITensorsExtensions include("itensor.jl") include("itensor_more.jl") -include("opsum.jl") end diff --git a/src/lib/ITensorsExtensions/src/itensor.jl b/src/lib/ITensorsExtensions/src/itensor.jl index a1895055..b4091473 100644 --- a/src/lib/ITensorsExtensions/src/itensor.jl +++ b/src/lib/ITensorsExtensions/src/itensor.jl @@ -1,29 +1,5 @@ -using ITensors.NDTensors: Block, BlockOffsets, DenseTensor, DiagBlockSparseTensor, - NDTensors, Tensor, blockdim, blockoffsets, denseblocks, diaglength, getdiagindex, - nzblocks, setdiagindex!, svd, tensor -using ITensors: ITensors, ITensor, Index, commonind, dag, dir, hasqns, indices, inds, - isdiag, itensor, map_diag, noncommonind, noprime, permute, replaceind, replaceinds, sim, - space, sqrt_decomp -using LinearAlgebra: LinearAlgebra, eigen, pinv - -function NDTensors.blockoffsets(dense::DenseTensor) - return BlockOffsets{ndims(dense)}([Block(ntuple(Returns(1), ndims(dense)))], [0]) -end -function NDTensors.nzblocks(dense::DenseTensor) - return nzblocks(blockoffsets(dense)) -end -NDTensors.blockdim(ind::Int, ::Block{1}) = ind -NDTensors.blockdim(i::Index{Int}, b::Integer) = blockdim(i, Block(b)) -NDTensors.blockdim(i::Index{Int}, b::Block) = blockdim(space(i), b) - -LinearAlgebra.isdiag(it::ITensor) = isdiag(tensor(it)) - -# Convenience functions -sqrt_diag(it::ITensor) = map_diag(sqrt, it) -inv_diag(it::ITensor) = map_diag(inv, it) -invsqrt_diag(it::ITensor) = map_diag(inv ∘ sqrt, it) -pinv_diag(it::ITensor) = map_diag(pinv, it) -pinvsqrt_diag(it::ITensor) = map_diag(pinv ∘ sqrt, it) +using ITensors: ITensor, commonind, dag, isdiag, map_diag, noncommonind, replaceinds +using LinearAlgebra: eigen #TODO: Make this work for non-hermitian A function eigendecomp(A::ITensor, linds, rinds; ishermitian = false, kwargs...) @@ -34,64 +10,8 @@ function eigendecomp(A::ITensor, linds, rinds; ishermitian = false, kwargs...) return Ul, D, dag(U) end -function make_bosonic(A::ITensor, Linds, Rinds) - # Bring indices into i',j',..,dag(j),dag(i) - # ordering with Out indices coming before In indices - # Resulting tensor acts like a normal matrix (no extra signs - # when taking powers A^n) - Linds = indices(Linds) - Rinds = indices(Rinds) - if all(j -> dir(j) == ITensors.Out, Linds) && all(j -> dir(j) == ITensors.In, Rinds) - ordered_inds = [Linds..., reverse(Rinds)...] - elseif all(j -> dir(j) == ITensors.Out, Rinds) && all(j -> dir(j) == ITensors.In, Linds) - ordered_inds = [Rinds..., reverse(Linds)...] - else - error( - "For fermionic exp, Linds and Rinds must have same directions within each set. Got dir.(Linds)=", - dir.(Linds), - ", dir.(Rinds)=", - dir.(Rinds) - ) - end - # permuted A^n will be sign free, ok to temporarily disable fermion system - return permute(A, ordered_inds) -end - function map_eigvals(f::Function, A::ITensor, Linds, Rinds; kws...) - # - fermionic_itensor = - ITensors.using_auto_fermion() && ITensors.has_fermionic_subspaces(inds(A)) - if fermionic_itensor - A = make_bosonic(A::ITensor, Linds, Rinds) - ordered_inds = inds(A) - end - - mapped_A = NDTensors.with_auto_fermion(!fermionic_itensor) do - isdiag(A) && return map_diag(f, A) - Ul, D, Ur = eigendecomp(A, Linds, Rinds; kws...) - return Ul * map_diag(f, D) * Ur - end - - # - if fermionic_itensor - # Ensure indices in "matrix" form before re-enabling fermion system - mapped_A = permute(mapped_A, ordered_inds) - end - - return mapped_A + isdiag(A) && return map_diag(f, A) + Ul, D, Ur = eigendecomp(A, Linds, Rinds; kws...) + return Ul * map_diag(f, D) * Ur end - -# Analagous to `denseblocks`. -# Extract the diagonal entries into a diagonal tensor. -function diagblocks(D::Tensor) - nzblocksD = nzblocks(D) - T = DiagBlockSparseTensor(eltype(D), nzblocksD, inds(D)) - for b in nzblocksD - for n in 1:diaglength(D) - setdiagindex!(T, getdiagindex(D, n), n) - end - end - return T -end - -diagblocks(it::ITensor) = itensor(diagblocks(tensor(it))) diff --git a/src/lib/ITensorsExtensions/src/itensor_more.jl b/src/lib/ITensorsExtensions/src/itensor_more.jl index 2121d86f..e5b37542 100644 --- a/src/lib/ITensorsExtensions/src/itensor_more.jl +++ b/src/lib/ITensorsExtensions/src/itensor_more.jl @@ -1,7 +1,5 @@ -using Dictionaries: AbstractDictionary, Dictionary -using ITensors.NDTensors: NDTensors -using ITensors: ITensors, ITensor, Index, QN, filterinds, inds, op, replaceinds, uniqueinds -using NamedGraphs.Keys: Key +using Dictionaries: AbstractDictionary +using ITensors: ITensor, Index, QN, filterinds, op, uniqueinds # Tensor sum: `A ⊞ B = A ⊗ Iᴮ + Iᴬ ⊗ B` # https://github.com/JuliaLang/julia/issues/13333#issuecomment-143825995 @@ -20,98 +18,27 @@ function tensor_sum(A::ITensor, B::ITensor) return A + B end -# Patch for contraction sequences with `Key` -# leaf values. -# TODO: Move patch to `ITensors.jl`. -ITensors._contract(As, index::Key) = As[index] - -# TODO: Replace with a trait of the same name. +# Spaces accepted by `Index`: a plain `Integer` dimension (dense) or a QN-block +# vector `Vector{Pair{QN, <:Integer}}` (block-sparse). Used as the dispatch tag +# for `_indtype` so a raw space spec gets mapped to the appropriate `Index{T}`. const IsIndexSpace = Union{<:Integer, Vector{<:Pair{QN, <:Integer}}} -# Infer the `Index` type of an `IndsNetwork` from the -# spaces that get input. +# Infer the `Index` type of an `IndsNetwork` from the per-edge / per-vertex +# space specs the user passes to the `IndsNetwork{V, I}(g, link_space, +# site_space)` constructor. Either argument may be `nothing` (defer entirely +# to the other), a space spec, or a container (`AbstractVector` / +# `AbstractDictionary`) of space specs. indtype(link_space::Nothing, site_space::Nothing) = Index indtype(link_space::Nothing, site_space) = indtype(site_space) indtype(link_space, site_space::Nothing) = indtype(link_space) indtype(link_space, site_space) = promote_type(indtype(link_space), indtype(site_space)) -# Default to type space indtype(space) = _indtype(typeof(space)) -# Base case -# Use `_indtype` to avoid recursion overflow +# `_indtype` dispatches on the static space type to avoid recursion through +# the user-facing `indtype(space)` entry point. _indtype(T::Type{<:Index}) = T _indtype(T::Type{<:IsIndexSpace}) = Index{T} _indtype(::Type{Nothing}) = Index - -# Containers _indtype(T::Type{<:AbstractDictionary}) = _indtype(eltype(T)) _indtype(T::Type{<:AbstractVector}) = _indtype(eltype(T)) - -indtype(a::ITensor) = promote_indtype(typeof.(inds(a))...) - -spacetype(::Index{T}) where {T} = T -spacetype(::Type{<:Index{T}}) where {T} = T - -function promote_indtype(is::Vararg{Type{<:Index}}) - return reduce(promote_indtype_rule, is; init = Index{Int}) -end - -function promote_spacetype_rule(type1::Type, type2::Type) - return error("Not implemented") -end - -function promote_spacetype_rule( - type1::Type{<:Integer}, type2::Type{<:Vector{<:Pair{QN, T2}}} - ) where {T2 <: Integer} - return Vector{Pair{QN, promote_type(type1, T2)}} -end - -function promote_spacetype_rule( - type1::Type{<:Vector{<:Pair{QN, <:Integer}}}, type2::Type{<:Integer} - ) - return promote_spacetype_rule(type2, type1) -end - -function promote_spacetype_rule( - type1::Type{<:Vector{<:Pair{QN, T1}}}, type2::Type{<:Vector{<:Pair{QN, T2}}} - ) where {T1 <: Integer, T2 <: Integer} - return Vector{Pair{QN, promote_type(T1, T2)}} -end - -function promote_spacetype_rule(type1::Type{<:Integer}, type2::Type{<:Integer}) - return promote_type(type1, type2) -end - -function promote_indtype_rule(type1::Type{<:Index}, type2::Type{<:Index}) - return Index{promote_spacetype_rule(spacetype(type1), spacetype(type2))} -end - -function promote_indtypeof end - -trivial_space(x) = trivial_space(promote_indtypeof(x)) -trivial_space(x::Type) = trivial_space(promote_indtype(x)) - -trivial_space(i::Type{<:Index{<:Integer}}) = 1 -trivial_space(i::Type{<:Index{<:Vector{<:Pair{<:QN, <:Integer}}}}) = [QN() => 1] - -# Given an input tensor and a Dict (ind_to_newind), replace inds of tensor that are also -# keys of ind_to_newind to the value of ind_to_newind. -# Note that it is the same as -# ITensors.replaceinds(tensor, collect(keys(ind_to_newind)) => collect(values(ind_to_newind))). -# Based on benchmark, this implementation is more efficient when the size of ind_to_newind is large. -# TODO: we can remove this function once the original replaceinds performance is improved. -function ITensors.replaceinds(tensor::ITensor, ind_to_newind::Dict{<:Index, <:Index}) - subset_inds = intersect(inds(tensor), collect(keys(ind_to_newind))) - if length(subset_inds) == 0 - return tensor - end - out_inds = map(i -> ind_to_newind[i], subset_inds) - return replaceinds(tensor, subset_inds => out_inds) -end - -is_delta(it::ITensor) = is_delta(NDTensors.tensor(it)) -is_delta(t::NDTensors.Tensor) = false -function is_delta(t::NDTensors.UniformDiagTensor) - return isone(NDTensors.getdiagindex(t, 1)) -end diff --git a/src/lib/ITensorsExtensions/src/opsum.jl b/src/lib/ITensorsExtensions/src/opsum.jl deleted file mode 100644 index 7c5d4c63..00000000 --- a/src/lib/ITensorsExtensions/src/opsum.jl +++ /dev/null @@ -1,39 +0,0 @@ -using ..BaseExtensions: maybe_real, to_tuple -using Graphs: dst, edges, src -using ITensors.LazyApply: Applied, Prod, Scaled, Sum -using ITensors.Ops: Op, Ops -using ITensors: ITensors -using SplitApplyCombine: group - -# TODO: Rename this `replace_sites`? -# TODO: Use `fmap`, `deepmap`, `treemap`? -function replace_vertices(f, ∑o::Sum) - return Sum(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∑o))) -end - -function replace_vertices(f, ∏o::Prod) - return Prod(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∏o))) -end - -function replace_vertices(f, o::Scaled) - return maybe_real(Ops.coefficient(o)) * replace_vertices(f, Ops.argument(o)) -end - -set_sites(o::Op, sites) = Op(Ops.which_op(o), sites...; Ops.params(o)...) - -function replace_vertices(f, o::Op) - return set_sites(o, f.(Ops.sites(o))) -end - -## function replace_vertices(o::Union{Op,Applied}, vertex_map) -## return replace_vertices(v -> get(vertex_map, v, v), o) -## end - -function group_terms(ℋ::Sum, g) - grouped_terms = group(ITensors.terms(ℋ)) do t - findfirst(edges(g)) do e - return to_tuple.(ITensors.sites(t)) ⊆ [src(e), dst(e)] - end - end - return Sum(collect(sum.(grouped_terms))) -end diff --git a/test/test_itensorsextensions.jl b/test/test_itensorsextensions.jl index f2d0ecca..e273f14b 100644 --- a/test/test_itensorsextensions.jl +++ b/test/test_itensorsextensions.jl @@ -79,56 +79,4 @@ using Test: @test, @testset @test Ul * D * Ur ≈ T end end - - @testset "Fermionic map eigvals tests" begin - with_auto_fermion() do - s1 = Index( - [QN("Nf", 0, -1) => 2, QN("Nf", 1, -1) => 2], "Site,Fermion,n=1" - ) - s2 = Index( - [QN("Nf", 0, -1) => 2, QN("Nf", 1, -1) => 2], "Site,Fermion,n=2" - ) - - # Make a random Hermitian matrix ITensor - M = random_itensor(s1', dag(s1)) - #M = mapprime(prime(M)*swapprime(dag(M),0=>1),2=>1) - M = apply(M, swapprime(dag(M), 0 => 1)) - - # Make a random Hermitian matrix-like 4th order ITensor - T = random_itensor(s1', s2', dag(s2), dag(s1)) - T = apply(T, swapprime(dag(T), 0 => 1)) - - # Matrix test - sqrtM = map_eigvals(sqrt, M, [s1'], [dag(s1)]; ishermitian = true) - @test M ≈ apply(sqrtM, sqrtM) - - ## Tensor test - sqrtT = map_eigvals(sqrt, T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian = true) - @test T ≈ apply(sqrtT, sqrtT) - - # Permute and test again - T = permute(T, dag(s2), s2', dag(s1), s1') - sqrtT = map_eigvals(sqrt, T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian = true) - @test T ≈ apply(sqrtT, sqrtT) - - ## Explicitly passing indices in different, valid orders - sqrtT = map_eigvals(sqrt, T, [s2', s1'], [dag(s2), dag(s1)]; ishermitian = true) - @test T ≈ apply(sqrtT, sqrtT) - sqrtT = map_eigvals( - sqrt, T, [dag(s2), dag(s1)], [s2', s1'], ; ishermitian = true - ) - @test T ≈ apply(sqrtT, sqrtT) - sqrtT = map_eigvals( - sqrt, T, [dag(s1), dag(s2)], [s1', s2'], ; ishermitian = true - ) - @test T ≈ apply(sqrtT, sqrtT) - - # Test bosonic index case while fermion system is enabled - b = Index([QN("Nb", 0) => 2, QN("Nb", 1) => 2]) - T = random_itensor(b', dag(b)) - T = apply(T, swapprime(dag(T), 0 => 1)) - sqrtT = map_eigvals(sqrt, T, [b'], [dag(b)]; ishermitian = true) - @test T ≈ apply(sqrtT, sqrtT) - end - end end diff --git a/test/test_opsum_to_ttn_mpo_cross_check.jl b/test/test_opsum_to_ttn_mpo_cross_check.jl index a0509f44..995c93f6 100644 --- a/test/test_opsum_to_ttn_mpo_cross_check.jl +++ b/test/test_opsum_to_ttn_mpo_cross_check.jl @@ -2,7 +2,6 @@ using DataGraphs: vertex_data using Dictionaries: Dictionary using Graphs: add_edge!, add_vertex!, rem_edge!, vertices using ITensorMPS: ITensorMPS -using ITensorNetworks.ITensorsExtensions: replace_vertices using ITensorNetworks: ITensorNetworks, TreeTensorNetwork, siteinds using ITensors.NDTensors: matrix, with_auto_fermion using ITensors: @disable_warn_order, ITensor, Index, combinedind, combiner, contract, dag, diff --git a/test/test_tebd.jl b/test/test_tebd.jl index 17993cb2..8fbfbb1e 100644 --- a/test/test_tebd.jl +++ b/test/test_tebd.jl @@ -1,5 +1,4 @@ using Graphs: vertices -using ITensorNetworks.ITensorsExtensions: group_terms using ITensorNetworks: ITensorNetwork, cartesian_to_linear, dmrg, expect, siteinds, tebd using ITensors: ITensors using NamedGraphs.GraphsExtensions: rename_vertices diff --git a/test/utils.jl b/test/utils.jl index b86432d9..93a701b2 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -5,14 +5,23 @@ using DataGraphs: underlying_graph, vertex_data using Graphs: AbstractGraph, add_edge!, dst, edges, src, vertices -using ITensorNetworks.ITensorsExtensions: trivial_space using ITensorNetworks: @preserve_graph, ITensorNetwork, IndsNetwork, data_graph using ITensors.NDTensors: dim -using ITensors: ITensors, ITensor, Index, dag, itensor, onehot +using ITensors: ITensors, ITensor, Index, QN, dag, hasqns, inds, itensor, onehot using NamedGraphs.GraphsExtensions: incident_edges using NamedGraphs: NamedGraph using Random: Random, AbstractRNG +# Pick a length-1 space matching `tn`'s Index style: a dense `1` if no +# vertex tensor has QN-graded indices, otherwise a single-block `[QN() => 1]`. +# Used by `_add_edge!` below to thread a trivial Link Index between two +# tensors without disturbing the QN structure (real `Index` constructors +# need a space, not just a dimension). +function _trivial_link_space(tn) + any(hasqns ∘ inds, (tn[v] for v in vertices(tn))) || return 1 + return [QN() => 1] +end + # --- random_tensornetwork ---------------------------------------------------- # Core: at each vertex of `graph`, place an `itensor(randn(rng, eltype, ...), inds_v)` @@ -82,7 +91,7 @@ end # `add_edge!` on the underlying graph so the graph-edge ↔ shared-Index # invariant is maintained without relying on auto-reconciliation. function _add_edge!(elt::Type, tn, edge) - iₑ = Index(trivial_space(tn), "Link") + iₑ = Index(_trivial_link_space(tn), "Link") X = ITensors.convert_eltype(elt, onehot(iₑ => 1)) @preserve_graph tn[src(edge)] = tn[src(edge)] * X @preserve_graph tn[dst(edge)] = tn[dst(edge)] * dag(X) @@ -209,3 +218,48 @@ module ModelHamiltonians end end # module ModelHamiltonians + +# --- OpSum helpers ------------------------------------------------------------ + +# Vertex-relabeling for OpSum trees: walks `Sum`/`Prod`/`Scaled`/`Op` nodes and +# applies `f` to every site label inside `Op`. The `Scaled` step also runs the +# coefficient through `maybe_real` to drop spurious complex zero imaginary +# parts. Used by `test_opsum_to_ttn_mpo_cross_check.jl` to compare against +# `ITensorMPS.MPO` by reindexing graph vertices onto an MPS line. +using ITensorNetworks.BaseExtensions: maybe_real, to_tuple +using ITensors.LazyApply: Prod, Scaled, Sum +using ITensors.Ops: Op, Ops + +function replace_vertices(f, ∑o::Sum) + return Sum(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∑o))) +end + +function replace_vertices(f, ∏o::Prod) + return Prod(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∏o))) +end + +function replace_vertices(f, o::Scaled) + return maybe_real(Ops.coefficient(o)) * replace_vertices(f, Ops.argument(o)) +end + +set_sites(o::Op, sites) = Op(Ops.which_op(o), sites...; Ops.params(o)...) + +function replace_vertices(f, o::Op) + return set_sites(o, f.(Ops.sites(o))) +end + +# Bucket `ITensors.terms(ℋ)` by which edge of `g` they sit on. Each term that +# lives on a single edge `e = src => dst` gets summed into one bucket; the +# returned `Sum` carries those per-edge sums in the order `edges(g)` produces +# them. Used by `test_tebd.jl` to pre-group an Ising Hamiltonian before feeding +# it to TEBD, which expects per-edge terms. +using SplitApplyCombine: group + +function group_terms(ℋ::Sum, g) + grouped_terms = group(ITensors.terms(ℋ)) do t + findfirst(edges(g)) do e + return to_tuple.(ITensors.sites(t)) ⊆ [src(e), dst(e)] + end + end + return Sum(collect(sum.(grouped_terms))) +end From 6e451ea610f0ef3709845d1b575879cac8e7834c Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 11 May 2026 19:36:21 -0400 Subject: [PATCH 2/5] Delete src/lib: move remaining helpers to their consumers After this collapse, src/lib/ goes away entirely. Each helper now lives in the single src/ or test/ file that uses it: - `eigendecomp` and `map_eigvals` move from `lib/ITensorsExtensions/` into `src/apply.jl` (only consumer in `src/` was `simple_update_bp`). Both are now module-level `ITensorNetworks` functions; `src/apply.jl` drops the `ITensorsExtensions.` qualifier on its four call sites. - `tensor_sum` moves from `lib/ITensorsExtensions/` into `src/opsum.jl` (only consumer was `ITensors.ITensor(::Sum, ::IndsNetwork)` in the same file). - The `indtype(link_space, site_space)` 4-form, `indtype(space)`, `_indtype` family, and `IsIndexSpace` move from `lib/ITensorsExtensions/` into `src/indsnetwork.jl` (only consumer was the `IndsNetwork{V, I}(g, link_space, site_space)` constructor). - `maybe_real` moves from `lib/BaseExtensions/` into `src/opsum.jl` (consumers were `src/opsum.jl` and `src/apply.jl`; both files reach it as a top-level `ITensorNetworks` function now). `to_tuple` was only used in `test/utils.jl::group_terms` and moves there directly. - `test/test_itensorsextensions.jl` renamed to `test/test_map_eigvals.jl`, imports updated from `ITensorNetworks.ITensorsExtensions: ...` to `ITensorNetworks: ...`. The testset name changes from `"ITensorsExtensions"` to `"map_eigvals"`. - `include("lib/BaseExtensions/...")` removed from `src/ITensorNetworks.jl`; `src/lib/` directory deleted outright. Net effect: drops two submodules' worth of indirection without losing any in-tree behavior. All tests pass. --- src/ITensorNetworks.jl | 2 - src/abstractitensornetwork.jl | 2 - src/apply.jl | 58 ++++++++++++++----- src/indsnetwork.jl | 26 ++++++++- src/lib/BaseExtensions/src/BaseExtensions.jl | 8 --- .../src/ITensorsExtensions.jl | 4 -- src/lib/ITensorsExtensions/src/itensor.jl | 17 ------ .../ITensorsExtensions/src/itensor_more.jl | 44 -------------- src/opsum.jl | 27 ++++++++- ...nsorsextensions.jl => test_map_eigvals.jl} | 4 +- test/utils.jl | 9 ++- 11 files changed, 102 insertions(+), 99 deletions(-) delete mode 100644 src/lib/BaseExtensions/src/BaseExtensions.jl delete mode 100644 src/lib/ITensorsExtensions/src/ITensorsExtensions.jl delete mode 100644 src/lib/ITensorsExtensions/src/itensor.jl delete mode 100644 src/lib/ITensorsExtensions/src/itensor_more.jl rename test/{test_itensorsextensions.jl => test_map_eigvals.jl} (96%) diff --git a/src/ITensorNetworks.jl b/src/ITensorNetworks.jl index 8c7d61ed..f97bd6a6 100644 --- a/src/ITensorNetworks.jl +++ b/src/ITensorNetworks.jl @@ -1,7 +1,5 @@ module ITensorNetworks -include("lib/BaseExtensions/src/BaseExtensions.jl") -include("lib/ITensorsExtensions/src/ITensorsExtensions.jl") include("visualize.jl") include("abstractindsnetwork.jl") include("indextags.jl") diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index 93d27ab9..36971ac6 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -1,4 +1,3 @@ -using .ITensorsExtensions: ITensorsExtensions using Adapt: Adapt, adapt, adapt_structure using DataGraphs: DataGraphs, edge_data, get_vertex_data, is_vertex_assigned, set_vertex_data!, underlying_graph, underlying_graph_type, vertex_data @@ -65,7 +64,6 @@ function ITensors.datatype(tn::AbstractITensorNetwork) return mapreduce(v -> datatype(tn[v]), promote_type, vertices(tn)) end -# TODO: Move to `BaseExtensions` module. function is_setindex!_expr(expr::Expr) return is_assignment_expr(expr) && is_getindex_expr(first(expr.args)) end diff --git a/src/apply.jl b/src/apply.jl index edbd4fca..fdeb974a 100644 --- a/src/apply.jl +++ b/src/apply.jl @@ -1,12 +1,30 @@ -using .BaseExtensions: maybe_real using Graphs: has_edge, ne using ITensors.NDTensors: scalartype using ITensors: ITensors, ITensor, Index, Ops, apply, commonind, commoninds, contract, dag, - denseblocks, factorize, factorize_svd, hascommoninds, hasqns, isdiag, noprime, prime, - replaceind, replaceinds, tags, unioninds, uniqueinds + denseblocks, factorize, factorize_svd, hascommoninds, hasqns, isdiag, map_diag, + noncommonind, noprime, prime, replaceind, replaceinds, tags, unioninds, uniqueinds using LinearAlgebra: eigen, norm, qr, svd using NamedGraphs: NamedEdge +#TODO: Make this work for non-hermitian A +function eigendecomp(A::ITensor, linds, rinds; ishermitian = false, kwargs...) + @assert ishermitian + D, U = eigen(A, linds, rinds; ishermitian, kwargs...) + ul, ur = noncommonind(D, U), commonind(D, U) + Ul = replaceinds(U, vcat(rinds, ur), vcat(linds, ul)) + return Ul, D, dag(U) +end + +# Apply `f` to the eigenvalues of `A` (assumed Hermitian along the +# `Linds | Rinds` bipartition) and rebuild the operator. Used by +# `simple_update_bp` to form `sqrt(env)` and `inv ∘ sqrt(env)` of each +# product-environment factor. +function map_eigvals(f::Function, A::ITensor, Linds, Rinds; kws...) + isdiag(A) && return map_diag(f, A) + Ul, D, Ur = eigendecomp(A, Linds, Rinds; kws...) + return Ul * map_diag(f, D) * Ur +end + # Reduced simple-update on a 2-site gate `o`. Assumes `envs` is a product # environment: each `env` shares indices with exactly one of `ψ[v⃗[1]]` or # `ψ[v⃗[2]]` and is a 2-leg matrix between an index of that tensor and its @@ -21,24 +39,34 @@ function simple_update_bp( envs_v2 = filter(env -> hascommoninds(env, ψ[v⃗[2]]), envs) @assert all(ndims(env) == 2 for env in vcat(envs_v1, envs_v2)) sqrt_envs_v1 = [ - ITensorsExtensions.map_eigvals( - sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true - ) for env in envs_v1 + map_eigvals(sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true) + for env in envs_v1 ] sqrt_envs_v2 = [ - ITensorsExtensions.map_eigvals( - sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true - ) for env in envs_v2 + map_eigvals(sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true) + for env in envs_v2 ] inv_sqrt_envs_v1 = [ - ITensorsExtensions.map_eigvals( - inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true - ) for env in envs_v1 + map_eigvals( + inv ∘ sqrt, + env, + inds(env)[1], + inds(env)[2]; + cutoff, + ishermitian = true + ) + for env in envs_v1 ] inv_sqrt_envs_v2 = [ - ITensorsExtensions.map_eigvals( - inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true - ) for env in envs_v2 + map_eigvals( + inv ∘ sqrt, + env, + inds(env)[1], + inds(env)[2]; + cutoff, + ishermitian = true + ) + for env in envs_v2 ] ψᵥ₁ = contract([ψ[v⃗[1]]; sqrt_envs_v1]) ψᵥ₂ = contract([ψ[v⃗[2]]; sqrt_envs_v2]) diff --git a/src/indsnetwork.jl b/src/indsnetwork.jl index 0c465770..92d1e981 100644 --- a/src/indsnetwork.jl +++ b/src/indsnetwork.jl @@ -1,4 +1,3 @@ -using .ITensorsExtensions: ITensorsExtensions, indtype using DataGraphs: DataGraphs, DataGraph, IsUnderlyingGraph, get_vertex_data, is_vertex_assigned, map_data, set_vertex_data!, underlying_graph_type, vertex_data using Dictionaries: AbstractDictionary, Dictionary, Indices @@ -10,6 +9,31 @@ using NamedGraphs.NamedGraphGenerators: named_path_graph using NamedGraphs: NamedGraphs, AbstractNamedGraph, NamedEdge, NamedGraph using SimpleTraits: SimpleTraits, @traitfn, Not +# Spaces accepted by `Index`: a plain `Integer` dimension (dense) or a QN-block +# vector `Vector{Pair{QN, <:Integer}}` (block-sparse). Used as the dispatch tag +# for `_indtype` so a raw space spec gets mapped to the appropriate `Index{T}`. +const IsIndexSpace = Union{<:Integer, Vector{<:Pair{QN, <:Integer}}} + +# Infer the `Index` type of an `IndsNetwork` from the per-edge / per-vertex +# space specs the user passes to the `IndsNetwork{V, I}(g, link_space, +# site_space)` constructor. Either argument may be `nothing` (defer entirely +# to the other), a space spec, or a container (`AbstractVector` / +# `AbstractDictionary`) of space specs. +indtype(link_space::Nothing, site_space::Nothing) = Index +indtype(link_space::Nothing, site_space) = indtype(site_space) +indtype(link_space, site_space::Nothing) = indtype(link_space) +indtype(link_space, site_space) = promote_type(indtype(link_space), indtype(site_space)) + +indtype(space) = _indtype(typeof(space)) + +# `_indtype` dispatches on the static space type to avoid recursion through +# the user-facing `indtype(space)` entry point. +_indtype(T::Type{<:Index}) = T +_indtype(T::Type{<:IsIndexSpace}) = Index{T} +_indtype(::Type{Nothing}) = Index +_indtype(T::Type{<:AbstractDictionary}) = _indtype(eltype(T)) +_indtype(T::Type{<:AbstractVector}) = _indtype(eltype(T)) + struct IndsNetwork{V, I} <: AbstractIndsNetwork{V, I} data_graph::DataGraph{V, Vector{I}, Vector{I}, NamedGraph{V}, NamedEdge{V}} global function _IndsNetwork(V::Type, I::Type, g::DataGraph) diff --git a/src/lib/BaseExtensions/src/BaseExtensions.jl b/src/lib/BaseExtensions/src/BaseExtensions.jl deleted file mode 100644 index 49ba9110..00000000 --- a/src/lib/BaseExtensions/src/BaseExtensions.jl +++ /dev/null @@ -1,8 +0,0 @@ -module BaseExtensions -# Convert to real if possible -maybe_real(x::Real) = x -maybe_real(x::Complex) = iszero(imag(x)) ? real(x) : x - -to_tuple(x) = (x,) -to_tuple(x::Tuple) = x -end diff --git a/src/lib/ITensorsExtensions/src/ITensorsExtensions.jl b/src/lib/ITensorsExtensions/src/ITensorsExtensions.jl deleted file mode 100644 index d0b5bbbe..00000000 --- a/src/lib/ITensorsExtensions/src/ITensorsExtensions.jl +++ /dev/null @@ -1,4 +0,0 @@ -module ITensorsExtensions -include("itensor.jl") -include("itensor_more.jl") -end diff --git a/src/lib/ITensorsExtensions/src/itensor.jl b/src/lib/ITensorsExtensions/src/itensor.jl deleted file mode 100644 index b4091473..00000000 --- a/src/lib/ITensorsExtensions/src/itensor.jl +++ /dev/null @@ -1,17 +0,0 @@ -using ITensors: ITensor, commonind, dag, isdiag, map_diag, noncommonind, replaceinds -using LinearAlgebra: eigen - -#TODO: Make this work for non-hermitian A -function eigendecomp(A::ITensor, linds, rinds; ishermitian = false, kwargs...) - @assert ishermitian - D, U = eigen(A, linds, rinds; ishermitian, kwargs...) - ul, ur = noncommonind(D, U), commonind(D, U) - Ul = replaceinds(U, vcat(rinds, ur), vcat(linds, ul)) - return Ul, D, dag(U) -end - -function map_eigvals(f::Function, A::ITensor, Linds, Rinds; kws...) - isdiag(A) && return map_diag(f, A) - Ul, D, Ur = eigendecomp(A, Linds, Rinds; kws...) - return Ul * map_diag(f, D) * Ur -end diff --git a/src/lib/ITensorsExtensions/src/itensor_more.jl b/src/lib/ITensorsExtensions/src/itensor_more.jl deleted file mode 100644 index e5b37542..00000000 --- a/src/lib/ITensorsExtensions/src/itensor_more.jl +++ /dev/null @@ -1,44 +0,0 @@ -using Dictionaries: AbstractDictionary -using ITensors: ITensor, Index, QN, filterinds, op, uniqueinds - -# Tensor sum: `A ⊞ B = A ⊗ Iᴮ + Iᴬ ⊗ B` -# https://github.com/JuliaLang/julia/issues/13333#issuecomment-143825995 -# "PRESERVATION OF TENSOR SUM AND TENSOR PRODUCT" -# C. S. KUBRUSLY and N. LEVAN -# https://www.emis.de/journals/AMUC/_vol-80/_no_1/_kubrusly/kubrusly.pdf -function tensor_sum(A::ITensor, B::ITensor) - extend_A = filterinds(uniqueinds(B, A); plev = 0) - extend_B = filterinds(uniqueinds(A, B); plev = 0) - for i in extend_A - A *= op("I", i) - end - for i in extend_B - B *= op("I", i) - end - return A + B -end - -# Spaces accepted by `Index`: a plain `Integer` dimension (dense) or a QN-block -# vector `Vector{Pair{QN, <:Integer}}` (block-sparse). Used as the dispatch tag -# for `_indtype` so a raw space spec gets mapped to the appropriate `Index{T}`. -const IsIndexSpace = Union{<:Integer, Vector{<:Pair{QN, <:Integer}}} - -# Infer the `Index` type of an `IndsNetwork` from the per-edge / per-vertex -# space specs the user passes to the `IndsNetwork{V, I}(g, link_space, -# site_space)` constructor. Either argument may be `nothing` (defer entirely -# to the other), a space spec, or a container (`AbstractVector` / -# `AbstractDictionary`) of space specs. -indtype(link_space::Nothing, site_space::Nothing) = Index -indtype(link_space::Nothing, site_space) = indtype(site_space) -indtype(link_space, site_space::Nothing) = indtype(link_space) -indtype(link_space, site_space) = promote_type(indtype(link_space), indtype(site_space)) - -indtype(space) = _indtype(typeof(space)) - -# `_indtype` dispatches on the static space type to avoid recursion through -# the user-facing `indtype(space)` entry point. -_indtype(T::Type{<:Index}) = T -_indtype(T::Type{<:IsIndexSpace}) = Index{T} -_indtype(::Type{Nothing}) = Index -_indtype(T::Type{<:AbstractDictionary}) = _indtype(eltype(T)) -_indtype(T::Type{<:AbstractVector}) = _indtype(eltype(T)) diff --git a/src/opsum.jl b/src/opsum.jl index 7e4bb139..2e5a20a5 100644 --- a/src/opsum.jl +++ b/src/opsum.jl @@ -1,8 +1,29 @@ -using .BaseExtensions: maybe_real -using .ITensorsExtensions: tensor_sum using ITensors.LazyApply: Applied, Prod, Scaled, Sum using ITensors.Ops: Op, Ops -using ITensors: ITensor, hascommoninds, op +using ITensors: ITensor, filterinds, hascommoninds, op, uniqueinds + +# Drop a spurious zero imaginary part on a complex scalar so that downstream +# scalar promotions stay real. Used when materializing `Scaled` `OpSum` terms +# and when applying scalar-multiplied gates in `apply.jl`. +maybe_real(x::Real) = x +maybe_real(x::Complex) = iszero(imag(x)) ? real(x) : x + +# Tensor sum: `A ⊞ B = A ⊗ Iᴮ + Iᴬ ⊗ B` +# https://github.com/JuliaLang/julia/issues/13333#issuecomment-143825995 +# "PRESERVATION OF TENSOR SUM AND TENSOR PRODUCT" +# C. S. KUBRUSLY and N. LEVAN +# https://www.emis.de/journals/AMUC/_vol-80/_no_1/_kubrusly/kubrusly.pdf +function tensor_sum(A::ITensor, B::ITensor) + extend_A = filterinds(uniqueinds(B, A); plev = 0) + extend_B = filterinds(uniqueinds(A, B); plev = 0) + for i in extend_A + A *= op("I", i) + end + for i in extend_B + B *= op("I", i) + end + return A + B +end function ITensors.ITensor(o::Op, s::IndsNetwork) s⃗ = [only(s[nᵢ]) for nᵢ in Ops.sites(o)] diff --git a/test/test_itensorsextensions.jl b/test/test_map_eigvals.jl similarity index 96% rename from test/test_itensorsextensions.jl rename to test/test_map_eigvals.jl index e273f14b..b20daab6 100644 --- a/test/test_itensorsextensions.jl +++ b/test/test_map_eigvals.jl @@ -1,10 +1,10 @@ -using ITensorNetworks.ITensorsExtensions: eigendecomp, map_eigvals +using ITensorNetworks: eigendecomp, map_eigvals using ITensors.NDTensors: with_auto_fermion using ITensors: ITensor, Index, QN, apply, dag, delta, inds, mapprime, noprime, norm, op, permute, prime, random_itensor, replaceind, replaceinds, sim, swapprime using StableRNGs: StableRNG using Test: @test, @testset -@testset "ITensorsExtensions" begin +@testset "map_eigvals" begin @testset "Test map eigvals without QNS (eltype=$elt, dim=$n)" for elt in ( Float32, Float64, Complex{Float32}, Complex{Float64}, ), diff --git a/test/utils.jl b/test/utils.jl index 93a701b2..b42f5ba6 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -226,10 +226,17 @@ end # module ModelHamiltonians # coefficient through `maybe_real` to drop spurious complex zero imaginary # parts. Used by `test_opsum_to_ttn_mpo_cross_check.jl` to compare against # `ITensorMPS.MPO` by reindexing graph vertices onto an MPS line. -using ITensorNetworks.BaseExtensions: maybe_real, to_tuple +using ITensorNetworks: maybe_real using ITensors.LazyApply: Prod, Scaled, Sum using ITensors.Ops: Op, Ops +# Promote a scalar / tuple-of-scalars into tuple form. Used below by +# `group_terms` to compare each `OpSum` term's site list against `[src(e), +# dst(e)]` uniformly, regardless of whether sites are bare scalars or already +# tuples (NamedGraph vertex labels can be either). +to_tuple(x) = (x,) +to_tuple(x::Tuple) = x + function replace_vertices(f, ∑o::Sum) return Sum(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∑o))) end From 0fe85c73c0c3600da05a15593130db7b66467ab6 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 11 May 2026 19:49:00 -0400 Subject: [PATCH 3/5] Drop dead apply(::Op|::Prod|::Scaled, ::AbstractITensorNetwork) chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These three methods in `src/apply.jl` had no callers anywhere — TEBD calls `apply(::Vector{ITensor}, ψ)` after materializing the Trotter product into ITensors via `Vector{ITensor}(::Prod, ::IndsNetwork)`, not `apply(::Op|::Prod|::Scaled, ψ)`. The rest of the OpSum→ITensor chain in `src/opsum.jl` (including `tensor_sum` and `maybe_real`) is still alive through that materialization step and stays. --- src/apply.jl | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/apply.jl b/src/apply.jl index fdeb974a..928c9574 100644 --- a/src/apply.jl +++ b/src/apply.jl @@ -1,6 +1,6 @@ using Graphs: has_edge, ne using ITensors.NDTensors: scalartype -using ITensors: ITensors, ITensor, Index, Ops, apply, commonind, commoninds, contract, dag, +using ITensors: ITensors, ITensor, Index, apply, commonind, commoninds, contract, dag, denseblocks, factorize, factorize_svd, hascommoninds, hasqns, isdiag, map_diag, noncommonind, noprime, prime, replaceind, replaceinds, tags, unioninds, uniqueinds using LinearAlgebra: eigen, norm, qr, svd @@ -159,31 +159,3 @@ function ITensors.apply( end return o⃗ψ end - -function ITensors.apply( - o⃗::Scaled, - ψ::AbstractITensorNetwork; - cutoff = nothing, - normalize = false, - ortho = false, - apply_kwargs... - ) - return maybe_real(Ops.coefficient(o⃗)) * - apply(Ops.argument(o⃗), ψ; cutoff, maxdim, normalize, ortho, apply_kwargs...) -end - -function ITensors.apply( - o⃗::Prod, ψ::AbstractITensorNetwork; normalize = false, ortho = false, apply_kwargs... - ) - o⃗ψ = ψ - for oᵢ in o⃗ - o⃗ψ = apply(oᵢ, o⃗ψ; normalize, ortho, apply_kwargs...) - end - return o⃗ψ -end - -function ITensors.apply( - o::Op, ψ::AbstractITensorNetwork; normalize = false, ortho = false, apply_kwargs... - ) - return apply(ITensor(o, siteinds(ψ)), ψ; normalize, ortho, apply_kwargs...) -end From 11fba3055ea510fdf6f3ac4560973354665c450f Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 11 May 2026 20:07:37 -0400 Subject: [PATCH 4/5] Drop unused deps, dead linkinds wrapper, NamedEdge apply arm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three follow-up cleanups beyond the `src/lib/` collapse: - Remove 8 `Project.toml` deps that have zero references anywhere in `src/`, `ext/`, or `docs/`: `AbstractTrees`, `Combinatorics`, `Compat`, `ConstructionBase`, `IterTools`, `SerializedElementArrays`, `Suppressor`, `TimerOutputs`. Tests aren't using them either — `Compat`'s test usage stays in `test/Project.toml`. - Delete `src/partitioneditensornetwork.jl` (10-line file whose sole contents were a `linkinds(::PartitionedGraph, ::QuotientEdge)` wrapper used only by `linkinds(::AbstractBeliefPropagationCache, ::QuotientEdge)`). Inline the 3-line body into the BPC method. - Narrow `apply(::Union{NamedEdge, ITensor}, ::AbstractITensorNetwork)`, `apply(::Union{Vector{NamedEdge}, Vector{ITensor}}, ::TN)`, and the internal `simple_update_bp(::Union{NamedEdge, ITensor}, ...)` to just `::ITensor` / `::Vector{ITensor}`. No callers anywhere exercised the `NamedEdge` arm. Doc updates: - Drop the `partitioneditensornetwork.jl` entry from `docs/src/deprecated_methods.md`. - Narrow the `apply` signature listing in `docs/src/interface_methods.md` to match. --- Project.toml | 16 ---------------- docs/src/deprecated_methods.md | 12 ------------ docs/src/interface_methods.md | 9 +++------ src/ITensorNetworks.jl | 1 - src/apply.jl | 7 +++---- src/caches/abstractbeliefpropagationcache.jl | 13 ++++++++----- src/partitioneditensornetwork.jl | 10 ---------- 7 files changed, 14 insertions(+), 54 deletions(-) delete mode 100644 src/partitioneditensornetwork.jl diff --git a/Project.toml b/Project.toml index 7a5bc756..c352657b 100644 --- a/Project.toml +++ b/Project.toml @@ -7,19 +7,14 @@ authors = ["Matthew Fishman , Joseph Tindall hascommoninds(env, ψ[v⃗[1]]), envs) @@ -96,7 +95,7 @@ function simple_update_bp( end function ITensors.apply( - o::Union{NamedEdge, ITensor}, + o::ITensor, ψ::AbstractITensorNetwork; envs = ITensor[], normalize = false, @@ -147,7 +146,7 @@ function ITensors.apply( end function ITensors.apply( - o⃗::Union{Vector{NamedEdge}, Vector{ITensor}}, + o⃗::Vector{ITensor}, ψ::AbstractITensorNetwork; normalize = false, ortho = false, diff --git a/src/caches/abstractbeliefpropagationcache.jl b/src/caches/abstractbeliefpropagationcache.jl index 6f5dc05f..79c6ef5e 100644 --- a/src/caches/abstractbeliefpropagationcache.jl +++ b/src/caches/abstractbeliefpropagationcache.jl @@ -1,10 +1,12 @@ using Adapt: Adapt, adapt, adapt_structure -using Graphs: Graphs, IsDirected -using ITensors: delta, dir +using Graphs: Graphs, IsDirected, dst, src +using ITensors: commoninds, delta, dir using LinearAlgebra: diag, dot using NDTensors: NDTensors -using NamedGraphs.PartitionedGraphs: PartitionedGraph, PartitionedGraphs, QuotientVertex, - boundary_quotientedges, quotientedges, quotientvertices, unpartitioned_graph +using NamedGraphs.GraphsExtensions: subgraph +using NamedGraphs.PartitionedGraphs: PartitionedGraph, PartitionedGraphs, QuotientEdge, + QuotientVertex, boundary_quotientedges, quotientedges, quotientvertices, + unpartitioned_graph using NamedGraphs.SimilarType: SimilarType using NamedGraphs: to_graph_index using SimpleTraits: SimpleTraits, @traitfn, Not @@ -161,7 +163,8 @@ function PartitionedGraphs.quotientedge( end function linkinds(bpc::AbstractBeliefPropagationCache, pe::QuotientEdge) - return linkinds(partitioned_tensornetwork(bpc), pe) + pitn = partitioned_tensornetwork(bpc) + return commoninds(subgraph(pitn, src(pe)), subgraph(pitn, dst(pe))) end NDTensors.scalartype(bpc::AbstractBeliefPropagationCache) = scalartype(tensornetwork(bpc)) diff --git a/src/partitioneditensornetwork.jl b/src/partitioneditensornetwork.jl deleted file mode 100644 index a559bcc6..00000000 --- a/src/partitioneditensornetwork.jl +++ /dev/null @@ -1,10 +0,0 @@ -using Graphs: dst, src -using ITensors: commoninds -using NamedGraphs.GraphsExtensions: subgraph -using NamedGraphs.PartitionedGraphs: PartitionedGraph, QuotientEdge - -function linkinds(pitn::PartitionedGraph, edge::QuotientEdge) - src_e_itn = subgraph(pitn, src(edge)) - dst_e_itn = subgraph(pitn, dst(edge)) - return commoninds(src_e_itn, dst_e_itn) -end From 22fddabc21fdf6c025add2f55184d2d27375c316 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 11 May 2026 20:13:21 -0400 Subject: [PATCH 5/5] Drop unused AbstractTrees from test/Project.toml Zero references in test/*.jl; was carried over alongside the same dep in the main Project.toml. --- test/Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/Project.toml b/test/Project.toml index 11505d90..5fe65649 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,4 @@ [deps] -AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" @@ -33,7 +32,6 @@ UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" path = ".." [compat] -AbstractTrees = "0.4.5" Adapt = "4.3" Aqua = "0.8.11" Compat = "4.16"