Skip to content

Commit 64a727c

Browse files
Improve QuantumObjectEvolution generation function and fix smesolve type instabilities (#548)
1 parent 3f6d8ed commit 64a727c

File tree

7 files changed

+94
-119
lines changed

7 files changed

+94
-119
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ changelog:
1212
${JULIA} -e 'using Changelog; Changelog.generate(Changelog.CommonMark(), "CHANGELOG.md"; repo = "qutip/QuantumToolbox.jl")'
1313

1414
test:
15-
${JULIA} --project -e 'using Pkg; Pkg.resolve(); Pkg.test()'
15+
${JULIA} --project -e 'using Pkg; Pkg.update(); Pkg.test()'
1616

1717
docs:
18-
${JULIA} --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
18+
${JULIA} --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.update()'
1919
${JULIA} --project=docs docs/make.jl
2020

2121
vitepress:

src/qobj/quantum_object_evo.jl

Lines changed: 80 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -175,19 +175,18 @@ function QuantumObjectEvolution(data::AbstractSciMLOperator; type = Operator(),
175175
end
176176

177177
@doc raw"""
178-
QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type=nothing)
179-
QuantumObjectEvolution(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type=nothing)
178+
QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}; type=nothing)
179+
QuantumObjectEvolution(op_func_list::Union{Tuple,AbstractQuantumObject}; type=nothing)
180180
181181
Generate [`QuantumObjectEvolution`](@ref).
182182
183183
# Arguments
184184
- `op_func_list::Union{Tuple,AbstractQuantumObject}`: A tuple of tuples or operators.
185-
- `α::Union{Nothing,Number}=nothing`: A scalar to pre-multiply the operators.
186185
187186
!!! warning "Beware of type-stability!"
188187
Please note that, unlike QuTiP, this function doesn't support `op_func_list` as `Vector` type. This is related to the type-stability issue. See the Section [The Importance of Type-Stability](@ref doc:Type-Stability) for more details.
189188
190-
Note that if `α` is provided, all the operators in `op_func_list` will be pre-multiplied by `α`. The `type` parameter is used to specify the type of the [`QuantumObject`](@ref), either `Operator` or `SuperOperator`. The `f` parameter is used to pre-apply a function to the operators before converting them to SciML operators.
189+
The `type` parameter is used to specify the type of the [`QuantumObject`](@ref), either `Operator` or `SuperOperator`.
191190
192191
!!! note
193192
`QobjEvo` is a synonym of `QuantumObjectEvolution`.
@@ -267,8 +266,8 @@ Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=f
267266
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦
268267
```
269268
"""
270-
function QuantumObjectEvolution(op_func_list::Tuple, α::Union{Nothing,Number} = nothing; type = nothing)
271-
op, data = _QobjEvo_generate_data(op_func_list, α)
269+
function QuantumObjectEvolution(op_func_list::Tuple; type = nothing)
270+
op, data = _QobjEvo_generate_data(op_func_list)
272271
dims = op.dimensions
273272
_check_type(type)
274273

@@ -284,17 +283,17 @@ function QuantumObjectEvolution(op_func_list::Tuple, α::Union{Nothing,Number} =
284283
end
285284

286285
# this is a extra method if user accidentally specify `QuantumObjectEvolution( (op, func) )` or `QuantumObjectEvolution( ((op, func)) )`
287-
QuantumObjectEvolution(op_func::Tuple{QuantumObject,Function}, α::Union{Nothing,Number} = nothing; type = nothing) =
288-
QuantumObjectEvolution((op_func,), α; type = type)
286+
QuantumObjectEvolution(op_func::Tuple{<:QuantumObject,<:Function}; type = nothing) =
287+
QuantumObjectEvolution((op_func,); type = type)
289288

290289
@doc raw"""
291-
QuantumObjectEvolution(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type = nothing)
292-
QobjEvo(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type = nothing)
290+
QuantumObjectEvolution(op::QuantumObject, f::Function; type = nothing)
291+
QobjEvo(op::QuantumObject, f::Function; type = nothing)
293292
294293
Generate [`QuantumObjectEvolution`](@ref).
295294
296295
# Notes
297-
- The `f` parameter is used to pre-apply a function to the operators before converting them to SciML operators. The `type` parameter is used to specify the type of the [`QuantumObject`](@ref), either `Operator` or `SuperOperator`.
296+
- The `f` parameter is time-dependent coefficient that multiplies the operator. The `type` parameter is used to specify the type of the [`QuantumObject`](@ref), either `Operator` or `SuperOperator`.
298297
- `QobjEvo` is a synonym of `QuantumObjectEvolution`.
299298
300299
# Examples
@@ -318,18 +317,17 @@ Quantum Object Evo.: type=Operator() dims=[10, 2] size=(20, 20) ishermit
318317
ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)
319318
```
320319
"""
321-
QuantumObjectEvolution(op::QuantumObject, f::Function, α::Union{Nothing,Number} = nothing; type = nothing) =
322-
QuantumObjectEvolution(((op, f),), α; type = type)
320+
QuantumObjectEvolution(op::QuantumObject, f::Function; type = nothing) = QuantumObjectEvolution(((op, f),); type = type)
323321

324-
function QuantumObjectEvolution(op::QuantumObject, α::Union{Nothing,Number} = nothing; type = nothing)
322+
function QuantumObjectEvolution(op::QuantumObject; type = nothing)
325323
_check_type(type)
326324
if type isa Nothing
327325
type = op.type
328326
end
329-
return QuantumObjectEvolution(_make_SciMLOperator(op, α), type, op.dimensions)
327+
return QuantumObjectEvolution(_make_SciMLOperator(op), type, op.dimensions)
330328
end
331329

332-
function QuantumObjectEvolution(op::QuantumObjectEvolution, α::Union{Nothing,Number} = nothing; type = nothing)
330+
function QuantumObjectEvolution(op::QuantumObjectEvolution; type = nothing)
333331
_check_type(type)
334332
if type isa Nothing
335333
type = op.type
@@ -340,114 +338,97 @@ function QuantumObjectEvolution(op::QuantumObjectEvolution, α::Union{Nothing,Nu
340338
),
341339
)
342340
end
343-
if α isa Nothing
344-
return QuantumObjectEvolution(op.data, type, op.dimensions)
345-
end
346-
return QuantumObjectEvolution(_promote_to_scimloperator(α, op.data), type, op.dimensions)
341+
return QuantumObjectEvolution(op.data, type, op.dimensions)
347342
end
348343

349344
#=
350-
_QobjEvo_generate_data(op_func_list::Tuple, α; f::Function=identity)
345+
_QobjEvo_generate_data(op_func_list::Tuple)
351346
352-
Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` object. The `op_func_list` is a tuple of tuples or operators. Each element of the tuple can be a tuple with two elements (operator, function) or an operator. The function is used to generate the time-dependent coefficients for the operators. The `α` parameter is used to pre-multiply the operators by a scalar. The `f` parameter is used to pre-applying a function to the operators before converting them to SciML operators. During the parsing, the dimensions of the operators are checked to be the same, and all the constant operators are summed together.
347+
Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` object. The `op_func_list` is a tuple of tuples or operators. Each element of the tuple can be a tuple with two elements (operator, function) or an operator. The function is used to generate the time-dependent coefficients for the operators. During the parsing, the dimensions of the operators are checked to be the same, and all the constant operators are summed together.
353348
354349
# Arguments
355350
- `op_func_list::Tuple`: A tuple of tuples or operators.
356-
- `α`: A scalar to pre-multiply the operators.
357-
- `f::Function=identity`: A function to pre-apply to the operators.
358351
=#
359-
@generated function _QobjEvo_generate_data(op_func_list::Tuple, α)
360-
op_func_list_types = op_func_list.parameters
361-
N = length(op_func_list_types)
362-
363-
dims_expr = ()
364-
func_methods_expr = ()
365-
first_op = nothing
366-
data_expr = :(0)
367-
qobj_expr_const = :(0)
368-
369-
for i in 1:N
370-
op_func_type = op_func_list_types[i]
371-
if op_func_type <: Tuple
372-
# check the structure of the tuple
373-
length(op_func_type.parameters) == 2 || throw(ArgumentError("The tuple must have two elements."))
374-
op_type = op_func_type.parameters[1]
375-
func_type = op_func_type.parameters[2]
376-
((isoper(op_type) || issuper(op_type)) && func_type <: Function) || throw(
377-
ArgumentError(
378-
"The first element must be a Operator or SuperOperator, and the second element must be a function.",
379-
),
380-
)
381-
382-
op = :(op_func_list[$i][1])
383-
dims_expr = (dims_expr..., :($op.dimensions))
384-
func_methods_expr = (func_methods_expr..., :(methods(op_func_list[$i][2], [Any, Real]).ms)) # [Any, Real] means each func must accept 2 arguments
385-
if i == 1
386-
first_op = :($op)
387-
end
388-
data_expr = :($data_expr + _make_SciMLOperator(op_func_list[$i], α))
352+
function _QobjEvo_generate_data(op_func_list::Tuple)
353+
first_op = _QobjEvo_get_first_op(op_func_list[1])
354+
355+
ops_constant = filter(op_func_list) do x
356+
if x isa QuantumObject
357+
x.type == first_op.type || throw(ArgumentError("The types of the operators must be the same."))
358+
x.dimensions == first_op.dimensions ||
359+
throw(ArgumentError("The dimensions of the operators must be the same."))
360+
return true
361+
elseif x isa Tuple
362+
return false
389363
else
390-
op_type = op_func_type
391-
(isoper(op_type) || issuper(op_type)) ||
392-
throw(ArgumentError("The element must be a Operator or SuperOperator."))
393-
394-
dims_expr = (dims_expr..., :(op_func_list[$i].dimensions))
395-
if i == 1
396-
first_op = :(op_func_list[$i])
397-
end
398-
qobj_expr_const = :($qobj_expr_const + op_func_list[$i])
364+
throw(ArgumentError("Each element of the tuple must be either a QuantumObject or a Tuple."))
399365
end
400366
end
401367

402-
quote
403-
# check the dims of the operators
404-
dims = tuple($(dims_expr...))
405-
allequal(dims) || throw(ArgumentError("The dimensions of the operators must be the same."))
406-
407-
# check if each func accepts 2 arguments
408-
func_methods = tuple($(func_methods_expr...))
409-
for i in eachindex(func_methods)
410-
length(func_methods[i]) == 0 && throw(
411-
ArgumentError(
412-
"The following function must only accept two arguments: `$(nameof(op_func_list[i][2]))(p, t)` with t<:Real",
413-
),
414-
)
368+
ops_time_dep = filter(op_func_list) do x
369+
if x isa Tuple
370+
_QobjEvo_check_op_func(x)
371+
op = x[1]
372+
_QobjEvo_check_op(op)
373+
op.type == first_op.type || throw(ArgumentError("The types of the operators must be the same."))
374+
op.dimensions == first_op.dimensions ||
375+
throw(ArgumentError("The dimensions of the operators must be the same."))
376+
return true
377+
elseif x isa QuantumObject
378+
return false
379+
else
380+
throw(ArgumentError("Each element of the tuple must be either a QuantumObject or a Tuple."))
415381
end
382+
end
416383

417-
data_expr_const = $qobj_expr_const isa Integer ? $qobj_expr_const : _make_SciMLOperator($qobj_expr_const, α)
384+
data_const = isempty(ops_constant) ? zero(eltype(first_op)) : _make_SciMLOperator(sum(ops_constant))
385+
data_td =
386+
length(ops_time_dep) == 1 ? _make_SciMLOperator(ops_time_dep[1]) :
387+
AddedOperator(map(_make_SciMLOperator, ops_time_dep))
388+
data = data_const + data_td
418389

419-
data_expr = data_expr_const + $data_expr
390+
return first_op, data
391+
end
420392

421-
return $first_op, data_expr
422-
end
393+
function _QobjEvo_check_op(op::AbstractQuantumObject)
394+
(isoper(op) || issuper(op)) || throw(ArgumentError("The element must be a Operator or SuperOperator."))
395+
return nothing
423396
end
424397

425-
function _make_SciMLOperator(op_func::Tuple, α)
426-
T = eltype(op_func[1])
427-
update_func = (a, u, p, t) -> op_func[2](p, t)
428-
if α isa Nothing
429-
return ScalarOperator(zero(T), update_func) * _promote_to_scimloperator(op_func[1].data)
430-
end
431-
return ScalarOperator(zero(T), update_func) * _promote_to_scimloperator(α, op_func[1].data)
398+
function _QobjEvo_check_op_func(op_func::Tuple)
399+
length(op_func) == 2 || throw(ArgumentError("The tuple must have two elements."))
400+
_QobjEvo_check_op(op_func[1])
401+
(op_func[2] isa Function) || throw(ArgumentError("The second element must be a function."))
402+
methods(op_func[2], (Any, Real)) |> length == 0 && throw(
403+
ArgumentError(
404+
"The following function must only accept two arguments: `$(nameof(op_func[2]))(p, t)` with t<:Real",
405+
),
406+
)
407+
return nothing
432408
end
433409

434-
function _make_SciMLOperator(op::AbstractQuantumObject, α)
435-
if α isa Nothing
436-
return _promote_to_scimloperator(op.data)
410+
_QobjEvo_get_first_op(op_func_list_1::Union{Tuple,AbstractQuantumObject}) =
411+
if op_func_list_1 isa Tuple
412+
_QobjEvo_check_op_func(op_func_list_1)
413+
op = op_func_list_1[1]
414+
_QobjEvo_check_op(op)
415+
return op
416+
else
417+
op = op_func_list_1
418+
_QobjEvo_check_op(op)
419+
return op
437420
end
438-
return _promote_to_scimloperator(α, op.data)
421+
422+
function _make_SciMLOperator(op_func::Tuple)
423+
op, coef = op_func
424+
T = eltype(op)
425+
update_func = (a, u, p, t) -> coef(p, t)
426+
return ScalarOperator(zero(T), update_func) * _promote_to_scimloperator(op.data)
439427
end
428+
_make_SciMLOperator(op::AbstractQuantumObject) = _promote_to_scimloperator(op.data)
440429

441430
_promote_to_scimloperator(data::AbstractMatrix) = MatrixOperator(data)
442431
_promote_to_scimloperator(data::AbstractSciMLOperator) = data
443-
_promote_to_scimloperator::Number, data::AbstractMatrix) = MatrixOperator* data)
444-
# We still have to define this for AddedOperator, as it is not present in SciMLOperators.jl
445-
function _promote_to_scimloperator::Number, data::AddedOperator)
446-
return AddedOperator(_promote_to_scimloperator.(α, data.ops)) # Try to propagate the rule
447-
end
448-
function _promote_to_scimloperator::Number, data::AbstractSciMLOperator)
449-
return α * data # Going back to the generic case
450-
end
451432

452433
@doc raw"""
453434
(A::QuantumObjectEvolution)(ψout, ψin, p, t)

src/time_evolution/mcsolve.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@ function _normalize_state!(u, dims, normalize_states)
2626
end
2727

2828
function _mcsolve_make_Heff_QobjEvo(H::QuantumObject, c_ops)
29-
c_ops isa Nothing && return QobjEvo(H)
30-
return QobjEvo(H - 1im * mapreduce(op -> op' * op, +, c_ops) / 2)
29+
c_ops isa Nothing && return QuantumObjectEvolution(H)
30+
return QuantumObjectEvolution(H - 1im * mapreduce(op -> op' * op, +, c_ops) / 2)
3131
end
3232
function _mcsolve_make_Heff_QobjEvo(H::Tuple, c_ops)
33-
c_ops isa Nothing && return QobjEvo(H)
34-
return QobjEvo((H..., -1im * mapreduce(op -> op' * op, +, c_ops) / 2))
33+
c_ops isa Nothing && return QuantumObjectEvolution(H)
34+
return QuantumObjectEvolution((H..., -1im * sum(op -> op' * op, c_ops) / 2))
3535
end
3636
function _mcsolve_make_Heff_QobjEvo(H::QuantumObjectEvolution, c_ops)
3737
c_ops isa Nothing && return H
38-
return H + QobjEvo(mapreduce(op -> op' * op, +, c_ops), -1im / 2)
38+
return H + QuantumObjectEvolution(sum(op -> -1im * op' * op / 2, c_ops))
3939
end
4040

4141
@doc raw"""

src/time_evolution/sesolve.jl

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
export sesolveProblem, sesolve
22

3-
_sesolve_make_U_QobjEvo(
4-
H::QuantumObjectEvolution{Operator,DimsType,<:MatrixOperator},
5-
) where {DimsType<:AbstractDimensions} =
6-
QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dimensions, type = Operator())
7-
_sesolve_make_U_QobjEvo(H::QuantumObject) =
8-
QobjEvo(MatrixOperator(-1im * H.data), dims = H.dimensions, type = Operator())
9-
_sesolve_make_U_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}) = QobjEvo(H, -1im)
3+
_sesolve_make_U_QobjEvo(H) = -1im * QuantumObjectEvolution(H, type = Operator())
104

115
@doc raw"""
126
sesolveProblem(

src/time_evolution/smesolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ function smesolveProblem(
119119
# TODO: # Currently, we are assuming a time-independent MatrixOperator
120120
# Also, the u state may become non-hermitian, so Tr[Sn * ρ + ρ * Sn'] != real(Tr[Sn * ρ]) / 2
121121
op_vec = mat2vec(adjoint(op.A))
122-
return _spre(op, Id) + _spost(op', Id) + _smesolve_ScalarOperator(op_vec) * Id_op
122+
return AddedOperator(_spre(op, Id), _spost(op', Id), _smesolve_ScalarOperator(op_vec) * Id_op)
123123
end
124124
D = DiffusionOperator(D_l)
125125

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 = get_data(QobjEvo(H_eff_evo, -1im)) + K_l
116+
K = get_data(-1im * QuantumObjectEvolution(H_eff_evo)) + K_l
117117

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

test/runtests.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const testdir = dirname(@__FILE__)
2626
if (GROUP == "All") || (GROUP == "Code-Quality")
2727
Pkg.activate("core-test/code-quality")
2828
Pkg.develop(PackageSpec(path = dirname(@__DIR__)))
29-
Pkg.instantiate()
29+
Pkg.update()
3030

3131
using QuantumToolbox
3232
using Aqua, JET
@@ -39,7 +39,7 @@ end
3939
if (GROUP == "AutoDiff_Ext")
4040
Pkg.activate("ext-test/cpu/autodiff")
4141
Pkg.develop(PackageSpec(path = dirname(@__DIR__)))
42-
Pkg.instantiate()
42+
Pkg.update()
4343

4444
using QuantumToolbox
4545
using ForwardDiff
@@ -55,7 +55,7 @@ end
5555
if (GROUP == "Makie_Ext")
5656
Pkg.activate("ext-test/cpu/makie")
5757
Pkg.develop(PackageSpec(path = dirname(@__DIR__)))
58-
Pkg.instantiate()
58+
Pkg.update()
5959

6060
using QuantumToolbox
6161
QuantumToolbox.about()
@@ -67,7 +67,7 @@ end
6767
if (GROUP == "CUDA_Ext")
6868
Pkg.activate("ext-test/gpu")
6969
Pkg.develop(PackageSpec(path = dirname(@__DIR__)))
70-
Pkg.instantiate()
70+
Pkg.update()
7171

7272
using QuantumToolbox
7373
import LinearAlgebra: Diagonal

0 commit comments

Comments
 (0)