From 0bef4a9e21c0a74f8bb1d4d331cfc8407dc9a0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Tue, 29 Oct 2024 10:28:34 +0100 Subject: [PATCH] Delete `Chain`, `Dense`, `Grid`, `Product` types --- ext/TenetQuacExt.jl | 12 +- ext/TenetReactantExt.jl | 20 +- src/Ansatz/Chain.jl | 749 ----------------------------- src/Ansatz/Dense.jl | 40 -- src/Ansatz/Grid.jl | 180 ------- src/Ansatz/Product.jl | 85 ---- test/Chain_test.jl | 436 ----------------- test/Product_test.jl | 58 +-- test/integration/YaoBlocks_test.jl | 44 +- test/runtests.jl | 3 +- 10 files changed, 68 insertions(+), 1559 deletions(-) delete mode 100644 src/Ansatz/Chain.jl delete mode 100644 src/Ansatz/Dense.jl delete mode 100644 src/Ansatz/Grid.jl delete mode 100644 src/Ansatz/Product.jl delete mode 100644 test/Chain_test.jl diff --git a/ext/TenetQuacExt.jl b/ext/TenetQuacExt.jl index 807fb040..15f6141a 100644 --- a/ext/TenetQuacExt.jl +++ b/ext/TenetQuacExt.jl @@ -3,13 +3,13 @@ module TenetQuacExt using Tenet using Quac: Gate, Circuit, lanes, arraytype, Swap -function Tenet.Dense(gate::Gate) - return Tenet.Dense( - Operator(), arraytype(gate)(gate); sites=Site[Site.(lanes(gate))..., Site.(lanes(gate); dual=true)...] - ) -end +# function Tenet.Dense(gate::Gate) +# return Tenet.Dense( +# Operator(), arraytype(gate)(gate); sites=Site[Site.(lanes(gate))..., Site.(lanes(gate); dual=true)...] +# ) +# end -Tenet.evolve!(qtn::Ansatz, gate::Gate; kwargs...) = evolve!(qtn, Tenet.Dense(gate); kwargs...) +# Tenet.evolve!(qtn::Ansatz, gate::Gate; kwargs...) = evolve!(qtn, Tenet.Dense(gate); kwargs...) function Tenet.Quantum(circuit::Circuit) n = lanes(circuit) diff --git a/ext/TenetReactantExt.jl b/ext/TenetReactantExt.jl index 69640667..456249d7 100644 --- a/ext/TenetReactantExt.jl +++ b/ext/TenetReactantExt.jl @@ -60,17 +60,17 @@ function Reactant.create_result(tocopy::Ansatz, @nospecialize(path), result_stor end # TODO try rely on generic fallback for ansatzes -function Reactant.create_result(tocopy::Tenet.Product, @nospecialize(path), result_stores) - tn = Reactant.create_result(Ansatz(tocopy), Reactant.append_path(path, :tn), result_stores) - return :($(Tenet.Product)($tn)) -end +# function Reactant.create_result(tocopy::Tenet.Product, @nospecialize(path), result_stores) +# tn = Reactant.create_result(Ansatz(tocopy), Reactant.append_path(path, :tn), result_stores) +# return :($(Tenet.Product)($tn)) +# end -for A in (MPS, MPO) - @eval function Reactant.create_result(tocopy::$A, @nospecialize(path), result_stores) - tn = Reactant.create_result(Ansatz(tocopy), Reactant.append_path(path, :tn), result_stores) - return :($A($tn, form(tocopy))) - end -end +# for A in (MPS, MPO) +# @eval function Reactant.create_result(tocopy::$A, @nospecialize(path), result_stores) +# tn = Reactant.create_result(Ansatz(tocopy), Reactant.append_path(path, :tn), result_stores) +# return :($A($tn, form(tocopy))) +# end +# end function Reactant.push_val!(ad_inputs, x::TensorNetwork, path) @assert length(path) == 2 diff --git a/src/Ansatz/Chain.jl b/src/Ansatz/Chain.jl deleted file mode 100644 index 72d80b23..00000000 --- a/src/Ansatz/Chain.jl +++ /dev/null @@ -1,749 +0,0 @@ -using LinearAlgebra -using Random - -struct Chain <: Ansatz - super::Quantum - boundary::Boundary -end - -Base.copy(tn::Chain) = Chain(copy(Quantum(tn)), boundary(tn)) - -Base.similar(tn::Chain) = Chain(similar(Quantum(tn)), boundary(tn)) -Base.zero(tn::Chain) = Chain(zero(Quantum(tn)), boundary(tn)) - -boundary(tn::Chain) = tn.boundary - -MPS(arrays) = Chain(State(), Open(), arrays) -pMPS(arrays) = Chain(State(), Periodic(), arrays) -MPO(arrays) = Chain(Operator(), Open(), arrays) -pMPO(arrays) = Chain(Operator(), Periodic(), arrays) - -alias(tn::Chain) = alias(socket(tn), boundary(tn), tn) -alias(::State, ::Open, ::Chain) = "MPS" -alias(::State, ::Periodic, ::Chain) = "pMPS" -alias(::Operator, ::Open, ::Chain) = "MPO" -alias(::Operator, ::Periodic, ::Chain) = "pMPO" - -function Chain(tn::TensorNetwork, sites, args...; kwargs...) - return Chain(Quantum(tn, sites), args...; kwargs...) -end - -defaultorder(::Type{Chain}, ::State) = (:o, :l, :r) -defaultorder(::Type{Chain}, ::Operator) = (:o, :i, :l, :r) - -function Chain(::State, boundary::Periodic, arrays::Vector{<:AbstractArray}; order=defaultorder(Chain, State())) - @assert all(==(3) ∘ ndims, arrays) "All arrays must have 3 dimensions" - issetequal(order, defaultorder(Chain, State())) || - throw(ArgumentError("order must be a permutation of $(String.(defaultorder(Chain, State())))")) - - n = length(arrays) - gen = IndexCounter() - symbols = [nextindex!(gen) for _ in 1:(2n)] - - _tensors = map(enumerate(arrays)) do (i, array) - inds = map(order) do dir - if dir == :o - symbols[i] - elseif dir == :r - symbols[n + mod1(i, n)] - elseif dir == :l - symbols[n + mod1(i - 1, n)] - else - throw(ArgumentError("Invalid direction: $dir")) - end - end - Tensor(array, inds) - end - - sitemap = Dict(Site(i) => symbols[i] for i in 1:n) - - return Chain(Quantum(TensorNetwork(_tensors), sitemap), boundary) -end - -function Chain(::State, boundary::Open, arrays::Vector{<:AbstractArray}; order=defaultorder(Chain, State())) - @assert ndims(arrays[1]) == 2 "First array must have 2 dimensions" - @assert all(==(3) ∘ ndims, arrays[2:(end - 1)]) "All arrays must have 3 dimensions" - @assert ndims(arrays[end]) == 2 "Last array must have 2 dimensions" - issetequal(order, defaultorder(Chain, State())) || - throw(ArgumentError("order must be a permutation of $(String.(defaultorder(Chain, State())))")) - - n = length(arrays) - gen = IndexCounter() - symbols = [nextindex!(gen) for _ in 1:(2n)] - - _tensors = map(enumerate(arrays)) do (i, array) - _order = if i == 1 - filter(x -> x != :l, order) - elseif i == n - filter(x -> x != :r, order) - else - order - end - - inds = map(_order) do dir - if dir == :o - symbols[i] - elseif dir == :r - symbols[n + mod1(i, n)] - elseif dir == :l - symbols[n + mod1(i - 1, n)] - else - throw(ArgumentError("Invalid direction: $dir")) - end - end - Tensor(array, inds) - end - - sitemap = Dict(Site(i) => symbols[i] for i in 1:n) - - return Chain(Quantum(TensorNetwork(_tensors), sitemap), boundary) -end - -function Chain(::Operator, boundary::Periodic, arrays::Vector{<:AbstractArray}; order=defaultorder(Chain, Operator())) - @assert all(==(4) ∘ ndims, arrays) "All arrays must have 4 dimensions" - issetequal(order, defaultorder(Chain, Operator())) || - throw(ArgumentError("order must be a permutation of $(String.(defaultorder(Chain, Operator())))")) - - n = length(arrays) - gen = IndexCounter() - symbols = [nextindex!(gen) for _ in 1:(3n)] - - _tensors = map(enumerate(arrays)) do (i, array) - inds = map(order) do dir - if dir == :o - symbols[i] - elseif dir == :i - symbols[i + n] - elseif dir == :l - symbols[2n + mod1(i - 1, n)] - elseif dir == :r - symbols[2n + mod1(i, n)] - else - throw(ArgumentError("Invalid direction: $dir")) - end - end - Tensor(array, inds) - end - - sitemap = Dict(Site(i) => symbols[i] for i in 1:n) - merge!(sitemap, Dict(Site(i; dual=true) => symbols[i + n] for i in 1:n)) - - return Chain(Quantum(TensorNetwork(_tensors), sitemap), boundary) -end - -function Chain(::Operator, boundary::Open, arrays::Vector{<:AbstractArray}; order=defaultorder(Chain, Operator())) - @assert ndims(arrays[1]) == 3 "First array must have 3 dimensions" - @assert all(==(4) ∘ ndims, arrays[2:(end - 1)]) "All arrays must have 4 dimensions" - @assert ndims(arrays[end]) == 3 "Last array must have 3 dimensions" - issetequal(order, defaultorder(Chain, Operator())) || - throw(ArgumentError("order must be a permutation of $(String.(defaultorder(Chain, Operator())))")) - - n = length(arrays) - gen = IndexCounter() - symbols = [nextindex!(gen) for _ in 1:(3n - 1)] - - _tensors = map(enumerate(arrays)) do (i, array) - _order = if i == 1 - filter(x -> x != :l, order) - elseif i == n - filter(x -> x != :r, order) - else - order - end - - inds = map(_order) do dir - if dir == :o - symbols[i] - elseif dir == :i - symbols[i + n] - elseif dir == :l - symbols[2n + mod1(i - 1, n)] - elseif dir == :r - symbols[2n + mod1(i, n)] - else - throw(ArgumentError("Invalid direction: $dir")) - end - end - Tensor(array, inds) - end - - sitemap = Dict(Site(i) => symbols[i] for i in 1:n) - merge!(sitemap, Dict(Site(i; dual=true) => symbols[i + n] for i in 1:n)) - - return Chain(Quantum(TensorNetwork(_tensors), sitemap), boundary) -end - -function Base.convert(::Type{Chain}, qtn::Product) - arrs::Vector{Array} = arrays(qtn) - arrs[1] = reshape(arrs[1], size(arrs[1])..., 1) - arrs[end] = reshape(arrs[end], size(arrs[end])..., 1) - map!(@view(arrs[2:(end - 1)]), @view(arrs[2:(end - 1)])) do arr - reshape(arr, size(arr)..., 1, 1) - end - - return Chain(socket(qtn), Open(), arrs) -end - -leftsite(tn::Chain, site::Site) = leftsite(boundary(tn), tn, site) -function leftsite(::Open, tn::Chain, site::Site) - return id(site) ∈ range(2, nlanes(tn)) ? Site(id(site) - 1; dual=isdual(site)) : nothing -end -leftsite(::Periodic, tn::Chain, site::Site) = Site(mod1(id(site) - 1, nlanes(tn)); dual=isdual(site)) - -rightsite(tn::Chain, site::Site) = rightsite(boundary(tn), tn, site) -function rightsite(::Open, tn::Chain, site::Site) - return id(site) ∈ range(1, nlanes(tn) - 1) ? Site(id(site) + 1; dual=isdual(site)) : nothing -end -rightsite(::Periodic, tn::Chain, site::Site) = Site(mod1(id(site) + 1, nlanes(tn)); dual=isdual(site)) - -leftindex(tn::Chain, site::Site) = leftindex(boundary(tn), tn, site) -leftindex(::Open, tn::Chain, site::Site) = site == site"1" ? nothing : leftindex(Periodic(), tn, site) -leftindex(::Periodic, tn::Chain, site::Site) = inds(tn; bond=(site, leftsite(tn, site))) - -rightindex(tn::Chain, site::Site) = rightindex(boundary(tn), tn, site) -function rightindex(::Open, tn::Chain, site::Site) - return site == Site(nlanes(tn); dual=isdual(site)) ? nothing : rightindex(Periodic(), tn, site) -end -rightindex(::Periodic, tn::Chain, site::Site) = inds(tn; bond=(site, rightsite(tn, site))) - -Base.adjoint(chain::Chain) = Chain(adjoint(Quantum(chain)), boundary(chain)) - -struct ChainSampler{B<:Boundary,S<:Socket,NT<:NamedTuple} <: Random.Sampler{Chain} - parameters::NT - - ChainSampler{B,S}(; kwargs...) where {B,S} = new{B,S,typeof(values(kwargs))}(values(kwargs)) -end - -function Base.rand(A::Type{<:Chain}, B::Type{<:Boundary}, S::Type{<:Socket}; kwargs...) - return rand(Random.default_rng(), A, B, S; kwargs...) -end - -function Base.rand(rng::AbstractRNG, ::Type{A}, ::Type{B}, ::Type{S}; kwargs...) where {A<:Chain,B<:Boundary,S<:Socket} - return rand(rng, ChainSampler{B,S}(; kwargs...), B, S) -end - -# TODO let choose the orthogonality center -function Base.rand(rng::Random.AbstractRNG, sampler::ChainSampler, ::Type{Open}, ::Type{State}) - n = sampler.parameters.n - χ = sampler.parameters.χ - p = get(sampler.parameters, :p, 2) - T = get(sampler.parameters, :eltype, Float64) - - arrays::Vector{AbstractArray{T,N} where {N}} = map(1:n) do i - χl, χr = let after_mid = i > n ÷ 2, i = (n + 1 - abs(2i - n - 1)) ÷ 2 - χl = min(χ, p^(i - 1)) - χr = min(χ, p^i) - - # swap bond dims after mid and handle midpoint for odd-length MPS - (isodd(n) && i == n ÷ 2 + 1) ? (χl, χl) : (after_mid ? (χr, χl) : (χl, χr)) - end - - # orthogonalize by QR factorization - F = lq!(rand(rng, T, χl, p * χr)) - - reshape(Matrix(F.Q), χl, p, χr) - end - - # reshape boundary sites - arrays[1] = reshape(arrays[1], p, p) - arrays[n] = reshape(arrays[n], p, p) - - return Chain(State(), Open(), arrays; order=(:l, :o, :r)) -end - -# TODO different input/output physical dims -function Base.rand(rng::Random.AbstractRNG, sampler::ChainSampler, ::Type{Open}, ::Type{Operator}) - n = sampler.parameters.n - χ = sampler.parameters.χ - p = get(sampler.parameters, :p, 2) - T = get(sampler.parameters, :eltype, Float64) - - ip = op = p - - arrays::Vector{AbstractArray{T,N} where {N}} = map(1:n) do i - χl, χr = let after_mid = i > n ÷ 2, i = (n + 1 - abs(2i - n - 1)) ÷ 2 - χl = min(χ, ip^(i - 1) * op^(i - 1)) - χr = min(χ, ip^i * op^i) - - # swap bond dims after mid and handle midpoint for odd-length MPS - (isodd(n) && i == n ÷ 2 + 1) ? (χl, χl) : (after_mid ? (χr, χl) : (χl, χr)) - end - - # orthogonalize by QR factorization - F = lq!(rand(rng, T, χl, ip * op * χr)) - reshape(Matrix(F.Q), χl, ip, op, χr) - end - - # reshape boundary sites - arrays[1] = reshape(arrays[1], p, p, min(χ, ip * op)) - arrays[n] = reshape(arrays[n], min(χ, ip * op), p, p) - - # TODO order might not be the best for performance - return Chain(Operator(), Open(), arrays; order=(:l, :i, :o, :r)) -end - -# """ -# Tenet.contract!(tn::Chain; between=(site1, site2), direction::Symbol = :left, delete_Λ = true) - -# For a given [`Chain`](@ref) tensor network, contracts the singular values Λ between two sites `site1` and `site2`. -# The `direction` keyword argument specifies the direction of the contraction, and the `delete_Λ` keyword argument -# specifies whether to delete the singular values tensor after the contraction. -# """ -@kwmethod contract(tn::Chain; between, direction, delete_Λ) = contract!(copy(tn); between, direction, delete_Λ) -@kwmethod function contract!(tn::Chain; between, direction, delete_Λ) - site1, site2 = between - Λᵢ = tensors(tn; between) - Λᵢ === nothing && return tn - - if direction === :right - Γᵢ₊₁ = tensors(tn; at=site2) - replace!(tn, Γᵢ₊₁ => contract(Γᵢ₊₁, Λᵢ; dims=())) - elseif direction === :left - Γᵢ = tensors(tn; at=site1) - replace!(tn, Γᵢ => contract(Λᵢ, Γᵢ; dims=())) - else - throw(ArgumentError("Unknown direction=:$direction")) - end - - delete_Λ && delete!(TensorNetwork(tn), Λᵢ) - - return tn -end -@kwmethod contract(tn::Chain; between) = contract(tn; between, direction=:left, delete_Λ=true) -@kwmethod contract!(tn::Chain; between) = contract!(tn; between, direction=:left, delete_Λ=true) -@kwmethod contract(tn::Chain; between, direction) = contract(tn; between, direction, delete_Λ=true) -@kwmethod contract!(tn::Chain; between, direction) = contract!(tn; between, direction, delete_Λ=true) - -canonize_site(tn::Chain, args...; kwargs...) = canonize_site!(deepcopy(tn), args...; kwargs...) -canonize_site!(tn::Chain, args...; kwargs...) = canonize_site!(boundary(tn), tn, args...; kwargs...) - -# NOTE: in method == :svd the spectral weights are stored in a vector connected to the now virtual hyperindex! -function canonize_site!(::Open, tn::Chain, site::Site; direction::Symbol, method=:qr) - left_inds = Symbol[] - right_inds = Symbol[] - - virtualind = if direction === :left - site == Site(1) && throw(ArgumentError("Cannot right-canonize left-most tensor")) - push!(right_inds, leftindex(tn, site)) - - site == Site(nsites(tn)) || push!(left_inds, rightindex(tn, site)) - push!(left_inds, inds(tn; at=site)) - - only(right_inds) - elseif direction === :right - site == Site(nsites(tn)) && throw(ArgumentError("Cannot left-canonize right-most tensor")) - push!(right_inds, rightindex(tn, site)) - - site == Site(1) || push!(left_inds, leftindex(tn, site)) - push!(left_inds, inds(tn; at=site)) - - only(right_inds) - else - throw(ArgumentError("Unknown direction=:$direction")) - end - - tmpind = gensym(:tmp) - if method === :svd - svd!(TensorNetwork(tn); left_inds, right_inds, virtualind=tmpind) - elseif method === :qr - qr!(TensorNetwork(tn); left_inds, right_inds, virtualind=tmpind) - else - throw(ArgumentError("Unknown factorization method=:$method")) - end - - contract!(tn, virtualind) - replace!(tn, tmpind => virtualind) - - return tn -end - -truncate(tn::Chain, args...; kwargs...) = truncate!(deepcopy(tn), args...; kwargs...) - -""" - truncate!(qtn::Chain, bond; threshold::Union{Nothing,Real} = nothing, maxdim::Union{Nothing,Int} = nothing) - -Truncate the dimension of the virtual `bond`` of the [`Chain`](@ref) Tensor Network by keeping only the `maxdim` largest Schmidt coefficients or those larger than`threshold`. - -# Notes - - - Either `threshold` or `maxdim` must be provided. If both are provided, `maxdim` is used. - - The bond must contain the Schmidt coefficients, i.e. a site canonization must be performed before calling `truncate!`. -""" -function truncate!(qtn::Chain, bond; threshold::Union{Nothing,Real}=nothing, maxdim::Union{Nothing,Int}=nothing) - # TODO replace for tensors(; between) - vind = rightindex(qtn, bond[1]) - if vind != leftindex(qtn, bond[2]) - throw(ArgumentError("Invalid bond $bond")) - end - - if vind ∉ inds(qtn; set=:hyper) - throw(MissingSchmidtCoefficientsException(bond)) - end - - tensor = TensorNetwork(qtn)[vind] - spectrum = parent(tensor) - - extent = collect( - if !isnothing(maxdim) - 1:min(size(qtn, vind), maxdim) - else - 1:size(qtn, vind) - end, - ) - - # remove 0s from spectrum - if isnothing(threshold) - threshold = wrap_eps(eltype(qtn)) - end - - filter!(extent) do i - abs(spectrum[i]) > threshold - end - - slice!(qtn, vind, extent) - - return qtn -end - -function isleftcanonical(qtn::Chain, site; atol::Real=1e-12) - right_ind = rightindex(qtn, site) - tensor = tensors(qtn; at=site) - - # we are at right-most site, we need to add an extra dummy dimension to the tensor - if isnothing(right_ind) - right_ind = gensym(:dummy) - tensor = Tensor(reshape(parent(tensor), size(tensor)..., 1), (inds(tensor)..., right_ind)) - end - - # TODO is replace(conj(A)...) copying too much? - contracted = contract(tensor, replace(conj(tensor), right_ind => gensym(:new_ind))) - n = size(tensor, right_ind) - identity_matrix = Matrix(I, n, n) - - return isapprox(contracted, identity_matrix; atol) -end - -function isrightcanonical(qtn::Chain, site; atol::Real=1e-12) - left_ind = leftindex(qtn, site) - tensor = tensors(qtn; at=site) - - # we are at left-most site, we need to add an extra dummy dimension to the tensor - if isnothing(left_ind) - left_ind = gensym(:dummy) - tensor = Tensor(reshape(parent(tensor), 1, size(tensor)...), (left_ind, inds(tensor)...)) - end - - #TODO is replace(conj(A)...) copying too much? - contracted = contract(tensor, replace(conj(tensor), left_ind => gensym(:new_ind))) - n = size(tensor, left_ind) - identity_matrix = Matrix(I, n, n) - - return isapprox(contracted, identity_matrix; atol) -end - -canonize(tn::Chain, args...; kwargs...) = canonize!(copy(tn), args...; kwargs...) -canonize!(tn::Chain, args...; kwargs...) = canonize!(boundary(tn), tn, args...; kwargs...) - -""" -canonize(boundary::Boundary, tn::Chain) - -Transform a `Chain` tensor network into the canonical form (Vidal form), that is, -we have the singular values matrix Λᵢ between each tensor Γᵢ₋₁ and Γᵢ. -""" -function canonize!(::Open, tn::Chain) - Λ = Tensor[] - - # right-to-left QR sweep, get right-canonical tensors - for i in nsites(tn):-1:2 - canonize_site!(tn, Site(i); direction=:left, method=:qr) - end - - # left-to-right SVD sweep, get left-canonical tensors and singular values without reversing - for i in 1:(nsites(tn) - 1) - canonize_site!(tn, Site(i); direction=:right, method=:svd) - - # extract the singular values and contract them with the next tensor - Λᵢ = pop!(TensorNetwork(tn), tensors(tn; between=(Site(i), Site(i + 1)))) - Aᵢ₊₁ = tensors(tn; at=Site(i + 1)) - replace!(tn, Aᵢ₊₁ => contract(Aᵢ₊₁, Λᵢ; dims=())) - push!(Λ, Λᵢ) - end - - for i in 2:nsites(tn) # tensors at i in "A" form, need to contract (Λᵢ)⁻¹ with A to get Γᵢ - Λᵢ = Λ[i - 1] # singular values start between site 1 and 2 - A = tensors(tn; at=Site(i)) - Γᵢ = contract(A, Tensor(diag(pinv(Diagonal(parent(Λᵢ)); atol=1e-64)), inds(Λᵢ)); dims=()) - replace!(tn, A => Γᵢ) - push!(TensorNetwork(tn), Λᵢ) - end - - return tn -end - -mixed_canonize(tn::Chain, args...; kwargs...) = mixed_canonize!(deepcopy(tn), args...; kwargs...) -mixed_canonize!(tn::Chain, args...; kwargs...) = mixed_canonize!(boundary(tn), tn, args...; kwargs...) - -""" - mixed_canonize!(boundary::Boundary, tn::Chain, center::Site) - -Transform a `Chain` tensor network into the mixed-canonical form, that is, -for i < center the tensors are left-canonical and for i >= center the tensors are right-canonical, -and in the center there is a matrix with singular values. -""" -function mixed_canonize!(::Open, tn::Chain, center::Site) # TODO: center could be a range of sites - # left-to-right QR sweep (left-canonical tensors) - for i in 1:(id(center) - 1) - canonize_site!(tn, Site(i); direction=:right, method=:qr) - end - - # right-to-left QR sweep (right-canonical tensors) - for i in nsites(tn):-1:(id(center) + 1) - canonize_site!(tn, Site(i); direction=:left, method=:qr) - end - - # center SVD sweep to get singular values - canonize_site!(tn, center; direction=:left, method=:svd) - - return tn -end - -""" - LinearAlgebra.normalize!(tn::Chain, center::Site) - -Normalizes the input [`Chain`](@ref) tensor network by transforming it -to mixed-canonized form with the given center site. -""" -function LinearAlgebra.normalize!(tn::Chain, root::Site; p::Real=2) - mixed_canonize!(tn, root) - normalize!(tensors(tn; between=(Site(id(root) - 1), root)), p) - return tn -end - -""" - evolve!(qtn::Chain, gate) - -Applies a local operator `gate` to the [`Chain`](@ref) tensor network. -""" -function evolve!(qtn::Chain, gate::Dense; threshold=nothing, maxdim=nothing, iscanonical=false, renormalize=false) - # check gate is a valid operator - if !(socket(gate) isa Operator) - throw(ArgumentError("Gate must be an operator, but got $(socket(gate))")) - end - - # TODO refactor out to `islane`? - if !issetequal(adjoint.(sites(gate; set=:inputs)), sites(gate; set=:outputs)) - throw( - ArgumentError( - "Gate inputs ($(sites(gate; set=:inputs))) and outputs ($(sites(gate; set=:outputs))) must be the same" - ), - ) - end - - # TODO refactor out to `canconnect`? - if adjoint.(sites(gate; set=:inputs)) ⊈ sites(qtn; set=:outputs) - throw( - ArgumentError("Gate inputs ($(sites(gate; set=:inputs))) must be a subset of the TN sites ($(sites(qtn)))") - ) - end - - if nlanes(gate) == 1 - evolve_1site!(qtn, gate) - elseif nlanes(gate) == 2 - # check gate sites are contiguous - # TODO refactor this out? - gate_inputs = sort!(id.(sites(gate; set=:inputs))) - range = UnitRange(extrema(gate_inputs)...) - - range != gate_inputs && throw(ArgumentError("Gate lanes must be contiguous")) - - # TODO check correctly for periodic boundary conditions - evolve_2site!(qtn, gate; threshold, maxdim, iscanonical, renormalize) - else - # TODO generalize for more than 2 lanes - throw(ArgumentError("Invalid number of lanes $(nlanes(gate)), maximum is 2")) - end - - return qtn -end - -function evolve_1site!(qtn::Chain, gate::Dense) - # shallow copy to avoid problems if errors in mid execution - gate = copy(gate) - resetindex!(gate; init=ninds(qtn)) - - contracting_index = gensym(:tmp) - targetsite = only(sites(gate; set=:inputs))' - - # reindex output of gate to match TN sitemap - replace!(gate, inds(gate; at=only(sites(gate; set=:outputs))) => inds(qtn; at=targetsite)) - - # reindex contracting index - replace!(qtn, inds(qtn; at=targetsite) => contracting_index) - replace!(gate, inds(gate; at=targetsite') => contracting_index) - - # contract gate with TN - merge!(qtn, gate; reset=false) - return contract!(qtn, contracting_index) -end - -# TODO: Maybe rename iscanonical kwarg ? -function evolve_2site!(qtn::Chain, gate::Dense; threshold, maxdim, iscanonical=false, renormalize=false) - # shallow copy to avoid problems if errors in mid execution - gate = copy(gate) - - bond = sitel, siter = minmax(sites(gate; set=:outputs)...) - left_inds::Vector{Symbol} = !isnothing(leftindex(qtn, sitel)) ? [leftindex(qtn, sitel)] : Symbol[] - right_inds::Vector{Symbol} = !isnothing(rightindex(qtn, siter)) ? [rightindex(qtn, siter)] : Symbol[] - - virtualind::Symbol = inds(qtn; bond=bond) - - iscanonical ? contract_2sitewf!(qtn, bond) : contract!(TensorNetwork(qtn), virtualind) - - # reindex contracting index - contracting_inds = [gensym(:tmp) for _ in sites(gate; set=:inputs)] - replace!( - TensorNetwork(qtn), - map(zip(sites(gate; set=:inputs), contracting_inds)) do (site, contracting_index) - inds(qtn; at=site') => contracting_index - end, - ) - replace!( - Quantum(gate), - map(zip(sites(gate; set=:inputs), contracting_inds)) do (site, contracting_index) - inds(gate; at=site) => contracting_index - end, - ) - - # replace output indices of the gate for gensym indices - output_inds = [gensym(:out) for _ in sites(gate; set=:outputs)] - replace!( - Quantum(gate), - map(zip(sites(gate; set=:outputs), output_inds)) do (site, out) - inds(gate; at=site) => out - end, - ) - - # reindex output of gate to match TN sitemap - for site in sites(gate; set=:outputs) - if inds(qtn; at=site) != inds(gate; at=site) - replace!(TensorNetwork(gate), inds(gate; at=site) => inds(qtn; at=site)) - end - end - - # contract physical inds - merge!(TensorNetwork(qtn), TensorNetwork(gate)) - contract!(qtn, contracting_inds) - - # decompose using SVD - push!(left_inds, inds(qtn; at=sitel)) - push!(right_inds, inds(qtn; at=siter)) - - if iscanonical - unpack_2sitewf!(qtn, bond, left_inds, right_inds, virtualind) - else - svd!(TensorNetwork(qtn); left_inds, right_inds, virtualind) - end - # truncate virtual index - if any(!isnothing, [threshold, maxdim]) - truncate!(qtn, bond; threshold, maxdim) - - # renormalize the bond - if renormalize && iscanonical - λ = tensors(qtn; between=bond) - replace!(qtn, λ => normalize(λ)) # TODO this can be replaced by `normalize!(λ)` - elseif renormalize && !iscanonical - normalize!(qtn, bond[1]) - end - end - - return qtn -end - -""" - contract_2sitewf!(ψ::Chain, bond) - -For a given [`Chain`](@ref) in the canonical form, creates the two-site wave function θ with Λᵢ₋₁Γᵢ₋₁ΛᵢΓᵢΛᵢ₊₁, -where i is the `bond`, and replaces the Γᵢ₋₁ΛᵢΓᵢ tensors with θ. -""" -function contract_2sitewf!(ψ::Chain, bond) - # TODO Check if ψ is in canonical form - - sitel, siter = bond # TODO Check if bond is valid - (0 < id(sitel) < nsites(ψ) || 0 < id(siter) < nsites(ψ)) || - throw(ArgumentError("The sites in the bond must be between 1 and $(nsites(ψ))")) - - Λᵢ₋₁ = id(sitel) == 1 ? nothing : tensors(ψ; between=(Site(id(sitel) - 1), sitel)) - Λᵢ₊₁ = id(sitel) == nsites(ψ) - 1 ? nothing : tensors(ψ; between=(siter, Site(id(siter) + 1))) - - !isnothing(Λᵢ₋₁) && contract!(ψ; between=(Site(id(sitel) - 1), sitel), direction=:right, delete_Λ=false) - !isnothing(Λᵢ₊₁) && contract!(ψ; between=(siter, Site(id(siter) + 1)), direction=:left, delete_Λ=false) - - contract!(ψ, inds(ψ; bond=bond)) - - return ψ -end - -""" - unpack_2sitewf!(ψ::Chain, bond) - -For a given [`Chain`](@ref) that contains a two-site wave function θ in a bond, it decomposes θ into the canonical -form: Γᵢ₋₁ΛᵢΓᵢ, where i is the `bond`. -""" -function unpack_2sitewf!(ψ::Chain, bond, left_inds, right_inds, virtualind) - # TODO Check if ψ is in canonical form - - sitel, siter = bond # TODO Check if bond is valid - (0 < id(sitel) < nsites(ψ) || 0 < id(site_r) < nsites(ψ)) || - throw(ArgumentError("The sites in the bond must be between 1 and $(nsites(ψ))")) - - Λᵢ₋₁ = id(sitel) == 1 ? nothing : tensors(ψ; between=(Site(id(sitel) - 1), sitel)) - Λᵢ₊₁ = id(siter) == nsites(ψ) ? nothing : tensors(ψ; between=(siter, Site(id(siter) + 1))) - - # do svd of the θ tensor - θ = tensors(ψ; at=sitel) - U, s, Vt = svd(θ; left_inds, right_inds, virtualind) - - # contract with the inverse of Λᵢ and Λᵢ₊₂ - Γᵢ₋₁ = - isnothing(Λᵢ₋₁) ? U : contract(U, Tensor(diag(pinv(Diagonal(parent(Λᵢ₋₁)); atol=1e-32)), inds(Λᵢ₋₁)); dims=()) - Γᵢ = - isnothing(Λᵢ₊₁) ? Vt : contract(Tensor(diag(pinv(Diagonal(parent(Λᵢ₊₁)); atol=1e-32)), inds(Λᵢ₊₁)), Vt; dims=()) - - delete!(TensorNetwork(ψ), θ) - - push!(TensorNetwork(ψ), Γᵢ₋₁) - push!(TensorNetwork(ψ), s) - push!(TensorNetwork(ψ), Γᵢ) - - return ψ -end - -function expect(ψ::Chain, observables) - # contract observable with TN - ϕ = copy(ψ) - for observable in observables - evolve!(ϕ, observable) - end - - # contract evolved TN with adjoint of original TN - tn = merge!(TensorNetwork(ϕ), TensorNetwork(ψ')) - - return contract(tn) -end - -overlap(a::Chain, b::Chain) = overlap(socket(a), a, socket(b), b) - -# TODO fix optimal path -function overlap(::State, a::Chain, ::State, b::Chain) - @assert issetequal(sites(a), sites(b)) "Ansatzes must have the same sites" - - b = copy(b) - b = @reindex! outputs(a) => outputs(b) - - tn = merge(TensorNetwork(a), TensorNetwork(b')) - return contract(tn) -end - -# TODO optimize -overlap(a::Product, b::Chain) = contract(merge(Quantum(a), Quantum(b)')) -overlap(a::Chain, b::Product) = contract(merge(Quantum(a), Quantum(b)')) diff --git a/src/Ansatz/Dense.jl b/src/Ansatz/Dense.jl deleted file mode 100644 index a0fa5355..00000000 --- a/src/Ansatz/Dense.jl +++ /dev/null @@ -1,40 +0,0 @@ -struct Dense <: Ansatz - super::Quantum -end - -function Dense(::State, array::AbstractArray; sites=Site.(1:ndims(array))) - @assert ndims(array) > 0 - @assert all(>(1), size(array)) - - gen = IndexCounter() - symbols = [nextindex!(gen) for _ in 1:ndims(array)] - sitemap = Dict{Site,Symbol}( - map(sites, 1:ndims(array)) do site, i - site => symbols[i] - end, - ) - - tensor = Tensor(array, symbols) - - tn = TensorNetwork([tensor]) - qtn = Quantum(tn, sitemap) - return Dense(qtn) -end - -function Dense(::Operator, array::AbstractArray; sites) - @assert ndims(array) > 0 - @assert all(>(1), size(array)) - @assert length(sites) == ndims(array) - - gen = IndexCounter() - tensor_inds = [nextindex!(gen) for _ in 1:ndims(array)] - tensor = Tensor(array, tensor_inds) - tn = TensorNetwork([tensor]) - - sitemap = Dict{Site,Symbol}(map(splat(Pair), zip(sites, tensor_inds))) - qtn = Quantum(tn, sitemap) - - return Dense(qtn) -end - -Base.copy(qtn::Dense) = Dense(copy(Quantum(qtn))) diff --git a/src/Ansatz/Grid.jl b/src/Ansatz/Grid.jl deleted file mode 100644 index ef59f3e8..00000000 --- a/src/Ansatz/Grid.jl +++ /dev/null @@ -1,180 +0,0 @@ -struct Grid <: Ansatz - super::Quantum - boundary::Boundary -end - -Base.copy(tn::Grid) = Grid(copy(Quantum(tn)), boundary(tn)) - -boundary(tn::Grid) = tn.boundary - -PEPS(arrays) = Grid(State(), Open(), arrays) -pPEPS(arrays) = Grid(State(), Periodic(), arrays) -PEPO(arrays) = Grid(Operator(), Open(), arrays) -pPEPO(arrays) = Grid(Operator(), Periodic(), arrays) - -alias(tn::Grid) = alias(socket(tn), boundary(tn), tn) -alias(::State, ::Open, ::Grid) = "PEPS" -alias(::State, ::Periodic, ::Grid) = "pPEPS" -alias(::Operator, ::Open, ::Grid) = "PEPO" -alias(::Operator, ::Periodic, ::Grid) = "pPEPO" - -function Grid(::State, ::Periodic, arrays::Matrix{<:AbstractArray}) - @assert all(==(4) ∘ ndims, arrays) "All arrays must have 4 dimensions" - - m, n = size(arrays) - gen = IndexCounter() - pinds = map(_ -> nextindex!(gen), arrays) - hvinds = map(_ -> nextindex!(gen), arrays) - vvinds = map(_ -> nextindex!(gen), arrays) - - _tensors = map(eachindex(IndexCartesian(), arrays)) do I - i, j = Tuple(I) - - array = arrays[i, j] - pind = pinds[i, j] - up, down = hvinds[i, j], hvinds[mod1(i + 1, m), j] - left, right = vvinds[i, j], vvinds[i, mod1(j + 1, n)] - - # TODO customize order - Tensor(array, [pind, up, down, left, right]) - end - - sitemap = Dict(Site(i, j) => pinds[i, j] for i in 1:m, j in 1:n) - - return Grid(Quantum(TensorNetwork(_tensors), sitemap), Periodic()) -end - -function Grid(::State, ::Open, arrays::Matrix{<:AbstractArray}) - m, n = size(arrays) - - predicate = all(eachindex(arrays)) do I - i, j = Tuple(I) - array = arrays[i, j] - - N = ndims(array) - 1 - (i == 1 || i == m) && (N -= 1) - (j == 1 || j == n) && (N -= 1) - - N > 0 - end - - if !predicate - throw(DimensionMismatch()) - end - - gen = IndexCounter() - pinds = map(_ -> nextindex!(gen), arrays) - vvinds = [nextindex!(gen) for _ in 1:(m - 1), _ in 1:n] - hvinds = [nextindex!(gen) for _ in 1:m, _ in 1:(n - 1)] - - _tensors = map(eachindex(IndexCartesian(), arrays)) do I - i, j = Tuple(I) - - array = arrays[i, j] - pind = pinds[i, j] - up = i == 1 ? missing : vvinds[i - 1, j] - down = i == m ? missing : vvinds[i, j] - left = j == 1 ? missing : hvinds[i, j - 1] - right = j == n ? missing : hvinds[i, j] - - # TODO customize order - Tensor(array, collect(skipmissing([pind, up, down, left, right]))) - end - - sitemap = Dict(Site(i, j) => pinds[i, j] for i in 1:m, j in 1:n) - - return Grid(Quantum(TensorNetwork(_tensors), sitemap), Open()) -end - -function Grid(::Operator, ::Periodic, arrays::Matrix{<:AbstractArray}) - @assert all(==(4) ∘ ndims, arrays) "All arrays must have 4 dimensions" - - m, n = size(arrays) - gen = IndexCounter() - ipinds = map(_ -> nextindex!(gen), arrays) - opinds = map(_ -> nextindex!(gen), arrays) - hvinds = map(_ -> nextindex!(gen), arrays) - vvinds = map(_ -> nextindex!(gen), arrays) - - _tensors = map(eachindex(IndexCartesian(), arrays)) do I - i, j = Tuple(I) - - array = arrays[i, j] - ipind, opind = ipinds[i, j], opinds[i, j] - up, down = hvinds[i, j], hvinds[mod1(i + 1, m), j] - left, right = vvinds[i, j], vvinds[i, mod1(j + 1, n)] - - # TODO customize order - Tensor(array, [ipind, opind, up, down, left, right]) - end - - sitemap = Dict( - flatten([ - (Site(i, j; dual=true) => ipinds[i, j] for i in 1:m, j in 1:n), - (Site(i, j) => opinds[i, j] for i in 1:m, j in 1:n), - ]), - ) - - return Grid(Quantum(TensorNetwork(_tensors), sitemap), Periodic()) -end - -function Grid(::Operator, ::Open, arrays::Matrix{<:AbstractArray}) - m, n = size(arrays) - - predicate = all(eachindex(IndexCartesian(), arrays)) do I - i, j = Tuple(I) - array = arrays[i, j] - - N = ndims(array) - 2 - (i == 1 || i == m) && (N -= 1) - (j == 1 || j == n) && (N -= 1) - - N > 0 - end - - if !predicate - throw(DimensionMismatch()) - end - - gen = IndexCounter() - ipinds = map(_ -> nextindex!(gen), arrays) - opinds = map(_ -> nextindex!(gen), arrays) - vvinds = [nextindex!(gen) for _ in 1:(m - 1), _ in 1:n] - hvinds = [nextindex!(gen) for _ in 1:m, _ in 1:(n - 1)] - - _tensors = map(eachindex(IndexCartesian(), arrays)) do I - i, j = Tuple(I) - - array = arrays[i, j] - ipind = ipinds[i, j] - opind = opinds[i, j] - up = i == 1 ? missing : vvinds[i - 1, j] - down = i == m ? missing : vvinds[i, j] - left = j == 1 ? missing : hvinds[i, j - 1] - right = j == n ? missing : hvinds[i, j] - - # TODO customize order - Tensor(array, collect(skipmissing([ipind, opind, up, down, left, right]))) - end - - sitemap = Dict( - flatten([ - (Site(i, j; dual=true) => ipinds[i, j] for i in 1:m, j in 1:n), - (Site(i, j) => opinds[i, j] for i in 1:m, j in 1:n), - ]), - ) - - return Grid(Quantum(TensorNetwork(_tensors), sitemap), Open()) -end - -function LinearAlgebra.transpose!(qtn::Grid) - old = Quantum(qtn).sites - new = Dict(Site(reverse(id(site)); dual=isdual(site)) => ind for (site, ind) in old) - - empty!(old) - merge!(old, new) - - return qtn -end - -Base.transpose(qtn::Grid) = LinearAlgebra.transpose!(copy(qtn)) diff --git a/src/Ansatz/Product.jl b/src/Ansatz/Product.jl deleted file mode 100644 index ab77d3be..00000000 --- a/src/Ansatz/Product.jl +++ /dev/null @@ -1,85 +0,0 @@ -using LinearAlgebra - -struct Product <: Ansatz - super::Quantum -end - -Base.copy(x::Product) = Product(copy(Quantum(x))) - -Base.similar(x::Product) = Product(similar(Quantum(x))) -Base.zero(x::Product) = Product(zero(Quantum(x))) - -function Product(tn::TensorNetwork, sites) - @assert isempty(inds(tn; set=:inner)) "Product ansatz must not have inner indices" - return Product(Quantum(tn, sites)) -end - -Product(arrays::Vector{<:AbstractVector}) = Product(State(), Open(), arrays) -Product(arrays::Vector{<:AbstractMatrix}) = Product(Operator(), Open(), arrays) - -function Product(::State, ::Open, arrays) - gen = IndexCounter() - symbols = [nextindex!(gen) for _ in 1:length(arrays)] - _tensors = map(enumerate(arrays)) do (i, array) - Tensor(array, [symbols[i]]) - end - - sitemap = Dict(Site(i) => symbols[i] for i in 1:length(arrays)) - - return Product(TensorNetwork(_tensors), sitemap) -end - -function Product(::Operator, ::Open, arrays) - n = length(arrays) - gen = IndexCounter() - symbols = [nextindex!(gen) for _ in 1:(2 * length(arrays))] - _tensors = map(enumerate(arrays)) do (i, array) - Tensor(array, [symbols[i + n], symbols[i]]) - end - - sitemap = merge!(Dict(Site(i; dual=true) => symbols[i] for i in 1:n), Dict(Site(i) => symbols[i + n] for i in 1:n)) - - return Product(TensorNetwork(_tensors), sitemap) -end - -function Base.zeros(::Type{Product}, n::Integer; p::Int=2, eltype=Bool) - return Product(State(), Open(), fill(append!([one(eltype)], collect(Iterators.repeated(zero(eltype), p - 1))), n)) -end - -function Base.ones(::Type{Product}, n::Integer; p::Int=2, eltype=Bool) - return Product( - State(), Open(), fill(append!([zero(eltype), one(eltype)], collect(Iterators.repeated(zero(eltype), p - 2))), n) - ) -end - -LinearAlgebra.norm(tn::Product, p::Real=2) = LinearAlgebra.norm(socket(tn), tn, p) -function LinearAlgebra.norm(::Union{State,Operator}, tn::Product, p::Real) - return mapreduce(*, tensors(tn)) do tensor - norm(tensor, p) - end^(1//p) -end - -LinearAlgebra.opnorm(tn::Product, p::Real=2) = LinearAlgebra.opnorm(socket(tn), tn, p) -function LinearAlgebra.opnorm(::Operator, tn::Product, p::Real) - return mapreduce(*, tensors(tn)) do tensor - opnorm(parent(tensor), p) - end^(1//p) -end - -LinearAlgebra.normalize!(tn::Product, p::Real=2) = LinearAlgebra.normalize!(socket(tn), tn, p) -function LinearAlgebra.normalize!(::Union{State,Operator}, tn::Product, p::Real) - for tensor in tensors(tn) - normalize!(tensor, p) - end - return tn -end - -overlap(a::Product, b::Product) = overlap(socket(a), a, socket(b), b) - -function overlap(::State, a::Product, ::State, b::Product) - @assert issetequal(sites(a), sites(b)) "Ansatzes must have the same sites" - - mapreduce(*, zip(tensors(a), tensors(b))) do (ta, tb) - dot(parent(ta), conj(parent(tb))) - end -end diff --git a/test/Chain_test.jl b/test/Chain_test.jl deleted file mode 100644 index d2b94689..00000000 --- a/test/Chain_test.jl +++ /dev/null @@ -1,436 +0,0 @@ -@testset "Chain ansatz" begin - @testset "Periodic boundary" begin - @testset "State" begin - qtn = Chain(State(), Periodic(), [rand(2, 4, 4) for _ in 1:3]) - @test socket(qtn) == State() - @test nsites(qtn; set=:inputs) == 0 - @test nsites(qtn; set=:outputs) == 3 - @test issetequal(sites(qtn), [site"1", site"2", site"3"]) - @test boundary(qtn) == Periodic() - @test leftindex(qtn, site"1") == rightindex(qtn, site"3") != nothing - - arrays = [rand(2, 1, 4), rand(2, 4, 3), rand(2, 3, 1)] - qtn = Chain(State(), Periodic(), arrays) # Default order (:o, :l, :r) - - @test size(tensors(qtn; at=Site(1))) == (2, 1, 4) - @test size(tensors(qtn; at=Site(2))) == (2, 4, 3) - @test size(tensors(qtn; at=Site(3))) == (2, 3, 1) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) - - arrays = [permutedims(array, (3, 1, 2)) for array in arrays] # now we have (:r, :o, :l) - qtn = Chain(State(), Periodic(), arrays; order=[:r, :o, :l]) - - @test size(tensors(qtn; at=Site(1))) == (4, 2, 1) - @test size(tensors(qtn; at=Site(2))) == (3, 2, 4) - @test size(tensors(qtn; at=Site(3))) == (1, 2, 3) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) - - for i in 1:nsites(qtn) - @test size(qtn, inds(qtn; at=Site(i))) == 2 - end - end - - @testset "Operator" begin - qtn = Chain(Operator(), Periodic(), [rand(2, 2, 4, 4) for _ in 1:3]) - @test socket(qtn) == Operator() - @test nsites(qtn; set=:inputs) == 3 - @test nsites(qtn; set=:outputs) == 3 - @test issetequal(sites(qtn), [site"1", site"2", site"3", site"1'", site"2'", site"3'"]) - @test boundary(qtn) == Periodic() - @test leftindex(qtn, site"1") == rightindex(qtn, site"3") != nothing - - arrays = [rand(2, 4, 1, 3), rand(2, 4, 3, 6), rand(2, 4, 6, 1)] # Default order (:o, :i, :l, :r) - qtn = Chain(Operator(), Periodic(), arrays) - - @test size(tensors(qtn; at=Site(1))) == (2, 4, 1, 3) - @test size(tensors(qtn; at=Site(2))) == (2, 4, 3, 6) - @test size(tensors(qtn; at=Site(3))) == (2, 4, 6, 1) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) - - for i in 1:length(arrays) - @test size(qtn, inds(qtn; at=Site(i))) == 2 - @test size(qtn, inds(qtn; at=Site(i; dual=true))) == 4 - end - - arrays = [permutedims(array, (4, 1, 3, 2)) for array in arrays] # now we have (:r, :o, :l, :i) - qtn = Chain(Operator(), Periodic(), arrays; order=[:r, :o, :l, :i]) - - @test size(tensors(qtn; at=Site(1))) == (3, 2, 1, 4) - @test size(tensors(qtn; at=Site(2))) == (6, 2, 3, 4) - @test size(tensors(qtn; at=Site(3))) == (1, 2, 6, 4) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) !== nothing - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) !== nothing - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) !== nothing - - for i in 1:length(arrays) - @test size(qtn, inds(qtn; at=Site(i))) == 2 - @test size(qtn, inds(qtn; at=Site(i; dual=true))) == 4 - end - end - end - - @testset "Open boundary" begin - @testset "State" begin - qtn = Chain(State(), Open(), [rand(2, 2), rand(2, 2, 2), rand(2, 2)]) - @test socket(qtn) == State() - @test nsites(qtn; set=:inputs) == 0 - @test nsites(qtn; set=:outputs) == 3 - @test issetequal(sites(qtn), [site"1", site"2", site"3"]) - @test boundary(qtn) == Open() - @test leftindex(qtn, site"1") == rightindex(qtn, site"3") == nothing - - arrays = [rand(2, 1), rand(2, 1, 3), rand(2, 3)] - qtn = Chain(State(), Open(), arrays) # Default order (:o, :l, :r) - - @test size(tensors(qtn; at=Site(1))) == (2, 1) - @test size(tensors(qtn; at=Site(2))) == (2, 1, 3) - @test size(tensors(qtn; at=Site(3))) == (2, 3) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) === nothing - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) - - arrays = [permutedims(arrays[1], (2, 1)), permutedims(arrays[2], (3, 1, 2)), permutedims(arrays[3], (1, 2))] # now we have (:r, :o, :l) - qtn = Chain(State(), Open(), arrays; order=[:r, :o, :l]) - - @test size(tensors(qtn; at=Site(1))) == (1, 2) - @test size(tensors(qtn; at=Site(2))) == (3, 2, 1) - @test size(tensors(qtn; at=Site(3))) == (2, 3) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) === nothing - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) !== nothing - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) !== nothing - - for i in 1:nsites(qtn) - @test size(qtn, inds(qtn; at=Site(i))) == 2 - end - end - @testset "Operator" begin - qtn = Chain(Operator(), Open(), [rand(2, 2, 4), rand(2, 2, 4, 4), rand(2, 2, 4)]) - @test socket(qtn) == Operator() - @test nsites(qtn; set=:inputs) == 3 - @test nsites(qtn; set=:outputs) == 3 - @test issetequal(sites(qtn), [site"1", site"2", site"3", site"1'", site"2'", site"3'"]) - @test boundary(qtn) == Open() - @test leftindex(qtn, site"1") == rightindex(qtn, site"3") == nothing - - arrays = [rand(2, 4, 1), rand(2, 4, 1, 3), rand(2, 4, 3)] # Default order (:o :i, :l, :r) - qtn = Chain(Operator(), Open(), arrays) - - @test size(tensors(qtn; at=Site(1))) == (2, 4, 1) - @test size(tensors(qtn; at=Site(2))) == (2, 4, 1, 3) - @test size(tensors(qtn; at=Site(3))) == (2, 4, 3) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) === nothing - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) !== nothing - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) !== nothing - - for i in 1:length(arrays) - @test size(qtn, inds(qtn; at=Site(i))) == 2 - @test size(qtn, inds(qtn; at=Site(i; dual=true))) == 4 - end - - arrays = [ - permutedims(arrays[1], (3, 1, 2)), - permutedims(arrays[2], (4, 1, 3, 2)), - permutedims(arrays[3], (1, 3, 2)), - ] # now we have (:r, :o, :l, :i) - qtn = Chain(Operator(), Open(), arrays; order=[:r, :o, :l, :i]) - - @test size(tensors(qtn; at=Site(1))) == (1, 2, 4) - @test size(tensors(qtn; at=Site(2))) == (3, 2, 1, 4) - @test size(tensors(qtn; at=Site(3))) == (2, 3, 4) - - @test leftindex(qtn, Site(1)) == rightindex(qtn, Site(3)) === nothing - @test leftindex(qtn, Site(2)) == rightindex(qtn, Site(1)) !== nothing - @test leftindex(qtn, Site(3)) == rightindex(qtn, Site(2)) !== nothing - - for i in 1:length(arrays) - @test size(qtn, inds(qtn; at=Site(i))) == 2 - @test size(qtn, inds(qtn; at=Site(i; dual=true))) == 4 - end - end - end - - @testset "Site" begin - using Tenet: leftsite, rightsite - qtn = Chain(State(), Periodic(), [rand(2, 4, 4) for _ in 1:3]) - - @test leftsite(qtn, Site(1)) == Site(3) - @test leftsite(qtn, Site(2)) == Site(1) - @test leftsite(qtn, Site(3)) == Site(2) - - @test rightsite(qtn, Site(1)) == Site(2) - @test rightsite(qtn, Site(2)) == Site(3) - @test rightsite(qtn, Site(3)) == Site(1) - - qtn = Chain(State(), Open(), [rand(2, 2), rand(2, 2, 2), rand(2, 2)]) - - @test isnothing(leftsite(qtn, Site(1))) - @test isnothing(rightsite(qtn, Site(3))) - - @test leftsite(qtn, Site(2)) == Site(1) - @test leftsite(qtn, Site(3)) == Site(2) - - @test rightsite(qtn, Site(2)) == Site(3) - @test rightsite(qtn, Site(1)) == Site(2) - end - - @testset "truncate" begin - qtn = Chain(State(), Open(), [rand(2, 2), rand(2, 2, 2), rand(2, 2)]) - canonize_site!(qtn, Site(2); direction=:right, method=:svd) - - @test_throws Tenet.MissingSchmidtCoefficientsException truncate!(qtn, [Site(1), Site(2)]; maxdim=1) - # @test_throws ArgumentError truncate!(qtn, [Site(2), Site(3)]) - - truncated = Tenet.truncate(qtn, [Site(2), Site(3)]; maxdim=1) - @test size(truncated, rightindex(truncated, Site(2))) == 1 - @test size(truncated, leftindex(truncated, Site(3))) == 1 - - singular_values = tensors(qtn; between=(Site(2), Site(3))) - truncated = Tenet.truncate(qtn, [Site(2), Site(3)]; threshold=singular_values[2] + 0.1) - @test size(truncated, rightindex(truncated, Site(2))) == 1 - @test size(truncated, leftindex(truncated, Site(3))) == 1 - end - - @testset "rand" begin - using LinearAlgebra: norm - - @testset "State" begin - n = 8 - χ = 10 - - qtn = rand(Chain, Open, State; n, p=2, χ) - @test socket(qtn) == State() - @test nsites(qtn; set=:inputs) == 0 - @test nsites(qtn; set=:outputs) == n - @test issetequal(sites(qtn), map(Site, 1:n)) - @test boundary(qtn) == Open() - @test isapprox(norm(qtn), 1.0) - @test maximum(last, size(qtn)) <= χ - end - - @testset "Operator" begin - n = 8 - χ = 10 - - qtn = rand(Chain, Open, Operator; n, p=2, χ) - @test socket(qtn) == Operator() - @test nsites(qtn; set=:inputs) == n - @test nsites(qtn; set=:outputs) == n - @test issetequal(sites(qtn), vcat(map(Site, 1:n), map(adjoint ∘ Site, 1:n))) - @test boundary(qtn) == Open() - @test isapprox(norm(qtn), 1.0) - @test maximum(last, size(qtn)) <= χ - end - end - - @testset "Canonization" begin - using Tenet - - @testset "contract" begin - qtn = rand(Chain, Open, State; n=5, p=2, χ=20) - let canonized = canonize(qtn) - @test_throws ArgumentError contract!(canonized; between=(Site(1), Site(2)), direction=:dummy) - end - - canonized = canonize(qtn) - - for i in 1:4 - contract_some = contract(canonized; between=(Site(i), Site(i + 1))) - Bᵢ = tensors(contract_some; at=Site(i)) - - @test isapprox(contract(contract_some), contract(qtn)) - @test_throws ArgumentError tensors(contract_some; between=(Site(i), Site(i + 1))) - - @test isrightcanonical(contract_some, Site(i)) - @test isleftcanonical( - contract(canonized; between=(Site(i), Site(i + 1)), direction=:right), Site(i + 1) - ) - - Γᵢ = tensors(canonized; at=Site(i)) - Λᵢ₊₁ = tensors(canonized; between=(Site(i), Site(i + 1))) - @test Bᵢ ≈ contract(Γᵢ, Λᵢ₊₁; dims=()) - end - end - - @testset "canonize_site" begin - qtn = Chain(State(), Open(), [rand(4, 4), rand(4, 4, 4), rand(4, 4)]) - - @test_throws ArgumentError canonize_site!(qtn, Site(1); direction=:left) - @test_throws ArgumentError canonize_site!(qtn, Site(3); direction=:right) - - for method in [:qr, :svd] - canonized = canonize_site(qtn, site"1"; direction=:right, method=method) - @test isleftcanonical(canonized, site"1") - @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(qtn)) - - canonized = canonize_site(qtn, site"2"; direction=:right, method=method) - @test isleftcanonical(canonized, site"2") - @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(qtn)) - - canonized = canonize_site(qtn, site"2"; direction=:left, method=method) - @test isrightcanonical(canonized, site"2") - @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(qtn)) - - canonized = canonize_site(qtn, site"3"; direction=:left, method=method) - @test isrightcanonical(canonized, site"3") - @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(qtn)) - end - - # Ensure that svd creates a new tensor - @test length(tensors(canonize_site(qtn, Site(2); direction=:left, method=:svd))) == 4 - end - - @testset "canonize" begin - using Tenet: isleftcanonical, isrightcanonical - - qtn = MPS([rand(4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4)]) - canonized = canonize(qtn) - - @test length(tensors(canonized)) == 9 # 5 tensors + 4 singular values vectors - @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(qtn)) - @test isapprox(norm(qtn), norm(canonized)) - - # Extract the singular values between each adjacent pair of sites in the canonized chain - Λ = [tensors(canonized; between=(Site(i), Site(i + 1))) for i in 1:4] - @test map(λ -> sum(abs2, λ), Λ) ≈ ones(length(Λ)) * norm(canonized)^2 - - for i in 1:5 - canonized = canonize(qtn) - - if i == 1 - @test isleftcanonical(canonized, Site(i)) - elseif i == 5 # in the limits of the chain, we get the norm of the state - contract!(canonized; between=(Site(i - 1), Site(i)), direction=:right) - tensor = tensors(canonized; at=Site(i)) - replace!(canonized, tensor => tensor / norm(canonized)) - @test isleftcanonical(canonized, Site(i)) - else - contract!(canonized; between=(Site(i - 1), Site(i)), direction=:right) - @test isleftcanonical(canonized, Site(i)) - end - end - - for i in 1:5 - canonized = canonize(qtn) - - if i == 1 # in the limits of the chain, we get the norm of the state - contract!(canonized; between=(Site(i), Site(i + 1)), direction=:left) - tensor = tensors(canonized; at=Site(i)) - replace!(canonized, tensor => tensor / norm(canonized)) - @test isrightcanonical(canonized, Site(i)) - elseif i == 5 - @test isrightcanonical(canonized, Site(i)) - else - contract!(canonized; between=(Site(i), Site(i + 1)), direction=:left) - @test isrightcanonical(canonized, Site(i)) - end - end - end - - @testset "mixed_canonize" begin - qtn = Chain(State(), Open(), [rand(4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4)]) - canonized = mixed_canonize(qtn, Site(3)) - - @test length(tensors(canonized)) == length(tensors(qtn)) + 1 - - @test isleftcanonical(canonized, Site(1)) - @test isleftcanonical(canonized, Site(2)) - @test isrightcanonical(canonized, Site(3)) - @test isrightcanonical(canonized, Site(4)) - @test isrightcanonical(canonized, Site(5)) - - @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(qtn)) - end - end - - @test begin - qtn = MPS([rand(4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4)]) - normalize!(qtn, Site(3)) - isapprox(norm(qtn), 1.0) - end - - @testset "adjoint" begin - qtn = rand(Chain, Open, State; n=5, p=2, χ=10) - adjoint_qtn = adjoint(qtn) - - for i in 1:nsites(qtn) - i < nsites(qtn) && - @test rightindex(adjoint_qtn, Site(i; dual=true)) == Symbol(String(rightindex(qtn, Site(i))) * "'") - i > 1 && @test leftindex(adjoint_qtn, Site(i; dual=true)) == Symbol(String(leftindex(qtn, Site(i))) * "'") - end - - @test isapprox(contract(qtn), contract(adjoint_qtn)) - end - - @testset "evolve!" begin - @testset "one site" begin - i = 2 - mat = reshape(LinearAlgebra.I(2), 2, 2) - gate = Dense(Tenet.Operator(), mat; sites=[Site(i), Site(i; dual=true)]) - - qtn = Chain(State(), Open(), [rand(2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2)]) - - @testset "canonical form" begin - canonized = canonize(qtn) - - evolved = evolve!(deepcopy(canonized), gate; threshold=1e-14) - @test isapprox(contract(evolved), contract(canonized)) - @test issetequal(size.(tensors(evolved)), [(2, 2), (2,), (2, 2, 2), (2,), (2, 2, 2), (2,), (2, 2)]) - @test isapprox(contract(evolved), contract(qtn)) - end - - @testset "arbitrary chain" begin - evolved = evolve!(deepcopy(qtn), gate; threshold=1e-14, iscanonical=false) - @test length(tensors(evolved)) == 5 - @test issetequal(size.(tensors(evolved)), [(2, 2), (2, 2, 2), (2, 2, 2), (2, 2, 2), (2, 2)]) - @test isapprox(contract(evolved), contract(qtn)) - end - end - - @testset "two sites" begin - i, j = 2, 3 - mat = reshape(kron(LinearAlgebra.I(2), LinearAlgebra.I(2)), 2, 2, 2, 2) - gate = Dense(Tenet.Operator(), mat; sites=[Site(i), Site(j), Site(i; dual=true), Site(j; dual=true)]) - - qtn = Chain(State(), Open(), [rand(2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2)]) - - @testset "canonical form" begin - canonized = canonize(qtn) - - evolved = evolve!(deepcopy(canonized), gate; threshold=1e-14) - @test isapprox(contract(evolved), contract(canonized)) - @test issetequal(size.(tensors(evolved)), [(2, 2), (2,), (2, 2, 2), (2,), (2, 2, 2), (2,), (2, 2)]) - @test isapprox(contract(evolved), contract(qtn)) - end - - @testset "arbitrary chain" begin - evolved = evolve!(deepcopy(qtn), gate; threshold=1e-14, iscanonical=false) - @test length(tensors(evolved)) == 5 - @test issetequal(size.(tensors(evolved)), [(2, 2), (2, 2, 2), (2,), (2, 2, 2), (2, 2, 2), (2, 2)]) - @test isapprox(contract(evolved), contract(qtn)) - end - end - end - - @testset "expect" begin - i, j = 2, 3 - mat = reshape(kron(LinearAlgebra.I(2), LinearAlgebra.I(2)), 2, 2, 2, 2) - gate = Dense(Tenet.Operator(), mat; sites=[Site(i), Site(j), Site(i; dual=true), Site(j; dual=true)]) - - qtn = Chain(State(), Open(), [rand(2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2)]) - - @test isapprox(expect(qtn, [gate]), norm(qtn)^2) - end -end diff --git a/test/Product_test.jl b/test/Product_test.jl index 14619605..187f72ac 100644 --- a/test/Product_test.jl +++ b/test/Product_test.jl @@ -1,33 +1,33 @@ -@testset "Product ansatz" begin - using LinearAlgebra +# @testset "Product ansatz" begin +# using LinearAlgebra - # TODO test `Product` with `Scalar` socket +# # TODO test `Product` with `Scalar` socket - qtn = Product([rand(2) for _ in 1:3]) - @test socket(qtn) == State() - @test nsites(qtn; set=:inputs) == 0 - @test nsites(qtn; set=:outputs) == 3 - @test norm(qtn) isa Number - @test begin - normalize!(qtn) - norm(qtn) ≈ 1 - end - @test adjoint(qtn) isa Product - @test socket(adjoint(qtn)) == State(; dual=true) +# qtn = Product([rand(2) for _ in 1:3]) +# @test socket(qtn) == State() +# @test nsites(qtn; set=:inputs) == 0 +# @test nsites(qtn; set=:outputs) == 3 +# @test norm(qtn) isa Number +# @test begin +# normalize!(qtn) +# norm(qtn) ≈ 1 +# end +# @test adjoint(qtn) isa Product +# @test socket(adjoint(qtn)) == State(; dual=true) - # conversion to `Quantum` - @test Quantum(qtn) isa Quantum +# # conversion to `Quantum` +# @test Quantum(qtn) isa Quantum - qtn = Product([rand(2, 2) for _ in 1:3]) - @test socket(qtn) == Operator() - @test nsites(qtn; set=:inputs) == 3 - @test nsites(qtn; set=:outputs) == 3 - @test norm(qtn) isa Number - @test opnorm(qtn) isa Number - @test begin - normalize!(qtn) - norm(qtn) ≈ 1 - end - @test adjoint(qtn) isa Product - @test socket(adjoint(qtn)) == Operator() -end +# qtn = Product([rand(2, 2) for _ in 1:3]) +# @test socket(qtn) == Operator() +# @test nsites(qtn; set=:inputs) == 3 +# @test nsites(qtn; set=:outputs) == 3 +# @test norm(qtn) isa Number +# @test opnorm(qtn) isa Number +# @test begin +# normalize!(qtn) +# norm(qtn) ≈ 1 +# end +# @test adjoint(qtn) isa Product +# @test socket(adjoint(qtn)) == Operator() +# end diff --git a/test/integration/YaoBlocks_test.jl b/test/integration/YaoBlocks_test.jl index 2b9e11ca..6734741a 100644 --- a/test/integration/YaoBlocks_test.jl +++ b/test/integration/YaoBlocks_test.jl @@ -8,35 +8,35 @@ @test issetequal(sites(tn), [site"1", site"2", site"1'", site"2'"]) @test Tenet.ntensors(tn) == 2 - @testset "GHZ Circuit" begin - circuit_GHZ = chain(n_qubits, put(1 => Yao.H), Yao.control(1, 2 => Yao.X), Yao.control(2, 3 => Yao.X)) + # @testset "GHZ Circuit" begin + # circuit_GHZ = chain(n_qubits, put(1 => Yao.H), Yao.control(1, 2 => Yao.X), Yao.control(2, 3 => Yao.X)) - quantum_circuit = Quantum(circuit_GHZ) + # quantum_circuit = Quantum(circuit_GHZ) - zeros = Quantum(Product(fill([1, 0], n_qubits))) #|000> - ones = Quantum(Product(fill([0, 1], n_qubits))) #|111> + # zeros = Quantum(Product(fill([1, 0], n_qubits))) #|000> + # ones = Quantum(Product(fill([0, 1], n_qubits))) #|111> - expected_value = Tenet.contract(merge(zeros, quantum_circuit, ones')) # <111|circuit|000> - @test only(expected_value) ≈ 1 / √2 + # expected_value = Tenet.contract(merge(zeros, quantum_circuit, ones')) # <111|circuit|000> + # @test only(expected_value) ≈ 1 / √2 - SV_Yao = apply!(zero_state(n_qubits), circuit_GHZ) # circuit|000> - @test only(statevec(ArrayReg(bit"111"))' * statevec(SV_Yao)) ≈ 1 / √2 - end + # SV_Yao = apply!(zero_state(n_qubits), circuit_GHZ) # circuit|000> + # @test only(statevec(ArrayReg(bit"111"))' * statevec(SV_Yao)) ≈ 1 / √2 + # end - @testset "two-qubit gate" begin - U = matblock(rand(ComplexF64, 4, 4); tag="U") - circuit = chain(2, put((1, 2) => U)) - psi = zero_state(2) - apply!(psi, circuit) + # @testset "two-qubit gate" begin + # U = matblock(rand(ComplexF64, 4, 4); tag="U") + # circuit = chain(2, put((1, 2) => U)) + # psi = zero_state(2) + # apply!(psi, circuit) - quantum_circuit = Quantum(circuit) - zeros = Quantum(Product(fill([1, 0], 2))) #|00> - ones = Quantum(Product(fill([0, 1], 2))) #|11> + # quantum_circuit = Quantum(circuit) + # zeros = Quantum(Product(fill([1, 0], 2))) #|00> + # ones = Quantum(Product(fill([0, 1], 2))) #|11> - expected_value = Tenet.contract(merge(zeros, quantum_circuit, ones')) # <11|circuit|00> + # expected_value = Tenet.contract(merge(zeros, quantum_circuit, ones')) # <11|circuit|00> - SV_Yao = apply!(zero_state(2), circuit) # circuit|00> + # SV_Yao = apply!(zero_state(2), circuit) # circuit|00> - @test only(expected_value) ≈ only(statevec(ArrayReg(bit"11"))' * statevec(SV_Yao)) - end + # @test only(expected_value) ≈ only(statevec(ArrayReg(bit"11"))' * statevec(SV_Yao)) + # end end diff --git a/test/runtests.jl b/test/runtests.jl index 16a09353..ecefff22 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,8 +11,7 @@ using OMEinsum include("Site_test.jl") include("Quantum_test.jl") include("Ansatz_test.jl") - include("Product_test.jl") - include("Chain_test.jl") + # include("Product_test.jl") end # CI hangs on these tests for some unknown reason on Julia 1.9