From 6e61b01d6f9c81484440522cb1b1f19811911db4 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 22 Jun 2026 15:13:07 -0400 Subject: [PATCH] Replace BackendSelection with dedicated contraction algorithm types ## Summary Replaces the string-based `BackendSelection.Algorithm"..."` selection for tensor-network contraction with dedicated algorithm types, and drops `BackendSelection`, `TypeParameterAccessors`, and `FunctionImplementations` as dependencies. Contraction order is now chosen with types instead of `Algorithm` strings: `Greedy` and `Optimal` (the `optimize_contraction_order` strategies, with `Optimal` provided by the TensorOperations extension), `Flat` and `LeftAssociative` (the `contraction_order` strategies), and `Exact` (the `contract_network` algorithm, carrying its `order`/`order_alg` as fields rather than as `Algorithm` parameters). `Greedy` replaces the old `"eager"` name, the standard term for the cost-minimizing pairwise heuristic. `TypeParameterAccessors` was unused. The lone `FunctionImplementations.zero!` call becomes `fill!(a, zero(eltype(a)))`. The structure-aware `zero!` it relied on is worth keeping as a shared overloadable primitive, but where that should live is a separate question, and the dense path `delta` actually uses is unaffected. Also un-breaks the `show`/`printnode`/`print_tree` tests for the lazy and symbolic tensors: the rendered output was already correct, the expected strings just predated ITensorBase's index-name display. This sets up moving the `LazyITensors` submodule into ITensorBase, which its `BackendSelection` use had stood in the way of. --- Project.toml | 8 +-- docs/Project.toml | 2 +- examples/Project.toml | 2 +- .../ITensorNetworksNextTensorOperationsExt.jl | 6 +- src/LazyITensors/evaluation_order.jl | 10 ++- src/TensorNetworkGenerators/delta_network.jl | 3 +- src/abstracttensornetwork.jl | 1 - src/beliefpropagation/beliefpropagation.jl | 5 +- src/contract_network.jl | 61 ++++++++----------- test/Project.toml | 4 +- test/test_contract_network.jl | 19 +++--- test/test_lazyitensors.jl | 21 +++---- 12 files changed, 62 insertions(+), 80 deletions(-) diff --git a/Project.toml b/Project.toml index cced775..17e6934 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorNetworksNext" uuid = "302f2e75-49f0-4526-aef7-d8ba550cb06c" -version = "0.5.0" +version = "0.6.0" authors = ["ITensor developers and contributors"] [workspace] @@ -10,11 +10,9 @@ projects = ["benchmark", "dev", "docs", "examples", "test"] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" AlgorithmsInterface = "d1e3940c-cd12-4505-8585-b0a4b322527d" -BackendSelection = "680c2d7c-f67a-4cc9-ae9c-da132b1447a5" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataGraphs = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" -FunctionImplementations = "7c7cc465-9c6a-495f-bdd1-f42428e86d0c" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" ITensorBase = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -26,7 +24,6 @@ SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" TensorAlgebra = "68bd88dc-f39d-4e12-b2ca-f046b68fcc6a" TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" -TypeParameterAccessors = "7e5a90cf-f82e-492e-a09b-e3e26432c138" WrappedUnions = "325db55a-9c6c-5b90-b1a2-ec87e7a38c44" [weakdeps] @@ -39,11 +36,9 @@ ITensorNetworksNextTensorOperationsExt = "TensorOperations" AbstractTrees = "0.4.5" Adapt = "4.3" AlgorithmsInterface = "0.1" -BackendSelection = "0.1.6" Combinatorics = "1" DataGraphs = "0.4" Dictionaries = "0.4.5" -FunctionImplementations = "0.4.1" Graphs = "1.13.1" ITensorBase = "0.6.2" LinearAlgebra = "1.10" @@ -56,6 +51,5 @@ SplitApplyCombine = "1.2.3" TensorAlgebra = "0.9.7" TensorOperations = "5.3.1" TermInterface = "2" -TypeParameterAccessors = "0.4.4" WrappedUnions = "0.3" julia = "1.10" diff --git a/docs/Project.toml b/docs/Project.toml index 3480d7c..1d28200 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -10,5 +10,5 @@ path = ".." [compat] Documenter = "1" ITensorFormatter = "0.2.27" -ITensorNetworksNext = "0.5" +ITensorNetworksNext = "0.6" Literate = "2" diff --git a/examples/Project.toml b/examples/Project.toml index dd00a07..9a19828 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -5,4 +5,4 @@ ITensorNetworksNext = "302f2e75-49f0-4526-aef7-d8ba550cb06c" path = ".." [compat] -ITensorNetworksNext = "0.5" +ITensorNetworksNext = "0.6" diff --git a/ext/ITensorNetworksNextTensorOperationsExt/ITensorNetworksNextTensorOperationsExt.jl b/ext/ITensorNetworksNextTensorOperationsExt/ITensorNetworksNextTensorOperationsExt.jl index a8dad8b..6d5e4f3 100644 --- a/ext/ITensorNetworksNextTensorOperationsExt/ITensorNetworksNextTensorOperationsExt.jl +++ b/ext/ITensorNetworksNextTensorOperationsExt/ITensorNetworksNextTensorOperationsExt.jl @@ -1,9 +1,9 @@ module ITensorNetworksNextTensorOperationsExt -using BackendSelection: @Algorithm_str, Algorithm using ITensorBase: denamed, inds using ITensorNetworksNext.LazyITensors.TermInterface: arguments -using ITensorNetworksNext.LazyITensors: LazyITensors, ismul, substitute, symnameddims +using ITensorNetworksNext.LazyITensors: + LazyITensors, Optimal, ismul, substitute, symnameddims using TensorOperations: TensorOperations, optimaltree function contraction_tree_to_expr(f, tree) @@ -14,7 +14,7 @@ function contraction_tree_to_expr(f, tree) end end -function LazyITensors.optimize_contraction_order(alg::Algorithm"optimal", a) +function LazyITensors.optimize_contraction_order(alg::Optimal, a) @assert ismul(a) ts = arguments(a) inds_network = collect.(inds.(ts)) diff --git a/src/LazyITensors/evaluation_order.jl b/src/LazyITensors/evaluation_order.jl index 5491cbe..1ff8111 100644 --- a/src/LazyITensors/evaluation_order.jl +++ b/src/LazyITensors/evaluation_order.jl @@ -85,15 +85,19 @@ function optimize_evaluation_order( return optimize_evaluation_order(alg, a) end -using BackendSelection: @Algorithm_str, Algorithm -default_optimize_evaluation_order_alg(a) = Algorithm"eager"() +abstract type EvaluationOrderAlgorithm end +struct Greedy <: EvaluationOrderAlgorithm end +# `Optimal` finds the cost-optimal contraction order. The method is provided by +# the TensorOperations extension. +struct Optimal <: EvaluationOrderAlgorithm end +default_optimize_evaluation_order_alg(a) = Greedy() function optimize_contraction_order(alg, a) return error("`alg = $alg` not supported.") end using Combinatorics: combinations -function optimize_contraction_order(alg::Algorithm"eager", a) +function optimize_contraction_order(alg::Greedy, a) @assert ismul(a) arity(a) in (1, 2) && return a a1, a2 = argmin(combinations(arguments(a), 2)) do (a1, a2) diff --git a/src/TensorNetworkGenerators/delta_network.jl b/src/TensorNetworkGenerators/delta_network.jl index c341e07..3af2912 100644 --- a/src/TensorNetworkGenerators/delta_network.jl +++ b/src/TensorNetworkGenerators/delta_network.jl @@ -1,5 +1,4 @@ using ..ITensorNetworksNext: TensorNetwork -using FunctionImplementations: zero! using Graphs: AbstractGraph using ITensorBase: NamedUnitRange, denamed, name, nameddims using NamedGraphs.GraphsExtensions: incident_edges @@ -22,7 +21,7 @@ diagview(a::AbstractArray) = @view a[diagindices(a)] function diagonaltensor(diag::AbstractVector, ax::Tuple{Vararg{AbstractUnitRange}}) a = similar(diag, ax) - zero!(a) + fill!(a, zero(eltype(a))) diagview(a) .= diag return a end diff --git a/src/abstracttensornetwork.jl b/src/abstracttensornetwork.jl index d8ff92f..809f9e4 100644 --- a/src/abstracttensornetwork.jl +++ b/src/abstracttensornetwork.jl @@ -1,5 +1,4 @@ using Adapt: Adapt, adapt -using BackendSelection: @Algorithm_str, Algorithm using DataGraphs: DataGraphs, AbstractDataGraph, edge_data, set_vertex_data!, underlying_graph, underlying_graph_type, vertex_data using Dictionaries: Dictionary diff --git a/src/beliefpropagation/beliefpropagation.jl b/src/beliefpropagation/beliefpropagation.jl index 7ad0eaf..b936035 100644 --- a/src/beliefpropagation/beliefpropagation.jl +++ b/src/beliefpropagation/beliefpropagation.jl @@ -1,7 +1,6 @@ using .AlgorithmsInterfaceExtensions: AlgorithmsInterfaceExtensions as AIE, StopWhenConverged, iterate_diff using AlgorithmsInterface: AlgorithmsInterface as AI -using BackendSelection: @Algorithm_str, Algorithm using DataGraphs: edge_data using Graphs: AbstractEdge, edges, edgetype, has_edge, vertices using ITensorBase: AbstractITensor @@ -236,14 +235,14 @@ end @kwdef struct SimpleMessageUpdate{ContractionAlg} <: MessageUpdateAlgorithm normalize::Bool = true - contraction_alg::ContractionAlg = Algorithm"exact" + contraction_alg::ContractionAlg = Exact() end function message_update!(algorithm::SimpleMessageUpdate, cache, factors, edge) messages = collect(incoming_messages(cache, edge)) factor = factors[src(edge)] - new_message = contract_network([messages; [factor]]; algorithm.contraction_alg) + new_message = contract_network([messages; [factor]]; alg = algorithm.contraction_alg) if algorithm.normalize message_norm = sum(new_message) diff --git a/src/contract_network.jl b/src/contract_network.jl index 1624e76..cdacc97 100644 --- a/src/contract_network.jl +++ b/src/contract_network.jl @@ -1,47 +1,35 @@ -using BackendSelection: @Algorithm_str, Algorithm using Base.Broadcast: materialize -using ITensorNetworksNext.LazyITensors: - Mul, lazy, optimize_evaluation_order, substitute, symnameddims +using Base: @kwdef +using ITensorNetworksNext.LazyITensors: EvaluationOrderAlgorithm, Greedy, Mul, lazy, + optimize_evaluation_order, substitute, symnameddims -# This is related to `MatrixAlgebraKit.select_algorithm`. -# TODO: Define this in BackendSelection.jl. -backend_value(::Algorithm{alg}) where {alg} = alg -using BackendSelection: parameters -function merge_parameters(alg::Algorithm; kwargs...) - return Algorithm(backend_value(alg); merge(parameters(alg), kwargs)...) +# `contract_network` +@kwdef struct Exact{Order, OrderAlg} + order::Order = nothing + order_alg::OrderAlg = Greedy() end -to_algorithm(alg::Algorithm; kwargs...) = merge_parameters(alg; kwargs...) -to_algorithm(alg; kwargs...) = Algorithm(alg; kwargs...) -# `contract_network` -function contract_network(alg::Algorithm, tn) +function contract_network(alg, tn) return throw(ArgumentError("`contract_network` algorithm `$(alg)` not implemented.")) end -function default_kwargs(::typeof(contract_network), tn) - return (; alg = Algorithm"exact"(; order_alg = Algorithm"eager"())) -end -function contract_network(tn; alg = default_kwargs(contract_network, tn).alg, kwargs...) - return contract_network(to_algorithm(alg; kwargs...), tn) +function contract_network(tn; alg = Exact()) + return contract_network(alg, tn) end -# `contract_network(::Algorithm"exact", ...)` -function get_order(alg::Algorithm"exact", tn) - # Allow specifying either `order` or `order_alg`. - order = get(alg, :order, nothing) - order = if !isnothing(order) - order +# `contract_network(::Exact, ...)` +function get_order(alg::Exact, tn) + # Allow specifying either an explicit `order` or an `order_alg` to compute one. + order = if !isnothing(alg.order) + alg.order else - default_order_alg = default_kwargs(contraction_order, tn).alg - order_alg = get(alg, :order_alg, default_order_alg) - # TODO: Capture other keyword arguments and pass them to `contraction_order`. - contraction_order(tn; alg = order_alg) + contraction_order(tn; alg = alg.order_alg) end # Contraction order may or may not have indices attached, canonicalize the format # by attaching indices. subs = Dict(symnameddims(i) => symnameddims(i, Tuple(axes(tn[i]))) for i in keys(tn)) return substitute(order, subs) end -function contract_network(alg::Algorithm"exact", tn) +function contract_network(alg::Exact, tn) order = get_order(alg, tn) syms_to_ts = Dict(symnameddims(i, Tuple(axes(tn[i]))) => lazy(tn[i]) for i in keys(tn)) tn_expression = substitute(order, syms_to_ts) @@ -50,20 +38,21 @@ end # `contraction_order` function contraction_order end -default_kwargs(::typeof(contraction_order), tn) = (; alg = Algorithm"eager"()) -function contraction_order(tn; alg = default_kwargs(contraction_order, tn).alg, kwargs...) - return contraction_order(to_algorithm(alg; kwargs...), tn) +function contraction_order(tn; alg = Greedy()) + return contraction_order(alg, tn) end # Convert the tensor network to a flat symbolic multiplication expression. -function contraction_order(alg::Algorithm"flat", tn) +struct Flat end +function contraction_order(alg::Flat, tn) # Same as: `reduce((a, b) -> *(a, b; flatten = true), syms)`. syms = vec([symnameddims(i, Tuple(axes(tn[i]))) for i in keys(tn)]) return lazy(Mul(syms)) end -function contraction_order(alg::Algorithm"left_associative", tn) +struct LeftAssociative end +function contraction_order(alg::LeftAssociative, tn) return prod(i -> symnameddims(i, Tuple(axes(tn[i]))), keys(tn)) end -function contraction_order(alg::Algorithm, tn) - s = contraction_order(Algorithm"flat"(), tn) +function contraction_order(alg::EvaluationOrderAlgorithm, tn) + s = contraction_order(Flat(), tn) return optimize_evaluation_order(s; alg) end diff --git a/test/Project.toml b/test/Project.toml index 17113bd..06fd820 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -2,7 +2,6 @@ AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" AlgorithmsInterface = "d1e3940c-cd12-4505-8585-b0a4b322527d" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -BackendSelection = "680c2d7c-f67a-4cc9-ae9c-da132b1447a5" DataGraphs = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" GradedArrays = "bc96ca6e-b7c8-4bb6-888e-c93f838762c2" @@ -31,13 +30,12 @@ path = ".." AbstractTrees = "0.4.5" AlgorithmsInterface = "0.1" Aqua = "0.8.14" -BackendSelection = "0.1" DataGraphs = "0.4" Dictionaries = "0.4.5" GradedArrays = "0.9.4" Graphs = "1.13.1" ITensorBase = "0.6.2" -ITensorNetworksNext = "0.5" +ITensorNetworksNext = "0.6" ITensorPkgSkeleton = "0.3.42" MatrixAlgebraKit = "0.6" NamedGraphs = "0.11.5" diff --git a/test/test_contract_network.jl b/test/test_contract_network.jl index b453e76..d8ee2d9 100644 --- a/test/test_contract_network.jl +++ b/test/test_contract_network.jl @@ -1,14 +1,15 @@ -using BackendSelection: @Algorithm_str, Algorithm using Graphs: edges using ITensorBase: Index -using ITensorNetworksNext: TensorNetwork, contract_network, linkinds, siteinds +using ITensorNetworksNext.LazyITensors: Greedy, Optimal +using ITensorNetworksNext: + Exact, LeftAssociative, TensorNetwork, contract_network, linkinds, siteinds using NamedGraphs.GraphsExtensions: arranged_edges, incident_edges using NamedGraphs.NamedGraphGenerators: named_grid using TensorOperations: TensorOperations using Test: @test, @testset @testset "contract_network" begin - orderalg = alg -> Algorithm"exact"(; order_alg = Algorithm(alg)) + orderalg = order_alg -> Exact(; order_alg) @testset "Contract Vectors of ITensors" begin i, j, k = Index(2), Index(2), Index(5) @@ -17,9 +18,9 @@ using Test: @test, @testset C = [5.0, 1.0][j] D = [-2.0, 3.0, 4.0, 5.0, 1.0][k] - ABCD_1 = contract_network([A, B, C, D]; alg = orderalg("left_associative")) - ABCD_2 = contract_network([A, B, C, D]; alg = orderalg("eager")) - ABCD_3 = contract_network([A, B, C, D]; alg = orderalg("optimal")) + ABCD_1 = contract_network([A, B, C, D]; alg = orderalg(LeftAssociative())) + ABCD_2 = contract_network([A, B, C, D]; alg = orderalg(Greedy())) + ABCD_3 = contract_network([A, B, C, D]; alg = orderalg(Optimal())) @test ABCD_1 == ABCD_2 == ABCD_3 end @@ -33,9 +34,9 @@ using Test: @test, @testset return randn(Tuple(is)) end - z1 = contract_network(tn; alg = orderalg("left_associative"))[] - z2 = contract_network(tn; alg = orderalg("eager"))[] - z3 = contract_network(tn; alg = orderalg("optimal"))[] + z1 = contract_network(tn; alg = orderalg(LeftAssociative()))[] + z2 = contract_network(tn; alg = orderalg(Greedy()))[] + z3 = contract_network(tn; alg = orderalg(Optimal()))[] @test abs(z1 - z2) / abs(z1) <= 1.0e3 * eps(Float64) @test abs(z1 - z3) / abs(z1) <= 1.0e3 * eps(Float64) diff --git a/test/test_lazyitensors.jl b/test/test_lazyitensors.jl index 06ac63c..ec29899 100644 --- a/test/test_lazyitensors.jl +++ b/test/test_lazyitensors.jl @@ -52,9 +52,9 @@ using WrappedUnions: unwrap @test AbstractTrees.children(l1) ≡ () @test AbstractTrees.nodevalue(l1) ≡ a1 @test sprint(show, l1) == sprint(show, a1) - # Show-string format depends on how `Index` names are displayed; not load-bearing. - @test_broken sprint(printnode, l1) == "[:i, :j]" - @test_broken sprint(print_tree, l1) == "[:i, :j]\n" + # The leaf format mirrors ITensorBase's display of a tensor's index names. + @test sprint(printnode, l1) == "{\"i\", \"j\"}" + @test sprint(print_tree, l1) == "{\"i\", \"j\"}\n" l = l1 * l2 * l3 @test arguments(l) == [l1 * l2, l3] @@ -69,14 +69,13 @@ using WrappedUnions: unwrap @test sorted_children(l) == [l1 * l2, l3] @test AbstractTrees.children(l) == [l1 * l2, l3] @test AbstractTrees.nodevalue(l) ≡ * - # Show-string format depends on how `Index` names are displayed; not load-bearing. - @test_broken sprint(show, l) == "(([:i, :j] * [:j, :k]) * [:k, :l])" - @test_broken sprint(printnode, l) == "(([:i, :j] * [:j, :k]) * [:k, :l])" - @test_broken sprint(print_tree, l) == - "(([:i, :j] * [:j, :k]) * [:k, :l])\n" * - "├─ ([:i, :j] * [:j, :k])\n" * - "│ ├─ [:i, :j]\n│ └─ [:j, :k]\n" * - "└─ [:k, :l]\n" + @test sprint(show, l) == "(({\"i\", \"j\"} * {\"j\", \"k\"}) * {\"k\", \"l\"})" + @test sprint(printnode, l) == "(({\"i\", \"j\"} * {\"j\", \"k\"}) * {\"k\", \"l\"})" + @test sprint(print_tree, l) == + "(({\"i\", \"j\"} * {\"j\", \"k\"}) * {\"k\", \"l\"})\n" * + "├─ ({\"i\", \"j\"} * {\"j\", \"k\"})\n" * + "│ ├─ {\"i\", \"j\"}\n│ └─ {\"j\", \"k\"}\n" * + "└─ {\"k\", \"l\"}\n" end @testset "symnameddims" begin