From ef9cae574d55dacffebd4119fa1523f6680aedac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 12 Jan 2025 20:36:29 +0100 Subject: [PATCH 01/13] Implement new `Circuit`, `Gate`, `Lane`, `Moment` types --- src/Circuit.jl | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Quantum.jl | 4 +- src/Site.jl | 101 +++++++++++++++++++++++++++++++++--------- src/Tenet.jl | 8 +++- 4 files changed, 206 insertions(+), 25 deletions(-) create mode 100644 src/Circuit.jl diff --git a/src/Circuit.jl b/src/Circuit.jl new file mode 100644 index 000000000..08c18cb58 --- /dev/null +++ b/src/Circuit.jl @@ -0,0 +1,118 @@ +using BijectiveDicts: BijectiveIdDict + +struct Gate + tensor::Tensor + sites::Vector{Site} + + function Gate(tensor::Tensor, sites) + @assert ndims(tensor) == length(sites) + @assert allunique(sites) + @assert allunique(inds(tensor)) + new(tensor, sites) + end +end + +Gate(array::AbstractArray, sites) = Gate(Tensor(array, [gensym(:i) for _ in 1:ndims(array)]), sites) + +Tensor(gate::Gate) = gate.tensor +Base.parent(gate::Gate) = Tensor(gate) +inds(gate::Gate) = inds(Tensor(gate)) + +sites(gate::Gate; kwargs...) = sites(sort_nt(values(kwargs)), gate) +sites(::@NamedTuple{}, gate::Gate) = Tuple(gate.sites) + +function sites(kwargs::@NamedTuple{set::Symbol}, gate::Gate) + pred = if kwargs.set === :outputs + !isdual + elseif kwargs.set === :inputs + isdual + else + throw(ArgumentError("Expected set to be one of `:inputs` or `:outputs`, but got $(kwargs.set)")) + end + return filter(pred, sites(gate)) +end + +lanes(gate::Gate) = unique(Iterators.map(Tenet.Lane, sites(gate))) + +Base.replace(gate::Gate, old_new::Pair{Symbol,Symbol}...) = Gate(replace(Tensor(gate), old_new...), sites(gate)) + +function Base.replace(gate::Gate, old_new::Pair{Site,Symbol}...) + mapping = ImmutableDict(Pair.(sites(gate), inds(gate))) + old_new = map(old_new) do (old, new) + mapping[old] => new + end + return replace(gate, old_new...) +end + +resetindex(gate::Gate) = replace(gate, [ind => gensym(:i) for ind in inds(gate)]...) + +struct Circuit <: AbstractQuantum + tn::TensorNetwork + moments::BijectiveIdDict{Moment,Symbol} # mapping between indices and `Moment`s + inputs::Dict{Lane,Int} + outputs::Dict{Lane,Int} # current moment for each lane +end + +Circuit() = Circuit(TensorNetwork(), BijectiveIdDict{Symbol,Moment}(), Dict(), Dict()) + +TensorNetwork(circuit::Circuit) = circuit.tn +# TODO conversion between `Quantum and `Circuit` + +function moment(circuit::Circuit, site::Site) + lane = Lane(site) + t = isdual(site) ? circuit.inputs[lane] : circuit.outputs[lane] + return Moment(lane, t) +end + +inds(kwargs::@NamedTuple{at::Site}, circuit::Circuit) = circuit.moments[moment(circuit, kwargs.at)] + +# NOTE `tensors(; at)` is implemented in `Quantum.jl` for `AbstractQuantum` + +function sites(::@NamedTuple{}, circuit::Circuit) + keys(circuit.inputs) ∪ keys(circuit.outputs) +end + +function sites(::@NamedTuple{set::Symbol}, circuit::Circuit) + if set === :inputs + return Site.(keys(circuit.inputs); dual=true) + elseif set === :outputs + return Site.(keys(circuit.outputs)) + else + throw(ArgumentError("Expected set to be one of `:inputs` or `:outputs`, but got $(set)")) + end +end + +# NOTE `lanes` is implemented in `Quantum.jl` for `AbstractQuantum` + +function Base.push!(circuit::Circuit, gate::Gate) + # only gates with same input/output lanes are allowed, even if `Circuit` could support unbalanced inputs/outputs + @assert issetequal(sites(gate; set=:outputs), adjoint.(sites(gate; set=:inputs))) + + connecting_lanes = lanes(gate) ∩ Lane.(sites(circuit; set=:outputs)) + new_lanes = setdiff(lanes(gate), connecting_lanes) + + # reindex gate to match circuit indices + gate = replace(gate, [site' => inds(circuit; at=site) for site in Iterators.map(Site, connecting_lanes)]) + + # add gate to circuit + push!(TensorNetwork(circuit), Tensor(gate)) + + # update moments: point to new output indices + for lane in new_lanes + circuit.moments[Moment(lane, 1)] = inds(gate; at=Site(lane; dual=true)) + circuit.moments[Moment(lane, 2)] = inds(gate; at=Site(lane)) + circuit.inputs[lane] = 1 + circuit.outputs[lane] = 2 + end + + for lane in connecting_lanes + circuit.outputs[lane] += 1 + t = circuit.outputs[lane] + moment = Moment(lane, t) + circuit.moments[moment] = inds(gate; at=Site(lane)) + end + + return circuit +end + +# TODO iterative walk through the gates diff --git a/src/Quantum.jl b/src/Quantum.jl index e2b150c2c..6beff1d52 100644 --- a/src/Quantum.jl +++ b/src/Quantum.jl @@ -302,7 +302,7 @@ Return the number of lanes of a [`AbstractQuantum`](@ref) Tensor Network. """ nlanes(tn::AbstractQuantum) = length(lanes(tn)) -function addsite!(tn::AbstractQuantum, site, index) +function addsite!(tn::Quantum, site, index) tn = Quantum(tn) if haskey(tn.sites, site) error("Site $site already exists") @@ -315,7 +315,7 @@ function addsite!(tn::AbstractQuantum, site, index) return tn.sites[site] = index end -function rmsite!(tn::AbstractQuantum, site) +function rmsite!(tn::Quantum, site) tn = Quantum(tn) if !haskey(tn.sites, site) error("Site $site does not exist") diff --git a/src/Site.jl b/src/Site.jl index 15665f54d..57bdc3945 100644 --- a/src/Site.jl +++ b/src/Site.jl @@ -1,37 +1,66 @@ +abstract type AbstractLane end + """ - Site(id[; dual = false]) - Site(i1, i2, ...[; dual = false]) - site"i,j,...[']" + Lane(id) + Lane(i, j, ...) + lane"i,j,..." -Represents the location of a physical index. `Site` objects are used to label the indices of tensors in a [`Quantum`](@ref) tensor network. -They are +Represents the location of a physical index. -See also: [`sites`](@ref), [`id`](@ref), [`isdual`](@ref) +See also: [`Site`](@ref), [`lanes`](@ref) """ -struct Site{N} +struct Lane{N} id::NTuple{N,Int} - dual::Bool - Site(id::NTuple{N,Int}; dual=false) where {N} = new{N}(id, dual) + Lane(id::NTuple{N,Int}) where {N} = new{N}(id) end -Site(id::Int; kwargs...) = Site((id,); kwargs...) -Site(id::Vararg{Int,N}; kwargs...) where {N} = Site(id; kwargs...) +Lane(lane::Lane) = lane +Lane(id::Int) = Lane((id,)) +Lane(id::Vararg{Int,N}) where {N} = Lane(id) -Base.copy(x::Site) = x +Base.copy(x::Lane) = x """ - id(site::Site) + id(lane::AbstractLane) -Returns the coordinate location of the `site`. +Returns the coordinate location of the `lane`. See also: [`lanes`](@ref) """ function id end -id(site::Site{1}) = only(site.id) -id(site::Site) = site.id +id(lane::Lane{1}) = only(lane.id) +id(lane::Lane) = lane.id +id(lane::AbstractLane) = id(Lane(lane)) -Base.CartesianIndex(site::Site) = CartesianIndex(id(site)) +Base.CartesianIndex(lane::AbstractLane) = CartesianIndex(id(lane)) + +Base.isless(a::AbstractLane, b::AbstractLane) = id(a) < id(b) + +""" + Site(id[; dual = false]) + Site(i, j, ...[; dual = false]) + site"i,j,...[']" + +Represents a [`Lane`](@ref) with an annotation of input or output. +`Site` objects are used to label the indices of tensors in a [`Quantum`](@ref) Tensor Network. + +See also: [`Lane`](@ref), [`sites`](@ref), [`isdual`](@ref) +""" +struct Site{N} <: AbstractLane + lane::Lane{N} + dual::Bool + + Site(lane::Lane{N}; dual=false) where {N} = new{N}(lane, dual) +end + +Site(id::Int; kwargs...) = Site(Lane(id); kwargs...) +Site(@nospecialize(id::NTuple{N,Int}); kwargs...) where {N} = Site(Lane(id); kwargs...) +Site(@nospecialize(id::Vararg{Int,N}); kwargs...) where {N} = Site(Lane(id); kwargs...) + +Lane(site::Site) = site.lane + +Base.copy(x::Site) = x """ isdual(site::Site) @@ -49,20 +78,35 @@ Base.show(io::IO, site::Site) = print(io, "$(id(site))$(site.dual ? "'" : "")") Returns the adjoint of `site`, i.e. a new `Site` object with the same coordinates as `site` but with the `dual` flag flipped (so an _input_ site becomes an _output_ site and vice versa). """ Base.adjoint(site::Site) = Site(id(site); dual=!site.dual) -Base.isless(a::Site, b::Site) = id(a) < id(b) + +""" + lane"i,j,..." + +Constructs a `Lane` object with the given coordinates. The coordinates are given as a comma-separated list of integers. + +See also: [`Lane`](@ref), [`@site_str`](@ref) +""" +macro lane_str(str) + m = match(r"^(\d+,)*\d+$", str) + isnothing(m) && error("Invalid site string: $str") + + id = tuple(map(eachmatch(r"(\d+)", str)) do match + parse(Int, only(match.captures)) + end...) + + return :(Lane($id)) +end """ site"i,j,...[']" Constructs a `Site` object with the given coordinates. The coordinates are given as a comma-separated list of integers. Optionally, a trailing `'` can be added to indicate that the site is a dual site (i.e. an "input"). -See also: [`Site`](@ref) +See also: [`Site`](@ref), [`@lane_str`](@ref) """ macro site_str(str) m = match(r"^(\d+,)*\d+('?)$", str) - if isnothing(m) - error("Invalid site string: $str") - end + isnothing(m) && error("Invalid site string: $str") id = tuple(map(eachmatch(r"(\d+)", str)) do match parse(Int, only(match.captures)) @@ -74,3 +118,16 @@ macro site_str(str) end Base.zero(x::Dict{Site,Symbol}) = x + +struct Moment <: AbstractLane + lane::Lane + t::Int +end + +Moment(lane::L, t) where {L<:AbstractLane} = Moment{L}(lane, t) + +Lane(x::Moment) = Lane(x.lane) + +struct Bond{L<:AbstractLane} + lanes::NTuple{2,L} +end diff --git a/src/Tenet.jl b/src/Tenet.jl index 0f02ed9a9..955e4db45 100644 --- a/src/Tenet.jl +++ b/src/Tenet.jl @@ -29,13 +29,17 @@ export transform, transform! #! format: on include("Site.jl") +export Lane, @lane_str export Site, @site_str, isdual -@compat public id +@compat public id Moment include("Quantum.jl") export Quantum, ninputs, noutputs, inputs, outputs, sites, lanes, socket @compat public AbstractQuantum, Socket, Scalar, State, Operator, reindex!, @reindex!, nsites, nlanes, hassite +include("Circuit.jl") +export Gate, Circuit + include("Lattice.jl") @compat public Lattice @@ -69,6 +73,8 @@ include("MPS.jl") export MPS, MPO @compat public AbstractMPS, AbstractMPO, defaultorder, check_form +include("PEPS.jl") + # reexports from EinExprs export einexpr, inds From 2f25f96d6ae2f9dbbe7710ae3d7104f4e7ab3f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 12 Jan 2025 21:31:13 +0100 Subject: [PATCH 02/13] Test new types --- src/Tenet.jl | 2 +- test/Circuit_test.jl | 147 +++++++++++++++++++++++++++++++++++++++++++ test/Gate_test.jl | 26 ++++++++ test/Lane_test.jl | 19 ++++++ test/Moment_test.jl | 23 +++++++ test/Site_test.jl | 12 ++++ test/runtests.jl | 4 ++ 7 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 test/Circuit_test.jl create mode 100644 test/Gate_test.jl create mode 100644 test/Lane_test.jl create mode 100644 test/Moment_test.jl diff --git a/src/Tenet.jl b/src/Tenet.jl index 955e4db45..b406c3626 100644 --- a/src/Tenet.jl +++ b/src/Tenet.jl @@ -31,7 +31,7 @@ export transform, transform! include("Site.jl") export Lane, @lane_str export Site, @site_str, isdual -@compat public id Moment +@compat public id, Moment include("Quantum.jl") export Quantum, ninputs, noutputs, inputs, outputs, sites, lanes, socket diff --git a/test/Circuit_test.jl b/test/Circuit_test.jl new file mode 100644 index 000000000..88810bf06 --- /dev/null +++ b/test/Circuit_test.jl @@ -0,0 +1,147 @@ +@testset "Circuit" begin + # test empty circuit + @testset let circuit = Circuit() + @test TensorNetwork(circuit) == TensorNetwork() + @test isempty(sites(circuit)) + @test isempty(lanes(circuit)) + @test isempty(inds(circuit; set=:inputs)) + @test isempty(inds(circuit; set=:outputs)) + @test isempty(inds(circuit; set=:physical)) + @test isempty(inds(circuit; set=:virtual)) + end + + # test applying a single lane gate + @testset let circuit = Circuit(), gate = Gate(zeros(2, 2), [site"1'", site"1"]) + push!(circuit, gate) + @test issetequal(sites(circuit), [site"1'", site"1"]) + @test issetequal(lanes(circuit), [Lane(1)]) + @test issetequal(sites(circuit; set=:inputs), [site"1'"]) + @test issetequal(sites(circuit; set=:outputs), [site"1"]) + @test Tenet.ninds(circuit) == 2 + @test Tenet.ninds(circuit; set=:inputs) == 1 + @test Tenet.ninds(circuit; set=:outputs) == 1 + @test Tenet.ninds(circuit; set=:physical) == 2 + @test Tenet.ninds(circuit; set=:virtual) == 0 + @test Tenet.ntensors(circuit) == 1 + @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) + @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) + @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) + end + + # test applying a gate with multiple lanes + @testset let circuit = Circuit(), gate = Gate(zeros(2, 2, 2, 2), [site"1'", site"2'", site"1", site"2"]) + push!(circuit, gate) + @test issetequal(sites(circuit), [site"1'", site"1"]) + @test issetequal(lanes(circuit), [Lane(1), Lane(2)]) + @test issetequal(sites(circuit; set=:inputs), [site"1'", site"2'"]) + @test issetequal(sites(circuit; set=:outputs), [site"1", site"2"]) + @test Tenet.ninds(circuit) == 4 + @test Tenet.ninds(circuit; set=:inputs) == 2 + @test Tenet.ninds(circuit; set=:outputs) == 2 + @test Tenet.ninds(circuit; set=:physical) == 4 + @test Tenet.ninds(circuit; set=:virtual) == 0 + @test Tenet.ntensors(circuit) == 1 + @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) + @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) + @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) + @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) + @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + end + + # test applying two gates in the same lane + @testset let circuit = Circuit(), + gate1 = Gate(zeros(2, 2), [site"1'", site"1"]), + gate2 = Gate(ones(2, 2), [site"1'", site"1"]) + + push!(circuit, gate1) + push!(circuit, gate2) + @test issetequal(sites(circuit), [site"1'", site"1"]) + @test issetequal(lanes(circuit), [Lane(1)]) + @test issetequal(sites(circuit; set=:inputs), [site"1'"]) + @test issetequal(sites(circuit; set=:outputs), [site"1"]) + @test Tenet.ninds(circuit) == 3 + @test Tenet.ninds(circuit; set=:inputs) == 1 + @test Tenet.ninds(circuit; set=:outputs) == 1 + @test Tenet.ninds(circuit; set=:physical) == 2 + @test Tenet.ninds(circuit; set=:virtual) == 1 + @test Tenet.ntensors(circuit) == 2 + @test parent(tensors(circuit; at=site"1'")) == parent(Tensor(gate1)) + @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate2)) + @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) + @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) + end + + # test applying the same gate twice + @testset let circuit = Circuit(), gate = Gate(zeros(2, 2), [site"1'", site"1"]) + push!(circuit, gate) + push!(circuit, gate) + @test issetequal(sites(circuit), [site"1'", site"1"]) + @test issetequal(lanes(circuit), [Lane(1)]) + @test issetequal(sites(circuit; set=:inputs), [site"1'"]) + @test issetequal(sites(circuit; set=:outputs), [site"1"]) + @test Tenet.ninds(circuit) == 3 + @test Tenet.ninds(circuit; set=:inputs) == 1 + @test Tenet.ninds(circuit; set=:outputs) == 1 + @test Tenet.ninds(circuit; set=:physical) == 2 + @test Tenet.ninds(circuit; set=:virtual) == 1 + @test Tenet.ntensors(circuit) == 2 + @test parent(tensors(circuit; at=site"1'")) == parent(Tensor(gate)) + @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) + @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) + @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) + end + + # test applying two gates in different lanes + @testset let circuit = Circuit(), + gate1 = Gate(zeros(2, 2), [site"1'", site"1"]), + gate2 = Gate(ones(2, 2), [site"2'", site"2"]) + + push!(circuit, gate1) + push!(circuit, gate2) + @test issetequal(sites(circuit), [site"1'", site"1", site"2'", site"2"]) + @test issetequal(lanes(circuit), [Lane(1), Lane(2)]) + @test issetequal(sites(circuit; set=:inputs), [site"1'", site"2'"]) + @test issetequal(sites(circuit; set=:outputs), [site"1", site"2"]) + @test Tenet.ninds(circuit) == 4 + @test Tenet.ninds(circuit; set=:inputs) == 2 + @test Tenet.ninds(circuit; set=:outputs) == 2 + @test Tenet.ninds(circuit; set=:physical) == 4 + @test Tenet.ninds(circuit; set=:virtual) == 0 + @test Tenet.ntensors(circuit) == 2 + @test parent(tensors(circuit; at=site"1'")) == parent(Tensor(gate1)) + @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate1)) + @test parent(tensors(circuit; at=site"2'")) == parent(Tensor(gate2)) + @test parent(tensors(circuit; at=site"2")) == parent(Tensor(gate2)) + @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) + @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) + @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) + @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + end + + # test applying two gates with a shared lane + @testset let circuit = Circuit(), + gate1 = Gate(zeros(2, 2), [site"1'", site"1"]), + gate2 = Gate(ones(2, 2, 2, 2), [site"1'", site"2'", site"1", site"2"]) + + push!(circuit, gate1) + push!(circuit, gate2) + @test issetequal(sites(circuit), [site"1'", site"1", site"2'", site"2"]) + @test issetequal(lanes(circuit), [Lane(1), Lane(2)]) + @test issetequal(sites(circuit; set=:inputs), [site"1'", site"2'"]) + @test issetequal(sites(circuit; set=:outputs), [site"1", site"2"]) + @test Tenet.ninds(circuit) == 5 + @test Tenet.ninds(circuit; set=:inputs) == 2 + @test Tenet.ninds(circuit; set=:outputs) == 2 + @test Tenet.ninds(circuit; set=:physical) == 4 + @test Tenet.ninds(circuit; set=:virtual) == 1 + @test Tenet.ntensors(circuit) == 2 + @test parent(tensors(circuit; at=site"1'")) == parent(Tensor(gate1)) + @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate2)) + @test parent(tensors(circuit; at=site"2'")) == parent(Tensor(gate2)) + @test parent(tensors(circuit; at=site"2")) == parent(Tensor(gate2)) + @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) + @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) + @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) + @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + end +end diff --git a/test/Gate_test.jl b/test/Gate_test.jl new file mode 100644 index 000000000..e1f6635eb --- /dev/null +++ b/test/Gate_test.jl @@ -0,0 +1,26 @@ +@testset "Gate" begin + @testset let data = zeros(2, 2), tensor = Tensor(data, (:i, :j)) + gate = Gate(tensor, [site"1'", site"1"]) + @test gate isa Gate + @test Tensor(gate) == tensor + @test Tensor(gate) == tensor + @test inds(gate) == inds(tensor) + @test sites(gate) == (site"1'", site"1") + @test lanes(gate) == (Lane(1),) + @test sites(gate; set=:inputs) == (site"1'",) + @test sites(gate; set=:outputs) == (site"1",) + @test replace(gate, :i => :k) == Gate(Tensor(data, (:k, :j)), [site"1'", site"1"]) + @test replace(gate, site"1'" => :k) == Gate(Tensor(data, (:k, :j)), [site"1'", site"1"]) + + gate = Tenet.resetindex(gate) + @test gate isa Gate + @test parent(Tensor(gate)) == data + @test isdisjoint(inds(gate), inds(tensor)) + @test sites(gate) == (site"1'", site"1") + + gate = Gate(data, [site"1'", site"1"]) + @test gate isa Gate + @test parent(Tensor(gate)) == data + @test sites(gate) == (site"1'", site"1") + end +end \ No newline at end of file diff --git a/test/Lane_test.jl b/test/Lane_test.jl new file mode 100644 index 000000000..d1ea6983a --- /dev/null +++ b/test/Lane_test.jl @@ -0,0 +1,19 @@ +@testset "Lane" begin + using Tenet: id + + lane = Lane(1) + @test id(lane) == 1 + @test CartesianIndex(lane) == CartesianIndex(1) + + lane = Lane(1, 2) + @test id(lane) == (1, 2) + @test CartesianIndex(lane) == CartesianIndex((1, 2)) + + lane = lane"1" + @test id(lane) == 1 + @test CartesianIndex(lane) == CartesianIndex(1) + + lane = lane"1,2" + @test id(lane) == (1, 2) + @test CartesianIndex(lane) == CartesianIndex((1, 2)) +end diff --git a/test/Moment_test.jl b/test/Moment_test.jl new file mode 100644 index 000000000..9715c0a44 --- /dev/null +++ b/test/Moment_test.jl @@ -0,0 +1,23 @@ +@testset "Moment" begin + using Tenet: id + + moment = Moment(Lane(1), 7) + @test Lane(moment) == Lane(1) + @test id(lane) == 1 + @test moment.t == 7 + + moment = Moment(Lane(1, 2), 7) + @test Lane(moment) == Lane(1, 2) + @test id(lane) == (1, 2) + @test moment.t == 7 + + moment = Moment(lane"1", 7) + @test Lane(moment) == Lane(1) + @test id(lane) == 1 + @test moment.t == 7 + + moment = Moment(lane"1,2", 7) + @test Lane(moment) == Lane(1, 2) + @test id(lane) == (1, 2) + @test moment.t == 7 +end diff --git a/test/Site_test.jl b/test/Site_test.jl index c5b8137ee..e7e7e631b 100644 --- a/test/Site_test.jl +++ b/test/Site_test.jl @@ -2,61 +2,73 @@ using Tenet: id s = Site(1) + @test Lane(s) == Lane(1) @test id(s) == 1 @test CartesianIndex(s) == CartesianIndex(1) @test isdual(s) == false s = Site(1; dual=true) + @test Lane(s) == Lane(1) @test id(s) == 1 @test CartesianIndex(s) == CartesianIndex(1) @test isdual(s) == true s = Site(1, 2) + @test Lane(s) == Lane(1, 2) @test id(s) == (1, 2) @test CartesianIndex(s) == CartesianIndex((1, 2)) @test isdual(s) == false s = Site(1, 2; dual=true) + @test Lane(s) == Lane(1, 2) @test id(s) == (1, 2) @test CartesianIndex(s) == CartesianIndex((1, 2)) @test isdual(s) == true s = site"1" + @test Lane(s) == Lane(1) @test id(s) == 1 @test CartesianIndex(s) == CartesianIndex(1) @test isdual(s) == false s = site"1'" + @test Lane(s) == Lane(1) @test id(s) == 1 @test CartesianIndex(s) == CartesianIndex(1) @test isdual(s) == true s = site"1,2" + @test Lane(s) == Lane(1, 2) @test id(s) == (1, 2) @test CartesianIndex(s) == CartesianIndex((1, 2)) @test isdual(s) == false s = site"1,2'" + @test Lane(s) == Lane(1, 2) @test id(s) == (1, 2) @test CartesianIndex(s) == CartesianIndex((1, 2)) @test isdual(s) == true s = adjoint(site"1") + @test Lane(s) == Lane(1) @test id(s) == 1 @test CartesianIndex(s) == CartesianIndex(1) @test isdual(s) == true s = adjoint(site"1'") + @test Lane(s) == Lane(1) @test id(s) == 1 @test CartesianIndex(s) == CartesianIndex(1) @test isdual(s) == false s = adjoint(site"1,2") + @test Lane(s) == Lane(1, 2) @test id(s) == (1, 2) @test CartesianIndex(s) == CartesianIndex((1, 2)) @test isdual(s) == true s = adjoint(site"1,2'") + @test Lane(s) == Lane(1, 2) @test id(s) == (1, 2) @test CartesianIndex(s) == CartesianIndex((1, 2)) @test isdual(s) == false diff --git a/test/runtests.jl b/test/runtests.jl index cf9f4de92..33315e536 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,8 +12,12 @@ include("Utils.jl") include("Numerics_test.jl") include("TensorNetwork_test.jl") include("Transformations_test.jl") + include("Lane_test.jl") include("Site_test.jl") + include("Moment_test.jl") include("Quantum_test.jl") + include("Gate_test.jl") + include("Circuit_test.jl") include("Lattice_test.jl") include("Ansatz_test.jl") include("Product_test.jl") From 139556b3b2edc3e44c5d6b92d1922907df9456c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 12 Jan 2025 21:35:55 +0100 Subject: [PATCH 03/13] last fixes --- src/Circuit.jl | 18 ++++++++++-------- src/Quantum.jl | 16 ++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Circuit.jl b/src/Circuit.jl index 08c18cb58..8141146b0 100644 --- a/src/Circuit.jl +++ b/src/Circuit.jl @@ -1,4 +1,4 @@ -using BijectiveDicts: BijectiveIdDict +using BijectiveDicts: BijectiveDict struct Gate tensor::Tensor @@ -48,12 +48,12 @@ resetindex(gate::Gate) = replace(gate, [ind => gensym(:i) for ind in inds(gate)] struct Circuit <: AbstractQuantum tn::TensorNetwork - moments::BijectiveIdDict{Moment,Symbol} # mapping between indices and `Moment`s + moments::BijectiveDict{Moment,Symbol} # mapping between indices and `Moment`s inputs::Dict{Lane,Int} outputs::Dict{Lane,Int} # current moment for each lane end -Circuit() = Circuit(TensorNetwork(), BijectiveIdDict{Symbol,Moment}(), Dict(), Dict()) +Circuit() = Circuit(TensorNetwork(), BijectiveDict(Dict{Moment,Symbol}(), Dict{Symbol,Moment}()), Dict(), Dict()) TensorNetwork(circuit::Circuit) = circuit.tn # TODO conversion between `Quantum and `Circuit` @@ -66,19 +66,20 @@ end inds(kwargs::@NamedTuple{at::Site}, circuit::Circuit) = circuit.moments[moment(circuit, kwargs.at)] +# NOTE `inds(; set)` is implemented in `Quantum.jl` for `AbstractQuantum` # NOTE `tensors(; at)` is implemented in `Quantum.jl` for `AbstractQuantum` function sites(::@NamedTuple{}, circuit::Circuit) - keys(circuit.inputs) ∪ keys(circuit.outputs) + Site[Site.(keys(circuit.inputs); dual=true)..., Site.(keys(circuit.outputs))...] end -function sites(::@NamedTuple{set::Symbol}, circuit::Circuit) - if set === :inputs +function sites(kwargs::@NamedTuple{set::Symbol}, circuit::Circuit) + if kwargs.set === :inputs return Site.(keys(circuit.inputs); dual=true) - elseif set === :outputs + elseif kwargs.set === :outputs return Site.(keys(circuit.outputs)) else - throw(ArgumentError("Expected set to be one of `:inputs` or `:outputs`, but got $(set)")) + throw(ArgumentError("Expected set to be one of `:inputs` or `:outputs`, but got $(kwargs.set)")) end end @@ -92,6 +93,7 @@ function Base.push!(circuit::Circuit, gate::Gate) new_lanes = setdiff(lanes(gate), connecting_lanes) # reindex gate to match circuit indices + gate = resetindex(gate) gate = replace(gate, [site' => inds(circuit; at=site) for site in Iterators.map(Site, connecting_lanes)]) # add gate to circuit diff --git a/src/Quantum.jl b/src/Quantum.jl index 6beff1d52..d74fa7105 100644 --- a/src/Quantum.jl +++ b/src/Quantum.jl @@ -124,12 +124,14 @@ inds(kwargs::NamedTuple{(:at,)}, tn::AbstractQuantum) = Quantum(tn).sites[kwargs function inds(kwargs::NamedTuple{(:set,)}, tn::AbstractQuantum) if kwargs.set === :physical - return collect(values(Quantum(tn).sites)) + return map(sites(tn)) do site + inds(tn; at=site)::Symbol + end elseif kwargs.set === :virtual - return setdiff(inds(tn), values(Quantum(tn).sites)) + return setdiff(inds(tn), inds(tn; set=:physical)) elseif kwargs.set ∈ (:inputs, :outputs) return map(sites(tn; kwargs.set)) do site - inds(tn; at=site) + inds(tn; at=site)::Symbol end else return inds(TensorNetwork(tn); set=kwargs.set) @@ -287,13 +289,7 @@ nsites(tn::AbstractQuantum; kwargs...) = length(sites(tn; kwargs...)) Return the lanes of a [`AbstractQuantum`](@ref) Tensor Network. """ -function lanes(tn::AbstractQuantum) - return unique( - Iterators.map(Iterators.flatten([sites(tn; set=:inputs), sites(tn; set=:outputs)])) do site - isdual(site) ? site' : site - end, - ) -end +lanes(tn::AbstractQuantum) = unique!(Lane[Lane.(sites(tn; set=:inputs))..., Lane.(sites(tn; set=:outputs))...]) """ nlanes(q::AbstractQuantum) From 5d939c46d3d6f911792edafc15076c96a644653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 12 Jan 2025 21:57:16 +0100 Subject: [PATCH 04/13] remove PEPS.jl inclusion --- src/Tenet.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tenet.jl b/src/Tenet.jl index b406c3626..c0b65b8ea 100644 --- a/src/Tenet.jl +++ b/src/Tenet.jl @@ -73,8 +73,6 @@ include("MPS.jl") export MPS, MPO @compat public AbstractMPS, AbstractMPO, defaultorder, check_form -include("PEPS.jl") - # reexports from EinExprs export einexpr, inds From 44e24271f37e913ff19008124f12c71e61265899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 12 Jan 2025 22:05:15 +0100 Subject: [PATCH 05/13] Add `moments` --- src/Circuit.jl | 2 ++ src/Tenet.jl | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Circuit.jl b/src/Circuit.jl index 8141146b0..661e2362b 100644 --- a/src/Circuit.jl +++ b/src/Circuit.jl @@ -58,6 +58,8 @@ Circuit() = Circuit(TensorNetwork(), BijectiveDict(Dict{Moment,Symbol}(), Dict{S TensorNetwork(circuit::Circuit) = circuit.tn # TODO conversion between `Quantum and `Circuit` +moments(circuit::Circuit) = collect(keys(circuit.moments)) + function moment(circuit::Circuit, site::Site) lane = Lane(site) t = isdual(site) ? circuit.inputs[lane] : circuit.outputs[lane] diff --git a/src/Tenet.jl b/src/Tenet.jl index c0b65b8ea..ec9bc80ac 100644 --- a/src/Tenet.jl +++ b/src/Tenet.jl @@ -38,7 +38,7 @@ export Quantum, ninputs, noutputs, inputs, outputs, sites, lanes, socket @compat public AbstractQuantum, Socket, Scalar, State, Operator, reindex!, @reindex!, nsites, nlanes, hassite include("Circuit.jl") -export Gate, Circuit +export Gate, Circuit, moments include("Lattice.jl") @compat public Lattice From 430c26baa2036cfed0e210ded82bef1b2f42209e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 12 Jan 2025 22:51:13 +0100 Subject: [PATCH 06/13] Update integrations to use new `Circuit` --- ext/TenetPythonCallExt/Cirq.jl | 39 +++++++---------------- ext/TenetPythonCallExt/Qibo.jl | 33 ++++++-------------- ext/TenetPythonCallExt/Qiskit.jl | 33 +++++--------------- ext/TenetYaoBlocksExt.jl | 43 +++++++++----------------- src/Circuit.jl | 7 ++++- test/Circuit_test.jl | 28 +++++++++++++++++ test/integration/YaoBlocks_test.jl | 32 +++++++++---------- test/integration/python/test_cirq.jl | 23 +++++++++----- test/integration/python/test_qibo.jl | 12 ++++--- test/integration/python/test_qiskit.jl | 12 ++++--- 10 files changed, 123 insertions(+), 139 deletions(-) diff --git a/ext/TenetPythonCallExt/Cirq.jl b/ext/TenetPythonCallExt/Cirq.jl index c0a5f2d05..32421bc1c 100644 --- a/ext/TenetPythonCallExt/Cirq.jl +++ b/ext/TenetPythonCallExt/Cirq.jl @@ -1,45 +1,30 @@ -function Base.convert(::Type{Site}, ::Val{Symbol("cirq.devices.line_qubit.LineQubit")}, pyobj::Py) - Site(pyconvert(Int, pyobj.x)) +function Base.convert(::Type{Lane}, ::Val{Symbol("cirq.devices.line_qubit.LineQubit")}, pyobj::Py) + Lane(pyconvert(Int, pyobj.x)) end -function Base.convert(::Type{Site}, ::Val{Symbol("cirq.devices.grid_qubit.GridQubit")}, pyobj::Py) - Site((pyconvert(Int, pyobj.row), pyconvert(Int, pyobj.col))) +function Base.convert(::Type{Lane}, ::Val{Symbol("cirq.devices.grid_qubit.GridQubit")}, pyobj::Py) + Lane((pyconvert(Int, pyobj.row), pyconvert(Int, pyobj.col))) end -function Base.convert(::Type{Quantum}, ::Val{:cirq}, pyobj::Py) +function Base.convert(::Type{Circuit}, ::Val{:cirq}, pyobj::Py) cirq = pyimport("cirq") if !pyissubclass(pytype(pyobj), cirq.circuits.circuit.Circuit) throw(ArgumentError("Expected a cirq.circuits.circuit.Circuit object, got $(pyfullyqualname(pyobj))")) end - gen = Tenet.IndexCounter() - - wire = Dict(qubit => [Tenet.nextindex!(gen)] for qubit in pyobj.all_qubits()) - tn = TensorNetwork() + circuit = Circuit() for moment in pyobj for gate in moment.operations - matrix = pyconvert(Array, cirq.unitary(gate)) - - array = reshape(matrix, fill(2, 2 * length(gate.qubits))...) + gatelanes = convert.(Lane, gate.qubits) + gatesites = [Site.(gatelanes; dual=true)..., Site.(gatelanes)...] - inds = (x -> collect(Iterators.flatten(zip(x...))))( - map(gate.qubits) do l - from, to = last(wire[l]), Tenet.nextindex!(gen) - push!(wire[l], to) - (from, to) - end, - ) + matrix = pyconvert(Array, cirq.unitary(gate)) + array = reshape(matrix, fill(2, length(gatesites))...) - tensor = Tensor(array, Tuple(inds)) - push!(tn, tensor) + push!(circuit, Gate(array, gatesites)) end end - sites = merge( - Dict([convert(Site, qubit)' => first(indices) for (qubit, indices) in wire if first(indices) ∈ tn]), - Dict([convert(Site, qubit) => last(indices) for (qubit, indices) in wire if last(indices) ∈ tn]), - ) - - return Quantum(tn, sites) + return circuit end diff --git a/ext/TenetPythonCallExt/Qibo.jl b/ext/TenetPythonCallExt/Qibo.jl index d4cf514ae..ede4918b8 100644 --- a/ext/TenetPythonCallExt/Qibo.jl +++ b/ext/TenetPythonCallExt/Qibo.jl @@ -1,40 +1,25 @@ -function Base.convert(::Type{Quantum}, ::Val{:qibo}, pyobj::Py) +using Tenet: Gate + +function Base.convert(::Type{Circuit}, ::Val{:qibo}, pyobj::Py) qibo = pyimport("qibo") qibo.set_backend("numpy") if !pyissubclass(pytype(pyobj), qibo.models.circuit.Circuit) throw(ArgumentError("Expected a qibo.models.circuit.Circuit object, got $(pyfullyqualname(pyobj))")) end - n = pyconvert(Int, pyobj.nqubits) - gen = Tenet.IndexCounter() - - wire = [[Tenet.nextindex!(gen)] for _ in 1:n] - tn = TensorNetwork() + circuit = Circuit() circgates = pyobj.queue for gate in circgates matrix = pyconvert(Array, gate.matrix()) - qubits = map(x -> pyconvert(Int, x), gate.qubits) - array = reshape(matrix, fill(2, 2 * length(qubits))...) + gatelanes = map(x -> Lane(pyconvert(Int, x)), gate.qubits) + gatesites = [Site.(gatelanes; dual=true)..., Site.(gatelanes)...] - inds = (x -> collect(Iterators.flatten(zip(x...))))( - map(qubits) do l - l += 1 - from, to = last(wire[l]), Tenet.nextindex!(gen) - push!(wire[l], to) - (from, to) - end, - ) + array = reshape(matrix, fill(2, length(gatesites))...) - tensor = Tensor(array, Tuple(inds)) - push!(tn, tensor) + push!(circuit, Gate(array, gatesites)) end - sites = merge( - Dict([Site(site; dual=true) => first(index) for (site, index) in enumerate(wire) if first(index) ∈ tn]), - Dict([Site(site; dual=false) => last(index) for (site, index) in enumerate(wire) if last(index) ∈ tn]), - ) - - return Quantum(tn, sites) + return circuit end diff --git a/ext/TenetPythonCallExt/Qiskit.jl b/ext/TenetPythonCallExt/Qiskit.jl index 59887b359..f07d209d9 100644 --- a/ext/TenetPythonCallExt/Qiskit.jl +++ b/ext/TenetPythonCallExt/Qiskit.jl @@ -8,13 +8,12 @@ function Base.convert(::Type{Quantum}, ::Val{:qiskit}, pyobj::Py) ) end - n = length(pyobj.qregs[0]) - gen = Tenet.IndexCounter() - - wire = [[Tenet.nextindex!(gen)] for _ in 1:n] - tn = TensorNetwork() + circuit = Circuit() for instr in pyobj + gatelanes = map(x -> Lane(pyconvert(Int, x._index)), instr.qubits) + gatesites = [Site.(gatelanes; dual=true)..., Site.(gatelanes)...] + # if unassigned parameters, throw matrix = if pyhasattr(instr, Py("matrix")) instr.matrix @@ -24,29 +23,11 @@ function Base.convert(::Type{Quantum}, ::Val{:qiskit}, pyobj::Py) if pyisnone(matrix) throw(ArgumentError("Expected parameters already assigned, but got $(pyobj.params)")) end - matrix = pyconvert(Array, matrix) + array = reshape(matrix, fill(2, length(gatesites))...) - qubits = map(x -> pyconvert(Int, x._index), instr.qubits) - array = reshape(matrix, fill(2, 2 * length(qubits))...) - - inds = (x -> collect(Iterators.flatten(zip(x...))))( - map(qubits) do l - l += 1 - from, to = last(wire[l]), Tenet.nextindex!(gen) - push!(wire[l], to) - (from, to) - end, - ) - - tensor = Tensor(array, Tuple(inds)) - push!(tn, tensor) + push!(circuit, Gate(array, gatesites)) end - sites = merge( - Dict([Site(site; dual=true) => first(index) for (site, index) in enumerate(wire) if first(index) ∈ tn]), - Dict([Site(site; dual=false) => last(index) for (site, index) in enumerate(wire) if last(index) ∈ tn]), - ) - - return Quantum(tn, sites) + return circuit end diff --git a/ext/TenetYaoBlocksExt.jl b/ext/TenetYaoBlocksExt.jl index 4033d532c..d92872b36 100644 --- a/ext/TenetYaoBlocksExt.jl +++ b/ext/TenetYaoBlocksExt.jl @@ -11,18 +11,18 @@ function flatten_circuit(x) end end -function Tenet.Quantum(circuit::AbstractBlock) - n = nqubits(circuit) - gen = Tenet.IndexCounter() - wire = [[Tenet.nextindex!(gen)] for _ in 1:n] - tensors = Tensor[] +function Base.convert(::Type{Circuit}, yaocirc::AbstractBlock) + circuit = Circuit() for gate in flatten_circuit(circuit) - if gate isa Swap - (a, b) = occupied_locs(gate) - wire[a], wire[b] = wire[b], wire[a] - continue - end + # if gate isa Swap + # (a, b) = occupied_locs(gate) + # wire[a], wire[b] = wire[b], wire[a] + # continue + # end + + gatelanes = Lane.(occupied_locs(gate)) + gatesites = [Site.(gatelanes; dual=true)..., Site.(gatelanes)...] # NOTE `YaoBlocks.mat` on m-site qubits still returns the operator on the full Hilbert space m = length(occupied_locs(gate)) @@ -31,27 +31,12 @@ function Tenet.Quantum(circuit::AbstractBlock) else content(gate) end - array = reshape(collect(mat(operator)), fill(nlevel(operator), 2 * nqubits(operator))...) - - inds = (x -> collect(Iterators.flatten(zip(x...))))( - map(occupied_locs(gate)) do l - from, to = last(wire[l]), Tenet.nextindex!(gen) - push!(wire[l], to) - (to, from) - end, - ) - - tensor = Tensor(array, inds) - push!(tensors, tensor) - end + array = reshape(collect(mat(operator)), fill(nlevel(operator), length(gatesites))...) - # if a wire has only one index, no gates have been applied to it - sites = merge( - Dict([Site(site; dual=true) => first(index) for (site, index) in enumerate(wire) if length(index) > 1]), - Dict([Site(site; dual=false) => last(index) for (site, index) in enumerate(wire) if length(index) > 1]), - ) + push!(circuit, Gate(array, gatesites)) + end - return Quantum(Tenet.TensorNetwork(tensors), sites) + return circuit end end diff --git a/src/Circuit.jl b/src/Circuit.jl index 661e2362b..605af8df5 100644 --- a/src/Circuit.jl +++ b/src/Circuit.jl @@ -56,7 +56,11 @@ end Circuit() = Circuit(TensorNetwork(), BijectiveDict(Dict{Moment,Symbol}(), Dict{Symbol,Moment}()), Dict(), Dict()) TensorNetwork(circuit::Circuit) = circuit.tn -# TODO conversion between `Quantum and `Circuit` + +# TODO conversion from `Quantum to `Circuit` +function Quantum(circuit::Circuit) + Quantum(TensorNetwork(circuit), Dict([site => inds(circuit; at=site) for site in sites(circuit)])) +end moments(circuit::Circuit) = collect(keys(circuit.moments)) @@ -67,6 +71,7 @@ function moment(circuit::Circuit, site::Site) end inds(kwargs::@NamedTuple{at::Site}, circuit::Circuit) = circuit.moments[moment(circuit, kwargs.at)] +inds(kwargs::@NamedTuple{at::Moment}, circuit::Circuit) = circuit.moments[kwargs.at] # NOTE `inds(; set)` is implemented in `Quantum.jl` for `AbstractQuantum` # NOTE `tensors(; at)` is implemented in `Quantum.jl` for `AbstractQuantum` diff --git a/test/Circuit_test.jl b/test/Circuit_test.jl index 88810bf06..76ce14296 100644 --- a/test/Circuit_test.jl +++ b/test/Circuit_test.jl @@ -8,6 +8,10 @@ @test isempty(inds(circuit; set=:outputs)) @test isempty(inds(circuit; set=:physical)) @test isempty(inds(circuit; set=:virtual)) + + qtn = Quantum(circuit) + @test issetequal(sites(qtn), sites(circuit)) + @test issetequal(tensors(qtn), tensors(circuit)) end # test applying a single lane gate @@ -26,6 +30,10 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) + + qtn = Quantum(circuit) + @test issetequal(sites(qtn), sites(circuit)) + @test issetequal(tensors(qtn), tensors(circuit)) end # test applying a gate with multiple lanes @@ -46,6 +54,10 @@ @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + + qtn = Quantum(circuit) + @test issetequal(sites(qtn), sites(circuit)) + @test issetequal(tensors(qtn), tensors(circuit)) end # test applying two gates in the same lane @@ -69,6 +81,10 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate2)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) + + qtn = Quantum(circuit) + @test issetequal(sites(qtn), sites(circuit)) + @test issetequal(tensors(qtn), tensors(circuit)) end # test applying the same gate twice @@ -89,6 +105,10 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) + + qtn = Quantum(circuit) + @test issetequal(sites(qtn), sites(circuit)) + @test issetequal(tensors(qtn), tensors(circuit)) end # test applying two gates in different lanes @@ -116,6 +136,10 @@ @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + + qtn = Quantum(circuit) + @test issetequal(sites(qtn), sites(circuit)) + @test issetequal(tensors(qtn), tensors(circuit)) end # test applying two gates with a shared lane @@ -143,5 +167,9 @@ @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + + qtn = Quantum(circuit) + @test issetequal(sites(qtn), sites(circuit)) + @test issetequal(tensors(qtn), tensors(circuit)) end end diff --git a/test/integration/YaoBlocks_test.jl b/test/integration/YaoBlocks_test.jl index 1684a4f79..975f7dab2 100644 --- a/test/integration/YaoBlocks_test.jl +++ b/test/integration/YaoBlocks_test.jl @@ -2,41 +2,41 @@ using YaoBlocks # NOTE qubit #3 left empty on purpose - circuit = chain(3, put(1 => X), cnot(1, 2)) - tn = Quantum(circuit) + @testset let yaocirc = chain(3, put(1 => X), cnot(1, 2)) + circuit = convert(Circuit, yaocirc) - @test issetequal(sites(tn), [site"1", site"2", site"1'", site"2'"]) - @test Tenet.ntensors(tn) == 2 + @test issetequal(sites(circuit), [site"1", site"2", site"1'", site"2'"]) + @test Tenet.ntensors(circuit) == 2 + end @testset_skip "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) + yaocirc = chain(n_qubits, put(1 => Yao.H), Yao.control(1, 2 => Yao.X), Yao.control(2, 3 => Yao.X)) + circuit = convert(Circuit, yaocirc) 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> + 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 + yaosv = apply!(zero_state(n_qubits), yaocirc) # circuit|000> + @test only(statevec(ArrayReg(bit"111"))' * statevec(yaosv)) ≈ 1 / √2 end @testset_skip "two-qubit gate" begin U = matblock(rand(ComplexF64, 4, 4); tag="U") - circuit = chain(2, put((1, 2) => U)) + yaocirc = chain(2, put((1, 2) => U)) psi = zero_state(2) - apply!(psi, circuit) + apply!(psi, yaocirc) - quantum_circuit = Quantum(circuit) + circuit = convert(Circuit, yaocirc) 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> + yaosv = apply!(zero_state(2), yaocirc) # circuit|00> - @test only(expected_value) ≈ only(statevec(ArrayReg(bit"11"))' * statevec(SV_Yao)) + @test only(expected_value) ≈ only(statevec(ArrayReg(bit"11"))' * statevec(yaosv)) end end diff --git a/test/integration/python/test_cirq.jl b/test/integration/python/test_cirq.jl index e0ba0d122..70716a5fc 100644 --- a/test/integration/python/test_cirq.jl +++ b/test/integration/python/test_cirq.jl @@ -13,10 +13,13 @@ circuit.append(cirq.H(qubits[1])) circuit.append(cirq.H(qubits[2])) - tn = convert(Quantum, circuit) - @test issetequal(sites(tn; set=:inputs), adjoint.(Site.([0, 1, 2]))) - @test issetequal(sites(tn; set=:outputs), Site.([0, 1, 2])) - @test Tenet.ntensors(tn) == 7 + circ = convert(Circuit, circuit) + @test issetequal(sites(circ; set=:inputs), Site.([0, 1, 2]; dual=true)) + @test issetequal(sites(circ; set=:outputs), Site.([0, 1, 2])) + @test Tenet.ntensors(circ) == 7 + @test issetequal( + moments(circ), [Moment.(Ref(Lane(0)), 1:4)..., Moment.(Ref(Lane(1)), 1:4)..., Moment.(Ref(Lane(2)), 1:4)...] + ) end @testset "GridQubit" begin @@ -30,9 +33,13 @@ circuit.append(cirq.H(qubits[1])) circuit.append(cirq.H(qubits[2])) - tn = convert(Quantum, circuit) - @test issetequal(sites(tn; set=:inputs), adjoint.(Site.([(0, 0), (1, 0), (2, 0)]))) - @test issetequal(sites(tn; set=:outputs), Site.([(0, 0), (1, 0), (2, 0)])) - @test Tenet.ntensors(tn) == 7 + circ = convert(Circuit, circuit) + @test issetequal(sites(circ; set=:inputs), Site.([(0, 0), (1, 0), (2, 0)]; dual=true)) + @test issetequal(sites(circ; set=:outputs), Site.([(0, 0), (1, 0), (2, 0)])) + @test Tenet.ntensors(circ) == 7 + @test issetequal( + moments(circ), + [Moment.(Ref(Lane(0, 0)), 1:4)..., Moment.(Ref(Lane(1, 0)), 1:4)..., Moment.(Ref(Lane(2, 0)), 1:4)...], + ) end end diff --git a/test/integration/python/test_qibo.jl b/test/integration/python/test_qibo.jl index ae453999f..29e674287 100644 --- a/test/integration/python/test_qibo.jl +++ b/test/integration/python/test_qibo.jl @@ -11,8 +11,12 @@ circuit.add(qibo.gates.H(1)) circuit.add(qibo.gates.H(2)) - tn = convert(Quantum, circuit) - @test issetequal(sites(tn; set=:inputs), adjoint.(Site.([1, 2, 3]))) - @test issetequal(sites(tn; set=:outputs), Site.([1, 2, 3])) - @test Tenet.ntensors(tn) == 7 + circ = convert(Circuit, circuit) + @test circ isa Circuit + @test issetequal(sites(circ; set=:inputs), Site.([0, 1, 2]; dual=true)) + @test issetequal(sites(circ; set=:outputs), Site.([0, 1, 2])) + @test Tenet.ntensors(circ) == 7 + @test issetequal( + moments(circ), [Moment.(Ref(Lane(0)), 1:4)..., Moment.(Ref(Lane(1)), 1:4)..., Moment.(Ref(Lane(2)), 1:4)...] + ) end diff --git a/test/integration/python/test_qiskit.jl b/test/integration/python/test_qiskit.jl index 9fbfd3386..eac65cb55 100644 --- a/test/integration/python/test_qiskit.jl +++ b/test/integration/python/test_qiskit.jl @@ -11,8 +11,12 @@ circuit.h(1) circuit.h(2) - tn = convert(Quantum, circuit) - @test issetequal(sites(tn; set=:inputs), adjoint.(Site.([1, 2, 3]))) - @test issetequal(sites(tn; set=:outputs), Site.([1, 2, 3])) - @test Tenet.ntensors(tn) == 7 + tn = convert(Circuit, circuit) + @test tn isa Circuit + @test issetequal(sites(circ; set=:inputs), Site.([1, 2, 3]; dual=true)) + @test issetequal(sites(circ; set=:outputs), Site.([1, 2, 3])) + @test Tenet.ntensors(circ) == 7 + @test issetequal( + moments(circ), [Moment.(Ref(Lane(1)), 1:4)..., Moment.(Ref(Lane(2)), 1:4)..., Moment.(Ref(Lane(3)), 1:4)...] + ) end From b0c87b909b2cbe997f0e4100a89b345df7b270cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Mon, 13 Jan 2025 17:36:48 +0100 Subject: [PATCH 07/13] update tests and fixes --- Project.toml | 2 +- src/Ansatz.jl | 1 + src/Circuit.jl | 62 ++++++++++++++++++---- src/Lattice.jl | 121 ++++++++++++++++++++++++++++++------------- src/Site.jl | 6 +-- test/Ansatz_test.jl | 2 +- test/Circuit_test.jl | 9 +++- test/Gate_test.jl | 12 ++--- test/Lattice_test.jl | 117 +++++++++++++++++++++++++++-------------- test/Moment_test.jl | 10 ++-- test/Quantum_test.jl | 2 +- 11 files changed, 238 insertions(+), 106 deletions(-) diff --git a/Project.toml b/Project.toml index f85adfc95..936ba8786 100644 --- a/Project.toml +++ b/Project.toml @@ -57,7 +57,7 @@ TenetYaoBlocksExt = "YaoBlocks" [compat] AbstractTrees = "0.4" Adapt = "4" -BijectiveDicts = "0.1" +BijectiveDicts = "0.2.1" ChainRules = "1.0" ChainRulesCore = "1.0" ChainRulesTestUtils = "1" diff --git a/src/Ansatz.jl b/src/Ansatz.jl index 1dee09f10..e0c3f91e4 100644 --- a/src/Ansatz.jl +++ b/src/Ansatz.jl @@ -97,6 +97,7 @@ struct Ansatz <: AbstractAnsatz lattice::Lattice function Ansatz(tn, lattice) + # TODO make comparison in another way when we start using `Site`s if !issetequal(lanes(tn), Graphs.vertices(lattice)) throw(ArgumentError("Sites of the tensor network and the lattice must be equal")) end diff --git a/src/Circuit.jl b/src/Circuit.jl index 605af8df5..fb4d530d1 100644 --- a/src/Circuit.jl +++ b/src/Circuit.jl @@ -8,7 +8,7 @@ struct Gate @assert ndims(tensor) == length(sites) @assert allunique(sites) @assert allunique(inds(tensor)) - new(tensor, sites) + new(tensor, collect(sites)) end end @@ -16,7 +16,10 @@ Gate(array::AbstractArray, sites) = Gate(Tensor(array, [gensym(:i) for _ in 1:nd Tensor(gate::Gate) = gate.tensor Base.parent(gate::Gate) = Tensor(gate) -inds(gate::Gate) = inds(Tensor(gate)) + +inds(gate::Gate; kwargs...) = inds(sort_nt(values(kwargs)), gate::Gate) +inds(::@NamedTuple{}, gate::Gate) = inds(Tensor(gate)) +inds(kwargs::@NamedTuple{at::S}, gate::Gate) where {S<:Site} = inds(gate)[findfirst(isequal(kwargs.at), sites(gate))] sites(gate::Gate; kwargs...) = sites(sort_nt(values(kwargs)), gate) sites(::@NamedTuple{}, gate::Gate) = Tuple(gate.sites) @@ -34,10 +37,13 @@ end lanes(gate::Gate) = unique(Iterators.map(Tenet.Lane, sites(gate))) +Base.:(==)(a::Gate, b::Gate) = sites(a) == sites(b) && Tensor(a) == Tensor(b) + +Base.replace(gate::Gate) = gate Base.replace(gate::Gate, old_new::Pair{Symbol,Symbol}...) = Gate(replace(Tensor(gate), old_new...), sites(gate)) -function Base.replace(gate::Gate, old_new::Pair{Site,Symbol}...) - mapping = ImmutableDict(Pair.(sites(gate), inds(gate))) +function Base.replace(gate::Gate, old_new::Pair{<:Site,Symbol}...) + mapping = Base.ImmutableDict(Pair.(sites(gate), inds(gate))...) old_new = map(old_new) do (old, new) mapping[old] => new end @@ -51,9 +57,12 @@ struct Circuit <: AbstractQuantum moments::BijectiveDict{Moment,Symbol} # mapping between indices and `Moment`s inputs::Dict{Lane,Int} outputs::Dict{Lane,Int} # current moment for each lane + ordered_gates::Vector{Gate} # used in iterate end -Circuit() = Circuit(TensorNetwork(), BijectiveDict(Dict{Moment,Symbol}(), Dict{Symbol,Moment}()), Dict(), Dict()) +function Circuit() + Circuit(TensorNetwork(), BijectiveDict(Dict{Moment,Symbol}(), Dict{Symbol,Moment}()), Dict(), Dict(), Gate[]) +end TensorNetwork(circuit::Circuit) = circuit.tn @@ -70,11 +79,28 @@ function moment(circuit::Circuit, site::Site) return Moment(lane, t) end -inds(kwargs::@NamedTuple{at::Site}, circuit::Circuit) = circuit.moments[moment(circuit, kwargs.at)] +inds(kwargs::@NamedTuple{at::S}, circuit::Circuit) where {S<:Site} = circuit.moments[moment(circuit, kwargs.at)] inds(kwargs::@NamedTuple{at::Moment}, circuit::Circuit) = circuit.moments[kwargs.at] -# NOTE `inds(; set)` is implemented in `Quantum.jl` for `AbstractQuantum` -# NOTE `tensors(; at)` is implemented in `Quantum.jl` for `AbstractQuantum` +# NOTE `inds(; set)` is implemented in `Quantum.jl` for `AbstractQuantum`, but reimplemented here for performance +function inds(kwargs::@NamedTuple{set::Symbol}, circuit::Circuit) + if kwargs.set ∈ (:inputs, :outputs) + return [inds(circuit; at=site) for site in sites(circuit; kwargs...)] + elseif kwargs.set === :physical + return [inds(circuit; at=site) for site in sites(circuit)] + elseif kwargs.set === :virtual + return setdiff(inds(circuit), inds(circuit; set=:physical)) + else + return inds(TensorNetwork(circuit); set=kwargs.set) + end +end + +# NOTE `tensors(; at)` is implemented in `Quantum.jl` for `AbstractQuantum`, but reimplemented for performance +function tensors(kwargs::@NamedTuple{at::S}, circuit::Circuit) where {S<:Site} + only(tensors(circuit; contains=inds(circuit; kwargs...))) +end + +# NOTE `tensors(; at::Moment)` not fully defined function sites(::@NamedTuple{}, circuit::Circuit) Site[Site.(keys(circuit.inputs); dual=true)..., Site.(keys(circuit.outputs))...] @@ -101,7 +127,9 @@ function Base.push!(circuit::Circuit, gate::Gate) # reindex gate to match circuit indices gate = resetindex(gate) - gate = replace(gate, [site' => inds(circuit; at=site) for site in Iterators.map(Site, connecting_lanes)]) + if !isempty(connecting_lanes) + gate = replace(gate, [site' => inds(circuit; at=site) for site in Iterators.map(Site, connecting_lanes)]...) + end # add gate to circuit push!(TensorNetwork(circuit), Tensor(gate)) @@ -121,7 +149,21 @@ function Base.push!(circuit::Circuit, gate::Gate) circuit.moments[moment] = inds(gate; at=Site(lane)) end + push!(circuit.ordered_gates, gate) + return circuit end -# TODO iterative walk through the gates +Base.eltype(::Type{Circuit}) = Gate +Base.IteratorSize(::Type{Circuit}) = Base.HasLength() + +# TODO choose between breadth-first and depth-first traversing algorithms +""" + Base.iterate(circuit::Circuit[, state=1]) + +Iterate over the gates in `circuit` in the order they were added. +""" +Base.iterate(circuit::Circuit, state=1) = iterate(circuit.ordered_gates, state) + +# NOTE if not specialized, it will fallback to the `TensorNetwork` method with returns `Vector{Tensor}` +Base.collect(circuit::Circuit) = copy(circuit.ordered_gates) diff --git a/src/Lattice.jl b/src/Lattice.jl index d11f2591b..e0dc5b7fd 100644 --- a/src/Lattice.jl +++ b/src/Lattice.jl @@ -1,20 +1,22 @@ using Graphs: Graphs using BijectiveDicts: BijectiveIdDict -struct LatticeEdge <: Graphs.AbstractEdge{Site} - src::Site - dst::Site +struct Bond{L<:AbstractLane} + src::L + dst::L end -Base.convert(::Type{LatticeEdge}, edge::Pair{<:Site,<:Site}) = LatticeEdge(edge.first, edge.second) +Base.convert(::Type{Bond}, edge::Pair{<:AbstractLane,<:AbstractLane}) = Bond(edge.first, edge.second) -Graphs.src(edge::LatticeEdge) = edge.src -Graphs.dst(edge::LatticeEdge) = edge.dst -Graphs.reverse(edge::LatticeEdge) = LatticeEdge(dst(edge), src(edge)) -Base.show(io::IO, edge::LatticeEdge) = write(io, "LatticeEdge $(src(edge)) → $(dst(edge))") +Base.:(==)(a::Bond, b::Bond) = a.src == b.src && a.dst == b.dst || a.src == b.dst && a.dst == b.src -Pair(e::LatticeEdge) = src(e) => dst(e) -Tuple(e::LatticeEdge) = (src(e), dst(e)) +Graphs.src(edge::Bond) = edge.src +Graphs.dst(edge::Bond) = edge.dst +Graphs.reverse(edge::Bond) = Bond(Graphs.dst(edge), Graphs.src(edge)) +Base.show(io::IO, edge::Bond) = write(io, "Bond: $(Graphs.src(edge)) - $(Graphs.dst(edge))") + +Pair(e::Bond) = src(e) => dst(e) +Tuple(e::Bond) = (src(e), dst(e)) """ Lattice @@ -23,24 +25,26 @@ A lattice is a graph where the vertices are [`Site`](@ref)s and the edges are vi It is used for representing the topology of a [`Ansatz`](@ref) Tensor Network. It fulfills the [`AbstractGraph`](https://juliagraphs.org/Graphs.jl/stable/core_functions/interface/) interface. """ -struct Lattice <: Graphs.AbstractGraph{Site} - mapping::BijectiveIdDict{Site,Int} - graph::Graphs.SimpleGraph{Int} +struct Lattice <: Graphs.AbstractGraph{AbstractLane} + mapping::BijectiveIdDict{AbstractLane,Int} + graph::Graphs.SimpleGraph{Int} # TODO replace graph format because `rem_vertex!` renames vertices end Base.copy(lattice::Lattice) = Lattice(copy(lattice.mapping), copy(lattice.graph)) Base.:(==)(a::Lattice, b::Lattice) = a.mapping == b.mapping && a.graph == b.graph # TODO these where needed by ChainRulesTestUtils, do we still need them? -Base.zero(::Type{Lattice}) = Lattice(BijectiveIdDict{Site,Int}(), zero(Graphs.SimpleGraph{Int})) +Base.zero(::Type{Lattice}) = Lattice(BijectiveIdDict{AbstractLane,Int}(), zero(Graphs.SimpleGraph{Int})) Base.zero(::Lattice) = zero(Lattice) +Base.in(v::AbstractLane, lattice::Lattice) = v ∈ keys(lattice.mapping) + Graphs.is_directed(::Type{Lattice}) = false """ Graphs.vertices(::Lattice) -Return the vertices of the lattice; i.e. the list of [`Site`](@ref)s. +Return the vertices of the lattice; i.e. the list of [`Lane`](@ref)s. """ function Graphs.vertices(lattice::Lattice) return map(Graphs.vertices(lattice.graph)) do vertex @@ -51,14 +55,14 @@ end """ Graphs.edges(::Lattice) -Return the edges of the lattice; i.e. pairs of [`Site`](@ref)s. +Return the edges of the lattice; i.e. pairs of [`Lane`](@ref)s. """ -Graphs.edges(lattice::Lattice) = LatticeEdgeIterator(Graphs.edges(lattice.graph), lattice) +Graphs.edges(lattice::Lattice) = BondIterator(Graphs.edges(lattice.graph), lattice) """ Graphs.nv(::Lattice) -Return the number of vertices/[`Site`](@ref)s in the lattice. +Return the number of vertices; i.e. [`Lane`](@ref)s, in the lattice. """ Graphs.nv(lattice::Lattice) = Graphs.nv(lattice.graph) @@ -70,52 +74,95 @@ Return the number of edges in the lattice. Graphs.ne(lattice::Lattice) = Graphs.ne(lattice.graph) """ - Graphs.has_vertex(lattice::Lattice, site::Site) + Graphs.has_vertex(lattice::Lattice, lane::AbstractLane) -Return `true` if the lattice has the given [`Site`](@ref). +Return `true` if the lattice has the given [`Lane`](@ref). """ -Graphs.has_vertex(lattice::Lattice, site::Site) = haskey(lattice.mapping, site) +Graphs.has_vertex(lattice::Lattice, lane::AbstractLane) = haskey(lattice.mapping, lane) """ Graphs.has_edge(lattice::Lattice, edge) - Graphs.has_edge(lattice::Lattice, a::Site, b::Site) + Graphs.has_edge(lattice::Lattice, a::Lane, b::Lane) Return `true` if the lattice has the given edge. """ -Graphs.has_edge(lattice::Lattice, edge::LatticeEdge) = Graphs.has_edge(lattice, edge.src, edge.dst) -function Graphs.has_edge(lattice::Lattice, a::Site, b::Site) +Graphs.has_edge(lattice::Lattice, edge::Bond) = Graphs.has_edge(lattice, edge.src, edge.dst) +function Graphs.has_edge(lattice::Lattice, a::AbstractLane, b::AbstractLane) return Graphs.has_vertex(lattice, a) && Graphs.has_vertex(lattice, b) && Graphs.has_edge(lattice.graph, lattice.mapping[a], lattice.mapping[b]) end """ - Graphs.neighbors(lattice::Lattice, site::Site) + Graphs.neighbors(lattice::Lattice, lane::AbstractLane) -Return the neighbors [`Site`](@ref)s of the given [`Site`](@ref). +Return the neighbors [`Lane`](@ref)s of the given [`Lane`](@ref). """ -function Graphs.neighbors(lattice::Lattice, site::Site) - Graphs.has_vertex(lattice, site) || throw(ArgumentError("site not in lattice")) - vertex = lattice.mapping[site] +function Graphs.neighbors(lattice::Lattice, lane::AbstractLane) + Graphs.has_vertex(lattice, lane) || throw(ArgumentError("lane not in lattice")) + vertex = lattice.mapping[lane] return map(Graphs.neighbors(lattice.graph, vertex)) do neighbor lattice.mapping'[neighbor] end end -struct LatticeEdgeIterator <: Graphs.AbstractEdgeIter +struct BondIterator <: Graphs.AbstractEdgeIter simpleit::Graphs.SimpleGraphs.SimpleEdgeIter{Graphs.SimpleGraph{Int}} lattice::Lattice end -Graphs.ne(iterator::LatticeEdgeIterator) = Graphs.ne(iterator.lattice) -Base.eltype(::Type{LatticeEdgeIterator}) = LatticeEdge -Base.length(iterator::LatticeEdgeIterator) = length(iterator.simpleit) -Base.in(e::LatticeEdge, it::LatticeEdgeIterator) = Graphs.has_edge(it.lattice, Graphs.src(e), Graphs.src(dst)) -Base.show(io::IO, iterator::LatticeEdgeIterator) = write(io, "LatticeEdgeIterator $(ne(iterator))") +Graphs.ne(iterator::BondIterator) = Graphs.ne(iterator.lattice) +Base.eltype(::Type{BondIterator}) = Bond +Base.length(iterator::BondIterator) = length(iterator.simpleit) +Base.in(e::Bond, it::BondIterator) = Graphs.has_edge(it.lattice, Graphs.src(e), Graphs.src(dst)) +Base.show(io::IO, iterator::BondIterator) = write(io, "BondIterator $(Graphs.ne(iterator))") -function Base.iterate(iterator::LatticeEdgeIterator, state=nothing) +function Base.iterate(iterator::BondIterator, state=nothing) itres = isnothing(state) ? iterate(iterator.simpleit) : iterate(iterator.simpleit, state) isnothing(itres) && return nothing edge, state = itres - return LatticeEdge(iterator.lattice.mapping'[Graphs.src(edge)], iterator.lattice.mapping'[Graphs.dst(edge)]), state + return Bond(iterator.lattice.mapping'[Graphs.src(edge)], iterator.lattice.mapping'[Graphs.dst(edge)]), state +end + +function Lattice(::Val{:chain}, n; periodic=false) + graph = periodic ? Graphs.cycle_graph(n) : Graphs.path_graph(n) + mapping = BijectiveIdDict{AbstractLane,Int}([Lane(i) => i for i in 1:n]) + Lattice(mapping, graph) +end + +function Lattice(::Val{:rectangular}, nrows, ncols; periodic=false) + graph = Graphs.grid((nrows, ncols); periodic) + mapping = BijectiveIdDict{AbstractLane,Int}([ + Lane(row, col) => row + (col - 1) * nrows for row in 1:nrows for col in 1:ncols + ]) + Lattice(mapping, graph) +end + +""" + Lattice(::Val{:lieb}, nrows, ncols) + +Create a Lieb lattice with `nrows` cell rows and `ncols` cell columns. +""" +function Lattice(::Val{:lieb}, ncellrows, ncellcols) + nrows, ncols = 1 .+ 2 .* (ncellrows, ncellcols) + + lanes = [Lane(row, col) for row in 1:nrows for col in 1:ncols if !(row % 2 == 0 && col % 2 == 0)] + mapping = BijectiveIdDict{AbstractLane,Int}([lane => i for (i, lane) in enumerate(lanes)]) + graph = Graphs.SimpleGraph{Int}(length(lanes)) + + # add horizontal edges + for row in 1:2:nrows, col in 1:(ncols - 1) + i = mapping[Lane(row, col)] + j = mapping[Lane(row, col + 1)] + Graphs.add_edge!(graph, i, j) + end + + # add vertical edges + for row in 1:(nrows - 1), col in 1:2:ncols + i = mapping[Lane(row, col)] + j = mapping[Lane(row + 1, col)] + Graphs.add_edge!(graph, i, j) + end + + return Lattice(mapping, graph) end diff --git a/src/Site.jl b/src/Site.jl index 57bdc3945..589965298 100644 --- a/src/Site.jl +++ b/src/Site.jl @@ -9,7 +9,7 @@ Represents the location of a physical index. See also: [`Site`](@ref), [`lanes`](@ref) """ -struct Lane{N} +struct Lane{N} <: AbstractLane id::NTuple{N,Int} Lane(id::NTuple{N,Int}) where {N} = new{N}(id) @@ -127,7 +127,3 @@ end Moment(lane::L, t) where {L<:AbstractLane} = Moment{L}(lane, t) Lane(x::Moment) = Lane(x.lane) - -struct Bond{L<:AbstractLane} - lanes::NTuple{2,L} -end diff --git a/test/Ansatz_test.jl b/test/Ansatz_test.jl index 696e11fea..186f5a04d 100644 --- a/test/Ansatz_test.jl +++ b/test/Ansatz_test.jl @@ -9,7 +9,7 @@ using LinearAlgebra tn = TensorNetwork([Tensor(ones(2), [:i]), Tensor(ones(2), [:j])]) qtn = Quantum(tn, Dict(site"1" => :i, site"2" => :j)) graph = Graph(2) - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[Site(i) => i for i in 1:2]) + mapping = BijectiveIdDict{AbstractLane,Int}(Pair{AbstractLane,Int}[Lane(i) => i for i in 1:2]) lattice = Lattice(mapping, graph) ansatz = Ansatz(qtn, lattice) diff --git a/test/Circuit_test.jl b/test/Circuit_test.jl index 76ce14296..6c3c6f61c 100644 --- a/test/Circuit_test.jl +++ b/test/Circuit_test.jl @@ -8,6 +8,7 @@ @test isempty(inds(circuit; set=:outputs)) @test isempty(inds(circuit; set=:physical)) @test isempty(inds(circuit; set=:virtual)) + @test collect(circuit) == Gate[] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -30,6 +31,7 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) + @collect circuit == [gate] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -39,7 +41,7 @@ # test applying a gate with multiple lanes @testset let circuit = Circuit(), gate = Gate(zeros(2, 2, 2, 2), [site"1'", site"2'", site"1", site"2"]) push!(circuit, gate) - @test issetequal(sites(circuit), [site"1'", site"1"]) + @test issetequal(sites(circuit), [site"1'", site"1", site"2'", site"2"]) @test issetequal(lanes(circuit), [Lane(1), Lane(2)]) @test issetequal(sites(circuit; set=:inputs), [site"1'", site"2'"]) @test issetequal(sites(circuit; set=:outputs), [site"1", site"2"]) @@ -54,6 +56,7 @@ @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + @test collect(circuit) = [gate] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -81,6 +84,7 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate2)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) + @test collect(circuit) == [gate1, gate2] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -105,6 +109,7 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) + @test collect(circuit) == [gate, gate] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -136,6 +141,7 @@ @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + @test collect(circuit) == [gate1, gate2] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -167,6 +173,7 @@ @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) + @test collect(circuit) == [gate1, gate2] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) diff --git a/test/Gate_test.jl b/test/Gate_test.jl index e1f6635eb..6c8e1c284 100644 --- a/test/Gate_test.jl +++ b/test/Gate_test.jl @@ -5,10 +5,10 @@ @test Tensor(gate) == tensor @test Tensor(gate) == tensor @test inds(gate) == inds(tensor) - @test sites(gate) == (site"1'", site"1") - @test lanes(gate) == (Lane(1),) - @test sites(gate; set=:inputs) == (site"1'",) - @test sites(gate; set=:outputs) == (site"1",) + @test issetequal(sites(gate), (site"1'", site"1")) + @test issetequal(lanes(gate), (Lane(1),)) + @test issetequal(sites(gate; set=:inputs), (site"1'",)) + @test issetequal(sites(gate; set=:outputs), (site"1",)) @test replace(gate, :i => :k) == Gate(Tensor(data, (:k, :j)), [site"1'", site"1"]) @test replace(gate, site"1'" => :k) == Gate(Tensor(data, (:k, :j)), [site"1'", site"1"]) @@ -16,11 +16,11 @@ @test gate isa Gate @test parent(Tensor(gate)) == data @test isdisjoint(inds(gate), inds(tensor)) - @test sites(gate) == (site"1'", site"1") + @test issetequal(sites(gate), (site"1'", site"1")) gate = Gate(data, [site"1'", site"1"]) @test gate isa Gate @test parent(Tensor(gate)) == data - @test sites(gate) == (site"1'", site"1") + @test issetequal(sites(gate), (site"1'", site"1")) end end \ No newline at end of file diff --git a/test/Lattice_test.jl b/test/Lattice_test.jl index 988ae1812..8976b793f 100644 --- a/test/Lattice_test.jl +++ b/test/Lattice_test.jl @@ -1,90 +1,129 @@ using Tenet -using Tenet: Lattice, LatticeEdge +using Tenet: Lattice, Bond, AbstractLane using Graphs using BijectiveDicts: BijectiveIdDict @testset "Lattice" begin - @testset let graph = SimpleGraph(), mapping = BijectiveIdDict{Site,Int}(), lattice = Lattice(mapping, graph) + @testset let graph = SimpleGraph(), mapping = BijectiveIdDict{AbstractLane,Int}(), lattice = Lattice(mapping, graph) @test lattice == zero(Lattice) @test nv(lattice) == 0 @test ne(lattice) == 0 @test isempty(vertices(lattice)) @test isempty(edges(lattice)) - @test !has_vertex(lattice, site"1") - @test !has_edge(lattice, site"1", site"2") - @test !has_edge(lattice, LatticeEdge(site"1", site"2")) - @test_throws ArgumentError neighbors(lattice, site"1") + @test !has_vertex(lattice, lane"1") + @test !has_edge(lattice, lane"1", lane"2") + @test !has_edge(lattice, Bond(lane"1", lane"2")) + @test_throws ArgumentError neighbors(lattice, lane"1") end - @testset let n = 5, - graph = path_graph(n), - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[Site(i) => i for i in 1:n]), - lattice = Lattice(mapping, graph) + @testset "Chain" begin + n = 5 + lattice = Lattice(Val(:chain), n) @test nv(lattice) == n @test ne(lattice) == n - 1 - @test issetequal(vertices(lattice), map(i -> Site(i), 1:n)) + @test issetequal(vertices(lattice), map(i -> Lane(i), 1:n)) @test issetequal( - edges(lattice), LatticeEdge[site"1" => site"2", site"2" => site"3", site"3" => site"4", site"4" => site"5"] + edges(lattice), Bond[lane"1" => lane"2", lane"2" => lane"3", lane"3" => lane"4", lane"4" => lane"5"] ) for i in 1:n - @test has_vertex(lattice, Site(i)) - @test neighbors(lattice, Site(i)) == if i == 1 - [site"2"] + @test has_vertex(lattice, Lane(i)) + @test neighbors(lattice, Lane(i)) == if i == 1 + [lane"2"] elseif i == n - [site"4"] + [lane"4"] else - [Site(i - 1), Site(i + 1)] + [Lane(i - 1), Lane(i + 1)] end end for i in 1:(n - 1) - @test has_edge(lattice, Site(i), Site(i + 1)) - @test has_edge(lattice, LatticeEdge(Site(i), Site(i + 1))) + @test has_edge(lattice, Lane(i), Lane(i + 1)) + @test has_edge(lattice, Bond(Lane(i), Lane(i + 1))) end end - @testset let m = 3, - n = 2, - graph = grid((m, n)), - mapping = BijectiveIdDict{Site,Int}( - vec(Pair{Site,Int}[Site(i, j) => k for (k, (i, j)) in enumerate(Iterators.product(1:m, 1:n))]) - ), - lattice = Lattice(mapping, graph) + @testset "Rectangular Grid" begin + m = 3 + n = 2 + lattice = Lattice(Val(:rectangular), m, n) @test nv(lattice) == m * n @test ne(lattice) == (m - 1) * n + m * (n - 1) - @test issetequal(vertices(lattice), map(Site, Iterators.product(1:m, 1:n))) + @test issetequal(vertices(lattice), map(Lane, Iterators.product(1:m, 1:n))) @test issetequal( edges(lattice), - LatticeEdge[ - site"1,1" => site"2,1", - site"1,1" => site"1,2", - site"2,1" => site"3,1", - site"2,1" => site"2,2", - site"3,1" => site"3,2", - site"1,2" => site"2,2", - site"2,2" => site"3,2", + Bond[ + lane"1,1" => lane"2,1", + lane"1,1" => lane"1,2", + lane"2,1" => lane"3,1", + lane"2,1" => lane"2,2", + lane"3,1" => lane"3,2", + lane"1,2" => lane"2,2", + lane"2,2" => lane"3,2", ], ) for i in 1:m, j in 1:n - site = Site(i, j) + site = Lane(i, j) @test has_vertex(lattice, site) @test issetequal( neighbors(lattice, site), filter( - site -> has_vertex(lattice, site), [Site(i - 1, j), Site(i + 1, j), Site(i, j - 1), Site(i, j + 1)] + site -> has_vertex(lattice, site), [Lane(i - 1, j), Lane(i + 1, j), Lane(i, j - 1), Lane(i, j + 1)] ), ) end for i in 1:(m - 1), j in 1:n - @test has_edge(lattice, Site(i, j), Site(i + 1, j)) - @test has_edge(lattice, LatticeEdge(Site(i, j), Site(i + 1, j))) + @test has_edge(lattice, Lane(i, j), Lane(i + 1, j)) + @test has_edge(lattice, Bond(Lane(i, j), Lane(i + 1, j))) end end + + @testset "Lieb" begin + m = 2 + n = 2 + lattice = Lattice(Val(:lieb), m, n) + + @test nv(lattice) == 21 + @test ne(lattice) == 24 + + @test issetequal( + vertices(lattice), + map(Lane, [Lane(row, col) for row in 1:(2m + 1) for col in 1:(2n + 1) if !(row % 2 == 0 && col % 2 == 0)]), + ) + @test issetequal( + edges(lattice), + Bond[ + lane"1,1" => lane"1,2", + lane"1,1" => lane"2,1", + lane"1,2" => lane"1,3", + lane"1,3" => lane"1,4", + lane"1,3" => lane"2,3", + lane"1,4" => lane"1,5", + lane"1,5" => lane"2,5", + lane"2,1" => lane"3,1", + lane"2,3" => lane"3,3", + lane"2,5" => lane"3,5", + lane"3,1" => lane"3,2", + lane"3,1" => lane"4,1", + lane"3,2" => lane"3,3", + lane"3,3" => lane"3,4", + lane"3,3" => lane"4,3", + lane"3,4" => lane"3,5", + lane"3,5" => lane"4,5", + lane"4,1" => lane"5,1", + lane"4,3" => lane"5,3", + lane"4,5" => lane"5,5", + lane"5,1" => lane"5,2", + lane"5,2" => lane"5,3", + lane"5,3" => lane"5,4", + lane"5,4" => lane"5,5", + ], + ) + end end diff --git a/test/Moment_test.jl b/test/Moment_test.jl index 9715c0a44..e01a9fa38 100644 --- a/test/Moment_test.jl +++ b/test/Moment_test.jl @@ -1,23 +1,23 @@ @testset "Moment" begin - using Tenet: id + using Tenet: id, Moment moment = Moment(Lane(1), 7) @test Lane(moment) == Lane(1) - @test id(lane) == 1 + @test id(moment) == 1 @test moment.t == 7 moment = Moment(Lane(1, 2), 7) @test Lane(moment) == Lane(1, 2) - @test id(lane) == (1, 2) + @test id(moment) == (1, 2) @test moment.t == 7 moment = Moment(lane"1", 7) @test Lane(moment) == Lane(1) - @test id(lane) == 1 + @test id(moment) == 1 @test moment.t == 7 moment = Moment(lane"1,2", 7) @test Lane(moment) == Lane(1, 2) - @test id(lane) == (1, 2) + @test id(moment) == (1, 2) @test moment.t == 7 end diff --git a/test/Quantum_test.jl b/test/Quantum_test.jl index 5d463d2bd..f06c2ce67 100644 --- a/test/Quantum_test.jl +++ b/test/Quantum_test.jl @@ -1,5 +1,5 @@ @testset "Quantum" begin - using Tenet + using Tenet: nsites, State, Operator, Scalar _tensors = Tensor[Tensor(zeros(2), [:i])] tn = TensorNetwork(_tensors) From 346f26a2672835a69b95c0da7ec850ac71861b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Mon, 13 Jan 2025 21:11:27 +0100 Subject: [PATCH 08/13] fix typo --- test/Circuit_test.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Circuit_test.jl b/test/Circuit_test.jl index 6c3c6f61c..a71c4b8bd 100644 --- a/test/Circuit_test.jl +++ b/test/Circuit_test.jl @@ -31,7 +31,7 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) - @collect circuit == [gate] + @test collect(circuit) == [gate] qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) From ec9805f6870400a78e7dff48dfa3cfc02247ef3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Mon, 13 Jan 2025 21:27:02 +0100 Subject: [PATCH 09/13] fix `Circuit` tests --- test/Circuit_test.jl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/Circuit_test.jl b/test/Circuit_test.jl index a71c4b8bd..98754afad 100644 --- a/test/Circuit_test.jl +++ b/test/Circuit_test.jl @@ -31,7 +31,8 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) - @test collect(circuit) == [gate] + @test parent.(Tensor.(circuit)) == parent.(Tensor.([gate])) + @test sites.(circuit) == sites.([gate]) qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -56,7 +57,8 @@ @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) - @test collect(circuit) = [gate] + @test parent.(Tensor.(circuit)) == parent.(Tensor.([gate])) + @test sites.(circuit) == sites.([gate]) qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -84,7 +86,8 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate2)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) - @test collect(circuit) == [gate1, gate2] + @test parent.(Tensor.(circuit)) == parent.(Tensor.([gate1, gate2])) + @test sites.(circuit) == sites.([gate1, gate2]) qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -109,7 +112,8 @@ @test parent(tensors(circuit; at=site"1")) == parent(Tensor(gate)) @test Tenet.moment(circuit, site"1'") == Moment(Lane(1), 1) @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) - @test collect(circuit) == [gate, gate] + @test parent.(Tensor.(circuit)) == parent.(Tensor.([gate, gate])) + @test sites.(circuit) == sites.([gate, gate]) qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -141,7 +145,8 @@ @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 2) @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) - @test collect(circuit) == [gate1, gate2] + @test parent.(Tensor.(circuit)) == parent.(Tensor.([gate1, gate2])) + @test sites.(circuit) == sites.([gate1, gate2]) qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) @@ -173,7 +178,8 @@ @test Tenet.moment(circuit, site"1") == Moment(Lane(1), 3) @test Tenet.moment(circuit, site"2'") == Moment(Lane(2), 1) @test Tenet.moment(circuit, site"2") == Moment(Lane(2), 2) - @test collect(circuit) == [gate1, gate2] + @test parent.(Tensor.(circuit)) == parent.(Tensor.([gate1, gate2])) + @test sites.(circuit) == sites.([gate1, gate2]) qtn = Quantum(circuit) @test issetequal(sites(qtn), sites(circuit)) From 79f0ec42c57ac50bf535b3d29eda950df7c54ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Tue, 14 Jan 2025 22:31:09 +0100 Subject: [PATCH 10/13] fixes --- ext/TenetChainRulesCoreExt/non_diff.jl | 2 +- src/Ansatz.jl | 86 ++++++------ src/Circuit.jl | 54 +++++++- src/Helpers.jl | 2 +- src/Lattice.jl | 30 ++-- src/MPS.jl | 147 +++++++++++--------- src/Product.jl | 5 +- src/Quantum.jl | 38 ++++-- src/Tenet.jl | 2 +- src/TensorNetwork.jl | 8 +- test/Ansatz_test.jl | 55 ++++---- test/Gate_test.jl | 11 +- test/Lattice_test.jl | 7 +- test/MPO_test.jl | 14 +- test/MPS_test.jl | 182 +++++++++++++------------ test/Product_test.jl | 5 +- 16 files changed, 385 insertions(+), 263 deletions(-) diff --git a/ext/TenetChainRulesCoreExt/non_diff.jl b/ext/TenetChainRulesCoreExt/non_diff.jl index c06849390..fa697832c 100644 --- a/ext/TenetChainRulesCoreExt/non_diff.jl +++ b/ext/TenetChainRulesCoreExt/non_diff.jl @@ -9,7 +9,7 @@ @non_differentiable Tenet.currindex(::Tenet.IndexCounter) @non_differentiable Tenet.nextindex!(::Tenet.IndexCounter) -@non_differentiable Tenet.resetindex!(::Tenet.IndexCounter) +@non_differentiable Tenet.resetinds!(::Tenet.IndexCounter) # WARN type-piracy @non_differentiable Base.setdiff(::Vector{Symbol}, ::Base.ValueIterator) diff --git a/src/Ansatz.jl b/src/Ansatz.jl index e0c3f91e4..b7acf26ea 100644 --- a/src/Ansatz.jl +++ b/src/Ansatz.jl @@ -46,11 +46,11 @@ struct NonCanonical <: Form end [`Form`](@ref) trait representing a [`AbstractAnsatz`](@ref) Tensor Network in mixed-canonical form. - - The orthogonality center is a [`Site`](@ref) or a vector of [`Site`](@ref)s. The tensors to the + - The orthogonality center is a [`Lane`](@ref) or a vector of [`Lane`](@ref)s. The tensors to the left of the orthogonality center are left-canonical and the tensors to the right are right-canonical. """ struct MixedCanonical <: Form - orthog_center::Union{Site,Vector{<:Site}} + orthog_center::Union{Lane,Vector{<:Lane}} end Base.copy(x::MixedCanonical) = MixedCanonical(copy(x.orthog_center)) @@ -70,10 +70,10 @@ Return the canonical form of the [`AbstractAnsatz`](@ref) Tensor Network. function form end struct MissingSchmidtCoefficientsException <: Base.Exception - bond::NTuple{2,Site} + bond::NTuple{2,AbstractLane} end -MissingSchmidtCoefficientsException(bond::Vector{<:Site}) = MissingSchmidtCoefficientsException(tuple(bond...)) +MissingSchmidtCoefficientsException(bond::Vector{<:AbstractLane}) = MissingSchmidtCoefficientsException(tuple(bond...)) function Base.showerror(io::IO, e::MissingSchmidtCoefficientsException) return print(io, "Can't access the spectrum on bond $(e.bond)") @@ -90,7 +90,7 @@ abstract type AbstractAnsatz <: AbstractQuantum end """ Ansatz -[`AbstractQuantum`](@ref) Tensor Network together with a [`Lattice`](@ref) for connectivity information between [`Site`](@ref)s. +[`AbstractQuantum`](@ref) Tensor Network together with a [`Lattice`](@ref) for connectivity information between [`Lane`](@ref)s. """ struct Ansatz <: AbstractAnsatz tn::Quantum @@ -133,42 +133,45 @@ function Base.isapprox(a::AbstractAnsatz, b::AbstractAnsatz; kwargs...) end """ - neighbors(tn::AbstractAnsatz, site::Site) + neighbors(tn::AbstractAnsatz, lane::AbstractLane) -Return the neighboring sites of a given [`Site`](@ref) in the [`Lattice`](@ref) of the [`AbstractAnsatz`](@ref) Tensor Network. +Return the neighboring sites of a given [`AbstractLane`](@ref) in the [`Lattice`](@ref) of the [`AbstractAnsatz`](@ref) Tensor Network. """ -Graphs.neighbors(tn::AbstractAnsatz, site::Site) = Graphs.neighbors(lattice(tn), site) +Graphs.neighbors(tn::AbstractAnsatz, lane::AbstractLane) = Graphs.neighbors(lattice(tn), lane) """ - has_edge(tn::AbstractAnsatz, a::Site, b::Site) + has_edge(tn::AbstractAnsatz, a::AbstractLane, b::AbstractLane) -Check whether there is an edge between two [`Site`](@ref)s in the [`Lattice`](@ref) of the [`AbstractAnsatz`](@ref) Tensor Network. +Check whether there is an edge between two [`AbstractLane`](@ref)s in the [`Lattice`](@ref) of the [`AbstractAnsatz`](@ref) Tensor Network. """ -Graphs.has_edge(tn::AbstractAnsatz, a::Site, b::Site) = Graphs.has_edge(lattice(tn), a, b) +Graphs.has_edge(tn::AbstractAnsatz, a::AbstractLane, b::AbstractLane) = Graphs.has_edge(lattice(tn), a, b) """ inds(tn::AbstractAnsatz; bond) -Return the index of the virtual bond between two [`Site`](@ref)s in a [`AbstractAnsatz`](@ref) Tensor Network. +Return the index of the virtual bond between two [`AbstractLane`](@ref)s in a [`AbstractAnsatz`](@ref) Tensor Network. """ function inds(kwargs::NamedTuple{(:bond,)}, tn::AbstractAnsatz) - (site1, site2) = kwargs.bond - @assert site1 ∈ sites(tn) "Site $site1 not found" - @assert site2 ∈ sites(tn) "Site $site2 not found" - @assert site1 != site2 "Sites must be different" - @assert Graphs.has_edge(tn, site1, site2) "Sites must be neighbors" + (lane1, lane2) = kwargs.bond + @assert lane1 ∈ lanes(tn) "Lane $lane1 not found" + @assert lane2 ∈ lanes(tn) "Lane $lane2 not found" + @assert lane1 != lane2 "Lanes must be different" + @assert Graphs.has_edge(tn, lane1, lane2) "Lanes must be neighbors" - tensor1 = tensors(tn; at=site1) - tensor2 = tensors(tn; at=site2) + tensor1 = tensors(tn; at=lane1) + tensor2 = tensors(tn; at=lane2) isdisjoint(inds(tensor1), inds(tensor2)) && return nothing return only(inds(tensor1) ∩ inds(tensor2)) end +# TODO fix this properly when we do the mapping +tensors(kwargs::NamedTuple{(:at,),Tuple{L}}, tn::AbstractAnsatz) where {L<:Lane} = tensors(tn; at=Site(kwargs.at)) + """ tensors(tn::AbstractAnsatz; bond) -Return the [`Tensor`](@ref) in a virtual bond between two [`Site`](@ref)s in a [`AbstractAnsatz`](@ref) Tensor Network. +Return the [`Tensor`](@ref) in a virtual bond between two [`AbstractLane`](@ref)s in a [`AbstractAnsatz`](@ref) Tensor Network. # Notes @@ -195,7 +198,7 @@ end """ contract!(tn::AbstractAnsatz; bond) -Contract the virtual bond between two [`Site`](@ref)s in a [`AbstractAnsatz`](@ref) Tensor Network. +Contract the virtual bond between two [`AbstractLane`](@ref)s in a [`AbstractAnsatz`](@ref) Tensor Network. """ contract!(kwargs::NamedTuple{(:bond,)}, tn::AbstractAnsatz) = contract!(tn, inds(tn; bond=kwargs.bond)) @@ -236,12 +239,12 @@ canonize_site(tn::AbstractAnsatz, args...; kwargs...) = canonize_site!(deepcopy( Normalize the state at a given [`Site`](@ref) or bond in a [`AbstractAnsatz`](@ref) Tensor Network. """ -LinearAlgebra.normalize(ψ::AbstractAnsatz, site) = normalize!(copy(ψ), site) +LinearAlgebra.normalize(ψ::AbstractAnsatz, lane) = normalize!(copy(ψ), lane) """ - isisometry(tn::AbstractAnsatz, site; dir, kwargs...) + isisometry(tn::AbstractAnsatz, lane; dir, kwargs...) -Check if the tensor at a given [`Site`](@ref) in a [`AbstractAnsatz`](@ref) Tensor Network is an isometry. +Check if the tensor at a given [`Lane`](@ref) in a [`AbstractAnsatz`](@ref) Tensor Network is an isometry. The `dir` keyword argument specifies the direction of the isometry to check. """ function isisometry end @@ -435,12 +438,14 @@ Update a [`AbstractAnsatz`](@ref) Tensor Network with a `gate` operator using th - If both `threshold` and `maxdim` are provided, `maxdim` is used. """ -function simple_update!(ψ::AbstractAnsatz, gate; threshold=nothing, maxdim=nothing, kwargs...) +function simple_update!(ψ::AbstractAnsatz, gate::Gate; threshold=nothing, maxdim=nothing, kwargs...) @assert issetequal(adjoint.(sites(gate; set=:inputs)), sites(gate; set=:outputs)) "Inputs of the gate must match outputs" + @assert isconnectable(ψ, gate) "Gate must be connectable to the Quantum Tensor Network" if nlanes(gate) == 1 return simple_update_1site!(ψ, gate) elseif nlanes(gate) == 2 + @assert Graphs.has_edge(ψ, lanes(gate)...) "Gate must act on neighboring sites of the lattice" return simple_update_2site!(form(ψ), ψ, gate; threshold, maxdim, kwargs...) else throw(ArgumentError("Only 1-site and 2-site gates are currently supported")) @@ -449,23 +454,18 @@ end # TODO a lot of problems with merging... maybe we shouldn't merge manually function simple_update_1site!(ψ::AbstractAnsatz, gate) - @assert nlanes(gate) == 1 "Gate must act only on one lane" - @assert ninputs(gate) == 1 "Gate must have only one input" - @assert noutputs(gate) == 1 "Gate must have only one output" - # shallow copy to avoid problems if errors in mid execution - gate = copy(gate) - resetindex!(gate; init=ninds(ψ)) + gate = resetinds(gate; init=ninds(ψ)) 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(ψ; at=targetsite)) + gate = replace(gate, inds(gate; at=only(sites(gate; set=:outputs))) => inds(ψ; at=targetsite)) # reindex contracting index replace!(ψ, inds(ψ; at=targetsite) => contracting_index) - replace!(gate, inds(gate; at=targetsite') => contracting_index) + gate = replace(gate, inds(gate; at=targetsite') => contracting_index) # contract gate with TN merge!(ψ, gate; reset=false) @@ -477,8 +477,6 @@ function simple_update_2site!(::MixedCanonical, ψ::AbstractAnsatz, gate; kwargs end function simple_update_2site!(::NonCanonical, ψ::AbstractAnsatz, gate; kwargs...) - @assert Graphs.has_edge(ψ, lanes(gate)...) "Gate must act on neighboring sites" - # shallow copy to avoid problems if errors in mid execution gate = copy(gate) @@ -493,13 +491,13 @@ function simple_update_2site!(::NonCanonical, ψ::AbstractAnsatz, gate; kwargs.. # reindex contracting indices to temporary names to avoid issues oinds = Dict(site => inds(ψ; at=site) for site in sites(gate; set=:outputs)) tmpinds = Dict(site => gensym(:tmp) for site in sites(gate; set=:inputs)) - replace!(gate, [inds(gate; at=site) => i for (site, i) in tmpinds]) + gate = replace(gate, [inds(gate; at=site) => i for (site, i) in tmpinds]...) replace!(ψ, [inds(ψ; at=site') => i for (site, i) in tmpinds]) # NOTE `replace!` is getting confused when a index is already there even if it would be overriden # TODO fix this to be handled in one call -> replace when #244 is fixed - replace!(gate, [inds(gate; at=site) => gensym() for (site, i) in oinds]) - replace!(gate, [inds(gate; at=site) => i for (site, i) in oinds]) + gate = replace(gate, [inds(gate; at=site) => gensym() for (site, i) in oinds]...) + gate = replace(gate, [inds(gate; at=site) => i for (site, i) in oinds]...) # contract physical inds with gate merge!(ψ, gate; reset=false) @@ -518,14 +516,14 @@ end function simple_update_2site!(::Canonical, ψ::AbstractAnsatz, gate; threshold, maxdim, normalize=false, canonize=true) # Contract the exterior Λ tensors sitel, siter = extrema(lanes(gate)) - (0 < id(sitel) < nsites(ψ) || 0 < id(siter) < nsites(ψ)) || - throw(ArgumentError("The sites in the bond must be between 1 and $(nsites(ψ))")) + (0 < id(sitel) < nlanes(ψ) || 0 < id(siter) < nlanes(ψ)) || + throw(ArgumentError("The sites in the bond must be between 1 and $(nlanes(ψ))")) - Λᵢ₋₁ = id(sitel) == 1 ? nothing : tensors(ψ; between=(Site(id(sitel) - 1), sitel)) - Λᵢ₊₁ = id(sitel) == nsites(ψ) - 1 ? nothing : tensors(ψ; between=(siter, Site(id(siter) + 1))) + Λᵢ₋₁ = id(sitel) == 1 ? nothing : tensors(ψ; between=(Lane(id(sitel) - 1), sitel)) + Λᵢ₊₁ = id(sitel) == nsites(ψ) - 1 ? nothing : tensors(ψ; between=(siter, Lane(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) + !isnothing(Λᵢ₋₁) && contract!(ψ; between=(Lane(id(sitel) - 1), sitel), direction=:right, delete_Λ=false) + !isnothing(Λᵢ₊₁) && contract!(ψ; between=(siter, Lane(id(siter) + 1)), direction=:left, delete_Λ=false) simple_update_2site!(NonCanonical(), ψ, gate; threshold, maxdim, normalize=false, canonize=false) diff --git a/src/Circuit.jl b/src/Circuit.jl index fb4d530d1..a416d9555 100644 --- a/src/Circuit.jl +++ b/src/Circuit.jl @@ -1,5 +1,13 @@ using BijectiveDicts: BijectiveDict +""" + Gate + +A `Gate` is a [`Tensor`](@ref) together with a set of [`Site`](@ref)s that represent the input/output indices. +It is similar to the relation between [`Quantum`](@ref) and [`TensorNetwork`](@ref), but only applies to one tensor. + +Although it is not a [`AbstractQuantum`](@ref), it can be converted to [`TensorNetwork`](@ref) or [`Quantum`](@ref). +""" struct Gate tensor::Tensor sites::Vector{Site} @@ -13,17 +21,48 @@ struct Gate end Gate(array::AbstractArray, sites) = Gate(Tensor(array, [gensym(:i) for _ in 1:ndims(array)]), sites) +Base.copy(gate::Gate) = Gate(copy(Tensor(gate)), sites(gate)) Tensor(gate::Gate) = gate.tensor Base.parent(gate::Gate) = Tensor(gate) -inds(gate::Gate; kwargs...) = inds(sort_nt(values(kwargs)), gate::Gate) +TensorNetwork(gate::Gate) = TensorNetwork([Tensor(gate)]) +Quantum(gate::Gate) = Quantum(TensorNetwork(gate), Dict(sites(gate) .=> inds(gate))) + +# AbstractTensorNetwork methods +inds(gate::Gate; kwargs...) = inds(sort_nt(values(kwargs)), gate) inds(::@NamedTuple{}, gate::Gate) = inds(Tensor(gate)) inds(kwargs::@NamedTuple{at::S}, gate::Gate) where {S<:Site} = inds(gate)[findfirst(isequal(kwargs.at), sites(gate))] +function inds(kwargs::@NamedTuple{set::Symbol}, gate::Gate) + if kwargs.set ∈ (:all, :open, :physical) + return inds(gate) + elseif kwargs.set ∈ (:inner, :hyper, :virtual) + return Symbol[] + elseif kwargs.set === :inputs + return last.( + Iterators.filter(zip(sites(gate), inds(gate))) do (site, ind) + isdual(site) + end + ) + elseif kwargs.set === :outputs + return last.( + Iterators.filter(zip(sites(gate), inds(gate))) do (site, ind) + !isdual(site) + end + ) + else + error( + "Expected set to be one of `:all`, `:open`, `:physical`, `:inner`, `:hyper`, `:virtual`, or `:inputs`, but got $(kwargs.set)", + ) + end +end + +tensors(gate::Gate; kwargs...) = tensors(sort_nt(values(kwargs)), gate) +tensors(::@NamedTuple{}, gate::Gate) = Tensor[Tensor(gate)] +# AbstractQuantum methods sites(gate::Gate; kwargs...) = sites(sort_nt(values(kwargs)), gate) sites(::@NamedTuple{}, gate::Gate) = Tuple(gate.sites) - function sites(kwargs::@NamedTuple{set::Symbol}, gate::Gate) pred = if kwargs.set === :outputs !isdual @@ -35,6 +74,7 @@ function sites(kwargs::@NamedTuple{set::Symbol}, gate::Gate) return filter(pred, sites(gate)) end +nlanes(gate::Gate) = length(lanes(gate)) lanes(gate::Gate) = unique(Iterators.map(Tenet.Lane, sites(gate))) Base.:(==)(a::Gate, b::Gate) = sites(a) == sites(b) && Tensor(a) == Tensor(b) @@ -50,7 +90,13 @@ function Base.replace(gate::Gate, old_new::Pair{<:Site,Symbol}...) return replace(gate, old_new...) end -resetindex(gate::Gate) = replace(gate, [ind => gensym(:i) for ind in inds(gate)]...) +resetinds(gate::Gate; init=nothing) = replace(gate, [ind => gensym(:i) for ind in inds(gate)]...) + +function Base.merge!(qtn::AbstractQuantum, gate::Gate; reset=false) + @assert isconnectable(qtn, gate) + merge!(qtn, Quantum(gate); reset) + return qtn +end struct Circuit <: AbstractQuantum tn::TensorNetwork @@ -126,7 +172,7 @@ function Base.push!(circuit::Circuit, gate::Gate) new_lanes = setdiff(lanes(gate), connecting_lanes) # reindex gate to match circuit indices - gate = resetindex(gate) + gate = resetinds(gate) if !isempty(connecting_lanes) gate = replace(gate, [site' => inds(circuit; at=site) for site in Iterators.map(Site, connecting_lanes)]...) end diff --git a/src/Helpers.jl b/src/Helpers.jl index 866afb8e6..0602c5d30 100644 --- a/src/Helpers.jl +++ b/src/Helpers.jl @@ -43,7 +43,7 @@ function nextindex!(gen::IndexCounter) end return letter(Threads.atomic_add!(gen.counter, 1)) end -resetindex!(gen::IndexCounter) = letter(Threads.atomic_xchg!(gen.counter, 1)) +resetinds!(gen::IndexCounter) = letter(Threads.atomic_xchg!(gen.counter, 1)) # eps wrapper so it handles Complex numbers # if is Complex, extract the parametric type and get the eps of that diff --git a/src/Lattice.jl b/src/Lattice.jl index e0dc5b7fd..ec879e3b9 100644 --- a/src/Lattice.jl +++ b/src/Lattice.jl @@ -1,5 +1,5 @@ using Graphs: Graphs -using BijectiveDicts: BijectiveIdDict +using BijectiveDicts: BijectiveDict struct Bond{L<:AbstractLane} src::L @@ -25,8 +25,8 @@ A lattice is a graph where the vertices are [`Site`](@ref)s and the edges are vi It is used for representing the topology of a [`Ansatz`](@ref) Tensor Network. It fulfills the [`AbstractGraph`](https://juliagraphs.org/Graphs.jl/stable/core_functions/interface/) interface. """ -struct Lattice <: Graphs.AbstractGraph{AbstractLane} - mapping::BijectiveIdDict{AbstractLane,Int} +struct Lattice <: Graphs.AbstractGraph{Lane} + mapping::BijectiveDict{Lane,Int,Dict{Lane,Int},Dict{Int,Lane}} graph::Graphs.SimpleGraph{Int} # TODO replace graph format because `rem_vertex!` renames vertices end @@ -34,10 +34,12 @@ Base.copy(lattice::Lattice) = Lattice(copy(lattice.mapping), copy(lattice.graph) Base.:(==)(a::Lattice, b::Lattice) = a.mapping == b.mapping && a.graph == b.graph # TODO these where needed by ChainRulesTestUtils, do we still need them? -Base.zero(::Type{Lattice}) = Lattice(BijectiveIdDict{AbstractLane,Int}(), zero(Graphs.SimpleGraph{Int})) +Base.zero(::Type{Lattice}) = Lattice(BijectiveDict{Lane,Int}(), zero(Graphs.SimpleGraph{Int})) Base.zero(::Lattice) = zero(Lattice) -Base.in(v::AbstractLane, lattice::Lattice) = v ∈ keys(lattice.mapping) +Base.in(v::Lane, lattice::Lattice) = v ∈ keys(lattice.mapping) +Base.in(v::Site, lattice::Lattice) = Lane(v) ∈ keys(lattice.mapping) +Base.in(e::Bond, lattice::Lattice) = e ∈ edges(lattice) Graphs.is_directed(::Type{Lattice}) = false @@ -124,17 +126,25 @@ function Base.iterate(iterator::BondIterator, state=nothing) return Bond(iterator.lattice.mapping'[Graphs.src(edge)], iterator.lattice.mapping'[Graphs.dst(edge)]), state end +""" + Lattice(::Val{:chain}, n; periodic=false) + +Create a chain lattice with `n` sites. +""" function Lattice(::Val{:chain}, n; periodic=false) graph = periodic ? Graphs.cycle_graph(n) : Graphs.path_graph(n) - mapping = BijectiveIdDict{AbstractLane,Int}([Lane(i) => i for i in 1:n]) + mapping = BijectiveDict{Lane,Int}([Lane(i) => i for i in 1:n]) Lattice(mapping, graph) end +""" + Lattice(::Val{:rectangular}, nrows, ncols; periodic=false) + +Create a rectangular lattice with `nrows` rows and `ncols` columns. +""" function Lattice(::Val{:rectangular}, nrows, ncols; periodic=false) graph = Graphs.grid((nrows, ncols); periodic) - mapping = BijectiveIdDict{AbstractLane,Int}([ - Lane(row, col) => row + (col - 1) * nrows for row in 1:nrows for col in 1:ncols - ]) + mapping = BijectiveDict{Lane,Int}([Lane(row, col) => row + (col - 1) * nrows for row in 1:nrows for col in 1:ncols]) Lattice(mapping, graph) end @@ -147,7 +157,7 @@ function Lattice(::Val{:lieb}, ncellrows, ncellcols) nrows, ncols = 1 .+ 2 .* (ncellrows, ncellcols) lanes = [Lane(row, col) for row in 1:nrows for col in 1:ncols if !(row % 2 == 0 && col % 2 == 0)] - mapping = BijectiveIdDict{AbstractLane,Int}([lane => i for (i, lane) in enumerate(lanes)]) + mapping = BijectiveDict{Lane,Int}([lane => i for (i, lane) in enumerate(lanes)]) graph = Graphs.SimpleGraph{Int}(length(lanes)) # add horizontal edges diff --git a/src/MPS.jl b/src/MPS.jl index 604733d55..ff853fce8 100644 --- a/src/MPS.jl +++ b/src/MPS.jl @@ -1,7 +1,7 @@ using Random using LinearAlgebra using Graphs: Graphs -using BijectiveDicts: BijectiveIdDict +using BijectiveDicts abstract type AbstractMPO <: AbstractAnsatz end abstract type AbstractMPS <: AbstractMPO end @@ -91,7 +91,7 @@ function MPS(::NonCanonical, arrays; order=defaultorder(MPS), check=true) sitemap = Dict(Site(i) => symbols[i] for i in 1:n) qtn = Quantum(tn, sitemap) graph = Graphs.path_graph(n) - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[site => i for (i, site) in enumerate(lanes(qtn))]) + mapping = BijectiveDict{Lane,Int}(Pair{Lane,Int}[lane => i for (i, lane) in enumerate(lanes(qtn))]) lattice = Lattice(mapping, graph) ansatz = Ansatz(qtn, lattice) return MPS(ansatz, NonCanonical()) @@ -133,20 +133,20 @@ Check if the tensors in the mps are in the proper [`Form`](@ref). """ check_form(mps::AbstractMPO; kwargs...) = check_form(form(mps), mps; kwargs...) -function check_form(config::MixedCanonical, mps::AbstractMPO; atol=1e-12) +function check_form(config::MixedCanonical, ψ::AbstractMPO; atol=1e-12) orthog_center = config.orthog_center - left, right = if orthog_center isa Site + left, right = if orthog_center isa Lane id(orthog_center) .+ (0, 0) # So left and right get the same value - elseif orthog_center isa Vector{<:Site} + elseif orthog_center isa Vector{<:Lane} extrema(id.(orthog_center)) end - for i in 1:nsites(mps) + for i in 1:nlanes(ψ) if i < left # Check left-canonical tensors - isisometry(mps, Site(i); dir=:right, atol) || throw(ArgumentError("Tensors are not left-canonical")) + isisometry(ψ, Lane(i); dir=:right, atol) || throw(ArgumentError("Tensors are not left-canonical")) elseif i > right # Check right-canonical tensors - isisometry(mps, Site(i); dir=:left, atol) || throw(ArgumentError("Tensors are not right-canonical")) + isisometry(ψ, Lane(i); dir=:left, atol) || throw(ArgumentError("Tensors are not right-canonical")) end end @@ -156,12 +156,12 @@ end function check_form(::Canonical, mps::AbstractMPO; atol=1e-12) for i in 1:nsites(mps) if i > 1 && - !isisometry(contract(mps; between=(Site(i - 1), Site(i)), direction=:right), Site(i); dir=:right, atol) - throw(ArgumentError("Can not form a left-canonical tensor in Site($i) from Γ and λ contraction.")) + !isisometry(contract(mps; between=(Lane(i - 1), Lane(i)), direction=:right), Lane(i); dir=:right, atol) + throw(ArgumentError("Can not form a left-canonical tensor in Lane($i) from Γ and λ contraction.")) end if i < nsites(mps) && - !isisometry(contract(mps; between=(Site(i), Site(i + 1)), direction=:left), Site(i); dir=:left, atol) + !isisometry(contract(mps; between=(Lane(i), Lane(i + 1)), direction=:left), Lane(i); dir=:left, atol) throw(ArgumentError("Can not form a right-canonical tensor in Site($i) from Γ and λ contraction.")) end end @@ -222,7 +222,7 @@ function MPO(arrays::Vector{<:AbstractArray}; order=defaultorder(MPO)) merge!(sitemap, Dict(Site(i; dual=true) => symbols[i + n] for i in 1:n)) qtn = Quantum(tn, sitemap) graph = Graphs.path_graph(n) - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[site => i for (i, site) in enumerate(lanes(qtn))]) + mapping = BijectiveDict{Lane,Int}(Pair{Lane,Int}[lane => i for (i, lane) in enumerate(lanes(qtn))]) lattice = Lattice(mapping, graph) ansatz = Ansatz(qtn, lattice) return MPO(ansatz, NonCanonical()) @@ -317,7 +317,7 @@ function Base.rand(rng::Random.AbstractRNG, ::Type{MPS}; n, maxdim=nothing, elty arrays[1] = reshape(arrays[1], p, p) arrays[n] = reshape(arrays[n], p, p) - return MPS(arrays; order=(:l, :o, :r), form=MixedCanonical(Site(1))) + return MPS(arrays; order=(:l, :o, :r), form=MixedCanonical(Lane(1))) end # TODO different input/output physical dims @@ -407,8 +407,18 @@ function sites(ψ::T, site::Site; dir) where {T<:AbstractMPO} end end +function lanes(ψ::T, lane::Lane; dir) where {T<:AbstractMPO} + if dir === :left + return lane <= lane"1" ? nothing : Lane(id(lane) - 1) + elseif dir === :right + return lane >= Lane(nlanes(ψ)) ? nothing : Lane(id(lane) + 1) + else + throw(ArgumentError("Unknown direction for $T = :$dir")) + end +end + # TODO refactor to use `Lattice` -function inds(kwargs::NamedTuple{(:at, :dir)}, ψ::T) where {T<:AbstractMPO} +function inds(kwargs::NamedTuple{(:at, :dir),Tuple{S,Symbol}}, ψ::T) where {S<:Site,T<:AbstractMPO} if kwargs.dir === :left && kwargs.at == site"1" return nothing elseif kwargs.dir === :right && kwargs.at == Site(nlanes(ψ); dual=isdual(kwargs.at)) @@ -420,6 +430,18 @@ function inds(kwargs::NamedTuple{(:at, :dir)}, ψ::T) where {T<:AbstractMPO} end end +function inds(kwargs::NamedTuple{(:at, :dir),Tuple{L,Symbol}}, ψ::T) where {L<:Lane,T<:AbstractMPO} + if kwargs.dir === :left && kwargs.at == lane"1" + return nothing + elseif kwargs.dir === :right && kwargs.at == Lane(nlanes(ψ)) + return nothing + elseif kwargs.dir ∈ (:left, :right) + return inds(ψ; bond=(kwargs.at, lanes(ψ, kwargs.at; dir=kwargs.dir))) + else + throw(ArgumentError("Unknown direction for $T = :$(kwargs.dir)")) + end +end + function isisometry(ψ::AbstractMPO, site; dir, atol::Real=1e-12) tensor = tensors(ψ; at=site) dirind = inds(ψ; at=site, dir) @@ -443,24 +465,27 @@ end # TODO generalize to AbstractAnsatz # NOTE: in method == :svd the spectral weights are stored in a vector connected to the now virtual hyperindex! -function canonize_site!(ψ::MPS, site::Site; direction::Symbol, method=:qr) +function canonize_site!(ψ::AbstractMPO, lane::Lane; direction::Symbol, method=:qr) left_inds = Symbol[] right_inds = Symbol[] + site = Site(lane) virtualind = if direction === :left - site == Site(1) && throw(ArgumentError("Cannot right-canonize left-most tensor")) - push!(right_inds, inds(ψ; at=site, dir=:left)) + lane == lane"1" && throw(ArgumentError("Cannot right-canonize left-most tensor")) + push!(right_inds, inds(ψ; at=lane, dir=:left)) - site == Site(nsites(ψ)) || push!(left_inds, inds(ψ; at=site, dir=:right)) - push!(left_inds, inds(ψ; at=site)) + lane == Lane(nlanes(ψ)) || push!(left_inds, inds(ψ; at=lane, dir=:right)) + site ∈ ψ && push!(left_inds, inds(ψ; at=site)) + site' ∈ ψ && push!(left_inds, inds(ψ; at=site')) only(right_inds) elseif direction === :right - site == Site(nsites(ψ)) && throw(ArgumentError("Cannot left-canonize right-most tensor")) - push!(right_inds, inds(ψ; at=site, dir=:right)) + lane == Lane(nlanes(ψ)) && throw(ArgumentError("Cannot left-canonize right-most tensor")) + push!(right_inds, inds(ψ; at=lane, dir=:right)) - site == Site(1) || push!(left_inds, inds(ψ; at=site, dir=:left)) - push!(left_inds, inds(ψ; at=site)) + lane == lane"1" || push!(left_inds, inds(ψ; at=lane, dir=:left)) + site ∈ ψ && push!(left_inds, inds(ψ; at=site)) + site' ∈ ψ && push!(left_inds, inds(ψ; at=site')) only(right_inds) else @@ -487,24 +512,24 @@ function canonize!(ψ::AbstractMPO; normalize=false) # right-to-left QR sweep, get right-canonical tensors for i in nsites(ψ):-1:2 - canonize_site!(ψ, Site(i); direction=:left, method=:qr) + canonize_site!(ψ, Lane(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(ψ) - 1) - canonize_site!(ψ, Site(i); direction=:right, method=:svd) + canonize_site!(ψ, Lane(i); direction=:right, method=:svd) # extract the singular values and contract them with the next tensor - Λᵢ = pop!(ψ, tensors(ψ; between=(Site(i), Site(i + 1)))) + Λᵢ = pop!(ψ, tensors(ψ; between=(Lane(i), Lane(i + 1)))) normalize && (Λᵢ ./= norm(Λᵢ)) - Aᵢ₊₁ = tensors(ψ; at=Site(i + 1)) + Aᵢ₊₁ = tensors(ψ; at=Lane(i + 1)) replace!(ψ, Aᵢ₊₁ => contract(Aᵢ₊₁, Λᵢ; dims=())) push!(Λ, Λᵢ) end for i in 2:nsites(ψ) # 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(ψ; at=Site(i)) + A = tensors(ψ; at=Lane(i)) Γᵢ = contract(A, Tensor(diag(pinv(Diagonal(parent(Λᵢ)); atol=1e-64)), inds(Λᵢ)); dims=()) replace!(ψ, A => Γᵢ) push!(ψ, Λᵢ) @@ -519,9 +544,9 @@ end # TODO dispatch on form # TODO generalize to AbstractAnsatz function mixed_canonize!(tn::AbstractMPO, orthog_center) - left, right = if orthog_center isa Site + left, right = if orthog_center isa Lane id(orthog_center) .+ (-1, 1) - elseif orthog_center isa Vector{<:Site} + elseif orthog_center isa Vector{<:Lane} extrema(id.(orthog_center)) .+ (-1, 1) else throw(ArgumentError("`orthog_center` must be a `Site` or a `Vector{Site}`")) @@ -529,12 +554,12 @@ function mixed_canonize!(tn::AbstractMPO, orthog_center) # left-to-right QR sweep (left-canonical tensors) for i in 1:left - canonize_site!(tn, Site(i); direction=:right, method=:qr) + canonize_site!(tn, Lane(i); direction=:right, method=:qr) end # right-to-left QR sweep (right-canonical tensors) - for i in nsites(tn):-1:right - canonize_site!(tn, Site(i); direction=:left, method=:qr) + for i in nlanes(tn):-1:right + canonize_site!(tn, Lane(i); direction=:left, method=:qr) end tn.form = MixedCanonical(orthog_center) @@ -549,13 +574,13 @@ Evolve the [`AbstractAnsatz`](@ref) `ψ` with the [`AbstractMPO`](@ref) `mpo` al If `threshold` or `maxdim` are not `nothing`, the tensors are truncated after each sweep at the proper value, and the bond is normalized if `normalize=true`. If `reset_index=true`, the indices of the `ψ` are reset to the original ones. """ -function evolve!(ψ::AbstractAnsatz, mpo::AbstractMPO; reset_index=true, kwargs...) +function evolve!(ψ::AbstractMPS, mpo::AbstractMPO; reset_index=true, kwargs...) original_sites = copy(Quantum(ψ).sites) normalize = get(kwargs, :normalize, true) evolve!(form(ψ), ψ, mpo; normalize, kwargs...) if reset_index - resetindex!(ψ; init=ninds(TensorNetwork(ψ)) + 1) + resetinds!(ψ; init=ninds(TensorNetwork(ψ)) + 1) replacements = [inds(ψ; at=site) => original_sites[site] for site in keys(original_sites)] replace!(ψ, replacements) @@ -564,15 +589,15 @@ function evolve!(ψ::AbstractAnsatz, mpo::AbstractMPO; reset_index=true, kwargs. return ψ end -function evolve!(::NonCanonical, ψ::AbstractAnsatz, mpo::AbstractMPO; kwargs...) +function evolve!(::NonCanonical, ψ::AbstractMPS, mpo::AbstractMPO; kwargs...) L = nsites(ψ) Tenet.@reindex! outputs(ψ) => inputs(mpo) - right_inds = [inds(ψ; at=Site(i), dir=:right) for i in 1:(L - 1)] + right_inds = [inds(ψ; at=Lane(i), dir=:right) for i in 1:(L - 1)] for i in 1:L contract_ind = inds(ψ; at=Site(i)) - push!(ψ, tensors(mpo; at=Site(i))) + push!(ψ, tensors(mpo; at=Lane(i))) contract!(ψ, contract_ind) merge!(Quantum(ψ).sites, Dict(Site(i) => inds(mpo; at=Site(i)))) end @@ -592,9 +617,9 @@ function evolve!(::NonCanonical, ψ::AbstractAnsatz, mpo::AbstractMPO; kwargs... return ψ end -function evolve!(::MixedCanonical, ψ::AbstractAnsatz, mpo::AbstractMPO; kwargs...) +function evolve!(::MixedCanonical, ψ::AbstractMPS, mpo::AbstractMPO; kwargs...) initial_form = form(ψ) - mixed_canonize!(ψ, Site(nsites(ψ))) # We convert all the tensors to left-canonical form + mixed_canonize!(ψ, Lane(nsites(ψ))) # We convert all the tensors to left-canonical form normalize = get(kwargs, :normalize, true) evolve!(NonCanonical(), ψ, mpo; normalize, kwargs...) @@ -604,10 +629,10 @@ function evolve!(::MixedCanonical, ψ::AbstractAnsatz, mpo::AbstractMPO; kwargs. return ψ end -function evolve!(::Canonical, ψ::AbstractAnsatz, mpo::AbstractMPO; kwargs...) - # We first join the λs to the Γs to get MixedCanonical(Site(1)) form +function evolve!(::Canonical, ψ::AbstractMPS, mpo::AbstractMPO; kwargs...) + # We first join the λs to the Γs to get MixedCanonical(lane"1") form for i in 1:(nsites(ψ) - 1) - contract!(ψ; between=(Site(i), Site(i + 1)), direction=:right) + contract!(ψ; between=(Lane(i), Lane(i + 1)), direction=:right) end # set `maxdim` and `threshold` to `nothing` so we later truncate in the `Canonical` form @@ -633,19 +658,19 @@ truncate_sweep!(ψ::AbstractMPO; kwargs...) = truncate_sweep!(form(ψ), ψ; kwar function truncate_sweep!(::NonCanonical, ψ::AbstractMPO; kwargs...) all(isnothing, get.(Ref(kwargs), [:threshold, :maxdim], nothing)) && return ψ - for i in nsites(ψ):-1:2 - canonize_site!(ψ, Site(i); direction=:left, method=:qr) + for i in nlanes(ψ):-1:2 + canonize_site!(ψ, Lane(i); direction=:left, method=:qr) end # left-to-right SVD sweep, get left-canonical tensors and singular values and truncate - for i in 1:(nsites(ψ) - 1) - canonize_site!(ψ, Site(i); direction=:right, method=:svd) + for i in 1:(nlanes(ψ) - 1) + canonize_site!(ψ, Lane(i); direction=:right, method=:svd) - truncate!(ψ, [Site(i), Site(i + 1)]; kwargs..., compute_local_svd=false) - contract!(ψ; between=(Site(i), Site(i + 1)), direction=:right) + truncate!(ψ, [Lane(i), Lane(i + 1)]; kwargs..., compute_local_svd=false) + contract!(ψ; between=(Lane(i), Lane(i + 1)), direction=:right) end - ψ.form = MixedCanonical(Site(nsites(ψ))) + ψ.form = MixedCanonical(Lane(nlanes(ψ))) return ψ end @@ -657,14 +682,14 @@ end function truncate_sweep!(::Canonical, ψ::AbstractMPO; kwargs...) all(isnothing, get.(Ref(kwargs), [:threshold, :maxdim], nothing)) && return ψ - for i in nsites(ψ):-1:2 - canonize_site!(ψ, Site(i); direction=:left, method=:qr) + for i in nlanes(ψ):-1:2 + canonize_site!(ψ, Lane(i); direction=:left, method=:qr) end # left-to-right SVD sweep, get left-canonical tensors and singular values and truncate - for i in 1:(nsites(ψ) - 1) - canonize_site!(ψ, Site(i); direction=:right, method=:svd) - truncate!(ψ, [Site(i), Site(i + 1)]; kwargs..., compute_local_svd=false) + for i in 1:(nlanes(ψ) - 1) + canonize_site!(ψ, Lane(i); direction=:right, method=:svd) + truncate!(ψ, [Lane(i), Lane(i + 1)]; kwargs..., compute_local_svd=false) end canonize!(ψ) @@ -673,11 +698,11 @@ function truncate_sweep!(::Canonical, ψ::AbstractMPO; kwargs...) end LinearAlgebra.normalize!(ψ::AbstractMPO; kwargs...) = normalize!(form(ψ), ψ; kwargs...) -LinearAlgebra.normalize!(ψ::AbstractMPO, at::Site) = normalize!(form(ψ), ψ; at) -LinearAlgebra.normalize!(ψ::AbstractMPO, bond::Base.AbstractVecOrTuple{Site}) = normalize!(form(ψ), ψ; bond) +LinearAlgebra.normalize!(ψ::AbstractMPO, at::Lane) = normalize!(form(ψ), ψ; at) +LinearAlgebra.normalize!(ψ::AbstractMPO, bond::Base.AbstractVecOrTuple{Lane}) = normalize!(form(ψ), ψ; bond) # NOTE: Inplace normalization of the arrays should be faster, but currently lead to problems for `copy` TensorNetworks -function LinearAlgebra.normalize!(::NonCanonical, ψ::AbstractMPO; at=Site(nsites(ψ) ÷ 2)) +function LinearAlgebra.normalize!(::NonCanonical, ψ::AbstractMPO; at=Lane(nlanes(ψ) ÷ 2)) if at isa Site tensor = tensors(ψ; at) replace!(ψ, tensor => tensor ./ norm(ψ)) @@ -697,9 +722,9 @@ end function LinearAlgebra.normalize!(config::Canonical, ψ::AbstractMPO; bond=nothing) old_norm = norm(ψ) if isnothing(bond) # Normalize all λ tensors - for i in 1:(nsites(ψ) - 1) - λ = tensors(ψ; between=(Site(i), Site(i + 1))) - replace!(ψ, λ => λ ./ old_norm^(1 / (nsites(ψ) - 1))) + for i in 1:(nlanes(ψ) - 1) + λ = tensors(ψ; between=(Lane(i), Lane(i + 1))) + replace!(ψ, λ => λ ./ old_norm^(1 / (nlanes(ψ) - 1))) end else λ = tensors(ψ; between=bond) diff --git a/src/Product.jl b/src/Product.jl index 0807cba73..7a0c56b25 100644 --- a/src/Product.jl +++ b/src/Product.jl @@ -1,5 +1,6 @@ using LinearAlgebra using Graphs: Graphs +using BijectiveDicts """ Product <: AbstractAnsatz @@ -34,7 +35,7 @@ function Product(arrays::AbstractArray{<:AbstractVector}) sitemap = Dict(Site(i) => symbols[i] for i in eachindex(arrays)) qtn = Quantum(TensorNetwork(_tensors), sitemap) graph = Graphs.Graph(n) - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[site => i for (i, site) in enumerate(lanes(qtn))]) + mapping = BijectiveDict{Lane,Int}(Pair{Lane,Int}[lane => i for (i, lane) in enumerate(lanes(qtn))]) lattice = Lattice(mapping, graph) ansatz = Ansatz(qtn, lattice) return Product(ansatz) @@ -56,7 +57,7 @@ function Product(arrays::AbstractArray{<:AbstractMatrix}) ) qtn = Quantum(TensorNetwork(_tensors), sitemap) graph = Graphs.Graph(n) - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[site => i for (i, site) in enumerate(lanes(qtn))]) + mapping = BijectiveDict{Lane,Int}(Pair{Lane,Int}[lane => i for (i, lane) in enumerate(lanes(qtn))]) lattice = Lattice(mapping, graph) ansatz = Ansatz(qtn, lattice) return Product(ansatz) diff --git a/src/Quantum.jl b/src/Quantum.jl index d74fa7105..df152e979 100644 --- a/src/Quantum.jl +++ b/src/Quantum.jl @@ -190,8 +190,8 @@ end function reindex!(a::Quantum, ioa, b::Quantum, iob; reset=true) if reset - resetindex!(a) - resetindex!(b; init=ninds(TensorNetwork(a)) + 1) + resetinds!(a) + resetinds!(b; init=ninds(TensorNetwork(a)) + 1) end sitesa = if ioa === :inputs @@ -228,16 +228,19 @@ function reindex!(a::Quantum, ioa, b::Quantum, iob; reset=true) return b end -function resetindex!(tn::AbstractQuantum; init=1) - tn = Quantum(tn) +function resetinds!(tn::AbstractQuantum; init=1) + qtn = Quantum(tn) - mapping = resetindex!(Val(:return_mapping), tn; init) - replace!(TensorNetwork(tn), mapping) + mapping = resetinds!(Val(:return_mapping), tn; init) + replace!(TensorNetwork(qtn), mapping) - for (site, index) in tn.sites - tn.sites[site] = mapping[index] + for (site, index) in qtn.sites + qtn.sites[site] = mapping[index] end + + return tn end +resetinds(tn::AbstractQuantum; init=1) = resetinds!(copy(tn); init) """ @reindex! a => b reset=true @@ -298,7 +301,7 @@ Return the number of lanes of a [`AbstractQuantum`](@ref) Tensor Network. """ nlanes(tn::AbstractQuantum) = length(lanes(tn)) -function addsite!(tn::Quantum, site, index) +function addsite!(tn::AbstractQuantum, site, index) tn = Quantum(tn) if haskey(tn.sites, site) error("Site $site already exists") @@ -311,7 +314,7 @@ function addsite!(tn::Quantum, site, index) return tn.sites[site] = index end -function rmsite!(tn::Quantum, site) +function rmsite!(tn::AbstractQuantum, site) tn = Quantum(tn) if !haskey(tn.sites, site) error("Site $site does not exist") @@ -341,6 +344,21 @@ function sites(kwargs::@NamedTuple{at::Symbol}, tn::AbstractQuantum) return findfirst(==(kwargs.at), tn.sites) end +""" + isconnectable(a::AbstractQuantum, b::AbstractQuantum) + +Return `true` if two [`AbstractQuantum`](@ref) Tensor Networks can be connected. This means: + + 1. The outputs of `a` are a superset of the inputs of `b`. + 2. The outputs of `a` and `b` are disjoint except for the sites that are connected. +""" +function isconnectable(a, b) + Lane.(sites(a; set=:outputs)) ⊇ Lane.(sites(b; set=:inputs)) && isdisjoint( + setdiff(Lane.(sites(a; set=:outputs)), Lane.(sites(b; set=:inputs))), + setdiff(Lane.(sites(b; set=:inputs)), Lane.(sites(b; set=:outputs))), + ) +end + """ socket(q::AbstractQuantum) diff --git a/src/Tenet.jl b/src/Tenet.jl index ec9bc80ac..551e37cc4 100644 --- a/src/Tenet.jl +++ b/src/Tenet.jl @@ -13,7 +13,7 @@ include("Numerics.jl") include("TensorNetwork.jl") export TensorNetwork, tensors, arrays, neighbors, slice!, contract, contract!, groupinds! -@compat public AbstractTensorNetwork, ninds, ntensors, @unsafe_region, tryprune!, resetindex! +@compat public AbstractTensorNetwork, ninds, ntensors, @unsafe_region, tryprune!, resetinds! include("Transformations.jl") export transform, transform! diff --git a/src/TensorNetwork.jl b/src/TensorNetwork.jl index 34451bea0..23848656a 100644 --- a/src/TensorNetwork.jl +++ b/src/TensorNetwork.jl @@ -532,16 +532,16 @@ function Base.replace!(tn::AbstractTensorNetwork, old_new::Pair{<:Tensor,<:Tenso end """ - resetindex!(tn::AbstractTensorNetwork; init::Int=1) + resetinds!(tn::AbstractTensorNetwork; init::Int=1) Rename all indices in the `TensorNetwork` to a new set of indices starting from `init`th Unicode character. """ -function resetindex!(tn::AbstractTensorNetwork; init::Int=1) - mapping = resetindex!(Val(:return_mapping), tn; init=init) +function resetinds!(tn::AbstractTensorNetwork; init::Int=1) + mapping = resetinds!(Val(:return_mapping), tn; init=init) return replace!(tn, mapping) end -function resetindex!(::Val{:return_mapping}, tn::AbstractTensorNetwork; init::Int=1) +function resetinds!(::Val{:return_mapping}, tn::AbstractTensorNetwork; init::Int=1) gen = IndexCounter(init) return Dict{Symbol,Symbol}([i => nextindex!(gen) for i in inds(tn)]) end diff --git a/test/Ansatz_test.jl b/test/Ansatz_test.jl index 186f5a04d..5bc7fea1e 100644 --- a/test/Ansatz_test.jl +++ b/test/Ansatz_test.jl @@ -1,22 +1,23 @@ using Tenet -using Tenet: Lattice, simple_update! -using BijectiveDicts: BijectiveIdDict +using Tenet: Lattice, simple_update!, AbstractLane +using BijectiveDicts: BijectiveDict using Graphs using LinearAlgebra @testset "Ansatz" begin @testset "No connectivity" begin + n = 2 tn = TensorNetwork([Tensor(ones(2), [:i]), Tensor(ones(2), [:j])]) qtn = Quantum(tn, Dict(site"1" => :i, site"2" => :j)) - graph = Graph(2) - mapping = BijectiveIdDict{AbstractLane,Int}(Pair{AbstractLane,Int}[Lane(i) => i for i in 1:2]) + graph = Graph(n) + mapping = BijectiveDict{Lane,Int}(Pair{Lane,Int}[Lane(i) => i for i in 1:n]) lattice = Lattice(mapping, graph) ansatz = Ansatz(qtn, lattice) @test zero(ansatz) == Ansatz(zero(qtn), lattice) @test Tenet.lattice(ansatz) == lattice - @test isempty(neighbors(ansatz, site"1")) - @test !has_edge(ansatz, site"1", site"2") + @test isempty(neighbors(ansatz, lane"1")) + @test !has_edge(ansatz, lane"1", lane"2") # some AbstractQuantum methods @test inds(ansatz; at=site"1") == :i @@ -32,26 +33,25 @@ using LinearAlgebra @test overlap(ansatz, ansatz) ≈ 4.0 @test norm(ansatz) ≈ 2.0 - @testset let gate = Quantum(TensorNetwork([Tensor([1 0; 0 0], [:a, :b])]), Dict(site"1'" => :a, site"1" => :b)) + @testset let gate = Gate([1 0; 0 0], [site"1'", site"1"]) @test tensors(simple_update!(copy(ansatz), gate); at=site"1") ≈ Tensor([1, 0], [:i]) @test tensors(evolve!(copy(ansatz), gate); at=site"1") ≈ Tensor([1, 0], [:i]) - @test expect(ansatz, gate) ≈ 2.0 + @test expect(ansatz, Quantum(gate)) ≈ 2.0 end - @testset let gate = Quantum( - TensorNetwork([Tensor([1 0; 0 0;;; 0 0; 0 0;;;; 0 0; 0 0;;; 0 0; 0 0], [:a1, :a2, :b1, :b2])]), - Dict(site"1'" => :a1, site"2'" => :a2, site"1" => :b1, site"2" => :b2), + @testset let gate = Gate( + [1 0; 0 0;;; 0 0; 0 0;;;; 0 0; 0 0;;; 0 0; 0 0], [site"1'", site"2'", site"1", site"2"] ) @test_throws Exception simple_update!(copy(ansatz), gate) @test_throws Exception evolve!(copy(ansatz), gate) - @test expect(ansatz, gate) ≈ 1.0 + @test expect(ansatz, Quantum(gate)) ≈ 1.0 end end @testset "Complete connectivity" begin n = 2 - graph = Graphs.complete_graph(2) - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[Site(i) => i for i in 1:n]) + graph = Graphs.complete_graph(n) + mapping = BijectiveDict{Lane,Int}(Pair{Lane,Int}[Lane(i) => i for i in 1:n]) lattice = Lattice(mapping, graph) tn = TensorNetwork([Tensor(ones(2, 2), [:s1, :i]), Tensor(ones(2, 2), [:s2, :i])]) qtn = Quantum(tn, Dict(site"1" => :s1, site"2" => :s2)) @@ -60,34 +60,33 @@ using LinearAlgebra @test zero(ansatz) == Ansatz(zero(qtn), lattice) @test Tenet.lattice(ansatz) == lattice - @test issetequal(neighbors(ansatz, site"1"), [site"2"]) - @test issetequal(neighbors(ansatz, site"2"), [site"1"]) + @test issetequal(neighbors(ansatz, lane"1"), [lane"2"]) + @test issetequal(neighbors(ansatz, lane"2"), [lane"1"]) - @test has_edge(ansatz, site"1", site"2") - @test has_edge(ansatz, site"2", site"1") + @test has_edge(ansatz, lane"1", lane"2") + @test has_edge(ansatz, lane"2", lane"1") - @test inds(ansatz; bond=(site"1", site"2")) == :i + @test inds(ansatz; bond=(lane"1", lane"2")) == :i # the following methods will throw a AssertionError in here, but it's not a hard API requirement - @test_throws Exception tensors(ansatz; bond=(site"1", site"2")) - @test_throws Exception truncate!(ansatz, bond=(site"1", site"2")) + @test_throws Exception tensors(ansatz; bond=(lane"1", lane"2")) + @test_throws Exception truncate!(ansatz, bond=(lane"1", lane"2")) @test overlap(ansatz, ansatz) ≈ 16.0 @test norm(ansatz) ≈ 4.0 - @testset let gate = Quantum(TensorNetwork([Tensor([1 0; 0 0], [:a, :b])]), Dict(site"1'" => :a, site"1" => :b)) + @testset let gate = Gate([1 0; 0 0], [site"1'", site"1"]) @test tensors(simple_update!(copy(ansatz), gate); at=site"1") ≈ Tensor([1 1; 0 0], [:s1, :i]) @test tensors(evolve!(copy(ansatz), gate); at=site"1") ≈ Tensor([1 1; 0 0], [:s1, :i]) - @test expect(ansatz, gate) ≈ 8.0 + @test expect(ansatz, Quantum(gate)) ≈ 8.0 end - @testset let gate = Quantum( - TensorNetwork([Tensor([1 0; 0 0;;; 0 0; 0 0;;;; 0 0; 0 0;;; 0 0; 0 0], [:a1, :a2, :b1, :b2])]), - Dict(site"1'" => :a1, site"2'" => :a2, site"1" => :b1, site"2" => :b2), + @testset let gate = Gate( + [1 0; 0 0;;; 0 0; 0 0;;;; 0 0; 0 0;;; 0 0; 0 0], [site"1'", site"2'", site"1", site"2"] ) - @test expect(ansatz, gate) ≈ 4.0 + @test expect(ansatz, Quantum(gate)) ≈ 4.0 @testset let ψ = simple_update!(copy(ansatz), gate) - @test tensors(ψ; bond=(site"1", site"2")) ≈ Tensor([2, 0], [:i]) + @test tensors(ψ; bond=(lane"1", lane"2")) ≈ Tensor([2, 0], [:i]) end end end diff --git a/test/Gate_test.jl b/test/Gate_test.jl index 6c8e1c284..b7ecf6f6a 100644 --- a/test/Gate_test.jl +++ b/test/Gate_test.jl @@ -12,7 +12,7 @@ @test replace(gate, :i => :k) == Gate(Tensor(data, (:k, :j)), [site"1'", site"1"]) @test replace(gate, site"1'" => :k) == Gate(Tensor(data, (:k, :j)), [site"1'", site"1"]) - gate = Tenet.resetindex(gate) + gate = Tenet.resetinds(gate) @test gate isa Gate @test parent(Tensor(gate)) == data @test isdisjoint(inds(gate), inds(tensor)) @@ -22,5 +22,14 @@ @test gate isa Gate @test parent(Tensor(gate)) == data @test issetequal(sites(gate), (site"1'", site"1")) + + tn = TensorNetwork(gate) + @test tn isa TensorNetwork + @test issetequal(tensors(tn), [Tensor(gate)]) + + qtn = Quantum(gate) + @test qtn isa Quantum + @test issetequal(sites(qtn), (site"1'", site"1")) + @test issetequal(tensors(qtn), [Tensor(gate)]) end end \ No newline at end of file diff --git a/test/Lattice_test.jl b/test/Lattice_test.jl index 8976b793f..ddb0ce513 100644 --- a/test/Lattice_test.jl +++ b/test/Lattice_test.jl @@ -1,11 +1,12 @@ using Tenet -using Tenet: Lattice, Bond, AbstractLane +using Tenet: Lattice, Bond using Graphs -using BijectiveDicts: BijectiveIdDict +using BijectiveDicts: BijectiveDict @testset "Lattice" begin - @testset let graph = SimpleGraph(), mapping = BijectiveIdDict{AbstractLane,Int}(), lattice = Lattice(mapping, graph) + @testset let graph = SimpleGraph(), mapping = BijectiveDict{Lane,Int}(), lattice = Lattice(mapping, graph) @test lattice == zero(Lattice) + @test lattice == copy(lattice) && lattice !== copy(lattice) @test nv(lattice) == 0 @test ne(lattice) == 0 @test isempty(vertices(lattice)) diff --git a/test/MPO_test.jl b/test/MPO_test.jl index 258c44679..612aa4915 100644 --- a/test/MPO_test.jl +++ b/test/MPO_test.jl @@ -5,7 +5,7 @@ @test nsites(H; set=:outputs) == 3 @test issetequal(sites(H), [site"1", site"2", site"3", site"1'", site"2'", site"3'"]) @test boundary(H) == Open() - @test inds(H; at=site"1", dir=:left) == inds(H; at=site"3", dir=:right) == nothing + @test inds(H; at=lane"1", dir=:left) == inds(H; at=lane"3", dir=:right) == nothing # Default order (:o :i, :l, :r) H = MPO([rand(2, 4, 1), rand(2, 4, 1, 3), rand(2, 4, 3)]) @@ -14,9 +14,9 @@ @test size(tensors(H; at=site"2")) == (2, 4, 1, 3) @test size(tensors(H; at=site"3")) == (2, 4, 3) - @test inds(H; at=site"1", dir=:left) == inds(H; at=site"3", dir=:right) === nothing - @test inds(H; at=site"2", dir=:left) == inds(H; at=site"1", dir=:right) !== nothing - @test inds(H; at=site"3", dir=:left) == inds(H; at=site"2", dir=:right) !== nothing + @test inds(H; at=lane"1", dir=:left) == inds(H; at=lane"3", dir=:right) === nothing + @test inds(H; at=lane"2", dir=:left) == inds(H; at=lane"1", dir=:right) !== nothing + @test inds(H; at=lane"3", dir=:left) == inds(H; at=lane"2", dir=:right) !== nothing for i in 1:Tenet.ntensors(H) @test size(H, inds(H; at=Site(i))) == 2 @@ -37,9 +37,9 @@ @test size(tensors(H; at=site"2")) == (3, 2, 1, 4) @test size(tensors(H; at=site"3")) == (2, 3, 4) - @test inds(H; at=site"1", dir=:left) == inds(H; at=site"3", dir=:right) === nothing - @test inds(H; at=site"2", dir=:left) == inds(H; at=site"1", dir=:right) !== nothing - @test inds(H; at=site"3", dir=:left) == inds(H; at=site"2", dir=:right) !== nothing + @test inds(H; at=lane"1", dir=:left) == inds(H; at=lane"3", dir=:right) === nothing + @test inds(H; at=lane"2", dir=:left) == inds(H; at=lane"1", dir=:right) !== nothing + @test inds(H; at=lane"3", dir=:left) == inds(H; at=lane"2", dir=:right) !== nothing for i in 1:Tenet.ntensors(H) @test size(H, inds(H; at=Site(i))) == 2 diff --git a/test/MPS_test.jl b/test/MPS_test.jl index b8359a4e9..20e847400 100644 --- a/test/MPS_test.jl +++ b/test/MPS_test.jl @@ -1,4 +1,4 @@ -using Tenet: canonize_site, canonize_site! +using Tenet: nsites, State, canonize_site, canonize_site! using LinearAlgebra @testset "MPS" begin @@ -15,18 +15,18 @@ using LinearAlgebra @test size(tensors(ψ; at=site"1")) == (2, 1) @test size(tensors(ψ; at=site"2")) == (2, 1, 3) @test size(tensors(ψ; at=site"3")) == (2, 3) - @test inds(ψ; at=site"1", dir=:left) == inds(ψ; at=site"3", dir=:right) === nothing - @test inds(ψ; at=site"2", dir=:left) == inds(ψ; at=site"1", dir=:right) - @test inds(ψ; at=site"3", dir=:left) == inds(ψ; at=site"2", dir=:right) + @test inds(ψ; at=lane"1", dir=:left) == inds(ψ; at=lane"3", dir=:right) === nothing + @test inds(ψ; at=lane"2", dir=:left) == inds(ψ; at=lane"1", dir=:right) + @test inds(ψ; at=lane"3", dir=:left) == inds(ψ; at=lane"2", dir=:right) arrays = [permutedims(arrays[1], (2, 1)), permutedims(arrays[2], (3, 1, 2)), permutedims(arrays[3], (1, 2))] # now we have (:r, :o, :l) ψ = MPS(arrays; order=[:r, :o, :l]) @test size(tensors(ψ; at=site"1")) == (1, 2) @test size(tensors(ψ; at=site"2")) == (3, 2, 1) @test size(tensors(ψ; at=site"3")) == (2, 3) - @test inds(ψ; at=site"1", dir=:left) == inds(ψ; at=site"3", dir=:right) === nothing - @test inds(ψ; at=site"2", dir=:left) == inds(ψ; at=site"1", dir=:right) !== nothing - @test inds(ψ; at=site"3", dir=:left) == inds(ψ; at=site"2", dir=:right) !== nothing + @test inds(ψ; at=lane"1", dir=:left) == inds(ψ; at=lane"3", dir=:right) === nothing + @test inds(ψ; at=lane"2", dir=:left) == inds(ψ; at=lane"1", dir=:right) !== nothing + @test inds(ψ; at=lane"3", dir=:left) == inds(ψ; at=lane"2", dir=:right) !== nothing @test all(i -> size(ψ, inds(ψ; at=Site(i))) == 2, 1:nsites(ψ)) @testset "identity constructor" begin @@ -75,7 +75,7 @@ using LinearAlgebra end end - @testset "Site" begin + @testset "sites" begin ψ = MPS([rand(2, 2), rand(2, 2, 2), rand(2, 2)]) @test isnothing(sites(ψ, site"1"; dir=:left)) @@ -88,6 +88,19 @@ using LinearAlgebra @test sites(ψ, site"1"; dir=:right) == site"2" end + @testset "lanes" begin + ψ = MPS([rand(2, 2), rand(2, 2, 2), rand(2, 2)]) + + @test isnothing(lanes(ψ, lane"1"; dir=:left)) + @test isnothing(lanes(ψ, lane"3"; dir=:right)) + + @test lanes(ψ, lane"2"; dir=:left) == lane"1" + @test lanes(ψ, lane"3"; dir=:left) == lane"2" + + @test lanes(ψ, lane"2"; dir=:right) == lane"3" + @test lanes(ψ, lane"1"; dir=:right) == lane"2" + end + @testset "adjoint" begin ψ = rand(MPS; n=3, maxdim=2, eltype=ComplexF64) @test socket(ψ') == State(; dual=true) @@ -97,31 +110,31 @@ using LinearAlgebra @testset "truncate!" begin @testset "NonCanonical" begin ψ = MPS([rand(2, 2), rand(2, 2, 2), rand(2, 2)]) - canonize_site!(ψ, Site(2); direction=:right, method=:svd) + canonize_site!(ψ, lane"2"; direction=:right, method=:svd) - truncated = truncate(ψ, [site"2", site"3"]; maxdim=1) - @test size(truncated, inds(truncated; bond=[site"2", site"3"])) == 1 + truncated = truncate(ψ, [lane"2", lane"3"]; maxdim=1) + @test size(truncated, inds(truncated; bond=[lane"2", lane"3"])) == 1 - singular_values = tensors(ψ; between=(site"2", site"3")) - truncated = truncate(ψ, [site"2", site"3"]; threshold=singular_values[2] + 0.1) - @test size(truncated, inds(truncated; bond=[site"2", site"3"])) == 1 + singular_values = tensors(ψ; between=(lane"2", lane"3")) + truncated = truncate(ψ, [lane"2", lane"3"]; threshold=singular_values[2] + 0.1) + @test size(truncated, inds(truncated; bond=[lane"2", lane"3"])) == 1 # If maxdim > size(spectrum), the bond dimension is not truncated - truncated = truncate(ψ, [site"2", site"3"]; maxdim=4) - @test size(truncated, inds(truncated; bond=[site"2", site"3"])) == 2 + truncated = truncate(ψ, [lane"2", lane"3"]; maxdim=4) + @test size(truncated, inds(truncated; bond=[lane"2", lane"3"])) == 2 normalize!(ψ) - truncated = truncate(ψ, [site"2", site"3"]; maxdim=1, normalize=true) + truncated = truncate(ψ, [lane"2", lane"3"]; maxdim=1, normalize=true) @test norm(truncated) ≈ 1.0 end @testset "MixedCanonical" begin ψ = rand(MPS; n=5, maxdim=16) - truncated = truncate(ψ, [site"2", site"3"]; maxdim=3) - @test size(truncated, inds(truncated; bond=[site"2", site"3"])) == 3 + truncated = truncate(ψ, [lane"2", lane"3"]; maxdim=3) + @test size(truncated, inds(truncated; bond=[lane"2", lane"3"])) == 3 - truncated = truncate(ψ, [site"2", site"3"]; maxdim=3, normalize=true) + truncated = truncate(ψ, [lane"2", lane"3"]; maxdim=3, normalize=true) @test norm(truncated) ≈ 1.0 end @@ -129,12 +142,12 @@ using LinearAlgebra ψ = rand(MPS; n=5, maxdim=16) canonize!(ψ) - truncated = truncate(ψ, [site"2", site"3"]; maxdim=2, canonize=true, normalize=true) - @test size(truncated, inds(truncated; bond=[site"2", site"3"])) == 2 + truncated = truncate(ψ, [lane"2", lane"3"]; maxdim=2, canonize=true, normalize=true) + @test size(truncated, inds(truncated; bond=[lane"2", lane"3"])) == 2 @test Tenet.check_form(truncated) @test norm(truncated) ≈ 1.0 - truncated = truncate(ψ, [site"2", site"3"]; maxdim=2, canonize=false, normalize=true) + truncated = truncate(ψ, [lane"2", lane"3"]; maxdim=2, canonize=false, normalize=true) @test norm(truncated) ≈ 1.0 end end @@ -164,7 +177,7 @@ using LinearAlgebra normalized = normalize(ψ) @test norm(normalized) ≈ 1.0 - normalize!(ψ, Site(3)) + normalize!(ψ, lane"3") @test norm(ψ) ≈ 1.0 end @@ -172,13 +185,13 @@ using LinearAlgebra ψ = rand(MPS; n=5, maxdim=16) # Perturb the state to make it non-normalized - t = tensors(ψ; at=site"3") + t = tensors(ψ; at=lane"3") replace!(ψ, t => Tensor(rand(size(t)...), inds(t))) normalized = normalize(ψ) @test norm(normalized) ≈ 1.0 - normalize!(ψ, Site(3)) + normalize!(ψ, lane"3") @test norm(ψ) ≈ 1.0 end @@ -189,7 +202,7 @@ using LinearAlgebra normalized = normalize(ψ) @test norm(normalized) ≈ 1.0 - normalize!(ψ, (Site(3), Site(4))) + normalize!(ψ, (lane"3", lane"4")) @test norm(ψ) ≈ 1.0 end end @@ -197,29 +210,29 @@ using LinearAlgebra @testset "canonize_site!" begin ψ = MPS([rand(4, 4), rand(4, 4, 4), rand(4, 4)]) - @test_throws ArgumentError canonize_site!(ψ, Site(1); direction=:left) - @test_throws ArgumentError canonize_site!(ψ, Site(3); direction=:right) + @test_throws ArgumentError canonize_site!(ψ, lane"1"; direction=:left) + @test_throws ArgumentError canonize_site!(ψ, lane"3"; direction=:right) for method in [:qr, :svd] - canonized = canonize_site(ψ, site"1"; direction=:right, method=method) - @test isleftcanonical(canonized, site"1") + canonized = canonize_site(ψ, lane"1"; direction=:right, method=method) + @test isleftcanonical(canonized, lane"1") @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(ψ)) - canonized = canonize_site(ψ, site"2"; direction=:right, method=method) - @test isleftcanonical(canonized, site"2") + canonized = canonize_site(ψ, lane"2"; direction=:right, method=method) + @test isleftcanonical(canonized, lane"2") @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(ψ)) - canonized = canonize_site(ψ, site"2"; direction=:left, method=method) - @test isrightcanonical(canonized, site"2") + canonized = canonize_site(ψ, lane"2"; direction=:left, method=method) + @test isrightcanonical(canonized, lane"2") @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(ψ)) - canonized = canonize_site(ψ, site"3"; direction=:left, method=method) - @test isrightcanonical(canonized, site"3") + canonized = canonize_site(ψ, lane"3"; direction=:left, method=method) + @test isrightcanonical(canonized, lane"3") @test isapprox(contract(transform(TensorNetwork(canonized), Tenet.HyperFlatten())), contract(ψ)) end # Ensure that svd creates a new tensor - @test length(tensors(canonize_site(ψ, Site(2); direction=:left, method=:svd))) == 4 + @test length(tensors(canonize_site(ψ, lane"2"; direction=:left, method=:svd))) == 4 end @testset "canonize!" begin @@ -235,21 +248,21 @@ using LinearAlgebra @test isapprox(norm(ψ), 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] + Λ = [tensors(canonized; between=(Lane(i), Lane(i + 1))) for i in 1:4] @test map(λ -> sum(abs2, λ), Λ) ≈ ones(length(Λ)) * norm(canonized)^2 for i in 1:5 canonized = canonize(ψ) if i == 1 - @test isleftcanonical(canonized, Site(i)) + @test isleftcanonical(canonized, Lane(i)) elseif i == 5 # in the limits of the chain, we get the norm of the state - normalize!(tensors(canonized; bond=(Site(i - 1), Site(i)))) - contract!(canonized; between=(Site(i - 1), Site(i)), direction=:right) - @test isleftcanonical(canonized, Site(i)) + normalize!(tensors(canonized; bond=(Lane(i - 1), Lane(i)))) + contract!(canonized; between=(Lane(i - 1), Lane(i)), direction=:right) + @test isleftcanonical(canonized, Lane(i)) else - contract!(canonized; between=(Site(i - 1), Site(i)), direction=:right) - @test isleftcanonical(canonized, Site(i)) + contract!(canonized; between=(Lane(i - 1), Lane(i)), direction=:right) + @test isleftcanonical(canonized, Lane(i)) end end @@ -257,14 +270,14 @@ using LinearAlgebra canonized = canonize(ψ) if i == 1 # in the limits of the chain, we get the norm of the state - normalize!(tensors(canonized; bond=(Site(i), Site(i + 1)))) - contract!(canonized; between=(Site(i), Site(i + 1)), direction=:left) - @test isrightcanonical(canonized, Site(i)) + normalize!(tensors(canonized; bond=(Lane(i), Lane(i + 1)))) + contract!(canonized; between=(Lane(i), Lane(i + 1)), direction=:left) + @test isrightcanonical(canonized, Lane(i)) elseif i == 5 - @test isrightcanonical(canonized, Site(i)) + @test isrightcanonical(canonized, Lane(i)) else - contract!(canonized; between=(Site(i), Site(i + 1)), direction=:left) - @test isrightcanonical(canonized, Site(i)) + contract!(canonized; between=(Lane(i), Lane(i + 1)), direction=:left) + @test isrightcanonical(canonized, Lane(i)) end end end @@ -272,31 +285,31 @@ using LinearAlgebra @testset "mixed_canonize!" begin @testset "single Site" begin ψ = MPS([rand(4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4)]) - canonized = mixed_canonize(ψ, site"3") + canonized = mixed_canonize(ψ, lane"3") @test Tenet.check_form(canonized) @test form(canonized) isa MixedCanonical - @test form(canonized).orthog_center == site"3" + @test form(canonized).orthog_center == lane"3" - @test isisometry(canonized, site"1"; dir=:right) - @test isisometry(canonized, site"2"; dir=:right) - @test isisometry(canonized, site"4"; dir=:left) - @test isisometry(canonized, site"5"; dir=:left) + @test isisometry(canonized, lane"1"; dir=:right) + @test isisometry(canonized, lane"2"; dir=:right) + @test isisometry(canonized, lane"4"; dir=:left) + @test isisometry(canonized, lane"5"; dir=:left) @test contract(canonized) ≈ contract(ψ) end @testset "multiple Sites" begin ψ = MPS([rand(4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4, 4), rand(4, 4)]) - canonized = mixed_canonize(ψ, [site"2", site"3"]) + canonized = mixed_canonize(ψ, [lane"2", lane"3"]) @test Tenet.check_form(canonized) @test form(canonized) isa MixedCanonical - @test form(canonized).orthog_center == [site"2", site"3"] + @test form(canonized).orthog_center == [lane"2", lane"3"] - @test isisometry(canonized, site"1"; dir=:right) - @test isisometry(canonized, site"4"; dir=:left) - @test isisometry(canonized, site"5"; dir=:left) + @test isisometry(canonized, lane"1"; dir=:right) + @test isisometry(canonized, lane"4"; dir=:left) + @test isisometry(canonized, lane"5"; dir=:left) @test contract(canonized) ≈ contract(ψ) end @@ -315,7 +328,7 @@ using LinearAlgebra @testset "one site" begin i = 2 mat = reshape(LinearAlgebra.I(2), 2, 2) - gate = Quantum(mat, [Site(i), Site(i; dual=true)]) + gate = Gate(mat, [Site(i), Site(i; dual=true)]) ψ = MPS([rand(2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2)]) @testset "NonCanonical" begin @@ -337,18 +350,19 @@ using LinearAlgebra @testset "two sites" begin mat = reshape(LinearAlgebra.I(4), 2, 2, 2, 2) - gate = Quantum(mat, [site"2", site"3", site"2'", site"3'"]) + gate = Gate(mat, [site"2", site"3", site"2'", site"3'"]) ψ = MPS([rand(2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2)]) @testset "NonCanonical" begin - ϕ = deepcopy(ψ) - evolve!(ϕ, gate; threshold=1e-14) - @test length(tensors(ϕ)) == 5 - @test issetequal(size.(tensors(ϕ)), [(2, 2), (2, 2, 2), (2,), (2, 2, 2), (2, 2, 2), (2, 2)]) - @test isapprox(contract(ϕ), contract(ψ)) - - evolved = evolve!(normalize(ψ), gate; maxdim=1, normalize=true) - @test norm(evolved) ≈ 1.0 + let ϕ = deepcopy(ψ) + evolve!(ϕ, gate; threshold=1e-14) + @test length(tensors(ϕ)) == 5 + @test issetequal(size.(tensors(ϕ)), [(2, 2), (2, 2, 2), (2,), (2, 2, 2), (2, 2, 2), (2, 2)]) + @test isapprox(contract(ϕ), contract(ψ)) + + evolved = evolve!(normalize(ψ), gate; maxdim=1, normalize=true) + @test norm(evolved) ≈ 1.0 + end end @testset "Canonical" begin @@ -408,16 +422,16 @@ using LinearAlgebra end @testset "MixedCanonical" begin - mixed_canonize!(ϕ_3, site"3") + mixed_canonize!(ϕ_3, lane"3") evolve!(ϕ_3, mpo) @test length(tensors(ϕ_3)) == 5 - @test form(ϕ_3) == MixedCanonical(Site(3)) + @test form(ϕ_3) == MixedCanonical(lane"3") @test norm(ϕ_3) ≈ 1.0 @test Tenet.check_form(ϕ_3) - evolved = evolve!(deepcopy(mixed_canonize!(ψ, site"3")), mpo; maxdim=3) + evolved = evolve!(deepcopy(mixed_canonize!(ψ, lane"3")), mpo; maxdim=3) @test all(x -> x ≤ 3, vcat([collect(t) for t in vec(size.(tensors(evolved)))]...)) - @test form(evolved) == MixedCanonical(Site(3)) + @test form(evolved) == MixedCanonical(lane"3") @test norm(evolved) ≈ 1.0 @test Tenet.check_form(evolved) end @@ -435,25 +449,25 @@ using LinearAlgebra @testset "contract between" begin ψ = rand(MPS; n=5, maxdim=20) let canonized = canonize(ψ) - @test_throws ArgumentError contract!(canonized; between=(site"1", site"2"), direction=:dummy) + @test_throws ArgumentError contract!(canonized; between=(lane"1", lane"2"), direction=:dummy) end canonized = canonize(ψ) for i in 1:4 - contract_some = contract(canonized; between=(Site(i), Site(i + 1))) - Bᵢ = tensors(contract_some; at=Site(i)) + contract_some = contract(canonized; between=(Lane(i), Lane(i + 1))) + Bᵢ = tensors(contract_some; at=Lane(i)) @test isapprox(contract(contract_some), contract(ψ)) @test_throws Tenet.MissingSchmidtCoefficientsException tensors( - contract_some; between=(Site(i), Site(i + 1)) + contract_some; between=(Lane(i), Lane(i + 1)) ) - @test isrightcanonical(contract_some, Site(i)) - @test isleftcanonical(contract(canonized; between=(Site(i), Site(i + 1)), direction=:right), Site(i + 1)) + @test isrightcanonical(contract_some, Lane(i)) + @test isleftcanonical(contract(canonized; between=(Lane(i), Lane(i + 1)), direction=:right), Lane(i + 1)) - Γᵢ = tensors(canonized; at=Site(i)) - Λᵢ₊₁ = tensors(canonized; between=(Site(i), Site(i + 1))) + Γᵢ = tensors(canonized; at=Lane(i)) + Λᵢ₊₁ = tensors(canonized; between=(Lane(i), Lane(i + 1))) @test Bᵢ ≈ contract(Γᵢ, Λᵢ₊₁; dims=()) end end diff --git a/test/Product_test.jl b/test/Product_test.jl index ce82a041b..b858f3d6e 100644 --- a/test/Product_test.jl +++ b/test/Product_test.jl @@ -1,6 +1,7 @@ -@testset "Product" begin - using LinearAlgebra +using LinearAlgebra +using Tenet: nsites, State, Operator +@testset "Product" begin # TODO test `Product` with `Scalar` socket qtn = Product([rand(2) for _ in 1:3]) From 283e53fcbc78b6511d23885d80cf0c0673bb9ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Tue, 14 Jan 2025 22:33:50 +0100 Subject: [PATCH 11/13] format code --- test/Gate_test.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Gate_test.jl b/test/Gate_test.jl index b7ecf6f6a..9f39c6469 100644 --- a/test/Gate_test.jl +++ b/test/Gate_test.jl @@ -32,4 +32,4 @@ @test issetequal(sites(qtn), (site"1'", site"1")) @test issetequal(tensors(qtn), [Tensor(gate)]) end -end \ No newline at end of file +end From 81dd976a91d79cc6437579e1e9805dfe5cc66e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Wed, 15 Jan 2025 00:21:31 +0100 Subject: [PATCH 12/13] fix Quac integration --- ext/TenetQuacExt.jl | 43 +++++++---------------------------- src/Quantum.jl | 23 ------------------- test/integration/Quac_test.jl | 21 ++++++++--------- 3 files changed, 17 insertions(+), 70 deletions(-) diff --git a/ext/TenetQuacExt.jl b/ext/TenetQuacExt.jl index 7907646cf..64373779a 100644 --- a/ext/TenetQuacExt.jl +++ b/ext/TenetQuacExt.jl @@ -1,49 +1,22 @@ module TenetQuacExt using Tenet -using Quac: Gate, Circuit, lanes, arraytype, Swap +using Quac: Quac, arraytype, Swap -function Tenet.Quantum(gate::Gate) - return Tenet.Quantum(arraytype(gate)(gate), Site[Site.(lanes(gate))..., Site.(lanes(gate); dual=true)...]) +function Base.convert(::Type{Tenet.Gate}, gate::Quac.Gate) + return Tenet.Gate(arraytype(gate)(gate), Site[Site.(Quac.lanes(gate))..., Site.(Quac.lanes(gate); dual=true)...]) end -Tenet.evolve!(qtn::Ansatz, gate::Gate; kwargs...) = evolve!(qtn, Quantum(gate); kwargs...) +Tenet.evolve!(qtn::Tenet.AbstractAnsatz, gate::Quac.Gate; kwargs...) = evolve!(qtn, convert(Gate, gate); kwargs...) -function Tenet.Quantum(circuit::Circuit) - n = lanes(circuit) - gen = Tenet.IndexCounter() - - wire = [[Tenet.nextindex!(gen)] for _ in 1:n] - tensors = Tensor[] +function Base.convert(::Type{Tenet.Circuit}, circuit::Quac.Circuit) + tenetcirc = Tenet.Circuit() for gate in circuit - G = arraytype(gate) - array = G(gate) - - if gate isa Swap - (a, b) = lanes(gate) - wire[a], wire[b] = wire[b], wire[a] - continue - end - - inds = (x -> collect(Iterators.flatten(zip(x...))))( - map(lanes(gate)) do l - from, to = last(wire[l]), Tenet.nextindex!(gen) - push!(wire[l], to) - (from, to) - end, - ) - - tensor = Tensor(array, tuple(inds...)) - push!(tensors, tensor) + push!(tenetcirc, convert(Tenet.Gate, gate)) end - sites = merge( - Dict([Site(site; dual=true) => first(index) for (site, index) in enumerate(wire)]), - Dict([Site(site; dual=false) => last(index) for (site, index) in enumerate(wire)]), - ) - - return Quantum(Tenet.TensorNetwork(tensors), sites) + return tenetcirc end end diff --git a/src/Quantum.jl b/src/Quantum.jl index df152e979..d3e499e72 100644 --- a/src/Quantum.jl +++ b/src/Quantum.jl @@ -71,29 +71,6 @@ end Quantum(tn::Quantum) = tn -""" - Quantum(array, sites) - -Construct a [`Quantum`](@ref) Tensor Network from an array and a list of sites. Useful for simple operators like gates. -""" -function Quantum(array, sites) - if ndims(array) != length(sites) - throw(ArgumentError("Number of sites must match number of dimensions of array")) - end - - gen = IndexCounter() - symbols = map(_ -> nextindex!(gen), sites) - 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 qtn -end - """ TensorNetwork(q::AbstractQuantum) diff --git a/test/integration/Quac_test.jl b/test/integration/Quac_test.jl index c6fe5d23f..15e7a010d 100644 --- a/test/integration/Quac_test.jl +++ b/test/integration/Quac_test.jl @@ -1,22 +1,21 @@ -@testset "Quac" begin - using Quac +using Test +using Tenet +using Tenet: nsites, Operator +using Quac: Quac +@testset "Quac" begin @testset "Gate" begin id_gate = 2 gate = Quac.Z(id_gate) + qgate = convert(Gate, gate) - qgate = Quantum(gate) - - @test nsites(qgate; set=:inputs) == 1 - @test nsites(qgate; set=:outputs) == 1 @test issetequal(sites(qgate), [Site(id_gate), Site(id_gate; dual=true)]) - @test socket(qgate) == Operator() end @testset "QFT" begin n = 3 qftcirc = Quac.Algorithms.QFT(n) - qftqtn = Quantum(qftcirc) + qftqtn = convert(Circuit, qftcirc) # correct number of inputs and outputs @test nsites(qftqtn; set=:inputs) == n @@ -24,12 +23,10 @@ @test socket(qftqtn) == Operator() # all open indices are sites - siteinds = getindex.((qftqtn,), sites(qftqtn)) - @test issetequal(inds(TensorNetwork(qftqtn); set=:open), siteinds) + @test issetequal(inds(qftqtn; set=:open), inds(qftqtn; set=:physical)) # all inner indices are not sites # TODO too strict condition. remove? - notsiteinds = setdiff(inds(TensorNetwork(qftqtn)), siteinds) - @test_skip issetequal(inds(TensorNetwork(qftqtn); set=:inner), notsiteinds) + @test_skip issetequal(inds(qftqtn; set=:inner), inds(qftqtn; set=:virtual)) end end From f76d46aa5950a2ce43999ed683868398efcb27f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Wed, 15 Jan 2025 00:26:42 +0100 Subject: [PATCH 13/13] last test fixes --- test/MPS_test.jl | 4 ++-- test/TensorNetwork_test.jl | 10 ++++++---- test/integration/ChainRules_test.jl | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/MPS_test.jl b/test/MPS_test.jl index 20e847400..43a5ef9ed 100644 --- a/test/MPS_test.jl +++ b/test/MPS_test.jl @@ -318,10 +318,10 @@ using LinearAlgebra @testset "expect" begin i, j = 2, 3 mat = reshape(kron(LinearAlgebra.I(2), LinearAlgebra.I(2)), 2, 2, 2, 2) - gate = Quantum(mat, [Site(i), Site(j), Site(i; dual=true), Site(j; dual=true)]) + gate = Gate(mat, [Site(i), Site(j), Site(i; dual=true), Site(j; dual=true)]) ψ = MPS([rand(2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2, 2), rand(2, 2)]) - @test isapprox(expect(ψ, [gate]), norm(ψ)^2) + @test expect(ψ, Quantum.([gate])) ≈ norm(ψ)^2 end @testset "evolve!" begin diff --git a/test/TensorNetwork_test.jl b/test/TensorNetwork_test.jl index 5450dffc4..92cc32bfe 100644 --- a/test/TensorNetwork_test.jl +++ b/test/TensorNetwork_test.jl @@ -1,8 +1,10 @@ -@testset "TensorNetwork" begin - using Serialization - using Graphs: neighbors - using LinearAlgebra +using Test +using Tenet +using Serialization +using Graphs: neighbors +using LinearAlgebra +@testset "TensorNetwork" begin @testset "Constructors" begin @testset "empty" begin tn = TensorNetwork() diff --git a/test/integration/ChainRules_test.jl b/test/integration/ChainRules_test.jl index 0ef7d8ac4..97b280ff3 100644 --- a/test/integration/ChainRules_test.jl +++ b/test/integration/ChainRules_test.jl @@ -2,7 +2,7 @@ using Tenet: Tensor, contract, Lattice using ChainRulesTestUtils using Graphs - using BijectiveDicts: BijectiveIdDict + using BijectiveDicts @testset "Tensor" begin test_frule(Tensor, ones(), Symbol[]) @@ -194,7 +194,7 @@ @testset "Ansatz" begin tn = Quantum(TensorNetwork([Tensor(ones(2), [:i])]), Dict{Site,Symbol}(site"1" => :i)) graph = Graph(1) - mapping = BijectiveIdDict{Site,Int}(Pair{Site,Int}[site"1" => 1]) + mapping = BijectiveDict{Lane,Int}(Pair{Lane,Int}[lane"1" => 1]) lattice = Lattice(mapping, graph) test_frule(Ansatz, tn, lattice) test_rrule(Ansatz, tn, lattice)