Skip to content

Commit e209b96

Browse files
Add spre and spost methods for ComposedOperator and cache propagator in every time evolution solver (#596)
1 parent 38f2d89 commit e209b96

File tree

9 files changed

+35
-6
lines changed

9 files changed

+35
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main)
99

10+
- Add `spre` and `spost` methods for `ComposedOperator` and cache propagator in every time evolution solver. ([#596])
11+
1012
## [v0.39.0]
1113
Release date: 2025-11-17
1214

@@ -374,3 +376,4 @@ Release date: 2024-11-13
374376
[#588]: https://github.com/qutip/QuantumToolbox.jl/issues/588
375377
[#589]: https://github.com/qutip/QuantumToolbox.jl/issues/589
376378
[#591]: https://github.com/qutip/QuantumToolbox.jl/issues/591
379+
[#596]: https://github.com/qutip/QuantumToolbox.jl/issues/596

src/QuantumToolbox.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import SciMLOperators:
5050
ScalarOperator,
5151
ScaledOperator,
5252
AddedOperator,
53+
ComposedOperator,
5354
IdentityOperator,
5455
update_coefficients!,
5556
concretize

src/qobj/superoperators.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ _sprepost(A, B) = _spre(A) * _spost(B) # for any other input types
2222
_spre(A::MatrixOperator) = MatrixOperator(_spre(A.A))
2323
_spre(A::ScaledOperator) = ScaledOperator(A.λ, _spre(A.L))
2424
_spre(A::AddedOperator) = AddedOperator(map(op -> _spre(op), A.ops))
25+
_spre(A::ComposedOperator) = ComposedOperator(map(_spre, A.ops), nothing)
2526
function _spre(A::AbstractSciMLOperator)
2627
Id = Eye(size(A, 1))
2728
_lazy_tensor_warning(Id, A)
@@ -31,6 +32,7 @@ end
3132
_spost(B::MatrixOperator) = MatrixOperator(_spost(B.A))
3233
_spost(B::ScaledOperator) = ScaledOperator(B.λ, _spost(B.L))
3334
_spost(B::AddedOperator) = AddedOperator(map(op -> _spost(op), B.ops))
35+
_spost(B::ComposedOperator) = ComposedOperator(map(_spost, reverse(B.ops)), nothing)
3436
function _spost(B::AbstractSciMLOperator)
3537
B_T = transpose(B)
3638
Id = Eye(size(B, 1))

src/time_evolution/mesolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function mesolveProblem(
112112
else
113113
to_dense(_complex_float_type(T), mat2vec(ket2dm(ψ0).data))
114114
end
115-
L = L_evo.data
115+
L = cache_operator(L_evo.data, ρ0)
116116

117117
kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_ODE_SOLVER_OPTIONS; kwargs...)
118118
kwargs3 = _merge_tstops(kwargs2, isconstant(L), tlist)

src/time_evolution/sesolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ function sesolveProblem(
8080

8181
T = Base.promote_eltype(H_evo, ψ0)
8282
ψ0 = to_dense(_complex_float_type(T), get_data(ψ0)) # Convert it to dense vector with complex element type
83-
U = H_evo.data
83+
U = cache_operator(H_evo.data, ψ0)
8484

8585
kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_ODE_SOLVER_OPTIONS; kwargs...)
8686
kwargs3 = _merge_tstops(kwargs2, isconstant(U), tlist)

src/time_evolution/smesolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function smesolveProblem(
109109

110110
sc_ops_evo_data = Tuple(map(get_data QobjEvo, sc_ops_list))
111111

112-
K = get_data(L_evo)
112+
K = cache_operator(get_data(L_evo), ρ0)
113113

114114
Id_op = IdentityOperator(prod(dims)^2)
115115
D_l = map(sc_ops_evo_data) do op

src/time_evolution/ssesolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ function ssesolveProblem(
111111
sc_ops_evo_data,
112112
)
113113

114-
K = get_data(-1im * QuantumObjectEvolution(H_eff_evo)) + K_l
114+
K = cache_operator(get_data(-1im * QuantumObjectEvolution(H_eff_evo)) + K_l, ψ0)
115115

116116
D_l = map(op -> op + _ScalarOperator_e(op, -) * IdentityOperator(prod(dims)), sc_ops_evo_data)
117117
D = DiffusionOperator(D_l)

test/core-test/quantum_objects_evo.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@
216216
coef2(p, t) * conj(coef3(p, t)) * (spre(a) * spost(X') - 0.5 * spre(X' * a) - 0.5 * spost(X' * a)) + # cross terms
217217
conj(coef2(p, t)) * coef3(p, t) * (spre(X) * spost(a') - 0.5 * spre(a' * X) - 0.5 * spost(a' * X)) # cross terms
218218
L_ti = liouvillian(H_ti) + D1_ti + D2_ti
219-
L_td = @test_logs (:warn,) (:warn,) liouvillian(H_td, c_ops) # warnings from lazy tensor in `lindblad_dissipator(c_op2)`
219+
L_td = @test_logs liouvillian(H_td, c_ops) # Test there are no warnings from lazy tensor
220220
ρvec = mat2vec(rand_dm(N))
221221
@test L_td(p, t) L_ti
222222
@test iscached(L_td) == false
@@ -237,7 +237,7 @@
237237

238238
coef_wrong1(t) = nothing
239239
coef_wrong2(p, t::ComplexF64) = nothing
240-
@test_logs (:warn,) (:warn,) liouvillian(H_td * H_td) # warnings from lazy tensor
240+
@test_logs liouvillian(H_td * H_td) # Check there are no warnings from lazy tensor
241241
@test_throws ArgumentError QobjEvo(a, coef_wrong1)
242242
@test_throws ArgumentError QobjEvo(a, coef_wrong2)
243243
@test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]])

test/core-test/time_evolution.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,3 +1256,26 @@ end
12561256
@test all(isapprox.(Z_analytic, sol_se.expect[2, :]; atol = 1e-6))
12571257
@test all(isapprox.(Z_analytic, sol_me.expect[2, :]; atol = 1e-6))
12581258
end
1259+
1260+
@testitem "ComposedOperator support in time evolution" begin
1261+
N = 10 # number of basis states
1262+
a = destroy(N)
1263+
H = a' * a
1264+
1265+
ψ0 = basis(N, 9) # initial state
1266+
1267+
γ0 = 0.5
1268+
γ1(p, t) = sqrt(p[1] * exp(-t))
1269+
c_ops_1 = [QobjEvo(2a, γ1)] # This generates a ScaledOperator internally
1270+
c_ops_2 = [QobjEvo(a, γ1) + QobjEvo(a, γ1)] # This generates a ComposedOperator internally
1271+
1272+
e_ops = [a' * a]
1273+
1274+
tlist = range(0, 10, 100)
1275+
params = [γ0]
1276+
sol_1 = mesolve(H, ψ0, tlist, c_ops_1, e_ops = e_ops; progress_bar = Val(false), params = params, saveat = tlist)
1277+
sol_2 = mesolve(H, ψ0, tlist, c_ops_2, e_ops = e_ops; progress_bar = Val(false), params = params, saveat = tlist)
1278+
1279+
@test sol_1.expect[1, :] sol_2.expect[1, :] atol=1e-10
1280+
@test all(x -> isapprox(x[1].data, x[2].data; atol = 1e-10), zip(sol_1.states, sol_2.states))
1281+
end

0 commit comments

Comments
 (0)