Skip to content

Commit fff4db5

Browse files
atombeardavid-pl
authored andcommitted
before embedding check that the destination basis matches the operator basis (#246)
* before embedding check that the destination basis matches the operator basis * Change embed to handle composite operators * perform embedding of an operator in a joint hilbert space * functional embedding with new syntax * more tests for composite bases * code review and a few more tests * Change Int64 to Int in type checks for x86
1 parent 40d0231 commit fff4db5

File tree

5 files changed

+172
-7
lines changed

5 files changed

+172
-7
lines changed

src/operators.jl

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export AbstractOperator, DataOperator, length, basis, dagger, ishermitian, tenso
66

77
import Base: ==, +, -, *, /, ^, length, one, exp, conj, conj!, transpose
88
import LinearAlgebra: tr, ishermitian
9+
import SparseArrays: sparse
910
import ..bases: basis, tensor, ptrace, permutesystems,
1011
samebases, check_samebases, multiplicable
1112
import ..states: dagger, normalize, normalize!
@@ -94,16 +95,98 @@ tensor(operators::AbstractOperator...) = reduce(tensor, operators)
9495
Tensor product of operators where missing indices are filled up with identity operators.
9596
"""
9697
function embed(basis_l::CompositeBasis, basis_r::CompositeBasis,
97-
indices::Vector{Int}, operators::Vector{T}) where T<:AbstractOperator
98+
indices::Vector, operators::Vector{T}) where T<:AbstractOperator
99+
100+
@assert sortedindices.check_embed_indices(indices)
101+
98102
N = length(basis_l.bases)
99103
@assert length(basis_r.bases) == N
100104
@assert length(indices) == length(operators)
105+
106+
# Embed all single-subspace operators.
107+
idxop_sb = [x for x in zip(indices, operators) if typeof(x[1]) <: Int]
108+
indices_sb = [x[1] for x in idxop_sb]
109+
ops_sb = [x[2] for x in idxop_sb]
110+
111+
for (idxsb, opsb) in zip(indices_sb, ops_sb)
112+
(opsb.basis_l == basis_l.bases[idxsb]) || throw(bases.IncompatibleBases())
113+
(opsb.basis_r == basis_r.bases[idxsb]) || throw(bases.IncompatibleBases())
114+
end
115+
116+
embed_op = tensor([i indices_sb ? ops_sb[indexin(i, indices_sb)[1]] : identityoperator(T, basis_l.bases[i], basis_r.bases[i]) for i=1:N]...)
117+
118+
# Embed all joint-subspace operators.
119+
idxop_comp = [x for x in zip(indices, operators) if typeof(x[1]) <: Array]
120+
for (idxs, op) in idxop_comp
121+
embed_op *= embed(basis_l, basis_r, idxs, op)
122+
end
123+
124+
return embed_op
125+
end
126+
127+
128+
"""
129+
embed(basis1[, basis2], indices::Vector, operators::Vector)
130+
131+
Embed operator acting on a joint Hilbert space where missing indices are filled up with identity operators.
132+
"""
133+
function embed(basis_l::CompositeBasis, basis_r::CompositeBasis,
134+
indices::Vector{Int}, op::T) where T<:AbstractOperator
135+
N = length(basis_l.bases)
136+
@assert length(basis_r.bases) == N
137+
138+
reduce(tensor, basis_l.bases[indices]) == op.basis_l || throw(bases.IncompatibleBases())
139+
reduce(tensor, basis_r.bases[indices]) == op.basis_r || throw(bases.IncompatibleBases())
140+
141+
index_order = [idx for idx in 1:length(basis_l.bases) if idx indices]
142+
all_operators = AbstractOperator[identityoperator(T, basis_l.bases[i], basis_r.bases[i]) for i in index_order]
143+
144+
for idx in indices
145+
pushfirst!(index_order, idx)
146+
end
147+
push!(all_operators, op)
148+
101149
sortedindices.check_indices(N, indices)
102-
tensor([i indices ? operators[indexin(i, indices)[1]] : identityoperator(T, basis_l.bases[i], basis_r.bases[i]) for i=1:N]...)
150+
151+
# Create the operator.
152+
permuted_op = tensor(all_operators...)
153+
154+
# Create a copy to fill with correctly-ordered objects (basis and data).
155+
unpermuted_op = copy(permuted_op)
156+
157+
# Create the correctly ordered basis.
158+
unpermuted_op.basis_l = basis_l
159+
unpermuted_op.basis_r = basis_r
160+
161+
# Reorient the matrix to act in the correctly ordered basis.
162+
# Get the dimensions necessary for index permuting.
163+
dims_l = [b.shape[1] for b in basis_l.bases]
164+
dims_r = [b.shape[1] for b in basis_r.bases]
165+
166+
# Get the order of indices to use in the first reshape. Julia indices go in
167+
# reverse order.
168+
expand_order = index_order[end:-1:1]
169+
# Get the dimensions associated with those indices.
170+
expand_dims_l = dims_l[expand_order]
171+
expand_dims_r = dims_r[expand_order]
172+
173+
# Prepare the permutation to the correctly ordered basis.
174+
perm_order_l = [indexin(idx, expand_order)[1] for idx in 1:length(dims_l)]
175+
perm_order_r = [indexin(idx, expand_order)[1] for idx in 1:length(dims_r)]
176+
177+
# Perform the index expansion, the permutation, and the index collapse.
178+
M = (reshape(permuted_op.data, tuple([expand_dims_l; expand_dims_r]...)) |>
179+
x -> permutedims(x, [perm_order_l; perm_order_r .+ length(dims_l)]) |>
180+
x -> sparse(reshape(x, (prod(dims_l), prod(dims_r)))))
181+
182+
unpermuted_op.data = M
183+
184+
return unpermuted_op
103185
end
104186
embed(basis_l::CompositeBasis, basis_r::CompositeBasis, index::Int, op::AbstractOperator) = embed(basis_l, basis_r, Int[index], [op])
105187
embed(basis::CompositeBasis, index::Int, op::AbstractOperator) = embed(basis, basis, Int[index], [op])
106-
embed(basis::CompositeBasis, indices::Vector{Int}, operators::Vector{T}) where {T<:AbstractOperator} = embed(basis, basis, indices, operators)
188+
embed(basis::CompositeBasis, indices::Vector, operators::Vector{T}) where {T<:AbstractOperator} = embed(basis, basis, indices, operators)
189+
embed(basis::CompositeBasis, indices::Vector{Int}, op::AbstractOperator) = embed(basis, basis, indices, op)
107190

108191
"""
109192
embed(basis1[, basis2], operators::Dict)

src/operators_sparse.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ function operators.permutesystems(rho::SparseOperator{B1,B2}, perm::Vector{Int})
130130
end
131131

132132
operators.identityoperator(::Type{T}, b1::Basis, b2::Basis) where {T<:SparseOperator} = SparseOperator(b1, b2, sparse(ComplexF64(1)*I, length(b1), length(b2)))
133+
operators.identityoperator(::Type{T}, b1::Basis, b2::Basis) where {T<:DataOperator} = SparseOperator(b1, b2, sparse(ComplexF64(1)*I, length(b1), length(b2)))
133134
operators.identityoperator(b1::Basis, b2::Basis) = identityoperator(SparseOperator, b1, b2)
134135
operators.identityoperator(b::Basis) = identityoperator(b, b)
135136

src/sortedindices.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,31 @@ function check_sortedindices(imax::Int, indices::Vector{Int})
176176
end
177177
end
178178

179+
"""
180+
check_embed_indices(indices::Array)
181+
182+
Determine whether a collection of indices, written as a list of (integers or lists of integers) is unique.
183+
This assures that the embedded operators are in non-overlapping subspaces.
184+
"""
185+
function check_embed_indices(indices::Array)
186+
# Check whether `indices` is empty.
187+
(length(indices) == 0) ? (return true) : nothing
188+
189+
err_str = "Variable `indices` comes in an unexpected form. Expecting `Array{Union{Int, Array{Int, 1}}, 1}`"
190+
@assert mapreduce(x -> typeof(x)<:Array || typeof(x)<:Int, &, indices) err_str
191+
192+
isunique = true
193+
# Check that no sub-list contains duplicates.
194+
for i in filter(x -> typeof(x) <: Array, indices)
195+
isunique &= (length(Set(i)) == length(i))
196+
end
197+
# Check that there are no duplicates across `indices`
198+
for (idx, i) in enumerate(indices[1:end-1])
199+
for j in indices[idx+1:end]
200+
isunique &= (length(Base.intersect(i, j)) == 0)
201+
end
202+
end
203+
return isunique
204+
end
205+
179206
end # module

test/test_operators.jl

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ end
1313

1414
Random.seed!(0)
1515

16-
b1 = GenericBasis(5)
17-
b2 = GenericBasis(3)
16+
b1 = GenericBasis(3)
17+
b2 = GenericBasis(2)
1818
b = b1 b2
1919
op1 = randoperator(b1)
20+
op2 = randoperator(b2)
2021
op = randoperator(b, b)
2122
op_test = test_operators(b, b, op.data)
2223
op_test2 = test_operators(b1, b, randoperator(b1, b).data)
@@ -25,7 +26,7 @@ op_test3 = test_operators(b1 ⊗ b2, b2 ⊗ b1, randoperator(b, b).data)
2526
ρ = randoperator(b)
2627

2728
@test basis(op1) == b1
28-
@test length(op1) == length(op1.data) == 25
29+
@test length(op1) == length(op1.data) == length(b1)^2
2930

3031
@test_throws ArgumentError op_test*op_test
3132
@test_throws ArgumentError -op_test
@@ -59,8 +60,56 @@ op_test3 = test_operators(b1 ⊗ b2, b2 ⊗ b1, randoperator(b, b).data)
5960
@test_throws ArgumentError tensor(op_test, op_test)
6061
@test_throws ArgumentError permutesystems(op_test, [1, 2])
6162

62-
@test embed(b, b, 1, op) == embed(b, 1, op)
63+
@test embed(b, b, [1,2], op) == embed(b, [1,2], op)
6364
@test embed(b, Dict{Vector{Int}, SparseOperator}()) == identityoperator(b)
65+
@test_throws bases.IncompatibleBases embed(b1b2, [2], [op1])
66+
67+
b_comp = bb
68+
@test embed(b_comp, [1,[3,4]], [op1,op]) == dense(op1 one(b2) op)
69+
@test embed(b_comp, [[1,2],4], [op,op2]) == dense(op one(b1) op2)
70+
@test_throws bases.IncompatibleBases embed(b_comp, [[1,2],3], [op,op2])
71+
@test_throws bases.IncompatibleBases embed(b_comp, [[1,3],4], [op,op2])
72+
73+
function basis_vec(n, N)
74+
x = zeros(Complex{Float64}, N)
75+
x[n+1] = 1
76+
return x
77+
end
78+
function basis_maker(dims...)
79+
function bm(ns...)
80+
bases = [basis_vec(n, dim) for (n, dim) in zip(ns, dims)][end:-1:1]
81+
return reduce(kron, bases)
82+
end
83+
end
84+
85+
embed_op = embed(b_comp, [1,4], op)
86+
bv = basis_maker(3,2,3,2)
87+
all_idxs = [(idx, jdx) for (idx, jdx) in [Iterators.product(0:1, 0:2)...]]
88+
89+
m11 = reshape([Bra(b_comp, bv(0,idx,jdx,0)) * embed_op * Ket(b_comp, bv(0,kdx,ldx,0))
90+
for ((idx, jdx), (kdx, ldx)) in Iterators.product(all_idxs, all_idxs)], (6,6))
91+
@test isapprox(m11 / op.data[1, 1], diagm(0=>ones(Complex{Float64}, 6)))
92+
93+
m21 = reshape([Bra(b_comp, bv(1,idx,jdx,0)) * embed_op * Ket(b_comp, bv(0,kdx,ldx,0))
94+
for ((idx, jdx), (kdx, ldx)) in Iterators.product(all_idxs, all_idxs)], (6,6))
95+
@test isapprox(m21 / op.data[2,1], diagm(0=>ones(Complex{Float64}, 6)))
96+
97+
m12 = reshape([Bra(b_comp, bv(0,idx,jdx,0)) * embed_op * Ket(b_comp, bv(1,kdx,ldx,0))
98+
for ((idx, jdx), (kdx, ldx)) in Iterators.product(all_idxs, all_idxs)], (6,6))
99+
@test isapprox(m12 / op.data[1,2], diagm(0=>ones(Complex{Float64}, 6)))
100+
101+
102+
b_comp = b_compb_comp
103+
OP_test1 = dense(tensor([op1,one(b2),op,one(b1),one(b2),op1,one(b2)]...))
104+
OP_test2 = embed(b_comp, [1,[3,4],7], [op1,op,op1])
105+
@test isapprox(OP_test1.data, OP_test2.data)
106+
107+
b8 = b2b2b2
108+
cnot = [1 0 0 0; 0 1 0 0; 0 0 0 1; 0 0 1 0]
109+
op_cnot = DenseOperator(b2b2, cnot)
110+
OP_cnot = embed(b8, [1,3], op_cnot)
111+
@test ptrace(OP_cnot, [2])/2. == op_cnot
112+
@test_throws AssertionError embed(b2b2, [1,1], op_cnot)
64113

65114
@test_throws ErrorException QuantumOptics.operators.gemm!()
66115
@test_throws ErrorException QuantumOptics.operators.gemv!()

test/test_sortedindices.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,9 @@ s.reducedindices!(x, [2, 3, 5, 6])
5252
@test s.check_sortedindices(5, Int[]) == nothing
5353
@test s.check_sortedindices(5, [1, 3]) == nothing
5454

55+
@test s.check_embed_indices([1,[3,5],10,2,[70,11]]) == true
56+
@test s.check_embed_indices([1,3,1]) == false
57+
@test s.check_embed_indices([1,[10,11],7,[3,1]]) == false
58+
@test s.check_embed_indices([[10,3],5,6,[3,7]]) == false
59+
@test s.check_embed_indices([]) == true
5560
end # testset

0 commit comments

Comments
 (0)