Skip to content

Commit 68ed7c3

Browse files
Fix definition of noise derivative in stochastic solvers (#453)
1 parent 9832ecc commit 68ed7c3

File tree

7 files changed

+40
-17
lines changed

7 files changed

+40
-17
lines changed

CHANGELOG.md

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

1010
- Support different length for `to` and `from` on GeneralDimensions. ([#448])
1111
- Extend the `Makie.jl` extension to all the other available backends. ([#450])
12+
- Fix definition of noise derivative in stochastic solvers. ([#453])
1213

1314
## [v0.30.0]
1415
Release date: 2025-04-12
@@ -206,3 +207,4 @@ Release date: 2024-11-13
206207
[#443]: https://github.com/qutip/QuantumToolbox.jl/issues/443
207208
[#448]: https://github.com/qutip/QuantumToolbox.jl/issues/448
208209
[#450]: https://github.com/qutip/QuantumToolbox.jl/issues/450
210+
[#453]: https://github.com/qutip/QuantumToolbox.jl/issues/453

src/qobj/quantum_object_evo.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,9 @@ function _promote_to_scimloperator(α::Number, data::ScaledOperator)
468468
isconstant(data.λ) && return ScaledOperator* data.λ, data.L)
469469
return ScaledOperator(data.λ, _promote_to_scimloperator(α, data.L)) # Try to propagate the rule
470470
end
471+
function _promote_to_scimloperator::Number, data::AddedOperator)
472+
return AddedOperator(_promote_to_scimloperator.(α, data.ops)) # Try to propagate the rule
473+
end
471474
function _promote_to_scimloperator::Number, data::AbstractSciMLOperator)
472475
return α * data # Going back to the generic case
473476
end

src/time_evolution/callback_helpers/callback_helpers.jl

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@ function _generate_stochastic_kwargs(
2424
) where {SF<:AbstractSaveFunc}
2525
cb_save = _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurement, progress_bar, method)
2626

27+
# Ensure that the noise is stored in tlist. # TODO: Fix this directly in DiffEqNoiseProcess.jl
28+
# See https://github.com/SciML/DiffEqNoiseProcess.jl/issues/214 for example
29+
tstops = haskey(kwargs, :tstops) ? unique!(sort!(vcat(tlist, kwargs.tstops))) : tlist
30+
kwargs2 = merge(kwargs, (tstops = tstops,))
31+
2732
if SF === SaveFuncSSESolve
2833
cb_normalize = _ssesolve_generate_normalize_cb()
29-
return _merge_kwargs_with_callback(kwargs, CallbackSet(cb_normalize, cb_save))
34+
return _merge_kwargs_with_callback(kwargs2, CallbackSet(cb_normalize, cb_save))
3035
end
3136

32-
return _merge_kwargs_with_callback(kwargs, cb_save)
37+
return _merge_kwargs_with_callback(kwargs2, cb_save)
3338
end
3439
_generate_stochastic_kwargs(
3540
e_ops::Nothing,
@@ -69,7 +74,9 @@ function _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurem
6974
expvals = e_ops isa Nothing ? nothing : Array{ComplexF64}(undef, length(e_ops), length(tlist))
7075
m_expvals = getVal(store_measurement) ? Array{Float64}(undef, length(sc_ops), length(tlist) - 1) : nothing
7176

72-
_save_func = method(store_measurement, e_ops_data, m_ops_data, progr, Ref(1), expvals, m_expvals)
77+
_save_func_cache = Array{Float64}(undef, length(sc_ops))
78+
_save_func =
79+
method(store_measurement, e_ops_data, m_ops_data, progr, Ref(1), expvals, m_expvals, tlist, _save_func_cache)
7380
return FunctionCallingCallback(_save_func, funcat = tlist)
7481
end
7582

@@ -162,9 +169,12 @@ _get_save_callback_idx(cb, method) = 1
162169

163170
# %% ------------ Noise Measurement Helpers ------------ %%
164171

165-
# TODO: Add some cache mechanism to avoid memory allocations
166-
function _homodyne_dWdt(integrator)
167-
@inbounds _dWdt = (integrator.W.u[end] .- integrator.W.u[end-1]) ./ (integrator.W.t[end] - integrator.W.t[end-1])
172+
# TODO: To improve. See https://github.com/SciML/DiffEqNoiseProcess.jl/issues/214
173+
function _homodyne_dWdt!(dWdt_cache, integrator, tlist, iter)
174+
idx = findfirst(>=(tlist[iter[]-1]), integrator.W.t)
175+
176+
# We are assuming that the last element is tlist[iter[]]
177+
@inbounds dWdt_cache .= (integrator.W.u[end] .- integrator.W.u[idx]) ./ (integrator.W.t[end] - integrator.W.t[idx])
168178

169-
return _dWdt
179+
return nothing
170180
end

src/time_evolution/callback_helpers/smesolve_callback_helpers.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ struct SaveFuncSMESolve{
1010
IT,
1111
TEXPV<:Union{Nothing,AbstractMatrix},
1212
TMEXPV<:Union{Nothing,AbstractMatrix},
13+
TLT<:AbstractVector,
14+
CT<:AbstractVector,
1315
} <: AbstractSaveFunc
1416
store_measurement::Val{SM}
1517
e_ops::TE
@@ -18,10 +20,12 @@ struct SaveFuncSMESolve{
1820
iter::IT
1921
expvals::TEXPV
2022
m_expvals::TMEXPV
23+
tlist::TLT
24+
dWdt_cache::CT
2125
end
2226

2327
(f::SaveFuncSMESolve)(u, t, integrator) =
24-
_save_func_smesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals)
28+
_save_func_smesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals, f.tlist, f.dWdt_cache)
2529
(f::SaveFuncSMESolve{false,Nothing})(u, t, integrator) = _save_func(integrator, f.progr) # Common for both all solvers
2630

2731
_get_e_ops_data(e_ops, ::Type{SaveFuncSMESolve}) = _get_e_ops_data(e_ops, SaveFuncMESolve)
@@ -31,7 +35,7 @@ _get_m_ops_data(sc_ops, ::Type{SaveFuncSMESolve}) =
3135
##
3236

3337
# When e_ops is a list of operators
34-
function _save_func_smesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals)
38+
function _save_func_smesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals, tlist, dWdt_cache)
3539
# This is equivalent to tr(op * ρ), when both are matrices.
3640
# The advantage of using this convention is that We don't need
3741
# to reshape u to make it a matrix, but we reshape the e_ops once.
@@ -45,8 +49,8 @@ function _save_func_smesolve(u, integrator, e_ops, m_ops, progr, iter, expvals,
4549
end
4650

4751
if !isnothing(m_expvals) && iter[] > 1
48-
_dWdt = _homodyne_dWdt(integrator)
49-
@. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + _dWdt
52+
_homodyne_dWdt!(dWdt_cache, integrator, tlist, iter)
53+
@. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + dWdt_cache
5054
end
5155

5256
iter[] += 1

src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ struct SaveFuncSSESolve{
1010
IT,
1111
TEXPV<:Union{Nothing,AbstractMatrix},
1212
TMEXPV<:Union{Nothing,AbstractMatrix},
13+
TLT<:AbstractVector,
14+
CT<:AbstractVector,
1315
} <: AbstractSaveFunc
1416
store_measurement::Val{SM}
1517
e_ops::TE
@@ -18,10 +20,12 @@ struct SaveFuncSSESolve{
1820
iter::IT
1921
expvals::TEXPV
2022
m_expvals::TMEXPV
23+
tlist::TLT
24+
dWdt_cache::CT
2125
end
2226

2327
(f::SaveFuncSSESolve)(u, t, integrator) =
24-
_save_func_ssesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals)
28+
_save_func_ssesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals, f.tlist, f.dWdt_cache)
2529
(f::SaveFuncSSESolve{false,Nothing})(u, t, integrator) = _save_func(integrator, f.progr) # Common for both all solvers
2630

2731
_get_e_ops_data(e_ops, ::Type{SaveFuncSSESolve}) = get_data.(e_ops)
@@ -32,7 +36,7 @@ _get_save_callback_idx(cb, ::Type{SaveFuncSSESolve}) = 2 # The first one is the
3236
##
3337

3438
# When e_ops is a list of operators
35-
function _save_func_ssesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals)
39+
function _save_func_ssesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals, tlist, dWdt_cache)
3640
ψ = u
3741

3842
_expect = op -> dot(ψ, op, ψ)
@@ -42,8 +46,8 @@ function _save_func_ssesolve(u, integrator, e_ops, m_ops, progr, iter, expvals,
4246
end
4347

4448
if !isnothing(m_expvals) && iter[] > 1
45-
_dWdt = _homodyne_dWdt(integrator)
46-
@. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + _dWdt
49+
_homodyne_dWdt!(dWdt_cache, integrator, tlist, iter)
50+
@. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + dWdt_cache
4751
end
4852

4953
iter[] += 1

src/time_evolution/ssesolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function ssesolveProblem(
113113
sc_ops_evo_data,
114114
)
115115

116-
K = -1im * get_data(H_eff_evo) + K_l
116+
K = get_data(QobjEvo(H_eff_evo, -1im)) + K_l
117117

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

src/time_evolution/time_evolution.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionStochasticSol
33
export liouvillian_floquet, liouvillian_generalized
44

55
const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true)
6-
const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true)
6+
const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-3, reltol = 2e-3, save_everystep = false, save_end = true)
77
const COL_TIMES_WHICH_INIT_SIZE = 200
88

99
@doc raw"""

0 commit comments

Comments
 (0)