Skip to content

Commit 65c0d0a

Browse files
authored
Request For Comment: Arithemtic between Operators and LazyOperators (#86)
Extend some of the current operation with LazyOperators to allow for smoother interface with e.g. LazyTensor. As an example +(a::LazyTensor,LazyTensor) now returns LazySum.
1 parent 9ae1a5f commit 65c0d0a

File tree

7 files changed

+150
-26
lines changed

7 files changed

+150
-26
lines changed

src/operators_lazyproduct.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ complex factor is stored in the `factor` field which allows for fast
1111
multiplication with numbers.
1212
"""
1313

14-
mutable struct LazyProduct{BL,BR,F,T,KTL,BTR} <: AbstractOperator{BL,BR}
14+
mutable struct LazyProduct{BL,BR,F,T,KTL,BTR} <: LazyOperator{BL,BR}
1515
basis_l::BL
1616
basis_r::BR
1717
factor::F
@@ -74,6 +74,16 @@ permutesystems(op::LazyProduct, perm::Vector{Int}) = LazyProduct(([permutesystem
7474
identityoperator(::Type{LazyProduct}, ::Type{S}, b1::Basis, b2::Basis) where S<:Number = LazyProduct(identityoperator(S, b1, b2))
7575

7676

77+
78+
function tensor(a::Operator{B1,B2},b::LazyProduct{B3, B4, F, T, KTL, BTR}) where {B1,B2,B3,B4, F, T, KTL, BTR}
79+
ops = ([(i == 1 ? a : identityoperator(a.basis_r) ) op for (i,op) in enumerate(b.operators)]...,)
80+
LazyProduct(ops,b.factor)
81+
end
82+
function tensor(a::LazyProduct{B1, B2, F, T, KTL, BTR},b::Operator{B3,B4}) where {B1,B2,B3,B4, F, T, KTL, BTR}
83+
ops = ([op (i == length(a.operators) ? b : identityoperator(a.basis_l) ) for (i,op) in enumerate(a.operators)]...,)
84+
LazyProduct(ops,a.factor)
85+
end
86+
7787
function mul!(result::Ket{B1},a::LazyProduct{B1,B2},b::Ket{B2},alpha,beta) where {B1,B2}
7888
if length(a.operators)==1
7989
mul!(result,a.operators[1],b,a.factor*alpha,beta)

src/operators_lazysum.jl

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ function _check_bases(basis_l, basis_r, operators)
99
end
1010
end
1111

12+
"""
13+
Abstract class for all Lazy type operators ([`LazySum`](@ref), [`LazyProduct`](@ref), and [`LazyTensor`](@ref))
14+
"""
15+
abstract type LazyOperator{BL,BR} <: AbstractOperator{BL,BR} end
16+
1217
"""
1318
LazySum([Tf,] [factors,] operators)
1419
LazySum([Tf,] basis_l, basis_r, [factors,] [operators])
@@ -28,7 +33,7 @@ of operator-state operations, such as simulating time evolution. A `Vector` can
2833
reduce compile-time overhead when doing arithmetic on `LazySum`s, such as
2934
summing many `LazySum`s together.
3035
"""
31-
mutable struct LazySum{BL,BR,F,T} <: AbstractOperator{BL,BR}
36+
mutable struct LazySum{BL,BR,F,T} <: LazyOperator{BL,BR}
3237
basis_l::BL
3338
basis_r::BR
3439
factors::F
@@ -57,6 +62,7 @@ end
5762
LazySum(::Type{Tf}, operators::AbstractOperator...) where Tf = LazySum(ones(Tf, length(operators)), (operators...,))
5863
LazySum(operators::AbstractOperator...) = LazySum(mapreduce(eltype, promote_type, operators), operators...)
5964
LazySum() = throw(ArgumentError("LazySum needs a basis, or at least one operator!"))
65+
LazySum(x::LazySum) = x
6066

6167
Base.copy(x::LazySum) = @samebases LazySum(x.basis_l, x.basis_r, copy(x.factors), copy.(x.operators))
6268
Base.eltype(x::LazySum) = mapreduce(eltype, promote_type, x.operators; init=eltype(x.factors))
@@ -81,8 +87,9 @@ function +(a::LazySum{B1,B2}, b::LazySum{B1,B2}) where {B1,B2}
8187
@samebases LazySum(a.basis_l, a.basis_r, factors, ops)
8288
end
8389
+(a::LazySum{B1,B2}, b::LazySum{B3,B4}) where {B1,B2,B3,B4} = throw(IncompatibleBases())
84-
+(a::LazySum, b::AbstractOperator) = a + LazySum(b)
85-
+(a::AbstractOperator, b::LazySum) = LazySum(a) + b
90+
+(a::LazyOperator, b::AbstractOperator) = LazySum(a) + LazySum(b)
91+
+(a::AbstractOperator, b::LazyOperator) = LazySum(a) + LazySum(b)
92+
+(a::O1, b::O2) where {O1<:LazyOperator,O2<:LazyOperator} = LazySum(a) + LazySum(b)
8693

8794
-(a::LazySum) = @samebases LazySum(a.basis_l, a.basis_r, -a.factors, a.operators)
8895
function -(a::LazySum{B1,B2}, b::LazySum{B1,B2}) where {B1,B2}
@@ -92,8 +99,10 @@ function -(a::LazySum{B1,B2}, b::LazySum{B1,B2}) where {B1,B2}
9299
@samebases LazySum(a.basis_l, a.basis_r, factors, ops)
93100
end
94101
-(a::LazySum{B1,B2}, b::LazySum{B3,B4}) where {B1,B2,B3,B4} = throw(IncompatibleBases())
95-
-(a::LazySum, b::AbstractOperator) = a - LazySum(b)
96-
-(a::AbstractOperator, b::LazySum) = LazySum(a) - b
102+
-(a::LazyOperator, b::AbstractOperator) = LazySum(a) - LazySum(b)
103+
-(a::AbstractOperator, b::LazyOperator) = LazySum(a) - LazySum(b)
104+
-(a::O1, b::O2) where {O1<:LazyOperator,O2<:LazyOperator} = LazySum(a) - LazySum(b)
105+
97106

98107
function *(a::LazySum, b::Number)
99108
factors = b*a.factors
@@ -106,6 +115,19 @@ function /(a::LazySum, b::Number)
106115
@samebases LazySum(a.basis_l, a.basis_r, factors, a.operators)
107116
end
108117

118+
function tensor(a::Operator,b::LazySum)
119+
btotal_l = a.basis_l b.basis_l
120+
btotal_r = a.basis_r b.basis_r
121+
ops = ([a op for op in b.operators]...,)
122+
LazySum(btotal_l,btotal_r,b.factors,ops)
123+
end
124+
function tensor(a::LazySum,b::Operator)
125+
btotal_l = a.basis_l b.basis_l
126+
btotal_r = a.basis_r b.basis_r
127+
ops = ([op b for op in a.operators]...,)
128+
LazySum(btotal_l,btotal_r,a.factors,ops)
129+
end
130+
109131
function dagger(op::LazySum)
110132
ops = dagger.(op.operators)
111133
@samebases LazySum(op.basis_r, op.basis_l, conj.(op.factors), ops)

src/operators_lazytensor.jl

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ must be sorted.
1111
Additionally, a factor is stored in the `factor` field which allows for fast
1212
multiplication with numbers.
1313
"""
14-
mutable struct LazyTensor{BL,BR,F,I,T} <: AbstractOperator{BL,BR}
14+
mutable struct LazyTensor{BL,BR,F,I,T} <: LazyOperator{BL,BR}
1515
basis_l::BL
1616
basis_r::BR
1717
factor::F
@@ -96,21 +96,37 @@ isequal(x::LazyTensor, y::LazyTensor) = samebases(x,y) && isequal(x.indices, y.i
9696
# Arithmetic operations
9797
-(a::LazyTensor) = LazyTensor(a, -a.factor)
9898

99-
function +(a::LazyTensor{B1,B2}, b::LazyTensor{B1,B2}) where {B1,B2}
100-
if length(a.indices) == 1 && a.indices == b.indices
99+
const single_dataoperator{B1,B2} = LazyTensor{B1,B2,F,I,Tuple{T}} where {B1,B2,F,I,T<:DataOperator}
100+
function +(a::T1,b::T2) where {T1 <: single_dataoperator{B1,B2},T2 <: single_dataoperator{B1,B2}} where {B1,B2}
101+
if a.indices == b.indices
101102
op = a.operators[1] * a.factor + b.operators[1] * b.factor
102103
return LazyTensor(a.basis_l, a.basis_r, a.indices, (op,))
103104
end
104-
throw(ArgumentError("Addition of LazyTensor operators is only defined in case both operators act nontrivially on the same, single tensor factor."))
105+
LazySum(a) + LazySum(b)
105106
end
106-
107-
function -(a::LazyTensor{B1,B2}, b::LazyTensor{B1,B2}) where {B1,B2}
108-
if length(a.indices) == 1 && a.indices == b.indices
107+
function -(a::T1,b::T2) where {T1 <: single_dataoperator{B1,B2},T2 <: single_dataoperator{B1,B2}} where {B1,B2}
108+
if a.indices == b.indices
109109
op = a.operators[1] * a.factor - b.operators[1] * b.factor
110110
return LazyTensor(a.basis_l, a.basis_r, a.indices, (op,))
111111
end
112-
throw(ArgumentError("Subtraction of LazyTensor operators is only defined in case both operators act nontrivially on the same, single tensor factor."))
112+
LazySum(a) - LazySum(b)
113+
end
114+
115+
function tensor(a::LazyTensor{B1,B2},b::AbstractOperator{B3,B4}) where {B1,B2,B3,B4}
116+
if B3 <: CompositeBasis || B4 <: CompositeBasis
117+
throw(ArgumentError("tensor(a::LazyTensor{B1,B2},b::AbstractOperator{B3,B4}) is not implemented for B3 or B4 being CompositeBasis unless b is identityoperator "))
118+
else
119+
a LazyTensor(b.basis_l,b.basis_r,[1],(b,),1)
120+
end
113121
end
122+
function tensor(a::AbstractOperator{B1,B2},b::LazyTensor{B3,B4}) where {B1,B2,B3,B4}
123+
if B1 <: CompositeBasis || B2 <: CompositeBasis
124+
throw(ArgumentError("tensor(a::AbstractOperator{B1,B2},b::LazyTensor{B3,B4}) is not implemented for B1 or B2 being CompositeBasis unless b is identityoperator "))
125+
else
126+
LazyTensor(a.basis_l,a.basis_r,[1],(a,),1) b
127+
end
128+
end
129+
114130

115131
function *(a::LazyTensor{B1,B2}, b::LazyTensor{B2,B3}) where {B1,B2,B3}
116132
indices = sort(union(a.indices, b.indices))

test/test_abstractdata.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,6 @@ xbra1 = Bra(b_l, rand(ComplexF64, length(b_l)))
413413
xbra2 = Bra(b_l, rand(ComplexF64, length(b_l)))
414414

415415
# Addition
416-
@test_throws ArgumentError op1 + op2
417-
@test_throws ArgumentError op1 - op2
418416
@test D(-op1_, -op1, 1e-12)
419417

420418
# Test multiplication
@@ -448,7 +446,7 @@ xbra1 = Bra(b_l, rand(ComplexF64, length(b_l)))
448446
xbra2 = Bra(b_l, rand(ComplexF64, length(b_l)))
449447

450448
# Addition
451-
@test_throws ArgumentError op1 + op2
449+
@test D(2.1*op1 + 0.3*op2, 2.1*op1_+0.3*op2_)
452450
@test D(-op1_, -op1)
453451

454452
# Test multiplication

test/test_operators_lazyproduct.jl

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,41 @@ op1b = randoperator(b_r, b_l)
5050
op2a = randoperator(b_l, b_r)
5151
op2b = randoperator(b_r, b_l)
5252
op3a = randoperator(b_l, b_l)
53+
op3b = randoperator(b_r, b_r)
54+
5355
op1 = LazyProduct([op1a, sparse(op1b)])*0.1
5456
op1_ = 0.1*(op1a*op1b)
5557
op2 = LazyProduct([sparse(op2a), op2b], 0.3)
5658
op2_ = 0.3*(op2a*op2b)
5759
op3 = LazyProduct(op3a)
5860
op3_ = op3a
61+
op4 = LazyProduct(op3b)
62+
op4_ = op3b
5963

6064
x1 = Ket(b_l, rand(ComplexF64, length(b_l)))
6165
x2 = Ket(b_l, rand(ComplexF64, length(b_l)))
6266
xbra1 = Bra(b_l, rand(ComplexF64, length(b_l)))
6367
xbra2 = Bra(b_l, rand(ComplexF64, length(b_l)))
6468

65-
# Addition
66-
@test_throws ArgumentError op1 + op2
69+
# Test Addition
70+
@test_throws QuantumOpticsBase.IncompatibleBases op1 + dagger(op4)
6771
@test 1e-14 > D(-op1_, -op1)
72+
@test 1e-14 > D(op1+op2, op1_+op2_)
73+
@test 1e-14 > D(op1+op2_, op1_+op2_)
74+
@test 1e-14 > D(op1_+op2, op1_+op2_)
75+
#Check for unallowed addition:
76+
@test_throws QuantumOpticsBase.IncompatibleBases LazyProduct([op1a, sparse(op1a)])+LazyProduct([sparse(op2b), op2b], 0.3)
77+
78+
# Test Subtraction
79+
@test_throws QuantumOpticsBase.IncompatibleBases op1 - dagger(op4)
80+
@test 1e-14 > D(op1 - op2, op1_ - op2_)
81+
@test 1e-14 > D(op1 - op2_, op1_ - op2_)
82+
@test 1e-14 > D(op1_ - op2, op1_ - op2_)
83+
@test 1e-14 > D(op1 + (-op2), op1_ - op2_)
84+
@test 1e-14 > D(op1 + (-1*op2), op1_ - op2_)
85+
#Check for unallowed subtraction:
86+
@test_throws QuantumOpticsBase.IncompatibleBases LazyProduct([op1a, sparse(op1a)])-LazyProduct([sparse(op2b), op2b], 0.3)
87+
6888

6989
# Test multiplication
7090
@test_throws DimensionMismatch op1a*op1a
@@ -78,6 +98,14 @@ xbra2 = Bra(b_l, rand(ComplexF64, length(b_l)))
7898
# Test division
7999
@test 1e-14 > D(op1/7, op1_/7)
80100

101+
#Test Tensor product
102+
op = op1a op1
103+
op_ = op1a op1_
104+
@test 1e-11 > D(op,op_)
105+
op = op1 op2a
106+
op_ = op1_ op2a
107+
@test 1e-11 > D(op,op_)
108+
81109
# Test identityoperator
82110
Idense = identityoperator(DenseOpType, b_l)
83111
id = identityoperator(LazyProduct, b_l)

test/test_operators_lazysum.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,29 @@ ops_tuple = (symmetric_op,hermitian_op,adjoint_op)
134134
# Test division
135135
@test 1e-14 > D(op1/7, op1_/7)
136136

137+
#Test Tensor
138+
op1_tensor = op1a op1
139+
op2_tensor = op1 op1a
140+
op1_tensor_ = op1a op1_
141+
op2_tensor_ = op1_ op1a
142+
@test 1e-14 > D(op1_tensor,op1_tensor_)
143+
@test 1e-14 > D(op1_tensor_,op1_tensor)
144+
145+
#Test addition of with and of LazyTensor and LazyProduct
146+
subop1 = randoperator(b1a, b1b)
147+
subop2 = randoperator(b2a, b2b)
148+
subop3 = randoperator(b3a, b3b)
149+
I1 = dense(identityoperator(b1a, b1b))
150+
I2 = dense(identityoperator(b2a, b2b))
151+
I3 = dense(identityoperator(b3a, b3b))
152+
op1_LT = LazyTensor(b_l, b_r, [1, 3], (subop1, sparse(subop3)), 0.1)
153+
op1_LT_ = 0.1*subop1 I2 subop3
154+
op1_LP = LazyProduct([op1a])*0.1
155+
op1_LP_ = op1a*0.1
156+
@test 1e-14 > D(op1_LT+op1_LP,op1_LT_+op1_LP_)
157+
@test 1e-14 > D(op1_LT+op1,op1_LT_+op1_)
158+
@test 1e-14 > D(op1_LP+op1,op1_LP_+op1_)
159+
137160
# Test tuples vs. vectors
138161
@test (op1+op1).operators isa Tuple
139162
@test (op1+op2).operators isa Tuple

test/test_operators_lazytensor.jl

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ mutable struct test_lazytensor{BL<:Basis,BR<:Basis} <: AbstractOperator{BL,BR}
1010
end
1111
Base.eltype(::test_lazytensor) = ComplexF64
1212

13-
@testset "operators-lazytensor" begin
1413

1514
Random.seed!(0)
1615

@@ -102,21 +101,51 @@ op2 = LazyTensor(b_l, b_r, [2, 3], (sparse(subop2), subop3), 0.7)
102101
op2_ = 0.7*I1 subop2 subop3
103102
op3 = 0.3*LazyTensor(b_l, b_r, 3, subop3)
104103
op3_ = 0.3*I1 I2 subop3
104+
op4 = 0.4*LazyTensor(b_l, b_r, 2, subop2)
105+
op4_ = 0.4*I1 subop2 I3
105106

106107
x1 = Ket(b_r, rand(ComplexF64, length(b_r)))
107108
x2 = Ket(b_r, rand(ComplexF64, length(b_r)))
108109
xbra1 = Bra(b_l, rand(ComplexF64, length(b_l)))
109110
xbra2 = Bra(b_l, rand(ComplexF64, length(b_l)))
110111

111-
# Allowed addition
112+
# Addition one operator at same index
112113
fac = randn()
113114
@test dense(op3 + fac * op3) dense(op3) * (1 + fac)
114115
@test dense(op3 - fac * op3) dense(op3) * (1 - fac)
116+
# Addition one operator at different index
117+
@test dense(op3 + fac * op4) dense(op3_ + fac*op4_)
118+
@test dense(op3 - fac * op4) dense(op3_ - fac*op4_)
115119

116-
# Forbidden addition
117-
@test_throws ArgumentError op1 + op2
118-
@test_throws ArgumentError op1 - op2
120+
#Test addition
121+
@test_throws QuantumOpticsBase.IncompatibleBases op1 + dagger(op2)
119122
@test 1e-14 > D(-op1_, -op1)
123+
@test 1e-14 > D(op1+op2, op1_+op2_)
124+
@test 1e-14 > D(op1+op2_, op1_+op2_)
125+
@test 1e-14 > D(op1_+op2, op1_+op2_)
126+
127+
#Test substraction
128+
@test_throws QuantumOpticsBase.IncompatibleBases op1 - dagger(op2)
129+
@test 1e-14 > D(op1 - op2, op1_ - op2_)
130+
@test 1e-14 > D(op1 - op2_, op1_ - op2_)
131+
@test 1e-14 > D(op1_ - op2, op1_ - op2_)
132+
@test 1e-14 > D(op1 + (-op2), op1_ - op2_)
133+
@test 1e-14 > D(op1 + (-1*op2), op1_ - op2_)
134+
135+
136+
#Test tensor
137+
op1_tensor = subop1 op1
138+
op1_tensor_ = subop1 op1_
139+
op2_tensor = op1 subop1
140+
op2_tensor_ = op1_ subop1
141+
@test 1e-14 > D(op1_tensor,op1_tensor_)
142+
@test 1e-14 > D(op1_tensor_,op1_tensor)
143+
144+
#Cannot tensor CompositeBasis with LazyTensor:
145+
@test_throws ArgumentError op1 op1_
146+
@test_throws ArgumentError op2_ op2
147+
148+
120149

121150
# Test multiplication
122151
@test_throws DimensionMismatch op1*op2
@@ -412,5 +441,3 @@ Lop1 = LazyTensor(b1^2, b2^2, 2, sparse(randoperator(b1, b2)))
412441
@test_throws DimensionMismatch sparse(Lop1)*Lop1
413442
@test_throws DimensionMismatch Lop1*dense(Lop1)
414443
@test_throws DimensionMismatch Lop1*sparse(Lop1)
415-
416-
end # testset

0 commit comments

Comments
 (0)