Skip to content

Commit a90301e

Browse files
committed
Superoperators
1 parent c3e7181 commit a90301e

File tree

1 file changed

+31
-343
lines changed

1 file changed

+31
-343
lines changed

src/superoperators.jl

Lines changed: 31 additions & 343 deletions
Original file line numberDiff line numberDiff line change
@@ -1,349 +1,21 @@
1-
import QuantumInterface: AbstractSuperOperator
2-
import FastExpm: fastExpm
1+
import LinearAlgebra: vec
32

4-
"""
5-
SuperOperator <: AbstractSuperOperator
3+
const SuperOperatorType = Operator{<:KetBraBasis,<:KetBraBasis}
4+
const ChoiStateType = Operator{CompositeBasis{<:Integer,<:Choi},CompositeBasis{<:Integer,<:Choi}}
65

7-
SuperOperator stored as representation, e.g. as a Matrix.
8-
"""
9-
mutable struct SuperOperator{B1,B2,T} <: AbstractSuperOperator
10-
basis_l::B1
11-
basis_r::B2
12-
data::T
13-
function SuperOperator{BL,BR,T}(basis_l::BL, basis_r::BR, data::T) where {BL,BR,T}
14-
if (length(basis_l) != 2 || length(basis_r) != 2 ||
15-
length(basis_l[1])*length(basis_l[2]) != size(data, 1) ||
16-
length(basis_r[1])*length(basis_r[2]) != size(data, 2))
17-
throw(DimensionMismatch("Tried to assign data of size $(size(data)) to Hilbert spaces of sizes $(length.(basis_l)), $(length.(basis_r))"))
18-
end
19-
new(basis_l, basis_r, data)
20-
end
21-
end
22-
SuperOperator{BL,BR}(b1::BL,b2::BR,data::T) where {BL,BR,T} = SuperOperator{BL,BR,T}(b1,b2,data)
23-
SuperOperator(b1::BL,b2::BR,data::T) where {BL,BR,T} = SuperOperator{BL,BR,T}(b1,b2,data)
24-
SuperOperator(b,data) = SuperOperator(b,b,data)
25-
26-
const DenseSuperOpType{BL,BR} = SuperOperator{BL,BR,<:Matrix}
27-
const SparseSuperOpType{BL,BR} = SuperOperator{BL,BR,<:SparseMatrixCSC}
28-
29-
"""
30-
DenseSuperOperator(b1[, b2, data])
31-
DenseSuperOperator([T=ComplexF64,], b1[, b2])
32-
33-
SuperOperator stored as dense matrix.
34-
"""
35-
DenseSuperOperator(basis_l,basis_r,data) = SuperOperator(basis_l, basis_r, Matrix(data))
36-
function DenseSuperOperator(::Type{T}, basis_l, basis_r) where T
37-
Nl = length(basis_l[1])*length(basis_l[2])
38-
Nr = length(basis_r[1])*length(basis_r[2])
39-
data = zeros(T, Nl, Nr)
40-
DenseSuperOperator(basis_l, basis_r, data)
41-
end
42-
DenseSuperOperator(basis_l, basis_r) = DenseSuperOperator(ComplexF64, basis_l, basis_r)
43-
DenseSuperOperator(::Type{T}, b) where T = DenseSuperOperator(T, b, b)
44-
DenseSuperOperator(b) = DenseSuperOperator(b,b)
45-
46-
47-
"""
48-
SparseSuperOperator(b1[, b2, data])
49-
SparseSuperOperator([T=ComplexF64,], b1[, b2])
50-
51-
SuperOperator stored as sparse matrix.
52-
"""
53-
SparseSuperOperator(basis_l, basis_r, data) = SuperOperator(basis_l, basis_r, sparse(data))
54-
55-
function SparseSuperOperator(::Type{T}, basis_l, basis_r) where T
56-
Nl = length(basis_l[1])*length(basis_l[2])
57-
Nr = length(basis_r[1])*length(basis_r[2])
58-
data = spzeros(T, Nl, Nr)
59-
SparseSuperOperator(basis_l, basis_r, data)
60-
end
61-
SparseSuperOperator(basis_l, basis_r) = SparseSuperOperator(ComplexF64, basis_l, basis_r)
62-
SparseSuperOperator(::Type{T}, b) where T = SparseSuperOperator(T, b, b)
63-
SparseSuperOperator(b) = DenseSuperOperator(b,b)
64-
65-
Base.copy(a::T) where {T<:SuperOperator} = T(a.basis_l, a.basis_r, copy(a.data))
66-
67-
dense(a::SuperOperator) = DenseSuperOperator(a.basis_l, a.basis_r, a.data)
68-
sparse(a::SuperOperator) = SuperOperator(a.basis_l, a.basis_r, sparse(a.data))
69-
70-
==(a::SuperOperator{B1,B2}, b::SuperOperator{B1,B2}) where {B1,B2} = (samebases(a,b) && a.data == b.data)
71-
==(a::SuperOperator, b::SuperOperator) = false
72-
73-
Base.length(a::SuperOperator) = length(a.basis_l[1])*length(a.basis_l[2])*length(a.basis_r[1])*length(a.basis_r[2])
74-
samebases(a::SuperOperator, b::SuperOperator) = samebases(a.basis_l[1], b.basis_l[1]) && samebases(a.basis_l[2], b.basis_l[2]) &&
75-
samebases(a.basis_r[1], b.basis_r[1]) && samebases(a.basis_r[2], b.basis_r[2])
76-
multiplicable(a::SuperOperator, b::SuperOperator) = multiplicable(a.basis_r[1], b.basis_l[1]) && multiplicable(a.basis_r[2], b.basis_l[2])
77-
multiplicable(a::SuperOperator, b::AbstractOperator) = multiplicable(a.basis_r[1], b.basis_l) && multiplicable(a.basis_r[2], b.basis_r)
78-
79-
80-
# Arithmetic operations
81-
function *(a::SuperOperator{B1,B2}, b::Operator{BL,BR}) where {BL,BR,B1,B2<:Tuple{BL,BR}}
82-
data = a.data*reshape(b.data, length(b.data))
83-
return Operator(a.basis_l[1], a.basis_l[2], reshape(data, length(a.basis_l[1]), length(a.basis_l[2])))
84-
end
85-
86-
function *(a::SuperOperator{B1,B2}, b::SuperOperator{B2,B3}) where {B1,B2,B3}
87-
return SuperOperator{B1,B3}(a.basis_l, b.basis_r, a.data*b.data)
88-
end
89-
90-
*(a::SuperOperator, b::Number) = SuperOperator(a.basis_l, a.basis_r, a.data*b)
91-
*(a::Number, b::SuperOperator) = b*a
92-
93-
/(a::SuperOperator, b::Number) = SuperOperator(a.basis_l, a.basis_r, a.data ./ b)
94-
95-
+(a::SuperOperator{B1,B2}, b::SuperOperator{B1,B2}) where {B1,B2} = SuperOperator{B1,B2}(a.basis_l, a.basis_r, a.data+b.data)
96-
+(a::SuperOperator, b::SuperOperator) = throw(IncompatibleBases())
97-
98-
-(a::SuperOperator{B1,B2}, b::SuperOperator{B1,B2}) where {B1,B2} = SuperOperator{B1,B2}(a.basis_l, a.basis_r, a.data-b.data)
99-
-(a::SuperOperator) = SuperOperator(a.basis_l, a.basis_r, -a.data)
100-
-(a::SuperOperator, b::SuperOperator) = throw(IncompatibleBases())
101-
102-
identitysuperoperator(b::Basis) =
103-
SuperOperator((b,b), (b,b), Eye{ComplexF64}(length(b)^2))
104-
105-
identitysuperoperator(op::DenseSuperOpType) =
106-
SuperOperator(op.basis_l, op.basis_r, Matrix(one(eltype(op.data))I, size(op.data)))
107-
108-
identitysuperoperator(op::SparseSuperOpType) =
109-
SuperOperator(op.basis_l, op.basis_r, sparse(one(eltype(op.data))I, size(op.data)))
110-
111-
dagger(x::DenseSuperOpType) = SuperOperator(x.basis_r, x.basis_l, copy(adjoint(x.data)))
112-
dagger(x::SparseSuperOpType) = SuperOperator(x.basis_r, x.basis_l, sparse(adjoint(x.data)))
113-
114-
115-
"""
116-
spre(op)
117-
118-
Create a super-operator equivalent for right side operator multiplication.
119-
120-
For operators ``A``, ``B`` the relation
121-
122-
```math
123-
\\mathrm{spre}(A) B = A B
124-
```
125-
126-
holds. `op` can be a dense or a sparse operator.
127-
"""
128-
function spre(op::AbstractOperator)
129-
if !multiplicable(op, op)
130-
throw(ArgumentError("It's not clear what spre of a non-square operator should be. See issue #113"))
131-
end
132-
SuperOperator((op.basis_l, op.basis_l), (op.basis_r, op.basis_r), tensor(op, identityoperator(op)).data)
133-
end
134-
135-
"""
136-
spost(op)
137-
138-
Create a super-operator equivalent for left side operator multiplication.
139-
140-
For operators ``A``, ``B`` the relation
6+
vec(op::Operator) = Ket(KetBraBasis(basis_l(op), basis_r(op)), reshape(op.data, length(op.data)))
1417

142-
```math
143-
\\mathrm{spost}(A) B = B A
144-
```
145-
146-
holds. `op` can be a dense or a sparse operator.
147-
"""
148-
function spost(op::AbstractOperator)
149-
if !multiplicable(op, op)
150-
throw(ArgumentError("It's not clear what spost of a non-square operator should be. See issue #113"))
151-
end
152-
SuperOperator((op.basis_r, op.basis_r), (op.basis_l, op.basis_l), kron(permutedims(op.data), identityoperator(op).data))
8+
function spre(op::Operator)
9+
multiplicable(op, op) || throw(ArgumentError("It's not clear what spre of a non-square operator should be. See issue #113"))
10+
Operator(KetBraBasis(basis_l(op)), KetBraBasis(basis_r(op)), tensor(op, identityoperator(op)).data)
15311
end
15412

155-
"""
156-
sprepost(op)
157-
158-
Create a super-operator equivalent for left and right side operator multiplication.
159-
160-
For operators ``A``, ``B``, ``C`` the relation
161-
162-
```math
163-
\\mathrm{sprepost}(A, B) C = A C B
164-
```
165-
166-
holds. `A` ond `B` can be dense or a sparse operators.
167-
"""
168-
sprepost(A::AbstractOperator, B::AbstractOperator) = SuperOperator((A.basis_l, B.basis_r), (A.basis_r, B.basis_l), kron(permutedims(B.data), A.data))
169-
170-
function _check_input(H::AbstractOperator, J::Vector, Jdagger::Vector, rates)
171-
for j=J
172-
@assert isa(j, AbstractOperator)
173-
end
174-
for j=Jdagger
175-
@assert isa(j, AbstractOperator)
176-
end
177-
@assert length(J)==length(Jdagger)
178-
if isa(rates, Matrix{<:Number})
179-
@assert size(rates, 1) == size(rates, 2) == length(J)
180-
elseif isa(rates, Vector{<:Number})
181-
@assert length(rates) == length(J)
182-
end
13+
function spost(op::Operator)
14+
multiplicable(op, op) || throw(ArgumentError("It's not clear what spost of a non-square operator should be. See issue #113"))
15+
SuperOperator(KetBraBasis(basis_r(op)), (op.basis_l, op.basis_l), kron(permutedims(op.data), identityoperator(op).data))
18316
end
18417

185-
186-
"""
187-
liouvillian(H, J; rates, Jdagger)
188-
189-
Create a super-operator equivalent to the master equation so that ``\\dot ρ = S ρ``.
190-
191-
The super-operator ``S`` is defined by
192-
193-
```math
194-
S ρ = -\\frac{i}{ħ} [H, ρ] + \\sum_i J_i ρ J_i^† - \\frac{1}{2} J_i^† J_i ρ - \\frac{1}{2} ρ J_i^† J_i
195-
```
196-
197-
# Arguments
198-
* `H`: Hamiltonian.
199-
* `J`: Vector containing the jump operators.
200-
* `rates`: Vector or matrix specifying the coefficients for the jump operators.
201-
* `Jdagger`: Vector containing the hermitian conjugates of the jump operators. If they
202-
are not given they are calculated automatically.
203-
"""
204-
function liouvillian(H, J; rates=ones(length(J)), Jdagger=dagger.(J))
205-
_check_input(H, J, Jdagger, rates)
206-
L = spre(-1im*H) + spost(1im*H)
207-
if isa(rates, AbstractMatrix)
208-
for i=1:length(J), j=1:length(J)
209-
jdagger_j = rates[i,j]/2*Jdagger[j]*J[i]
210-
L -= spre(jdagger_j) + spost(jdagger_j)
211-
L += spre(rates[i,j]*J[i]) * spost(Jdagger[j])
212-
end
213-
elseif isa(rates, AbstractVector)
214-
for i=1:length(J)
215-
jdagger_j = rates[i]/2*Jdagger[i]*J[i]
216-
L -= spre(jdagger_j) + spost(jdagger_j)
217-
L += spre(rates[i]*J[i]) * spost(Jdagger[i])
218-
end
219-
end
220-
return L
221-
end
222-
223-
"""
224-
exp(op::DenseSuperOperator)
225-
226-
Superoperator exponential which can, for example, be used to calculate time evolutions.
227-
Uses LinearAlgebra's `Base.exp`.
228-
229-
If you only need the result of the exponential acting on an operator,
230-
consider using much faster implicit methods that do not calculate the entire exponential.
231-
"""
232-
Base.exp(op::DenseSuperOpType) = DenseSuperOperator(op.basis_l, op.basis_r, exp(op.data))
233-
234-
"""
235-
exp(op::SparseSuperOperator; opts...)
236-
237-
Superoperator exponential which can, for example, be used to calculate time evolutions.
238-
Uses [`FastExpm.jl.jl`](https://github.com/fmentink/FastExpm.jl) which will return a sparse
239-
or dense operator depending on which is more efficient.
240-
All optional arguments are passed to `fastExpm` and can be used to specify tolerances.
241-
242-
If you only need the result of the exponential acting on an operator,
243-
consider using much faster implicit methods that do not calculate the entire exponential.
244-
"""
245-
function Base.exp(op::SparseSuperOpType; opts...)
246-
if iszero(op)
247-
return identitysuperoperator(op)
248-
else
249-
return SuperOperator(op.basis_l, op.basis_r, fastExpm(op.data; opts...))
250-
end
251-
end
252-
253-
# Array-like functions
254-
Base.zero(A::SuperOperator) = SuperOperator(A.basis_l, A.basis_r, zero(A.data))
255-
Base.size(A::SuperOperator) = size(A.data)
256-
@inline Base.axes(A::SuperOperator) = axes(A.data)
257-
Base.ndims(A::SuperOperator) = 2
258-
Base.ndims(::Type{<:SuperOperator}) = 2
259-
260-
# Broadcasting
261-
Base.broadcastable(A::SuperOperator) = A
262-
263-
# Custom broadcasting styles
264-
struct SuperOperatorStyle{BL,BR} <: Broadcast.BroadcastStyle end
265-
# struct DenseSuperOperatorStyle{BL,BR} <: SuperOperatorStyle{BL,BR} end
266-
# struct SparseSuperOperatorStyle{BL,BR} <: SuperOperatorStyle{BL,BR} end
267-
268-
# Style precedence rules
269-
Broadcast.BroadcastStyle(::Type{<:SuperOperator{BL,BR}}) where {BL,BR} = SuperOperatorStyle{BL,BR}()
270-
# Broadcast.BroadcastStyle(::Type{<:SparseSuperOperator{BL,BR}}) where {BL,BR} = SparseSuperOperatorStyle{BL,BR}()
271-
# Broadcast.BroadcastStyle(::DenseSuperOperatorStyle{B1,B2}, ::SparseSuperOperatorStyle{B1,B2}) where {B1,B2} = DenseSuperOperatorStyle{B1,B2}()
272-
# Broadcast.BroadcastStyle(::DenseSuperOperatorStyle{B1,B2}, ::DenseSuperOperatorStyle{B3,B4}) where {B1,B2,B3,B4} = throw(IncompatibleBases())
273-
# Broadcast.BroadcastStyle(::SparseSuperOperatorStyle{B1,B2}, ::SparseSuperOperatorStyle{B3,B4}) where {B1,B2,B3,B4} = throw(IncompatibleBases())
274-
# Broadcast.BroadcastStyle(::DenseSuperOperatorStyle{B1,B2}, ::SparseSuperOperatorStyle{B3,B4}) where {B1,B2,B3,B4} = throw(IncompatibleBases())
275-
276-
# Out-of-place broadcasting
277-
@inline function Base.copy(bc::Broadcast.Broadcasted{Style,Axes,F,Args}) where {BL,BR,Style<:SuperOperatorStyle{BL,BR},Axes,F,Args<:Tuple}
278-
bcf = Broadcast.flatten(bc)
279-
bl,br = find_basis(bcf.args)
280-
bc_ = Broadcasted_restrict_f(bcf.f, bcf.args, axes(bcf))
281-
return SuperOperator{BL,BR}(bl, br, copy(bc_))
282-
end
283-
# @inline function Base.copy(bc::Broadcast.Broadcasted{Style,Axes,F,Args}) where {BL,BR,Style<:SparseSuperOperatorStyle{BL,BR},Axes,F,Args<:Tuple}
284-
# bcf = Broadcast.flatten(bc)
285-
# bl,br = find_basis(bcf.args)
286-
# bc_ = Broadcasted_restrict_f(bcf.f, bcf.args, axes(bcf))
287-
# return SuperOperator{BL,BR}(bl, br, copy(bc_))
288-
# end
289-
find_basis(a::SuperOperator, rest) = (a.basis_l, a.basis_r)
290-
291-
const BasicMathFunc = Union{typeof(+),typeof(-),typeof(*),typeof(/)}
292-
function Broadcasted_restrict_f(f::BasicMathFunc, args::Tuple{Vararg{<:SuperOperator}}, axes)
293-
args_ = Tuple(a.data for a=args)
294-
return Broadcast.Broadcasted(f, args_, axes)
295-
end
296-
297-
# In-place broadcasting
298-
@inline function Base.copyto!(dest::SuperOperator{BL,BR}, bc::Broadcast.Broadcasted{Style,Axes,F,Args}) where {BL,BR,Style<:SuperOperatorStyle{BL,BR},Axes,F,Args}
299-
axes(dest) == axes(bc) || Base.Broadcast.throwdm(axes(dest), axes(bc))
300-
# Performance optimization: broadcast!(identity, dest, A) is equivalent to copyto!(dest, A) if indices match
301-
if bc.f === identity && isa(bc.args, Tuple{<:SuperOperator{BL,BR}}) # only a single input argument to broadcast!
302-
A = bc.args[1]
303-
if axes(dest) == axes(A)
304-
return copyto!(dest, A)
305-
end
306-
end
307-
# Get the underlying data fields of operators and broadcast them as arrays
308-
bcf = Broadcast.flatten(bc)
309-
bc_ = Broadcasted_restrict_f(bcf.f, bcf.args, axes(bcf))
310-
copyto!(dest.data, bc_)
311-
return dest
312-
end
313-
@inline Base.copyto!(A::SuperOperator{BL,BR},B::SuperOperator{BL,BR}) where {BL,BR} = (copyto!(A.data,B.data); A)
314-
@inline function Base.copyto!(dest::SuperOperator{B1,B2}, bc::Broadcast.Broadcasted{Style,Axes,F,Args}) where {
315-
B1,B2,B3,
316-
B4,Style<:SuperOperatorStyle{B3,B4},Axes,F,Args
317-
}
318-
throw(IncompatibleBases())
319-
end
320-
321-
"""
322-
ChoiState <: AbstractSuperOperator
323-
324-
Superoperator represented as a choi state.
325-
"""
326-
mutable struct ChoiState{B1,B2,T} <: AbstractSuperOperator
327-
basis_l::B1
328-
basis_r::B2
329-
data::T
330-
function ChoiState{BL,BR,T}(basis_l::BL, basis_r::BR, data::T) where {BL,BR,T}
331-
if (length(basis_l) != 2 || length(basis_r) != 2 ||
332-
length(basis_l[1])*length(basis_l[2]) != size(data, 1) ||
333-
length(basis_r[1])*length(basis_r[2]) != size(data, 2))
334-
throw(DimensionMismatch("Tried to assign data of size $(size(data)) to Hilbert spaces of sizes $(length.(basis_l)), $(length.(basis_r))"))
335-
end
336-
new(basis_l, basis_r, data)
337-
end
338-
end
339-
ChoiState(b1::BL, b2::BR, data::T) where {BL,BR,T} = ChoiState{BL,BR,T}(b1, b2, data)
340-
341-
dense(a::ChoiState) = ChoiState(a.basis_l, a.basis_r, Matrix(a.data))
342-
sparse(a::ChoiState) = ChoiState(a.basis_l, a.basis_r, sparse(a.data))
343-
dagger(a::ChoiState) = ChoiState(dagger(SuperOperator(a)))
344-
*(a::ChoiState, b::ChoiState) = ChoiState(SuperOperator(a)*SuperOperator(b))
345-
*(a::ChoiState, b::Operator) = SuperOperator(a)*b
346-
==(a::ChoiState, b::ChoiState) = (SuperOperator(a) == SuperOperator(b))
18+
sprepost(A::Operator, B::Operator) = SuperOperator((A.basis_l, B.basis_r), (A.basis_r, B.basis_l), kron(permutedims(B.data), A.data))
34719

34820
# reshape swaps within systems due to colum major ordering
34921
# https://docs.qojulia.org/quantumobjects/operators/#tensor_order
@@ -369,9 +41,25 @@ function _super_choi((r2, l2), (r1, l1), data::SparseMatrixCSC)
36941
return (l1, l2), (r1, r2), sparse(data)
37042
end
37143

372-
ChoiState(op::SuperOperator) = ChoiState(_super_choi(op.basis_l, op.basis_r, op.data)...)
373-
SuperOperator(op::ChoiState) = SuperOperator(_super_choi(op.basis_l, op.basis_r, op.data)...)
44+
function choi(op::SuperOperatorType)
45+
bl, br = basis_l(op), basis_r(op)
46+
(l1, l2), (r1, r2) = (basis_l(bl), basis_r(bl)), (basis_l(br), basis_r(br))
47+
(l1, l2), (r1, r2), data = _super_choi((l1, l2), (r1, r2), op.data)
48+
return Operator(ChoiBasis(l1,true)ChoiBasis(l2,false),
49+
ChoiBasis(r1,true)ChoiBasis(r2,false), data)
50+
end
51+
52+
function super(op::ChoiStateType)
53+
bl, br = basis_l(op), basis_r(op)
54+
(l1, l2), (r1, r2) = (basis_l(bl), basis_r(bl)), (basis_l(br), basis_r(br))
55+
(l1, l2), (r1, r2), data = _super_choi((l1, l2), (r1, r2), op.data)
56+
return Operator(KetBraBasis(l1,l2), KetBraBasis(r1,r2), data)
57+
end
37458

375-
*(a::ChoiState, b::SuperOperator) = SuperOperator(a)*b
376-
*(a::SuperOperator, b::ChoiState) = a*SuperOperator(b)
59+
dagger(a::ChoiStateType) = choi(dagger(super(a)))
37760

61+
*(a::SuperOperatorType, b::Operator) = a*vec(b)
62+
*(a::ChoiStateType, b::SuperOperatorType) = super(a)*b
63+
*(a::SuperOperatorType, b::ChoiStateType) = a*super(b)
64+
*(a::ChoiStateType, b::ChoiStateType) = choi(super(a)*super(b))
65+
*(a::ChoiStateType, b::Operator) = super(a)*b

0 commit comments

Comments
 (0)