diff --git a/CHANGELOG.md b/CHANGELOG.md index 24aa649e5..0a7f8fed3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix Dynamical Fock Dimension states saving due to wrong saving of dimensions. ([#375]) - Support a list of observables for `expect`. ([#374], [#376]) +- Add checks for `tlist` in time evolution solvers. The checks are to ensure that `tlist` is not empty, the elements are in increasing order, and the elements are unique. ([#378]) ## [v0.25.0] Release date: 2025-01-20 @@ -89,3 +90,4 @@ Release date: 2024-11-13 [#374]: https://github.com/qutip/QuantumToolbox.jl/issues/374 [#375]: https://github.com/qutip/QuantumToolbox.jl/issues/375 [#376]: https://github.com/qutip/QuantumToolbox.jl/issues/376 +[#378]: https://github.com/qutip/QuantumToolbox.jl/issues/378 diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index bddcc40bf..873e7e9ed 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -409,7 +409,7 @@ function lr_mesolveProblem( c_ops = get_data.(c_ops) e_ops = get_data.(e_ops) - t_l = convert(Vector{_FType(H)}, tlist) + t_l = _check_tlist(tlist, _FType(H)) # Initialization of Arrays expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index de37de3d1..94c0372f8 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -167,7 +167,7 @@ function mcsolveProblem( c_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + tlist = _check_tlist(tlist, _FType(ψ0)) H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, c_ops) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index b7eb62886..ae1a40667 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -68,7 +68,7 @@ function mesolveProblem( haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) - tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + tlist = _check_tlist(tlist, _FType(ψ0)) L_evo = _mesolve_make_L_QobjEvo(H, c_ops) check_dimensions(L_evo, ψ0) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index b76613f88..999218b9d 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -59,7 +59,7 @@ function sesolveProblem( haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) - tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + tlist = _check_tlist(tlist, _FType(ψ0)) H_evo = _sesolve_make_U_QobjEvo(H) # Multiply by -i isoper(H_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index d0cee3721..5e4a15e12 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -162,7 +162,7 @@ function ssesolveProblem( sc_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - tlist = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for StochasticDiffEq.jl + tlist = _check_tlist(tlist, _FType(ψ0)) H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, sc_ops) isoper(H_eff_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 75f385e34..39d5d1641 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -212,6 +212,19 @@ struct DiscreteLindbladJumpCallback <: LindbladJumpCallbackType end ContinuousLindbladJumpCallback(; interp_points::Int = 10) = ContinuousLindbladJumpCallback(interp_points) +function _check_tlist(tlist, T::Type) + tlist2 = convert(Vector{T}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + + # Check if the list of times is not empty + isempty(tlist2) && throw(ArgumentError("The time list must not be empty.")) + # Check if the list of times is sorted + issorted(tlist2) || throw(ArgumentError("The time list must be sorted.")) + # Check if the list of times is unique + allunique(tlist2) || throw(ArgumentError("The time list must be unique.")) + + return tlist2 +end + ####################################### function liouvillian_floquet( diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 9695c20cd..5f62a2479 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -63,6 +63,13 @@ "abstol = $(sol2.abstol)\n" * "reltol = $(sol2.reltol)\n" + tlist1 = Float64[] + tlist2 = [0, 0.2, 0.1] + tlist3 = [0, 0.1, 0.1, 0.2] + @test_throws ArgumentError sesolve(H, ψ0, tlist1, progress_bar = Val(false)) + @test_throws ArgumentError sesolve(H, ψ0, tlist2, progress_bar = Val(false)) + @test_throws ArgumentError sesolve(H, ψ0, tlist3, progress_bar = Val(false)) + @testset "Memory Allocations" begin allocs_tot = @allocations sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) # Warm-up allocs_tot = @allocations sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) @@ -190,6 +197,19 @@ "abstol = $(sol_sse.abstol)\n" * "reltol = $(sol_sse.reltol)\n" + tlist1 = Float64[] + tlist2 = [0, 0.2, 0.1] + tlist3 = [0, 0.1, 0.1, 0.2] + @test_throws ArgumentError mesolve(H, ψ0, tlist1, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mesolve(H, ψ0, tlist2, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mesolve(H, ψ0, tlist3, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mcsolve(H, ψ0, tlist1, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mcsolve(H, ψ0, tlist2, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mcsolve(H, ψ0, tlist3, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError ssesolve(H, ψ0, tlist1, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError ssesolve(H, ψ0, tlist2, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError ssesolve(H, ψ0, tlist3, c_ops, progress_bar = Val(false)) + # Time-Dependent Hamiltonian # ssesolve is slow to be run on CI. It is not removed from the test because it may be useful for testing in more powerful machines.