From 842e668ed2e7e3f6ffdb34cb3b50c3fc9fdd4f0c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Sun, 15 Sep 2024 20:58:53 +0800 Subject: [PATCH 001/329] update README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 369f9cec2..cd4219aa6 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,6 @@ We can extract the expectation value of the number operator $\hat{a}^\dagger \ha We can easily pass the computation to the GPU, by simply passing all the `Qobj`s to the GPU: -> **_NOTE:_** The described feature requires `Julia 1.9+`. - ```julia using QuantumToolbox using CUDA From 871a29aecc67c41ef50a818939679636af7866ef Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Sun, 15 Sep 2024 20:59:37 +0800 Subject: [PATCH 002/329] extended methods for `expect` and `variance` --- src/qobj/functions.jl | 19 +++++++++++++++++-- src/qobj/superoperators.jl | 3 +++ test/quantum_objects.jl | 7 +++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 667f7930b..58bfd4f74 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -17,7 +17,7 @@ ket2dm(ψ::QuantumObject{<:AbstractArray{T},KetQuantumObject}) where {T} = ψ * ket2dm(ρ::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = ρ @doc raw""" - expect(O::QuantumObject, ψ::QuantumObject) + expect(O::QuantumObject, ψ::Union{QuantumObject,Vector{QuantumObject}}) Expectation value of the [`Operator`](@ref) `O` with the state `ψ`. The state can be a [`Ket`](@ref), [`Bra`](@ref) or [`Operator`](@ref). @@ -27,6 +27,8 @@ If `ψ` is a density matrix ([`Operator`](@ref)), the function calculates ``\tex The function returns a real number if `O` is of `Hermitian` type or `Symmetric` type, and returns a complex number otherwise. You can make an operator `O` hermitian by using `Hermitian(O)`. +Note that `ψ` can also be given as a list of [`QuantumObject`](@ref), it returns a list of expectation values. + # Examples ``` @@ -77,20 +79,33 @@ function expect( ) where {TF<:Number,TR<:Real,T2} return real(tr(O * ρ)) end +function expect( + O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, + ρ::Vector{<:QuantumObject}, +) where {T1} + _expect = _ρ -> expect(O, _ρ) + return _expect.(ρ) +end @doc raw""" - variance(O::QuantumObject, ψ::QuantumObject) + variance(O::QuantumObject, ψ::Union{QuantumObject,Vector{QuantumObject}}) Variance of the [`Operator`](@ref) `O`: ``\langle\hat{O}^2\rangle - \langle\hat{O}\rangle^2``, where ``\langle\hat{O}\rangle`` is the expectation value of `O` with the state `ψ` (see also [`expect`](@ref)), and the state `ψ` can be a [`Ket`](@ref), [`Bra`](@ref) or [`Operator`](@ref). The function returns a real number if `O` is hermitian, and returns a complex number otherwise. + +Note that `ψ` can also be given as a list of [`QuantumObject`](@ref), it returns a list of expectation values. """ variance( O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ::QuantumObject{<:AbstractArray{T2}}, ) where {T1,T2} = expect(O^2, ψ) - expect(O, ψ)^2 +variance( + O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, + ψ::Vector{<:QuantumObject}, +) where {T1} = expect(O^2, ψ) .- expect(O, ψ).^2 @doc raw""" sparse_to_dense(A::QuantumObject) diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 61945013b..62d761eea 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -25,6 +25,7 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r ```math \mathcal{O} \left(\hat{A}\right) \left[ \hat{\rho} \right] = \hat{\mathbb{1}} \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle ``` +(see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. @@ -42,6 +43,7 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r ```math \mathcal{O} \left(\hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{\mathbb{1}} ~ |\hat{\rho}\rangle\rangle ``` +(see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. @@ -59,6 +61,7 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r ```math \mathcal{O} \left(\hat{A}, \hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle = \textrm{spre}(A) * \textrm{spost}(B) ~ |\hat{\rho}\rangle\rangle ``` +(see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) See also [`spre`](@ref) and [`spost`](@ref). """ diff --git a/test/quantum_objects.jl b/test/quantum_objects.jl index e61ff9964..cbf546673 100644 --- a/test/quantum_objects.jl +++ b/test/quantum_objects.jl @@ -384,6 +384,11 @@ @test expect(a, ρ) ≈ tr(a * ρ) @test variance(a, ρ) ≈ tr(a^2 * ρ) - tr(a * ρ)^2 + # when input is a vector of states + xlist = [1.0, 1.0im, -1.0, -1.0im] + ψlist = [normalize!(basis(N, 4) + x * basis(N, 3)) for x in xlist] + @test all(expect(a', ψlist) .≈ xlist) + @testset "Type Inference (expect)" begin @inferred expect(a, ψ) @inferred expect(a, ψ') @@ -391,6 +396,8 @@ @inferred variance(a, ψ') @inferred expect(a, ρ) @inferred variance(a, ρ) + @inferred expect(a, ψlist) + @inferred variance(a, ψlist) end end From f58de01caa4186f5c975f9519ed24ad81546e822 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Sun, 15 Sep 2024 21:00:21 +0800 Subject: [PATCH 003/329] add new section to docs --- docs/make.jl | 2 +- docs/src/index.md | 3 - docs/src/{users_guide => }/type_stability.md | 0 .../QuantumObject/QuantumObject.md | 5 +- docs/src/users_guide/extensions/cuda.md | 3 - docs/src/users_guide/states_and_operators.md | 406 +++++++++++++++++- docs/src/users_guide/tensor.md | 2 +- 7 files changed, 404 insertions(+), 17 deletions(-) rename docs/src/{users_guide => }/type_stability.md (100%) diff --git a/docs/make.jl b/docs/make.jl index fccd116db..9255bca50 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -21,6 +21,7 @@ const PAGES = [ "Getting Started" => [ "Introduction" => "index.md", "Key differences from QuTiP" => "qutip_differences.md", + "The Importance of Type-Stability" => "type_stability.md", # "Cite QuantumToolbox.jl" => "cite.md", ], "Users Guide" => [ @@ -28,7 +29,6 @@ const PAGES = [ "users_guide/QuantumObject/QuantumObject.md", "users_guide/QuantumObject/QuantumObject_functions.md", ], - "The Importance of Type-Stability" => "users_guide/type_stability.md", "Manipulating States and Operators" => "users_guide/states_and_operators.md", "Tensor Products and Partial Traces" => "users_guide/tensor.md", "Time Evolution and Dynamics" => [ diff --git a/docs/src/index.md b/docs/src/index.md index 149df2998..4d1642c6f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -97,9 +97,6 @@ We can extract the expectation value of the number operator ``\hat{a}^\dagger \h We can easily pass the computation to the GPU, by simply passing all the `Qobj`s to the GPU: -!!! compat "Compat" - The described feature requires `Julia 1.9+`. See [CUDA extension](@ref doc:CUDA) for more details. - ```julia using QuantumToolbox using CUDA diff --git a/docs/src/users_guide/type_stability.md b/docs/src/type_stability.md similarity index 100% rename from docs/src/users_guide/type_stability.md rename to docs/src/type_stability.md diff --git a/docs/src/users_guide/QuantumObject/QuantumObject.md b/docs/src/users_guide/QuantumObject/QuantumObject.md index 61e9569ff..01a86914f 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject.md @@ -14,6 +14,9 @@ The key difference between classical and quantum mechanics is the use of operato - `CUDA.CUSPARSE.CuSparseMatrixCSR` (sparse GPU matrix) - and even more ... +!!! note "Support for GPU arrays" + See [CUDA extension](@ref doc:CUDA) for more details. + We can create a [`QuantumObject`](@ref) with a user defined data set by passing an array of data into the [`QuantumObject`](@ref): ```@setup Qobj @@ -53,7 +56,7 @@ Qobj(rand(4, 4), type = SuperOperator) ``` !!! note "Difference between `dims` and `size`" - Notice that `type`, `dims`, and `size` will change according to the input `data`. Although `dims` and `size` appear to be the same, `dims` keep tracking the dimension of individual Hilbert spaces of a multipartite system, while `size` does not. We refer the reader to the section [tensor products and partial traces](@ref doc:Tensor-products) for more information. + Notice that `type`, `dims`, and `size` will change according to the input `data`. Although `dims` and `size` appear to be the same, `dims` keep tracking the dimension of individual Hilbert spaces of a multipartite system, while `size` does not. We refer the reader to the section [Tensor Products and Partial Traces](@ref doc:Tensor-products-and-Partial-Traces) for more information. ## States and operators diff --git a/docs/src/users_guide/extensions/cuda.md b/docs/src/users_guide/extensions/cuda.md index e40c90915..11290c6f7 100644 --- a/docs/src/users_guide/extensions/cuda.md +++ b/docs/src/users_guide/extensions/cuda.md @@ -4,9 +4,6 @@ This is an extension to support `QuantumObject.data` conversion from standard dense and sparse CPU arrays to GPU ([`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl)) arrays. -!!! note "Requirements" - The [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl) extension for `QuantumToolbox.jl` requires `Julia 1.9+`. - This extension will be automatically loaded if user imports both `QuantumToolbox` and [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl): ```julia diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index 565aece24..afed566d4 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -1,17 +1,407 @@ # [States and Operators](@id doc:States-and-Operators) -This page is still under construction, please visit [API](@ref doc-API) first. - ## Introduction +In the previous guide section [Basic Operations on Quantum Objects](@ref doc:Qobj), we saw how to create states and operators, using the functions built into `QuantumToolbox`. In this portion of the guide, we will look at performing basic operations with states and operators. For more detailed demonstrations on how to use and manipulate these objects, see the examples given in the tutorial section. + +```@setup states_and_operators +using QuantumToolbox +``` + +## [State Vectors (kets or bras)](@id doc:State-vectors) +Here we begin by creating a Fock [`basis`](@ref) (or [`fock`](@ref)) vacuum state vector ``|0\rangle`` with in a Hilbert space with `5` number states, from `0` to `4`: + +```@example states_and_operators +vac = basis(5, 0) +``` + +and then create a lowering operator ``\hat{a}`` corresponding to `5` number states using the [`destroy`](@ref) function: + +```@example states_and_operators +a = destroy(5) +``` + +Now lets apply the lowering operator `a` to our vacuum state `vac`: + +```@example states_and_operators +a * vac +``` + +We see that, as expected, the vacuum is transformed to the zero vector. A more interesting example comes from using the `adjoint` of the lowering operator ``\hat{a}``, the raising operator ``\hat{a}^\dagger``: + +```@example states_and_operators +a' * vac +``` + +The raising operator has in indeed raised the state `vac` from the vacuum to the ``|1\rangle`` state. Instead of using the `adjoint` method to raise the state, we could have also used the built-in [`create`](@ref) function to make a raising operator: + +```@example states_and_operators +ad = create(5) +ad * vac +``` + +which does the same thing. We can raise the vacuum state more than once by successively apply the raising operator: + +```@example states_and_operators +ad * ad * vac +``` + +or just taking the square of the raising operator ``\left(\hat{a}^\dagger\right)^2``: + +```@example states_and_operators +ad^2 * vac +``` + +Applying the raising operator twice gives the expected ``\sqrt{n+1}`` dependence. We can use the product of ``a^\dagger a`` to also apply the number operator to the state vector `vac`: + +```@example states_and_operators +ad * a * vac +``` + +or on the ``|1\rangle`` state: + +```@example states_and_operators +ad * a * (ad * vac) +``` + +or on the ``|2\rangle`` state: + +```@example states_and_operators +ad * a * (ad^2 * vac) +``` + +Notice how in this last example, application of the number operator does not give the expected value ``n=2``, but rather ``2\sqrt{2}``. This is because this last state is not normalized to unity as ``a^\dagger|n\rangle=\sqrt{n+1}|n+1\rangle``. Therefore, we should [`normalize`](@ref) (or use [`unit`](@ref)) our vector first: + +```@example states_and_operators +ad * a * normalize(ad^2 * vac) +``` + +Since we are giving a demonstration of using states and operators, we have done a lot more work than we should have. For example, we do not need to operate on the vacuum state to generate a higher number Fock state. Instead we can use the [`basis`](@ref) (or [`fock`](@ref)) function to directly obtain the required state: + +```@example states_and_operators +ket = basis(5, 2) +``` + +Notice how it is automatically normalized. We can also use the built in number operator [`num`](@ref): + +```@example states_and_operators +n = num(5) +``` + +Therefore, instead of `ad * a * normalize(ad^2 * vac)`, we have: + +```@example states_and_operators +n * ket +``` + +We can also create superpositions of states: + +```@example states_and_operators +ket = normalize(basis(5, 0) + basis(5, 1)) +``` + +where we have used the `normalize` function again to normalize the state. Apply the number opeartor again: + +```@example states_and_operators +n * ket +``` + +We can also create coherent states and squeezed states by applying the [`displace`](@ref) and [`squeeze`](@ref) functions to the vacuum state: + +```@example states_and_operators +vac = basis(5, 0) + +d = displace(5, 1im) + +s = squeeze(5, 0.25 + 0.25im) + +d * vac +``` + +```@example states_and_operators +d * s * vac +``` + +Of course, displacing the vacuum gives a coherent state, which can also be generated using the built in [`coherent`](@ref) function. + +## [Density matrices](@id doc:Density-matrices) + +One of the main purpose of `QuantumToolbox` is to explore the dynamics of open quantum systems, where the most general state of a system is no longer a state vector, but rather a density matrix. Since operations on density matrices operate identically to those of vectors, we will just briefly highlight creating and using these structures. + +The simplest density matrix is created by forming the outer-product ``|\psi\rangle\langle\psi|`` of a ket vector: + +```@example states_and_operators +ket = basis(5, 2) +ket * ket' +``` + +A similar task can also be accomplished via the [`fock_dm`](@ref) or [`ket2dm`](@ref) functions: + +```@example states_and_operators +fock_dm(5, 2) +``` + +```@example states_and_operators +ket2dm(ket) +``` + +If we want to create a density matrix with equal classical probability of being found in the ``|2\rangle`` or ``|4\rangle`` number states, we can do the following: + +```@example states_and_operators +0.5 * fock_dm(5, 2) + 0.5 * fock_dm(5, 4) # with fock_dm +0.5 * ket2dm(basis(5, 2)) + 0.5 * ket2dm(basis(5, 4)) # with ket2dm +``` + +There are also several other built-in functions for creating predefined density matrices, for example [`coherent_dm`](@ref) and [`thermal_dm`](@ref) which create coherent state and thermal state density matrices, respectively. + +```@example states_and_operators +coherent_dm(5, 1.25) +``` + +```@example states_and_operators +thermal_dm(5, 1.25) +``` + +`QuantumToolbox` also provides a set of distance metrics for determining how close two density matrix distributions are to each other. Included are the [`fidelity`](@ref), and trace distance ([`tracedist`](@ref)). + +```@example states_and_operators +x = coherent_dm(5, 1.25) + +y = coherent_dm(5, 1.25im) + +z = thermal_dm(5, 0.125) + +fidelity(x, y) +``` +Note that the definition of [`fidelity`](@ref) here is from **Nielsen & Chuang, "Quantum Computation and Quantum Information"**. It is the square root of the fidelity defined in **R. Jozsa, Journal of Modern Optics, 41:12, 2315 (1994)**. We also know that for two pure states, the trace distance (``T``) and the fidelity (``F``) are related by ``T = \sqrt{1-F^2}``: + +```@example states_and_operators +tracedist(x, y) ≈ sqrt(1 - (fidelity(x, y))^2) +``` + +For a pure state and a mixed state, ``1 - F \leq T`` which can also be verified: + +```@example states_and_operators +1 - fidelity(x, z) < tracedist(x, z) +``` + +## [Two-level systems (Qubits)](@id doc:Two-level-systems) + +Having spent a fair amount of time on basis states that represent harmonic oscillator states, we now move on to qubit, or two-level quantum systems (for example a spin-``1/2``). To create a state vector corresponding to a qubit system, we use the same basis, or fock, function with only two levels: + +```@example states_and_operators +spin = basis(2, 0) +``` + +Now at this point one may ask how this state is different than that of a harmonic oscillator in the vacuum state truncated to two energy levels? + +```@example states_and_operators +vac = basis(2, 0) +``` + +At this stage, there is no difference. This should not be surprising as we called the exact same function twice. The difference between the two comes from the action of the spin operators [`sigmax`](@ref), [`sigmay`](@ref), [`sigmaz`](@ref), [`sigmap`](@ref), and [`sigmam`](@ref) on these two-level states. For example, if `vac` corresponds to the vacuum state of a harmonic oscillator, then, as we have already seen, we can use the raising operator ([`create`](@ref)) to get the ``|1\rangle`` state: + +```@example states_and_operators +create(2) * vac +``` + +For a spin system, the operator analogous to the raising operator is the ``\sigma_+`` operator [`sigmap`](@ref). Applying on the spin state gives: + +```@example states_and_operators +sigmap() * spin +``` + +Now we see the difference! The [`sigmap`](@ref) operator acting on the spin state returns the zero vector. Why is this? To see what happened, let us use the [`sigmaz`](@ref) operator: + +```@example states_and_operators +sigmaz() +``` + +```@example states_and_operators +sigmaz() * spin +``` + +```@example states_and_operators +spin2 = basis(2, 1) +``` + +```@example states_and_operators +sigmaz() * spin2 +``` + +The answer is now apparent. Since the `QuantumToolbox` [`sigmaz`](@ref) function uses the standard ``Z``-basis representation of the ``\sigma_z`` spin operator, the `spin` state corresponds to the ``|\uparrow\rangle`` state of a two-level spin system while `spin2` gives the ``|\downarrow\rangle`` state. Therefore, in our previous example `sigmap() * spin`, we raised the qubit state out of the truncated two-level Hilbert space resulting in the zero state. + +While at first glance this convention might seem somewhat odd, it is in fact quite handy. For one, the spin operators remain in the conventional form. Second, this corresponds nicely with the quantum information definitions of qubit states, where the excited ``|\uparrow\rangle`` state is label as ``|0\rangle``, and the ``|\downarrow\rangle`` state by ``|1\rangle``. + +If one wants to create spin operators for higher spin systems, then the [`jmat`](@ref) function comes in handy. + +## [Expectation values](@id doc:Expectation-values) + +Some of the most important information about quantum systems comes from calculating the expectation value of operators, both Hermitian and non-Hermitian, as the state or density matrix of the system varies in time. Therefore, in this section we demonstrate the use of the [`expect`](@ref) function. To begin: + +```@example states_and_operators +vac = basis(5, 0) + +one = basis(5, 1) + +c = create(5) + +N = num(5) + +coh = coherent_dm(5, 1.0im) + +cat = normalize(basis(5, 4) + 1.0im * basis(5, 3)) + +println(expect(N, vac) ≈ 0) +println(expect(N, one) ≈ 1) +println(expect(N, coh) ≈ 0.9970555745806597) +println(expect(c, cat) ≈ 1im) +``` + +The [`expect`](@ref) function also accepts lists or arrays of state vectors or density matrices for the second input: + +```@example states_and_operators +states = [normalize(c^k * vac) for k in 0:4] + +expect(N, states) +``` + +```@example states_and_operators +cat_list = [normalize(basis(5, 4) + x * basis(5, 3)) for x in [0, 1.0im, -1.0, -1.0im]] + +expect(c, cat_list) +``` + +Notice how in this last example, all of the return values are complex numbers. This is because the expect function looks to see whether the operator is Hermitian or not. If the operator is Hermitian, then the output will always be real. In the case of non-Hermitian operators, the return values may be complex. Therefore, the expect function will return an array of complex values for non-Hermitian operators when the input is a list/array of states or density matrices. + +Of course, the expect function works for spin states and operators: + +```@example states_and_operators +up = basis(2, 0) + +dn = basis(2, 1) + +println(expect(sigmaz(), up) ≈ 1) +println(expect(sigmaz(), dn) ≈ -1) +``` + +as well as the composite objects discussed in the next section [Tensor Products and Partial Traces](@ref doc:Tensor-products-and-Partial-Traces): + +```@example states_and_operators +spin1 = basis(2, 0) + +spin2 = basis(2, 1) + +two_spins = tensor(spin1, spin2) + +sz1 = tensor(sigmaz(), qeye(2)) + +sz2 = tensor(qeye(2), sigmaz()) + +println(expect(sz1, two_spins) ≈ 1) +println(expect(sz2, two_spins) ≈ -1) +``` + +## [Superoperators and Vectorized Operators](@id doc:Superoperators-and-Vectorized-Operators) + +In addition to state vectors and density operators, `QuantumToolbox` allows for representing maps that act linearly on density operators using the Liouville supermatrix formalisms. + +This support is based on the correspondence between linear operators acting on a Hilbert space, and vectors in two copies of that Hilbert space (which is also called the Fock-Liouville space), +```math +\textrm{vec} : \mathcal{L}(\mathcal{H}) \rightarrow \mathcal{H}\otimes\mathcal{H}. +``` +Therefore, a given density matrix ``\rho`` can then be vectorized, denoted as +```math +|\rho\rangle\rangle = \textrm{vec}(\rho). +``` + +`QuantumToolbox` uses the column-stacking convention for the isomorphism between ``\mathcal{L}(\mathcal{H})`` and ``\mathcal{H}\otimes\mathcal{H}``. This isomorphism is implemented by the functions [`mat2vec`](@ref) and [`vec2mat`](@ref): + +```@example states_and_operators +rho = Qobj([1 2; 3 4]) +``` + +```@example states_and_operators +vec_rho = mat2vec(rho) +``` + +```@example states_and_operators +rho2 = vec2mat(vec_rho) +``` + +The `QuantumObject.type` attribute indicates whether a quantum object is a vector corresponding to an [`OperatorKet`](@ref), or its Hermitian conjugate [`OperatorBra`](@ref). One can also use [`isoperket`](@ref) and [`isoperbra`](@ref) to check the type: + +```@example states_and_operators +println(isoper(vec_rho)) +println(isoperket(vec_rho)) +println(isoperbra(vec_rho)) +println(isoper(vec_rho')) +println(isoperket(vec_rho')) +println(isoperbra(vec_rho')) +``` + +Because `Julia` is a column-oriented languages (like `Fortran` and `MATLAB`), in `QuantumToolbox`, we define the [`spre`](@ref) (left), [`spost`](@ref) (right), and [`sprepost`](@ref) (left-and-right) multiplication superoperators as follows: + +```math +\begin{align} +A\rho~~~ &\rightarrow \textrm{spre}(A) * \textrm{vec}(\rho) = \mathbb{1}\otimes A ~ |\rho\rangle\rangle,\notag\\ +\rho B &\rightarrow \textrm{spost}(B) * \textrm{vec}(\rho) = B^T\otimes \mathbb{1} ~ |\rho\rangle\rangle,\notag\\ +A \rho B &\rightarrow \textrm{sprepost}(A,B) * \textrm{vec}(\rho) = B^T\otimes A ~ |\rho\rangle\rangle,\notag +\end{align} +``` +where ``\mathbb{1}`` represents the identity operator with Hilbert space dimension equal to ``\rho``. + +```@example states_and_operators +A = Qobj([1 2; 3 4]) +S_A = spre(A) +``` + +```@example states_and_operators +B = Qobj([5 6; 7 8]) +S_B = spost(B) +``` + +```@example states_and_operators +S_AB = sprepost(A, B) +``` + +```@example states_and_operators +S_AB ≈ S_A * S_B ≈ S_B * S_A +``` + +One can also use [`issuper`](@ref) to check the type: + +```@example states_and_operators +println(isoper(S_AB)) +println(issuper(S_AB)) +``` + +With the above definitions, the following equality holds in `Julia`: + +```math +\textrm{vec}(A \rho B) = \textrm{spre}(A) * \textrm{spre}(B) * \textrm{vec}(\rho) = \textrm{sprepost}(A,B) * \textrm{vec}(\rho) ~~\forall~~A, B, \rho +``` + +```@example states_and_operators +N = 10 +A = Qobj(rand(ComplexF64, N, N)) +B = Qobj(rand(ComplexF64, N, N)) +ρ = rand_dm(N) # random density matrix +mat2vec(A * ρ * B) ≈ spre(A) * spost(B) * mat2vec(ρ) ≈ sprepost(A, B) * mat2vec(ρ) +``` -## [State Vectors (kets or bras)](@id doc: State vectors) +In addition, dynamical generators on this extended space, often called Liouvillian superoperators, can be created using the [`liouvillian`](@ref) function. Each of these takes a Hamiltonian along with a list of collapse operators, and returns a [`type=SuperOperator`](@ref SuperOperator) object that can be exponentiated to find the superoperator for that evolution. -## [Density matrices](@id doc: Density matrices) +```@example states_and_operators +H = 10 * sigmaz() -## [Two-level systems (qubits)](@id doc: Two-level systems) +c = destroy(2) -## [Expectation values](@id doc: Expectation values) +L = liouvillian(H, [c]) +``` -## [Superoperators and Vectorized Operators](@id doc: Superoperators and Vectorized Operators) +```@example states_and_operators +t = 0.8 +exp(L * t) +``` -## [Generating Random States and Operators](@id doc: Generating Random States and Operators) +See the section [Time Evolution and Quantum System Dynamics](@ref doc:Time-Evolution-and-Quantum-System-Dynamics) for more details. diff --git a/docs/src/users_guide/tensor.md b/docs/src/users_guide/tensor.md index 23d89e0ad..faf4ae7d4 100644 --- a/docs/src/users_guide/tensor.md +++ b/docs/src/users_guide/tensor.md @@ -1,3 +1,3 @@ -# [Tensor products](@id doc:Tensor-products) +# [Tensor Products and Partial Traces](@id doc:Tensor-products-and-Partial-Traces) This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file From eb4a3490ba19f2515a1e8f0407ddd2eb2f1baa3f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Sun, 15 Sep 2024 21:03:09 +0800 Subject: [PATCH 004/329] format files --- src/qobj/functions.jl | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 58bfd4f74..1c506aa2b 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -79,10 +79,7 @@ function expect( ) where {TF<:Number,TR<:Real,T2} return real(tr(O * ρ)) end -function expect( - O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ρ::Vector{<:QuantumObject}, -) where {T1} +function expect(O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ρ::Vector{<:QuantumObject}) where {T1} _expect = _ρ -> expect(O, _ρ) return _expect.(ρ) end @@ -102,10 +99,8 @@ variance( O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ::QuantumObject{<:AbstractArray{T2}}, ) where {T1,T2} = expect(O^2, ψ) - expect(O, ψ)^2 -variance( - O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ::Vector{<:QuantumObject}, -) where {T1} = expect(O^2, ψ) .- expect(O, ψ).^2 +variance(O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ::Vector{<:QuantumObject}) where {T1} = + expect(O^2, ψ) .- expect(O, ψ) .^ 2 @doc raw""" sparse_to_dense(A::QuantumObject) From 6fb83bca142a19856c3dcebfc7c3d14c19035adc Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Sun, 15 Sep 2024 21:05:42 +0800 Subject: [PATCH 005/329] change section title in docs --- docs/src/users_guide/states_and_operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index afed566d4..5849bf07a 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -1,4 +1,4 @@ -# [States and Operators](@id doc:States-and-Operators) +# [Manipulating States and Operators](@id doc:Manipulating-States-and-Operators) ## Introduction In the previous guide section [Basic Operations on Quantum Objects](@ref doc:Qobj), we saw how to create states and operators, using the functions built into `QuantumToolbox`. In this portion of the guide, we will look at performing basic operations with states and operators. For more detailed demonstrations on how to use and manipulate these objects, see the examples given in the tutorial section. From 02658fdbea8eebea08c6c5ce1d0f3f0a2c552554 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 16 Sep 2024 00:01:23 +0800 Subject: [PATCH 006/329] extended methods for `tensor` and `kron` --- src/qobj/functions.jl | 10 +++++++--- src/qobj/synonyms.jl | 2 +- test/quantum_objects.jl | 13 +++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 1c506aa2b..a69d8c108 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -187,11 +187,15 @@ Quantum Object: type=Operator dims=[20, 20] size=(400, 400) ishermitian= ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦ ``` """ -function LinearAlgebra.kron( +LinearAlgebra.kron( A::QuantumObject{<:AbstractArray{T1},OpType}, B::QuantumObject{<:AbstractArray{T2},OpType}, -) where {T1,T2,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} - return QuantumObject(kron(A.data, B.data), A.type, vcat(A.dims, B.dims)) +) where {T1,T2,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} = + QuantumObject(kron(A.data, B.data), A.type, vcat(A.dims, B.dims)) +LinearAlgebra.kron(A::QuantumObject) = A +function LinearAlgebra.kron(A::Vector{<:QuantumObject}) + @warn "`tensor(A)` or `kron(A)` with `A` is a `Vector` can hurt performance. Try to use `tensor(A...)` or `kron(A...)` instead." + return kron(A...) end @doc raw""" diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 5b991d5cb..9457bbb65 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -184,7 +184,7 @@ Quantum Object: type=Operator dims=[2, 2, 2] size=(8, 8) ishermitian=tru 1.0+0.0im ⋅ ⋅ ⋅ ⋅ ⋅ ``` """ -tensor(A::QuantumObject...) = kron(A...) +tensor(A...) = kron(A...) @doc raw""" ⊗(A::QuantumObject, B::QuantumObject) diff --git a/test/quantum_objects.jl b/test/quantum_objects.jl index cbf546673..64d1433f5 100644 --- a/test/quantum_objects.jl +++ b/test/quantum_objects.jl @@ -309,11 +309,24 @@ @inferred a .^ 2 @inferred a * a @inferred a * a' + @inferred kron(a) @inferred kron(a, σx) @inferred kron(a, eye(2)) end end + @testset "tensor" begin + σx = sigmax() + X3 = kron(σx, σx, σx) + @test tensor(σx) == kron(σx) + @test tensor(fill(σx, 3)...) == X3 + X_warn = @test_logs ( + :warn, + "`tensor(A)` or `kron(A)` with `A` is a `Vector` can hurt performance. Try to use `tensor(A...)` or `kron(A...)` instead.", + ) tensor(fill(σx, 3)) + @test X_warn == X3 + end + @testset "projection" begin N = 10 ψ = fock(N, 3) From c61937960c5e92f5aab5addf9a0a6dbd8a0adb1f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 16 Sep 2024 00:43:15 +0800 Subject: [PATCH 007/329] add new section to docs --- docs/src/users_guide/tensor.md | 151 ++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/docs/src/users_guide/tensor.md b/docs/src/users_guide/tensor.md index faf4ae7d4..4c7bdfbe5 100644 --- a/docs/src/users_guide/tensor.md +++ b/docs/src/users_guide/tensor.md @@ -1,3 +1,152 @@ # [Tensor Products and Partial Traces](@id doc:Tensor-products-and-Partial-Traces) -This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file +```@setup tensor_products +using QuantumToolbox +``` + +## [Tensor products](@id doc:Tensor-products) + +To describe the states of multipartite quantum systems (such as two coupled qubits, a qubit coupled to an oscillator, etc.) we need to expand the Hilbert space by taking the tensor product of the state vectors for each of the system components. Similarly, the operators acting on the state vectors in the combined Hilbert space (describing the coupled system) are formed by taking the tensor product of the individual operators. + +In `QuantumToolbox`, the function [`tensor`](@ref) (or [`kron`](@ref)) is used to accomplish this task. This function takes a collection of [`Ket`](@ref) or [`Operator`](@ref) as argument and returns a composite [`QuantumObject`](@ref) for the combined Hilbert space. The function accepts an arbitrary number of [`QuantumObject`](@ref) as argument. The `type` of returned [`QuantumObject`](@ref) is the same as that of the input(s). + +A collection of [`QuantumObject`](@ref): +```@example tensor_products +tensor(sigmax(), sigmax(), sigmax()) +``` + +or a `Vector{QuantumObject}`: + +```@example tensor_products +op_list = fill(sigmax(), 3) +tensor(op_list) +``` + +!!! warning "Beware of type-stability!" + Please note that `tensor(op_list)` or `kron(op_list)` with `op_list` is a `Vector` is type-instable and can hurt performance. It is recommended to use `tensor(op_list...)` or `kron(op_list...)` instead. See the Section [The Importance of Type-Stability](@ref doc:Type-Stability) for more details. + +For example, the state vector describing two qubits in their ground states is formed by taking the tensor product of the two single-qubit ground state vectors: + +```@example tensor_products +tensor(basis(2, 0), basis(2, 0)) +``` + +One can generalize to more qubits by adding more component state vectors in the argument list to the [`tensor`](@ref) (or [`kron`](@ref)) function, as illustrated in the following example: + +```@example tensor_products +states = QuantumObject[ + normalize(basis(2, 0) + basis(2, 1)), + normalize(basis(2, 0) + basis(2, 1)), + basis(2, 0) +] +tensor(states...) +``` +This state is slightly more complicated, describing two qubits in a superposition between the up and down states, while the third qubit is in its ground state. + +To construct operators that act on an extended Hilbert space of a combined system, we similarly pass a list of operators for each component system to the [`tensor`](@ref) (or [`kron`](@ref)) function. For example, to form the operator that represents the simultaneous action of the ``\sigma_x`` operator on two qubits: + +```@example tensor_products +tensor(sigmax(), sigmax()) +``` + +To create operators in a combined Hilbert space that only act on a single component, we take the tensor product of the operator acting on the subspace of interest, with the identity operators corresponding to the components that are to be unchanged. For example, the operator that represents ``\sigma_z`` on the first qubit in a two-qubit system, while leaving the second qubit unaffected: + +```@example tensor_products +tensor(sigmaz(), qeye(2)) +``` + +## Example: Constructing composite Hamiltonians + +The [`tensor`](@ref) (or [`kron`](@ref)) function is extensively used when constructing Hamiltonians for composite systems. Here we’ll look at some simple examples. + +### Two coupled qubits + +First, let’s consider a system of two coupled qubits. Assume that both the qubits have equal energy splitting, and that the qubits are coupled through a ``\sigma_x \otimes \sigma_x`` interaction with strength ``g = 0.05`` (in units where the bare qubit energy splitting is unity). The Hamiltonian describing this system is: + +```@example tensor_products +H = tensor(sigmaz(), qeye(2)) + + tensor(qeye(2), sigmaz()) + + 0.05 * tensor(sigmax(), sigmax()) +``` + +### Three coupled qubits + +The two-qubit example is easily generalized to three coupled qubits: + +```@example tensor_products +H = tensor(sigmaz(), qeye(2), qeye(2)) + + tensor(qeye(2), sigmaz(), qeye(2)) + + tensor(qeye(2), qeye(2), sigmaz()) + + 0.5 * tensor(sigmax(), sigmax(), qeye(2)) + + 0.25 * tensor(qeye(2), sigmax(), sigmax()) +``` + +### A two-level system coupled to a cavity: The Jaynes-Cummings model + +The simplest possible quantum mechanical description for light-matter interaction is encapsulated in the Jaynes-Cummings model, which describes the coupling between a two-level atom and a single-mode electromagnetic field (a cavity mode). Denoting the energy splitting of the atom and cavity ``\omega_a`` and ``\omega_c``, respectively, and the atom-cavity interaction strength ``g``, the Jaynes-Cummings Hamiltonian can be constructed as: + +```math +H = \frac{\omega_a}{2}\sigma_z + \omega_c a^\dagger a + g (a^\dagger \sigma_- + a \sigma_+) +``` + +```@example tensor_products +N = 6 # cavity fock space truncation +ωc = 1.25 # frequency of cavity +ωa = 1.0 # frequency of two-level atom +g = 0.75 # interaction strength + +a = tensor(qeye(2), destroy(N)) # cavity annihilation operator + +# two-level atom operators +σm = tensor(destroy(2), qeye(N)) +σz = tensor(sigmaz(), qeye(N)) + +H = 0.5 * ωa * σz + ωc * a' * a + g * (a' * σm + a * σm') +``` + +## [Partial trace](@id doc:Partial-trace) + +The partial trace is an operation that reduces the dimension of a Hilbert space by eliminating some degrees of freedom by averaging (tracing). In this sense it is therefore the converse of the tensor product. It is useful when one is interested in only a part of a coupled quantum system. For open quantum systems, this typically involves tracing over the environment leaving only the system of interest. In `QuantumToolbox` the function [`ptrace`](@ref) is used to take partial traces. [`ptrace`](@ref) takes one [`QuantumObject`](@ref) as an input, and also one argument `sel`, which marks the component systems that should be kept, and all the other components are traced out. + +Remember that the index of `Julia` starts from `1`, and all the elements in `sel` should be positive `Integer`. Therefore, the type of `sel` can be either `Integer`, `Tuple`, `SVector`, or `Vector`. + +!!! warning "Beware of type-stability!" + Although it supports also `Vector` type, it is recommended to use `Tuple` or `SVector` from [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) to improve performance. For a brief explanation on the impact of the type of `sel`, see the section [The Importance of Type-Stability](@ref doc:Type-Stability). + +For example, the density matrix describing a single qubit obtained from a coupled two-qubit system is obtained via: + +```@example tensor_products +ψ = tensor( + basis(2, 0), + basis(2, 1), + normalize(basis(2, 0) + basis(2, 1)) +) +``` + +```@example tensor_products +ptrace(ψ, 1) # trace out 2nd and 3rd systems +``` + +```@example tensor_products +ptrace(ψ, (1, 3)) # trace out 2nd system +``` + +Note that the partial trace always results in a [`Operator`](@ref) (density matrix), regardless of whether the composite system is a pure state (described by a [`Ket`](@ref)) or a mixed state (described by a [`Operator`](@ref)): + +```@example tensor_products +ψ1 = normalize(basis(2, 0) + basis(2, 1)) +ψ2 = basis(2, 0) +ψT = tensor(ψ1, ψ2) +``` + +```@example tensor_products +ptrace(ψT, 1) +``` + +```@example tensor_products +ρT = tensor(ket2dm(ψ1), ket2dm(ψ1)) +``` + +```@example tensor_products +ptrace(ρT, 1) +``` From 6edd193372d8c7c9799b2ddca28ec06b6b78a0b8 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:17:47 +0800 Subject: [PATCH 008/329] add `hat` to all docs and make `fcreate` and `fdestroy` compat with `qutip` (#226) --- docs/src/users_guide/states_and_operators.md | 30 +++++------ docs/src/users_guide/tensor.md | 8 +-- src/metrics.jl | 4 +- src/negativity.jl | 6 +-- src/qobj/arithmetic_and_attributes.jl | 6 +-- src/qobj/operators.jl | 56 +++++++++++--------- src/qobj/states.jl | 8 +-- src/qobj/superoperators.jl | 2 +- src/qobj/synonyms.jl | 2 +- src/steadystate.jl | 4 +- src/time_evolution/mesolve.jl | 8 +-- test/states_and_operators.jl | 19 +++++-- 12 files changed, 85 insertions(+), 68 deletions(-) diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index 5849bf07a..b836c3489 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -20,7 +20,7 @@ and then create a lowering operator ``\hat{a}`` corresponding to `5` number stat a = destroy(5) ``` -Now lets apply the lowering operator `a` to our vacuum state `vac`: +Now lets apply the lowering operator `\hat{a}` to our vacuum state `vac`: ```@example states_and_operators a * vac @@ -51,7 +51,7 @@ or just taking the square of the raising operator ``\left(\hat{a}^\dagger\right) ad^2 * vac ``` -Applying the raising operator twice gives the expected ``\sqrt{n+1}`` dependence. We can use the product of ``a^\dagger a`` to also apply the number operator to the state vector `vac`: +Applying the raising operator twice gives the expected ``\sqrt{n+1}`` dependence. We can use the product of ``\hat{a}^\dagger \hat{a}`` to also apply the number operator to the state vector `vac`: ```@example states_and_operators ad * a * vac @@ -69,7 +69,7 @@ or on the ``|2\rangle`` state: ad * a * (ad^2 * vac) ``` -Notice how in this last example, application of the number operator does not give the expected value ``n=2``, but rather ``2\sqrt{2}``. This is because this last state is not normalized to unity as ``a^\dagger|n\rangle=\sqrt{n+1}|n+1\rangle``. Therefore, we should [`normalize`](@ref) (or use [`unit`](@ref)) our vector first: +Notice how in this last example, application of the number operator does not give the expected value ``n=2``, but rather ``2\sqrt{2}``. This is because this last state is not normalized to unity as ``\hat{a}^\dagger|n\rangle=\sqrt{n+1}|n+1\rangle``. Therefore, we should [`normalize`](@ref) (or use [`unit`](@ref)) our vector first: ```@example states_and_operators ad * a * normalize(ad^2 * vac) @@ -204,13 +204,13 @@ At this stage, there is no difference. This should not be surprising as we calle create(2) * vac ``` -For a spin system, the operator analogous to the raising operator is the ``\sigma_+`` operator [`sigmap`](@ref). Applying on the spin state gives: +For a spin system, the operator analogous to the raising operator is the ``\hat{\sigma}_+`` operator [`sigmap`](@ref). Applying on the spin state gives: ```@example states_and_operators sigmap() * spin ``` -Now we see the difference! The [`sigmap`](@ref) operator acting on the spin state returns the zero vector. Why is this? To see what happened, let us use the [`sigmaz`](@ref) operator: +Now we see the difference! The [`sigmap`](@ref) operator acting on the spin state returns the zero vector. Why is this? To see what happened, let us use the ``\hat{\sigma}_z`` ([`sigmaz`](@ref)) operator: ```@example states_and_operators sigmaz() @@ -228,7 +228,7 @@ spin2 = basis(2, 1) sigmaz() * spin2 ``` -The answer is now apparent. Since the `QuantumToolbox` [`sigmaz`](@ref) function uses the standard ``Z``-basis representation of the ``\sigma_z`` spin operator, the `spin` state corresponds to the ``|\uparrow\rangle`` state of a two-level spin system while `spin2` gives the ``|\downarrow\rangle`` state. Therefore, in our previous example `sigmap() * spin`, we raised the qubit state out of the truncated two-level Hilbert space resulting in the zero state. +The answer is now apparent. Since the `QuantumToolbox` [`sigmaz`](@ref) function uses the standard ``Z``-basis representation of the ``\hat{\sigma}_z`` spin operator, the `spin` state corresponds to the ``|\uparrow\rangle`` state of a two-level spin system while `spin2` gives the ``|\downarrow\rangle`` state. Therefore, in our previous example `sigmap() * spin`, we raised the qubit state out of the truncated two-level Hilbert space resulting in the zero state. While at first glance this convention might seem somewhat odd, it is in fact quite handy. For one, the spin operators remain in the conventional form. Second, this corresponds nicely with the quantum information definitions of qubit states, where the excited ``|\uparrow\rangle`` state is label as ``|0\rangle``, and the ``|\downarrow\rangle`` state by ``|1\rangle``. @@ -309,9 +309,9 @@ This support is based on the correspondence between linear operators acting on a ```math \textrm{vec} : \mathcal{L}(\mathcal{H}) \rightarrow \mathcal{H}\otimes\mathcal{H}. ``` -Therefore, a given density matrix ``\rho`` can then be vectorized, denoted as +Therefore, a given density matrix ``\hat{\rho}`` can then be vectorized, denoted as ```math -|\rho\rangle\rangle = \textrm{vec}(\rho). +|\hat{\rho}\rangle\rangle = \textrm{vec}(\hat{\rho}). ``` `QuantumToolbox` uses the column-stacking convention for the isomorphism between ``\mathcal{L}(\mathcal{H})`` and ``\mathcal{H}\otimes\mathcal{H}``. This isomorphism is implemented by the functions [`mat2vec`](@ref) and [`vec2mat`](@ref): @@ -328,7 +328,7 @@ vec_rho = mat2vec(rho) rho2 = vec2mat(vec_rho) ``` -The `QuantumObject.type` attribute indicates whether a quantum object is a vector corresponding to an [`OperatorKet`](@ref), or its Hermitian conjugate [`OperatorBra`](@ref). One can also use [`isoperket`](@ref) and [`isoperbra`](@ref) to check the type: +The `QuantumObject.type` attribute indicates whether a quantum object is a vector corresponding to an [`OperatorKet`](@ref), or its Hermitian conjugate [`OperatorBra`](@ref). One can also use [`isoper`](@ref), [`isoperket`](@ref), and [`isoperbra`](@ref) to check the type: ```@example states_and_operators println(isoper(vec_rho)) @@ -343,12 +343,12 @@ Because `Julia` is a column-oriented languages (like `Fortran` and `MATLAB`), in ```math \begin{align} -A\rho~~~ &\rightarrow \textrm{spre}(A) * \textrm{vec}(\rho) = \mathbb{1}\otimes A ~ |\rho\rangle\rangle,\notag\\ -\rho B &\rightarrow \textrm{spost}(B) * \textrm{vec}(\rho) = B^T\otimes \mathbb{1} ~ |\rho\rangle\rangle,\notag\\ -A \rho B &\rightarrow \textrm{sprepost}(A,B) * \textrm{vec}(\rho) = B^T\otimes A ~ |\rho\rangle\rangle,\notag +\hat{A}\hat{\rho}~~~ &\rightarrow \textrm{spre}(\hat{A}) * \textrm{vec}(\hat{\rho}) = \hat{\mathbb{1}}\otimes \hat{A} ~ |\hat{\rho}\rangle\rangle,\notag\\ +\hat{\rho} \hat{B} &\rightarrow \textrm{spost}(\hat{B}) * \textrm{vec}(\hat{\rho}) = \hat{B}^T\otimes \hat{\mathbb{1}} ~ |\hat{\rho}\rangle\rangle,\notag\\ +\hat{A} \hat{\rho} \hat{B} &\rightarrow \textrm{sprepost}(\hat{A},\hat{B}) * \textrm{vec}(\hat{\rho}) = \hat{B}^T\otimes \hat{A} ~ |\hat{\rho}\rangle\rangle,\notag \end{align} ``` -where ``\mathbb{1}`` represents the identity operator with Hilbert space dimension equal to ``\rho``. +where ``\hat{\mathbb{1}}`` represents the identity operator with Hilbert space dimension equal to ``\hat{\rho}``. ```@example states_and_operators A = Qobj([1 2; 3 4]) @@ -375,10 +375,10 @@ println(isoper(S_AB)) println(issuper(S_AB)) ``` -With the above definitions, the following equality holds in `Julia`: +With the above definitions, the following equalities hold in `Julia`: ```math -\textrm{vec}(A \rho B) = \textrm{spre}(A) * \textrm{spre}(B) * \textrm{vec}(\rho) = \textrm{sprepost}(A,B) * \textrm{vec}(\rho) ~~\forall~~A, B, \rho +\textrm{vec}(\hat{A} \hat{\rho} \hat{B}) = \textrm{spre}(\hat{A}) * \textrm{spre}(\hat{B}) * \textrm{vec}(\hat{\rho}) = \textrm{sprepost}(\hat{A},\hat{B}) * \textrm{vec}(\hat{\rho}) ~~\forall~~\hat{A}, \hat{B}, \hat{\rho} ``` ```@example states_and_operators diff --git a/docs/src/users_guide/tensor.md b/docs/src/users_guide/tensor.md index 4c7bdfbe5..61e125358 100644 --- a/docs/src/users_guide/tensor.md +++ b/docs/src/users_guide/tensor.md @@ -43,13 +43,13 @@ tensor(states...) ``` This state is slightly more complicated, describing two qubits in a superposition between the up and down states, while the third qubit is in its ground state. -To construct operators that act on an extended Hilbert space of a combined system, we similarly pass a list of operators for each component system to the [`tensor`](@ref) (or [`kron`](@ref)) function. For example, to form the operator that represents the simultaneous action of the ``\sigma_x`` operator on two qubits: +To construct operators that act on an extended Hilbert space of a combined system, we similarly pass a list of operators for each component system to the [`tensor`](@ref) (or [`kron`](@ref)) function. For example, to form the operator that represents the simultaneous action of the ``\hat{\sigma}_x`` operator on two qubits: ```@example tensor_products tensor(sigmax(), sigmax()) ``` -To create operators in a combined Hilbert space that only act on a single component, we take the tensor product of the operator acting on the subspace of interest, with the identity operators corresponding to the components that are to be unchanged. For example, the operator that represents ``\sigma_z`` on the first qubit in a two-qubit system, while leaving the second qubit unaffected: +To create operators in a combined Hilbert space that only act on a single component, we take the tensor product of the operator acting on the subspace of interest, with the identity operators corresponding to the components that are to be unchanged. For example, the operator that represents ``\hat{\sigma}_z`` on the first qubit in a two-qubit system, while leaving the second qubit unaffected: ```@example tensor_products tensor(sigmaz(), qeye(2)) @@ -61,7 +61,7 @@ The [`tensor`](@ref) (or [`kron`](@ref)) function is extensively used when const ### Two coupled qubits -First, let’s consider a system of two coupled qubits. Assume that both the qubits have equal energy splitting, and that the qubits are coupled through a ``\sigma_x \otimes \sigma_x`` interaction with strength ``g = 0.05`` (in units where the bare qubit energy splitting is unity). The Hamiltonian describing this system is: +First, let’s consider a system of two coupled qubits. Assume that both the qubits have equal energy splitting, and that the qubits are coupled through a ``\hat{\sigma}_x \otimes \hat{\sigma}_x`` interaction with strength ``g = 0.05`` (in units where the bare qubit energy splitting is unity). The Hamiltonian describing this system is: ```@example tensor_products H = tensor(sigmaz(), qeye(2)) + @@ -86,7 +86,7 @@ H = tensor(sigmaz(), qeye(2), qeye(2)) + The simplest possible quantum mechanical description for light-matter interaction is encapsulated in the Jaynes-Cummings model, which describes the coupling between a two-level atom and a single-mode electromagnetic field (a cavity mode). Denoting the energy splitting of the atom and cavity ``\omega_a`` and ``\omega_c``, respectively, and the atom-cavity interaction strength ``g``, the Jaynes-Cummings Hamiltonian can be constructed as: ```math -H = \frac{\omega_a}{2}\sigma_z + \omega_c a^\dagger a + g (a^\dagger \sigma_- + a \sigma_+) +H = \frac{\omega_a}{2}\hat{\sigma}_z + \omega_c \hat{a}^\dagger \hat{a} + g (\hat{a}^\dagger \hat{\sigma}_- + \hat{a} \hat{\sigma}_+) ``` ```@example tensor_products diff --git a/src/metrics.jl b/src/metrics.jl index d257d24ba..d8fd4f5ef 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -82,7 +82,7 @@ entanglement(QO::QuantumObject, sel::Int) = entanglement(QO, (sel,)) tracedist(ρ::QuantumObject, σ::QuantumObject) Calculates the [trace distance](https://en.wikipedia.org/wiki/Trace_distance) between two [`QuantumObject`](@ref): -``T(\rho, \sigma) = \frac{1}{2} \lVert \rho - \sigma \rVert_1`` +``T(\hat{\rho}, \hat{\sigma}) = \frac{1}{2} \lVert \hat{\rho} - \hat{\sigma} \rVert_1`` Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). """ @@ -100,7 +100,7 @@ tracedist( fidelity(ρ::QuantumObject, σ::QuantumObject) Calculate the fidelity of two [`QuantumObject`](@ref): -``F(\rho, \sigma) = \textrm{Tr} \sqrt{\sqrt{\rho} \sigma \sqrt{\rho}}`` +``F(\hat{\rho}, \hat{\sigma}) = \textrm{Tr} \sqrt{\sqrt{\hat{\rho}} \hat{\sigma} \sqrt{\hat{\rho}}}`` Here, the definition is from Nielsen & Chuang, "Quantum Computation and Quantum Information". It is the square root of the fidelity defined in R. Jozsa, Journal of Modern Optics, 41:12, 2315 (1994). diff --git a/src/negativity.jl b/src/negativity.jl index e3669e383..35d91c75f 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -3,9 +3,9 @@ export negativity, partial_transpose @doc raw""" negativity(ρ::QuantumObject, subsys::Int; logarithmic::Bool=false) -Compute the [negativity](https://en.wikipedia.org/wiki/Negativity_(quantum_mechanics)) ``N(\rho) = \frac{\Vert \rho^{\Gamma}\Vert_1 - 1}{2}`` -where ``\rho^{\Gamma}`` is the partial transpose of ``\rho`` with respect to the subsystem, -and ``\Vert X \Vert_1=\textrm{Tr}\sqrt{X^\dagger X}`` is the trace norm. +Compute the [negativity](https://en.wikipedia.org/wiki/Negativity_(quantum_mechanics)) ``N(\hat{\rho}) = \frac{\Vert \hat{\rho}^{\Gamma}\Vert_1 - 1}{2}`` +where ``\hat{\rho}^{\Gamma}`` is the partial transpose of ``\hat{\rho}`` with respect to the subsystem, +and ``\Vert \hat{X} \Vert_1=\textrm{Tr}\sqrt{\hat{X}^\dagger \hat{X}}`` is the trace norm. # Arguments - `ρ::QuantumObject`: The density matrix (`ρ.type` must be [`OperatorQuantumObject`](@ref)). diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 4ba7d4bc2..52bfe115b 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -135,7 +135,7 @@ end @doc raw""" dot(i::QuantumObject, A::QuantumObject j::QuantumObject) -Compute the generalized dot product `dot(i, A*j)` between three [`QuantumObject`](@ref): ``\langle i | A | j \rangle`` +Compute the generalized dot product `dot(i, A*j)` between three [`QuantumObject`](@ref): ``\langle i | \hat{A} | j \rangle`` Supports the following inputs: - `A` is in the type of [`Operator`](@ref), with `i` and `j` are both [`Ket`](@ref). @@ -638,7 +638,7 @@ get_data(A::QuantumObject) = A.data Get the coherence value ``\alpha`` by measuring the expectation value of the destruction operator ``\hat{a}`` on a state ``\ket{\psi}`` or a density matrix ``\hat{\rho}``. -It returns both ``\alpha`` and the corresponding state with the coherence removed: ``\ket{\delta_\alpha} = \exp ( \bar{\alpha} \hat{a} - \alpha \hat{a}^\dagger ) \ket{\psi}`` for a pure state, and ``\hat{\rho_\alpha} = \exp ( \bar{\alpha} \hat{a} - \alpha \hat{a}^\dagger ) \hat{\rho} \exp ( -\bar{\alpha} \hat{a} + \alpha \hat{a}^\dagger )`` for a density matrix. These states correspond to the quantum fluctuations around the coherent state ``\ket{\alpha}`` or ``\dyad{\alpha}``. +It returns both ``\alpha`` and the corresponding state with the coherence removed: ``\ket{\delta_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \ket{\psi}`` for a pure state, and ``\hat{\rho_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \hat{\rho} \exp ( -\bar{\alpha} \hat{a} + \alpha \hat{a}^\dagger )`` for a density matrix. These states correspond to the quantum fluctuations around the coherent state ``\ket{\alpha}`` or ``|\alpha\rangle\langle\alpha|``. """ function get_coherence(ψ::QuantumObject{<:AbstractArray,KetQuantumObject}) a = destroy(prod(ψ.dims)) @@ -665,7 +665,7 @@ Note that this method currently works for [`Ket`](@ref), [`Bra`](@ref), and [`Op # Examples -If `order = [2, 1, 3]`, the Hilbert space structure will be re-arranged: H₁ ⊗ H₂ ⊗ H₃ → H₂ ⊗ H₁ ⊗ H₃. +If `order = [2, 1, 3]`, the Hilbert space structure will be re-arranged: ``\mathcal{H}_1 \otimes \mathcal{H}_2 \otimes \mathcal{H}_3 \rightarrow \mathcal{H}_2 \otimes \mathcal{H}_1 \otimes \mathcal{H}_3``. ``` julia> ψ1 = fock(2, 0) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index cf934fe5e..008f29435 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -23,7 +23,7 @@ The `dimensions` can be either the following types: The `distribution` specifies which of the method used to obtain the unitary matrix: - `:haar`: Haar random unitary matrix using the algorithm from reference 1 -- `:exp`: Uses ``\exp(-iH)``, where ``H`` is a randomly generated Hermitian operator. +- `:exp`: Uses ``\exp(-i\hat{H})``, where ``\hat{H}`` is a randomly generated Hermitian operator. # References 1. [F. Mezzadri, How to generate random matrices from the classical compact groups, arXiv:math-ph/0609050 (2007)](https://arxiv.org/abs/math-ph/0609050) @@ -68,8 +68,8 @@ rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{T}) where {T} = commutator(A::QuantumObject, B::QuantumObject; anti::Bool=false) Return the commutator (or `anti`-commutator) of the two [`QuantumObject`](@ref): -- commutator (`anti=false`): ``AB-BA`` -- anticommutator (`anti=true`): ``AB+BA`` +- commutator (`anti=false`): ``\hat{A}\hat{B}-\hat{B}\hat{A}`` +- anticommutator (`anti=true`): ``\hat{A}\hat{B}+\hat{B}\hat{A}`` Note that `A` and `B` must be [`Operator`](@ref) """ @@ -233,13 +233,13 @@ end Generate higher-order Spin-`j` operators, where `j` is the spin quantum number and can be a non-negative integer or half-integer The parameter `which` specifies which of the following operator to return. -- `:x`: ``S_x`` -- `:y`: ``S_y`` -- `:z`: ``S_z`` -- `:+`: ``S_+`` -- `:-`: ``S_-`` +- `:x`: ``\hat{S}_x`` +- `:y`: ``\hat{S}_y`` +- `:z`: ``\hat{S}_z`` +- `:+`: ``\hat{S}_+`` +- `:-`: ``\hat{S}_-`` -Note that if the parameter `which` is not specified, returns a set of Spin-`j` operators: ``(S_x, S_y, S_z)`` +Note that if the parameter `which` is not specified, returns a set of Spin-`j` operators: ``(\hat{S}_x, \hat{S}_y, \hat{S}_z)`` # Examples ``` @@ -318,7 +318,7 @@ _jz(j::Real) = spdiagm(0 => Array{ComplexF64}(j .- (0:Int(2 * j)))) @doc raw""" spin_Jx(j::Real) -``S_x`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer +``\hat{S}_x`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer. See also [`jmat`](@ref). """ @@ -327,7 +327,7 @@ spin_Jx(j::Real) = jmat(j, Val(:x)) @doc raw""" spin_Jy(j::Real) -``S_y`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer +``\hat{S}_y`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer. See also [`jmat`](@ref). """ @@ -336,7 +336,7 @@ spin_Jy(j::Real) = jmat(j, Val(:y)) @doc raw""" spin_Jz(j::Real) -``S_z`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer +``\hat{S}_z`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer. See also [`jmat`](@ref). """ @@ -345,7 +345,7 @@ spin_Jz(j::Real) = jmat(j, Val(:z)) @doc raw""" spin_Jm(j::Real) -``S_-`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer +``\hat{S}_-`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer. See also [`jmat`](@ref). """ @@ -354,7 +354,7 @@ spin_Jm(j::Real) = jmat(j, Val(:-)) @doc raw""" spin_Jp(j::Real) -``S_+`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer +``\hat{S}_+`` operator for Spin-`j`, where `j` is the spin quantum number and can be a non-negative integer or half-integer. See also [`jmat`](@ref). """ @@ -363,7 +363,7 @@ spin_Jp(j::Real) = jmat(j, Val(:+)) @doc raw""" spin_J_set(j::Real) -A set of Spin-`j` operators ``(S_x, S_y, S_z)``, where `j` is the spin quantum number and can be a non-negative integer or half-integer +A set of Spin-`j` operators ``(\hat{S}_x, \hat{S}_y, \hat{S}_z)``, where `j` is the spin quantum number and can be a non-negative integer or half-integer. Note that this functions is same as `jmat(j)`. See also [`jmat`](@ref). """ @@ -372,7 +372,7 @@ spin_J_set(j::Real) = jmat(j) @doc raw""" sigmap() -Pauli ladder operator ``\hat{\sigma}_+ = \hat{\sigma}_x + i \hat{\sigma}_y``. +Pauli ladder operator ``\hat{\sigma}_+ = (\hat{\sigma}_x + i \hat{\sigma}_y) / 2``. See also [`jmat`](@ref). """ @@ -381,7 +381,7 @@ sigmap() = jmat(0.5, Val(:+)) @doc raw""" sigmam() -Pauli ladder operator ``\hat{\sigma}_- = \hat{\sigma}_x - i \hat{\sigma}_y``. +Pauli ladder operator ``\hat{\sigma}_- = (\hat{\sigma}_x - i \hat{\sigma}_y) / 2``. See also [`jmat`](@ref). """ @@ -437,15 +437,17 @@ Construct a fermionic destruction operator acting on the `j`-th site, where the Here, we use the [Jordan-Wigner transformation](https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation), namely ```math -d_j = \sigma_z^{\otimes j} \otimes \sigma_{-} \otimes I^{\otimes N-j-1} +\hat{d}_j = \hat{\sigma}_z^{\otimes j-1} \otimes \hat{\sigma}_{+} \otimes \hat{\mathbb{1}}^{\otimes N-j} ``` -Note that the site index `j` should satisfy: `0 ≤ j ≤ N - 1`. +The site index `j` should satisfy: `1 ≤ j ≤ N`. + +Note that we put ``\hat{\sigma}_{+} = \begin{pmatrix} 0 & 1 \\ 0 & 0 \end{pmatrix}`` here because we consider ``|0\rangle = \begin{pmatrix} 1 \\ 0 \end{pmatrix}`` to be ground (vacant) state, and ``|1\rangle = \begin{pmatrix} 0 \\ 1 \end{pmatrix}`` to be excited (occupied) state. !!! warning "Beware of type-stability!" If you want to keep type stability, it is recommended to use `fdestroy(Val(N), j)` instead of `fdestroy(N, j)`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ -fdestroy(N::Union{Int,Val}, j::Int) = _Jordan_Wigner(N, j, sigmam()) +fdestroy(N::Union{Int,Val}, j::Int) = _Jordan_Wigner(N, j, sigmap()) @doc raw""" fcreate(N::Union{Int,Val}, j::Int) @@ -454,27 +456,29 @@ Construct a fermionic creation operator acting on the `j`-th site, where the foc Here, we use the [Jordan-Wigner transformation](https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation), namely ```math -d_j^\dagger = \sigma_z^{\otimes j} \otimes \sigma_{+} \otimes I^{\otimes N-j-1} +\hat{d}^\dagger_j = \hat{\sigma}_z^{\otimes j-1} \otimes \hat{\sigma}_{-} \otimes \hat{\mathbb{1}}^{\otimes N-j} ``` -Note that the site index `j` should satisfy: `0 ≤ j ≤ N - 1`. +The site index `j` should satisfy: `1 ≤ j ≤ N`. + +Note that we put ``\hat{\sigma}_{-} = \begin{pmatrix} 0 & 0 \\ 1 & 0 \end{pmatrix}`` here because we consider ``|0\rangle = \begin{pmatrix} 1 \\ 0 \end{pmatrix}`` to be ground (vacant) state, and ``|1\rangle = \begin{pmatrix} 0 \\ 1 \end{pmatrix}`` to be excited (occupied) state. !!! warning "Beware of type-stability!" If you want to keep type stability, it is recommended to use `fcreate(Val(N), j)` instead of `fcreate(N, j)`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ -fcreate(N::Union{Int,Val}, j::Int) = _Jordan_Wigner(N, j, sigmap()) +fcreate(N::Union{Int,Val}, j::Int) = _Jordan_Wigner(N, j, sigmam()) _Jordan_Wigner(N::Int, j::Int, op::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = _Jordan_Wigner(Val(N), j, op) function _Jordan_Wigner(::Val{N}, j::Int, op::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {N,T} (N < 1) && throw(ArgumentError("The total number of sites (N) cannot be less than 1")) - ((j >= N) || (j < 0)) && throw(ArgumentError("The site index (j) should satisfy: 0 ≤ j ≤ N - 1")) + ((j > N) || (j < 1)) && throw(ArgumentError("The site index (j) should satisfy: 1 ≤ j ≤ N")) σz = sigmaz().data - Z_tensor = kron(1, 1, fill(σz, j)...) + Z_tensor = kron(1, 1, fill(σz, j - 1)...) - S = 2^(N - j - 1) + S = 2^(N - j) I_tensor = sparse((1.0 + 0.0im) * LinearAlgebra.I, S, S) return QuantumObject(kron(Z_tensor, op.data, I_tensor); type = Operator, dims = ntuple(i -> 2, Val(N))) diff --git a/src/qobj/states.jl b/src/qobj/states.jl index 04a3cdccd..df01f81ce 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -190,7 +190,7 @@ end Generate the spin state: ``|j, m\rangle`` -The eigenstate of the Spin-`j` ``S_z`` operator with eigenvalue `m`, where where `j` is the spin quantum number and can be a non-negative integer or half-integer +The eigenstate of the Spin-`j` ``\hat{S}_z`` operator with eigenvalue `m`, where where `j` is the spin quantum number and can be a non-negative integer or half-integer See also [`jmat`](@ref). """ @@ -213,15 +213,17 @@ end Generate the coherent spin state (rotation of the ``|j, j\rangle`` state), namely ```math -|\theta, \phi \rangle = R(\theta, \phi) |j, j\rangle +|\theta, \phi \rangle = \hat{R}(\theta, \phi) |j, j\rangle ``` where the rotation operator is defined as ```math -R(\theta, \phi) = \exp \left( \frac{\theta}{2} (S_- e^{i\phi} - S_+ e^{-i\phi}) \right) +\hat{R}(\theta, \phi) = \exp \left( \frac{\theta}{2} (\hat{S}_- e^{i\phi} - \hat{S}_+ e^{-i\phi}) \right) ``` +and ``\hat{S}_\pm`` are plus and minus Spin-`j` operators, respectively. + # Arguments - `j::Real`: The spin quantum number and can be a non-negative integer or half-integer - `θ::Real`: rotation angle from z-axis diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 62d761eea..b95c2ee2a 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -59,7 +59,7 @@ Returns the [`SuperOperator`](@ref) form of `A` and `B` acting on the left and r Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\rho}\rangle\rangle``, this [`SuperOperator`](@ref) is always a matrix ``\hat{B}^T \otimes \hat{A}``, namely ```math -\mathcal{O} \left(\hat{A}, \hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle = \textrm{spre}(A) * \textrm{spost}(B) ~ |\hat{\rho}\rangle\rangle +\mathcal{O} \left(\hat{A}, \hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle = \textrm{spre}(\hat{A}) * \textrm{spost}(\hat{B}) ~ |\hat{\rho}\rangle\rangle ``` (see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 9457bbb65..17f59e997 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -58,7 +58,7 @@ dag(A::QuantumObject{<:AbstractArray{T}}) where {T} = adjoint(A) @doc raw""" matrix_element(i::QuantumObject, A::QuantumObject j::QuantumObject) -Compute the generalized dot product `dot(i, A*j)` between three [`QuantumObject`](@ref): ``\langle i | A | j \rangle`` +Compute the generalized dot product `dot(i, A*j)` between three [`QuantumObject`](@ref): ``\langle i | \hat{A} | j \rangle`` Note that this function is same as `dot(i, A, j)` diff --git a/src/steadystate.jl b/src/steadystate.jl index 7cdd0f144..be06cbf1a 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -58,13 +58,13 @@ Solve the stationary state based on time evolution (ordinary differential equati The termination condition of the stationary state ``|\rho\rangle\rangle`` is that either the following condition is `true`: ```math -\lVert\frac{\partial |\rho\rangle\rangle}{\partial t}\rVert \leq \textrm{reltol} \times\lVert\frac{\partial |\rho\rangle\rangle}{\partial t}+|\rho\rangle\rangle\rVert +\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{reltol} \times\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}+|\hat{\rho}\rangle\rangle\rVert ``` or ```math -\lVert\frac{\partial |\rho\rangle\rangle}{\partial t}\rVert \leq \textrm{abstol} +\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{abstol} ``` # Parameters diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 404ec4947..f83a1bc3b 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -63,13 +63,13 @@ end Generates the ODEProblem for the master equation time evolution of an open quantum system: ```math -\frac{\partial \rho(t)}{\partial t} = -i[\hat{H}, \rho(t)] + \sum_n \mathcal{D}(\hat{C}_n) [\rho(t)] +\frac{\partial \hat{\rho}(t)}{\partial t} = -i[\hat{H}, \hat{\rho}(t)] + \sum_n \mathcal{D}(\hat{C}_n) [\hat{\rho}(t)] ``` where ```math -\mathcal{D}(\hat{C}_n) [\rho(t)] = \hat{C}_n \rho(t) \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n \rho(t) - \frac{1}{2} \rho(t) \hat{C}_n^\dagger \hat{C}_n +\mathcal{D}(\hat{C}_n) [\hat{\rho}(t)] = \hat{C}_n \hat{\rho}(t) \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n \hat{\rho}(t) - \frac{1}{2} \hat{\rho}(t) \hat{C}_n^\dagger \hat{C}_n ``` # Arguments @@ -176,13 +176,13 @@ end Time evolution of an open quantum system using Lindblad master equation: ```math -\frac{\partial \rho(t)}{\partial t} = -i[\hat{H}, \rho(t)] + \sum_n \mathcal{D}(\hat{C}_n) [\rho(t)] +\frac{\partial \hat{\rho}(t)}{\partial t} = -i[\hat{H}, \hat{\rho}(t)] + \sum_n \mathcal{D}(\hat{C}_n) [\hat{\rho}(t)] ``` where ```math -\mathcal{D}(\hat{C}_n) [\rho(t)] = \hat{C}_n \rho(t) \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n \rho(t) - \frac{1}{2} \rho(t) \hat{C}_n^\dagger \hat{C}_n +\mathcal{D}(\hat{C}_n) [\hat{\rho}(t)] = \hat{C}_n \hat{\rho}(t) \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n \hat{\rho}(t) - \frac{1}{2} \hat{\rho}(t) \hat{C}_n^\dagger \hat{C}_n ``` # Arguments diff --git a/test/states_and_operators.jl b/test/states_and_operators.jl index 08aa0c953..573dedd04 100644 --- a/test/states_and_operators.jl +++ b/test/states_and_operators.jl @@ -285,11 +285,11 @@ dims = ntuple(i -> 2, Val(sites)) Q_iden = Qobj(sparse((1.0 + 0.0im) * LinearAlgebra.I, SIZE, SIZE); dims = dims) Q_zero = Qobj(spzeros(ComplexF64, SIZE, SIZE); dims = dims) - for i in 0:(sites-1) + for i in 1:sites d_i = fdestroy(sites, i) @test d_i' ≈ fcreate(sites, i) - for j in 0:(sites-1) + for j in 1:sites d_j = fdestroy(sites, j) if i == j @@ -301,9 +301,20 @@ @test commutator(d_i, d_j; anti = true) ≈ Q_zero end end + zero = zero_ket((2, 2)) + vac = tensor(basis(2, 0), basis(2, 0)) + d1 = sigmap() ⊗ qeye(2) + d2 = sigmaz() ⊗ sigmap() + @test d1 * vac == zero + @test d2 * vac == zero + @test d1' * vac == tensor(basis(2, 1), basis(2, 0)) + @test d2' * vac == tensor(basis(2, 0), basis(2, 1)) + @test d1' * d2' * vac == tensor(basis(2, 1), basis(2, 1)) + @test d1' * d1' * d2' * vac == zero + @test d2' * d1' * d2' * vac == zero @test_throws ArgumentError fdestroy(0, 0) - @test_throws ArgumentError fdestroy(sites, -1) - @test_throws ArgumentError fdestroy(sites, sites) + @test_throws ArgumentError fdestroy(sites, 0) + @test_throws ArgumentError fdestroy(sites, sites + 1) end @testset "identity operator" begin From 3422581ff07aa763d99429e5448cd70cfd9fca7a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:22:11 +0800 Subject: [PATCH 009/329] Fix `ptrace` (#225) --- src/qobj/arithmetic_and_attributes.jl | 84 ++++++++++++++++++--------- src/qobj/quantum_object.jl | 11 ---- src/utilities.jl | 11 ++++ test/quantum_objects.jl | 52 +++++++++++++++++ 4 files changed, 121 insertions(+), 37 deletions(-) diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 52bfe115b..29b872dec 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -475,8 +475,9 @@ proj(ψ::QuantumObject{<:AbstractArray{T},BraQuantumObject}) where {T} = ψ' * @doc raw""" ptrace(QO::QuantumObject, sel) -[Partial trace](https://en.wikipedia.org/wiki/Partial_trace) of a quantum state `QO` leaving only the dimensions -with the indices present in the `sel` vector. +[Partial trace](https://en.wikipedia.org/wiki/Partial_trace) of a quantum state `QO` leaving only the dimensions with the indices present in the `sel` vector. + +Note that this function will always return [`Operator`](@ref). No matter the input [`QuantumObject`](@ref) is a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). # Examples Two qubits in the state ``\ket{\psi} = \ket{e,g}``: @@ -514,18 +515,46 @@ Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true ``` """ function ptrace(QO::QuantumObject{<:AbstractArray,KetQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) - length(QO.dims) == 1 && return QO + _non_static_array_warning("sel", sel) + + ns = length(sel) + if ns == 0 # return full trace for empty sel + return tr(ket2dm(QO)) + else + nd = length(QO.dims) + + (any(>(nd), sel) || any(<(1), sel)) && throw( + ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(nd) sub-systems"), + ) + (ns != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) + (nd == 1) && return ket2dm(QO) # ptrace should always return Operator + end - ρtr, dkeep = _ptrace_ket(QO.data, QO.dims, SVector(sel)) + _sort_sel = sort(SVector{length(sel),Int}(sel)) + ρtr, dkeep = _ptrace_ket(QO.data, QO.dims, _sort_sel) return QuantumObject(ρtr, type = Operator, dims = dkeep) end ptrace(QO::QuantumObject{<:AbstractArray,BraQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel) function ptrace(QO::QuantumObject{<:AbstractArray,OperatorQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) - length(QO.dims) == 1 && return QO + _non_static_array_warning("sel", sel) + + ns = length(sel) + if ns == 0 # return full trace for empty sel + return tr(QO) + else + nd = length(QO.dims) + + (any(>(nd), sel) || any(<(1), sel)) && throw( + ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(nd) sub-systems"), + ) + (ns != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) + (nd == 1) && return QO + end - ρtr, dkeep = _ptrace_oper(QO.data, QO.dims, SVector(sel)) + _sort_sel = sort(SVector{length(sel),Int}(sel)) + ρtr, dkeep = _ptrace_oper(QO.data, QO.dims, _sort_sel) return QuantumObject(ρtr, type = Operator, dims = dkeep) end ptrace(QO::QuantumObject, sel::Int) = ptrace(QO, SVector(sel)) @@ -538,17 +567,20 @@ function _ptrace_ket(QO::AbstractArray, dims::Union{SVector,MVector}, sel) qtrace = filter(i -> i ∉ sel, 1:nd) dkeep = dims[sel] dtrace = dims[qtrace] - # Concatenate sel and qtrace without loosing the length information - sel_qtrace = ntuple(Val(nd)) do i - if i <= length(sel) - @inbounds sel[i] + nt = length(dtrace) + + # Concatenate qtrace and sel without losing the length information + # Tuple(qtrace..., sel...) + qtrace_sel = ntuple(Val(nd)) do i + if i <= nt + @inbounds qtrace[i] else - @inbounds qtrace[i-length(sel)] + @inbounds sel[i-nt] end end vmat = reshape(QO, reverse(dims)...) - topermute = nd + 1 .- sel_qtrace + topermute = reverse(nd + 1 .- qtrace_sel) vmat = permutedims(vmat, topermute) # TODO: use PermutedDimsArray when Julia v1.11.0 is released vmat = reshape(vmat, prod(dkeep), prod(dtrace)) @@ -563,27 +595,27 @@ function _ptrace_oper(QO::AbstractArray, dims::Union{SVector,MVector}, sel) qtrace = filter(i -> i ∉ sel, 1:nd) dkeep = dims[sel] dtrace = dims[qtrace] - # Concatenate sel and qtrace without loosing the length information + nk = length(dkeep) + nt = length(dtrace) + _2_nt = 2 * nt + + # Concatenate qtrace and sel without losing the length information + # Tuple(qtrace..., sel...) qtrace_sel = ntuple(Val(2 * nd)) do i - if i <= length(qtrace) + if i <= nt @inbounds qtrace[i] - elseif i <= 2 * length(qtrace) - @inbounds qtrace[i-length(qtrace)] + nd - elseif i <= 2 * length(qtrace) + length(sel) - @inbounds sel[i-length(qtrace)-length(sel)] + elseif i <= _2_nt + @inbounds qtrace[i-nt] + nd + elseif i <= _2_nt + nk + @inbounds sel[i-_2_nt] else - @inbounds sel[i-length(qtrace)-2*length(sel)] + nd + @inbounds sel[i-_2_nt-nk] + nd end end ρmat = reshape(QO, reverse(vcat(dims, dims))...) - topermute = 2 * nd + 1 .- qtrace_sel - ρmat = permutedims(ρmat, reverse(topermute)) # TODO: use PermutedDimsArray when Julia v1.11.0 is released - - ## TODO: Check if it works always - - # ρmat = row_major_reshape(ρmat, prod(dtrace), prod(dtrace), prod(dkeep), prod(dkeep)) - # res = dropdims(mapslices(tr, ρmat, dims=(1,2)), dims=(1,2)) + topermute = reverse(2 * nd + 1 .- qtrace_sel) + ρmat = permutedims(ρmat, topermute) # TODO: use PermutedDimsArray when Julia v1.11.0 is released ρmat = reshape(ρmat, prod(dkeep), prod(dkeep), prod(dtrace), prod(dtrace)) res = map(tr, eachslice(ρmat, dims = (1, 2))) diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 1420cc70a..3a2ce8ed8 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -215,17 +215,6 @@ function QuantumObject( throw(DomainError(size(A), "The size of the array is not compatible with vector or matrix.")) end -_get_size(A::AbstractMatrix) = size(A) -_get_size(A::AbstractVector) = (length(A), 1) - -_non_static_array_warning(argname, arg::Tuple{}) = - throw(ArgumentError("The argument $argname must be a Tuple or a StaticVector of non-zero length.")) -_non_static_array_warning(argname, arg::Union{SVector{N,T},MVector{N,T},NTuple{N,T}}) where {N,T} = nothing -_non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = - @warn "The argument $argname should be a Tuple or a StaticVector for better performance. Try to use `$argname = $(Tuple(arg))` or `$argname = SVector(" * - join(arg, ", ") * - ")` instead of `$argname = $arg`." maxlog = 1 - function _check_dims(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Integer,N} _non_static_array_warning("dims", dims) return (all(>(0), dims) && length(dims) > 0) || diff --git a/src/utilities.jl b/src/utilities.jl index 63d5a6d41..df93872d4 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -54,3 +54,14 @@ makeVal(x::Val{T}) where {T} = x makeVal(x) = Val(x) getVal(x::Val{T}) where {T} = T + +_get_size(A::AbstractMatrix) = size(A) +_get_size(A::AbstractVector) = (length(A), 1) + +_non_static_array_warning(argname, arg::Tuple{}) = + throw(ArgumentError("The argument $argname must be a Tuple or a StaticVector of non-zero length.")) +_non_static_array_warning(argname, arg::Union{SVector{N,T},MVector{N,T},NTuple{N,T}}) where {N,T} = nothing +_non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = + @warn "The argument $argname should be a Tuple or a StaticVector for better performance. Try to use `$argname = $(Tuple(arg))` or `$argname = SVector(" * + join(arg, ", ") * + ")` instead of `$argname = $arg`." maxlog = 1 diff --git a/test/quantum_objects.jl b/test/quantum_objects.jl index 64d1433f5..8ef46b783 100644 --- a/test/quantum_objects.jl +++ b/test/quantum_objects.jl @@ -596,6 +596,58 @@ @test ρ1.data ≈ ρ1_ptr.data atol = 1e-10 @test ρ2.data ≈ ρ2_ptr.data atol = 1e-10 + ψlist = [rand_ket(2), rand_ket(3), rand_ket(4), rand_ket(5)] + ρlist = [rand_dm(2), rand_dm(3), rand_dm(4), rand_dm(5)] + ψtotal = tensor(ψlist...) + ρtotal = tensor(ρlist...) + sel_tests = [ + SVector{0,Int}(), + 1, + 2, + 3, + 4, + (1, 2), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 4), + (1, 2, 3), + (1, 2, 4), + (1, 3, 4), + (2, 3, 4), + (1, 2, 3, 4), + ] + for sel in sel_tests + if length(sel) == 0 + @test ptrace(ψtotal, sel) ≈ 1.0 + @test ptrace(ρtotal, sel) ≈ 1.0 + else + @test ptrace(ψtotal, sel) ≈ tensor([ket2dm(ψlist[i]) for i in sel]...) + @test ptrace(ρtotal, sel) ≈ tensor([ρlist[i] for i in sel]...) + end + end + @test ptrace(ψtotal, (1, 3, 4)) ≈ ptrace(ψtotal, (4, 3, 1)) # check sort of sel + @test ptrace(ρtotal, (1, 3, 4)) ≈ ptrace(ρtotal, (3, 1, 4)) # check sort of sel + @test_logs ( + :warn, + "The argument sel should be a Tuple or a StaticVector for better performance. Try to use `sel = (1, 2)` or `sel = SVector(1, 2)` instead of `sel = [1, 2]`.", + ) ptrace(ψtotal, [1, 2]) + @test_logs ( + :warn, + "The argument sel should be a Tuple or a StaticVector for better performance. Try to use `sel = (1, 2)` or `sel = SVector(1, 2)` instead of `sel = [1, 2]`.", + ) ptrace(ρtotal, [1, 2]) + @test_throws ArgumentError ptrace(ψtotal, 0) + @test_throws ArgumentError ptrace(ψtotal, 5) + @test_throws ArgumentError ptrace(ψtotal, (0, 2)) + @test_throws ArgumentError ptrace(ψtotal, (2, 5)) + @test_throws ArgumentError ptrace(ψtotal, (2, 2, 3)) + @test_throws ArgumentError ptrace(ρtotal, 0) + @test_throws ArgumentError ptrace(ρtotal, 5) + @test_throws ArgumentError ptrace(ρtotal, (0, 2)) + @test_throws ArgumentError ptrace(ρtotal, (2, 5)) + @test_throws ArgumentError ptrace(ρtotal, (2, 2, 3)) + @testset "Type Inference (ptrace)" begin @inferred ptrace(ρ, 1) @inferred ptrace(ρ, 2) From 93c3ff62c3590e1e6e97c31f6814096778c11310 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 17 Sep 2024 09:43:00 +0200 Subject: [PATCH 010/329] Change version to v0.14.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 63533afb2..13503d4ee 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Luca Gravina", "Yi-Te Huang"] -version = "0.13.1" +version = "0.14.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From da37b688678b68c922ef3d957ea162422ddaa5ff Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 19 Sep 2024 13:01:03 +0800 Subject: [PATCH 011/329] fix type conversion of `tlist` in time evolution --- src/time_evolution/mcsolve.jl | 2 +- src/time_evolution/mesolve.jl | 2 +- src/time_evolution/sesolve.jl | 2 +- src/utilities.jl | 10 ++++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index bd62bb410..a6f67f074 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -190,7 +190,7 @@ function mcsolveProblem( c_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - t_l = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for OrdinaryDiffEq.jl + t_l = _convert_tlist(eltype(H), tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl H_eff = H - 1im * mapreduce(op -> op' * op, +, c_ops) / 2 diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index f83a1bc3b..363e0790f 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -122,7 +122,7 @@ function mesolveProblem( is_time_dependent = !(H_t isa Nothing) progress_bar_val = makeVal(progress_bar) - t_l = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for OrdinaryDiffEq.jl + t_l = _convert_tlist(eltype(H), tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl ρ0 = mat2vec(ket2dm(ψ0).data) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 6ef530d65..a550e48aa 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -103,7 +103,7 @@ function sesolveProblem( is_time_dependent = !(H_t isa Nothing) progress_bar_val = makeVal(progress_bar) - t_l = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for OrdinaryDiffEq.jl + t_l = _convert_tlist(eltype(H), tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl ϕ0 = get_data(ψ0) diff --git a/src/utilities.jl b/src/utilities.jl index df93872d4..487624455 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -65,3 +65,13 @@ _non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = @warn "The argument $argname should be a Tuple or a StaticVector for better performance. Try to use `$argname = $(Tuple(arg))` or `$argname = SVector(" * join(arg, ", ") * ")` instead of `$argname = $arg`." maxlog = 1 + +# convert tlist in time evolution +_convert_tlist(::Int32, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) +_convert_tlist(::Float32, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) +_convert_tlist(::ComplexF32, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) +_convert_tlist(::Int64, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) +_convert_tlist(::Float64, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) +_convert_tlist(::ComplexF64, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) +_convert_tlist(::Val{32}, tlist::AbstractVector) = convert(Vector{Float32}, tlist) +_convert_tlist(::Val{64}, tlist::AbstractVector) = convert(Vector{Float64}, tlist) \ No newline at end of file From f888a305f5502a29eb8ab46e744eddd366b18e85 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 19 Sep 2024 13:15:18 +0800 Subject: [PATCH 012/329] fix typo --- src/utilities.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 487624455..5e906edc8 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -67,11 +67,11 @@ _non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = ")` instead of `$argname = $arg`." maxlog = 1 # convert tlist in time evolution -_convert_tlist(::Int32, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) -_convert_tlist(::Float32, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) -_convert_tlist(::ComplexF32, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) -_convert_tlist(::Int64, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) -_convert_tlist(::Float64, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) -_convert_tlist(::ComplexF64, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) +_convert_tlist(::Type{Int32}, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) +_convert_tlist(::Type{Float32}, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) +_convert_tlist(::Type{ComplexF32}, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) +_convert_tlist(::Type{Int64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) +_convert_tlist(::Type{Float64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) +_convert_tlist(::Type{ComplexF64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) _convert_tlist(::Val{32}, tlist::AbstractVector) = convert(Vector{Float32}, tlist) _convert_tlist(::Val{64}, tlist::AbstractVector) = convert(Vector{Float64}, tlist) \ No newline at end of file From 9a924d255e251bbaddea1db5b282965e0b94fc3b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 19 Sep 2024 13:16:32 +0800 Subject: [PATCH 013/329] format files --- src/utilities.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities.jl b/src/utilities.jl index 5e906edc8..fce5d46e4 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -74,4 +74,4 @@ _convert_tlist(::Type{Int64}, tlist::AbstractVector) = _convert_tlist(Val(64), t _convert_tlist(::Type{Float64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) _convert_tlist(::Type{ComplexF64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) _convert_tlist(::Val{32}, tlist::AbstractVector) = convert(Vector{Float32}, tlist) -_convert_tlist(::Val{64}, tlist::AbstractVector) = convert(Vector{Float64}, tlist) \ No newline at end of file +_convert_tlist(::Val{64}, tlist::AbstractVector) = convert(Vector{Float64}, tlist) From fc5da367c3ca4c8718d64f41e2cf8e651a194c63 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 19 Sep 2024 14:05:45 +0800 Subject: [PATCH 014/329] introduce inner function `_convert_u0` --- ext/QuantumToolboxCUDAExt.jl | 4 ++++ src/time_evolution/mcsolve.jl | 2 +- src/time_evolution/mesolve.jl | 4 ++-- src/time_evolution/sesolve.jl | 4 ++-- src/utilities.jl | 11 ++--------- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index e1b3418bb..8886a2e35 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -1,6 +1,7 @@ module QuantumToolboxCUDAExt using QuantumToolbox +import QuantumToolbox: _convert_u0 import CUDA: cu, CuArray import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR import SparseArrays: SparseVector, SparseMatrixCSC @@ -89,4 +90,7 @@ _change_eltype(::Type{T}, ::Val{32}) where {T<:AbstractFloat} = Float32 _change_eltype(::Type{Complex{T}}, ::Val{64}) where {T<:Union{Int,AbstractFloat}} = ComplexF64 _change_eltype(::Type{Complex{T}}, ::Val{32}) where {T<:Union{Int,AbstractFloat}} = ComplexF32 +# make sure u0 in time evolution is dense vector and has complex element type +_convert_u0(u0::Union{CuArray{T},CuSparseVector{T}}) where {T<:Number} = convert(CuArray{complex(T)}, u0) + end diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index a6f67f074..9dbd68da1 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -190,7 +190,7 @@ function mcsolveProblem( c_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - t_l = _convert_tlist(eltype(H), tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + t_l = convert(Vector{real(eltype(ψ0))}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl H_eff = H - 1im * mapreduce(op -> op' * op, +, c_ops) / 2 diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 363e0790f..9b829ddcd 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -122,9 +122,9 @@ function mesolveProblem( is_time_dependent = !(H_t isa Nothing) progress_bar_val = makeVal(progress_bar) - t_l = _convert_tlist(eltype(H), tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + ρ0 = _convert_u0(mat2vec(ket2dm(ψ0).data)) - ρ0 = mat2vec(ket2dm(ψ0).data) + t_l = convert(Vector{real(eltype(ρ0))}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl L = liouvillian(H, c_ops).data progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index a550e48aa..28f39a8d0 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -103,9 +103,9 @@ function sesolveProblem( is_time_dependent = !(H_t isa Nothing) progress_bar_val = makeVal(progress_bar) - t_l = _convert_tlist(eltype(H), tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + ϕ0 = _convert_u0(get_data(ψ0)) - ϕ0 = get_data(ψ0) + t_l = convert(Vector{real(eltype(ϕ0))}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl U = -1im * get_data(H) progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) diff --git a/src/utilities.jl b/src/utilities.jl index fce5d46e4..7fb8ccbcc 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -66,12 +66,5 @@ _non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = join(arg, ", ") * ")` instead of `$argname = $arg`." maxlog = 1 -# convert tlist in time evolution -_convert_tlist(::Type{Int32}, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) -_convert_tlist(::Type{Float32}, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) -_convert_tlist(::Type{ComplexF32}, tlist::AbstractVector) = _convert_tlist(Val(32), tlist) -_convert_tlist(::Type{Int64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) -_convert_tlist(::Type{Float64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) -_convert_tlist(::Type{ComplexF64}, tlist::AbstractVector) = _convert_tlist(Val(64), tlist) -_convert_tlist(::Val{32}, tlist::AbstractVector) = convert(Vector{Float32}, tlist) -_convert_tlist(::Val{64}, tlist::AbstractVector) = convert(Vector{Float64}, tlist) +# make sure u0 in time evolution is dense vector and has complex element type +_convert_u0(u0::AbstractVector{T}) where {T<:Number} = convert(Vector{complex(T)}, u0) From 79bc22fcd47475a705d581f1bcd3e29afb8deb3e Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 19 Sep 2024 14:21:37 +0800 Subject: [PATCH 015/329] fix element type for `steadystate` --- src/steadystate.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/steadystate.jl b/src/steadystate.jl index be06cbf1a..c7f2aac80 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -95,10 +95,14 @@ function steadystate( (H.dims != ψ0.dims) && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) N = prod(H.dims) - u0 = mat2vec(ket2dm(ψ0).data) + u0 = _convert_u0(mat2vec(ket2dm(ψ0).data)) + + Ftype = real(eltype(u0)) + Tspan = (convert(Ftype, 0), convert(Ftype, tspan)) + L = MatrixOperator(liouvillian(H, c_ops).data) - prob = ODEProblem{true}(L, u0, (0.0, tspan)) + prob = ODEProblem{true}(L, u0, Tspan) sol = solve( prob, solver.alg; @@ -109,7 +113,6 @@ function steadystate( ) ρss = reshape(sol.u[end], N, N) - ρss = (ρss + ρss') / 2 # Hermitianize return QuantumObject(ρss, Operator, H.dims) end From 1ececdaff26df9392f83509336baff7735daab96 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:56:20 +0800 Subject: [PATCH 016/329] add comments --- src/steadystate.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/steadystate.jl b/src/steadystate.jl index c7f2aac80..86c9cc2e0 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -98,7 +98,7 @@ function steadystate( u0 = _convert_u0(mat2vec(ket2dm(ψ0).data)) Ftype = real(eltype(u0)) - Tspan = (convert(Ftype, 0), convert(Ftype, tspan)) + Tspan = (convert(Ftype, 0), convert(Ftype, tspan)) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl L = MatrixOperator(liouvillian(H, c_ops).data) From fb4844714ea8406ae27e47b063c700952fe1447c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:09:36 +0800 Subject: [PATCH 017/329] re-organize runtests directory (#228) --- .buildkite/pipeline.yml | 4 ++-- .github/workflows/CI.yml | 6 ++++-- test/{ => core-test}/code_quality.jl | 0 test/{ => core-test}/correlations_and_spectrum.jl | 0 test/{ => core-test}/dynamical-shifted-fock.jl | 0 test/{ => core-test}/dynamical_fock_dimension_mesolve.jl | 0 test/{ => core-test}/eigenvalues_and_operators.jl | 0 test/{ => core-test}/entanglement.jl | 0 test/{ => core-test}/generalized_master_equation.jl | 0 test/{ => core-test}/low_rank_dynamics.jl | 0 test/{ => core-test}/negativity_and_partial_transpose.jl | 0 test/{ => core-test}/permutation.jl | 0 test/{ => core-test}/progress_bar.jl | 0 test/{ => core-test}/quantum_objects.jl | 0 test/{ => core-test}/states_and_operators.jl | 0 test/{ => core-test}/steady_state.jl | 0 test/{ => core-test}/time_evolution.jl | 0 test/{ => core-test}/wigner.jl | 0 test/{ => ext-test}/cuda_ext.jl | 0 test/runtests.jl | 6 +++--- 20 files changed, 9 insertions(+), 7 deletions(-) rename test/{ => core-test}/code_quality.jl (100%) rename test/{ => core-test}/correlations_and_spectrum.jl (100%) rename test/{ => core-test}/dynamical-shifted-fock.jl (100%) rename test/{ => core-test}/dynamical_fock_dimension_mesolve.jl (100%) rename test/{ => core-test}/eigenvalues_and_operators.jl (100%) rename test/{ => core-test}/entanglement.jl (100%) rename test/{ => core-test}/generalized_master_equation.jl (100%) rename test/{ => core-test}/low_rank_dynamics.jl (100%) rename test/{ => core-test}/negativity_and_partial_transpose.jl (100%) rename test/{ => core-test}/permutation.jl (100%) rename test/{ => core-test}/progress_bar.jl (100%) rename test/{ => core-test}/quantum_objects.jl (100%) rename test/{ => core-test}/states_and_operators.jl (100%) rename test/{ => core-test}/steady_state.jl (100%) rename test/{ => core-test}/time_evolution.jl (100%) rename test/{ => core-test}/wigner.jl (100%) rename test/{ => ext-test}/cuda_ext.jl (100%) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index e8de06364..83595dff4 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -2,14 +2,14 @@ steps: - label: ":runner: Dynamically launch pipelines" plugins: - - staticfloat/forerunner: + - staticfloat/forerunner: # CUDA.jl tests watch: - ".buildkite/pipeline.yml" - ".buildkite/CUDA_Ext.yml" - "src/**" - "ext/QuantumToolboxCUDAExt.jl" - "test/runtests.jl" - - "test/cuda_ext.jl" + - "test/ext-test/cuda_ext.jl" - "Project.toml" target: ".buildkite/CUDA_Ext.yml" agents: diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0729432fd..9c35ff9a2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -8,7 +8,8 @@ on: - '.github/workflows/CI.yml' - 'src/**' - 'ext/**' - - 'test/**' + - 'test/runtests.jl' + - 'test/core-test/**' - 'Project.toml' pull_request: branches: @@ -17,7 +18,8 @@ on: - '.github/workflows/CI.yml' - 'src/**' - 'ext/**' - - 'test/**' + - 'test/runtests.jl' + - 'test/core-test/**' - 'Project.toml' types: - opened diff --git a/test/code_quality.jl b/test/core-test/code_quality.jl similarity index 100% rename from test/code_quality.jl rename to test/core-test/code_quality.jl diff --git a/test/correlations_and_spectrum.jl b/test/core-test/correlations_and_spectrum.jl similarity index 100% rename from test/correlations_and_spectrum.jl rename to test/core-test/correlations_and_spectrum.jl diff --git a/test/dynamical-shifted-fock.jl b/test/core-test/dynamical-shifted-fock.jl similarity index 100% rename from test/dynamical-shifted-fock.jl rename to test/core-test/dynamical-shifted-fock.jl diff --git a/test/dynamical_fock_dimension_mesolve.jl b/test/core-test/dynamical_fock_dimension_mesolve.jl similarity index 100% rename from test/dynamical_fock_dimension_mesolve.jl rename to test/core-test/dynamical_fock_dimension_mesolve.jl diff --git a/test/eigenvalues_and_operators.jl b/test/core-test/eigenvalues_and_operators.jl similarity index 100% rename from test/eigenvalues_and_operators.jl rename to test/core-test/eigenvalues_and_operators.jl diff --git a/test/entanglement.jl b/test/core-test/entanglement.jl similarity index 100% rename from test/entanglement.jl rename to test/core-test/entanglement.jl diff --git a/test/generalized_master_equation.jl b/test/core-test/generalized_master_equation.jl similarity index 100% rename from test/generalized_master_equation.jl rename to test/core-test/generalized_master_equation.jl diff --git a/test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl similarity index 100% rename from test/low_rank_dynamics.jl rename to test/core-test/low_rank_dynamics.jl diff --git a/test/negativity_and_partial_transpose.jl b/test/core-test/negativity_and_partial_transpose.jl similarity index 100% rename from test/negativity_and_partial_transpose.jl rename to test/core-test/negativity_and_partial_transpose.jl diff --git a/test/permutation.jl b/test/core-test/permutation.jl similarity index 100% rename from test/permutation.jl rename to test/core-test/permutation.jl diff --git a/test/progress_bar.jl b/test/core-test/progress_bar.jl similarity index 100% rename from test/progress_bar.jl rename to test/core-test/progress_bar.jl diff --git a/test/quantum_objects.jl b/test/core-test/quantum_objects.jl similarity index 100% rename from test/quantum_objects.jl rename to test/core-test/quantum_objects.jl diff --git a/test/states_and_operators.jl b/test/core-test/states_and_operators.jl similarity index 100% rename from test/states_and_operators.jl rename to test/core-test/states_and_operators.jl diff --git a/test/steady_state.jl b/test/core-test/steady_state.jl similarity index 100% rename from test/steady_state.jl rename to test/core-test/steady_state.jl diff --git a/test/time_evolution.jl b/test/core-test/time_evolution.jl similarity index 100% rename from test/time_evolution.jl rename to test/core-test/time_evolution.jl diff --git a/test/wigner.jl b/test/core-test/wigner.jl similarity index 100% rename from test/wigner.jl rename to test/core-test/wigner.jl diff --git a/test/cuda_ext.jl b/test/ext-test/cuda_ext.jl similarity index 100% rename from test/cuda_ext.jl rename to test/ext-test/cuda_ext.jl diff --git a/test/runtests.jl b/test/runtests.jl index ea61379d6..70c361cd0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -28,18 +28,18 @@ core_tests = [ if (GROUP == "All") || (GROUP == "Code-Quality") Pkg.add(["Aqua", "JET"]) - include(joinpath(testdir, "code_quality.jl")) + include(joinpath(testdir, "core-test", "code_quality.jl")) end if (GROUP == "All") || (GROUP == "Core") QuantumToolbox.about() for test in core_tests - include(joinpath(testdir, test)) + include(joinpath(testdir, "core-test", test)) end end if (GROUP == "CUDA_Ext")# || (GROUP == "All") Pkg.add("CUDA") - include(joinpath(testdir, "cuda_ext.jl")) + include(joinpath(testdir, "ext-test", "cuda_ext.jl")) end From b4bf1a11011d84d22db015cb9545c182e4fe4855 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 19 Sep 2024 20:59:53 +0800 Subject: [PATCH 018/329] minor change --- src/steadystate.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/steadystate.jl b/src/steadystate.jl index 86c9cc2e0..f8eec06ee 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -97,12 +97,10 @@ function steadystate( N = prod(H.dims) u0 = _convert_u0(mat2vec(ket2dm(ψ0).data)) - Ftype = real(eltype(u0)) - Tspan = (convert(Ftype, 0), convert(Ftype, tspan)) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl - L = MatrixOperator(liouvillian(H, c_ops).data) - prob = ODEProblem{true}(L, u0, Tspan) + Ftype = real(eltype(u0)) + prob = ODEProblem{true}(L, u0, (Ftype(0), Ftype(tspan))) # Convert tspan to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl sol = solve( prob, solver.alg; From 6bca7394fc0efa9b26ebdbf7ee8636bf62a29ca2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 20 Sep 2024 00:09:29 +0800 Subject: [PATCH 019/329] handle type of `u0` with `sparse_to_dense` --- ext/QuantumToolboxCUDAExt.jl | 8 +++++--- src/qobj/functions.jl | 6 +++++- src/qobj/quantum_object.jl | 4 ++++ src/steadystate.jl | 6 +++--- src/time_evolution/mcsolve.jl | 2 +- src/time_evolution/mesolve.jl | 4 ++-- src/time_evolution/sesolve.jl | 4 ++-- src/utilities.jl | 17 +++++++++++++++-- test/core-test/time_evolution.jl | 8 +++++++- test/runtests.jl | 2 +- 10 files changed, 45 insertions(+), 16 deletions(-) diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index 8886a2e35..7d379c3a1 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -1,7 +1,6 @@ module QuantumToolboxCUDAExt using QuantumToolbox -import QuantumToolbox: _convert_u0 import CUDA: cu, CuArray import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR import SparseArrays: SparseVector, SparseMatrixCSC @@ -90,7 +89,10 @@ _change_eltype(::Type{T}, ::Val{32}) where {T<:AbstractFloat} = Float32 _change_eltype(::Type{Complex{T}}, ::Val{64}) where {T<:Union{Int,AbstractFloat}} = ComplexF64 _change_eltype(::Type{Complex{T}}, ::Val{32}) where {T<:Union{Int,AbstractFloat}} = ComplexF32 -# make sure u0 in time evolution is dense vector and has complex element type -_convert_u0(u0::Union{CuArray{T},CuSparseVector{T}}) where {T<:Number} = convert(CuArray{complex(T)}, u0) +sparse_to_dense(::Type{T}, A::CuArray{T}) where {T<:Number} = A +sparse_to_dense(::Type{T1}, A::CuArray{T2}) where {T1<:Number,T2<:Number} = CuArray{T1}(A) +sparse_to_dense(::Type{T}, A::CuSparseVector) where {T<:Number} = CuArray{T}(A) +sparse_to_dense(::Type{T}, A::CuSparseMatrixCSC) where {T<:Number} = CuArray{T}(A) +sparse_to_dense(::Type{T}, A::CuSparseMatrixCSR) where {T<:Number} = CuArray{T}(A) end diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index a69d8c108..de729d08c 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -108,12 +108,16 @@ variance(O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ::Vector Converts a sparse QuantumObject to a dense QuantumObject. """ sparse_to_dense(A::QuantumObject{<:AbstractVecOrMat}) = QuantumObject(sparse_to_dense(A.data), A.type, A.dims) -sparse_to_dense(A::MT) where {MT<:AbstractSparseMatrix} = Array(A) +sparse_to_dense(A::MT) where {MT<:AbstractSparseArray} = Array(A) for op in (:Transpose, :Adjoint) @eval sparse_to_dense(A::$op{T,<:AbstractSparseMatrix}) where {T<:BlasFloat} = Array(A) end sparse_to_dense(A::MT) where {MT<:AbstractArray} = A +sparse_to_dense(::Type{T}, A::AbstractSparseArray) where {T<:Number} = Array{T}(A) +sparse_to_dense(::Type{T1}, A::AbstractArray{T2}) where {T1<:Number,T2<:Number} = Array{T1}(A) +sparse_to_dense(::Type{T}, A::AbstractArray{T}) where {T<:Number} = A + function sparse_to_dense(::Type{M}) where {M<:SparseMatrixCSC} T = M par = T.parameters diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 3a2ce8ed8..f55244b2b 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -368,3 +368,7 @@ SparseArrays.SparseMatrixCSC(A::QuantumObject{<:AbstractMatrix}) = QuantumObject(SparseMatrixCSC(A.data), A.type, A.dims) SparseArrays.SparseMatrixCSC{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T<:Number} = QuantumObject(SparseMatrixCSC{T}(A.data), A.type, A.dims) + +# functions for getting Float or Complex element type +_FType(::QuantumObject{<:AbstractArray{T}}) where {T<:Number} = _FType(T) +_CType(::QuantumObject{<:AbstractArray{T}}) where {T<:Number} = _CType(T) diff --git a/src/steadystate.jl b/src/steadystate.jl index f8eec06ee..080f17281 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -95,12 +95,12 @@ function steadystate( (H.dims != ψ0.dims) && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) N = prod(H.dims) - u0 = _convert_u0(mat2vec(ket2dm(ψ0).data)) + u0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) L = MatrixOperator(liouvillian(H, c_ops).data) - Ftype = real(eltype(u0)) - prob = ODEProblem{true}(L, u0, (Ftype(0), Ftype(tspan))) # Convert tspan to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + ftype = _FType(ψ0) + prob = ODEProblem{true}(L, u0, (ftype(0), ftype(tspan))) # Convert tspan to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl sol = solve( prob, solver.alg; diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 9dbd68da1..34a722ba8 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -190,7 +190,7 @@ function mcsolveProblem( c_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - t_l = convert(Vector{real(eltype(ψ0))}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl H_eff = H - 1im * mapreduce(op -> op' * op, +, c_ops) / 2 diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 9b829ddcd..95a591f58 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -122,9 +122,9 @@ function mesolveProblem( is_time_dependent = !(H_t isa Nothing) progress_bar_val = makeVal(progress_bar) - ρ0 = _convert_u0(mat2vec(ket2dm(ψ0).data)) + ρ0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type - t_l = convert(Vector{real(eltype(ρ0))}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl L = liouvillian(H, c_ops).data progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 28f39a8d0..264a6527b 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -103,9 +103,9 @@ function sesolveProblem( is_time_dependent = !(H_t isa Nothing) progress_bar_val = makeVal(progress_bar) - ϕ0 = _convert_u0(get_data(ψ0)) + ϕ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) # Convert it to dense vector with complex element type - t_l = convert(Vector{real(eltype(ϕ0))}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl U = -1im * get_data(H) progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) diff --git a/src/utilities.jl b/src/utilities.jl index 7fb8ccbcc..a2888b70c 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -66,5 +66,18 @@ _non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = join(arg, ", ") * ")` instead of `$argname = $arg`." maxlog = 1 -# make sure u0 in time evolution is dense vector and has complex element type -_convert_u0(u0::AbstractVector{T}) where {T<:Number} = convert(Vector{complex(T)}, u0) +# functions for getting Float or Complex element type +_FType(::AbstractArray{T}) where {T<:Number} = _FType(T) +_FType(::Type{Int32}) = Float32 +_FType(::Type{Int64}) = Float64 +_FType(::Type{Float32}) = Float32 +_FType(::Type{Float64}) = Float64 +_FType(::Type{ComplexF32}) = Float32 +_FType(::Type{ComplexF64}) = Float64 +_CType(::AbstractArray{T}) where {T<:Number} = _CType(T) +_CType(::Type{Int32}) = ComplexF32 +_CType(::Type{Int64}) = ComplexF64 +_CType(::Type{Float32}) = ComplexF32 +_CType(::Type{Float64}) = ComplexF64 +_CType(::Type{ComplexF32}) = ComplexF32 +_CType(::Type{ComplexF64}) = ComplexF64 diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index b4d097dea..f86d0bfe6 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -33,7 +33,9 @@ "reltol = $(sol.reltol)\n" @testset "Type Inference sesolve" begin - @inferred sesolveProblem(H, psi0, t_l) + @inferred sesolveProblem(H, psi0, t_l, progress_bar = Val(false)) + @inferred sesolveProblem(H, psi0, [0, 10], progress_bar = Val(false)) + @inferred sesolveProblem(H, Qobj(zeros(Int64, N * 2); dims = (N, 2)), t_l, progress_bar = Val(false)) @inferred sesolve(H, psi0, t_l, e_ops = e_ops, progress_bar = Val(false)) @inferred sesolve(H, psi0, t_l, progress_bar = Val(false)) @inferred sesolve(H, psi0, t_l, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) @@ -91,6 +93,8 @@ @testset "Type Inference mesolve" begin @inferred mesolveProblem(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolveProblem(H, psi0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolveProblem(H, Qobj(zeros(Int64, N)), t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolve(H, psi0, t_l, c_ops, progress_bar = Val(false)) @inferred mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) @@ -108,6 +112,8 @@ ) @inferred mcsolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) @inferred mcsolve(H, psi0, t_l, c_ops, n_traj = 500, progress_bar = Val(true)) + @inferred mcsolve(H, psi0, [0, 10], c_ops, n_traj = 500, progress_bar = Val(false)) + @inferred mcsolve(H, Qobj(zeros(Int64, N)), t_l, c_ops, n_traj = 500, progress_bar = Val(false)) end end diff --git a/test/runtests.jl b/test/runtests.jl index 70c361cd0..cd69fb2cf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,7 +39,7 @@ if (GROUP == "All") || (GROUP == "Core") end end -if (GROUP == "CUDA_Ext")# || (GROUP == "All") +if (GROUP == "CUDA_Ext") || (GROUP == "All") Pkg.add("CUDA") include(joinpath(testdir, "ext-test", "cuda_ext.jl")) end From 725fa0b3ff220f5523dc482416d72e2d3a13c97b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 20 Sep 2024 00:11:13 +0800 Subject: [PATCH 020/329] fix typo --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index cd69fb2cf..70c361cd0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,7 +39,7 @@ if (GROUP == "All") || (GROUP == "Core") end end -if (GROUP == "CUDA_Ext") || (GROUP == "All") +if (GROUP == "CUDA_Ext")# || (GROUP == "All") Pkg.add("CUDA") include(joinpath(testdir, "ext-test", "cuda_ext.jl")) end From 7605b69e2093386eea0f634a20cb617093233b7f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:22:42 +0800 Subject: [PATCH 021/329] fix `rand_unitary` (#230) --- src/qobj/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 008f29435..feb15780b 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -48,7 +48,7 @@ function rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{:haar} # Because inv(Λ) ⋅ R has real and strictly positive elements, Q · Λ is therefore Haar distributed. Λ = diag(R) # take the diagonal elements of R Λ ./= abs.(Λ) # rescaling the elements - return QuantumObject(dense_to_sparse(Q * Diagonal(Λ)); type = Operator, dims = dimensions) + return QuantumObject(sparse_to_dense(Q * Diagonal(Λ)); type = Operator, dims = dimensions) end function rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{:exp}) N = prod(dimensions) @@ -59,7 +59,7 @@ function rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{:exp}) # generate Hermitian matrix H = QuantumObject((Z + Z') / 2; type = Operator, dims = dimensions) - return exp(-1.0im * H) + return sparse_to_dense(exp(-1.0im * H)) end rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{T}) where {T} = throw(ArgumentError("Invalid distribution: $(T)")) From e21f940ff7504256ce71dda41b5564b89557619c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:26:35 +0800 Subject: [PATCH 022/329] fix CI config (#232) --- .github/workflows/CI.yml | 46 +++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9c35ff9a2..f1ebe096a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -29,8 +29,8 @@ on: jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} ( ${{ matrix.group }} ) - runs-on: ${{ matrix.os }} + name: Julia ${{ matrix.version }} - ${{ matrix.node.os }} - ${{ matrix.node.arch }} ( ${{ matrix.group }} ) + runs-on: ${{ matrix.node.os }} permissions: # needed to allow julia-actions/cache to delete old caches that it has created actions: write contents: read @@ -42,43 +42,37 @@ jobs: version: - '1.10' # oldest # - '1' # latest - os: - - ubuntu-latest - - windows-latest - arch: - - x64 + node: + - os: 'ubuntu-latest' + arch: 'x64' + - os: 'windows-latest' + arch: 'x64' + - os: 'macOS-latest' + arch: 'arm64' group: - - Core + - 'Core' include: - # for core tests on macOS (M-series chip) - - version: '1.10' # oldest - os: 'macOS-latest' - arch: 'x64' - group: 'Core' - # - version: '1' # latest - # os: 'macOS-latest' - # arch: 'arm64' - # group: 'Core' + # for code quality tests + - version: '1' + node: + os: 'ubuntu-latest' + arch: 'x64' + group: 'Code-Quality' # for core tests (intermediate versions) # - version: '1.x' - # os: 'ubuntu-latest' - # arch: 'x64' + # node: + # os: 'ubuntu-latest' + # arch: 'x64' # group: 'Core' - # for code quality tests - - version: '1' - os: 'ubuntu-latest' - arch: 'x64' - group: 'Code-Quality' - steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} - arch: ${{ matrix.arch }} + arch: ${{ matrix.node.arch }} - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 From 6d4bfec2a599a205a5a067538f315ab50de4bccc Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:39:21 +0800 Subject: [PATCH 023/329] Add pipeline to run core and code quality tests under `Julia nightly` (#231) --- .github/workflows/CI-Julia-nightly.yml | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/CI-Julia-nightly.yml diff --git a/.github/workflows/CI-Julia-nightly.yml b/.github/workflows/CI-Julia-nightly.yml new file mode 100644 index 000000000..389e2d3fd --- /dev/null +++ b/.github/workflows/CI-Julia-nightly.yml @@ -0,0 +1,61 @@ +name: Runtests (Julia nightly) + +on: + push: + branches: + - 'main' + paths: + - '.github/workflows/CI-Julia-nightly.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + pull_request: + branches: + - 'main' + paths: + - '.github/workflows/CI-Julia-nightly.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + types: + - opened + - reopened + - synchronize + - ready_for_review + +jobs: + test: + name: ${{ matrix.os }} - ${{ matrix.arch }} ( ${{ matrix.group }} ) + runs-on: ${{ matrix.os }} + permissions: # needed to allow julia-actions/cache to delete old caches that it has created + actions: write + contents: read + if: ${{ !github.event.pull_request.draft }} + strategy: + fail-fast: false + matrix: + version: + - 'nightly' + os: + - 'ubuntu-latest' + arch: + - 'x64' + group: + - 'Core' + - 'Code-Quality' + + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + env: + GROUP: ${{ matrix.group }} From db333c250169146b7af284e34039dbb7510fc552 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:33:45 +0800 Subject: [PATCH 024/329] Introduce `Makefile` to make development more convenient (#234) * introduce `Makefile` * minor changes --- .github/pull_request_template.md | 10 +++++----- .github/workflows/FormatCheck.yml | 2 +- Makefile | 21 +++++++++++++++++++++ docs/README.md | 15 ++++++++++++--- 4 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 Makefile diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f486924a9..dbb5b2596 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,9 +3,9 @@ Thank you for contributing to `QuantumToolbox.jl`! Please make sure you have fin - [ ] Please read [Contributor Covenant Code of Conduct](https://github.com/qutip/QuantumToolbox.jl/blob/main/CODE_OF_CONDUCT.md) - [ ] Any code changes were done in a way that does not break public API -- [ ] Appropriate tests were added. -- [ ] Any code changes should be formatted by running: `julia -e 'using JuliaFormatter; format(".")'` -- [ ] All documentation (in `docs/` folder) related to code changes were updated. +- [ ] Appropriate tests were added and tested locally by running: `make test`. +- [ ] Any code changes should be `julia` formatted by running: `make format`. +- [ ] All documents (in `docs/` folder) related to code changes were updated and able to build locally by running `make docs`. Request for a review after you have completed all the tasks. If you have not finished them all, you can also open a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) to let the others know this on-going work. @@ -13,6 +13,6 @@ Request for a review after you have completed all the tasks. If you have not fin Describe the proposed change here. ## Related issues or PRs -Please mention the related issues or PRs here. If the PR fixes an issue, use the keyword close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved followed by the issue id, e.g. fix #1234 +Please mention the related issues or PRs here. If the PR fixes an issue, use the keyword close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved followed by the issue id, e.g. fix #[id] -## Additional context \ No newline at end of file +## Additional context diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 2b46352d5..dda62fa1c 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -45,6 +45,6 @@ jobs: write(stdout, output) write(stdout, "-----\n") write(stdout, "Please format them by running the following command:\n") - write(stdout, "julia -e \"using JuliaFormatter; format(\\\".\\\")\"") + write(stdout, "make format") exit(1) end' \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..920c480ff --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +JULIA:=julia + +default: help + +docs: + ${JULIA} --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + ${JULIA} --project=docs docs/make.jl + +format: + ${JULIA} -e 'using JuliaFormatter; format(".")' + +test: + ${JULIA} --project -e 'using Pkg; Pkg.resolve(); Pkg.test()' + +help: + @echo "The following make commands are available:" + @echo " - make docs: instantiate and build the documentation" + @echo " - make format: format codes with JuliaFormatter" + @echo " - make test: run the tests" + +.PHONY: default docs format test help \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index e7c090ca1..9fe1e761c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,16 +3,25 @@ ## Working Directory All the commands should be run under the root folder of the package: `/path/to/QuantumToolbox.jl/` -## Build Pkg +The document pages will be generated in the directory: `/path/to/QuantumToolbox.jl/docs/build/` (which is ignored by git). + +## Method 1: Run with `make` command +Run the following command: +```shell +make docs +``` + +## Method 2: Run `julia` command manually + +### Build Pkg Run the following command: ```shell julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' ``` > **_NOTE:_** `Pkg.develop(PackageSpec(path=pwd()))` adds the local version of `QuantumToolbox` as dev-dependency instead of pulling from the registered version. -## Build Documentation +### Build Documentation Run the following command: ```shell julia --project=docs docs/make.jl ``` -The document pages will be generated in the directory: `/path/to/QuantumToolbox.jl/docs/build/` (which is ignored by git). \ No newline at end of file From 96d6f0a78896a817d24999e961bd2649378c421e Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Thu, 26 Sep 2024 19:51:29 +0200 Subject: [PATCH 025/329] Automatically convert TimeDependentOperatorSum to liouvillian --- src/qobj/operator_sum.jl | 6 +++++- src/time_evolution/mesolve.jl | 4 ++++ src/time_evolution/time_evolution.jl | 4 ++++ test/core-test/steady_state.jl | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/qobj/operator_sum.jl b/src/qobj/operator_sum.jl index 6f3642f5e..d19b4eac7 100644 --- a/src/qobj/operator_sum.jl +++ b/src/qobj/operator_sum.jl @@ -3,7 +3,7 @@ export OperatorSum @doc raw""" struct OperatorSum -A structure to represent a sum of operators ``\sum_i c_i \hat{O}_i`` with a list of coefficients ``c_i`` and a list of operators ``\hat{O}_i``. +A constructor to represent a sum of operators ``\sum_i c_i \hat{O}_i`` with a list of coefficients ``c_i`` and a list of operators ``\hat{O}_i``. This is very useful when we have to update only the coefficients, without allocating memory by performing the sum of the operators. """ @@ -47,3 +47,7 @@ end end return y end + +function liouvillian(A::OperatorSum, Id_cache = I(prod(A.operators[1].dims))) + return OperatorSum(A.coefficients, liouvillian.(A.operators, Ref(Id_cache))) +end diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 95a591f58..64cc2bdc4 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -139,6 +139,10 @@ function mesolveProblem( is_empty_e_ops = isempty(e_ops) end + if H_t isa TimeDependentOperatorSum + H_t = liouvillian(H_t) + end + p = ( L = L, progr = progr, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 6751a3da1..b0ba76719 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -140,6 +140,10 @@ end return mul!(y, A.operator_sum, x, α, β) end +function liouvillian(A::TimeDependentOperatorSum, Id_cache = I(prod(A.operator_sum.operators[1].dims))) + return TimeDependentOperatorSum(A.coefficient_functions, liouvillian(A.operator_sum, Id_cache)) +end + ####################################### ### LIOUVILLIAN ### diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index 924cc0907..8bc4ee0c9 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -63,7 +63,7 @@ e_ops = [a_d * a] psi0 = fock(N, 3) t_l = LinRange(0, 100 * 2π, 1000) - H_t_f = TimeDependentOperatorSum([(t, p) -> sin(t)], [liouvillian(H_t)]) + H_t_f = TimeDependentOperatorSum((((t, p) -> sin(t)),), [H_t]) # It will be converted to liouvillian internally sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, H_t = H_t_f, progress_bar = Val(false)) ρ_ss1 = steadystate_floquet(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetLinearSystem())[1] ρ_ss2 = From fd098b3e0e9a439d245fa118a76eedcf1760ae4b Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:44:12 +0200 Subject: [PATCH 026/329] Change version to v0.14.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 13503d4ee..067c9222f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Luca Gravina", "Yi-Te Huang"] -version = "0.14.0" +version = "0.14.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From a13454dac12d2c15925e48d7730968d32933de27 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:37:52 +0800 Subject: [PATCH 027/329] add command `all` to Makefile (#236) --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 920c480ff..1da025146 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,13 @@ format: test: ${JULIA} --project -e 'using Pkg; Pkg.resolve(); Pkg.test()' +all: format test docs + help: @echo "The following make commands are available:" @echo " - make docs: instantiate and build the documentation" @echo " - make format: format codes with JuliaFormatter" @echo " - make test: run the tests" + @echo " - make all: run every commands above" -.PHONY: default docs format test help \ No newline at end of file +.PHONY: default docs format test all help \ No newline at end of file From d47e9e92697337631af02f0dbf189cc132426c0c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 27 Sep 2024 22:00:00 +0800 Subject: [PATCH 028/329] remove code quality tests under Julia nightly (#237) --- .github/workflows/CI-Julia-nightly.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/CI-Julia-nightly.yml b/.github/workflows/CI-Julia-nightly.yml index 389e2d3fd..1e23477eb 100644 --- a/.github/workflows/CI-Julia-nightly.yml +++ b/.github/workflows/CI-Julia-nightly.yml @@ -46,7 +46,6 @@ jobs: - 'x64' group: - 'Core' - - 'Code-Quality' steps: - uses: actions/checkout@v4 From 9172e829f1f35f5007373e20b470a90ca7282849 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:18:34 +0200 Subject: [PATCH 029/329] Implement Stochastic Schrodinger equation (#238) * Implement Stochastic Schrodinger equation * Minor changes * Minor changes on the import * Fix wrong link * Fix instabilities * Avoid safety_copy --- Project.toml | 4 + docs/src/api.md | 8 +- src/QuantumToolbox.jl | 4 + src/qobj/operator_sum.jl | 2 +- src/time_evolution/ssesolve.jl | 416 +++++++++++++++++++++++++++ src/time_evolution/time_evolution.jl | 49 +++- test/core-test/time_evolution.jl | 29 +- 7 files changed, 507 insertions(+), 5 deletions(-) create mode 100644 src/time_evolution/ssesolve.jl diff --git a/Project.toml b/Project.toml index 067c9222f..49f0ee0b0 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ version = "0.14.1" ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def" +DiffEqNoiseProcess = "77a26b50-5914-5dd7-bc55-306e6241c503" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895" @@ -22,6 +23,7 @@ SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" [weakdeps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" @@ -34,6 +36,7 @@ ArrayInterface = "6, 7" CUDA = "5" DiffEqBase = "6" DiffEqCallbacks = "2 - 3.1, 3.8" +DiffEqNoiseProcess = "5" FFTW = "1.5" Graphs = "1.7" IncompleteLU = "0.2" @@ -49,6 +52,7 @@ SciMLOperators = "0.3" SparseArrays = "<0.0.1, 1" SpecialFunctions = "2" StaticArraysCore = "1" +StochasticDiffEq = "6" Test = "<0.0.1, 1" julia = "1.10" diff --git a/docs/src/api.md b/docs/src/api.md index 20c35bd29..bf0c796a9 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -175,18 +175,22 @@ qeye ```@docs TimeEvolutionSol TimeEvolutionMCSol +TimeEvolutionSSESol sesolveProblem mesolveProblem -lr_mesolveProblem mcsolveProblem mcsolveEnsembleProblem +ssesolveProblem +ssesolveEnsembleProblem +lr_mesolveProblem sesolve mesolve -lr_mesolve mcsolve +ssesolve dfd_mesolve dsf_mesolve dsf_mcsolve +lr_mesolve liouvillian liouvillian_generalized steadystate diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index d6735644d..1822ff09b 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -22,18 +22,21 @@ import SciMLBase: remake, u_modified!, ODEProblem, + SDEProblem, EnsembleProblem, EnsembleThreads, FullSpecialize, CallbackSet, ContinuousCallback, DiscreteCallback +import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 import SciMLOperators: MatrixOperator import LinearSolve: LinearProblem, SciMLLinearSolveAlgorithm, KrylovJL_MINRES, KrylovJL_GMRES import DiffEqBase: get_tstops import DiffEqCallbacks: PeriodicCallback, PresetTimeCallback, TerminateSteadyState import OrdinaryDiffEqCore: OrdinaryDiffEqAlgorithm import OrdinaryDiffEqTsit5: Tsit5 +import DiffEqNoiseProcess: RealWienerProcess # other dependencies (in alphabetical order) import ArrayInterface: allowed_getindex, allowed_setindex! @@ -73,6 +76,7 @@ include("time_evolution/mesolve.jl") include("time_evolution/lr_mesolve.jl") include("time_evolution/sesolve.jl") include("time_evolution/mcsolve.jl") +include("time_evolution/ssesolve.jl") include("time_evolution/time_evolution_dynamical.jl") # Others diff --git a/src/qobj/operator_sum.jl b/src/qobj/operator_sum.jl index d19b4eac7..909e27dbb 100644 --- a/src/qobj/operator_sum.jl +++ b/src/qobj/operator_sum.jl @@ -18,7 +18,7 @@ struct OperatorSum{CT<:Vector{<:Number},OT<:AbstractVector} <: AbstractQuantumOb mapreduce(x -> size(x) == size_1, &, operators) || throw(DimensionMismatch("All the operators must have the same dimensions.")) T = promote_type( - mapreduce(x -> eltype(x.data), promote_type, operators), + mapreduce(x -> eltype(x), promote_type, operators), mapreduce(eltype, promote_type, coefficients), ) coefficients2 = T.(coefficients) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl new file mode 100644 index 000000000..d9fcbe34c --- /dev/null +++ b/src/time_evolution/ssesolve.jl @@ -0,0 +1,416 @@ +export ssesolveProblem, ssesolveEnsembleProblem, ssesolve + +#TODO: Check if works in GPU +function _ssesolve_update_coefficients!(ψ, coefficients, sc_ops) + _get_en = op -> real(dot(ψ, op, ψ)) #this is en/2: /2 = Re + @. coefficients[2:end-1] = _get_en(sc_ops) #coefficients of the OperatorSum: Σ Sn * en/2 + coefficients[end] = -sum(x -> x^2, coefficients[2:end-1]) / 2 #this last coefficient is -Σen^2/8 + return nothing +end + +function ssesolve_drift!(du, u, p, t) + _ssesolve_update_coefficients!(u, p.K.coefficients, p.sc_ops) + + mul!(du, p.K, u) + + return nothing +end + +function ssesolve_diffusion!(du, u, p, t) + @inbounds en = @view(p.K.coefficients[2:end-1]) + + # du:(H,W). du_reshaped:(H*W,). + # H:Hilbert space dimension, W: number of sc_ops + du_reshaped = reshape(du, :) + mul!(du_reshaped, p.D, u) #du[:,i] = D[i] * u + + du .-= u .* reshape(en, 1, :) #du[:,i] -= en[i] * u + + return nothing +end + +function _ssesolve_prob_func(prob, i, repeat) + internal_params = prob.p + + noise = RealWienerProcess( + prob.tspan[1], + zeros(length(internal_params.sc_ops)), + zeros(length(internal_params.sc_ops)), + save_everystep = false, + ) + + noise_rate_prototype = similar(prob.u0, length(prob.u0), length(internal_params.sc_ops)) + + prm = merge( + internal_params, + ( + expvals = similar(internal_params.expvals), + progr = ProgressBar(size(internal_params.expvals, 2), enable = false), + ), + ) + + return remake(prob, p = prm, noise = noise, noise_rate_prototype = noise_rate_prototype) +end + +_ssesolve_output_func(sol, i) = (sol, false) + +function _ssesolve_generate_statistics!(sol, i, states, expvals_all) + sol_i = sol[:, i] + !isempty(sol_i.prob.kwargs[:saveat]) ? + states[i] = [QuantumObject(sol_i.u[i], dims = sol_i.prob.p.Hdims) for i in 1:length(sol_i.u)] : nothing + + copyto!(view(expvals_all, i, :, :), sol_i.prob.p.expvals) + return nothing +end + +@doc raw""" + ssesolveProblem(H::QuantumObject, + ψ0::QuantumObject, + tlist::AbstractVector; + sc_ops::Union{Nothing,AbstractVector}=nothing; + alg::StochasticDiffEqAlgorithm=SRA1() + e_ops::Union{Nothing,AbstractVector} = nothing, + H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, + params::NamedTuple=NamedTuple(), + progress_bar::Union{Val,Bool}=Val(true), + kwargs...) + +Generates the SDEProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: + + ```math + d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) + ``` + +where + +```math + K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), + ``` + ```math + M_n = C_n - \frac{e_n}{2}, + ``` + and + ```math + e_n = \langle C_n + C_n^\dagger \rangle. + ``` + +Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. + +# Arguments + +- `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. +- `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. +- `tlist::AbstractVector`: The time list of the evolution. +- `sc_ops::Union{Nothing,AbstractVector}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. +- `alg::StochasticDiffEqAlgorithm`: The algorithm used for the time evolution. +- `e_ops::Union{Nothing,AbstractVector}=nothing`: The list of operators to be evaluated during the evolution. +- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. +- `params::NamedTuple`: The parameters of the system. +- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs...`: The keyword arguments passed to the `SDEProblem` constructor. + +# Notes + +- The states will be saved depend on the keyword argument `saveat` in `kwargs`. +- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. +- For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) +- For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) + +# Returns + +- `prob`: The `SDEProblem` for the Stochastic Schrödinger time evolution of the system. +""" +function ssesolveProblem( + H::QuantumObject{MT1,OperatorQuantumObject}, + ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + tlist::AbstractVector, + sc_ops::Union{Nothing,AbstractVector} = nothing; + alg::StochasticDiffEqAlgorithm = SRA1(), + e_ops::Union{Nothing,AbstractVector} = nothing, + H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + params::NamedTuple = NamedTuple(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., +) where {MT1<:AbstractMatrix,T2} + H.dims != ψ0.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + + haskey(kwargs, :save_idxs) && + throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) + + sc_ops isa Nothing && + throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) + + !(H_t isa Nothing) && throw(ArgumentError("Time-dependent Hamiltonians are not currently supported in ssesolve.")) + + progress_bar_val = makeVal(progress_bar) + + t_l = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for StochasticDiffEq.jl + + ϕ0 = get_data(ψ0) + + H_eff = get_data(H - T2(0.5im) * mapreduce(op -> op' * op, +, sc_ops)) + sc_ops2 = get_data.(sc_ops) + + coefficients = [1.0, fill(0.0, length(sc_ops) + 1)...] + operators = [-1im * H_eff, sc_ops2..., MT1(I(prod(H.dims)))] + K = OperatorSum(coefficients, operators) + _ssesolve_update_coefficients!(ϕ0, K.coefficients, sc_ops2) + + D = reduce(vcat, sc_ops2) + + progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) + + if e_ops isa Nothing + expvals = Array{ComplexF64}(undef, 0, length(t_l)) + e_ops2 = MT1[] + is_empty_e_ops = true + else + expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) + e_ops2 = get_data.(e_ops) + is_empty_e_ops = isempty(e_ops) + end + + p = ( + K = K, + D = D, + e_ops = e_ops2, + sc_ops = sc_ops2, + expvals = expvals, + progr = progr, + Hdims = H.dims, + H_t = H_t, + is_empty_e_ops = is_empty_e_ops, + params..., + ) + + saveat = e_ops isa Nothing ? t_l : [t_l[end]] + default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) + kwargs2 = merge(default_values, kwargs) + kwargs3 = _generate_sesolve_kwargs(e_ops, progress_bar_val, t_l, kwargs2) + + tspan = (t_l[1], t_l[end]) + noise = RealWienerProcess(t_l[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false) + noise_rate_prototype = similar(ϕ0, length(ϕ0), length(sc_ops)) + return SDEProblem{true}( + ssesolve_drift!, + ssesolve_diffusion!, + ϕ0, + tspan, + p; + noise_rate_prototype = noise_rate_prototype, + noise = noise, + kwargs3..., + ) +end + +@doc raw""" + ssesolveEnsembleProblem(H::QuantumObject, + ψ0::QuantumObject, + tlist::AbstractVector; + sc_ops::Union{Nothing,AbstractVector} = nothing; + alg::StochasticDiffEqAlgorithm=SRA1() + e_ops::Union{Nothing,AbstractVector} = nothing, + H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, + params::NamedTuple=NamedTuple(), + prob_func::Function=_mcsolve_prob_func, + output_func::Function=_mcsolve_output_func, + kwargs...) + +Generates the SDE EnsembleProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: + + ```math + d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) + ``` + +where + +```math + K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), + ``` + ```math + M_n = C_n - \frac{e_n}{2}, + ``` + and + ```math + e_n = \langle C_n + C_n^\dagger \rangle. + ``` + +Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. + +# Arguments + +- `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. +- `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. +- `tlist::AbstractVector`: The time list of the evolution. +- `sc_ops::Union{Nothing,AbstractVector}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. +- `alg::StochasticDiffEqAlgorithm`: The algorithm used for the time evolution. +- `e_ops::Union{Nothing,AbstractVector}=nothing`: The list of operators to be evaluated during the evolution. +- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. +- `params::NamedTuple`: The parameters of the system. +- `prob_func::Function`: Function to use for generating the SDEProblem. +- `output_func::Function`: Function to use for generating the output of a single trajectory. +- `kwargs...`: The keyword arguments passed to the `SDEProblem` constructor. + +# Notes + +- The states will be saved depend on the keyword argument `saveat` in `kwargs`. +- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. +- For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) +- For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) + +# Returns + +- `prob::EnsembleProblem with SDEProblem`: The Ensemble SDEProblem for the Stochastic Shrödinger time evolution. +""" +function ssesolveEnsembleProblem( + H::QuantumObject{MT1,OperatorQuantumObject}, + ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + tlist::AbstractVector, + sc_ops::Union{Nothing,AbstractVector} = nothing; + alg::StochasticDiffEqAlgorithm = SRA1(), + e_ops::Union{Nothing,AbstractVector} = nothing, + H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + params::NamedTuple = NamedTuple(), + prob_func::Function = _ssesolve_prob_func, + output_func::Function = _ssesolve_output_func, + kwargs..., +) where {MT1<:AbstractMatrix,T2} + prob_sse = ssesolveProblem(H, ψ0, tlist, sc_ops; alg = alg, e_ops = e_ops, H_t = H_t, params = params, kwargs...) + + ensemble_prob = EnsembleProblem(prob_sse, prob_func = prob_func, output_func = output_func, safetycopy = false) + + return ensemble_prob +end + +@doc raw""" + ssesolve(H::QuantumObject, + ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + tlist::AbstractVector, + sc_ops::Union{Nothing, AbstractVector}=nothing; + alg::StochasticDiffEqAlgorithm=SRA1(), + e_ops::Union{Nothing,AbstractVector}=nothing, + H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, + params::NamedTuple=NamedTuple(), + n_traj::Int=1, + ensemble_method=EnsembleThreads(), + prob_func::Function=_mcsolve_prob_func, + output_func::Function=_mcsolve_output_func, + kwargs...) + + +Stochastic Schrödinger equation evolution of a quantum system given the system Hamiltonian ``\hat{H}`` and a list of stochadtic collapse (jump) operators ``\{\hat{C}_n\}_n``. +The stochastic evolution of the state ``|\psi(t)\rangle`` is defined by: + + ```math + d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) + ``` + +where + +```math + K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), + ``` + ```math + M_n = C_n - \frac{e_n}{2}, + ``` + and + ```math + e_n = \langle C_n + C_n^\dagger \rangle. + ``` + +Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. + + +# Arguments + +- `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. +- `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist::AbstractVector`: List of times at which to save the state of the system. +- `sc_ops::Union{Nothing,AbstractVector}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. +- `alg::StochasticDiffEqAlgorithm`: Algorithm to use for the time evolution. +- `e_ops::Union{Nothing,AbstractVector}`: List of operators for which to calculate expectation values. +- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. +- `params::NamedTuple`: Dictionary of parameters to pass to the solver. +- `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. +- `n_traj::Int`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. +- `prob_func::Function`: Function to use for generating the SDEProblem. +- `output_func::Function`: Function to use for generating the output of a single trajectory. +- `kwargs...`: Additional keyword arguments to pass to the solver. + +# Notes + +- `ensemble_method` can be one of `EnsembleThreads()`, `EnsembleSerial()`, `EnsembleDistributed()` +- The states will be saved depend on the keyword argument `saveat` in `kwargs`. +- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. +- For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) +- For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) + +# Returns + +- `sol::TimeEvolutionSSESol`: The solution of the time evolution. See also [`TimeEvolutionSSESol`](@ref) +""" +function ssesolve( + H::QuantumObject{MT1,OperatorQuantumObject}, + ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + tlist::AbstractVector, + sc_ops::Union{Nothing,AbstractVector} = nothing; + alg::StochasticDiffEqAlgorithm = SRA1(), + e_ops::Union{Nothing,AbstractVector} = nothing, + H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + params::NamedTuple = NamedTuple(), + n_traj::Int = 1, + ensemble_method = EnsembleThreads(), + prob_func::Function = _ssesolve_prob_func, + output_func::Function = _ssesolve_output_func, + kwargs..., +) where {MT1<:AbstractMatrix,T2} + ens_prob = ssesolveEnsembleProblem( + H, + ψ0, + tlist, + sc_ops; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = params, + prob_func = prob_func, + output_func = output_func, + kwargs..., + ) + + return ssesolve(ens_prob; alg = alg, n_traj = n_traj, ensemble_method = ensemble_method) +end + +function ssesolve( + ens_prob::EnsembleProblem; + alg::StochasticDiffEqAlgorithm = SRA1(), + n_traj::Int = 1, + ensemble_method = EnsembleThreads(), +) + sol = solve(ens_prob, alg, ensemble_method, trajectories = n_traj) + _sol_1 = sol[:, 1] + + expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) + states = + isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : + Vector{Vector{QuantumObject}}(undef, length(sol)) + + foreach(i -> _ssesolve_generate_statistics!(sol, i, states, expvals_all), eachindex(sol)) + expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) + + return TimeEvolutionSSESol( + n_traj, + _sol_1.t, + states, + expvals, + expvals_all, + sol.converged, + _sol_1.alg, + _sol_1.prob.kwargs[:abstol], + _sol_1.prob.kwargs[:reltol], + ) +end diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index b0ba76719..bd60ede23 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -1,9 +1,10 @@ export TimeDependentOperatorSum -export TimeEvolutionSol, TimeEvolutionMCSol +export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionSSESol export liouvillian, liouvillian_floquet, liouvillian_generalized const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true) +const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true) @doc raw""" struct TimeEvolutionSol @@ -95,6 +96,52 @@ function Base.show(io::IO, sol::TimeEvolutionMCSol) return nothing end +@doc raw""" + struct TimeEvolutionSSESol + A structure storing the results and some information from solving trajectories of the Stochastic Shrodinger equation time evolution. + # Fields (Attributes) + - `n_traj::Int`: Number of trajectories + - `times::AbstractVector`: The time list of the evolution in each trajectory. + - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. + - `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. + - `expect_all::Array`: The expectation values corresponding to each trajectory and each time point in `times` + - `converged::Bool`: Whether the solution is converged or not. + - `alg`: The algorithm which is used during the solving process. + - `abstol::Real`: The absolute tolerance which is used during the solving process. + - `reltol::Real`: The relative tolerance which is used during the solving process. + """ +struct TimeEvolutionSSESol{ + TT<:Vector{<:Real}, + TS<:AbstractVector, + TE<:Matrix{ComplexF64}, + TEA<:Array{ComplexF64,3}, + T1<:Real, + T2<:Real, +} + n_traj::Int + times::TT + states::TS + expect::TE + expect_all::TEA + converged::Bool + alg::StochasticDiffEqAlgorithm + abstol::T1 + reltol::T2 +end + +function Base.show(io::IO, sol::TimeEvolutionSSESol) + print(io, "Solution of quantum trajectories\n") + print(io, "(converged: $(sol.converged))\n") + print(io, "--------------------------------\n") + print(io, "num_trajectories = $(sol.n_traj)\n") + print(io, "num_states = $(length(sol.states[1]))\n") + print(io, "num_expect = $(size(sol.expect, 1))\n") + print(io, "SDE alg.: $(sol.alg)\n") + print(io, "abstol = $(sol.abstol)\n") + print(io, "reltol = $(sol.reltol)\n") + return nothing +end + abstract type LindbladJumpCallbackType end struct ContinuousLindbladJumpCallback <: LindbladJumpCallbackType diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index f86d0bfe6..3d0546983 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -42,7 +42,7 @@ end end - @testset "mesolve and mcsolve" begin + @testset "mesolve, mcsolve, and ssesolve" begin N = 10 a = destroy(N) a_d = a' @@ -56,6 +56,7 @@ sol_me3 = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) sol_mc = mcsolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) sol_mc_states = mcsolve(H, psi0, t_l, c_ops, n_traj = 500, saveat = t_l, progress_bar = Val(false)) + sol_sse = ssesolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) @@ -63,8 +64,10 @@ sol_me_string = sprint((t, s) -> show(t, "text/plain", s), sol_me) sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) + sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(t_l) < 0.1 @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect))) / length(t_l) < 0.1 + @test sum(abs.(sol_sse.expect .- sol_me.expect)) / length(t_l) < 0.1 @test length(sol_me.states) == 1 @test size(sol_me.expect) == (length(e_ops), length(t_l)) @test length(sol_me2.states) == length(t_l) @@ -90,6 +93,16 @@ "ODE alg.: $(sol_mc.alg)\n" * "abstol = $(sol_mc.abstol)\n" * "reltol = $(sol_mc.reltol)\n" + @test sol_sse_string == + "Solution of quantum trajectories\n" * + "(converged: $(sol_sse.converged))\n" * + "--------------------------------\n" * + "num_trajectories = $(sol_sse.n_traj)\n" * + "num_states = $(length(sol_sse.states[1]))\n" * + "num_expect = $(size(sol_sse.expect, 1))\n" * + "SDE alg.: $(sol_sse.alg)\n" * + "abstol = $(sol_sse.abstol)\n" * + "reltol = $(sol_sse.reltol)\n" @testset "Type Inference mesolve" begin @inferred mesolveProblem(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) @@ -115,6 +128,20 @@ @inferred mcsolve(H, psi0, [0, 10], c_ops, n_traj = 500, progress_bar = Val(false)) @inferred mcsolve(H, Qobj(zeros(Int64, N)), t_l, c_ops, n_traj = 500, progress_bar = Val(false)) end + + @testset "Type Inference ssesolve" begin + @inferred ssesolveEnsembleProblem( + H, + psi0, + t_l, + c_ops, + n_traj = 500, + e_ops = e_ops, + progress_bar = Val(false), + ) + @inferred ssesolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) + @inferred ssesolve(H, psi0, t_l, c_ops, n_traj = 500, progress_bar = Val(true)) + end end @testset "exceptions" begin From d6342342964fe58e1014abf693ce9c8be2473168 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:11:41 +0200 Subject: [PATCH 030/329] Change to version v0.15.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 49f0ee0b0..cec0d14d7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Luca Gravina", "Yi-Te Huang"] -version = "0.14.1" +version = "0.15.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From c4df4976b2b018c6b780fbda23441081fcda0a7d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 10:51:56 +0200 Subject: [PATCH 031/329] CompatHelper: bump compat for DiffEqCallbacks to 4, (keep existing compat) (#239) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index cec0d14d7..b46f4e747 100644 --- a/Project.toml +++ b/Project.toml @@ -35,7 +35,7 @@ QuantumToolboxCUDAExt = "CUDA" ArrayInterface = "6, 7" CUDA = "5" DiffEqBase = "6" -DiffEqCallbacks = "2 - 3.1, 3.8" +DiffEqCallbacks = "2 - 3.1, 3.8, 4" DiffEqNoiseProcess = "5" FFTW = "1.5" Graphs = "1.7" From bdac5f4ac254830eaf9b902d3273b87dd1d7dc0f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 28 Sep 2024 17:58:20 +0800 Subject: [PATCH 032/329] [Docs] Update docstring and documentation for `steadystate` (#240) * add missing docstrings for `steadystate` solvers * update documentation for `steadystate` * format files * improve `steadystate` documentation --- docs/make.jl | 2 +- docs/src/api.md | 3 + docs/src/users_guide/steadystate.md | 124 +++++++++++++++ src/steadystate.jl | 228 +++++++++++++++++----------- test/core-test/steady_state.jl | 1 + 5 files changed, 267 insertions(+), 91 deletions(-) create mode 100644 docs/src/users_guide/steadystate.md diff --git a/docs/make.jl b/docs/make.jl index 9255bca50..95c9eaed7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -34,7 +34,7 @@ const PAGES = [ "Time Evolution and Dynamics" => [ "Introduction" => "users_guide/time_evolution/intro.md", ], - "Solving for Steady-State Solutions" => [], + "Solving for Steady-State Solutions" => "users_guide/steadystate.md", "Symmetries" => [], "Two-time correlation functions" => [], "Extensions" => [ diff --git a/docs/src/api.md b/docs/src/api.md index bf0c796a9..6d5fdb11e 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -195,6 +195,9 @@ liouvillian liouvillian_generalized steadystate steadystate_floquet +SteadyStateDirectSolver +SteadyStateEigenSolver +SteadyStateLinearSolver SteadyStateODESolver ``` diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md new file mode 100644 index 000000000..0eec4f190 --- /dev/null +++ b/docs/src/users_guide/steadystate.md @@ -0,0 +1,124 @@ +# [Solving for Steady-State Solutions](@id doc:Solving-for-Steady-State-Solutions) + +## Introduction + +For time-independent open quantum systems with decay rates larger than the corresponding excitation rates, the system will tend toward a steady state as ``t\rightarrow\infty`` that satisfies the equation + +```math +\frac{d\hat{\rho}_{\textrm{ss}}}{dt} = \mathcal{L}\hat{\rho}_{\textrm{ss}}=0. +``` + +Although the requirement for time-independence seems quite restrictive, one can often employ a transformation to the interaction picture that yields a time-independent Hamiltonian. For many these systems, solving for the asymptotic density matrix ``\hat{\rho}_{\textrm{ss}}`` can be achieved using direct or iterative solution methods faster than using master equation or Monte-Carlo simulations. Although the steady state equation has a simple mathematical form, the properties of the Liouvillian operator are such that the solutions to this equation are anything but straightforward to find. + +## Steady State solvers in `QuantumToolbox.jl` +In `QuantumToolbox.jl`, the steady-state solution for a system Hamiltonian or Liouvillian is given by [`steadystate`](@ref). This function implements a number of different methods for finding the steady state, each with their own pros and cons, where the method used can be chosen using the `solver` keyword argument. + +| **Solver** | **Description** | +|:-----------|:----------------| +| [`SteadyStateDirectSolver()`](@ref SteadyStateDirectSolver) | Directly solve ``Ax=b`` using the standard method given in `Julia` `LinearAlgebra` | +| [`SteadyStateLinearSolver()`](@ref SteadyStateLinearSolver) | Directly solve ``Ax=b`` using the algorithms given in [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/) | +| [`SteadyStateEigenSolver()`](@ref SteadyStateEigenSolver) | Find the zero (or lowest) eigenvalue of ``\mathcal{L}`` using [`eigsolve`](@ref) | +| [`SteadyStateODESolver()`](@ref SteadyStateODESolver) | Solving time evolution with algorithms given in [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) | + +## Using Steady State solvers + +The function [`steadystate`](@ref) can take either a Hamiltonian and a list of collapse operators `c_ops` as input, generating internally the corresponding Liouvillian ``\mathcal{L}`` in Lindblad form, or alternatively, a Liouvillian ``\mathcal{L}`` passed by the user. + +```julia +ρ_ss = steadystate(H) # Hamiltonian +ρ_ss = steadystate(H, c_ops) # Hamiltonian and collapse operators +ρ_ss = steadystate(L) # Liouvillian +``` +where `H` is a quantum object representing the system Hamiltonian ([`Operator`](@ref)) or Liouvillian ([`SuperOperator`](@ref)), and `c_ops` (defaults to `nothing`) can be a list of [`QuantumObject`](@ref) for the system collapse operators. The output, labelled as `ρ_ss`, is the steady-state solution for the systems. If no other keywords are passed to the function, the default solver [`SteadyStateDirectSolver()`](@ref SteadyStateDirectSolver) is used. + +To change a solver, use the keyword argument `solver`, for example: + +```julia +ρ_ss = steadystate(H, c_ops; solver = SteadyStateLinearSolver()) +``` + +!!! note "Initial state for `SteadyStateODESolver()`" + It is necessary to provide the initial state `ψ0` for ODE solving method, namely + `steadystate(H, ψ0, tspan, c_ops, solver = SteadyStateODESolver())`, where `tspan::Real` represents the final time step, defaults to `Inf` (infinity). + +Although it is not obvious, the [`SteadyStateDirectSolver()`](@ref SteadyStateDirectSolver) and [`SteadyStateEigenSolver()`](@ref SteadyStateEigenSolver) methods all use an LU decomposition internally and thus can have a large memory overhead. In contrast, for [`SteadyStateLinearSolver()`](@ref SteadyStateLinearSolver), iterative algorithms provided by [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/solvers/solvers/), such as `KrylovJL_GMRES()` and `KrylovJL_BICGSTAB()`, do not factor the matrix and thus take less memory than the LU methods and allow, in principle, for extremely large system sizes. The downside is that these methods can take much longer than the direct method as the condition number of the Liouvillian matrix is large, indicating that these iterative methods require a large number of iterations for convergence. + +To overcome this, one can provide preconditioner that solves for an approximate inverse for the (modified) Liouvillian, thus better conditioning the problem, leading to faster convergence. The left and right preconditioner can be specified as the keyword argument `Pl` and `Pr`, respectively: +```julia +solver = SteadyStateLinearSolver(alg = KrylovJL_GMRES(), Pl = Pl, Pr = Pr) +``` +The use of a preconditioner can actually make these iterative methods faster than the other solution methods. The problem with precondioning is that it is only well defined for Hermitian matrices. Since the Liouvillian is non-Hermitian, the ability to find a good preconditioner is not guaranteed. Moreover, if a preconditioner is found, it is not guaranteed to have a good condition number. + +Furthermore, `QuantiumToolbox` can take advantage of the Intel (Pardiso) LU solver in the Intel Math Kernel library (Intel-MKL) by using [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/) and either [`Pardiso.jl`](https://github.com/JuliaSparse/Pardiso.jl) or [`MKL_jll.jl`](https://github.com/JuliaBinaryWrappers/MKL_jll.jl): + +```julia +using QuantumToolbox +using LinearSolve # must be loaded + +using Pardiso +solver = SteadyStateLinearSolver(alg = MKLPardisoFactorize()) + +using MKL_jll +solver = SteadyStateLinearSolver(alg = MKLLUFactorization()) +``` + +See [`LinearSolve.jl` Solvers](https://docs.sciml.ai/LinearSolve/stable/solvers/solvers/) for more details. + +## Example: Harmonic Oscillator in Thermal Bath + +```@example steady_state_example +using QuantumToolbox +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) + + +# Define parameters +N = 20 # number of basis states to consider +a = destroy(N) +H = a' * a +ψ0 = basis(N, 10) # initial state +κ = 0.1 # coupling to oscillator +n_th = 2 # temperature with average of 2 excitations + +# collapse operators +# c_op_list = [ emission ; absorption ] +c_op_list = [ sqrt(κ * (1 + n_th)) * a ; sqrt(κ * n_th) * a' ] + +# find steady-state solution +ρ_ss = steadystate(H, c_op_list) + +# find expectation value for particle number in steady state +e_ops = [a' * a] +exp_ss = real(expect(e_ops[1], ρ_ss)) + +tlist = LinRange(0, 50, 100) + +# monte-carlo +sol_mc = mcsolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, n_traj=100, progress_bar=false) +exp_mc = real(sol_mc.expect[1, :]) + +# master eq. +sol_me = mesolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, progress_bar=false) +exp_me = real(sol_me.expect[1, :]) + +# plot the results +fig = Figure(size = (800, 400), fontsize = 15) +ax = Axis(fig[1, 1], + title = L"Decay of Fock state $|10\rangle$ in a thermal environment with $\langle n\rangle=2$", + xlabel = "Time", + ylabel = "Number of excitations", + titlesize = 24, + xlabelsize = 20, + ylabelsize = 20 +) +lines!(ax, tlist, exp_mc, label = "Monte-Carlo", linewidth = 2, color = :blue) +lines!(ax, tlist, exp_me, label = "Master Equation", linewidth = 2, color = :orange, linestyle = :dash) +lines!(ax, tlist, exp_ss .* ones(length(tlist)), label = "Steady State", linewidth = 2, color = :red) +axislegend(ax, position = :rt) + +fig +``` + +## Calculate steady state for periodically driven systems + +See the docstring of [`steadystate_floquet`](@ref) for more details. diff --git a/src/steadystate.jl b/src/steadystate.jl index 080f17281..ab60695e5 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -1,9 +1,9 @@ export steadystate, steadystate_floquet export SteadyStateSolver, - SteadyStateODESolver, - SteadyStateLinearSolver, - SteadyStateEigenSolver, SteadyStateDirectSolver, + SteadyStateEigenSolver, + SteadyStateLinearSolver, + SteadyStateODESolver, SteadyStateFloquetSolver, SSFloquetLinearSystem, SSFloquetEffectiveLiouvillian @@ -12,25 +12,29 @@ abstract type SteadyStateSolver end abstract type SteadyStateFloquetSolver end @doc raw""" - SteadyStateODESolver{::OrdinaryDiffEqAlgorithm} + SteadyStateDirectSolver() -A structure representing an ordinary differential equation (ODE) solver for solving [`steadystate`](@ref) +A solver which solves [`steadystate`](@ref) by finding the inverse of Liouvillian [`SuperOperator`](@ref) using the standard method given in `LinearAlgebra`. +""" +struct SteadyStateDirectSolver <: SteadyStateSolver end -It includes a field (attribute) `SteadyStateODESolver.alg` that specifies the solving algorithm. Default to `Tsit5()`. +@doc raw""" + SteadyStateEigenSolver() -For more details about the solvers, please refer to [`OrdinaryDiffEq.jl`](https://docs.sciml.ai/OrdinaryDiffEq/stable/) +A solver which solves [`steadystate`](@ref) by finding the zero (or lowest) eigenvalue of Liouvillian [`SuperOperator`](@ref) using [`eigsolve`](@ref). """ -Base.@kwdef struct SteadyStateODESolver{MT<:OrdinaryDiffEqAlgorithm} <: SteadyStateSolver - alg::MT = Tsit5() -end +struct SteadyStateEigenSolver <: SteadyStateSolver end -struct SSFloquetLinearSystem <: SteadyStateFloquetSolver end -Base.@kwdef struct SSFloquetEffectiveLiouvillian{SSST<:SteadyStateSolver} <: SteadyStateFloquetSolver - steadystate_solver::SSST = SteadyStateDirectSolver() -end +@doc raw""" + SteadyStateLinearSolver(alg = KrylovJL_GMRES(), Pl = nothing, Pr = nothing) -struct SteadyStateDirectSolver <: SteadyStateSolver end -struct SteadyStateEigenSolver <: SteadyStateSolver end +A solver which solves [`steadystate`](@ref) by finding the inverse of Liouvillian [`SuperOperator`](@ref) using the `alg`orithms given in [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/). + +# Parameters +- `alg::SciMLLinearSolveAlgorithm=KrylovJL_GMRES()`: algorithms given in [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/) +- `Pl::Union{Function,Nothing}=nothing`: left preconditioner, see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for more details. +- `Pr::Union{Function,Nothing}=nothing`: right preconditioner, see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for more details. +""" Base.@kwdef struct SteadyStateLinearSolver{ MT<:Union{SciMLLinearSolveAlgorithm,Nothing}, PlT<:Union{Function,Nothing}, @@ -42,90 +46,39 @@ Base.@kwdef struct SteadyStateLinearSolver{ end @doc raw""" - steadystate( - H::QuantumObject{MT1,HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, - tspan::Real = Inf, - c_ops::Union{Nothing,AbstractVector} = nothing; - solver::SteadyStateODESolver = SteadyStateODESolver(), - reltol::Real = 1.0e-8, - abstol::Real = 1.0e-10, - kwargs..., - ) + SteadyStateODESolver(alg = Tsit5()) -Solve the stationary state based on time evolution (ordinary differential equations; `OrdinaryDiffEq.jl`) with a given initial state. +An ordinary differential equation (ODE) solver for solving [`steadystate`](@ref). -The termination condition of the stationary state ``|\rho\rangle\rangle`` is that either the following condition is `true`: +It includes a field (attribute) `SteadyStateODESolver.alg` that specifies the solving algorithm. Default to `Tsit5()`. -```math -\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{reltol} \times\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}+|\hat{\rho}\rangle\rangle\rVert -``` +For more details about the solvers, please refer to [`OrdinaryDiffEq.jl`](https://docs.sciml.ai/OrdinaryDiffEq/stable/) +""" +Base.@kwdef struct SteadyStateODESolver{MT<:OrdinaryDiffEqAlgorithm} <: SteadyStateSolver + alg::MT = Tsit5() +end -or +struct SSFloquetLinearSystem <: SteadyStateFloquetSolver end +Base.@kwdef struct SSFloquetEffectiveLiouvillian{SSST<:SteadyStateSolver} <: SteadyStateFloquetSolver + steadystate_solver::SSST = SteadyStateDirectSolver() +end -```math -\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{abstol} -``` +@doc raw""" + steadystate( + H::QuantumObject, + c_ops::Union{Nothing,AbstractVector} = nothing; + solver::SteadyStateSolver = SteadyStateDirectSolver(), + kwargs... + ) + +Solve the stationary state based on different solvers. # Parameters - `H::QuantumObject`: The Hamiltonian or the Liouvillian of the system. -- `ψ0::QuantumObject`: The initial state of the system. -- `tspan::Real=Inf`: The final time step for the steady state problem. - `c_ops::Union{Nothing,AbstractVector}=nothing`: The list of the collapse operators. -- `solver::SteadyStateODESolver=SteadyStateODESolver()`: see [`SteadyStateODESolver`](@ref) for more details. -- `reltol::Real=1.0e-8`: Relative tolerance in steady state terminate condition and solver adaptive timestepping. -- `abstol::Real=1.0e-10`: Absolute tolerance in steady state terminate condition and solver adaptive timestepping. -- `kwargs...`: The keyword arguments for the ODEProblem. +- `solver::SteadyStateSolver=SteadyStateDirectSolver()`: see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for different solvers. +- `kwargs...`: The keyword arguments for the solver. """ -function steadystate( - H::QuantumObject{MT1,HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, - tspan::Real = Inf, - c_ops::Union{Nothing,AbstractVector} = nothing; - solver::SteadyStateODESolver = SteadyStateODESolver(), - reltol::Real = 1.0e-8, - abstol::Real = 1.0e-10, - kwargs..., -) where { - MT1<:AbstractMatrix, - T2, - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} - (H.dims != ψ0.dims) && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) - - N = prod(H.dims) - u0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) - - L = MatrixOperator(liouvillian(H, c_ops).data) - - ftype = _FType(ψ0) - prob = ODEProblem{true}(L, u0, (ftype(0), ftype(tspan))) # Convert tspan to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl - sol = solve( - prob, - solver.alg; - callback = TerminateSteadyState(abstol, reltol, _steadystate_ode_condition), - reltol = reltol, - abstol = abstol, - kwargs..., - ) - - ρss = reshape(sol.u[end], N, N) - return QuantumObject(ρss, Operator, H.dims) -end - -function _steadystate_ode_condition(integrator, abstol, reltol, min_t) - # this condition is same as DiffEqBase.NormTerminationMode - - du_dt = (integrator.u - integrator.uprev) / integrator.dt - norm_du_dt = norm(du_dt) - if (norm_du_dt <= reltol * norm(du_dt + integrator.u)) || (norm_du_dt <= abstol) - return true - else - return false - end -end - function steadystate( H::QuantumObject{<:AbstractArray,OpType}, c_ops::Union{Nothing,AbstractVector} = nothing; @@ -220,6 +173,101 @@ function _steadystate( return QuantumObject(ρss, Operator, L.dims) end +_steadystate( + L::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, + solver::SteadyStateODESolver; + kwargs..., +) where {T} = throw( + ArgumentError( + "The initial state ψ0 is required for SteadyStateODESolver, use the following call instead: `steadystate(H, ψ0, tspan, c_ops)`.", + ), +) + +@doc raw""" + steadystate( + H::QuantumObject, + ψ0::QuantumObject, + tspan::Real = Inf, + c_ops::Union{Nothing,AbstractVector} = nothing; + solver::SteadyStateODESolver = SteadyStateODESolver(), + reltol::Real = 1.0e-8, + abstol::Real = 1.0e-10, + kwargs... + ) + +Solve the stationary state based on time evolution (ordinary differential equations; `OrdinaryDiffEq.jl`) with a given initial state. + +The termination condition of the stationary state ``|\rho\rangle\rangle`` is that either the following condition is `true`: + +```math +\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{reltol} \times\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}+|\hat{\rho}\rangle\rangle\rVert +``` + +or + +```math +\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{abstol} +``` + +# Parameters +- `H::QuantumObject`: The Hamiltonian or the Liouvillian of the system. +- `ψ0::QuantumObject`: The initial state of the system. +- `tspan::Real=Inf`: The final time step for the steady state problem. +- `c_ops::Union{Nothing,AbstractVector}=nothing`: The list of the collapse operators. +- `solver::SteadyStateODESolver=SteadyStateODESolver()`: see [`SteadyStateODESolver`](@ref) for more details. +- `reltol::Real=1.0e-8`: Relative tolerance in steady state terminate condition and solver adaptive timestepping. +- `abstol::Real=1.0e-10`: Absolute tolerance in steady state terminate condition and solver adaptive timestepping. +- `kwargs...`: The keyword arguments for the ODEProblem. +""" +function steadystate( + H::QuantumObject{MT1,HOpType}, + ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + tspan::Real = Inf, + c_ops::Union{Nothing,AbstractVector} = nothing; + solver::SteadyStateODESolver = SteadyStateODESolver(), + reltol::Real = 1.0e-8, + abstol::Real = 1.0e-10, + kwargs..., +) where { + MT1<:AbstractMatrix, + T2, + HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, +} + (H.dims != ψ0.dims) && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + + N = prod(H.dims) + u0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) + + L = MatrixOperator(liouvillian(H, c_ops).data) + + ftype = _FType(ψ0) + prob = ODEProblem{true}(L, u0, (ftype(0), ftype(tspan))) # Convert tspan to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + sol = solve( + prob, + solver.alg; + callback = TerminateSteadyState(abstol, reltol, _steadystate_ode_condition), + reltol = reltol, + abstol = abstol, + kwargs..., + ) + + ρss = reshape(sol.u[end], N, N) + return QuantumObject(ρss, Operator, H.dims) +end + +function _steadystate_ode_condition(integrator, abstol, reltol, min_t) + # this condition is same as DiffEqBase.NormTerminationMode + + du_dt = (integrator.u - integrator.uprev) / integrator.dt + norm_du_dt = norm(du_dt) + if (norm_du_dt <= reltol * norm(du_dt + integrator.u)) || (norm_du_dt <= abstol) + return true + else + return false + end +end + @doc raw""" steadystate_floquet( H_0::QuantumObject{MT,OpType1}, diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index 8bc4ee0c9..ed850565e 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -13,6 +13,7 @@ solver = SteadyStateODESolver() ρ_ss = steadystate(H, psi0, t_l[end], c_ops, solver = solver) @test tracedist(rho_me, ρ_ss) < 1e-4 + @test_throws ArgumentError steadystate(H, c_ops, solver = solver) solver = SteadyStateDirectSolver() ρ_ss = steadystate(H, c_ops, solver = solver) From e70a26c339e7a6004f2a08e1b36bb931987c18d4 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sat, 28 Sep 2024 23:49:33 +0200 Subject: [PATCH 033/329] Replace `n_traj` with `ntraj` --- benchmarks/timeevolution.jl | 4 +-- docs/src/users_guide/steadystate.md | 2 +- src/time_evolution/mcsolve.jl | 18 ++++++------ src/time_evolution/ssesolve.jl | 14 +++++----- src/time_evolution/time_evolution.jl | 12 ++++---- .../time_evolution_dynamical.jl | 6 ++-- test/core-test/dynamical-shifted-fock.jl | 4 +-- test/core-test/time_evolution.jl | 28 +++++++++---------- 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/benchmarks/timeevolution.jl b/benchmarks/timeevolution.jl index da9919dc3..b76816ed4 100644 --- a/benchmarks/timeevolution.jl +++ b/benchmarks/timeevolution.jl @@ -47,7 +47,7 @@ function benchmark_timeevolution!(SUITE) $ψ0, $tlist, $c_ops, - n_traj = 100, + ntraj = 100, e_ops = $e_ops, progress_bar = Val(false), ensemble_method = EnsembleSerial(), @@ -57,7 +57,7 @@ function benchmark_timeevolution!(SUITE) $ψ0, $tlist, $c_ops, - n_traj = 100, + ntraj = 100, e_ops = $e_ops, progress_bar = Val(false), ensemble_method = EnsembleThreads(), diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index 0eec4f190..332f10d78 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -94,7 +94,7 @@ exp_ss = real(expect(e_ops[1], ρ_ss)) tlist = LinRange(0, 50, 100) # monte-carlo -sol_mc = mcsolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, n_traj=100, progress_bar=false) +sol_mc = mcsolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, ntraj=100, progress_bar=false) exp_mc = real(sol_mc.expect[1, :]) # master eq. diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 34a722ba8..7e5be938b 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -402,7 +402,7 @@ end e_ops::Union{Nothing,AbstractVector}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), - n_traj::Int=1, + ntraj::Int=1, ensemble_method=EnsembleThreads(), jump_callback::TJC=ContinuousLindbladJumpCallback(), kwargs...) @@ -452,7 +452,7 @@ If the environmental measurements register a quantum jump, the wave function und - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. -- `n_traj::Int`: Number of trajectories to use. +- `ntraj::Int`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. - `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. - `prob_func::Function`: Function to use for generating the ODEProblem. @@ -482,15 +482,15 @@ function mcsolve( H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), seeds::Union{Nothing,Vector{Int}} = nothing, - n_traj::Int = 1, + ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), prob_func::Function = _mcsolve_prob_func, output_func::Function = _mcsolve_output_func, kwargs..., ) where {MT1<:AbstractMatrix,T2,TJC<:LindbladJumpCallbackType} - if !isnothing(seeds) && length(seeds) != n_traj - throw(ArgumentError("Length of seeds must match n_traj ($n_traj), but got $(length(seeds))")) + if !isnothing(seeds) && length(seeds) != ntraj + throw(ArgumentError("Length of seeds must match ntraj ($ntraj), but got $(length(seeds))")) end ens_prob_mc = mcsolveEnsembleProblem( @@ -509,16 +509,16 @@ function mcsolve( kwargs..., ) - return mcsolve(ens_prob_mc; alg = alg, n_traj = n_traj, ensemble_method = ensemble_method) + return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) end function mcsolve( ens_prob_mc::EnsembleProblem; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - n_traj::Int = 1, + ntraj::Int = 1, ensemble_method = EnsembleThreads(), ) - sol = solve(ens_prob_mc, alg, ensemble_method, trajectories = n_traj) + sol = solve(ens_prob_mc, alg, ensemble_method, trajectories = ntraj) _sol_1 = sol[:, 1] expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) @@ -536,7 +536,7 @@ function mcsolve( expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) return TimeEvolutionMCSol( - n_traj, + ntraj, times, states, expvals, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index d9fcbe34c..b32b46ea1 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -293,7 +293,7 @@ end e_ops::Union{Nothing,AbstractVector}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), - n_traj::Int=1, + ntraj::Int=1, ensemble_method=EnsembleThreads(), prob_func::Function=_mcsolve_prob_func, output_func::Function=_mcsolve_output_func, @@ -334,7 +334,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. -- `n_traj::Int`: Number of trajectories to use. +- `ntraj::Int`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. - `prob_func::Function`: Function to use for generating the SDEProblem. - `output_func::Function`: Function to use for generating the output of a single trajectory. @@ -362,7 +362,7 @@ function ssesolve( e_ops::Union{Nothing,AbstractVector} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), - n_traj::Int = 1, + ntraj::Int = 1, ensemble_method = EnsembleThreads(), prob_func::Function = _ssesolve_prob_func, output_func::Function = _ssesolve_output_func, @@ -382,16 +382,16 @@ function ssesolve( kwargs..., ) - return ssesolve(ens_prob; alg = alg, n_traj = n_traj, ensemble_method = ensemble_method) + return ssesolve(ens_prob; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) end function ssesolve( ens_prob::EnsembleProblem; alg::StochasticDiffEqAlgorithm = SRA1(), - n_traj::Int = 1, + ntraj::Int = 1, ensemble_method = EnsembleThreads(), ) - sol = solve(ens_prob, alg, ensemble_method, trajectories = n_traj) + sol = solve(ens_prob, alg, ensemble_method, trajectories = ntraj) _sol_1 = sol[:, 1] expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) @@ -403,7 +403,7 @@ function ssesolve( expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) return TimeEvolutionSSESol( - n_traj, + ntraj, _sol_1.t, states, expvals, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index bd60ede23..8efa46175 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -50,7 +50,7 @@ A structure storing the results and some information from solving quantum trajec # Fields (Attributes) -- `n_traj::Int`: Number of trajectories +- `ntraj::Int`: Number of trajectories - `times::AbstractVector`: The time list of the evolution in each trajectory. - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. - `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. @@ -70,7 +70,7 @@ struct TimeEvolutionMCSol{ TJT<:Vector{<:Vector{<:Real}}, TJW<:Vector{<:Vector{<:Integer}}, } - n_traj::Int + ntraj::Int times::TT states::TS expect::TE @@ -87,7 +87,7 @@ function Base.show(io::IO, sol::TimeEvolutionMCSol) print(io, "Solution of quantum trajectories\n") print(io, "(converged: $(sol.converged))\n") print(io, "--------------------------------\n") - print(io, "num_trajectories = $(sol.n_traj)\n") + print(io, "num_trajectories = $(sol.ntraj)\n") print(io, "num_states = $(length(sol.states[1]))\n") print(io, "num_expect = $(size(sol.expect, 1))\n") print(io, "ODE alg.: $(sol.alg)\n") @@ -100,7 +100,7 @@ end struct TimeEvolutionSSESol A structure storing the results and some information from solving trajectories of the Stochastic Shrodinger equation time evolution. # Fields (Attributes) - - `n_traj::Int`: Number of trajectories + - `ntraj::Int`: Number of trajectories - `times::AbstractVector`: The time list of the evolution in each trajectory. - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. - `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. @@ -118,7 +118,7 @@ struct TimeEvolutionSSESol{ T1<:Real, T2<:Real, } - n_traj::Int + ntraj::Int times::TT states::TS expect::TE @@ -133,7 +133,7 @@ function Base.show(io::IO, sol::TimeEvolutionSSESol) print(io, "Solution of quantum trajectories\n") print(io, "(converged: $(sol.converged))\n") print(io, "--------------------------------\n") - print(io, "num_trajectories = $(sol.n_traj)\n") + print(io, "num_trajectories = $(sol.ntraj)\n") print(io, "num_states = $(length(sol.states[1]))\n") print(io, "num_expect = $(size(sol.expect, 1))\n") print(io, "SDE alg.: $(sol.alg)\n") diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 9ee68fac3..ae4af1075 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -687,7 +687,7 @@ end H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), δα_list::Vector{<:Real}=fill(0.2, length(op_list)), - n_traj::Int=1, + ntraj::Int=1, ensemble_method=EnsembleThreads(), jump_callback::LindbladJumpCallbackType=ContinuousLindbladJumpCallback(), krylov_dim::Int=max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), @@ -712,7 +712,7 @@ function dsf_mcsolve( H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), - n_traj::Int = 1, + ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), @@ -736,5 +736,5 @@ function dsf_mcsolve( kwargs..., ) - return mcsolve(ens_prob_mc; alg = alg, n_traj = n_traj, ensemble_method = ensemble_method) + return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) end diff --git a/test/core-test/dynamical-shifted-fock.jl b/test/core-test/dynamical-shifted-fock.jl index b0b529ac9..bb1bebc16 100644 --- a/test/core-test/dynamical-shifted-fock.jl +++ b/test/core-test/dynamical-shifted-fock.jl @@ -60,7 +60,7 @@ dsf_params, e_ops = e_ops_dsf, progress_bar = Val(false), - n_traj = 500, + ntraj = 500, ) val_ss = abs2(sol0.expect[1, end]) @test sum(abs2.(sol0.expect[1, :] .- sol_dsf_me.expect[1, :])) / (val_ss * length(tlist)) < 0.1 @@ -140,7 +140,7 @@ dsf_params, e_ops = e_ops_dsf2, progress_bar = Val(false), - n_traj = 500, + ntraj = 500, ) val_ss = abs2(sol0.expect[1, end]) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 3d0546983..8f0dcbad6 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -54,9 +54,9 @@ sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) sol_me2 = mesolve(H, psi0, t_l, c_ops, progress_bar = Val(false)) sol_me3 = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) - sol_mc = mcsolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) - sol_mc_states = mcsolve(H, psi0, t_l, c_ops, n_traj = 500, saveat = t_l, progress_bar = Val(false)) - sol_sse = ssesolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_mc = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_mc_states = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, saveat = t_l, progress_bar = Val(false)) + sol_sse = ssesolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) @@ -87,7 +87,7 @@ "Solution of quantum trajectories\n" * "(converged: $(sol_mc.converged))\n" * "--------------------------------\n" * - "num_trajectories = $(sol_mc.n_traj)\n" * + "num_trajectories = $(sol_mc.ntraj)\n" * "num_states = $(length(sol_mc.states[1]))\n" * "num_expect = $(size(sol_mc.expect, 1))\n" * "ODE alg.: $(sol_mc.alg)\n" * @@ -97,7 +97,7 @@ "Solution of quantum trajectories\n" * "(converged: $(sol_sse.converged))\n" * "--------------------------------\n" * - "num_trajectories = $(sol_sse.n_traj)\n" * + "num_trajectories = $(sol_sse.ntraj)\n" * "num_states = $(length(sol_sse.states[1]))\n" * "num_expect = $(size(sol_sse.expect, 1))\n" * "SDE alg.: $(sol_sse.alg)\n" * @@ -119,14 +119,14 @@ psi0, t_l, c_ops, - n_traj = 500, + ntraj = 500, e_ops = e_ops, progress_bar = Val(false), ) - @inferred mcsolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) - @inferred mcsolve(H, psi0, t_l, c_ops, n_traj = 500, progress_bar = Val(true)) - @inferred mcsolve(H, psi0, [0, 10], c_ops, n_traj = 500, progress_bar = Val(false)) - @inferred mcsolve(H, Qobj(zeros(Int64, N)), t_l, c_ops, n_traj = 500, progress_bar = Val(false)) + @inferred mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + @inferred mcsolve(H, psi0, t_l, c_ops, ntraj = 500, progress_bar = Val(true)) + @inferred mcsolve(H, psi0, [0, 10], c_ops, ntraj = 500, progress_bar = Val(false)) + @inferred mcsolve(H, Qobj(zeros(Int64, N)), t_l, c_ops, ntraj = 500, progress_bar = Val(false)) end @testset "Type Inference ssesolve" begin @@ -135,12 +135,12 @@ psi0, t_l, c_ops, - n_traj = 500, + ntraj = 500, e_ops = e_ops, progress_bar = Val(false), ) - @inferred ssesolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = e_ops, progress_bar = Val(false)) - @inferred ssesolve(H, psi0, t_l, c_ops, n_traj = 500, progress_bar = Val(true)) + @inferred ssesolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + @inferred ssesolve(H, psi0, t_l, c_ops, ntraj = 500, progress_bar = Val(true)) end end @@ -179,7 +179,7 @@ psi0 = kron(psi0_1, psi0_2) t_l = LinRange(0, 20 / γ1, 1000) sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = false) # Here we don't put Val(false) because we want to test the support for Bool type - sol_mc = mcsolve(H, psi0, t_l, c_ops, n_traj = 500, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = Val(false)) + sol_mc = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = Val(false)) @test sum(abs.(sol_mc.expect[1:2, :] .- sol_me.expect[1:2, :])) / length(t_l) < 0.1 @test expect(sp1 * sm1, sol_me.states[end]) ≈ expect(sigmap() * sigmam(), ptrace(sol_me.states[end], 1)) end From db5d48510a4e274b9f3c6a9a2798311bc4f6bea5 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sun, 29 Sep 2024 00:53:16 +0200 Subject: [PATCH 034/329] Format document --- test/core-test/time_evolution.jl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 8f0dcbad6..b022229c9 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -114,15 +114,7 @@ end @testset "Type Inference mcsolve" begin - @inferred mcsolveEnsembleProblem( - H, - psi0, - t_l, - c_ops, - ntraj = 500, - e_ops = e_ops, - progress_bar = Val(false), - ) + @inferred mcsolveEnsembleProblem(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) @inferred mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) @inferred mcsolve(H, psi0, t_l, c_ops, ntraj = 500, progress_bar = Val(true)) @inferred mcsolve(H, psi0, [0, 10], c_ops, ntraj = 500, progress_bar = Val(false)) From 4df91fb6ac059f2c5fe03a80030655df4cd1abb2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 29 Sep 2024 08:00:19 +0800 Subject: [PATCH 035/329] [Docs] Update documentation for time evolution (#241) * update documentation for time evolution * separate `sesolve` and `mesolve` in docs --- docs/make.jl | 11 ++++++- docs/src/users_guide/time_evolution/intro.md | 30 ++++++++++++++++++- .../src/users_guide/time_evolution/mcsolve.md | 5 ++++ .../src/users_guide/time_evolution/mesolve.md | 9 ++++++ .../src/users_guide/time_evolution/sesolve.md | 5 ++++ .../users_guide/time_evolution/solution.md | 9 ++++++ .../users_guide/time_evolution/stochastic.md | 7 +++++ .../time_evolution/time_dependent.md | 5 ++++ 8 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 docs/src/users_guide/time_evolution/mcsolve.md create mode 100644 docs/src/users_guide/time_evolution/mesolve.md create mode 100644 docs/src/users_guide/time_evolution/sesolve.md create mode 100644 docs/src/users_guide/time_evolution/solution.md create mode 100644 docs/src/users_guide/time_evolution/stochastic.md create mode 100644 docs/src/users_guide/time_evolution/time_dependent.md diff --git a/docs/make.jl b/docs/make.jl index 95c9eaed7..77fbb75e9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -6,6 +6,8 @@ using Documenter DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) +const DRAFT = false # set `true` to disable cell evaluation + const MathEngine = MathJax3( Dict( :loader => Dict("load" => ["[tex]/physics"]), @@ -33,6 +35,12 @@ const PAGES = [ "Tensor Products and Partial Traces" => "users_guide/tensor.md", "Time Evolution and Dynamics" => [ "Introduction" => "users_guide/time_evolution/intro.md", + "Time Evolution Solutions" => "users_guide/time_evolution/solution.md", + "Schrödinger Equation Solver" => "users_guide/time_evolution/sesolve.md", + "Lindblad Master Equation Solver" => "users_guide/time_evolution/mesolve.md", + "Monte-Carlo Solver" => "users_guide/time_evolution/mcsolve.md", + "Stochastic Solver" => "users_guide/time_evolution/stochastic.md", + "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", ], "Solving for Steady-State Solutions" => "users_guide/steadystate.md", "Symmetries" => [], @@ -66,7 +74,8 @@ makedocs(; assets = ["assets/favicon.ico"], mathengine = MathEngine, size_threshold_ignore = ["api.md"], - ) + ), + draft = DRAFT, ) deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", devbranch = "main") diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index fe0097ff0..d69ab9fa4 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -1,3 +1,31 @@ # [Time Evolution and Quantum System Dynamics](@id doc:Time-Evolution-and-Quantum-System-Dynamics) -This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file +**Table of contents** + +```@contents +Pages = [ + "intro.md", + "solution.md", + "sesolve.md", + "mesolve.md", + "mcsolve.md", + "stochastic.md", + "time_dependent.md", +] +Depth = 2:3 +``` + +## [Introduction](@id doc-TE:Introduction) + +Although in some cases, we want to find the stationary states of a quantum system, often we are interested in the dynamics: how the state of a system or an ensemble of systems evolves with time. `QuantumToolbox` provides many ways to model dynamics. + +There are two kinds of quantum systems: open systems that interact with a larger environment and closed systems that do not. In a closed system, the state can be described by a state vector. When we are modeling an open system, or an ensemble of systems, the use of the density matrix is mandatory. + +The following table lists the solvers provided by `QuantumToolbox` for dynamic quantum systems and the corresponding type of solution returned by the solver: + +| **Equation** | **Function Call** | **Returned Solution** | +|:-------------|:------------------|:----------------------| +| Unitary evolution, Schrödinger equation | [`sesolve`](@ref) | [`TimeEvolutionSol`](@ref) | +| Lindblad master eqn. or Von Neuman eqn. | [`mesolve`](@ref) | [`TimeEvolutionSol`](@ref) | +| Monte Carlo evolution | [`mcsolve`](@ref) | [`TimeEvolutionMCSol`](@ref) | +| Stochastic Schrödinger equation | [`ssesolve`](@ref) | [`TimeEvolutionSSESol`](@ref) | \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/mcsolve.md b/docs/src/users_guide/time_evolution/mcsolve.md new file mode 100644 index 000000000..b6504e394 --- /dev/null +++ b/docs/src/users_guide/time_evolution/mcsolve.md @@ -0,0 +1,5 @@ +# Time Evolution and Quantum System Dynamics + +## [Monte-Carlo Solver](@id doc-TE:Monte-Carlo-Solver) + +This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/mesolve.md b/docs/src/users_guide/time_evolution/mesolve.md new file mode 100644 index 000000000..b63c85d74 --- /dev/null +++ b/docs/src/users_guide/time_evolution/mesolve.md @@ -0,0 +1,9 @@ +# Time Evolution and Quantum System Dynamics + +## [Lindblad Master Equation Solver](@id doc-TE:Lindblad-Master-Equation-Solver) + +This page is still under construction, please visit [API](@ref doc-API) first. + +### Von Neumann equation + +### The Lindblad master equation \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/sesolve.md b/docs/src/users_guide/time_evolution/sesolve.md new file mode 100644 index 000000000..7df270a22 --- /dev/null +++ b/docs/src/users_guide/time_evolution/sesolve.md @@ -0,0 +1,5 @@ +# Time Evolution and Quantum System Dynamics + +## [Schrödinger Equation Solver](@id doc-TE:Schrödinger-Equation-Solver) + +This page is still under construction, please visit [API](@ref doc-API) first. diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md new file mode 100644 index 000000000..d1b1b47c5 --- /dev/null +++ b/docs/src/users_guide/time_evolution/solution.md @@ -0,0 +1,9 @@ +# Time Evolution and Quantum System Dynamics + +## [Time Evolution Solutions](@id doc-TE:Time-Evolution-Solutions) + +This page is still under construction, please visit [API](@ref doc-API) first. + +### Solution + +### Multiple trajectories solution \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/stochastic.md b/docs/src/users_guide/time_evolution/stochastic.md new file mode 100644 index 000000000..49259e78b --- /dev/null +++ b/docs/src/users_guide/time_evolution/stochastic.md @@ -0,0 +1,7 @@ +# Time Evolution and Quantum System Dynamics + +## [Stochastic Solver](@id doc-TE:Stochastic-Solver) + +This page is still under construction, please visit [API](@ref doc-API) first. + +### Stochastic Schrodinger equation \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/time_dependent.md b/docs/src/users_guide/time_evolution/time_dependent.md new file mode 100644 index 000000000..37274c343 --- /dev/null +++ b/docs/src/users_guide/time_evolution/time_dependent.md @@ -0,0 +1,5 @@ +# Time Evolution and Quantum System Dynamics + +## [Solving Problems with Time-dependent Hamiltonians](@id doc-TE:Solving-Problems-with-Time-dependent-Hamiltonians) + +This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file From 905e658a18d80a4ab61c5cc6993caeff5b6d4384 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:48:21 +0800 Subject: [PATCH 036/329] fix incorrect `times` in time evolution solutions (#244) --- src/time_evolution/lr_mesolve.jl | 24 +++++++------- src/time_evolution/mcsolve.jl | 11 ++----- src/time_evolution/mesolve.jl | 3 +- src/time_evolution/sesolve.jl | 3 +- src/time_evolution/ssesolve.jl | 3 +- src/time_evolution/time_evolution.jl | 33 ++++++++++--------- .../time_evolution_dynamical.jl | 2 +- test/core-test/time_evolution.jl | 12 +++++++ 8 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index eeda3efa7..4d530bb36 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -147,19 +147,19 @@ function _periodicsave_func(integrator) return u_modified!(integrator, false) end -_save_control_lr_mesolve(u, t, integrator) = t in integrator.p.t_l +_save_control_lr_mesolve(u, t, integrator) = t in integrator.p.times function _save_affect_lr_mesolve!(integrator) ip = integrator.p N, M = ip.N, ip.M - idx = select(integrator.t, ip.t_l) + idx = select(integrator.t, ip.times) @views z = reshape(integrator.u[1:N*M], N, M) @views B = reshape(integrator.u[N*M+1:end], M, M) _calculate_expectation!(ip, z, B, idx) if integrator.p.opt.progress - print("\rProgress: $(round(Int, 100*idx/length(ip.t_l)))%") + print("\rProgress: $(round(Int, 100*idx/length(ip.times)))%") flush(stdout) end return u_modified!(integrator, false) @@ -365,7 +365,7 @@ end #=======================================================# @doc raw""" - lr_mesolveProblem(H, z, B, t_l, c_ops; e_ops=(), f_ops=(), opt=LRMesolveOptions(), kwargs...) where T + lr_mesolveProblem(H, z, B, tlist, c_ops; e_ops=(), f_ops=(), opt=LRMesolveOptions(), kwargs...) where T Formulates the ODEproblem for the low-rank time evolution of the system. The function is called by lr_mesolve. Parameters @@ -376,7 +376,7 @@ end The initial z matrix. B : AbstractMatrix{T} The initial B matrix. - t_l : AbstractVector{T} + tlist : AbstractVector{T} The time steps at which the expectation values and function values are calculated. c_ops : AbstractVector{QuantumObject} The jump operators of the system. @@ -393,7 +393,7 @@ function lr_mesolveProblem( H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, z::AbstractArray{T2,2}, B::AbstractArray{T2,2}, - t_l::AbstractVector, + tlist::AbstractVector, c_ops::AbstractVector = []; e_ops::Tuple = (), f_ops::Tuple = (), @@ -407,6 +407,8 @@ function lr_mesolveProblem( c_ops = get_data.(c_ops) e_ops = get_data.(e_ops) + t_l = convert(Vector{_FType(H)}, tlist) + # Initialization of Arrays expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) funvals = Array{ComplexF64}(undef, length(f_ops), length(t_l)) @@ -421,7 +423,7 @@ function lr_mesolveProblem( e_ops = e_ops, f_ops = f_ops, opt = opt, - t_l = t_l, + times = t_l, expvals = expvals, funvals = funvals, Ml = Ml, @@ -489,14 +491,14 @@ function lr_mesolve( H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, z::AbstractArray{T2,2}, B::AbstractArray{T2,2}, - t_l::AbstractVector, + tlist::AbstractVector, c_ops::AbstractVector = []; e_ops::Tuple = (), f_ops::Tuple = (), opt::LRMesolveOptions{AlgType} = LRMesolveOptions(), kwargs..., ) where {T1,T2,AlgType<:OrdinaryDiffEqAlgorithm} - prob = lr_mesolveProblem(H, z, B, t_l, c_ops; e_ops = e_ops, f_ops = f_ops, opt = opt, kwargs...) + prob = lr_mesolveProblem(H, z, B, tlist, c_ops; e_ops = e_ops, f_ops = f_ops, opt = opt, kwargs...) return lr_mesolve(prob; kwargs...) end @@ -520,7 +522,7 @@ get_B(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, ( Additional keyword arguments for the ODEProblem. """ function lr_mesolve(prob::ODEProblem; kwargs...) - sol = solve(prob, prob.p.opt.alg, tstops = prob.p.t_l) + sol = solve(prob, prob.p.opt.alg, tstops = prob.p.times) prob.p.opt.progress && print("\n") N = prob.p.N @@ -535,5 +537,5 @@ function lr_mesolve(prob::ODEProblem; kwargs...) zt = get_z(sol.u, N, Ml) end - return LRTimeEvolutionSol(sol.t, zt, Bt, prob.p.expvals, prob.p.funvals, prob.p.Ml) + return LRTimeEvolutionSol(sol.prob.p.times, zt, Bt, prob.p.expvals, prob.p.funvals, prob.p.Ml) end diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 7e5be938b..cfebcbf9a 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -86,13 +86,12 @@ function _mcsolve_output_func(sol, i) return (sol, false) end -function _mcsolve_generate_statistics(sol, i, times, states, expvals_all, jump_times, jump_which) +function _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which) sol_i = sol[:, i] !isempty(sol_i.prob.kwargs[:saveat]) ? states[i] = [QuantumObject(normalize!(sol_i.u[i]), dims = sol_i.prob.p.Hdims) for i in 1:length(sol_i.u)] : nothing copyto!(view(expvals_all, i, :, :), sol_i.prob.p.expvals) - times[i] = sol_i.t jump_times[i] = sol_i.prob.p.jump_times return jump_which[i] = sol_i.prob.p.jump_which end @@ -522,22 +521,18 @@ function mcsolve( _sol_1 = sol[:, 1] expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) - times = Vector{Vector{Float64}}(undef, length(sol)) states = isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : Vector{Vector{QuantumObject}}(undef, length(sol)) jump_times = Vector{Vector{Float64}}(undef, length(sol)) jump_which = Vector{Vector{Int16}}(undef, length(sol)) - foreach( - i -> _mcsolve_generate_statistics(sol, i, times, states, expvals_all, jump_times, jump_which), - eachindex(sol), - ) + foreach(i -> _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which), eachindex(sol)) expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) return TimeEvolutionMCSol( ntraj, - times, + _sol_1.prob.p.times, states, expvals, expvals_all, diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 64cc2bdc4..922f29942 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -150,6 +150,7 @@ function mesolveProblem( e_ops = e_ops2, expvals = expvals, H_t = H_t, + times = t_l, is_empty_e_ops = is_empty_e_ops, params..., ) @@ -253,7 +254,7 @@ function mesolve(prob::ODEProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = sol.prob.p.Hdims), sol.u) return TimeEvolutionSol( - sol.t, + sol.prob.p.times, ρt, sol.prob.p.expvals, sol.retcode, diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 264a6527b..383ff2a0d 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -127,6 +127,7 @@ function sesolveProblem( progr = progr, Hdims = H.dims, H_t = H_t, + times = t_l, is_empty_e_ops = is_empty_e_ops, params..., ) @@ -215,7 +216,7 @@ function sesolve(prob::ODEProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) ψt = map(ϕ -> QuantumObject(ϕ, type = Ket, dims = sol.prob.p.Hdims), sol.u) return TimeEvolutionSol( - sol.t, + sol.prob.p.times, ψt, sol.prob.p.expvals, sol.retcode, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index b32b46ea1..9d77861bf 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -180,6 +180,7 @@ function ssesolveProblem( progr = progr, Hdims = H.dims, H_t = H_t, + times = t_l, is_empty_e_ops = is_empty_e_ops, params..., ) @@ -404,7 +405,7 @@ function ssesolve( return TimeEvolutionSSESol( ntraj, - _sol_1.t, + _sol_1.prob.p.times, states, expvals, expvals_all, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 8efa46175..31e4d2ab4 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -51,7 +51,7 @@ A structure storing the results and some information from solving quantum trajec # Fields (Attributes) - `ntraj::Int`: Number of trajectories -- `times::AbstractVector`: The time list of the evolution in each trajectory. +- `times::AbstractVector`: The time list of the evolution. - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. - `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. - `expect_all::Array`: The expectation values corresponding to each trajectory and each time point in `times` @@ -63,7 +63,7 @@ A structure storing the results and some information from solving quantum trajec - `reltol::Real`: The relative tolerance which is used during the solving process. """ struct TimeEvolutionMCSol{ - TT<:Vector{<:Vector{<:Real}}, + TT<:Vector{<:Real}, TS<:AbstractVector, TE<:Matrix{ComplexF64}, TEA<:Array{ComplexF64,3}, @@ -97,19 +97,22 @@ function Base.show(io::IO, sol::TimeEvolutionMCSol) end @doc raw""" - struct TimeEvolutionSSESol - A structure storing the results and some information from solving trajectories of the Stochastic Shrodinger equation time evolution. - # Fields (Attributes) - - `ntraj::Int`: Number of trajectories - - `times::AbstractVector`: The time list of the evolution in each trajectory. - - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. - - `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. - - `expect_all::Array`: The expectation values corresponding to each trajectory and each time point in `times` - - `converged::Bool`: Whether the solution is converged or not. - - `alg`: The algorithm which is used during the solving process. - - `abstol::Real`: The absolute tolerance which is used during the solving process. - - `reltol::Real`: The relative tolerance which is used during the solving process. - """ + struct TimeEvolutionSSESol + +A structure storing the results and some information from solving trajectories of the Stochastic Shrodinger equation time evolution. + +# Fields (Attributes) + +- `ntraj::Int`: Number of trajectories +- `times::AbstractVector`: The time list of the evolution. +- `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. +- `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. +- `expect_all::Array`: The expectation values corresponding to each trajectory and each time point in `times` +- `converged::Bool`: Whether the solution is converged or not. +- `alg`: The algorithm which is used during the solving process. +- `abstol::Real`: The absolute tolerance which is used during the solving process. +- `reltol::Real`: The relative tolerance which is used during the solving process. +""" struct TimeEvolutionSSESol{ TT<:Vector{<:Real}, TS<:AbstractVector, diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index ae4af1075..1b1f48278 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -248,7 +248,7 @@ function dfd_mesolve( ) return TimeEvolutionSol( - sol.t, + sol.prob.p.times, ρt, sol.prob.p.expvals, sol.retcode, diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index b022229c9..9c02da211 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -16,10 +16,13 @@ sol3 = sesolve(H, psi0, t_l, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) sol_string = sprint((t, s) -> show(t, "text/plain", s), sol) @test sum(abs.(sol.expect[1, :] .- sin.(η * t_l) .^ 2)) / length(t_l) < 0.1 + @test length(sol.times) == length(t_l) @test length(sol.states) == 1 @test size(sol.expect) == (length(e_ops), length(t_l)) + @test length(sol2.times) == length(t_l) @test length(sol2.states) == length(t_l) @test size(sol2.expect) == (0, length(t_l)) + @test length(sol3.times) == length(t_l) @test length(sol3.states) == length(t_l) @test size(sol3.expect) == (length(e_ops), length(t_l)) @test sol_string == @@ -68,12 +71,21 @@ @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(t_l) < 0.1 @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect))) / length(t_l) < 0.1 @test sum(abs.(sol_sse.expect .- sol_me.expect)) / length(t_l) < 0.1 + @test length(sol_me.times) == length(t_l) @test length(sol_me.states) == 1 @test size(sol_me.expect) == (length(e_ops), length(t_l)) + @test length(sol_me2.times) == length(t_l) @test length(sol_me2.states) == length(t_l) @test size(sol_me2.expect) == (0, length(t_l)) + @test length(sol_me3.times) == length(t_l) @test length(sol_me3.states) == length(t_l) @test size(sol_me3.expect) == (length(e_ops), length(t_l)) + @test length(sol_mc.times) == length(t_l) + @test size(sol_mc.expect) == (length(e_ops), length(t_l)) + @test length(sol_mc_states.times) == length(t_l) + @test size(sol_mc_states.expect) == (0, length(t_l)) + @test length(sol_sse.times) == length(t_l) + @test size(sol_sse.expect) == (length(e_ops), length(t_l)) @test sol_me_string == "Solution of time evolution\n" * "(return code: $(sol_me.retcode))\n" * From 57f3f37de810d12e701822b3d8cb69d805348ca7 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:50:40 +0800 Subject: [PATCH 037/329] update docs (time evolution introduction and solution) (#245) --- docs/src/users_guide/time_evolution/intro.md | 15 ++-- .../users_guide/time_evolution/solution.md | 82 ++++++++++++++++++- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index d69ab9fa4..6a7f2f5ab 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -23,9 +23,12 @@ There are two kinds of quantum systems: open systems that interact with a larger The following table lists the solvers provided by `QuantumToolbox` for dynamic quantum systems and the corresponding type of solution returned by the solver: -| **Equation** | **Function Call** | **Returned Solution** | -|:-------------|:------------------|:----------------------| -| Unitary evolution, Schrödinger equation | [`sesolve`](@ref) | [`TimeEvolutionSol`](@ref) | -| Lindblad master eqn. or Von Neuman eqn. | [`mesolve`](@ref) | [`TimeEvolutionSol`](@ref) | -| Monte Carlo evolution | [`mcsolve`](@ref) | [`TimeEvolutionMCSol`](@ref) | -| Stochastic Schrödinger equation | [`ssesolve`](@ref) | [`TimeEvolutionSSESol`](@ref) | \ No newline at end of file +| **Equation** | **Function Call** | **Problem** | **Returned Solution** | +|:-------------|:------------------|:------------|:----------------------| +| Unitary evolution, Schrödinger equation | [`sesolve`](@ref) | [`sesolveProblem`](@ref) | [`TimeEvolutionSol`](@ref) | +| Lindblad master eqn. or Von Neuman eqn.| [`mesolve`](@ref) | [`mesolveProblem`](@ref) | [`TimeEvolutionSol`](@ref) | +| Monte Carlo evolution | [`mcsolve`](@ref) | [`mcsolveProblem`](@ref) [`mcsolveEnsembleProblem`](@ref) | [`TimeEvolutionMCSol`](@ref) | +| Stochastic Schrödinger equation | [`ssesolve`](@ref) | [`ssesolveProblem`](@ref) [`ssesolveEnsembleProblem`](@ref) | [`TimeEvolutionSSESol`](@ref) | + +!!! note "Solving dynamics with pre-defined problems" + `QuantumToolbox` provides two different methods to solve the dynamics. One can use the function calls listed above by either taking all the operators (like Hamiltonian and collapse operators, etc.) as inputs directly, or generating the `prob`lems by yourself and take it as an input of the function call, e.g., `sesolve(prob)`. diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index d1b1b47c5..f3f3f0a83 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -2,8 +2,86 @@ ## [Time Evolution Solutions](@id doc-TE:Time-Evolution-Solutions) -This page is still under construction, please visit [API](@ref doc-API) first. +```@setup TE-solution +using QuantumToolbox +``` ### Solution +`QuantumToolbox` utilizes the powerful [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/) to simulate different kinds of quantum system dynamics. Thus, we will first look at the data structure used for returning the solution (`sol`) from [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/). The solution stores all the crucial data needed for analyzing and plotting the results of a simulation. A generic structure [`TimeEvolutionSol`](@ref) contains the following properties for storing simulation data: -### Multiple trajectories solution \ No newline at end of file +| **Fields (Attributes)** | **Description** | +|:------------------------|:----------------| +| `sol.times` | The time list of the evolution. | +| `sol.states` | The list of result states. | +| `sol.expect` | The expectation values corresponding to each time point in `sol.times`. | +| `sol.alg` | The algorithm which is used during the solving process. | +| `sol.abstol` | The absolute tolerance which is used during the solving process. | +| `sol.reltol` | The relative tolerance which is used during the solving process. | +| `sol.retcode` (or `sol.converged`) | The returned status from the solver. | + +### Accessing data in solutions + +To understand how to access the data in solution, we will use an example as a guide, although we do not worry about the simulation details at this stage. The Schrödinger equation solver ([`sesolve`](@ref)) used in this example returns [`TimeEvolutionSol`](@ref): + +```@example TE-solution +H = 0.5 * sigmax() +ψ0 = basis(2, 0) +e_ops = [ + proj(basis(2, 0)), + proj(basis(2, 1)), + basis(2, 0) * basis(2, 1)' +] +tlist = LinRange(0, 10, 100) +sol = sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)); nothing # hide +``` + +To see what is contained inside the solution, we can use the `print` function: + +```@example TE-solution +print(sol) +``` + +It tells us the number of expectation values are computed and the number of states are stored. Now we have all the information needed to analyze the simulation results. To access the data for the three expectation values, one can do: + +```@example TE-solution +expt1 = real(sol.expect[1,:]) +expt2 = real(sol.expect[2,:]) +expt3 = real(sol.expect[3,:]); nothing # hide +``` + +Recall that `Julia` uses `Fortran`-style indexing that begins with one (i.e., `[1,:]` represents the 1-st observable, where `:` represents all values corresponding to `tlist`). + +Together with the array of times at which these expectation values are calculated: + +```@example TE-solution +times = sol.times; nothing # hide +``` + +we can plot the resulting expectation values: + +```@example TE-solution +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) + +fig = Figure() +ax = Axis(fig[1, 1]) +lines!(ax, times, expt1, label = L"P_00") +lines!(ax, times, expt2, label = L"P_11") +lines!(ax, times, expt3, label = L"P_01") + +fig +``` + +State vectors, or density matrices, are accessed in a similar manner: + +```@example TE-solution +sol.states +``` + +Here, the solution contains only one (final) state. Because the `states` will be saved depend on the keyword argument `saveat` in `kwargs`. If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). One can also specify `e_ops` and `saveat` separately. + +Some other solvers can have other output. + +### Multiple trajectories solution + +This part is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file From 88519acc33f93406a0200896a3111a8f20e5ce14 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 29 Sep 2024 10:52:13 +0200 Subject: [PATCH 038/329] Bump version to v0.16.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b46f4e747..dfbfae0ed 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Luca Gravina", "Yi-Te Huang"] -version = "0.15.0" +version = "0.16.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From f6ccbba7e4c158fffd5f5bfa3adf642df7a8c3ad Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:27:09 +0800 Subject: [PATCH 039/329] [Docs] fix typo in `@example` block (#246) --- docs/src/users_guide/steadystate.md | 7 +++--- .../users_guide/time_evolution/solution.md | 22 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index 332f10d78..74164c7d6 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -71,7 +71,6 @@ using QuantumToolbox using CairoMakie CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) - # Define parameters N = 20 # number of basis states to consider a = destroy(N) @@ -81,8 +80,10 @@ H = a' * a n_th = 2 # temperature with average of 2 excitations # collapse operators -# c_op_list = [ emission ; absorption ] -c_op_list = [ sqrt(κ * (1 + n_th)) * a ; sqrt(κ * n_th) * a' ] +c_op_list = [ + sqrt(κ * (n_th + 1)) * a, # emission + sqrt(κ * n_th ) * a' # absorption +] # find steady-state solution ρ_ss = steadystate(H, c_op_list) diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index f3f3f0a83..dae69ff05 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -24,7 +24,7 @@ using QuantumToolbox To understand how to access the data in solution, we will use an example as a guide, although we do not worry about the simulation details at this stage. The Schrödinger equation solver ([`sesolve`](@ref)) used in this example returns [`TimeEvolutionSol`](@ref): ```@example TE-solution -H = 0.5 * sigmax() +H = 0.5 * sigmay() ψ0 = basis(2, 0) e_ops = [ proj(basis(2, 0)), @@ -32,7 +32,8 @@ e_ops = [ basis(2, 0) * basis(2, 1)' ] tlist = LinRange(0, 10, 100) -sol = sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)); nothing # hide +sol = sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) +nothing # hide ``` To see what is contained inside the solution, we can use the `print` function: @@ -46,7 +47,8 @@ It tells us the number of expectation values are computed and the number of stat ```@example TE-solution expt1 = real(sol.expect[1,:]) expt2 = real(sol.expect[2,:]) -expt3 = real(sol.expect[3,:]); nothing # hide +expt3 = real(sol.expect[3,:]) +nothing # hide ``` Recall that `Julia` uses `Fortran`-style indexing that begins with one (i.e., `[1,:]` represents the 1-st observable, where `:` represents all values corresponding to `tlist`). @@ -54,7 +56,8 @@ Recall that `Julia` uses `Fortran`-style indexing that begins with one (i.e., `[ Together with the array of times at which these expectation values are calculated: ```@example TE-solution -times = sol.times; nothing # hide +times = sol.times +nothing # hide ``` we can plot the resulting expectation values: @@ -64,10 +67,13 @@ using CairoMakie CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) fig = Figure() -ax = Axis(fig[1, 1]) -lines!(ax, times, expt1, label = L"P_00") -lines!(ax, times, expt2, label = L"P_11") -lines!(ax, times, expt3, label = L"P_01") +ax = Axis(fig[1, 1], xlabel = L"t") +lines!(ax, times, expt1, label = L"\langle 0 | \rho(t) | 0 \rangle") +lines!(ax, times, expt2, label = L"\langle 1 | \rho(t) | 1 \rangle") +lines!(ax, times, expt3, label = L"\langle 0 | \rho(t) | 1 \rangle") + +ylims!(ax, (-0.5, 1.0)) +axislegend(ax, position = :lb) fig ``` From f34d9ffa1f6c21c490e755fb8dadb8c2aaa9a601 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 1 Oct 2024 15:45:14 +0200 Subject: [PATCH 040/329] Reduce figures size in Documentation --- docs/src/tutorials/logo.md | 6 +++--- docs/src/tutorials/lowrank.md | 2 +- docs/src/users_guide/steadystate.md | 2 +- docs/src/users_guide/time_evolution/solution.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/tutorials/logo.md b/docs/src/tutorials/logo.md index ac39523e7..b68386bce 100644 --- a/docs/src/tutorials/logo.md +++ b/docs/src/tutorials/logo.md @@ -83,7 +83,7 @@ wig = wigner(ψ, xvec, yvec, g = 2) Finally, we plot the Wigner function using the `heatmap` function from the `CairoMakie` package. ```@example logo -fig = Figure(size = (500, 500), figure_padding = 0) +fig = Figure(size = (250, 250), figure_padding = 0) ax = Axis(fig[1, 1]) heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1) hidespines!(ax) @@ -120,7 +120,7 @@ And the Wigner function becomes more uniform: ```@example logo wig = wigner(sol.states[end], xvec, yvec, g = 2) -fig = Figure(size = (500, 500), figure_padding = 0) +fig = Figure(size = (250, 250), figure_padding = 0) ax = Axis(fig[1, 1]) img_wig = heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1) @@ -197,7 +197,7 @@ img = set_color_julia.(X, Y, wig_normalized, α1, α2, α3, Ref(cmap1), Ref(cmap Finally, we plot the Wigner function with the custom colormap. ```@example logo -fig = Figure(size = (500, 500), figure_padding = 0, backgroundcolor = :transparent) +fig = Figure(size = (250, 250), figure_padding = 0, backgroundcolor = :transparent) ax = Axis(fig[1, 1], backgroundcolor = :transparent) image!(ax, img', rasterize = 1) hidespines!(ax) diff --git a/docs/src/tutorials/lowrank.md b/docs/src/tutorials/lowrank.md index 42a100bd0..030446dd4 100644 --- a/docs/src/tutorials/lowrank.md +++ b/docs/src/tutorials/lowrank.md @@ -141,7 +141,7 @@ Plot the results m_me = real(mesol.expect[3, :]) / Nx / Ny m_lr = real(lrsol.expvals[3, :]) / Nx / Ny -fig = Figure(size = (800, 400), fontsize = 15) +fig = Figure(size = (500, 350), fontsize = 15) ax = Axis(fig[1, 1], xlabel = L"\gamma t", ylabel = L"M_{z}", xlabelsize = 20, ylabelsize = 20) lines!(ax, tl, m_lr, label = L"LR $[M=M(t)]$", linewidth = 2) lines!(ax, tl, m_me, label = "Fock", linewidth = 2, linestyle = :dash) diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index 74164c7d6..ab06406a9 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -103,7 +103,7 @@ sol_me = mesolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, progress_bar=false) exp_me = real(sol_me.expect[1, :]) # plot the results -fig = Figure(size = (800, 400), fontsize = 15) +fig = Figure(size = (500, 350), fontsize = 15) ax = Axis(fig[1, 1], title = L"Decay of Fock state $|10\rangle$ in a thermal environment with $\langle n\rangle=2$", xlabel = "Time", diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index dae69ff05..efaea742f 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -66,7 +66,7 @@ we can plot the resulting expectation values: using CairoMakie CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) -fig = Figure() +fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = L"t") lines!(ax, times, expt1, label = L"\langle 0 | \rho(t) | 0 \rangle") lines!(ax, times, expt2, label = L"\langle 1 | \rho(t) | 1 \rangle") From d21041fe403693092e5977e53b3276c3f7eafbf4 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:13:13 +0200 Subject: [PATCH 041/329] Extend the support to `Tuple` in time_evolution (#248) * Extend the support to `Tuple` in time_evolution * Add missing cases --- src/correlations.jl | 16 ++++++------- src/qobj/eigsolve.jl | 4 ++-- src/qobj/operator_sum.jl | 10 +++++--- src/steadystate.jl | 18 +++++++------- src/time_evolution/mcsolve.jl | 36 ++++++++++++++-------------- src/time_evolution/mesolve.jl | 24 +++++++++---------- src/time_evolution/sesolve.jl | 12 +++++----- src/time_evolution/ssesolve.jl | 34 +++++++++++++------------- src/time_evolution/time_evolution.jl | 8 +++---- test/core-test/steady_state.jl | 2 +- test/core-test/time_evolution.jl | 3 +++ 11 files changed, 87 insertions(+), 80 deletions(-) diff --git a/src/correlations.jl b/src/correlations.jl index 5f3c6d033..38d7cb994 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -20,7 +20,7 @@ ExponentialSeries(; tol = 1e-14, calc_steadystate = false) = ExponentialSeries(t A::QuantumObject, B::QuantumObject, C::QuantumObject, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; kwargs...) Returns the two-times correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\expval{\hat{A}(t) \hat{B}(t + \tau) \hat{C}(t)}`` @@ -35,7 +35,7 @@ function correlation_3op_2t( A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, C::QuantumObject{<:AbstractArray{T5},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; kwargs..., ) where { T1, @@ -65,7 +65,7 @@ end τ_l::AbstractVector, A::QuantumObject, B::QuantumObject, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; reverse::Bool=false, kwargs...) @@ -81,7 +81,7 @@ function correlation_2op_2t( τ_l::AbstractVector, A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; reverse::Bool = false, kwargs..., ) where { @@ -108,7 +108,7 @@ end τ_l::AbstractVector, A::QuantumObject, B::QuantumObject, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; reverse::Bool=false, kwargs...) @@ -122,7 +122,7 @@ function correlation_2op_1t( τ_l::AbstractVector, A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; reverse::Bool = false, kwargs..., ) where { @@ -143,7 +143,7 @@ end ω_list::AbstractVector, A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; solver::MySolver=ExponentialSeries(), kwargs...) @@ -158,7 +158,7 @@ function spectrum( ω_list::AbstractVector, A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::MySolver = ExponentialSeries(), kwargs..., ) where { diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index dee88be26..483b82a5d 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -323,7 +323,7 @@ end @doc raw""" eigsolve_al(H::QuantumObject, - T::Real, c_ops::Union{Nothing,AbstractVector}=nothing; + T::Real, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; alg::OrdinaryDiffEqAlgorithm=Tsit5(), H_t::Union{Nothing,Function}=nothing, params::NamedTuple=NamedTuple(), @@ -363,7 +363,7 @@ Solve the eigenvalue problem for a Liouvillian superoperator `L` using the Arnol function eigsolve_al( H::QuantumObject{MT1,HOpType}, T::Real, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), H_t::Union{Nothing,Function} = nothing, params::NamedTuple = NamedTuple(), diff --git a/src/qobj/operator_sum.jl b/src/qobj/operator_sum.jl index 909e27dbb..5b2398217 100644 --- a/src/qobj/operator_sum.jl +++ b/src/qobj/operator_sum.jl @@ -7,10 +7,13 @@ A constructor to represent a sum of operators ``\sum_i c_i \hat{O}_i`` with a li This is very useful when we have to update only the coefficients, without allocating memory by performing the sum of the operators. """ -struct OperatorSum{CT<:Vector{<:Number},OT<:AbstractVector} <: AbstractQuantumObject +struct OperatorSum{CT<:AbstractVector{<:Number},OT<:Union{AbstractVector,Tuple}} <: AbstractQuantumObject coefficients::CT operators::OT - function OperatorSum(coefficients::CT, operators::OT) where {CT<:Vector{<:Number},OT<:AbstractVector} + function OperatorSum( + coefficients::CT, + operators::OT, + ) where {CT<:AbstractVector{<:Number},OT<:Union{AbstractVector,Tuple}} length(coefficients) == length(operators) || throw(DimensionMismatch("The number of coefficients must be the same as the number of operators.")) # Check if all the operators have the same dimensions @@ -22,7 +25,8 @@ struct OperatorSum{CT<:Vector{<:Number},OT<:AbstractVector} <: AbstractQuantumOb mapreduce(eltype, promote_type, coefficients), ) coefficients2 = T.(coefficients) - return new{Vector{T},OT}(coefficients2, operators) + CT2 = typeof(coefficients2) + return new{CT2,OT}(coefficients2, operators) end end diff --git a/src/steadystate.jl b/src/steadystate.jl index ab60695e5..59e6f6ad9 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -66,7 +66,7 @@ end @doc raw""" steadystate( H::QuantumObject, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateSolver = SteadyStateDirectSolver(), kwargs... ) @@ -75,13 +75,13 @@ Solve the stationary state based on different solvers. # Parameters - `H::QuantumObject`: The Hamiltonian or the Liouvillian of the system. -- `c_ops::Union{Nothing,AbstractVector}=nothing`: The list of the collapse operators. +- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators. - `solver::SteadyStateSolver=SteadyStateDirectSolver()`: see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for different solvers. - `kwargs...`: The keyword arguments for the solver. """ function steadystate( H::QuantumObject{<:AbstractArray,OpType}, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateSolver = SteadyStateDirectSolver(), kwargs..., ) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} @@ -188,7 +188,7 @@ _steadystate( H::QuantumObject, ψ0::QuantumObject, tspan::Real = Inf, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateODESolver = SteadyStateODESolver(), reltol::Real = 1.0e-8, abstol::Real = 1.0e-10, @@ -213,7 +213,7 @@ or - `H::QuantumObject`: The Hamiltonian or the Liouvillian of the system. - `ψ0::QuantumObject`: The initial state of the system. - `tspan::Real=Inf`: The final time step for the steady state problem. -- `c_ops::Union{Nothing,AbstractVector}=nothing`: The list of the collapse operators. +- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators. - `solver::SteadyStateODESolver=SteadyStateODESolver()`: see [`SteadyStateODESolver`](@ref) for more details. - `reltol::Real=1.0e-8`: Relative tolerance in steady state terminate condition and solver adaptive timestepping. - `abstol::Real=1.0e-10`: Absolute tolerance in steady state terminate condition and solver adaptive timestepping. @@ -223,7 +223,7 @@ function steadystate( H::QuantumObject{MT1,HOpType}, ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, tspan::Real = Inf, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateODESolver = SteadyStateODESolver(), reltol::Real = 1.0e-8, abstol::Real = 1.0e-10, @@ -274,7 +274,7 @@ end H_p::QuantumObject{<:AbstractArray,OpType2}, H_m::QuantumObject{<:AbstractArray,OpType3}, ωd::Number, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Integer = 2, tol::R = 1e-8, solver::FSolver = SSFloquetLinearSystem, @@ -341,7 +341,7 @@ In the case of `SSFloquetEffectiveLiouvillian`, instead, the effective Liouvilli - `H_p::QuantumObject`: The Hamiltonian or the Liouvillian of the part of the drive that oscillates as ``e^{i \omega t}``. - `H_m::QuantumObject`: The Hamiltonian or the Liouvillian of the part of the drive that oscillates as ``e^{-i \omega t}``. - `ωd::Number`: The frequency of the drive. -- `c_ops::AbstractVector = QuantumObject`: The optional collapse operators. +- `c_ops::Union{Nothing,AbstractVector} = nothing`: The optional collapse operators. - `n_max::Integer = 2`: The number of Fourier components to consider. - `tol::R = 1e-8`: The tolerance for the solver. - `solver::FSolver = SSFloquetLinearSystem`: The solver to use. @@ -352,7 +352,7 @@ function steadystate_floquet( H_p::QuantumObject{<:AbstractArray,OpType2}, H_m::QuantumObject{<:AbstractArray,OpType3}, ωd::Number, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Integer = 2, tol::R = 1e-8, solver::FSolver = SSFloquetLinearSystem(), diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index cfebcbf9a..f73c1edc2 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -100,9 +100,9 @@ end mcsolveProblem(H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector}=nothing, + e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), jump_callback::TJC=ContinuousLindbladJumpCallback(), @@ -147,9 +147,9 @@ If the environmental measurements register a quantum jump, the wave function und - `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. - `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: List of times at which to save the state of the system. -- `c_ops::Union{Nothing,AbstractVector}`: List of collapse operators ``\{\hat{C}_n\}_n``. +- `c_ops::Union{Nothing,AbstractVector,Tuple}`: List of collapse operators ``\{\hat{C}_n\}_n``. - `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}`: List of operators for which to calculate expectation values. +- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. @@ -172,9 +172,9 @@ function mcsolveProblem( H::QuantumObject{MT1,OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray,KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), seeds::Union{Nothing,Vector{Int}} = nothing, @@ -286,9 +286,9 @@ end mcsolveEnsembleProblem(H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector}=nothing, + e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), jump_callback::TJC=ContinuousLindbladJumpCallback(), @@ -335,9 +335,9 @@ If the environmental measurements register a quantum jump, the wave function und - `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. - `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: List of times at which to save the state of the system. -- `c_ops::Union{Nothing,AbstractVector}`: List of collapse operators ``\{\hat{C}_n\}_n``. +- `c_ops::Union{Nothing,AbstractVector,Tuple}`: List of collapse operators ``\{\hat{C}_n\}_n``. - `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}`: List of operators for which to calculate expectation values. +- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. @@ -362,9 +362,9 @@ function mcsolveEnsembleProblem( H::QuantumObject{MT1,OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), jump_callback::TJC = ContinuousLindbladJumpCallback(), @@ -396,9 +396,9 @@ end mcsolve(H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector}=nothing, + e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), ntraj::Int=1, @@ -445,9 +445,9 @@ If the environmental measurements register a quantum jump, the wave function und - `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. - `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: List of times at which to save the state of the system. -- `c_ops::Union{Nothing,AbstractVector}`: List of collapse operators ``\{\hat{C}_n\}_n``. +- `c_ops::Union{Nothing,AbstractVector,Tuple}`: List of collapse operators ``\{\hat{C}_n\}_n``. - `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}`: List of operators for which to calculate expectation values. +- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. @@ -475,9 +475,9 @@ function mcsolve( H::QuantumObject{MT1,OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), seeds::Union{Nothing,Vector{Int}} = nothing, diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 922f29942..8dc5895d9 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -52,9 +52,9 @@ end mesolveProblem(H::QuantumObject, ψ0::QuantumObject, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector}=nothing, + e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), progress_bar::Union{Val,Bool}=Val(true), @@ -77,9 +77,9 @@ where - `H::QuantumObject`: The Hamiltonian ``\hat{H}`` or the Liouvillian of the system. - `ψ0::QuantumObject`: The initial state of the system. - `tlist::AbstractVector`: The time list of the evolution. -- `c_ops::Union{Nothing,AbstractVector}=nothing`: The list of the collapse operators ``\{\hat{C}_n\}_n``. +- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators ``\{\hat{C}_n\}_n``. - `alg::OrdinaryDiffEqAlgorithm=Tsit5()`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}=nothing`: The list of the operators for which the expectation values are calculated. +- `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the operators for which the expectation values are calculated. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing`: The time-dependent Hamiltonian or Liouvillian. - `params::NamedTuple=NamedTuple()`: The parameters of the time evolution. - `progress_bar::Union{Val,Bool}=Val(true)`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. @@ -101,9 +101,9 @@ function mesolveProblem( H::QuantumObject{MT1,HOpType}, ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, tlist, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), @@ -170,9 +170,9 @@ end mesolve(H::QuantumObject, ψ0::QuantumObject, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector}=nothing; + c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector}=nothing, + e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), progress_bar::Union{Val,Bool}=Val(true), @@ -195,9 +195,9 @@ where - `H::QuantumObject`: The Hamiltonian ``\hat{H}`` or the Liouvillian of the system. - `ψ0::QuantumObject`: The initial state of the system. - `tlist::AbstractVector`: The time list of the evolution. -- `c_ops::Union{Nothing,AbstractVector}=nothing`: The list of the collapse operators ``\{\hat{C}_n\}_n``. +- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators ``\{\hat{C}_n\}_n``. - `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}`: List of operators for which to calculate expectation values. +- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Named Tuple of parameters to pass to the solver. - `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. @@ -219,9 +219,9 @@ function mesolve( H::QuantumObject{MT1,HOpType}, ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 383ff2a0d..42966e12b 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -48,7 +48,7 @@ end ψ0::QuantumObject, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm=Tsit5() - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), progress_bar::Union{Val,Bool}=Val(true), @@ -66,7 +66,7 @@ Generates the ODEProblem for the Schrödinger time evolution of a quantum system - `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: The time list of the evolution. - `alg::OrdinaryDiffEqAlgorithm`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}`: The list of operators to be evaluated during the evolution. +- `e_ops::Union{Nothing,AbstractVector,Tuple}`: The list of operators to be evaluated during the evolution. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. - `params::NamedTuple`: The parameters of the system. - `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. @@ -89,7 +89,7 @@ function sesolveProblem( ψ0::QuantumObject{<:AbstractVector{T2},KetQuantumObject}, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), @@ -148,7 +148,7 @@ end ψ0::QuantumObject, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), progress_bar::Union{Val,Bool}=Val(true), @@ -166,7 +166,7 @@ Time evolution of a closed quantum system using the Schrödinger equation: - `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: List of times at which to save the state of the system. - `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}`: List of operators for which to calculate expectation values. +- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. @@ -189,7 +189,7 @@ function sesolve( ψ0::QuantumObject{<:AbstractVector{T2},KetQuantumObject}, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 9d77861bf..a423e66d1 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -67,9 +67,9 @@ end ssesolveProblem(H::QuantumObject, ψ0::QuantumObject, tlist::AbstractVector; - sc_ops::Union{Nothing,AbstractVector}=nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing; alg::StochasticDiffEqAlgorithm=SRA1() - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), progress_bar::Union{Val,Bool}=Val(true), @@ -101,9 +101,9 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. - `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: The time list of the evolution. -- `sc_ops::Union{Nothing,AbstractVector}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. +- `sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. - `alg::StochasticDiffEqAlgorithm`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}=nothing`: The list of operators to be evaluated during the evolution. +- `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. - `params::NamedTuple`: The parameters of the system. - `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. @@ -125,9 +125,9 @@ function ssesolveProblem( H::QuantumObject{MT1,OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), @@ -209,9 +209,9 @@ end ssesolveEnsembleProblem(H::QuantumObject, ψ0::QuantumObject, tlist::AbstractVector; - sc_ops::Union{Nothing,AbstractVector} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm=SRA1() - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), prob_func::Function=_mcsolve_prob_func, @@ -244,9 +244,9 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. - `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: The time list of the evolution. -- `sc_ops::Union{Nothing,AbstractVector}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. +- `sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. - `alg::StochasticDiffEqAlgorithm`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}=nothing`: The list of operators to be evaluated during the evolution. +- `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. - `params::NamedTuple`: The parameters of the system. - `prob_func::Function`: Function to use for generating the SDEProblem. @@ -269,9 +269,9 @@ function ssesolveEnsembleProblem( H::QuantumObject{MT1,OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), prob_func::Function = _ssesolve_prob_func, @@ -291,7 +291,7 @@ end tlist::AbstractVector, sc_ops::Union{Nothing, AbstractVector}=nothing; alg::StochasticDiffEqAlgorithm=SRA1(), - e_ops::Union{Nothing,AbstractVector}=nothing, + e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), ntraj::Int=1, @@ -329,9 +329,9 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. - `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. - `tlist::AbstractVector`: List of times at which to save the state of the system. -- `sc_ops::Union{Nothing,AbstractVector}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. +- `sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. - `alg::StochasticDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector}`: List of operators for which to calculate expectation values. +- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. @@ -358,9 +358,9 @@ function ssesolve( H::QuantumObject{MT1,OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), - e_ops::Union{Nothing,AbstractVector} = nothing, + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), ntraj::Int = 1, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 31e4d2ab4..f38782954 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -164,7 +164,7 @@ end function TimeDependentOperatorSum( coefficient_functions, - operators::Vector{<:QuantumObject}; + operators::Union{AbstractVector{<:QuantumObject},Tuple}; params = nothing, init_time = 0.0, ) @@ -198,7 +198,7 @@ end ### LIOUVILLIAN ### @doc raw""" - liouvillian(H::QuantumObject, c_ops::Union{AbstractVector,Nothing}=nothing, Id_cache=I(prod(H.dims))) + liouvillian(H::QuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims))) Construct the Liouvillian [`SuperOperator`](@ref) for a system Hamiltonian ``\hat{H}`` and a set of collapse operators ``\{\hat{C}_n\}_n``: @@ -218,7 +218,7 @@ See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref). """ function liouvillian( H::QuantumObject{MT1,OpType1}, - c_ops::Union{AbstractVector,Nothing} = nothing, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, Id_cache = I(prod(H.dims)), ) where {MT1<:AbstractMatrix,OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, Id_cache) @@ -254,7 +254,7 @@ function liouvillian_floquet( Hₚ::QuantumObject{<:AbstractArray{T2},OpType2}, Hₘ::QuantumObject{<:AbstractArray{T3},OpType3}, ω::Real, - c_ops::Union{AbstractVector,Nothing} = nothing; + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Int = 3, tol::Real = 1e-15, ) where { diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index ed850565e..1b3df4e79 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -64,7 +64,7 @@ e_ops = [a_d * a] psi0 = fock(N, 3) t_l = LinRange(0, 100 * 2π, 1000) - H_t_f = TimeDependentOperatorSum((((t, p) -> sin(t)),), [H_t]) # It will be converted to liouvillian internally + H_t_f = TimeDependentOperatorSum((((t, p) -> sin(t)),), (H_t,)) # It will be converted to liouvillian internally sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, H_t = H_t_f, progress_bar = Val(false)) ρ_ss1 = steadystate_floquet(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetLinearSystem())[1] ρ_ss2 = diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 9c02da211..7b1f4b424 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -42,6 +42,7 @@ @inferred sesolve(H, psi0, t_l, e_ops = e_ops, progress_bar = Val(false)) @inferred sesolve(H, psi0, t_l, progress_bar = Val(false)) @inferred sesolve(H, psi0, t_l, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) + @inferred sesolve(H, psi0, t_l, e_ops = (a_d * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types end end @@ -123,6 +124,7 @@ @inferred mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolve(H, psi0, t_l, c_ops, progress_bar = Val(false)) @inferred mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) + @inferred mesolve(H, psi0, t_l, (a, a'), e_ops = (a_d * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types end @testset "Type Inference mcsolve" begin @@ -131,6 +133,7 @@ @inferred mcsolve(H, psi0, t_l, c_ops, ntraj = 500, progress_bar = Val(true)) @inferred mcsolve(H, psi0, [0, 10], c_ops, ntraj = 500, progress_bar = Val(false)) @inferred mcsolve(H, Qobj(zeros(Int64, N)), t_l, c_ops, ntraj = 500, progress_bar = Val(false)) + @inferred mcsolve(H, psi0, t_l, (a, a'), e_ops = (a_d * a, a'), ntraj = 500, progress_bar = Val(false)) # We test the type inference for Tuple of different types end @testset "Type Inference ssesolve" begin From a3068b7a6af6caa70ddc6f9595612eb2f7d066dc Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:39:40 +0800 Subject: [PATCH 042/329] Replace `n_th` with `n_thermal` for QuTiP compatibility (#249) * make `n_th` to `n_thermal` and align with qutip * fix fontsize in docs * improve type stability --- benchmarks/eigenvalues.jl | 4 ++-- docs/src/api.md | 2 +- docs/src/users_guide/steadystate.md | 7 ++----- src/time_evolution/time_evolution.jl | 2 +- src/utilities.jl | 19 +++++++++++-------- test/core-test/eigenvalues_and_operators.jl | 8 ++++---- test/core-test/generalized_master_equation.jl | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/benchmarks/eigenvalues.jl b/benchmarks/eigenvalues.jl index d6143b9a2..3fc4287a0 100644 --- a/benchmarks/eigenvalues.jl +++ b/benchmarks/eigenvalues.jl @@ -9,10 +9,10 @@ function benchmark_eigenvalues!(SUITE) ωb = 1 g = 0.2 κ = 0.01 - n_thermal = 0.1 + n_th = 0.1 H = ωc * a_d * a + ωb * b_d * b + g * (a + a_d) * (b + b_d) - c_ops = [√((1 + n_thermal) * κ) * a, √κ * b, √(n_thermal * κ) * a_d] + c_ops = [√((1 + n_th) * κ) * a, √κ * b, √(n_th * κ) * a_d] L = liouvillian(H, c_ops) SUITE["Eigenvalues"]["eigenstates"]["dense"] = @benchmarkable eigenstates($L) diff --git a/docs/src/api.md b/docs/src/api.md index 6d5fdb11e..30c367bb7 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -238,7 +238,7 @@ AbstractLinearMap QuantumToolbox.versioninfo QuantumToolbox.about gaussian -n_th +n_thermal row_major_reshape meshgrid _calculate_expectation! diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index ab06406a9..a69409f5c 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -103,14 +103,11 @@ sol_me = mesolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, progress_bar=false) exp_me = real(sol_me.expect[1, :]) # plot the results -fig = Figure(size = (500, 350), fontsize = 15) +fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], title = L"Decay of Fock state $|10\rangle$ in a thermal environment with $\langle n\rangle=2$", xlabel = "Time", - ylabel = "Number of excitations", - titlesize = 24, - xlabelsize = 20, - ylabelsize = 20 + ylabel = "Number of excitations", ) lines!(ax, tlist, exp_mc, label = "Monte-Carlo", linewidth = 2, color = :blue) lines!(ax, tlist, exp_me, label = "Master Equation", linewidth = 2, color = :orange, linestyle = :dash) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index f38782954..971d53c63 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -322,7 +322,7 @@ function liouvillian_generalized( end # Ohmic reservoir - N_th = n_th.(Ωp, T_list[i]) + N_th = n_thermal.(Ωp, T_list[i]) Sp₀ = QuantumObject(triu(X_op, 1), type = Operator, dims = dims) Sp₁ = QuantumObject(droptol!((@. Ωp * N_th * Sp₀.data), tol), type = Operator, dims = dims) Sp₂ = QuantumObject(droptol!((@. Ωp * (1 + N_th) * Sp₀.data), tol), type = Operator, dims = dims) diff --git a/src/utilities.jl b/src/utilities.jl index a2888b70c..69d250a70 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -3,7 +3,7 @@ Utilities: internal (or external) functions which will be used throughout the entire package =# -export gaussian, n_th +export gaussian, n_thermal export row_major_reshape, meshgrid @doc raw""" @@ -34,15 +34,18 @@ where ``\mu`` and ``\sigma^2`` are the mean and the variance respectively. gaussian(x::Number, μ::Number, σ::Number) = exp(-(x - μ)^2 / (2 * σ^2)) @doc raw""" - n_th(ω::Number, T::Real) + n_thermal(ω::Real, ω_th::Real) -Gives the mean number of excitations in a mode with frequency ω at temperature T: -``n_{\rm th} (\omega, T) = \frac{1}{e^{\omega/T} - 1}`` +Return the number of photons in thermal equilibrium for an harmonic oscillator mode with frequency ``\omega``, at the temperature described by ``\omega_{\textrm{th}} \equiv k_B T / \hbar``: +```math +n(\omega, \omega_{\textrm{th}}) = \frac{1}{e^{\omega/\omega_{\textrm{th}}} - 1}, +``` +where ``\hbar`` is the reduced Planck constant, and ``k_B`` is the Boltzmann constant. """ -function n_th(ω::Real, T::Real)::Float64 - (T == 0 || ω == 0) && return 0.0 - abs(ω / T) > 50 && return 0.0 - return 1 / (exp(ω / T) - 1) +function n_thermal(ω::Real, ω_th::Real)::Float64 + x = exp(ω / ω_th) + n = ((x != 1) && (ω_th > 0)) ? (1.0 / (x - 1.0)) : 0.0 + return n end _get_dense_similar(A::AbstractArray, args...) = similar(A, args...) diff --git a/test/core-test/eigenvalues_and_operators.jl b/test/core-test/eigenvalues_and_operators.jl index 9e8ad76a3..31d5997af 100644 --- a/test/core-test/eigenvalues_and_operators.jl +++ b/test/core-test/eigenvalues_and_operators.jl @@ -50,10 +50,10 @@ ωb = 1 g = 0.01 κ = 0.1 - n_thermal = 0.01 + n_th = 0.01 H = ωc * a_d * a + ωb * b_d * b + g * (a + a_d) * (b + b_d) - c_ops = [√((1 + n_thermal) * κ) * a, √κ * b, √(n_thermal * κ) * a_d] + c_ops = [√((1 + n_th) * κ) * a, √κ * b, √(n_th * κ) * a_d] L = liouvillian(H, c_ops) # eigen solve for general matrices @@ -103,10 +103,10 @@ ωb = 1 g = 0.01 κ = 0.1 - n_thermal = 0.01 + n_th = 0.01 H = ωc * a_d * a + ωb * b_d * b + g * (a + a_d) * (b + b_d) - c_ops = [√((1 + n_thermal) * κ) * a, √κ * b, √(n_thermal * κ) * a_d] + c_ops = [√((1 + n_th) * κ) * a, √κ * b, √(n_th * κ) * a_d] L = liouvillian(H, c_ops) @inferred eigenstates(H, sparse = false) diff --git a/test/core-test/generalized_master_equation.jl b/test/core-test/generalized_master_equation.jl index 080ab17e7..a35114e86 100644 --- a/test/core-test/generalized_master_equation.jl +++ b/test/core-test/generalized_master_equation.jl @@ -40,7 +40,7 @@ a2 = Qobj(dense_to_sparse((U'*a*U).data[1:N_trunc, 1:N_trunc], tol)) sm2 = Qobj(dense_to_sparse((U'*sm*U).data[1:N_trunc, 1:N_trunc], tol)) - @test abs(expect(Xp' * Xp, steadystate(L1)) - n_th(1, Tlist[1])) / n_th(1, Tlist[1]) < 1e-4 + @test abs(expect(Xp' * Xp, steadystate(L1)) - n_thermal(1, Tlist[1])) / n_thermal(1, Tlist[1]) < 1e-4 @testset "Type Inference (liouvillian_generalized)" begin N_c = 30 From 6972ee5c78135def23e527460fd6e2c49964fc8f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 3 Oct 2024 17:30:48 +0800 Subject: [PATCH 043/329] fix return type precision for `n_thermal` --- src/utilities.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 69d250a70..a9ee9af94 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -42,10 +42,10 @@ n(\omega, \omega_{\textrm{th}}) = \frac{1}{e^{\omega/\omega_{\textrm{th}}} - 1}, ``` where ``\hbar`` is the reduced Planck constant, and ``k_B`` is the Boltzmann constant. """ -function n_thermal(ω::Real, ω_th::Real)::Float64 +function n_thermal(ω::T1, ω_th::T2) where {T1<:Real,T2<:Real} x = exp(ω / ω_th) - n = ((x != 1) && (ω_th > 0)) ? (1.0 / (x - 1.0)) : 0.0 - return n + n = ((x != 1) && (ω_th > 0)) ? 1 / (x - 1) : 0 + return _FType(promote_type(T1, T2))(n) end _get_dense_similar(A::AbstractArray, args...) = similar(A, args...) From 58d5d881953cdf2e4a2e23502d1b99421d93b521 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:55:07 +0200 Subject: [PATCH 044/329] [DOCS] add Documentation about type instabilities (#250) * General type instabilties and SVector dims * Write second part * Fix typos --- docs/Project.toml | 1 + docs/src/type_stability.md | 284 ++++++++++++++++++++++++++++++++++++- 2 files changed, 284 insertions(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index 08fe32855..88173fe01 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,4 +1,5 @@ [deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" diff --git a/docs/src/type_stability.md b/docs/src/type_stability.md index c3cd0e120..425ff2b4b 100644 --- a/docs/src/type_stability.md +++ b/docs/src/type_stability.md @@ -1,3 +1,285 @@ # [The Importance of Type-Stability](@id doc:Type-Stability) -This page is still under construction. +You are here because you have probably heard about the excellent performance of Julia compared to other common programming languages like Python. One of the reasons is the Just-In-Time (JIT) compiler of Julia, which is able to generate highly optimized machine code. However, the JIT compiler can only do its job if the code type can be inferred. You can also read the [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/) section in Julia's documentation for more details. Here, we try to explain it briefly, with a focus on the `QuantumToolbox.jl` package. + +!!! note + This page is not a tutorial on `QuantumToolbox.jl`, but rather a general guide to writing Julia code for simulating quantum systems efficiently. If you don't care about the performance of your code, you can skip this page. + +## Basics of type stability + +Let's have a look at the following example: + +```@setup type-stability +using InteractiveUtils +using QuantumToolbox +``` + +```@example type-stability +function foo(x) + if x > 0 + return 1 + else + return -1.0 + end +end +nothing # hide +``` + +The function `foo` apparently seems to be innocent. It takes an argument `x` and returns either `1` or `-1.0` depending on the sign of `x`. However, the return type of `foo` is not clear. If `x` is positive, the return type is `Int`, otherwise it is `Float64`. This is a problem for the JIT compiler, because it has to determine the return type of `foo` at runtime. This is called type instability (even though it is a weak form) and may lead to a significant performance penalty. To avoid this, always aim for type-stable code. This means that the return type of a function should be clear from the types of its arguments. We can check the inferred return type of `foo` using the `@code_warntype` macro: + +```@example type-stability +@code_warntype foo(1) +``` + +The key point is to ensure the return type of a function is clear from the types of its arguments. There are several ways to achieve this, and the best approach depends on the specific problem. For example, one can use the same return type: + +```@example type-stability +function foo(x) + if x > 0 + return 1.0 + else + return -1.0 + end +end +nothing # hide +``` + +Or you can ensure the return type matches the type of the argument: + +```@example type-stability +function foo(x::T) where T + if x > 0 + return T(1) + else + return -T(1) + end +end +nothing # hide +``` + +The latter example is very important because it takes advantage of Julia's multiple dispatch, which is one of the most powerful features of the language. Depending on the type `T` of the argument `x`, the Julia compiler generates a specialized version of `foo` that is optimized for this type. If the input type is an `Int64`, the return type is `Int64`, if `x` is a `Float64`, the return type is `Float64`, and so on. + +```@example type-stability +@show foo(1) +@show foo(-4.4) +@show foo(1//2) +nothing # hide +``` + +!!! note + If you didn't know how to make this function type-stable, it is probably a good idea to read the official Julia documentation, and in particular its [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/) section. + +## Global variables + +Another source of type instability is the use of global variables. In general, it is a good idea to declare global variables as `const` to ensure their type is fixed for the entire program. For example, consider the following function that internally takes a global variable `y`: + +```@example type-stability +y = 2.4 + +function bar(x) + res = zero(x) # this returns the zero of the same type of x + for i in 1:1000 + res += y * x + end + return res +end +nothing # hide +``` + +The Julia compiler cannot infer the type of `res` because it depends on the type of `y`, which is a global variable that can change at any time of the program. We can check it using the `@code_warntype` macro: + +```@example type-stability +@code_warntype bar(3.2) +``` + +While in the last example of the `foo` function we got a weak form of type instability, returning a `Union{Int, Float64}`, in this case the return type of `bar` is `Any`, meaning that the compiler doesn't know anything about the return type. Thus, this function has nothing different from a dynamically typed language like Python. We can benchmark the performance of `bar` using the [BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl) package: + +```@example type-stability +using BenchmarkTools + +@benchmark bar(3.2) +``` + +Here we see a lot of memory allocations and low performances in general. To fix this, we can declare a `const` (constant) variable instead: + +```@example type-stability +const z = 2.4 + +function bar(x) + res = zero(x) # this returns the zero of the same type of x + for i in 1:1000 + res += z * x + end + return res +end + +@benchmark bar(3.2) +``` + +And we can see that the performance has improved significantly. Hence, we highly recommend using global variables as `const`, but only when truly necessary. This choice is problem-dependent, but in the case of `QuantumToolbox.jl`, this can be applied for example in the case of defining the Hilbert space dimensions, static parameters, or the system operators. + +Although it is always a good practice to avoid such kind of type instabilities, in the actual implementation of `QuantumToolbox.jl` (where we mainly deal with linear algebra operations), the compiler may perform only a few runtime dispatches, and the performance penalty may be negligible compared to the heavy linear algebra operations. + +## Vectors vs Tuples vs StaticArrays + +Julia has many ways to represent arrays or lists of general objects. The most common are `Vector`s and `Tuple`s. The former is a dynamic array that can change its size at runtime, while the latter is a fixed-size array that is immutable, and where the type of each element is already known at compile time. For example: + +```@example type-stability +v1 = [1, 2, 3] # Vector of Int64 +v2 = [1.0 + 2.0im, 3.0 + 4.0im] # Vector of ComplexF64 +v3 = [1, "ciao", 3.0] # Vector of Any + +t1 = (1, 2, 3) # Tuple of {Int64, Int64, Int64} +t2 = (1.0 + 2.0im, 3.0 + 4.0im) # Tuple of {ComplexF64, ComplexF64} +t3 = (1, "ciao", 3.0) # Tuple of {Int64, String, Float64} + +@show typeof(v1) +@show typeof(v2) +@show typeof(v3) +@show typeof(t1) +@show typeof(t2) +@show typeof(t3) +nothing # hide +``` + +Thus, we highly recommend using `Vector` only when we are sure that it contains elements of the same type, and only when we don't need to know its size at compile time. On the other hand, `Tuple`s are less flexible but more efficient in terms of performance. A third option is to use the `SVector` type from the [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) package. This is similar to `Vector`, where the elements should have the same type, but it is fixed-size and immutable. One may ask when it is necessary to know the array size at compile time. A practical example is the case of [`ptrace`](@ref), where it internally reshapes the quantum state into a tensor whose dimensions depend on the number of subsystems. We will see this in more detail in the next section. + +## The [`QuantumObject`](@ref) internal structure + +Before making a practical example, let's see the internal structure of the [`QuantumObject`](@ref) type. As an example, we consider the case of three qubits, and we study the internal structure of the ``\hat{\sigma}_x^{(2)}`` operator: + +```@example type-stability +σx_2 = tensor(qeye(2), sigmax(), qeye(2)) +``` + +and its type is + +```@example type-stability +obj_type = typeof(σx_2) +``` + +This is exactly what the Julia compiler sees: it is a [`QuantumObject`](@ref), composed by a field of type `SparseMatrixCSC{ComplexF64, Int64}` (i.e., the 8x8 matrix containing the Pauli matrix, tensored with the identity matrices of the other two qubits). Then, we can also see that it is a [`OperatorQuantumObject`](@ref), with `3` subsystems in total. Hence, just looking at the type of the object, the compiler has all the information it needs to generate a specialized version of the functions. + +Let's see more in the details all the internal fields of the [`QuantumObject`](@ref) type: + +```@example type-stability +fieldnames(obj_type) +``` + +```@example type-stability +σx_2.data +``` + +```@example type-stability +σx_2.type +``` + +[`Operator`](@ref) is a synonym for [`OperatorQuantumObject`](@ref). + +```@example type-stability +σx_2.dims +``` + +The `dims` field contains the dimensions of the subsystems (in this case, three subsystems with dimension `2` each). We can see that the type of `dims` is `SVector` instead of `Vector`. As we mentioned before, this is very useful in functions like [`ptrace`](@ref). Let's do a simple example of reshaping an operator internally generated from some `dims` input: + +```@example type-stability +function reshape_operator_data(dims) + op = Qobj(randn(prod(dims), prod(dims)), type=Operator, dims=dims) + op_dims = op.dims + op_data = op.data + return reshape(op_data, vcat(op_dims, op_dims)...) +end + +typeof(reshape_operator_data([2, 2, 2])) +``` + +Which returns a tensor of size `2x2x2x2x2x2`. Let's check the `@code_warntype`: + +```@example type-stability +@code_warntype reshape_operator_data([2, 2, 2]) +``` + +We got a `Any` type, because the compiler doesn't know the size of the `dims` vector. We can fix this by using a `Tuple` (or `SVector`): + +```@example type-stability +typeof(reshape_operator_data((2, 2, 2))) +``` + +```@example type-stability +@code_warntype reshape_operator_data((2, 2, 2)) +``` + +Finally, let's look at the benchmarks + +```@example type-stability +@benchmark reshape_operator_data($[2, 2, 2]) +``` + +```@example type-stability +@benchmark reshape_operator_data($((2, 2, 2))) +``` + +Which is an innocuous but huge difference in terms of performance. Hence, we highly recommend using `Tuple` or `SVector` when defining the dimensions of a user-defined [`QuantumObject`](@ref). + +## The use of `Val` in some `QuantumToolbox.jl` functions + +In some functions of `QuantumToolbox.jl`, you may find the use of the [`Val`](https://docs.julialang.org/en/v1/base/base/#Base.Val) type in the arguments. This is a trick to pass a value at compile time, and it is very useful to avoid type instabilities. Let's make a very simple example, where we want to create a Fock state ``|j\rangle`` of a given dimension `N`, and we give the possibility to create it as a sparse or dense vector. At first, we can write the function without using `Val`: + +```@example type-stability +function my_fock(N::Int, j::Int = 0; sparse::Bool = false) + if sparse + array = sparsevec([j + 1], [1.0 + 0im], N) + else + array = zeros(ComplexF64, N) + array[j+1] = 1 + end + return QuantumObject(array; type = Ket) +end +@show my_fock(2, 1) +@show my_fock(2, 1; sparse = true) +nothing # hide +``` + +But it is immediately clear that the return type of this function is not clear, because it depends on the value of the `sparse` argument. We can check it using the `@code_warntype` macro: + +```@example type-stability +@code_warntype my_fock(2, 1) +``` + +```@example type-stability +@code_warntype my_fock(2, 1; sparse = true) +``` + +We can fix this by using the `Val` type, where we enable the multiple dispatch of the function: + +```@example type-stability +getVal(::Val{N}) where N = N +function my_fock_good(N::Int, j::Int = 0; sparse::Val = Val(false)) + if getVal(sparse) + array = zeros(ComplexF64, N) + array[j+1] = 1 + else + array = sparsevec([j + 1], [1.0 + 0im], N) + end + return QuantumObject(array; type = Ket) +end +@show my_fock_good(2, 1) +@show my_fock_good(2, 1; sparse = Val(true)) +nothing # hide +``` + +And now the return type of the function is clear: + +```@example type-stability +@code_warntype my_fock_good(2, 1) +``` + +```@example type-stability +@code_warntype my_fock_good(2, 1; sparse = Val(true)) +``` + +## Conclusions + +In this page, we have seen the importance of type stability in Julia, and how to write efficient code in the context of `QuantumToolbox.jl`. We have seen that the internal structure of the [`QuantumObject`](@ref) type is already optimized for the compiler, and we have seen some practical examples of how to write efficient code. We have seen that the use of `Vector` should be avoided when the elements don't have the same type, and that the use of `Tuple` or `SVector` is highly recommended when the size of the array is known at compile time. Finally, we have seen the use of `Val` to pass values at compile time, to avoid type instabilities in some functions. +``` + From ce7af7cbeeff12530176f4fbefa99123bbc80dd4 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Thu, 3 Oct 2024 15:25:44 +0200 Subject: [PATCH 045/329] Remove Luca from list of maintainers --- Project.toml | 2 +- README.md | 3 +-- docs/make.jl | 2 +- docs/src/type_stability.md | 2 ++ src/versioninfo.jl | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index dfbfae0ed..b3170b2d9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" -authors = ["Alberto Mercurio", "Luca Gravina", "Yi-Te Huang"] +authors = ["Alberto Mercurio", "Yi-Te Huang"] version = "0.16.0" [deps] diff --git a/README.md b/README.md index cd4219aa6..d22917c45 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ # QuantumToolbox.jl -[A. Mercurio](https://github.com/albertomercurio), -[L. Gravina](https://github.com/lgravina1997), +[A. Mercurio](https://github.com/albertomercurio) and [Y.-T. Huang](https://github.com/ytdHuang). diff --git a/docs/make.jl b/docs/make.jl index 77fbb75e9..d0a631ad6 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -63,7 +63,7 @@ const PAGES = [ makedocs(; modules = [QuantumToolbox], - authors = "Alberto Mercurio, Luca Gravina and Yi-Te Huang", + authors = "Alberto Mercurio and Yi-Te Huang", repo = Remotes.GitHub("qutip", "QuantumToolbox.jl"), sitename = "QuantumToolbox.jl", pages = PAGES, diff --git a/docs/src/type_stability.md b/docs/src/type_stability.md index 425ff2b4b..b9a31f20d 100644 --- a/docs/src/type_stability.md +++ b/docs/src/type_stability.md @@ -278,6 +278,8 @@ And now the return type of the function is clear: @code_warntype my_fock_good(2, 1; sparse = Val(true)) ``` +This is exactly how the current [`fock`](@ref) function is implemented in `QuantumToolbox.jl`. There are many other functions that support this feature, and we highly recommend using it when necessary. + ## Conclusions In this page, we have seen the importance of type stability in Julia, and how to write efficient code in the context of `QuantumToolbox.jl`. We have seen that the internal structure of the [`QuantumObject`](@ref) type is already optimized for the compiler, and we have seen some practical examples of how to write efficient code. We have seen that the use of `Vector` should be avoided when the elements don't have the same type, and that the use of `Tuple` or `SVector` is highly recommended when the size of the array is known at compile time. Finally, we have seen the use of `Val` to pass values at compile time, to avoid type instabilities in some functions. diff --git a/src/versioninfo.jl b/src/versioninfo.jl index 4b67e3116..abe725f59 100644 --- a/src/versioninfo.jl +++ b/src/versioninfo.jl @@ -20,7 +20,7 @@ function versioninfo(io::IO = stdout) "≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n", "Copyright © QuTiP team 2022 and later.\n", "Current admin team:\n", - " Alberto Mercurio, Luca Gravina, Yi-Te Huang\n", + " Alberto Mercurio and Yi-Te Huang\n", ) # print package informations From 48f8c41840c623d791789bedd4e7272c0522139b Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:42:14 +0200 Subject: [PATCH 046/329] Add progress_bar in mcsolve, ssesolve and dsf_mcsolve (#254) --- Project.toml | 12 +-- src/QuantumToolbox.jl | 1 + src/qobj/operators.jl | 2 +- src/qobj/states.jl | 4 +- src/time_evolution/mcsolve.jl | 76 ++++++++++++------- src/time_evolution/mesolve.jl | 7 +- src/time_evolution/sesolve.jl | 7 +- src/time_evolution/ssesolve.jl | 60 +++++++++------ .../time_evolution_dynamical.jl | 50 +++++++----- src/utilities.jl | 1 + 10 files changed, 136 insertions(+), 84 deletions(-) diff --git a/Project.toml b/Project.toml index b3170b2d9..dbcb68c70 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def" DiffEqNoiseProcess = "77a26b50-5914-5dd7-bc55-306e6241c503" +Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895" @@ -37,23 +38,24 @@ CUDA = "5" DiffEqBase = "6" DiffEqCallbacks = "2 - 3.1, 3.8, 4" DiffEqNoiseProcess = "5" +Distributed = "1" FFTW = "1.5" Graphs = "1.7" IncompleteLU = "0.2" -LinearAlgebra = "<0.0.1, 1" +LinearAlgebra = "1" LinearSolve = "2" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" -Pkg = "<0.0.1, 1" -Random = "<0.0.1, 1" +Pkg = "1" +Random = "1" Reexport = "1" SciMLBase = "2" SciMLOperators = "0.3" -SparseArrays = "<0.0.1, 1" +SparseArrays = "1" SpecialFunctions = "2" StaticArraysCore = "1" StochasticDiffEq = "6" -Test = "<0.0.1, 1" +Test = "1" julia = "1.10" [extras] diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 1822ff09b..2326a0846 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -40,6 +40,7 @@ import DiffEqNoiseProcess: RealWienerProcess # other dependencies (in alphabetical order) import ArrayInterface: allowed_getindex, allowed_setindex! +import Distributed: RemoteChannel import FFTW: fft, fftshift import Graphs: connected_components, DiGraph import IncompleteLU: ilu diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index feb15780b..9ce680012 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -511,7 +511,7 @@ function tunneling(N::Int, m::Int = 1; sparse::Union{Bool,Val} = Val(false)) (m < 1) && throw(ArgumentError("The number of excitations (m) cannot be less than 1")) data = ones(ComplexF64, N - m) - if getVal(makeVal(sparse)) + if getVal(sparse) return QuantumObject(spdiagm(m => data, -m => data); type = Operator, dims = N) else return QuantumObject(diagm(m => data, -m => data); type = Operator, dims = N) diff --git a/src/qobj/states.jl b/src/qobj/states.jl index df01f81ce..92c5ff205 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -34,7 +34,7 @@ It is also possible to specify the list of dimensions `dims` if different subsys If you want to keep type stability, it is recommended to use `fock(N, j, dims=dims, sparse=Val(sparse))` instead of `fock(N, j, dims=dims, sparse=sparse)`. Consider also to use `dims` as a `Tuple` or `SVector` instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ function fock(N::Int, j::Int = 0; dims::Union{Int,AbstractVector{Int},Tuple} = N, sparse::Union{Bool,Val} = Val(false)) - if getVal(makeVal(sparse)) + if getVal(sparse) array = sparsevec([j + 1], [1.0 + 0im], N) else array = zeros(ComplexF64, N) @@ -130,7 +130,7 @@ function thermal_dm(N::Int, n::Real; sparse::Union{Bool,Val} = Val(false)) β = log(1.0 / n + 1.0) N_list = Array{Float64}(0:N-1) data = exp.(-β .* N_list) - if getVal(makeVal(sparse)) + if getVal(sparse) return QuantumObject(spdiagm(0 => data ./ sum(data)), Operator, N) else return QuantumObject(diagm(0 => data ./ sum(data)), Operator, N) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index f73c1edc2..6b63570c6 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -83,6 +83,7 @@ end function _mcsolve_output_func(sol, i) resize!(sol.prob.p.jump_times, sol.prob.p.jump_times_which_idx[] - 1) resize!(sol.prob.p.jump_which, sol.prob.p.jump_times_which_idx[] - 1) + put!(sol.prob.p.progr_channel, true) return (sol, false) end @@ -204,7 +205,8 @@ function mcsolveProblem( end saveat = e_ops isa Nothing ? t_l : [t_l[end]] - default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) + # We disable the progress bar of the sesolveProblem because we use a global progress bar for all the trajectories + default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat, progress_bar = Val(false)) kwargs2 = merge(default_values, kwargs) cache_mc = similar(ψ0.data) @@ -396,15 +398,20 @@ end mcsolve(H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - ntraj::Int=1, - ensemble_method=EnsembleThreads(), - jump_callback::TJC=ContinuousLindbladJumpCallback(), - kwargs...) + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + alg::OrdinaryDiffEqAlgorithm = Tsit5(), + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + params::NamedTuple = NamedTuple(), + seeds::Union{Nothing,Vector{Int}} = nothing, + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + jump_callback::TJC = ContinuousLindbladJumpCallback(), + prob_func::Function = _mcsolve_prob_func, + output_func::Function = _mcsolve_output_func, + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) Time evolution of an open quantum system using quantum trajectories. @@ -457,6 +464,7 @@ If the environmental measurements register a quantum jump, the wave function und - `prob_func::Function`: Function to use for generating the ODEProblem. - `output_func::Function`: Function to use for generating the output of a single trajectory. - `kwargs...`: Additional keyword arguments to pass to the solver. +- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. # Notes @@ -486,29 +494,42 @@ function mcsolve( jump_callback::TJC = ContinuousLindbladJumpCallback(), prob_func::Function = _mcsolve_prob_func, output_func::Function = _mcsolve_output_func, + progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2,TJC<:LindbladJumpCallbackType} if !isnothing(seeds) && length(seeds) != ntraj throw(ArgumentError("Length of seeds must match ntraj ($ntraj), but got $(length(seeds))")) end - ens_prob_mc = mcsolveEnsembleProblem( - H, - ψ0, - tlist, - c_ops; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = params, - seeds = seeds, - jump_callback = jump_callback, - prob_func = prob_func, - output_func = output_func, - kwargs..., - ) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) + @async while take!(progr_channel) + next!(progr) + end - return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + # Stop the async task if an error occurs + try + ens_prob_mc = mcsolveEnsembleProblem( + H, + ψ0, + tlist, + c_ops; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = merge(params, (progr_channel = progr_channel,)), + seeds = seeds, + jump_callback = jump_callback, + prob_func = prob_func, + output_func = output_func, + kwargs..., + ) + + return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + catch e + put!(progr_channel, false) + rethrow() + end end function mcsolve( @@ -518,6 +539,9 @@ function mcsolve( ensemble_method = EnsembleThreads(), ) sol = solve(ens_prob_mc, alg, ensemble_method, trajectories = ntraj) + + put!(sol[:, 1].prob.p.progr_channel, false) + _sol_1 = sol[:, 1] expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 8dc5895d9..3188e3b45 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -120,14 +120,13 @@ function mesolveProblem( throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) is_time_dependent = !(H_t isa Nothing) - progress_bar_val = makeVal(progress_bar) ρ0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl L = liouvillian(H, c_ops).data - progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) + progr = ProgressBar(length(t_l), enable = getVal(progress_bar)) if e_ops isa Nothing expvals = Array{ComplexF64}(undef, 0, length(t_l)) @@ -158,7 +157,7 @@ function mesolveProblem( saveat = e_ops isa Nothing ? t_l : [t_l[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_mesolve_kwargs(e_ops, progress_bar_val, t_l, kwargs2) + kwargs3 = _generate_mesolve_kwargs(e_ops, makeVal(progress_bar), t_l, kwargs2) dudt! = is_time_dependent ? mesolve_td_dudt! : mesolve_ti_dudt! @@ -241,7 +240,7 @@ function mesolve( e_ops = e_ops, H_t = H_t, params = params, - progress_bar = makeVal(progress_bar), + progress_bar = progress_bar, kwargs..., ) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 42966e12b..b30ed2cb2 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -101,14 +101,13 @@ function sesolveProblem( throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) is_time_dependent = !(H_t isa Nothing) - progress_bar_val = makeVal(progress_bar) ϕ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) # Convert it to dense vector with complex element type t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl U = -1im * get_data(H) - progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) + progr = ProgressBar(length(t_l), enable = getVal(progress_bar)) if e_ops isa Nothing expvals = Array{ComplexF64}(undef, 0, length(t_l)) @@ -135,7 +134,7 @@ function sesolveProblem( saveat = e_ops isa Nothing ? t_l : [t_l[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_sesolve_kwargs(e_ops, progress_bar_val, t_l, kwargs2) + kwargs3 = _generate_sesolve_kwargs(e_ops, makeVal(progress_bar), t_l, kwargs2) dudt! = is_time_dependent ? sesolve_td_dudt! : sesolve_ti_dudt! @@ -203,7 +202,7 @@ function sesolve( e_ops = e_ops, H_t = H_t, params = params, - progress_bar = makeVal(progress_bar), + progress_bar = progress_bar, kwargs..., ) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index a423e66d1..bdd92ba2d 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -52,7 +52,10 @@ function _ssesolve_prob_func(prob, i, repeat) return remake(prob, p = prm, noise = noise, noise_rate_prototype = noise_rate_prototype) end -_ssesolve_output_func(sol, i) = (sol, false) +function _ssesolve_output_func(sol, i) + put!(sol.prob.p.progr_channel, true) + return (sol, false) +end function _ssesolve_generate_statistics!(sol, i, states, expvals_all) sol_i = sol[:, i] @@ -72,7 +75,6 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), - progress_bar::Union{Val,Bool}=Val(true), kwargs...) Generates the SDEProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: @@ -106,7 +108,6 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. - `params::NamedTuple`: The parameters of the system. -- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs...`: The keyword arguments passed to the `SDEProblem` constructor. # Notes @@ -130,7 +131,6 @@ function ssesolveProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), - progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2} H.dims != ψ0.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) @@ -143,8 +143,6 @@ function ssesolveProblem( !(H_t isa Nothing) && throw(ArgumentError("Time-dependent Hamiltonians are not currently supported in ssesolve.")) - progress_bar_val = makeVal(progress_bar) - t_l = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for StochasticDiffEq.jl ϕ0 = get_data(ψ0) @@ -159,8 +157,6 @@ function ssesolveProblem( D = reduce(vcat, sc_ops2) - progr = ProgressBar(length(t_l), enable = getVal(progress_bar_val)) - if e_ops isa Nothing expvals = Array{ComplexF64}(undef, 0, length(t_l)) e_ops2 = MT1[] @@ -177,7 +173,6 @@ function ssesolveProblem( e_ops = e_ops2, sc_ops = sc_ops2, expvals = expvals, - progr = progr, Hdims = H.dims, H_t = H_t, times = t_l, @@ -188,7 +183,7 @@ function ssesolveProblem( saveat = e_ops isa Nothing ? t_l : [t_l[end]] default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_sesolve_kwargs(e_ops, progress_bar_val, t_l, kwargs2) + kwargs3 = _generate_sesolve_kwargs(e_ops, Val(false), t_l, kwargs2) tspan = (t_l[1], t_l[end]) noise = RealWienerProcess(t_l[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false) @@ -298,6 +293,7 @@ end ensemble_method=EnsembleThreads(), prob_func::Function=_mcsolve_prob_func, output_func::Function=_mcsolve_output_func, + progress_bar::Union{Val,Bool} = Val(true), kwargs...) @@ -339,6 +335,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `ensemble_method`: Ensemble method to use. - `prob_func::Function`: Function to use for generating the SDEProblem. - `output_func::Function`: Function to use for generating the output of a single trajectory. +- `progress_bar::Union{Val,Bool}`: Whether to show a progress bar. - `kwargs...`: Additional keyword arguments to pass to the solver. # Notes @@ -367,23 +364,35 @@ function ssesolve( ensemble_method = EnsembleThreads(), prob_func::Function = _ssesolve_prob_func, output_func::Function = _ssesolve_output_func, + progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2} - ens_prob = ssesolveEnsembleProblem( - H, - ψ0, - tlist, - sc_ops; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = params, - prob_func = prob_func, - output_func = output_func, - kwargs..., - ) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) + @async while take!(progr_channel) + next!(progr) + end - return ssesolve(ens_prob; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + try + ens_prob = ssesolveEnsembleProblem( + H, + ψ0, + tlist, + sc_ops; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = merge(params, (progr_channel = progr_channel,)), + prob_func = prob_func, + output_func = output_func, + kwargs..., + ) + + return ssesolve(ens_prob; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + catch e + put!(progr_channel, false) + rethrow() + end end function ssesolve( @@ -393,6 +402,9 @@ function ssesolve( ensemble_method = EnsembleThreads(), ) sol = solve(ens_prob, alg, ensemble_method, trajectories = ntraj) + + put!(sol[:, 1].prob.p.progr_channel, false) + _sol_1 = sol[:, 1] expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 1b1f48278..496f25130 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -691,6 +691,7 @@ end ensemble_method=EnsembleThreads(), jump_callback::LindbladJumpCallbackType=ContinuousLindbladJumpCallback(), krylov_dim::Int=max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), + progress_bar::Union{Bool,Val} = Val(true) kwargs...) Time evolution of a quantum system using the Monte Carlo wave function method and the Dynamical Shifted Fock algorithm. @@ -716,25 +717,38 @@ function dsf_mcsolve( ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), + progress_bar::Union{Bool,Val} = Val(true), kwargs..., ) where {T,TOl,TJC<:LindbladJumpCallbackType} - ens_prob_mc = dsf_mcsolveEnsembleProblem( - H, - ψ0, - t_l, - c_ops, - op_list, - α0_l, - dsf_params; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = params, - δα_list = δα_list, - jump_callback = jump_callback, - krylov_dim = krylov_dim, - kwargs..., - ) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) + @async while take!(progr_channel) + next!(progr) + end - return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + # Stop the async task if an error occurs + try + ens_prob_mc = dsf_mcsolveEnsembleProblem( + H, + ψ0, + t_l, + c_ops, + op_list, + α0_l, + dsf_params; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = merge(params, (progr_channel = progr_channel,)), + δα_list = δα_list, + jump_callback = jump_callback, + krylov_dim = krylov_dim, + kwargs..., + ) + + return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + catch e + put!(progr_channel, false) + rethrow() + end end diff --git a/src/utilities.jl b/src/utilities.jl index a9ee9af94..d81b5f8d0 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -57,6 +57,7 @@ makeVal(x::Val{T}) where {T} = x makeVal(x) = Val(x) getVal(x::Val{T}) where {T} = T +getVal(x) = x # getVal for any other type _get_size(A::AbstractMatrix) = size(A) _get_size(A::AbstractVector) = (length(A), 1) From 51684ab99544e57b1ff8404e5dffc84f2e8acc8b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:17:16 +0800 Subject: [PATCH 047/329] Introduce `convert_unit` (#255) * introduce `convert_unit` * add tests for utility functions * fix value of physical constants --- docs/src/api.md | 1 + src/utilities.jl | 46 ++++++++++++++++++++++++++++++++++++- test/core-test/utilities.jl | 45 ++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 test/core-test/utilities.jl diff --git a/docs/src/api.md b/docs/src/api.md index 30c367bb7..4fa4131f0 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -239,6 +239,7 @@ QuantumToolbox.versioninfo QuantumToolbox.about gaussian n_thermal +convert_unit row_major_reshape meshgrid _calculate_expectation! diff --git a/src/utilities.jl b/src/utilities.jl index d81b5f8d0..74af168ba 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -3,7 +3,7 @@ Utilities: internal (or external) functions which will be used throughout the entire package =# -export gaussian, n_thermal +export gaussian, n_thermal, convert_unit export row_major_reshape, meshgrid @doc raw""" @@ -48,6 +48,50 @@ function n_thermal(ω::T1, ω_th::T2) where {T1<:Real,T2<:Real} return _FType(promote_type(T1, T2))(n) end +# some fundamental physical constants and common energy units +const _e = 1.602176565e-19 # elementary charge (C) +const _kB = 1.3806488e-23 # Boltzmann constant (J/K) +const _h = 6.62607015e-34 # Planck constant (J⋅s) +const _energy_units::Dict{Symbol,Float64} = Dict( + # the values below are all in the unit of Joule + :J => 1.0, + :eV => _e, + :meV => 1e-3 * _e, + :GHz => 1e9 * _h, + :mK => 1e-3 * _kB, +) + +@doc raw""" + convert_unit(value::Real, unit1::Symbol, unit2::Symbol) + +Convert the energy `value` from `unit1` to `unit2`. + +Note that `unit1` and `unit2` can be either the following `Symbol`: +- `:J`: Joule +- `:eV`: electron volt. +- `:meV`: milli-electron volt. +- `:GHz`: Giga-Hertz multiplied by Planck constant ``h``. +- `:mK`: milli-Kelvin multiplied by Boltzmann constant ``k_{\textrm{B}}``. + +# Examples + +``` +julia> convert_unit(1, :eV, :J) +1.602176565e-19 + +julia> convert_unit(1, :GHz, :J) +6.62607015e-25 + +julia> convert_unit(1, :meV, :mK) +11604.51930280894 +``` +""" +function convert_unit(value::T, unit1::Symbol, unit2::Symbol) where {T<:Real} + !haskey(_energy_units, unit1) && throw(ArgumentError("Invalid unit :$(unit1)")) + !haskey(_energy_units, unit2) && throw(ArgumentError("Invalid unit :$(unit2)")) + return _FType(T)(value * (_energy_units[unit1] / _energy_units[unit2])) +end + _get_dense_similar(A::AbstractArray, args...) = similar(A, args...) _get_dense_similar(A::AbstractSparseMatrix, args...) = similar(nonzeros(A), args...) diff --git a/test/core-test/utilities.jl b/test/core-test/utilities.jl new file mode 100644 index 000000000..5d269d695 --- /dev/null +++ b/test/core-test/utilities.jl @@ -0,0 +1,45 @@ +@testset "Utilities" verbose = true begin + @testset "n_thermal" begin + ω1 = rand(Float64) + ω2 = rand(Float64) + @test n_thermal(0, ω2) == 0.0 + @test n_thermal(ω1, 0) == 0.0 + @test n_thermal(ω1, -ω2) == 0.0 + @test n_thermal(ω1, ω2) == 1 / (exp(ω1 / ω2) - 1) + @test typeof(n_thermal(Int32(2), Int32(3))) == Float32 + @test typeof(n_thermal(Float32(2), Float32(3))) == Float32 + @test typeof(n_thermal(Int64(2), Int32(3))) == Float64 + @test typeof(n_thermal(Int32(2), Int64(3))) == Float64 + @test typeof(n_thermal(Float64(2), Float32(3))) == Float64 + @test typeof(n_thermal(Float32(2), Float64(3))) == Float64 + end + + @testset "convert unit" begin + V = 100 * rand(Float64) + _unit_list = [:J, :eV, :meV, :GHz, :mK] + for origin in _unit_list + for middle in _unit_list + for target in _unit_list + V_middle = convert_unit(V, origin, middle) + V_target = convert_unit(V_middle, middle, target) + V_origin = convert_unit(V_target, target, origin) + @test V ≈ V_origin + end + end + end + @test_throws ArgumentError convert_unit(V, :bad_unit, :J) + @test_throws ArgumentError convert_unit(V, :J, :bad_unit) + end + + @testset "Type Inference" begin + v1 = rand(Float64) + @inferred n_thermal(v1, Int32(123)) + + _unit_list = [:J, :eV, :meV, :GHz, :mK] + for u1 in _unit_list + for u2 in _unit_list + @inferred convert_unit(v1, u1, u2) + end + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 70c361cd0..95709e3a7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -23,6 +23,7 @@ core_tests = [ "states_and_operators.jl", "steady_state.jl", "time_evolution.jl", + "utilities.jl", "wigner.jl", ] From c7a34832a65a7a99a62ffa588a61830b96db8b06 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:17:52 +0200 Subject: [PATCH 048/329] Bump version to v0.17.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index dbcb68c70..83d330b9f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.16.0" +version = "0.17.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 221e70c2501e5bfedd1d5e3c1a363749a9f4820a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:38:20 +0800 Subject: [PATCH 049/329] modify Makefile (#256) --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1da025146..8255db3fd 100644 --- a/Makefile +++ b/Makefile @@ -2,23 +2,23 @@ JULIA:=julia default: help -docs: - ${JULIA} --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - ${JULIA} --project=docs docs/make.jl - format: ${JULIA} -e 'using JuliaFormatter; format(".")' test: ${JULIA} --project -e 'using Pkg; Pkg.resolve(); Pkg.test()' +docs: + ${JULIA} --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + ${JULIA} --project=docs docs/make.jl + all: format test docs help: @echo "The following make commands are available:" - @echo " - make docs: instantiate and build the documentation" @echo " - make format: format codes with JuliaFormatter" @echo " - make test: run the tests" - @echo " - make all: run every commands above" + @echo " - make docs: instantiate and build the documentation" + @echo " - make all: run every commands in the above order" -.PHONY: default docs format test all help \ No newline at end of file +.PHONY: default format test docs all help From 2f92e5bde0d2847c0e4dd75cedcceb18e44d6ad2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:39:07 +0800 Subject: [PATCH 050/329] introduce `PhysicalConstants` and new energy units (#257) --- docs/src/api.md | 1 + src/utilities.jl | 74 +++++++++++++++++++++++++++---------- test/core-test/utilities.jl | 14 ++++++- 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/docs/src/api.md b/docs/src/api.md index 4fa4131f0..dae7f72ae 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -239,6 +239,7 @@ QuantumToolbox.versioninfo QuantumToolbox.about gaussian n_thermal +PhysicalConstants convert_unit row_major_reshape meshgrid diff --git a/src/utilities.jl b/src/utilities.jl index 74af168ba..67f62697f 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -3,7 +3,8 @@ Utilities: internal (or external) functions which will be used throughout the entire package =# -export gaussian, n_thermal, convert_unit +export gaussian, n_thermal +export PhysicalConstants, convert_unit export row_major_reshape, meshgrid @doc raw""" @@ -48,42 +49,77 @@ function n_thermal(ω::T1, ω_th::T2) where {T1<:Real,T2<:Real} return _FType(promote_type(T1, T2))(n) end -# some fundamental physical constants and common energy units -const _e = 1.602176565e-19 # elementary charge (C) -const _kB = 1.3806488e-23 # Boltzmann constant (J/K) -const _h = 6.62607015e-34 # Planck constant (J⋅s) +@doc raw""" + const PhysicalConstants + +A `NamedTuple` which stores some constant values listed in [*CODATA recommended values of the fundamental physical constants: 2022*](https://physics.nist.gov/cuu/pdf/wall_2022.pdf) + +The current stored constants are: +- `c` : (exact) speed of light in vacuum with unit ``[\textrm{m}\cdot\textrm{s}^{-1}]`` +- `G` : Newtonian constant of gravitation with unit ``[\textrm{m}^3\cdot\textrm{kg}^{−1}\cdot\textrm{s}^{−2}]`` +- `h` : (exact) Planck constant with unit ``[\textrm{J}\cdot\textrm{s}]`` +- `ħ` : reduced Planck constant with unit ``[\textrm{J}\cdot\textrm{s}]`` +- `e` : (exact) elementary charge with unit ``[\textrm{C}]`` +- `μ0` : vacuum magnetic permeability with unit ``[\textrm{N}\cdot\textrm{A}^{-2}]`` +- `ϵ0` : vacuum electric permittivity with unit ``[\textrm{F}\cdot\textrm{m}^{-1}]`` +- `k` : (exact) Boltzmann constant with unit ``[\textrm{J}\cdot\textrm{K}^{-1}]`` +- `NA` : (exact) Avogadro constant with unit ``[\textrm{mol}^{-1}]`` + +# Examples + +``` +julia> PhysicalConstants.ħ +1.0545718176461565e-34 +``` +""" +const PhysicalConstants = ( + c = 299792458.0, + G = 6.67430e-11, + h = 6.62607015e-34, + ħ = 6.62607015e-34 / (2 * π), + e = 1.602176634e-19, + μ0 = 1.25663706127e-6, + ϵ0 = 8.8541878188e-12, + k = 1.380649e-23, + NA = 6.02214076e23, +) + +# common energy units (the values below are all in the unit of Joule) const _energy_units::Dict{Symbol,Float64} = Dict( - # the values below are all in the unit of Joule :J => 1.0, - :eV => _e, - :meV => 1e-3 * _e, - :GHz => 1e9 * _h, - :mK => 1e-3 * _kB, + :eV => PhysicalConstants.e, + :meV => 1.0e-3 * PhysicalConstants.e, + :MHz => 1.0e6 * PhysicalConstants.h, + :GHz => 1.0e9 * PhysicalConstants.h, + :K => PhysicalConstants.k, + :mK => 1.0e-3 * PhysicalConstants.k, ) @doc raw""" convert_unit(value::Real, unit1::Symbol, unit2::Symbol) -Convert the energy `value` from `unit1` to `unit2`. +Convert the energy `value` from `unit1` to `unit2`. The `unit1` and `unit2` can be either the following `Symbol`: +- `:J` : Joule +- `:eV` : electron volt +- `:meV` : milli-electron volt +- `:MHz` : Mega-Hertz multiplied by Planck constant ``h`` +- `:GHz` : Giga-Hertz multiplied by Planck constant ``h`` +- `:K` : Kelvin multiplied by Boltzmann constant ``k`` +- `:mK` : milli-Kelvin multiplied by Boltzmann constant ``k`` -Note that `unit1` and `unit2` can be either the following `Symbol`: -- `:J`: Joule -- `:eV`: electron volt. -- `:meV`: milli-electron volt. -- `:GHz`: Giga-Hertz multiplied by Planck constant ``h``. -- `:mK`: milli-Kelvin multiplied by Boltzmann constant ``k_{\textrm{B}}``. +Note that we use the values stored in [`PhysicalConstants`](@ref) to do the conversion. # Examples ``` julia> convert_unit(1, :eV, :J) -1.602176565e-19 +1.602176634e-19 julia> convert_unit(1, :GHz, :J) 6.62607015e-25 julia> convert_unit(1, :meV, :mK) -11604.51930280894 +11604.518121550082 ``` """ function convert_unit(value::T, unit1::Symbol, unit2::Symbol) where {T<:Real} diff --git a/test/core-test/utilities.jl b/test/core-test/utilities.jl index 5d269d695..3b5f6173c 100644 --- a/test/core-test/utilities.jl +++ b/test/core-test/utilities.jl @@ -14,9 +14,21 @@ @test typeof(n_thermal(Float32(2), Float64(3))) == Float64 end + @testset "CODATA Physical Constants" begin + c = PhysicalConstants.c + h = PhysicalConstants.h + ħ = PhysicalConstants.ħ + μ0 = PhysicalConstants.μ0 + ϵ0 = PhysicalConstants.ϵ0 + + @test h / ħ ≈ 2 * π + @test μ0 / (4e-7 * π) ≈ 1.0 + @test c^2 * μ0 * ϵ0 ≈ 1.0 + end + @testset "convert unit" begin V = 100 * rand(Float64) - _unit_list = [:J, :eV, :meV, :GHz, :mK] + _unit_list = [:J, :eV, :meV, :MHz, :GHz, :K, :mK] for origin in _unit_list for middle in _unit_list for target in _unit_list From cec3e08da122212290fbb63fe7de8f62500f0104 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:40:07 +0800 Subject: [PATCH 051/329] Update README.md (#258) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d22917c45..9601e3b85 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,8 @@ QuantumToolbox.jl is equipped with a robust set of features: ## Installation -> **_NOTE:_** `QuantumToolbox.jl` requires `Julia 1.10+`. +> [!NOTE] +> `QuantumToolbox.jl` requires `Julia 1.10+`. To install `QuantumToolbox.jl`, run the following commands inside Julia's interactive session (also known as REPL): ```julia From 863d7ecd4c4d5009136c1095d5577471e1a21c79 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 7 Oct 2024 22:07:23 +0200 Subject: [PATCH 052/329] Dispatch progress bar method in EnsembleProblems --- src/QuantumToolbox.jl | 2 + src/time_evolution/mcsolve.jl | 196 +++++++++++------- src/time_evolution/ssesolve.jl | 159 +++++++++----- .../time_evolution_dynamical.jl | 57 +++-- 4 files changed, 254 insertions(+), 160 deletions(-) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 2326a0846..87e6a2151 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -24,7 +24,9 @@ import SciMLBase: ODEProblem, SDEProblem, EnsembleProblem, + EnsembleSerial, EnsembleThreads, + EnsembleDistributed, FullSpecialize, CallbackSet, ContinuousCallback, diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 6b63570c6..4eaf30ad7 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -80,13 +80,29 @@ function _mcsolve_prob_func(prob, i, repeat) return remake(prob, p = prm) end +# Standard output function function _mcsolve_output_func(sol, i) resize!(sol.prob.p.jump_times, sol.prob.p.jump_times_which_idx[] - 1) resize!(sol.prob.p.jump_which, sol.prob.p.jump_times_which_idx[] - 1) - put!(sol.prob.p.progr_channel, true) return (sol, false) end +# Output function with progress bar update +function _mcsolve_output_func_progress(sol, i) + next!(sol.prob.p.progr_trajectories) + return _mcsolve_output_func(sol, i) +end + +# Output function with distributed channel update for progress bar +function _mcsolve_output_func_distributed(sol, i) + put!(sol.prob.p.progr_channel, true) + return _mcsolve_output_func(sol, i) +end + +_mcsolve_dispatch_output_func() = _mcsolve_output_func +_mcsolve_dispatch_output_func(::ET) where {ET<:Union{EnsembleSerial,EnsembleThreads}} = _mcsolve_output_func_progress +_mcsolve_dispatch_output_func(::EnsembleDistributed) = _mcsolve_output_func_distributed + function _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which) sol_i = sol[:, i] !isempty(sol_i.prob.kwargs[:saveat]) ? @@ -293,9 +309,12 @@ end e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), + ntraj::Int=1, + ensemble_method=EnsembleThreads(), jump_callback::TJC=ContinuousLindbladJumpCallback(), prob_func::Function=_mcsolve_prob_func, output_func::Function=_mcsolve_output_func, + progress_bar::Union{Val,Bool}=Val(true), kwargs...) Generates the `EnsembleProblem` of `ODEProblem`s for the ensemble of trajectories of the Monte Carlo wave function time evolution of an open quantum system. @@ -343,9 +362,12 @@ If the environmental measurements register a quantum jump, the wave function und - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. - `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. +- `ntraj::Int`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. - `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. - `prob_func::Function`: Function to use for generating the ODEProblem. - `output_func::Function`: Function to use for generating the output of a single trajectory. +- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs...`: Additional keyword arguments to pass to the solver. # Notes @@ -369,29 +391,51 @@ function mcsolveEnsembleProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), seeds::Union{Nothing,Vector{Int}} = nothing, prob_func::Function = _mcsolve_prob_func, - output_func::Function = _mcsolve_output_func, + output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), + progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2,TJC<:LindbladJumpCallbackType} - prob_mc = mcsolveProblem( - H, - ψ0, - tlist, - c_ops; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = params, - seeds = seeds, - jump_callback = jump_callback, - kwargs..., - ) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + if ensemble_method isa EnsembleDistributed + progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) + @async while take!(progr_channel) + next!(progr) + end + params = merge(params, (progr_channel = progr_channel,)) + else + params = merge(params, (progr_trajectories = progr,)) + end + + # Stop the async task if an error occurs + try + prob_mc = mcsolveProblem( + H, + ψ0, + tlist, + c_ops; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = params, + seeds = seeds, + jump_callback = jump_callback, + kwargs..., + ) - ensemble_prob = EnsembleProblem(prob_mc, prob_func = prob_func, output_func = output_func, safetycopy = false) + ensemble_prob = EnsembleProblem(prob_mc, prob_func = prob_func, output_func = output_func, safetycopy = false) - return ensemble_prob + return ensemble_prob + catch e + if ensemble_method isa EnsembleDistributed + put!(progr_channel, false) + end + rethrow() + end end @doc raw""" @@ -408,7 +452,7 @@ end ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), prob_func::Function = _mcsolve_prob_func, - output_func::Function = _mcsolve_output_func, + output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) @@ -493,7 +537,7 @@ function mcsolve( ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), prob_func::Function = _mcsolve_prob_func, - output_func::Function = _mcsolve_output_func, + output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2,TJC<:LindbladJumpCallbackType} @@ -501,35 +545,26 @@ function mcsolve( throw(ArgumentError("Length of seeds must match ntraj ($ntraj), but got $(length(seeds))")) end - progr = ProgressBar(ntraj, enable = getVal(progress_bar)) - progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) - @async while take!(progr_channel) - next!(progr) - end - - # Stop the async task if an error occurs - try - ens_prob_mc = mcsolveEnsembleProblem( - H, - ψ0, - tlist, - c_ops; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = merge(params, (progr_channel = progr_channel,)), - seeds = seeds, - jump_callback = jump_callback, - prob_func = prob_func, - output_func = output_func, - kwargs..., - ) + ens_prob_mc = mcsolveEnsembleProblem( + H, + ψ0, + tlist, + c_ops; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = params, + seeds = seeds, + ntraj = ntraj, + ensemble_method = ensemble_method, + jump_callback = jump_callback, + prob_func = prob_func, + output_func = output_func, + progress_bar = progress_bar, + kwargs..., + ) - return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) - catch e - put!(progr_channel, false) - rethrow() - end + return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) end function mcsolve( @@ -538,33 +573,42 @@ function mcsolve( ntraj::Int = 1, ensemble_method = EnsembleThreads(), ) - sol = solve(ens_prob_mc, alg, ensemble_method, trajectories = ntraj) - - put!(sol[:, 1].prob.p.progr_channel, false) - - _sol_1 = sol[:, 1] - - expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) - states = - isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : - Vector{Vector{QuantumObject}}(undef, length(sol)) - jump_times = Vector{Vector{Float64}}(undef, length(sol)) - jump_which = Vector{Vector{Int16}}(undef, length(sol)) - - foreach(i -> _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which), eachindex(sol)) - expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) - - return TimeEvolutionMCSol( - ntraj, - _sol_1.prob.p.times, - states, - expvals, - expvals_all, - jump_times, - jump_which, - sol.converged, - _sol_1.alg, - _sol_1.prob.kwargs[:abstol], - _sol_1.prob.kwargs[:reltol], - ) + try + sol = solve(ens_prob_mc, alg, ensemble_method, trajectories = ntraj) + + if ensemble_method isa EnsembleDistributed + put!(sol[:, 1].prob.p.progr_channel, false) + end + + _sol_1 = sol[:, 1] + + expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) + states = + isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : + Vector{Vector{QuantumObject}}(undef, length(sol)) + jump_times = Vector{Vector{Float64}}(undef, length(sol)) + jump_which = Vector{Vector{Int16}}(undef, length(sol)) + + foreach(i -> _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which), eachindex(sol)) + expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) + + return TimeEvolutionMCSol( + ntraj, + _sol_1.prob.p.times, + states, + expvals, + expvals_all, + jump_times, + jump_which, + sol.converged, + _sol_1.alg, + _sol_1.prob.kwargs[:abstol], + _sol_1.prob.kwargs[:reltol], + ) + catch e + if ensemble_method isa EnsembleDistributed + put!(ens_prob_mc.prob.p.progr_channel, false) + end + rethrow() + end end diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index bdd92ba2d..fae961178 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -52,11 +52,25 @@ function _ssesolve_prob_func(prob, i, repeat) return remake(prob, p = prm, noise = noise, noise_rate_prototype = noise_rate_prototype) end -function _ssesolve_output_func(sol, i) +# Standard output function +_ssesolve_output_func(sol, i) = (sol, false) + +# Output function with progress bar update +function _ssesolve_output_func_progress(sol, i) + next!(sol.prob.p.progr) + return _ssesolve_output_func(sol, i) +end + +# Output function with distributed channel update for progress bar +function _ssesolve_output_func_distributed(sol, i) put!(sol.prob.p.progr_channel, true) - return (sol, false) + return _ssesolve_output_func(sol, i) end +_ssesolve_dispatch_output_func() = _ssesolve_output_func +_ssesolve_dispatch_output_func(::ET) where {ET<:Union{EnsembleSerial,EnsembleThreads}} = _ssesolve_output_func_progress +_ssesolve_dispatch_output_func(::EnsembleDistributed) = _ssesolve_output_func_distributed + function _ssesolve_generate_statistics!(sol, i, states, expvals_all) sol_i = sol[:, i] !isempty(sol_i.prob.kwargs[:saveat]) ? @@ -209,8 +223,11 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), + ntraj::Int=1, + ensemble_method=EnsembleThreads(), prob_func::Function=_mcsolve_prob_func, - output_func::Function=_mcsolve_output_func, + output_func::Function=_ssesolve_dispatch_output_func(ensemble_method), + progress_bar::Union{Val,Bool}=Val(true), kwargs...) Generates the SDE EnsembleProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: @@ -244,8 +261,11 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. - `params::NamedTuple`: The parameters of the system. +- `ntraj::Int`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. - `prob_func::Function`: Function to use for generating the SDEProblem. - `output_func::Function`: Function to use for generating the output of a single trajectory. +- `progress_bar::Union{Val,Bool}`: Whether to show a progress bar. - `kwargs...`: The keyword arguments passed to the `SDEProblem` constructor. # Notes @@ -269,15 +289,38 @@ function ssesolveEnsembleProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), prob_func::Function = _ssesolve_prob_func, - output_func::Function = _ssesolve_output_func, + output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), + progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2} - prob_sse = ssesolveProblem(H, ψ0, tlist, sc_ops; alg = alg, e_ops = e_ops, H_t = H_t, params = params, kwargs...) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + if ensemble_method isa EnsembleDistributed + progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) + @async while take!(progr_channel) + next!(progr) + end + params = merge(params, (progr_channel = progr_channel,)) + else + params = merge(params, (progr_trajectories = progr,)) + end - ensemble_prob = EnsembleProblem(prob_sse, prob_func = prob_func, output_func = output_func, safetycopy = false) + # Stop the async task if an error occurs + try + prob_sse = + ssesolveProblem(H, ψ0, tlist, sc_ops; alg = alg, e_ops = e_ops, H_t = H_t, params = params, kwargs...) - return ensemble_prob + ensemble_prob = EnsembleProblem(prob_sse, prob_func = prob_func, output_func = output_func, safetycopy = false) + + return ensemble_prob + catch e + if ensemble_method isa EnsembleDistributed + put!(progr_channel, false) + end + rethrow(e) + end end @doc raw""" @@ -291,8 +334,8 @@ end params::NamedTuple=NamedTuple(), ntraj::Int=1, ensemble_method=EnsembleThreads(), - prob_func::Function=_mcsolve_prob_func, - output_func::Function=_mcsolve_output_func, + prob_func::Function=_ssesolve_prob_func, + output_func::Function=_ssesolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs...) @@ -363,7 +406,7 @@ function ssesolve( ntraj::Int = 1, ensemble_method = EnsembleThreads(), prob_func::Function = _ssesolve_prob_func, - output_func::Function = _ssesolve_output_func, + output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2} @@ -373,26 +416,24 @@ function ssesolve( next!(progr) end - try - ens_prob = ssesolveEnsembleProblem( - H, - ψ0, - tlist, - sc_ops; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = merge(params, (progr_channel = progr_channel,)), - prob_func = prob_func, - output_func = output_func, - kwargs..., - ) + ens_prob = ssesolveEnsembleProblem( + H, + ψ0, + tlist, + sc_ops; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = params, + ntraj = ntraj, + ensemble_method = ensemble_method, + prob_func = prob_func, + output_func = output_func, + progress_bar = progress_bar, + kwargs..., + ) - return ssesolve(ens_prob; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) - catch e - put!(progr_channel, false) - rethrow() - end + return ssesolve(ens_prob; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) end function ssesolve( @@ -401,29 +442,39 @@ function ssesolve( ntraj::Int = 1, ensemble_method = EnsembleThreads(), ) - sol = solve(ens_prob, alg, ensemble_method, trajectories = ntraj) - - put!(sol[:, 1].prob.p.progr_channel, false) - - _sol_1 = sol[:, 1] - - expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) - states = - isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : - Vector{Vector{QuantumObject}}(undef, length(sol)) - - foreach(i -> _ssesolve_generate_statistics!(sol, i, states, expvals_all), eachindex(sol)) - expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) - - return TimeEvolutionSSESol( - ntraj, - _sol_1.prob.p.times, - states, - expvals, - expvals_all, - sol.converged, - _sol_1.alg, - _sol_1.prob.kwargs[:abstol], - _sol_1.prob.kwargs[:reltol], - ) + # Stop the async task if an error occurs + try + sol = solve(ens_prob, alg, ensemble_method, trajectories = ntraj) + + if ensemble_method isa EnsembleDistributed + put!(sol[:, 1].prob.p.progr_channel, false) + end + + _sol_1 = sol[:, 1] + + expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) + states = + isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : + Vector{Vector{QuantumObject}}(undef, length(sol)) + + foreach(i -> _ssesolve_generate_statistics!(sol, i, states, expvals_all), eachindex(sol)) + expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) + + return TimeEvolutionSSESol( + ntraj, + _sol_1.prob.p.times, + states, + expvals, + expvals_all, + sol.converged, + _sol_1.alg, + _sol_1.prob.kwargs[:abstol], + _sol_1.prob.kwargs[:reltol], + ) + catch e + if ensemble_method isa EnsembleDistributed + put!(ens_prob.prob.p.progr_channel, false) + end + rethrow(e) + end end diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 496f25130..4ffe31873 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -620,9 +620,12 @@ function dsf_mcsolveEnsembleProblem( e_ops::Function = (op_list, p) -> Vector{TOl}([]), H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), jump_callback::TJC = ContinuousLindbladJumpCallback(), krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), + progress_bar::Union{Bool,Val} = Val(true), kwargs..., ) where {T,TOl,TJC<:LindbladJumpCallbackType} op_l = op_list @@ -669,8 +672,11 @@ function dsf_mcsolveEnsembleProblem( alg = alg, H_t = H_t, params = params2, + ntraj = ntraj, + ensemble_method = ensemble_method, jump_callback = jump_callback, prob_func = _dsf_mcsolve_prob_func, + progress_bar = progress_bar, kwargs2..., ) end @@ -720,35 +726,26 @@ function dsf_mcsolve( progress_bar::Union{Bool,Val} = Val(true), kwargs..., ) where {T,TOl,TJC<:LindbladJumpCallbackType} - progr = ProgressBar(ntraj, enable = getVal(progress_bar)) - progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) - @async while take!(progr_channel) - next!(progr) - end + ens_prob_mc = dsf_mcsolveEnsembleProblem( + H, + ψ0, + t_l, + c_ops, + op_list, + α0_l, + dsf_params; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = params, + ntraj = ntraj, + ensemble_method = ensemble_method, + δα_list = δα_list, + jump_callback = jump_callback, + krylov_dim = krylov_dim, + progress_bar = progress_bar, + kwargs..., + ) - # Stop the async task if an error occurs - try - ens_prob_mc = dsf_mcsolveEnsembleProblem( - H, - ψ0, - t_l, - c_ops, - op_list, - α0_l, - dsf_params; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = merge(params, (progr_channel = progr_channel,)), - δα_list = δα_list, - jump_callback = jump_callback, - krylov_dim = krylov_dim, - kwargs..., - ) - - return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) - catch e - put!(progr_channel, false) - rethrow() - end + return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) end From bdedbda99469df8014bbaf4920fbb61328ae5ed2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:01:24 +0800 Subject: [PATCH 053/329] Fix default value of keyword parameter `saveat` (#260) * fix default value of keyword parameter `saveat` * fix typos in docstrings --- docs/src/users_guide/time_evolution/solution.md | 2 +- src/time_evolution/mcsolve.jl | 10 +++++----- src/time_evolution/mesolve.jl | 6 +++--- src/time_evolution/sesolve.jl | 6 +++--- src/time_evolution/ssesolve.jl | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index efaea742f..8889f5604 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -84,7 +84,7 @@ State vectors, or density matrices, are accessed in a similar manner: sol.states ``` -Here, the solution contains only one (final) state. Because the `states` will be saved depend on the keyword argument `saveat` in `kwargs`. If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). One can also specify `e_ops` and `saveat` separately. +Here, the solution contains only one (final) state. Because the `states` will be saved depend on the keyword argument `saveat` in `kwargs`. If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). One can also specify `e_ops` and `saveat` separately. Some other solvers can have other output. diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 4eaf30ad7..58913233f 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -176,7 +176,7 @@ If the environmental measurements register a quantum jump, the wave function und # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -216,11 +216,11 @@ function mcsolveProblem( e_ops2 = MT1[] else expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) - is_empty_e_ops_mc = false e_ops2 = get_data.(e_ops) + is_empty_e_ops_mc = isempty(e_ops) end - saveat = e_ops isa Nothing ? t_l : [t_l[end]] + saveat = is_empty_e_ops_mc ? t_l : [t_l[end]] # We disable the progress bar of the sesolveProblem because we use a global progress bar for all the trajectories default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat, progress_bar = Val(false)) kwargs2 = merge(default_values, kwargs) @@ -373,7 +373,7 @@ If the environmental measurements register a quantum jump, the wave function und # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -514,7 +514,7 @@ If the environmental measurements register a quantum jump, the wave function und - `ensemble_method` can be one of `EnsembleThreads()`, `EnsembleSerial()`, `EnsembleDistributed()` - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 3188e3b45..994bdb08c 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -88,7 +88,7 @@ where # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -154,7 +154,7 @@ function mesolveProblem( params..., ) - saveat = e_ops isa Nothing ? t_l : [t_l[end]] + saveat = is_empty_e_ops ? t_l : [t_l[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) kwargs3 = _generate_mesolve_kwargs(e_ops, makeVal(progress_bar), t_l, kwargs2) @@ -205,7 +205,7 @@ where # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index b30ed2cb2..f2699776e 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -75,7 +75,7 @@ Generates the ODEProblem for the Schrödinger time evolution of a quantum system # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -131,7 +131,7 @@ function sesolveProblem( params..., ) - saveat = e_ops isa Nothing ? t_l : [t_l[end]] + saveat = is_empty_e_ops ? t_l : [t_l[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) kwargs3 = _generate_sesolve_kwargs(e_ops, makeVal(progress_bar), t_l, kwargs2) @@ -174,7 +174,7 @@ Time evolution of a closed quantum system using the Schrödinger equation: # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index fae961178..79bb5286c 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -127,7 +127,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -194,7 +194,7 @@ function ssesolveProblem( params..., ) - saveat = e_ops isa Nothing ? t_l : [t_l[end]] + saveat = is_empty_e_ops ? t_l : [t_l[end]] default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) kwargs3 = _generate_sesolve_kwargs(e_ops, Val(false), t_l, kwargs2) @@ -271,7 +271,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -385,7 +385,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `ensemble_method` can be one of `EnsembleThreads()`, `EnsembleSerial()`, `EnsembleDistributed()` - The states will be saved depend on the keyword argument `saveat` in `kwargs`. -- If `e_ops` is specified, the default value of `saveat=[tlist[end]]` (only save the final state), otherwise, `saveat=tlist` (saving the states corresponding to `tlist`). You can also specify `e_ops` and `saveat` separately. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) From 8f646198ec2656a6da58e9897b8e0832a3b1982a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:09:34 +0900 Subject: [PATCH 054/329] Add `Julia v1.11` to CI and buildkite config (#262) * add Julia v1.11 to CI and buildkite config * separate code quality ci pipeline from runtests --- .buildkite/CUDA_Ext.yml | 2 +- .github/workflows/CI.yml | 11 +----- .github/workflows/Code-Quality.yml | 60 ++++++++++++++++++++++++++++++ README.md | 6 ++- 4 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/Code-Quality.yml diff --git a/.buildkite/CUDA_Ext.yml b/.buildkite/CUDA_Ext.yml index b5035f9fe..589a310d3 100644 --- a/.buildkite/CUDA_Ext.yml +++ b/.buildkite/CUDA_Ext.yml @@ -4,7 +4,7 @@ steps: setup: version: - "1.10" # oldest - #- "1" # latest + - "1" # latest plugins: - JuliaCI/julia#v1: version: "{{matrix.version}}" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f1ebe096a..e45dcbbff 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -41,7 +41,7 @@ jobs: # for core tests (latest and oldest supported versions) version: - '1.10' # oldest - # - '1' # latest + - '1' # latest node: - os: 'ubuntu-latest' arch: 'x64' @@ -52,14 +52,7 @@ jobs: group: - 'Core' - include: - # for code quality tests - - version: '1' - node: - os: 'ubuntu-latest' - arch: 'x64' - group: 'Code-Quality' - + # include: # for core tests (intermediate versions) # - version: '1.x' # node: diff --git a/.github/workflows/Code-Quality.yml b/.github/workflows/Code-Quality.yml new file mode 100644 index 000000000..29d56c446 --- /dev/null +++ b/.github/workflows/Code-Quality.yml @@ -0,0 +1,60 @@ +name: Code Quality + +on: + push: + branches: + - 'main' + paths: + - '.github/workflows/Code-Quality.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + pull_request: + branches: + - 'main' + paths: + - '.github/workflows/Code-Quality.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + types: + - opened + - reopened + - synchronize + - ready_for_review + +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + permissions: # needed to allow julia-actions/cache to delete old caches that it has created + actions: write + contents: read + if: ${{ !github.event.pull_request.draft }} + strategy: + fail-fast: false + matrix: + version: + - '1' + os: + - 'ubuntu-latest' + arch: + - 'x64' + group: + - 'Code-Quality' + + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + env: + GROUP: ${{ matrix.group }} diff --git a/README.md b/README.md index 9601e3b85..f3bfceee4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ and [Y.-T. Huang](https://github.com/ytdHuang). | **Release** | [![Release][release-img]][release-url] [![License][license-img]][license-url] [![DOI][doi-img]][doi-url] [![Downloads][download-img]][download-url] | |:-----------------:|:-------------| -| **Runtests** | [![Runtests][runtests-img]][runtests-url] [![Coverage][codecov-img]][codecov-url] [![Aqua QA][aqua-img]][aqua-url] [![JET][jet-img]][jet-url] | +| **Runtests** | [![Runtests][runtests-img]][runtests-url] [![Coverage][codecov-img]][codecov-url] | +| **Code Quality** | [![Code Quality][code-quality-img]][code-quality-url] [![Aqua QA][aqua-img]][aqua-url] [![JET][jet-img]][jet-url] | | **Documentation** | [![Doc-Stable][docs-stable-img]][docs-stable-url] [![Doc-Dev][docs-develop-img]][docs-develop-url] | | **Benchmark** | [![Benchmarks][benchmark-img]][benchmark-url] | | **Support** | [![Unitary Fund](https://img.shields.io/badge/Supported%20By-UNITARY%20FUND-brightgreen.svg?style=for-the-badge)](https://unitary.fund) | @@ -35,6 +36,9 @@ and [Y.-T. Huang](https://github.com/ytdHuang). [codecov-img]: https://codecov.io/gh/qutip/QuantumToolbox.jl/branch/main/graph/badge.svg [codecov-url]: https://codecov.io/gh/qutip/QuantumToolbox.jl +[code-quality-img]: https://github.com/qutip/QuantumToolbox.jl/actions/workflows/Code-Quality.yml/badge.svg +[code-quality-url]: https://github.com/qutip/QuantumToolbox.jl/actions/workflows/Code-Quality.yml + [aqua-img]: https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg [aqua-url]: https://github.com/JuliaTesting/Aqua.jl From dca631a3899d605d808f4c37e6bcb6db983bf7b7 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:10:33 +0900 Subject: [PATCH 055/329] [Doc] update documentation for `sesolve` and `mesolve` (#261) * add documentation for `sesolve` * fix typo in `sesolve` * add documentation for von Neumann eq. * remove title from each time evolution page to fix typo in sidebar * minor changes * update documentation for `mesolve` --- docs/src/users_guide/states_and_operators.md | 2 +- docs/src/users_guide/steadystate.md | 14 +- docs/src/users_guide/time_evolution/intro.md | 4 +- .../src/users_guide/time_evolution/mcsolve.md | 4 +- .../src/users_guide/time_evolution/mesolve.md | 228 +++++++++++++++++- .../src/users_guide/time_evolution/sesolve.md | 112 ++++++++- .../users_guide/time_evolution/solution.md | 10 +- .../users_guide/time_evolution/stochastic.md | 6 +- .../time_evolution/time_dependent.md | 4 +- 9 files changed, 351 insertions(+), 33 deletions(-) diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index b836c3489..3cd714820 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -404,4 +404,4 @@ t = 0.8 exp(L * t) ``` -See the section [Time Evolution and Quantum System Dynamics](@ref doc:Time-Evolution-and-Quantum-System-Dynamics) for more details. +See the section [Lindblad Master Equation Solver](@ref doc-TE:Lindblad-Master-Equation-Solver) for more details. diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index a69409f5c..2c53efb03 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -64,7 +64,9 @@ solver = SteadyStateLinearSolver(alg = MKLLUFactorization()) See [`LinearSolve.jl` Solvers](https://docs.sciml.ai/LinearSolve/stable/solvers/solvers/) for more details. -## Example: Harmonic Oscillator in Thermal Bath +## Example: Harmonic oscillator in thermal bath + +Here, we demonstrate [`steadystate`](@ref) by using the example with the harmonic oscillator in thermal bath from the previous section ([Lindblad Master Equation Solver](@ref doc-TE:Lindblad-Master-Equation-Solver)). ```@example steady_state_example using QuantumToolbox @@ -77,16 +79,16 @@ a = destroy(N) H = a' * a ψ0 = basis(N, 10) # initial state κ = 0.1 # coupling to oscillator -n_th = 2 # temperature with average of 2 excitations +n_th = 2 # temperature with average of 2 excitations # collapse operators -c_op_list = [ +c_ops = [ sqrt(κ * (n_th + 1)) * a, # emission sqrt(κ * n_th ) * a' # absorption ] # find steady-state solution -ρ_ss = steadystate(H, c_op_list) +ρ_ss = steadystate(H, c_ops) # find expectation value for particle number in steady state e_ops = [a' * a] @@ -95,11 +97,11 @@ exp_ss = real(expect(e_ops[1], ρ_ss)) tlist = LinRange(0, 50, 100) # monte-carlo -sol_mc = mcsolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, ntraj=100, progress_bar=false) +sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj=100, progress_bar=false) exp_mc = real(sol_mc.expect[1, :]) # master eq. -sol_me = mesolve(H, ψ0, tlist, c_op_list, e_ops=e_ops, progress_bar=false) +sol_me = mesolve(H, ψ0, tlist, c_ops, e_ops=e_ops, progress_bar=false) exp_me = real(sol_me.expect[1, :]) # plot the results diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index 6a7f2f5ab..37488c956 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -12,10 +12,10 @@ Pages = [ "stochastic.md", "time_dependent.md", ] -Depth = 2:3 +Depth = 1:2 ``` -## [Introduction](@id doc-TE:Introduction) +# [Introduction](@id doc-TE:Introduction) Although in some cases, we want to find the stationary states of a quantum system, often we are interested in the dynamics: how the state of a system or an ensemble of systems evolves with time. `QuantumToolbox` provides many ways to model dynamics. diff --git a/docs/src/users_guide/time_evolution/mcsolve.md b/docs/src/users_guide/time_evolution/mcsolve.md index b6504e394..b0cfa5591 100644 --- a/docs/src/users_guide/time_evolution/mcsolve.md +++ b/docs/src/users_guide/time_evolution/mcsolve.md @@ -1,5 +1,3 @@ -# Time Evolution and Quantum System Dynamics - -## [Monte-Carlo Solver](@id doc-TE:Monte-Carlo-Solver) +# [Monte-Carlo Solver](@id doc-TE:Monte-Carlo-Solver) This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/mesolve.md b/docs/src/users_guide/time_evolution/mesolve.md index b63c85d74..fdda3e9bb 100644 --- a/docs/src/users_guide/time_evolution/mesolve.md +++ b/docs/src/users_guide/time_evolution/mesolve.md @@ -1,9 +1,227 @@ -# Time Evolution and Quantum System Dynamics +# [Lindblad Master Equation Solver](@id doc-TE:Lindblad-Master-Equation-Solver) -## [Lindblad Master Equation Solver](@id doc-TE:Lindblad-Master-Equation-Solver) +```@setup mesolve +using QuantumToolbox +``` -This page is still under construction, please visit [API](@ref doc-API) first. +## Von Neumann equation -### Von Neumann equation +While the evolution of the state vector in a closed quantum system is deterministic (as we discussed in the previous section: [Schrödinger Equation Solver](@ref doc-TE:Schrödinger-Equation-Solver)), open quantum systems are stochastic in nature. The effect of an environment on the system of interest is to induce stochastic transitions between energy levels, and to introduce uncertainty in the phase difference between states of the system. The state of an open quantum system is therefore described in terms of ensemble averaged states using the density matrix formalism. A density matrix ``\hat{\rho}`` describes a probability distribution of quantum states ``|\psi_n\rangle`` in a matrix representation, namely -### The Lindblad master equation \ No newline at end of file +```math +\hat{\rho} = \sum_n p_n |\psi_n\rangle\langle\psi_n|, +``` + +where ``p_n`` is the classical probability that the system is in the quantum state ``|\psi_n\rangle``. The time evolution of a density matrix ``\hat{\rho}`` is the topic of the remaining portions of this section. + +The time evolution of the density matrix ``\hat{\rho}(t)`` under closed system dynamics is governed by the von Neumann equation: + +```math +\begin{equation} +\label{von-Neumann-Eq} +\frac{d}{dt}\hat{\rho}(t) = -\frac{i}{\hbar}\left[\hat{H}, \hat{\rho}(t)\right], +\end{equation} +``` + +where ``[\cdot, \cdot]`` represents the commutator. The above equation is equivalent to the Schrödinger equation described in the [previous section](@ref doc-TE:Schrödinger-Equation-Solver) under the density matrix formalism. + +In `QuantumToolbox`, given a Hamiltonian, we can calculate the unitary (non-dissipative) time-evolution of an arbitrary initial state using the `QuantumToolbox` time evolution problem [`mesolveProblem`](@ref) or directly call the function [`mesolve`](@ref). It evolves the density matrix ``\hat{\rho}(t)`` and evaluates the expectation values for a set of operators `e_ops` at each given time points, using an ordinary differential equation solver provided by the powerful julia package [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/). + +```@example mesolve +H = 0.5 * sigmax() +state0 = basis(2, 0) # state vector +state0 = ket2dm(basis(2, 0)) # density matrix +tlist = LinRange(0.0, 10.0, 20) + +sol = mesolve(H, state0, tlist, e_ops = [sigmaz()]) +``` + +!!! note "Type of initial state" + The initial state `state0` here can be given as a state vector ``|\psi(0)\rangle`` (in the type of [`Ket`](@ref)) or a density matrix ``\hat{\rho}(0)`` (in the type of [`Operator`](@ref)). If it is given as a [`Ket`](@ref), it will be transformed to density matrix ``\hat{\rho}(0) = |\psi(0)\rangle\langle\psi(0)|`` internally in [`mesolve`](@ref). + +The function returns [`TimeEvolutionSol`](@ref), as described in the previous section [Time Evolution Solutions](@ref doc-TE:Time-Evolution-Solutions). The stored `states` will always be in the type of [`Operator`](@ref) (density matrix). + +```@example mesolve +sol.states +``` + +Here, only the final state is stored because the `states` will be saved depend on the keyword argument `saveat` in `kwargs`. If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). + +One can also specify `e_ops` and `saveat` separately: + +```@example mesolve +tlist = [0, 5, 10] +sol = mesolve(H, state0, tlist, e_ops = [sigmay()], saveat = tlist) +``` + +```@example mesolve +sol.expect +``` + +```@example mesolve +sol.states +``` + +## The Lindblad master equation + +The standard approach for deriving the equations of motion for a system interacting with its environment is to expand the scope of the system to include the environment. The combined quantum system is then closed, and its evolution is governed by the von Neumann equation given in Eq. \eqref{von-Neumann-Eq} + +```math +\begin{equation} +\label{tot-von-Neumann-Eq} +\frac{d}{dt}\hat{\rho}_{\textrm{tot}}(t) = -\frac{i}{\hbar}\left[\hat{H}_{\textrm{tot}}, \hat{\rho}_{\textrm{tot}}(t)\right]. +\end{equation} +``` + +Here, the total Hamiltonian + +```math +\hat{H}_{\textrm{tot}} = \hat{H}_{\textrm{sys}} + \hat{H}_{\textrm{env}} + \hat{H}_{\textrm{int}}, +``` + +includes the original system Hamiltonian ``\hat{H}_{\textrm{sys}}``, the Hamiltonian for the environment ``\hat{H}_{\textrm{env}}``, and a term representing the interaction between the system and its environment ``\hat{H}_{\textrm{int}}``. Since we are only interested in the dynamics of the system, we can, perform a partial trace over the environmental degrees of freedom in Eq. \eqref{tot-von-Neumann-Eq}, and thereby obtain a master equation for the motion of the original system density matrix ``\hat{\rho}_{\textrm{sys}}(t)=\textrm{Tr}_{\textrm{env}}[\hat{\rho}_{\textrm{tot}}(t)]``. The most general trace-preserving and completely positive form of this evolution is the Lindblad master equation for the reduced density matrix, namely + +```math +\begin{equation} +\label{Lindblad-master-Eq} +\frac{d}{dt}\hat{\rho}_{\textrm{sys}}(t) = -\frac{i}{\hbar}\left[\hat{H}_{\textrm{sys}}, \hat{\rho}_{\textrm{sys}}(t)\right] + \sum_n \hat{C}_n \hat{\rho}_{\textrm{sys}}(t) \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n \hat{\rho}_{\textrm{sys}}(t) - \frac{1}{2} \hat{\rho}_{\textrm{sys}}(t) \hat{C}_n^\dagger \hat{C}_n +\end{equation} +``` + +where ``\hat{C}_n \equiv \sqrt{\gamma_n}\hat{A}_n`` are the collapse operators, ``\hat{A}_n`` are the operators acting on the system in ``\hat{H}_{\textrm{int}}`` which describes the system-environment interaction, and ``\gamma_n`` are the corresponding rates. The derivation of Eq. \eqref{Lindblad-master-Eq} may be found in several sources, and will not be reproduced here. Instead, we emphasize the approximations that are required to arrive at the master equation in the form of Eq. \eqref{Lindblad-master-Eq} from physical arguments, and hence perform a calculation in `QuantumToolbox`: + +- **Separability:** At ``t = 0``, there are no correlations between the system and environment, such that the total density matrix can be written as a tensor product, namely ``\hat{\rho}_{\textrm{tot}}(0)=\hat{\rho}_{\textrm{sys}}(0)\otimes\hat{\rho}_{\textrm{env}}(0)``. +- **Born approximation:** Requires: (i) the state of the environment does not significantly change as a result of the interaction with the system; (ii) the system and the environment remain separable throughout the evolution. These assumptions are justified if the interaction is weak, and if the environment is much larger than the system. In summary, ``\hat{\rho}_{\textrm{tot}}(t)\approx\hat{\rho}_{\textrm{sys}}(t)\otimes\hat{\rho}_{\textrm{env}}(0)``. +- **Markov approximation:** The time-scale of decay for the environment ``\tau_{\textrm{env}}`` is much shorter than the smallest time-scale of the system dynamics, i.e., ``\tau_{\textrm{sys}}\gg\tau_{\textrm{env}}``. This approximation is often deemed a “short-memory environment” as it requires the environmental correlation functions decay in a fast time-scale compared to those of the system. +- **Secular approximation:** Stipulates that elements in the master equation corresponding to transition frequencies satisfy ``|\omega_{ab}-\omega_{cd}| \ll 1/\tau_{\textrm{sys}}``, i.e., all fast rotating terms in the interaction picture can be neglected. It also ignores terms that lead to a small renormalization of the system energy levels. This approximation is not strictly necessary for all master-equation formalisms (e.g., the Block-Redfield master equation), but it is required for arriving at the Lindblad form in Eq. \eqref{Lindblad-master-Eq} which is used in [`mesolve`](@ref). + +For systems with environments satisfying the conditions outlined above, the Lindblad master equation in Eq. \eqref{Lindblad-master-Eq} governs the time-evolution of the system density matrix, giving an ensemble average of the system dynamics. In order to ensure that these approximations are not violated, it is important that the decay rates ``\gamma_n`` be smaller than the minimum energy splitting in the system Hamiltonian. Situations that demand special attention therefore include, for example, systems strongly coupled to their environment, and systems with degenerate or nearly degenerate energy levels. + +What is new in the master equation compared to the Schrödinger equation (or von Neumann equation) are processes that describe dissipation in the quantum system due to its interaction with an environment. For example, evolution that includes incoherent processes such as relaxation and dephasing. These environmental interactions are defined by the operators ``\hat{A}_n`` through which the system couples to the environment, and rates ``\gamma_n`` that describe the strength of the processes. + +In `QuantumToolbox`, the function [`mesolve`](@ref) can also be used for solving the master equation. This is done by passing a list of collapse operators (`c_ops`) as the fourth argument of the [`mesolve`](@ref) function in order to define the dissipation processes of the master equation in Eq. \eqref{Lindblad-master-Eq}. As we mentioned above, each collapse operator ``\hat{C}_n`` is the product of ``\sqrt{\gamma_n}`` (the square root of the rate) and ``\hat{A}_n`` (operator which describes the dissipation process). + +Furthermore, `QuantumToolbox` solves the master equation in the [`SuperOperator`](@ref) formalism. That is, a Liouvillian [`SuperOperator`](@ref) will be generated internally in [`mesolve`](@ref) by the input system Hamiltonian ``\hat{H}_{\textrm{sys}}`` and the collapse operators ``\hat{C}_n``. One can also generate the Liouvillian [`SuperOperator`](@ref) manually for special purposes, and pass it as the first argument of the [`mesolve`](@ref) function. To do so, it is useful to read the section [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators), and also the docstrings of the following functions: +- [`spre`](@ref) +- [`spost`](@ref) +- [`sprepost`](@ref) +- [`liouvillian`](@ref) +- [`lindblad_dissipator`](@ref) + +## Example: Spin dynamics + +Using the example with the dynamics of spin-``\frac{1}{2}`` from the previous section ([Schrödinger Equation Solver](@ref doc-TE:Schrödinger-Equation-Solver)), we can easily add a relaxation process (describing the dissipation of energy from the spin to the environment), by adding `[sqrt(γ) * sigmax()]` in the fourth parameter of the [`mesolve`](@ref) function. + +```@example mesolve +H = 2 * π * 0.1 * sigmax() +ψ0 = basis(2, 0) # spin-up +tlist = LinRange(0.0, 10.0, 100) + +γ = 0.05 +c_ops = [sqrt(γ) * sigmax()] + +sol = mesolve(H, ψ0, tlist, c_ops, e_ops = [sigmaz(), sigmay()]) +``` + +We can therefore plot the expectation values: + +```@example mesolve +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) + +times = sol.times +expt_z = real(sol.expect[1,:]) +expt_y = real(sol.expect[2,:]) + +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") +lines!(ax, times, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) +lines!(ax, times, expt_y, label = L"\langle\hat{\sigma}_y\rangle", linestyle = :dash) + +axislegend(ax, position = :rt) + +fig +``` + +## Example: Harmonic oscillator in thermal bath + +Consider a harmonic oscillator (single-mode cavity) couples to a thermal bath. If the single-mode cavity initially is in a `10`-photon [`fock`](@ref) state, the dynamics is calculated with the following code: + +```@example mesolve +# Define parameters +N = 20 # number of basis states to consider +a = destroy(N) +H = a' * a +ψ0 = fock(N, 10) # initial state +κ = 0.1 # coupling to oscillator +n_th = 2 # temperature with average of 2 excitations +tlist = LinRange(0, 50, 100) + +# collapse operators +c_ops = [ + sqrt(κ * (n_th + 1)) * a, # emission + sqrt(κ * n_th ) * a' # absorption +] + +# find expectation value for particle number +e_ops = [a' * a] + +sol = mesolve(H, ψ0, tlist, c_ops, e_ops=e_ops) +Num = real(sol.expect[1, :]) + +# plot the results +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], + title = L"Decay of Fock state $|10\rangle$ in a thermal environment with $\langle n\rangle=2$", + xlabel = "Time", + ylabel = "Number of excitations", +) +lines!(ax, tlist, Num) + +fig +``` + +## Example: Two-level atom coupled to dissipative single-mode cavity + +Consider a two-level atom coupled to a dissipative single-mode cavity through a dipole-type interaction, which supports a coherent exchange of quanta between the two systems. If the atom initially is in its ground state and the cavity in a `5`-photon [`fock`](@ref) state, the dynamics is calculated with the following code: + +!!! note "Generate Liouviilian superoperator manually" + In this example, we demonstrate how to generate the Liouvillian [`SuperOperator`](@ref) manually and pass it as the first argument of the [`mesolve`](@ref) function. + +```@example mesolve +# two-level atom +σm = tensor(destroy(2), qeye(10)) +H_a = 2 * π * σm' * σm + +# dissipative single-mode cavity +γ = 0.1 # dissipation rate +a = tensor(qeye(2), destroy(10)) +H_c = 2 * π * a' * a +c_ops = [sqrt(γ) * a] + +# coupling between two-level atom and single-mode cavity +g = 0.25 # coupling strength +H_I = 2 * π * g * (σm * a' + σm' * a) + +ψ0 = tensor(basis(2,0), fock(10, 5)) # initial state +tlist = LinRange(0.0, 10.0, 200) + +# generate Liouvillian superoperator manually +L = liouvillian(H_a + H_c + H_I, c_ops) +sol = mesolve(L, ψ0, tlist, e_ops=[σm' * σm, a' * a]) + +times = sol.times + +# expectation value of Number operator +N_atom = real(sol.expect[1,:]) +N_cavity = real(sol.expect[2,:]) + +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") +lines!(ax, times, N_atom, label = "atom excitation probability", linestyle = :solid) +lines!(ax, times, N_cavity, label = "cavity photon number", linestyle = :dash) + +axislegend(ax, position = :rt) + +fig +``` diff --git a/docs/src/users_guide/time_evolution/sesolve.md b/docs/src/users_guide/time_evolution/sesolve.md index 7df270a22..fce63f7ac 100644 --- a/docs/src/users_guide/time_evolution/sesolve.md +++ b/docs/src/users_guide/time_evolution/sesolve.md @@ -1,5 +1,111 @@ -# Time Evolution and Quantum System Dynamics +# [Schrödinger Equation Solver](@id doc-TE:Schrödinger-Equation-Solver) -## [Schrödinger Equation Solver](@id doc-TE:Schrödinger-Equation-Solver) +## Unitary evolution -This page is still under construction, please visit [API](@ref doc-API) first. +The dynamics of a closed (pure) quantum system is governed by the Schrödinger equation + +```math +i\hbar\frac{\partial}{\partial t}\Psi(\vec{x}, t) = \hat{H}\Psi(\vec{x}, t), +``` + +where ``\Psi(\vec{x}, t)`` is the wave function, ``\hat{H}`` is the Hamiltonian, and ``\hbar`` is reduced Planck constant. In general, the Schrödinger equation is a partial differential equation (PDE) where both +``\Psi`` and ``\hat{H}`` are functions of space ``\vec{x}`` and time ``t``. For computational purposes it is useful to expand the PDE in a set of basis functions that span the Hilbert space of the Hamiltonian, and to write the equation in matrix and vector form, namely + +```math +i\hbar\frac{d}{dt}|\psi(t)\rangle = \hat{H}|\psi(t)\rangle, +``` + +where ``|\psi(t)\rangle`` is the state vector, and the Hamiltonian ``\hat{H}`` is now under matrix representation. This matrix equation can, in principle, be solved by diagonalizing the Hamiltonian matrix ``\hat{H}``. In practice, however, it is difficult to perform this diagonalization unless the size of the Hilbert space (dimension of the matrix ``\hat{H}``) is small. Analytically, it is a formidable task to calculate the dynamics for systems with more than two states. If, in addition, we consider dissipation due to the inevitable interaction with a surrounding environment, the computational complexity grows even larger, and we have to resort to numerical calculations in all realistic situations. This illustrates the importance of numerical calculations in describing the dynamics of open quantum systems, and the need for efficient and accessible tools for this task. + +The Schrödinger equation, which governs the time-evolution of closed quantum systems, is defined by its Hamiltonian and state vector. In the previous sections, [Manipulating States and Operators](@ref doc:Manipulating-States-and-Operators) and [Tensor Products and Partial Traces](@ref doc:Tensor-products-and-Partial-Traces), we showed how Hamiltonians and state vectors are constructed in `QuantumToolbox.jl`. Given a Hamiltonian, we can calculate the unitary (non-dissipative) time-evolution of an arbitrary initial state vector ``|\psi(0)\rangle`` using the `QuantumToolbox` time evolution problem [`sesolveProblem`](@ref) or directly call the function [`sesolve`](@ref). It evolves the state vector ``|\psi(t)\rangle`` and evaluates the expectation values for a set of operators `e_ops` at each given time points, using an ordinary differential equation solver provided by the powerful julia package [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/). + +## Example: Spin dynamics + +```@setup sesolve +using QuantumToolbox +``` + +For example, the time evolution of a quantum spin-``\frac{1}{2}`` system (initialized in spin-``\uparrow``) with tunneling rate ``0.1`` is calculated, and the expectation values of the Pauli-Z operator ``\hat{\sigma}_z`` is also evaluated, with the following code + +```@example sesolve +H = 2 * π * 0.1 * sigmax() +ψ0 = basis(2, 0) # spin-up +tlist = LinRange(0.0, 10.0, 20) + +prob = sesolveProblem(H, ψ0, tlist, e_ops = [sigmaz()]) +sol = sesolve(prob) +``` + +!!! note "Note" + Here, we generate the time evolution problem by [`sesolveProblem`](@ref) first and then put it into the function [`sesolve`](@ref). One can also directly call [`sesolve`](@ref), which we also demonstrates in below. + +The function returns [`TimeEvolutionSol`](@ref), as described in the previous section [Time Evolution Solutions](@ref doc-TE:Time-Evolution-Solutions). The attribute `expect` in `sol`ution is a list of expectation values for the operator(s) that are passed to the `e_ops` keyword argument. + +```@example sesolve +sol.expect +``` + +Passing multiple operators to `e_ops` as a `Vector` results in the expectation values for each operators at each time points. + +```@example sesolve +tlist = LinRange(0.0, 10.0, 100) +sol = sesolve(H, ψ0, tlist, e_ops = [sigmaz(), sigmay()]) +``` + +!!! note "Note" + Here, we call [`sesolve`](@ref) directly instead of pre-defining [`sesolveProblem`](@ref) first (as shown previously). + +```@example sesolve +times = sol.times +print(size(times)) +``` + +```@example sesolve +expt = sol.expect +print(size(expt)) +``` + +We can therefore plot the expectation values: + +```@example sesolve +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) + +expt_z = real(expt[1,:]) +expt_y = real(expt[2,:]) + +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") +lines!(ax, times, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) +lines!(ax, times, expt_y, label = L"\langle\hat{\sigma}_y\rangle", linestyle = :dash) + +axislegend(ax, position = :rb) + +fig +``` + +If the keyword argument `e_ops` is not specified (or given as an empty `Vector`), the [`sesolve`](@ref) and functions return a [`TimeEvolutionSol`](@ref) that contains a list of state vectors which corresponds to the time points specified in `tlist`: + +```@example sesolve +tlist = [0, 10] +sol = sesolve(H, ψ0, tlist) # or specify: e_ops = [] + +sol.states +``` + +This is because the `states` will be saved depend on the keyword argument `saveat` in `kwargs`. If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). + +One can also specify `e_ops` and `saveat` separately: + +```@example sesolve +tlist = [0, 5, 10] +sol = sesolve(H, ψ0, tlist, e_ops = [sigmay()], saveat = tlist) +``` + +```@example sesolve +sol.expect +``` + +```@example sesolve +sol.states +``` diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index 8889f5604..4ed7d1c37 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -1,12 +1,10 @@ -# Time Evolution and Quantum System Dynamics - -## [Time Evolution Solutions](@id doc-TE:Time-Evolution-Solutions) +# [Time Evolution Solutions](@id doc-TE:Time-Evolution-Solutions) ```@setup TE-solution using QuantumToolbox ``` -### Solution +## Solution `QuantumToolbox` utilizes the powerful [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/) to simulate different kinds of quantum system dynamics. Thus, we will first look at the data structure used for returning the solution (`sol`) from [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/). The solution stores all the crucial data needed for analyzing and plotting the results of a simulation. A generic structure [`TimeEvolutionSol`](@ref) contains the following properties for storing simulation data: | **Fields (Attributes)** | **Description** | @@ -19,7 +17,7 @@ using QuantumToolbox | `sol.reltol` | The relative tolerance which is used during the solving process. | | `sol.retcode` (or `sol.converged`) | The returned status from the solver. | -### Accessing data in solutions +## Accessing data in solutions To understand how to access the data in solution, we will use an example as a guide, although we do not worry about the simulation details at this stage. The Schrödinger equation solver ([`sesolve`](@ref)) used in this example returns [`TimeEvolutionSol`](@ref): @@ -88,6 +86,6 @@ Here, the solution contains only one (final) state. Because the `states` will be Some other solvers can have other output. -### Multiple trajectories solution +## Multiple trajectories solution This part is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/stochastic.md b/docs/src/users_guide/time_evolution/stochastic.md index 49259e78b..2006195ab 100644 --- a/docs/src/users_guide/time_evolution/stochastic.md +++ b/docs/src/users_guide/time_evolution/stochastic.md @@ -1,7 +1,5 @@ -# Time Evolution and Quantum System Dynamics - -## [Stochastic Solver](@id doc-TE:Stochastic-Solver) +# [Stochastic Solver](@id doc-TE:Stochastic-Solver) This page is still under construction, please visit [API](@ref doc-API) first. -### Stochastic Schrodinger equation \ No newline at end of file +## Stochastic Schrodinger equation \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/time_dependent.md b/docs/src/users_guide/time_evolution/time_dependent.md index 37274c343..f9346e884 100644 --- a/docs/src/users_guide/time_evolution/time_dependent.md +++ b/docs/src/users_guide/time_evolution/time_dependent.md @@ -1,5 +1,3 @@ -# Time Evolution and Quantum System Dynamics - -## [Solving Problems with Time-dependent Hamiltonians](@id doc-TE:Solving-Problems-with-Time-dependent-Hamiltonians) +# [Solving Problems with Time-dependent Hamiltonians](@id doc-TE:Solving-Problems-with-Time-dependent-Hamiltonians) This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file From ddc98fcb962dd8d7bc7598e2a0a0757c53dd358c Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 10 Oct 2024 01:48:02 +0200 Subject: [PATCH 056/329] Improve random number generation on mcsolve and ssesolve (#263) --- src/QuantumToolbox.jl | 2 +- src/time_evolution/mcsolve.jl | 46 ++++++++++++++++---------------- src/time_evolution/ssesolve.jl | 38 +++++++++++++++++++++----- test/core-test/time_evolution.jl | 44 ++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 5 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 87e6a2151..9c2920dfe 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -47,7 +47,7 @@ import FFTW: fft, fftshift import Graphs: connected_components, DiGraph import IncompleteLU: ilu import Pkg -import Random +import Random: AbstractRNG, default_rng, seed! import SpecialFunctions: loggamma import StaticArraysCore: MVector diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 58913233f..e828118a9 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -29,6 +29,7 @@ function LindbladJumpAffect!(integrator) random_n = internal_params.random_n jump_times = internal_params.jump_times jump_which = internal_params.jump_which + traj_rng = internal_params.traj_rng ψ = integrator.u @inbounds for i in eachindex(weights_mc) @@ -36,14 +37,12 @@ function LindbladJumpAffect!(integrator) weights_mc[i] = real(dot(cache_mc, cache_mc)) end cumsum!(cumsum_weights_mc, weights_mc) - collaps_idx = getindex(1:length(weights_mc), findfirst(>(rand() * sum(weights_mc)), cumsum_weights_mc)) + collaps_idx = getindex(1:length(weights_mc), findfirst(>(rand(traj_rng) * sum(weights_mc)), cumsum_weights_mc)) mul!(cache_mc, c_ops[collaps_idx], ψ) normalize!(cache_mc) copyto!(integrator.u, cache_mc) - # push!(jump_times, integrator.t) - # push!(jump_which, collaps_idx) - random_n[] = rand() + random_n[] = rand(traj_rng) jump_times[internal_params.jump_times_which_idx[]] = integrator.t jump_which[internal_params.jump_times_which_idx[]] = collaps_idx internal_params.jump_times_which_idx[] += 1 @@ -59,8 +58,11 @@ LindbladJumpDiscreteCondition(u, t, integrator) = real(dot(u, u)) < integrator.p function _mcsolve_prob_func(prob, i, repeat) internal_params = prob.p - seeds = internal_params.seeds - !isnothing(seeds) && Random.seed!(seeds[i]) + + global_rng = internal_params.global_rng + seed = internal_params.seeds[i] + traj_rng = typeof(global_rng)() + seed!(traj_rng, seed) prm = merge( internal_params, @@ -69,7 +71,8 @@ function _mcsolve_prob_func(prob, i, repeat) cache_mc = similar(internal_params.cache_mc), weights_mc = similar(internal_params.weights_mc), cumsum_weights_mc = similar(internal_params.weights_mc), - random_n = Ref(rand()), + traj_rng = traj_rng, + random_n = Ref(rand(traj_rng)), progr_mc = ProgressBar(size(internal_params.expvals, 2), enable = false), jump_times_which_idx = Ref(1), jump_times = similar(internal_params.jump_times), @@ -122,6 +125,7 @@ end e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), + rng::AbstractRNG=default_rng(), jump_callback::TJC=ContinuousLindbladJumpCallback(), kwargs...) @@ -169,7 +173,7 @@ If the environmental measurements register a quantum jump, the wave function und - `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. +- `rng::AbstractRNG`: Random number generator for reproducibility. - `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. - `kwargs...`: Additional keyword arguments to pass to the solver. @@ -194,7 +198,7 @@ function mcsolveProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), - seeds::Union{Nothing,Vector{Int}} = nothing, + rng::AbstractRNG = default_rng(), jump_callback::TJC = ContinuousLindbladJumpCallback(), kwargs..., ) where {MT1<:AbstractMatrix,TJC<:LindbladJumpCallbackType} @@ -238,8 +242,7 @@ function mcsolveProblem( e_ops_mc = e_ops2, is_empty_e_ops_mc = is_empty_e_ops_mc, progr_mc = ProgressBar(length(t_l), enable = false), - seeds = seeds, - random_n = Ref(rand()), + traj_rng = rng, c_ops = get_data.(c_ops), cache_mc = cache_mc, weights_mc = weights_mc, @@ -361,7 +364,7 @@ If the environmental measurements register a quantum jump, the wave function und - `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. +- `rng::AbstractRNG`: Random number generator for reproducibility. - `ntraj::Int`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. - `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. @@ -391,10 +394,10 @@ function mcsolveEnsembleProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), - seeds::Union{Nothing,Vector{Int}} = nothing, prob_func::Function = _mcsolve_prob_func, output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), @@ -413,6 +416,7 @@ function mcsolveEnsembleProblem( # Stop the async task if an error occurs try + seeds = map(i -> rand(rng, UInt64), 1:ntraj) prob_mc = mcsolveProblem( H, ψ0, @@ -421,8 +425,8 @@ function mcsolveEnsembleProblem( alg = alg, e_ops = e_ops, H_t = H_t, - params = params, - seeds = seeds, + params = merge(params, (global_rng = rng, seeds = seeds)), + rng = rng, jump_callback = jump_callback, kwargs..., ) @@ -447,7 +451,7 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), - seeds::Union{Nothing,Vector{Int}} = nothing, + rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), @@ -501,7 +505,7 @@ If the environmental measurements register a quantum jump, the wave function und - `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. +- `rng::AbstractRNG`: Random number generator for reproducibility. - `ntraj::Int`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. - `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. @@ -532,7 +536,7 @@ function mcsolve( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), - seeds::Union{Nothing,Vector{Int}} = nothing, + rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), @@ -541,10 +545,6 @@ function mcsolve( progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where {MT1<:AbstractMatrix,T2,TJC<:LindbladJumpCallbackType} - if !isnothing(seeds) && length(seeds) != ntraj - throw(ArgumentError("Length of seeds must match ntraj ($ntraj), but got $(length(seeds))")) - end - ens_prob_mc = mcsolveEnsembleProblem( H, ψ0, @@ -554,7 +554,7 @@ function mcsolve( e_ops = e_ops, H_t = H_t, params = params, - seeds = seeds, + rng = rng, ntraj = ntraj, ensemble_method = ensemble_method, jump_callback = jump_callback, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 79bb5286c..6482f433c 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -32,11 +32,17 @@ end function _ssesolve_prob_func(prob, i, repeat) internal_params = prob.p + global_rng = internal_params.global_rng + seed = internal_params.seeds[i] + traj_rng = typeof(global_rng)() + seed!(traj_rng, seed) + noise = RealWienerProcess( prob.tspan[1], zeros(length(internal_params.sc_ops)), zeros(length(internal_params.sc_ops)), save_everystep = false, + rng = traj_rng, ) noise_rate_prototype = similar(prob.u0, length(prob.u0), length(internal_params.sc_ops)) @@ -49,7 +55,7 @@ function _ssesolve_prob_func(prob, i, repeat) ), ) - return remake(prob, p = prm, noise = noise, noise_rate_prototype = noise_rate_prototype) + return remake(prob, p = prm, noise = noise, noise_rate_prototype = noise_rate_prototype, seed = seed) end # Standard output function @@ -89,6 +95,7 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), + rng::AbstractRNG=default_rng(), kwargs...) Generates the SDEProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: @@ -122,6 +129,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. - `params::NamedTuple`: The parameters of the system. +- `rng::AbstractRNG`: The random number generator for reproducibility. - `kwargs...`: The keyword arguments passed to the `SDEProblem` constructor. # Notes @@ -145,6 +153,7 @@ function ssesolveProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), kwargs..., ) where {MT1<:AbstractMatrix,T2} H.dims != ψ0.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) @@ -200,7 +209,7 @@ function ssesolveProblem( kwargs3 = _generate_sesolve_kwargs(e_ops, Val(false), t_l, kwargs2) tspan = (t_l[1], t_l[end]) - noise = RealWienerProcess(t_l[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false) + noise = RealWienerProcess(t_l[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) noise_rate_prototype = similar(ϕ0, length(ϕ0), length(sc_ops)) return SDEProblem{true}( ssesolve_drift!, @@ -223,6 +232,7 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), + rng::AbstractRNG=default_rng(), ntraj::Int=1, ensemble_method=EnsembleThreads(), prob_func::Function=_mcsolve_prob_func, @@ -261,6 +271,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. - `params::NamedTuple`: The parameters of the system. +- `rng::AbstractRNG`: The random number generator for reproducibility. - `ntraj::Int`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. - `prob_func::Function`: Function to use for generating the SDEProblem. @@ -289,6 +300,7 @@ function ssesolveEnsembleProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), prob_func::Function = _ssesolve_prob_func, @@ -309,10 +321,21 @@ function ssesolveEnsembleProblem( # Stop the async task if an error occurs try - prob_sse = - ssesolveProblem(H, ψ0, tlist, sc_ops; alg = alg, e_ops = e_ops, H_t = H_t, params = params, kwargs...) + seeds = map(i -> rand(rng, UInt64), 1:ntraj) + prob_sse = ssesolveProblem( + H, + ψ0, + tlist, + sc_ops; + alg = alg, + e_ops = e_ops, + H_t = H_t, + params = merge(params, (global_rng = rng, seeds = seeds)), + rng = rng, + kwargs..., + ) - ensemble_prob = EnsembleProblem(prob_sse, prob_func = prob_func, output_func = output_func, safetycopy = false) + ensemble_prob = EnsembleProblem(prob_sse, prob_func = prob_func, output_func = output_func, safetycopy = true) return ensemble_prob catch e @@ -332,6 +355,7 @@ end e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), + rng::AbstractRNG=default_rng(), ntraj::Int=1, ensemble_method=EnsembleThreads(), prob_func::Function=_ssesolve_prob_func, @@ -373,7 +397,7 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. - `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. - `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `seeds::Union{Nothing, Vector{Int}}`: List of seeds for the random number generator. Length must be equal to the number of trajectories provided. +- `rng::AbstractRNG`: Random number generator for reproducibility. - `ntraj::Int`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. - `prob_func::Function`: Function to use for generating the SDEProblem. @@ -403,6 +427,7 @@ function ssesolve( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), prob_func::Function = _ssesolve_prob_func, @@ -425,6 +450,7 @@ function ssesolve( e_ops = e_ops, H_t = H_t, params = params, + rng = rng, ntraj = ntraj, ensemble_method = ensemble_method, prob_func = prob_func, diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 7b1f4b424..50d3306e9 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -149,6 +149,50 @@ @inferred ssesolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) @inferred ssesolve(H, psi0, t_l, c_ops, ntraj = 500, progress_bar = Val(true)) end + + @testset "mcsolve and ssesolve reproducibility" begin + N = 10 + a = tensor(destroy(N), qeye(2)) + σm = tensor(qeye(N), sigmam()) + σp = σm' + σz = tensor(qeye(N), sigmaz()) + + ω = 1.0 + g = 0.1 + γ = 0.01 + nth = 0.1 + + H = ω * a' * a + ω * σz / 2 + g * (a' * σm + a * σp) + c_ops = [sqrt(γ * (1 + nth)) * a, sqrt(γ * nth) * a', sqrt(γ * (1 + nth)) * σm, sqrt(γ * nth) * σp] + e_ops = [a' * a, σz] + + psi0 = tensor(basis(N, 0), basis(2, 0)) + tlist = range(0, 20 / γ, 1000) + + rng = MersenneTwister(1234) + sleep(0.1) # If we don't sleep, we get an error (why?) + sol_mc1 = mcsolve(H, psi0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sse1 = ssesolve(H, psi0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + rng = MersenneTwister(1234) + sleep(0.1) + sol_mc2 = mcsolve(H, psi0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sse2 = ssesolve(H, psi0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + rng = MersenneTwister(1234) + sleep(0.1) + sol_mc3 = mcsolve(H, psi0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 + @test sol_mc1.expect_all ≈ sol_mc2.expect_all atol = 1e-10 + @test sol_mc1.jump_times ≈ sol_mc2.jump_times atol = 1e-10 + @test sol_mc1.jump_which ≈ sol_mc2.jump_which atol = 1e-10 + + @test sol_mc1.expect_all ≈ sol_mc3.expect_all[1:500, :, :] atol = 1e-10 + + @test sol_sse1.expect ≈ sol_sse2.expect atol = 1e-10 + @test sol_sse1.expect_all ≈ sol_sse2.expect_all atol = 1e-10 + end end @testset "exceptions" begin diff --git a/test/runtests.jl b/test/runtests.jl index 95709e3a7..566104ebb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using Test using Pkg using QuantumToolbox using QuantumToolbox: position, momentum +using Random const GROUP = get(ENV, "GROUP", "All") From 110c2e5715646c3f7dcfe73d9efd073131e261ff Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 10 Oct 2024 01:48:51 +0200 Subject: [PATCH 057/329] Bump version to v0.18.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 83d330b9f..acfb92ee0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.17.0" +version = "0.18.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 5e94453a40808b18ac3c1a7d2e39892fd57c7966 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 13 Oct 2024 03:06:34 +0200 Subject: [PATCH 058/329] Improve mcsolve performance (#265) --- src/time_evolution/mcsolve.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index e828118a9..fb08a28d1 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -23,6 +23,7 @@ end function LindbladJumpAffect!(integrator) internal_params = integrator.p c_ops = internal_params.c_ops + c_ops_herm = internal_params.c_ops_herm cache_mc = internal_params.cache_mc weights_mc = internal_params.weights_mc cumsum_weights_mc = internal_params.cumsum_weights_mc @@ -33,11 +34,11 @@ function LindbladJumpAffect!(integrator) ψ = integrator.u @inbounds for i in eachindex(weights_mc) - mul!(cache_mc, c_ops[i], ψ) - weights_mc[i] = real(dot(cache_mc, cache_mc)) + weights_mc[i] = real(dot(ψ, c_ops_herm[i], ψ)) end cumsum!(cumsum_weights_mc, weights_mc) - collaps_idx = getindex(1:length(weights_mc), findfirst(>(rand(traj_rng) * sum(weights_mc)), cumsum_weights_mc)) + r = rand(traj_rng) * sum(weights_mc) + collaps_idx = getindex(1:length(weights_mc), findfirst(>(r), cumsum_weights_mc)) mul!(cache_mc, c_ops[collaps_idx], ψ) normalize!(cache_mc) copyto!(integrator.u, cache_mc) @@ -237,13 +238,17 @@ function mcsolveProblem( jump_times = Vector{Float64}(undef, jump_times_which_init_size) jump_which = Vector{Int16}(undef, jump_times_which_init_size) + c_ops_data = get_data.(c_ops) + c_ops_herm_data = map(op -> op' * op, c_ops_data) + params2 = ( expvals = expvals, e_ops_mc = e_ops2, is_empty_e_ops_mc = is_empty_e_ops_mc, progr_mc = ProgressBar(length(t_l), enable = false), traj_rng = rng, - c_ops = get_data.(c_ops), + c_ops = c_ops_data, + c_ops_herm = c_ops_herm_data, cache_mc = cache_mc, weights_mc = weights_mc, cumsum_weights_mc = cumsum_weights_mc, From 40f0cce434169a65e3a7fcbda7433af98843e056 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 15 Oct 2024 15:07:04 +0200 Subject: [PATCH 059/329] Make ProgressBar mutable --- src/progress_bar.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/progress_bar.jl b/src/progress_bar.jl index 40c81d452..0fe989f52 100644 --- a/src/progress_bar.jl +++ b/src/progress_bar.jl @@ -1,6 +1,6 @@ export ProgressBar, next! -struct ProgressBar{CT,T1<:Integer,T2<:Real,T3,T4<:Real,LT} +mutable struct ProgressBar{CT,T1<:Integer,T2<:Real,T3,T4<:Real,LT} counter::CT max_counts::T1 enable::Bool From 33577062136b89dde85b9aefbbc93a9b4889b93d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:39:16 +0200 Subject: [PATCH 060/329] Introduce `QobjEvo` and use `SciMLOperators` for time evolution (#266) * First working implementation * Minor changes * First working case of sesolve * Minor changes * Rebase commits * Apply Yi-Te comments * Working mesolve * Make dsf_mesolve and dfd_mesolve work * Minor changes * Working version of ssesolve * Remove sleep in runtests * Remove OperatorSum and TimeDependentOperatorSum * Remove alg as argument from all problem definitions * First working runtests * Add tests for QObjEvo and time evolution * Add docstrings to documentation * Reduce tolerance for stochastic runtests * Make runtests multithreaded and reduce time for timeevolution tests * Add QobjEvo tests and minor changes * Update docstrings * Add comment on the use of MatrixOperator --- .github/workflows/CI.yml | 1 + docs/src/api.md | 10 +- src/QuantumToolbox.jl | 16 +- src/qobj/arithmetic_and_attributes.jl | 176 ++++--- src/qobj/boolean_functions.jl | 79 ++-- src/qobj/eigsolve.jl | 69 ++- src/qobj/functions.jl | 40 +- src/qobj/operator_sum.jl | 57 --- src/qobj/quantum_object.jl | 205 +-------- src/qobj/quantum_object_base.jl | 216 +++++++++ src/qobj/quantum_object_evo.jl | 418 +++++++++++++++++ src/qobj/superoperators.jl | 47 +- src/qobj/synonyms.jl | 155 +++++-- src/steadystate.jl | 81 ++-- src/time_evolution/mcsolve.jl | 239 +++++----- src/time_evolution/mesolve.jl | 179 ++++---- src/time_evolution/sesolve.jl | 151 +++--- src/time_evolution/ssesolve.jl | 432 +++++++++--------- src/time_evolution/time_evolution.jl | 121 ++--- .../time_evolution_dynamical.jl | 232 +++++----- src/utilities.jl | 1 + test/core-test/quantum_objects.jl | 18 + test/core-test/quantum_objects_evo.jl | 180 ++++++++ test/core-test/steady_state.jl | 7 +- test/core-test/time_evolution.jl | 258 +++++++++-- test/runtests.jl | 2 + 26 files changed, 2104 insertions(+), 1286 deletions(-) delete mode 100644 src/qobj/operator_sum.jl create mode 100644 src/qobj/quantum_object_base.jl create mode 100644 src/qobj/quantum_object_evo.jl create mode 100644 test/core-test/quantum_objects_evo.jl diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e45dcbbff..66b845727 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -71,6 +71,7 @@ jobs: - uses: julia-actions/julia-runtest@v1 env: GROUP: ${{ matrix.group }} + JULIA_NUM_THREADS: auto - uses: julia-actions/julia-processcoverage@v1 with: directories: src,ext diff --git a/docs/src/api.md b/docs/src/api.md index dae7f72ae..554d1e8b0 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -13,6 +13,7 @@ Pages = ["api.md"] ## [Quantum object (Qobj) and type](@id doc-API:Quantum-object-and-type) ```@docs +AbstractQuantumObject BraQuantumObject Bra KetQuantumObject @@ -26,10 +27,15 @@ OperatorKet SuperOperatorQuantumObject SuperOperator QuantumObject -OperatorSum +QuantumObjectEvolution size eltype length +``` + +## [Qobj boolean functions](@id doc-API:Qobj-boolean-functions) + +```@docs isbra isket isoper @@ -40,6 +46,7 @@ LinearAlgebra.ishermitian LinearAlgebra.issymmetric LinearAlgebra.isposdef isunitary +isconstant ``` ## [Qobj arithmetic and attributes](@id doc-API:Qobj-arithmetic-and-attributes) @@ -154,6 +161,7 @@ lindblad_dissipator ## [Synonyms of functions for Qobj](@id doc-API:Synonyms-of-functions-for-Qobj) ```@docs Qobj +QobjEvo shape isherm trans diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 9c2920dfe..549df5b01 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -21,6 +21,7 @@ import SciMLBase: reinit!, remake, u_modified!, + ODEFunction, ODEProblem, SDEProblem, EnsembleProblem, @@ -32,13 +33,21 @@ import SciMLBase: ContinuousCallback, DiscreteCallback import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 -import SciMLOperators: MatrixOperator +import SciMLOperators: + AbstractSciMLOperator, + MatrixOperator, + ScalarOperator, + IdentityOperator, + cache_operator, + update_coefficients!, + concretize, + isconstant import LinearSolve: LinearProblem, SciMLLinearSolveAlgorithm, KrylovJL_MINRES, KrylovJL_GMRES import DiffEqBase: get_tstops import DiffEqCallbacks: PeriodicCallback, PresetTimeCallback, TerminateSteadyState import OrdinaryDiffEqCore: OrdinaryDiffEqAlgorithm import OrdinaryDiffEqTsit5: Tsit5 -import DiffEqNoiseProcess: RealWienerProcess +import DiffEqNoiseProcess: RealWienerProcess! # other dependencies (in alphabetical order) import ArrayInterface: allowed_getindex, allowed_setindex! @@ -62,7 +71,9 @@ include("progress_bar.jl") include("linear_maps.jl") # Quantum Object +include("qobj/quantum_object_base.jl") include("qobj/quantum_object.jl") +include("qobj/quantum_object_evo.jl") include("qobj/boolean_functions.jl") include("qobj/arithmetic_and_attributes.jl") include("qobj/eigsolve.jl") @@ -71,7 +82,6 @@ include("qobj/states.jl") include("qobj/operators.jl") include("qobj/superoperators.jl") include("qobj/synonyms.jl") -include("qobj/operator_sum.jl") # time evolution include("time_evolution/time_evolution.jl") diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 29b872dec..96947bb2c 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -36,84 +36,80 @@ end for op in (:(+), :(-), :(*)) @eval begin - function LinearAlgebra.$op( - A::QuantumObject{<:AbstractArray{T1},OpType}, - B::QuantumObject{<:AbstractArray{T2},OpType}, - ) where {T1,T2,OpType<:QuantumObjectType} - A.dims != B.dims && - throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) - return QuantumObject($(op)(A.data, B.data), A.type, A.dims) + function LinearAlgebra.$op(A::AbstractQuantumObject, B::AbstractQuantumObject) + check_dims(A, B) + QType = promote_op_type(A, B) + return QType($(op)(A.data, B.data), A.type, A.dims) end - LinearAlgebra.$op(A::QuantumObject{<:AbstractArray{T}}) where {T} = QuantumObject($(op)(A.data), A.type, A.dims) + LinearAlgebra.$op(A::AbstractQuantumObject) = get_typename_wrapper(A)($(op)(A.data), A.type, A.dims) - LinearAlgebra.$op(n::T1, A::QuantumObject{<:AbstractArray{T2}}) where {T1<:Number,T2} = - QuantumObject($(op)(n * I, A.data), A.type, A.dims) - LinearAlgebra.$op(A::QuantumObject{<:AbstractArray{T1}}, n::T2) where {T1,T2<:Number} = - QuantumObject($(op)(A.data, n * I), A.type, A.dims) + LinearAlgebra.$op(n::T, A::AbstractQuantumObject) where {T<:Number} = + get_typename_wrapper(A)($(op)(n * I, A.data), A.type, A.dims) + LinearAlgebra.$op(A::AbstractQuantumObject, n::T) where {T<:Number} = + get_typename_wrapper(A)($(op)(A.data, n * I), A.type, A.dims) end end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, -) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + A::AbstractQuantumObject{DT1,OperatorQuantumObject}, + B::QuantumObject{DT2,KetQuantumObject}, +) where {DT1,DT2} + check_dims(A, B) return QuantumObject(A.data * B.data, Ket, A.dims) end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},BraQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, -) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + A::QuantumObject{DT1,BraQuantumObject}, + B::AbstractQuantumObject{DT2,OperatorQuantumObject}, +) where {DT1,DT2} + check_dims(A, B) return QuantumObject(A.data * B.data, Bra, A.dims) end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},KetQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},BraQuantumObject}, -) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + A::QuantumObject{DT1,KetQuantumObject}, + B::QuantumObject{DT2,BraQuantumObject}, +) where {DT1,DT2} + check_dims(A, B) return QuantumObject(A.data * B.data, Operator, A.dims) end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},BraQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, -) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + A::QuantumObject{DT1,BraQuantumObject}, + B::QuantumObject{DT2,KetQuantumObject}, +) where {DT1,DT2} + check_dims(A, B) return A.data * B.data end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, -) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + A::AbstractQuantumObject{DT1,SuperOperatorQuantumObject}, + B::QuantumObject{DT2,OperatorQuantumObject}, +) where {DT1,DT2} + check_dims(A, B) return QuantumObject(vec2mat(A.data * mat2vec(B.data)), Operator, A.dims) end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},OperatorBraQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},OperatorKetQuantumObject}, -) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + A::QuantumObject{DT1,OperatorBraQuantumObject}, + B::QuantumObject{DT2,OperatorKetQuantumObject}, +) where {DT1,DT2} + check_dims(A, B) return A.data * B.data end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},OperatorKetQuantumObject}, -) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + A::AbstractQuantumObject{DT1,SuperOperatorQuantumObject}, + B::QuantumObject{DT2,OperatorKetQuantumObject}, +) where {DT1,DT2} + check_dims(A, B) return QuantumObject(A.data * B.data, OperatorKet, A.dims) end function LinearAlgebra.:(*)( A::QuantumObject{<:AbstractArray{T1},OperatorBraQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, + B::AbstractQuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, ) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + check_dims(A, B) return QuantumObject(A.data * B.data, OperatorBra, A.dims) end -LinearAlgebra.:(^)(A::QuantumObject{<:AbstractArray{T}}, n::T1) where {T,T1<:Number} = - QuantumObject(^(A.data, n), A.type, A.dims) -LinearAlgebra.:(/)(A::QuantumObject{<:AbstractArray{T}}, n::T1) where {T,T1<:Number} = - QuantumObject(/(A.data, n), A.type, A.dims) +LinearAlgebra.:(^)(A::QuantumObject{DT}, n::T) where {DT,T<:Number} = QuantumObject(^(A.data, n), A.type, A.dims) +LinearAlgebra.:(/)(A::AbstractQuantumObject{DT}, n::T) where {DT,T<:Number} = + get_typename_wrapper(A)(A.data / n, A.type, A.dims) @doc raw""" dot(A::QuantumObject, B::QuantumObject) @@ -125,93 +121,91 @@ Note that `A` and `B` should be [`Ket`](@ref) or [`OperatorKet`](@ref) `A ⋅ B` (where `⋅` can be typed by tab-completing `\cdot` in the REPL) is a synonym for `dot(A, B)` """ function LinearAlgebra.dot( - A::QuantumObject{<:AbstractArray{T1},OpType}, - B::QuantumObject{<:AbstractArray{T2},OpType}, -) where {T1<:Number,T2<:Number,OpType<:Union{KetQuantumObject,OperatorKetQuantumObject}} + A::QuantumObject{DT1,OpType}, + B::QuantumObject{DT2,OpType}, +) where {DT1,DT2,OpType<:Union{KetQuantumObject,OperatorKetQuantumObject}} A.dims != B.dims && throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) return LinearAlgebra.dot(A.data, B.data) end @doc raw""" - dot(i::QuantumObject, A::QuantumObject j::QuantumObject) + dot(i::QuantumObject, A::AbstractQuantumObject j::QuantumObject) -Compute the generalized dot product `dot(i, A*j)` between three [`QuantumObject`](@ref): ``\langle i | \hat{A} | j \rangle`` +Compute the generalized dot product `dot(i, A*j)` between a [`AbstractQuantumObject`](@ref) and two [`QuantumObject`](@ref) (`i` and `j`), namely ``\langle i | \hat{A} | j \rangle``. Supports the following inputs: - `A` is in the type of [`Operator`](@ref), with `i` and `j` are both [`Ket`](@ref). - `A` is in the type of [`SuperOperator`](@ref), with `i` and `j` are both [`OperatorKet`](@ref) """ function LinearAlgebra.dot( - i::QuantumObject{<:AbstractArray{T1},KetQuantumObject}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - j::QuantumObject{<:AbstractArray{T3},KetQuantumObject}, -) where {T1<:Number,T2<:Number,T3<:Number} + i::QuantumObject{DT1,KetQuantumObject}, + A::AbstractQuantumObject{DT2,OperatorQuantumObject}, + j::QuantumObject{DT3,KetQuantumObject}, +) where {DT1,DT2,DT3} ((i.dims != A.dims) || (A.dims != j.dims)) && throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) return LinearAlgebra.dot(i.data, A.data, j.data) end function LinearAlgebra.dot( - i::QuantumObject{<:AbstractArray{T1},OperatorKetQuantumObject}, - A::QuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, - j::QuantumObject{<:AbstractArray{T3},OperatorKetQuantumObject}, -) where {T1<:Number,T2<:Number,T3<:Number} + i::QuantumObject{DT1,OperatorKetQuantumObject}, + A::AbstractQuantumObject{DT2,SuperOperatorQuantumObject}, + j::QuantumObject{DT3,OperatorKetQuantumObject}, +) where {DT1,DT2,DT3} ((i.dims != A.dims) || (A.dims != j.dims)) && throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) return LinearAlgebra.dot(i.data, A.data, j.data) end @doc raw""" - conj(A::QuantumObject) + conj(A::AbstractQuantumObject) -Return the element-wise complex conjugation of the [`QuantumObject`](@ref). +Return the element-wise complex conjugation of the [`AbstractQuantumObject`](@ref). """ -Base.conj(A::QuantumObject{<:AbstractArray{T}}) where {T} = QuantumObject(conj(A.data), A.type, A.dims) +Base.conj(A::AbstractQuantumObject) = get_typename_wrapper(A)(conj(A.data), A.type, A.dims) @doc raw""" - transpose(A::QuantumObject) + transpose(A::AbstractQuantumObject) -Lazy matrix transpose of the [`QuantumObject`](@ref). +Lazy matrix transpose of the [`AbstractQuantumObject`](@ref). """ LinearAlgebra.transpose( - A::QuantumObject{<:AbstractArray{T},OpType}, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(transpose(A.data), A.type, A.dims) + A::AbstractQuantumObject{DT,OpType}, +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + get_typename_wrapper(A)(transpose(A.data), A.type, A.dims) @doc raw""" A' - adjoint(A::QuantumObject) + adjoint(A::AbstractQuantumObject) -Lazy adjoint (conjugate transposition) of the [`QuantumObject`](@ref) +Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) Note that `A'` is a synonym for `adjoint(A)` """ LinearAlgebra.adjoint( - A::QuantumObject{<:AbstractArray{T},OpType}, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(adjoint(A.data), A.type, A.dims) -LinearAlgebra.adjoint(A::QuantumObject{<:AbstractArray{T},KetQuantumObject}) where {T} = - QuantumObject(adjoint(A.data), Bra, A.dims) -LinearAlgebra.adjoint(A::QuantumObject{<:AbstractArray{T},BraQuantumObject}) where {T} = - QuantumObject(adjoint(A.data), Ket, A.dims) -LinearAlgebra.adjoint(A::QuantumObject{<:AbstractArray{T},OperatorKetQuantumObject}) where {T} = + A::AbstractQuantumObject{DT,OpType}, +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + get_typename_wrapper(A)(adjoint(A.data), A.type, A.dims) +LinearAlgebra.adjoint(A::QuantumObject{DT,KetQuantumObject}) where {DT} = QuantumObject(adjoint(A.data), Bra, A.dims) +LinearAlgebra.adjoint(A::QuantumObject{DT,BraQuantumObject}) where {DT} = QuantumObject(adjoint(A.data), Ket, A.dims) +LinearAlgebra.adjoint(A::QuantumObject{DT,OperatorKetQuantumObject}) where {DT} = QuantumObject(adjoint(A.data), OperatorBra, A.dims) -LinearAlgebra.adjoint(A::QuantumObject{<:AbstractArray{T},OperatorBraQuantumObject}) where {T} = +LinearAlgebra.adjoint(A::QuantumObject{DT,OperatorBraQuantumObject}) where {DT} = QuantumObject(adjoint(A.data), OperatorKet, A.dims) @doc raw""" - inv(A::QuantumObject) + inv(A::AbstractQuantumObject) -Matrix inverse of the [`QuantumObject`](@ref) +Matrix inverse of the [`AbstractQuantumObject`](@ref). If `A` is a [`QuantumObjectEvolution`](@ref), the inverse is computed at the last computed time. """ LinearAlgebra.inv( - A::QuantumObject{<:AbstractArray{T},OpType}, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + A::AbstractQuantumObject{DT,OpType}, +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(sparse(inv(Matrix(A.data))), A.type, A.dims) LinearAlgebra.Hermitian( - A::QuantumObject{<:AbstractArray{T},OpType}, + A::QuantumObject{DT,OpType}, uplo::Symbol = :U, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(Hermitian(A.data, uplo), A.type, A.dims) @doc raw""" @@ -436,8 +430,8 @@ Matrix sine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ LinearAlgebra.sin( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) - exp(-1im * A)) / 2im + A::QuantumObject{DT,ObjType}, +) where {DT,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) - exp(-1im * A)) / 2im @doc raw""" cos(A::QuantumObject) @@ -449,8 +443,8 @@ Matrix cosine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ LinearAlgebra.cos( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) + exp(-1im * A)) / 2 + A::QuantumObject{DT,ObjType}, +) where {DT,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) + exp(-1im * A)) / 2 @doc raw""" diag(A::QuantumObject, k::Int=0) @@ -659,11 +653,11 @@ tidyup!(A::AbstractArray{T}, tol::T2 = 1e-14) where {T,T2<:Real} = @. A = T(abs(real(A)) > tol) * real(A) + 1im * T(abs(imag(A)) > tol) * imag(A) @doc raw""" - get_data(A::QuantumObject) + get_data(A::AbstractQuantumObject) -Returns the data of a QuantumObject. +Returns the data of a [`AbstractQuantumObject`](@ref). """ -get_data(A::QuantumObject) = A.data +get_data(A::AbstractQuantumObject) = A.data @doc raw""" get_coherence(ψ::QuantumObject) diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index 4ec347679..cccea5321 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -3,74 +3,82 @@ All boolean functions for checking the data or type in `QuantumObject` =# export isket, isbra, isoper, isoperbra, isoperket, issuper -export isunitary +export isunitary, isconstant @doc raw""" - isbra(A::QuantumObject) + isbra(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`BraQuantumObject`](@ref). +Checks if the [`QuantumObject`](@ref) `A` is a [`BraQuantumObject`](@ref). Default case returns `false` for any other inputs. """ -isbra(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = OpType <: BraQuantumObject +isbra(A::QuantumObject) = isbra(typeof(A)) +isbra(::Type{QuantumObject{DT,BraQuantumObject,N}}) where {DT,N} = true +isbra(A) = false # default case @doc raw""" - isket(A::QuantumObject) + isket(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`KetQuantumObject`](@ref). +Checks if the [`QuantumObject`](@ref) `A` is a [`KetQuantumObject`](@ref). Default case returns `false` for any other inputs. """ -isket(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = OpType <: KetQuantumObject +isket(A::QuantumObject) = isket(typeof(A)) +isket(::Type{QuantumObject{DT,KetQuantumObject,N}}) where {DT,N} = true +isket(A) = false # default case @doc raw""" - isoper(A::QuantumObject) + isoper(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorQuantumObject`](@ref). +Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`OperatorQuantumObject`](@ref). Default case returns `false` for any other inputs. """ -isoper(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = - OpType <: OperatorQuantumObject +isoper(A::AbstractQuantumObject) = isoper(typeof(A)) +isoper(::Type{<:AbstractQuantumObject{DT,OperatorQuantumObject,N}}) where {DT,N} = true +isoper(A) = false # default case @doc raw""" - isoperbra(A::QuantumObject) + isoperbra(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBraQuantumObject`](@ref). +Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBraQuantumObject`](@ref). Default case returns `false` for any other inputs. """ -isoperbra(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = - OpType <: OperatorBraQuantumObject +isoperbra(A::QuantumObject) = isoperbra(typeof(A)) +isoperbra(::Type{QuantumObject{DT,OperatorBraQuantumObject,N}}) where {DT,N} = true +isoperbra(A) = false # default case @doc raw""" - isoperket(A::QuantumObject) + isoperket(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKetQuantumObject`](@ref). +Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKetQuantumObject`](@ref). Default case returns `false` for any other inputs. """ -isoperket(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = - OpType <: OperatorKetQuantumObject +isoperket(A::QuantumObject) = isoperket(typeof(A)) +isoperket(::Type{QuantumObject{DT,OperatorKetQuantumObject,N}}) where {DT,N} = true +isoperket(A) = false # default case @doc raw""" - issuper(A::QuantumObject) + issuper(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`SuperOperatorQuantumObject`](@ref). +Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`SuperOperatorQuantumObject`](@ref). Default case returns `false` for any other inputs. """ -issuper(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = - OpType <: SuperOperatorQuantumObject +issuper(A::AbstractQuantumObject) = issuper(typeof(A)) +issuper(::Type{<:AbstractQuantumObject{DT,SuperOperatorQuantumObject,N}}) where {DT,N} = true +issuper(A) = false # default case @doc raw""" - ishermitian(A::QuantumObject) + ishermitian(A::AbstractQuantumObject) -Test whether the [`QuantumObject`](@ref) is Hermitian. +Test whether the [`AbstractQuantumObject`](@ref) is Hermitian. """ -LinearAlgebra.ishermitian(A::QuantumObject{<:AbstractArray{T}}) where {T} = ishermitian(A.data) +LinearAlgebra.ishermitian(A::AbstractQuantumObject) = ishermitian(A.data) @doc raw""" - issymmetric(A::QuantumObject) + issymmetric(A::AbstractQuantumObject) -Test whether the [`QuantumObject`](@ref) is symmetric. +Test whether the [`AbstractQuantumObject`](@ref) is symmetric. """ -LinearAlgebra.issymmetric(A::QuantumObject{<:AbstractArray{T}}) where {T} = issymmetric(A.data) +LinearAlgebra.issymmetric(A::AbstractQuantumObject) = issymmetric(A.data) @doc raw""" - isposdef(A::QuantumObject) + isposdef(A::AbstractQuantumObject) -Test whether the [`QuantumObject`](@ref) is positive definite (and Hermitian) by trying to perform a Cholesky factorization of `A`. +Test whether the [`AbstractQuantumObject`](@ref) is positive definite (and Hermitian) by trying to perform a Cholesky factorization of `A`. """ -LinearAlgebra.isposdef(A::QuantumObject{<:AbstractArray{T}}) where {T} = isposdef(A.data) +LinearAlgebra.isposdef(A::AbstractQuantumObject) = isposdef(A.data) @doc raw""" isunitary(U::QuantumObject; kwargs...) @@ -81,3 +89,10 @@ Note that all the keyword arguments will be passed to `Base.isapprox`. """ isunitary(U::QuantumObject{<:AbstractArray{T}}; kwargs...) where {T} = isoper(U) ? isapprox(U.data * U.data', I(size(U, 1)); kwargs...) : false + +@doc raw""" + isconstant(A::AbstractQuantumObject) + +Test whether the [`AbstractQuantumObject`](@ref) `A` is constant in time. For a [`QuantumObject`](@ref), this function returns `true`, while for a [`QuantumObjectEvolution`](@ref), this function returns `true` if the operator is contant in time. +""" +isconstant(A::AbstractQuantumObject) = isconstant(A.data) diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 483b82a5d..0c972a3f7 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -3,7 +3,8 @@ Eigen solvers and results for QuantumObject =# export EigsolveResult -export eigenenergies, eigenstates, eigsolve, eigsolve_al +export eigenenergies, eigenstates, eigsolve +export eigsolve_al @doc raw""" struct EigsolveResult{T1<:Vector{<:Number}, T2<:AbstractMatrix{<:Number}, ObjType<:Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject},N} @@ -322,33 +323,34 @@ function eigsolve( end @doc raw""" - eigsolve_al(H::QuantumObject, - T::Real, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - alg::OrdinaryDiffEqAlgorithm=Tsit5(), - H_t::Union{Nothing,Function}=nothing, - params::NamedTuple=NamedTuple(), - ρ0::Union{Nothing, AbstractMatrix} = nothing, - k::Int=1, - krylovdim::Int=min(10, size(H, 1)), - maxiter::Int=200, - eigstol::Real=1e-6, - kwargs...) + eigsolve_al( + H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, + T::Real, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + alg::OrdinaryDiffEqAlgorithm = Tsit5(), + params::NamedTuple = NamedTuple(), + ρ0::AbstractMatrix = rand_dm(prod(H.dims)).data, + k::Int = 1, + krylovdim::Int = min(10, size(H, 1)), + maxiter::Int = 200, + eigstol::Real = 1e-6, + kwargs..., + ) Solve the eigenvalue problem for a Liouvillian superoperator `L` using the Arnoldi-Lindblad method. # Arguments -- `H`: The Hamiltonian (or directly the Liouvillian) of the system. -- `T`: The time at which to evaluate the time evolution +- `H`: The Hamiltonian (or directly the Liouvillian) of the system. It can be a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a tuple of the form supported by [`mesolve`](@ref). +- `T`: The time at which to evaluate the time evolution. - `c_ops`: A vector of collapse operators. Default is `nothing` meaning the system is closed. -- `alg`: The differential equation solver algorithm -- `H_t`: A function `H_t(t)` that returns the additional term at time `t` -- `params`: A dictionary of additional parameters -- `ρ0`: The initial density matrix. If not specified, a random density matrix is used -- `k`: The number of eigenvalues to compute -- `krylovdim`: The dimension of the Krylov subspace -- `maxiter`: The maximum number of iterations for the eigsolver -- `eigstol`: The tolerance for the eigsolver -- `kwargs`: Additional keyword arguments passed to the differential equation solver +- `alg`: The differential equation solver algorithm. Default is `Tsit5()`. +- `params`: A `NamedTuple` containing the parameters of the system. +- `ρ0`: The initial density matrix. If not specified, a random density matrix is used. +- `k`: The number of eigenvalues to compute. +- `krylovdim`: The dimension of the Krylov subspace. +- `maxiter`: The maximum number of iterations for the eigsolver. +- `eigstol`: The tolerance for the eigsolver. +- `kwargs`: Additional keyword arguments passed to the differential equation solver. # Notes - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) @@ -361,11 +363,10 @@ Solve the eigenvalue problem for a Liouvillian superoperator `L` using the Arnol - [1] Minganti, F., & Huybrechts, D. (2022). Arnoldi-Lindblad time evolution: Faster-than-the-clock algorithm for the spectrum of time-independent and Floquet open quantum systems. Quantum, 6, 649. """ function eigsolve_al( - H::QuantumObject{MT1,HOpType}, + H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, T::Real, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), - H_t::Union{Nothing,Function} = nothing, params::NamedTuple = NamedTuple(), ρ0::AbstractMatrix = rand_dm(prod(H.dims)).data, k::Int = 1, @@ -373,14 +374,12 @@ function eigsolve_al( maxiter::Int = 200, eigstol::Real = 1e-6, kwargs..., -) where {MT1<:AbstractMatrix,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} - L = liouvillian(H, c_ops) +) where {DT1,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + L_evo = _mesolve_make_L_QobjEvo(H, c_ops) prob = mesolveProblem( - L, - QuantumObject(ρ0, dims = H.dims), - [0, T]; - alg = alg, - H_t = H_t, + L_evo, + QuantumObject(ρ0, type = Operator, dims = H.dims), + [zero(T), T]; params = params, progress_bar = Val(false), kwargs..., @@ -389,9 +388,9 @@ function eigsolve_al( # prog = ProgressUnknown(desc="Applications:", showspeed = true, enabled=progress) - Lmap = ArnoldiLindbladIntegratorMap(eltype(MT1), size(L), integrator) + Lmap = ArnoldiLindbladIntegratorMap(eltype(DT1), size(L_evo), integrator) - res = _eigsolve(Lmap, mat2vec(ρ0), L.type, L.dims, k, krylovdim, maxiter = maxiter, tol = eigstol) + res = _eigsolve(Lmap, mat2vec(ρ0), L_evo.type, L_evo.dims, k, krylovdim, maxiter = maxiter, tol = eigstol) # finish!(prog) vals = similar(res.values) @@ -399,7 +398,7 @@ function eigsolve_al( for i in eachindex(res.values) vec = view(res.vectors, :, i) - vals[i] = dot(vec, L.data, vec) + vals[i] = dot(vec, L_evo.data, vec) @. vecs[:, i] = vec * exp(-1im * angle(vec[1])) end diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index de729d08c..ef98af4d9 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -17,7 +17,7 @@ ket2dm(ψ::QuantumObject{<:AbstractArray{T},KetQuantumObject}) where {T} = ψ * ket2dm(ρ::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = ρ @doc raw""" - expect(O::QuantumObject, ψ::Union{QuantumObject,Vector{QuantumObject}}) + expect(O::AbstractQuantumObject, ψ::Union{QuantumObject,Vector{QuantumObject}}) Expectation value of the [`Operator`](@ref) `O` with the state `ψ`. The state can be a [`Ket`](@ref), [`Bra`](@ref) or [`Operator`](@ref). @@ -44,15 +44,15 @@ julia> expect(Hermitian(a' * a), ψ) |> round ``` """ function expect( - O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, -) where {T1,T2} + O::AbstractQuantumObject{DT1,OperatorQuantumObject}, + ψ::QuantumObject{DT2,KetQuantumObject}, +) where {DT1,DT2} return dot(ψ.data, O.data, ψ.data) end function expect( - O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ::QuantumObject{<:AbstractArray{T2},BraQuantumObject}, -) where {T1,T2} + O::AbstractQuantumObject{DT1,OperatorQuantumObject}, + ψ::QuantumObject{DT2,BraQuantumObject}, +) where {DT1,DT2} return expect(O, ψ') end function expect( @@ -95,11 +95,9 @@ The function returns a real number if `O` is hermitian, and returns a complex nu Note that `ψ` can also be given as a list of [`QuantumObject`](@ref), it returns a list of expectation values. """ -variance( - O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ::QuantumObject{<:AbstractArray{T2}}, -) where {T1,T2} = expect(O^2, ψ) - expect(O, ψ)^2 -variance(O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ψ::Vector{<:QuantumObject}) where {T1} = +variance(O::QuantumObject{DT1,OperatorQuantumObject}, ψ::QuantumObject{DT2}) where {DT1,DT2} = + expect(O^2, ψ) - expect(O, ψ)^2 +variance(O::QuantumObject{DT1,OperatorQuantumObject}, ψ::Vector{<:QuantumObject}) where {DT1} = expect(O^2, ψ) .- expect(O, ψ) .^ 2 @doc raw""" @@ -149,7 +147,7 @@ function dense_to_sparse(A::VT, tol::Real = 1e-10) where {VT<:AbstractVector} end @doc raw""" - kron(A::QuantumObject, B::QuantumObject, ...) + kron(A::AbstractQuantumObject, B::AbstractQuantumObject, ...) Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ``\hat{A} \otimes \hat{B} \otimes \cdots``. @@ -191,13 +189,15 @@ Quantum Object: type=Operator dims=[20, 20] size=(400, 400) ishermitian= ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦ ``` """ -LinearAlgebra.kron( - A::QuantumObject{<:AbstractArray{T1},OpType}, - B::QuantumObject{<:AbstractArray{T2},OpType}, -) where {T1,T2,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} = - QuantumObject(kron(A.data, B.data), A.type, vcat(A.dims, B.dims)) -LinearAlgebra.kron(A::QuantumObject) = A -function LinearAlgebra.kron(A::Vector{<:QuantumObject}) +function LinearAlgebra.kron( + A::AbstractQuantumObject{DT1,OpType}, + B::AbstractQuantumObject{DT2,OpType}, +) where {DT1,DT2,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} + QType = promote_op_type(A, B) + return QType(kron(A.data, B.data), A.type, vcat(A.dims, B.dims)) +end +LinearAlgebra.kron(A::AbstractQuantumObject) = A +function LinearAlgebra.kron(A::Vector{<:AbstractQuantumObject}) @warn "`tensor(A)` or `kron(A)` with `A` is a `Vector` can hurt performance. Try to use `tensor(A...)` or `kron(A...)` instead." return kron(A...) end diff --git a/src/qobj/operator_sum.jl b/src/qobj/operator_sum.jl deleted file mode 100644 index 5b2398217..000000000 --- a/src/qobj/operator_sum.jl +++ /dev/null @@ -1,57 +0,0 @@ -export OperatorSum - -@doc raw""" - struct OperatorSum - -A constructor to represent a sum of operators ``\sum_i c_i \hat{O}_i`` with a list of coefficients ``c_i`` and a list of operators ``\hat{O}_i``. - -This is very useful when we have to update only the coefficients, without allocating memory by performing the sum of the operators. -""" -struct OperatorSum{CT<:AbstractVector{<:Number},OT<:Union{AbstractVector,Tuple}} <: AbstractQuantumObject - coefficients::CT - operators::OT - function OperatorSum( - coefficients::CT, - operators::OT, - ) where {CT<:AbstractVector{<:Number},OT<:Union{AbstractVector,Tuple}} - length(coefficients) == length(operators) || - throw(DimensionMismatch("The number of coefficients must be the same as the number of operators.")) - # Check if all the operators have the same dimensions - size_1 = size(operators[1]) - mapreduce(x -> size(x) == size_1, &, operators) || - throw(DimensionMismatch("All the operators must have the same dimensions.")) - T = promote_type( - mapreduce(x -> eltype(x), promote_type, operators), - mapreduce(eltype, promote_type, coefficients), - ) - coefficients2 = T.(coefficients) - CT2 = typeof(coefficients2) - return new{CT2,OT}(coefficients2, operators) - end -end - -Base.size(A::OperatorSum) = size(A.operators[1]) -Base.size(A::OperatorSum, inds...) = size(A.operators[1], inds...) -Base.length(A::OperatorSum) = length(A.operators[1]) -Base.copy(A::OperatorSum) = OperatorSum(copy(A.coefficients), copy(A.operators)) -Base.deepcopy(A::OperatorSum) = OperatorSum(deepcopy(A.coefficients), deepcopy(A.operators)) - -function update_coefficients!(A::OperatorSum, coefficients) - length(A.coefficients) == length(coefficients) || - throw(DimensionMismatch("The number of coefficients must be the same as the number of operators.")) - return A.coefficients .= coefficients -end - -@inline function LinearAlgebra.mul!(y::AbstractVector{T}, A::OperatorSum, x::AbstractVector, α, β) where {T} - # Note that β is applied only to the first term - mul!(y, A.operators[1], x, α * A.coefficients[1], β) - @inbounds for i in 2:length(A.operators) - A.coefficients[i] == 0 && continue - mul!(y, A.operators[i], x, α * A.coefficients[i], 1) - end - return y -end - -function liouvillian(A::OperatorSum, Id_cache = I(prod(A.operators[1].dims))) - return OperatorSum(A.coefficients, liouvillian.(A.operators, Ref(Id_cache))) -end diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index f55244b2b..84e347851 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -1,114 +1,11 @@ #= -This file defines: - 1. the QuantumObject (Qobj) structure - 2. all the type structures for QuantumObject -Also support for fundamental functions in Julia standard library: - - Base: show, length, size, eltype, getindex, setindex!, isequal, :(==), isapprox, Vector, Matrix +This file defines the QuantumObject (Qobj) structure. +It also implements the fundamental functions in Julia standard library: + - Base: show, real, imag, Vector, Matrix - SparseArrays: sparse, nnz, nonzeros, rowvals, droptol!, dropzeros, dropzeros!, SparseVector, SparseMatrixCSC =# -export AbstractQuantumObject, QuantumObject -export QuantumObjectType, - BraQuantumObject, - KetQuantumObject, - OperatorQuantumObject, - OperatorBraQuantumObject, - OperatorKetQuantumObject, - SuperOperatorQuantumObject -export Bra, Ket, Operator, OperatorBra, OperatorKet, SuperOperator - -abstract type AbstractQuantumObject end -abstract type QuantumObjectType end - -@doc raw""" - BraQuantumObject <: QuantumObjectType - -Constructor representing a bra state ``\langle\psi|``. -""" -struct BraQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::BraQuantumObject) = print(io, "Bra") - -@doc raw""" - const Bra = BraQuantumObject() - -A constant representing the type of [`BraQuantumObject`](@ref): a bra state ``\langle\psi|`` -""" -const Bra = BraQuantumObject() - -@doc raw""" - KetQuantumObject <: QuantumObjectType - -Constructor representing a ket state ``|\psi\rangle``. -""" -struct KetQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::KetQuantumObject) = print(io, "Ket") - -@doc raw""" - const Ket = KetQuantumObject() - -A constant representing the type of [`KetQuantumObject`](@ref): a ket state ``|\psi\rangle`` -""" -const Ket = KetQuantumObject() - -@doc raw""" - OperatorQuantumObject <: QuantumObjectType - -Constructor representing an operator ``\hat{O}``. -""" -struct OperatorQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::OperatorQuantumObject) = print(io, "Operator") - -@doc raw""" - const Operator = OperatorQuantumObject() - -A constant representing the type of [`OperatorQuantumObject`](@ref): an operator ``\hat{O}`` -""" -const Operator = OperatorQuantumObject() - -@doc raw""" - SuperOperatorQuantumObject <: QuantumObjectType - -Constructor representing a super-operator ``\hat{\mathcal{O}}`` acting on vectorized density operator matrices. -""" -struct SuperOperatorQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::SuperOperatorQuantumObject) = print(io, "SuperOperator") - -@doc raw""" - const SuperOperator = SuperOperatorQuantumObject() - -A constant representing the type of [`SuperOperatorQuantumObject`](@ref): a super-operator ``\hat{\mathcal{O}}`` acting on vectorized density operator matrices -""" -const SuperOperator = SuperOperatorQuantumObject() - -@doc raw""" - OperatorBraQuantumObject <: QuantumObjectType - -Constructor representing a bra state in the [`SuperOperator`](@ref) formalism ``\langle\langle\rho|``. -""" -struct OperatorBraQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::OperatorBraQuantumObject) = print(io, "OperatorBra") - -@doc raw""" - const OperatorBra = OperatorBraQuantumObject() - -A constant representing the type of [`OperatorBraQuantumObject`](@ref): a bra state in the [`SuperOperator`](@ref) formalism ``\langle\langle\rho|``. -""" -const OperatorBra = OperatorBraQuantumObject() - -@doc raw""" - OperatorKetQuantumObject <: QuantumObjectType - -Constructor representing a ket state in the [`SuperOperator`](@ref) formalism ``|\rho\rangle\rangle``. -""" -struct OperatorKetQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::OperatorKetQuantumObject) = print(io, "OperatorKet") - -@doc raw""" - const OperatorKet = OperatorKetQuantumObject() - -A constant representing the type of [`OperatorKetQuantumObject`](@ref): a ket state in the [`SuperOperator`](@ref) formalism ``|\rho\rangle\rangle`` -""" -const OperatorKet = OperatorKetQuantumObject() +export QuantumObject @doc raw""" struct QuantumObject{MT<:AbstractArray,ObjType<:QuantumObjectType,N} @@ -135,7 +32,7 @@ julia> a isa QuantumObject true ``` """ -struct QuantumObject{MT<:AbstractArray,ObjType<:QuantumObjectType,N} <: AbstractQuantumObject +struct QuantumObject{MT<:AbstractArray,ObjType<:QuantumObjectType,N} <: AbstractQuantumObject{MT,ObjType,N} data::MT type::ObjType dims::SVector{N,Int} @@ -215,57 +112,6 @@ function QuantumObject( throw(DomainError(size(A), "The size of the array is not compatible with vector or matrix.")) end -function _check_dims(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Integer,N} - _non_static_array_warning("dims", dims) - return (all(>(0), dims) && length(dims) > 0) || - throw(DomainError(dims, "The argument dims must be of non-zero length and contain only positive integers.")) -end -_check_dims(dims::Any) = throw( - ArgumentError( - "The argument dims must be a Tuple or a StaticVector of non-zero length and contain only positive integers.", - ), -) - -function _check_QuantumObject(type::KetQuantumObject, dims, m::Int, n::Int) - (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Ket")) - (prod(dims) != m) && throw(DimensionMismatch("Ket with dims = $(dims) does not fit the array size = $((m, n)).")) - return nothing -end - -function _check_QuantumObject(type::BraQuantumObject, dims, m::Int, n::Int) - (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Bra")) - (prod(dims) != n) && throw(DimensionMismatch("Bra with dims = $(dims) does not fit the array size = $((m, n)).")) - return nothing -end - -function _check_QuantumObject(type::OperatorQuantumObject, dims, m::Int, n::Int) - (m != n) && throw(DomainError((m, n), "The size of the array is not compatible with Operator")) - (prod(dims) != m) && - throw(DimensionMismatch("Operator with dims = $(dims) does not fit the array size = $((m, n)).")) - return nothing -end - -function _check_QuantumObject(type::SuperOperatorQuantumObject, dims, m::Int, n::Int) - (m != n) && throw(DomainError((m, n), "The size of the array is not compatible with SuperOperator")) - (prod(dims) != sqrt(m)) && - throw(DimensionMismatch("SuperOperator with dims = $(dims) does not fit the array size = $((m, n)).")) - return nothing -end - -function _check_QuantumObject(type::OperatorKetQuantumObject, dims, m::Int, n::Int) - (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorKet")) - (prod(dims) != sqrt(m)) && - throw(DimensionMismatch("OperatorKet with dims = $(dims) does not fit the array size = $((m, n)).")) - return nothing -end - -function _check_QuantumObject(type::OperatorBraQuantumObject, dims, m::Int, n::Int) - (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorBra")) - (prod(dims) != sqrt(n)) && - throw(DimensionMismatch("OperatorBra with dims = $(dims) does not fit the array size = $((m, n)).")) - return nothing -end - function QuantumObject( A::QuantumObject{<:AbstractArray{T,N}}; type::ObjType = A.type, @@ -294,7 +140,7 @@ function Base.show( return show(io, MIME("text/plain"), op_data) end -function Base.show(io::IO, QO::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:OperatorQuantumObject} +function Base.show(io::IO, QO::AbstractQuantumObject) op_data = QO.data println( io, @@ -310,41 +156,6 @@ function Base.show(io::IO, QO::QuantumObject{<:AbstractArray{T},OpType}) where { return show(io, MIME("text/plain"), op_data) end -@doc raw""" - size(A::QuantumObject) - size(A::QuantumObject, idx::Int) - -Returns a tuple containing each dimensions of the array in the [`QuantumObject`](@ref). - -Optionally, you can specify an index (`idx`) to just get the corresponding dimension of the array. -""" -Base.size(A::QuantumObject{<:AbstractArray{T}}) where {T} = size(A.data) -Base.size(A::QuantumObject{<:AbstractArray{T}}, idx::Int) where {T} = size(A.data, idx) - -Base.getindex(A::QuantumObject{<:AbstractArray{T}}, inds...) where {T} = getindex(A.data, inds...) -Base.setindex!(A::QuantumObject{<:AbstractArray{T}}, val, inds...) where {T} = setindex!(A.data, val, inds...) - -@doc raw""" - eltype(A::QuantumObject) - -Returns the elements type of the matrix or vector corresponding to the [`QuantumObject`](@ref) `A`. -""" -Base.eltype(A::QuantumObject) = eltype(A.data) - -@doc raw""" - length(A::QuantumObject) - -Returns the length of the matrix or vector corresponding to the [`QuantumObject`](@ref) `A`. -""" -Base.length(A::QuantumObject{<:AbstractArray{T}}) where {T} = length(A.data) - -Base.isequal(A::QuantumObject{<:AbstractArray{T}}, B::QuantumObject{<:AbstractArray{T}}) where {T} = - isequal(A.type, B.type) && isequal(A.dims, B.dims) && isequal(A.data, B.data) -Base.isapprox(A::QuantumObject{<:AbstractArray{T}}, B::QuantumObject{<:AbstractArray{T}}; kwargs...) where {T} = - isequal(A.type, B.type) && isequal(A.dims, B.dims) && isapprox(A.data, B.data; kwargs...) -Base.:(==)(A::QuantumObject{<:AbstractArray{T}}, B::QuantumObject{<:AbstractArray{T}}) where {T} = - (A.type == B.type) && (A.dims == B.dims) && (A.data == B.data) - Base.real(x::QuantumObject) = QuantumObject(real(x.data), x.type, x.dims) Base.imag(x::QuantumObject) = QuantumObject(imag(x.data), x.type, x.dims) @@ -368,7 +179,3 @@ SparseArrays.SparseMatrixCSC(A::QuantumObject{<:AbstractMatrix}) = QuantumObject(SparseMatrixCSC(A.data), A.type, A.dims) SparseArrays.SparseMatrixCSC{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T<:Number} = QuantumObject(SparseMatrixCSC{T}(A.data), A.type, A.dims) - -# functions for getting Float or Complex element type -_FType(::QuantumObject{<:AbstractArray{T}}) where {T<:Number} = _FType(T) -_CType(::QuantumObject{<:AbstractArray{T}}) where {T<:Number} = _CType(T) diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl new file mode 100644 index 000000000..0190e4c44 --- /dev/null +++ b/src/qobj/quantum_object_base.jl @@ -0,0 +1,216 @@ +#= +This file defines the AbstractQuantumObject structure, all the type structures for AbstractQuantumObject, and fundamental functions in Julia standard library: + - Base: show, length, size, eltype, getindex, setindex!, isequal, :(==), isapprox +=# + +export AbstractQuantumObject +export QuantumObjectType, + BraQuantumObject, + KetQuantumObject, + OperatorQuantumObject, + OperatorBraQuantumObject, + OperatorKetQuantumObject, + SuperOperatorQuantumObject +export Bra, Ket, Operator, OperatorBra, OperatorKet, SuperOperator + +@doc raw""" + abstract type AbstractQuantumObject{DataType,ObjType,N} + +Abstract type for all quantum objects like [`QuantumObject`](@ref) and [`QuantumObjectEvolution`](@ref). + +# Example +``` +julia> sigmax() isa AbstractQuantumObject +true +``` +""" +abstract type AbstractQuantumObject{DataType,ObjType,N} end + +abstract type QuantumObjectType end + +@doc raw""" + BraQuantumObject <: QuantumObjectType + +Constructor representing a bra state ``\langle\psi|``. +""" +struct BraQuantumObject <: QuantumObjectType end +Base.show(io::IO, ::BraQuantumObject) = print(io, "Bra") + +@doc raw""" + const Bra = BraQuantumObject() + +A constant representing the type of [`BraQuantumObject`](@ref): a bra state ``\langle\psi|`` +""" +const Bra = BraQuantumObject() + +@doc raw""" + KetQuantumObject <: QuantumObjectType + +Constructor representing a ket state ``|\psi\rangle``. +""" +struct KetQuantumObject <: QuantumObjectType end +Base.show(io::IO, ::KetQuantumObject) = print(io, "Ket") + +@doc raw""" + const Ket = KetQuantumObject() + +A constant representing the type of [`KetQuantumObject`](@ref): a ket state ``|\psi\rangle`` +""" +const Ket = KetQuantumObject() + +@doc raw""" + OperatorQuantumObject <: QuantumObjectType + +Constructor representing an operator ``\hat{O}``. +""" +struct OperatorQuantumObject <: QuantumObjectType end +Base.show(io::IO, ::OperatorQuantumObject) = print(io, "Operator") + +@doc raw""" + const Operator = OperatorQuantumObject() + +A constant representing the type of [`OperatorQuantumObject`](@ref): an operator ``\hat{O}`` +""" +const Operator = OperatorQuantumObject() + +@doc raw""" + SuperOperatorQuantumObject <: QuantumObjectType + +Constructor representing a super-operator ``\hat{\mathcal{O}}`` acting on vectorized density operator matrices. +""" +struct SuperOperatorQuantumObject <: QuantumObjectType end +Base.show(io::IO, ::SuperOperatorQuantumObject) = print(io, "SuperOperator") + +@doc raw""" + const SuperOperator = SuperOperatorQuantumObject() + +A constant representing the type of [`SuperOperatorQuantumObject`](@ref): a super-operator ``\hat{\mathcal{O}}`` acting on vectorized density operator matrices +""" +const SuperOperator = SuperOperatorQuantumObject() + +@doc raw""" + OperatorBraQuantumObject <: QuantumObjectType + +Constructor representing a bra state in the [`SuperOperator`](@ref) formalism ``\langle\langle\rho|``. +""" +struct OperatorBraQuantumObject <: QuantumObjectType end +Base.show(io::IO, ::OperatorBraQuantumObject) = print(io, "OperatorBra") + +@doc raw""" + const OperatorBra = OperatorBraQuantumObject() + +A constant representing the type of [`OperatorBraQuantumObject`](@ref): a bra state in the [`SuperOperator`](@ref) formalism ``\langle\langle\rho|``. +""" +const OperatorBra = OperatorBraQuantumObject() + +@doc raw""" + OperatorKetQuantumObject <: QuantumObjectType + +Constructor representing a ket state in the [`SuperOperator`](@ref) formalism ``|\rho\rangle\rangle``. +""" +struct OperatorKetQuantumObject <: QuantumObjectType end +Base.show(io::IO, ::OperatorKetQuantumObject) = print(io, "OperatorKet") + +@doc raw""" + const OperatorKet = OperatorKetQuantumObject() + +A constant representing the type of [`OperatorKetQuantumObject`](@ref): a ket state in the [`SuperOperator`](@ref) formalism ``|\rho\rangle\rangle`` +""" +const OperatorKet = OperatorKetQuantumObject() + +@doc raw""" + size(A::AbstractQuantumObject) + size(A::AbstractQuantumObject, idx::Int) + +Returns a tuple containing each dimensions of the array in the [`AbstractQuantumObject`](@ref). + +Optionally, you can specify an index (`idx`) to just get the corresponding dimension of the array. +""" +Base.size(A::AbstractQuantumObject) = size(A.data) +Base.size(A::AbstractQuantumObject, idx::Int) = size(A.data, idx) + +Base.getindex(A::AbstractQuantumObject, inds...) = getindex(A.data, inds...) +Base.setindex!(A::AbstractQuantumObject, val, inds...) = setindex!(A.data, val, inds...) + +@doc raw""" + eltype(A::AbstractQuantumObject) + +Returns the elements type of the matrix or vector corresponding to the [`AbstractQuantumObject`](@ref) `A`. +""" +Base.eltype(A::AbstractQuantumObject) = eltype(A.data) + +@doc raw""" + length(A::AbstractQuantumObject) + +Returns the length of the matrix or vector corresponding to the [`AbstractQuantumObject`](@ref) `A`. +""" +Base.length(A::AbstractQuantumObject) = length(A.data) + +Base.isequal(A::AbstractQuantumObject, B::AbstractQuantumObject) = + isequal(A.type, B.type) && isequal(A.dims, B.dims) && isequal(A.data, B.data) +Base.isapprox(A::AbstractQuantumObject, B::AbstractQuantumObject; kwargs...) = + isequal(A.type, B.type) && isequal(A.dims, B.dims) && isapprox(A.data, B.data; kwargs...) +Base.:(==)(A::AbstractQuantumObject, B::AbstractQuantumObject) = + (A.type == B.type) && (A.dims == B.dims) && (A.data == B.data) + +function check_dims(A::AbstractQuantumObject, B::AbstractQuantumObject) + A.dims != B.dims && throw(DimensionMismatch("The two quantum objects don't have the same Hilbert dimension.")) + return nothing +end + +function _check_dims(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Integer,N} + _non_static_array_warning("dims", dims) + return (all(>(0), dims) && length(dims) > 0) || + throw(DomainError(dims, "The argument dims must be of non-zero length and contain only positive integers.")) +end +_check_dims(dims::Any) = throw( + ArgumentError( + "The argument dims must be a Tuple or a StaticVector of non-zero length and contain only positive integers.", + ), +) + +function _check_QuantumObject(type::KetQuantumObject, dims, m::Int, n::Int) + (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Ket")) + (prod(dims) != m) && throw(DimensionMismatch("Ket with dims = $(dims) does not fit the array size = $((m, n)).")) + return nothing +end + +function _check_QuantumObject(type::BraQuantumObject, dims, m::Int, n::Int) + (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Bra")) + (prod(dims) != n) && throw(DimensionMismatch("Bra with dims = $(dims) does not fit the array size = $((m, n)).")) + return nothing +end + +function _check_QuantumObject(type::OperatorQuantumObject, dims, m::Int, n::Int) + (m != n) && throw(DomainError((m, n), "The size of the array is not compatible with Operator")) + (prod(dims) != m) && + throw(DimensionMismatch("Operator with dims = $(dims) does not fit the array size = $((m, n)).")) + return nothing +end + +function _check_QuantumObject(type::SuperOperatorQuantumObject, dims, m::Int, n::Int) + (m != n) && throw(DomainError((m, n), "The size of the array is not compatible with SuperOperator")) + (prod(dims) != sqrt(m)) && + throw(DimensionMismatch("SuperOperator with dims = $(dims) does not fit the array size = $((m, n)).")) + return nothing +end + +function _check_QuantumObject(type::OperatorKetQuantumObject, dims, m::Int, n::Int) + (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorKet")) + (prod(dims) != sqrt(m)) && + throw(DimensionMismatch("OperatorKet with dims = $(dims) does not fit the array size = $((m, n)).")) + return nothing +end + +function _check_QuantumObject(type::OperatorBraQuantumObject, dims, m::Int, n::Int) + (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorBra")) + (prod(dims) != sqrt(n)) && + throw(DimensionMismatch("OperatorBra with dims = $(dims) does not fit the array size = $((m, n)).")) + return nothing +end + +get_typename_wrapper(A::AbstractQuantumObject) = Base.typename(typeof(A)).wrapper + +# functions for getting Float or Complex element type +_FType(A::AbstractQuantumObject) = _FType(eltype(A)) +_CType(A::AbstractQuantumObject) = _CType(eltype(A)) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl new file mode 100644 index 000000000..9d25858e0 --- /dev/null +++ b/src/qobj/quantum_object_evo.jl @@ -0,0 +1,418 @@ +export QuantumObjectEvolution + +@doc raw""" + struct QuantumObjectEvolution{DT<:AbstractSciMLOperator,ObjType<:QuantumObjectType,N} <: AbstractQuantumObject + data::DT + type::ObjType + dims::SVector{N,Int} + end + +Julia struct representing any time-dependent quantum object. The `data` field is a `AbstractSciMLOperator` object that represents the time-dependent quantum object. It can be seen as + +```math +\hat{O}(t) = \sum_{i} c_i(p, t) \hat{O}_i +``` + +where ``c_i(p, t)`` is a function that depends on the parameters `p` and time `t`, and ``\hat{O}_i`` are the operators that form the quantum object. The `type` field is the type of the quantum object, and the `dims` field is the dimensions of the quantum object. For more information about `type` and `dims`, see [`QuantumObject`](@ref). For more information about `AbstractSciMLOperator`, see the [SciML](https://docs.sciml.ai/SciMLOperators/stable/) documentation. + +# Examples +This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example +``` +julia> a = tensor(destroy(10), qeye(2)) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: +⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ + +julia> σm = tensor(qeye(10), sigmam()) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: +⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡀⠀⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡀⠀⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡀⎦ + +julia> coef1(p, t) = exp(-1im * t) +coef1 (generic function with 1 method) + +julia> coef2(p, t) = sin(t) +coef2 (generic function with 1 method) + +julia> op1 = QuantumObjectEvolution(((a, coef1), (σm, coef2))) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) +``` + +We can also concretize the operator at a specific time `t` +``` +julia> op1(0.1) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: +⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ +``` + +It also supports parameter-dependent time evolution +``` +julia> coef1(p, t) = exp(-1im * p.ω1 * t) +coef1 (generic function with 1 method) + +julia> coef2(p, t) = sin(p.ω2 * t) +coef2 (generic function with 1 method) + +julia> op1 = QuantumObjectEvolution(((a, coef1), (σm, coef2))) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) + +julia> p = (ω1 = 1.0, ω2 = 0.5) +(ω1 = 1.0, ω2 = 0.5) + +julia> op1(p, 0.1) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: +⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ +``` +""" +struct QuantumObjectEvolution{ + DT<:AbstractSciMLOperator, + ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + N, +} <: AbstractQuantumObject{DT,ObjType,N} + data::DT + type::ObjType + dims::SVector{N,Int} + + function QuantumObjectEvolution( + data::DT, + type::ObjType, + dims, + ) where {DT<:AbstractSciMLOperator,ObjType<:QuantumObjectType} + (type == Operator || type == SuperOperator) || + throw(ArgumentError("The type $type is not supported for QuantumObjectEvolution.")) + + _check_dims(dims) + + _size = _get_size(data) + _check_QuantumObject(type, dims, _size[1], _size[2]) + + N = length(dims) + + return new{DT,ObjType,N}(data, type, SVector{N,Int}(dims)) + end +end + +function QuantumObjectEvolution(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) + return QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) +end + +""" + QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) + +Generate a [`QuantumObjectEvolution`](@ref) object from a [`SciMLOperator`](https://github.com/SciML/SciMLOperators.jl), in the same way as [`QuantumObject`](@ref) for `AbstractArray` inputs. +""" +function QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) + _size = _get_size(data) + + if dims isa Nothing + if type == Operator + dims = SVector{1,Int}(_size[2]) + elseif type == SuperOperator + dims = SVector{1,Int}(isqrt(_size[2])) + end + end + + return QuantumObjectEvolution(data, type, dims) +end + +# Make the QuantumObjectEvolution, with the option to pre-multiply by a scalar +function QuantumObjectEvolution( + op_func_list::Tuple, + α::Union{Nothing,Number} = nothing; + type::Union{Nothing,QuantumObjectType} = nothing, + f::Function = identity, +) + op, data = _QobjEvo_generate_data(op_func_list, α; f = f) + dims = op.dims + if type isa Nothing + type = op.type + end + + # Preallocate the SciMLOperator cache using a dense vector as a reference + v0 = sparse_to_dense(similar(op.data, size(op, 1))) + data = cache_operator(data, v0) + + return QuantumObjectEvolution(data, type, dims) +end + +function QuantumObjectEvolution( + op::QuantumObject, + α::Union{Nothing,Number} = nothing; + type::Union{Nothing,QuantumObjectType} = nothing, + f::Function = identity, +) + if type isa Nothing + type = op.type + end + return QuantumObjectEvolution(_make_SciMLOperator(op, α, f = f), type, op.dims) +end + +function QuantumObjectEvolution( + op::QuantumObjectEvolution, + α::Union{Nothing,Number} = nothing; + type::Union{Nothing,QuantumObjectType} = nothing, + f::Function = identity, +) + f !== identity && throw(ArgumentError("The function `f` is not supported for QuantumObjectEvolution inputs.")) + if type isa Nothing + type = op.type + else + throw( + ArgumentError( + "The type of the QuantumObjectEvolution object cannot be changed when using another QuantumObjectEvolution object as input.", + ), + ) + end + if α isa Nothing + return QuantumObjectEvolution(op.data, type, op.dims) + end + return QuantumObjectEvolution(α * op.data, type, op.dims) +end + +#= + _QobjEvo_generate_data(op_func_list::Tuple, α; f::Function=identity) + +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. + +# Arguments +- `op_func_list::Tuple`: A tuple of tuples or operators. +- `α`: A scalar to pre-multiply the operators. +- `f::Function=identity`: A function to pre-apply to the operators. +=# +@generated function _QobjEvo_generate_data(op_func_list::Tuple, α; f::Function = identity) + op_func_list_types = op_func_list.parameters + N = length(op_func_list_types) + + dims_expr = () + first_op = nothing + data_expr = :(0) + qobj_expr_const = :(0) + + for i in 1:N + op_func_type = op_func_list_types[i] + if op_func_type <: Tuple + length(op_func_type.parameters) == 2 || throw(ArgumentError("The tuple must have two elements.")) + op_type = op_func_type.parameters[1] + func_type = op_func_type.parameters[2] + ((isoper(op_type) || issuper(op_type)) && func_type <: Function) || throw( + ArgumentError( + "The first element must be a Operator or SuperOperator, and the second element must be a function.", + ), + ) + + op = :(op_func_list[$i][1]) + data_type = op_type.parameters[1] + dims_expr = (dims_expr..., :($op.dims)) + if i == 1 + first_op = :(f($op)) + end + data_expr = :($data_expr + _make_SciMLOperator(op_func_list[$i], α, f = f)) + else + op_type = op_func_type + (isoper(op_type) || issuper(op_type)) || + throw(ArgumentError("The element must be a Operator or SuperOperator.")) + + data_type = op_type.parameters[1] + dims_expr = (dims_expr..., :(op_func_list[$i].dims)) + if i == 1 + first_op = :(op_func_list[$i]) + end + qobj_expr_const = :($qobj_expr_const + f(op_func_list[$i])) + end + end + + quote + dims = tuple($(dims_expr...)) + + length(unique(dims)) == 1 || throw(ArgumentError("The dimensions of the operators must be the same.")) + + data_expr_const = $qobj_expr_const isa Integer ? $qobj_expr_const : _make_SciMLOperator($qobj_expr_const, α) + + data_expr = data_expr_const + $data_expr + + return $first_op, data_expr + end +end + +function _make_SciMLOperator(op_func::Tuple, α; f::Function = identity) + T = eltype(op_func[1]) + update_func = (a, u, p, t) -> op_func[2](p, t) + if α isa Nothing + return ScalarOperator(zero(T), update_func) * MatrixOperator(f(op_func[1]).data) + end + return ScalarOperator(zero(T), update_func) * MatrixOperator(α * f(op_func[1]).data) +end + +function _make_SciMLOperator(op::QuantumObject, α; f::Function = identity) + if α isa Nothing + return MatrixOperator(f(op).data) + end + return MatrixOperator(α * f(op.data)) +end + +@doc raw""" + (A::QuantumObjectEvolution)(ψout, ψin, p, t) + +Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the input state `ψin` at time `t` with parameters `p`. The output state is stored in `ψout`. This function mimics the behavior of a `AbstractSciMLOperator` object. + +# Arguments +- `ψout::QuantumObject`: The output state. It must have the same type as `ψin`. +- `ψin::QuantumObject`: The input state. It must be either a [`KetQuantumObject`](@ref) or a [`OperatorKetQuantumObject`](@ref). +- `p`: The parameters of the time-dependent coefficients. +- `t`: The time at which the coefficients are evaluated. + +# Returns +- `ψout::QuantumObject`: The output state. + +# Examples +``` +julia> a = destroy(20) +Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: +⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⎦ + +julia> coef1(p, t) = sin(t) +coef1 (generic function with 1 method) + +julia> coef2(p, t) = cos(t) +coef2 (generic function with 1 method) + +julia> A = QobjEvo(((a, coef1), (a', coef2))) +Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=true +(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) + +julia> ψ1 = fock(20, 3) +Quantum Object: type=Ket dims=[20] size=(20,) +20-element Vector{ComplexF64}: + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 1.0 + 0.0im + 0.0 + 0.0im + ⋮ + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + +julia> ψ2 = zero_ket(20) +Quantum Object: type=Ket dims=[20] size=(20,) +20-element Vector{ComplexF64}: + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + ⋮ + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + +julia> A(ψ2, ψ1, nothing, 0.1) +20-element Vector{ComplexF64}: + 0.0 + 0.0im + 0.0 + 0.0im + 0.1729165499254989 + 0.0im + 0.0 + 0.0im + 1.9900083305560516 + 0.0im + ⋮ + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im +``` +""" +function (A::QuantumObjectEvolution)( + ψout::QuantumObject{DT1,QobjType}, + ψin::QuantumObject{DT2,QobjType}, + p, + t, +) where {DT1,DT2,QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} + check_dims(ψout, ψin) + check_dims(ψout, A) + + if isoper(A) && isoperket(ψin) + throw( + ArgumentError( + "The input state must be a KetQuantumObject if the QuantumObjectEvolution object is an Operator.", + ), + ) + elseif issuper(A) && isket(ψin) + throw( + ArgumentError( + "The input state must be an OperatorKetQuantumObject if the QuantumObjectEvolution object is a SuperOperator.", + ), + ) + end + + A.data(ψout.data, ψin.data, p, t) + + return ψout +end + +@doc raw""" + (A::QuantumObjectEvolution)(ψ, p, t) + +Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the input state `ψ` at time `t` with parameters `p`. Out-of-place version of [`(A::QuantumObjectEvolution)(ψout, ψin, p, t)`](@ref). The output state is stored in a new [`QuantumObject`](@ref) object. This function mimics the behavior of a `AbstractSciMLOperator` object. +""" +function (A::QuantumObjectEvolution)( + ψ::QuantumObject{DT,QobjType}, + p, + t, +) where {DT,QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} + ψout = QuantumObject(similar(ψ.data), ψ.type, ψ.dims) + return A(ψout, ψ, p, t) +end + +@doc raw""" + (A::QuantumObjectEvolution)(p, t) + +Calculate the time-dependent [`QuantumObjectEvolution`](@ref) object `A` at time `t` with parameters `p`. + +# Arguments +- `p`: The parameters of the time-dependent coefficients. +- `t`: The time at which the coefficients are evaluated. + +# Returns +- `A::QuantumObject`: The output state. +""" +function (A::QuantumObjectEvolution)(p, t) + # We put 0 in the place of `u` because the time-dependence doesn't depend on the state + update_coefficients!(A.data, 0, p, t) + return QuantumObject(concretize(A.data), A.type, A.dims) +end + +(A::QuantumObjectEvolution)(t) = A(nothing, t) + +#= +`promote_type` should be applied on types. Here I define `promote_op_type` because it is applied to operators. +=# +promote_op_type(A::QuantumObjectEvolution, B::QuantumObjectEvolution) = get_typename_wrapper(A) +promote_op_type(A::QuantumObjectEvolution, B::QuantumObject) = get_typename_wrapper(A) +promote_op_type(A::QuantumObject, B::QuantumObjectEvolution) = get_typename_wrapper(B) +promote_op_type(A::QuantumObject, B::QuantumObject) = get_typename_wrapper(A) diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index b95c2ee2a..08f3672a6 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -69,7 +69,7 @@ function sprepost( A::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, B::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, ) where {T1,T2} - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) + check_dims(A, B) return QuantumObject(_sprepost(A.data, B.data), SuperOperator, A.dims) end @@ -89,13 +89,48 @@ the same function is applied multiple times with a known Hilbert space dimension See also [`spre`](@ref) and [`spost`](@ref). """ -function lindblad_dissipator( - O::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, - Id_cache = I(size(O, 1)), -) where {T} +function lindblad_dissipator(O::QuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(O, 1))) where {DT} Od_O = O' * O return sprepost(O, O') - spre(Od_O, Id_cache) / 2 - spost(Od_O, Id_cache) / 2 end # It is already a SuperOperator -lindblad_dissipator(O::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, Id_cache) where {T} = O +lindblad_dissipator(O::QuantumObject{DT,SuperOperatorQuantumObject}, Id_cache = nothing) where {DT} = O + +@doc raw""" + liouvillian(H::QuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims))) + +Construct the Liouvillian [`SuperOperator`](@ref) for a system Hamiltonian ``\hat{H}`` and a set of collapse operators ``\{\hat{C}_n\}_n``: + +```math +\mathcal{L} [\cdot] = -i[\hat{H}, \cdot] + \sum_n \mathcal{D}(\hat{C}_n) [\cdot] +``` + +where + +```math +\mathcal{D}(\hat{C}_n) [\cdot] = \hat{C}_n [\cdot] \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n [\cdot] - \frac{1}{2} [\cdot] \hat{C}_n^\dagger \hat{C}_n +``` + +The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. + +See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref). +""" +function liouvillian( + H::QuantumObject{MT1,OpType1}, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + Id_cache = I(prod(H.dims)), +) where {MT1<:AbstractMatrix,OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + L = liouvillian(H, Id_cache) + if !(c_ops isa Nothing) + for c_op in c_ops + L += lindblad_dissipator(c_op, Id_cache) + end + end + return L +end + +liouvillian(H::QuantumObject{<:AbstractMatrix,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dims))) = + -1im * (spre(H, Id_cache) - spost(H, Id_cache)) + +liouvillian(H::QuantumObject{<:AbstractMatrix,SuperOperatorQuantumObject}, Id_cache::Diagonal) = H diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 17f59e997..8155ba3fa 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -2,7 +2,7 @@ Synonyms of the functions for QuantumObject =# -export Qobj, shape, isherm +export Qobj, QobjEvo, shape, isherm export trans, dag, matrix_element, unit export sqrtm, logm, expm, sinm, cosm export tensor, ⊗ @@ -13,47 +13,148 @@ export qeye Generate [`QuantumObject`](@ref) -Note that this functions is same as `QuantumObject(A; type=type, dims=dims)` +Note that this functions is same as `QuantumObject(A; type=type, dims=dims)`. """ Qobj(A; kwargs...) = QuantumObject(A; kwargs...) @doc raw""" - shape(A::QuantumObject) + QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing, f::Function=identity) -Returns a tuple containing each dimensions of the array in the [`QuantumObject`](@ref). +Generate [`QuantumObjectEvolution`](@ref). -Note that this function is same as `size(A)` +Note that this functions is same as `QuantumObjectEvolution(op_func_list)`. 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. + +# Arguments +- `op_func_list::Union{Tuple,AbstractQuantumObject}`: A tuple of tuples or operators. +- `α::Union{Nothing,Number}=nothing`: A scalar to pre-multiply the operators. +- `f::Function=identity`: A function to pre-apply to the operators. + +!!! warning "Beware of type-stability!" + 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. + +# Examples +This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example +``` +julia> a = tensor(destroy(10), qeye(2)) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: +⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ + +julia> σm = tensor(qeye(10), sigmam()) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: +⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡀⠀⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡀⠀⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡀⎦ + +julia> coef1(p, t) = exp(-1im * t) +coef1 (generic function with 1 method) + +julia> coef2(p, t) = sin(t) +coef2 (generic function with 1 method) + +julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) +``` + +We can also concretize the operator at a specific time `t` +``` +julia> op1(0.1) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: +⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ +``` + +It also supports parameter-dependent time evolution +``` +julia> coef1(p, t) = exp(-1im * p.ω1 * t) +coef1 (generic function with 1 method) + +julia> coef2(p, t) = sin(p.ω2 * t) +coef2 (generic function with 1 method) + +julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) + +julia> p = (ω1 = 1.0, ω2 = 0.5) +(ω1 = 1.0, ω2 = 0.5) + +julia> op1(p, 0.1) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: +⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ +``` +""" +QobjEvo( + op_func_list::Union{Tuple,AbstractQuantumObject}, + α::Union{Nothing,Number} = nothing; + type::Union{Nothing,QuantumObjectType} = nothing, + f::Function = identity, +) = QuantumObjectEvolution(op_func_list, α; type = type, f = f) + +QobjEvo(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) = + QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) + +""" + QobjEvo(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) + +Synonym of [`QuantumObjectEvolution`](@ref) object from a [`SciMLOperator`](https://github.com/SciML/SciMLOperators.jl). See the documentation for [`QuantumObjectEvolution`](@ref) for more information. +""" +QobjEvo(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) = + QuantumObjectEvolution(data; type = type, dims = dims) + +@doc raw""" + shape(A::AbstractQuantumObject) + +Returns a tuple containing each dimensions of the array in the [`AbstractQuantumObject`](@ref). + +Note that this function is same as `size(A)`. """ -shape(A::QuantumObject{<:AbstractArray{T}}) where {T} = size(A.data) +shape(A::AbstractQuantumObject) = size(A.data) @doc raw""" - isherm(A::QuantumObject) + isherm(A::AbstractQuantumObject) -Test whether the [`QuantumObject`](@ref) is Hermitian. +Test whether the [`AbstractQuantumObject`](@ref) is Hermitian. -Note that this functions is same as `ishermitian(A)` +Note that this functions is same as `ishermitian(A)`. """ -isherm(A::QuantumObject{<:AbstractArray{T}}) where {T} = ishermitian(A) +isherm(A::AbstractQuantumObject) = ishermitian(A) @doc raw""" - trans(A::QuantumObject) + trans(A::AbstractQuantumObject) -Lazy matrix transpose of the [`QuantumObject`](@ref). +Lazy matrix transpose of the [`AbstractQuantumObject`](@ref). -Note that this function is same as `transpose(A)` +Note that this function is same as `transpose(A)`. """ -trans( - A::QuantumObject{<:AbstractArray{T},OpType}, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = transpose(A) +trans(A::AbstractQuantumObject{DT,OpType}) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + transpose(A) @doc raw""" - dag(A::QuantumObject) + dag(A::AbstractQuantumObject) -Lazy adjoint (conjugate transposition) of the [`QuantumObject`](@ref) +Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) -Note that this function is same as `adjoint(A)` +Note that this function is same as `adjoint(A)`. """ -dag(A::QuantumObject{<:AbstractArray{T}}) where {T} = adjoint(A) +dag(A::AbstractQuantumObject) = adjoint(A) @doc raw""" matrix_element(i::QuantumObject, A::QuantumObject j::QuantumObject) @@ -110,7 +211,7 @@ sqrtm(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = sq Matrix logarithm of [`QuantumObject`](@ref) -Note that this function is same as `log(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) +Note that this function is same as `log(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ logm( A::QuantumObject{<:AbstractMatrix{T},ObjType}, @@ -121,7 +222,7 @@ logm( Matrix exponential of [`QuantumObject`](@ref) -Note that this function is same as `exp(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) +Note that this function is same as `exp(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ expm( A::QuantumObject{<:AbstractMatrix{T},ObjType}, @@ -134,7 +235,7 @@ Matrix sine of [`QuantumObject`](@ref), defined as ``\sin \left( \hat{A} \right) = \frac{e^{i \hat{A}} - e^{-i \hat{A}}}{2 i}`` -Note that this function is same as `sin(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) +Note that this function is same as `sin(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ sinm( A::QuantumObject{<:AbstractMatrix{T},ObjType}, @@ -147,7 +248,7 @@ Matrix cosine of [`QuantumObject`](@ref), defined as ``\cos \left( \hat{A} \right) = \frac{e^{i \hat{A}} + e^{-i \hat{A}}}{2}`` -Note that this function is same as `cos(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) +Note that this function is same as `cos(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ cosm( A::QuantumObject{<:AbstractMatrix{T},ObjType}, @@ -158,7 +259,7 @@ cosm( Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ``\hat{A} \otimes \hat{B} \otimes \cdots``. -Note that this function is same as `kron(A, B, ...)` +Note that this function is same as `kron(A, B, ...)`. # Examples @@ -191,7 +292,7 @@ tensor(A...) = kron(A...) Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ``\hat{A} \otimes \hat{B}``. -Note that this function is same as `kron(A, B)` +Note that this function is same as `kron(A, B)`. # Examples @@ -240,7 +341,7 @@ Identity operator ``\hat{\mathbb{1}}`` with size `N`. It is also possible to specify the list of Hilbert dimensions `dims` if different subsystems are present. -Note that this function is same as `eye(N, type=type, dims=dims)`, and `type` can only be either [`Operator`](@ref) or [`SuperOperator`](@ref) +Note that this function is same as `eye(N, type=type, dims=dims)`, and `type` can only be either [`Operator`](@ref) or [`SuperOperator`](@ref). """ qeye( N::Int; diff --git a/src/steadystate.jl b/src/steadystate.jl index 59e6f6ad9..097611d9a 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -65,26 +65,26 @@ end @doc raw""" steadystate( - H::QuantumObject, + H::QuantumObject{DT,OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateSolver = SteadyStateDirectSolver(), - kwargs... + kwargs..., ) Solve the stationary state based on different solvers. # Parameters -- `H::QuantumObject`: The Hamiltonian or the Liouvillian of the system. -- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators. -- `solver::SteadyStateSolver=SteadyStateDirectSolver()`: see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for different solvers. -- `kwargs...`: The keyword arguments for the solver. +- `H`: The Hamiltonian or the Liouvillian of the system. +- `c_ops`: The list of the collapse operators. +- `solver`: see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for different solvers. +- `kwargs`: The keyword arguments for the solver. """ function steadystate( - H::QuantumObject{<:AbstractArray,OpType}, + H::QuantumObject{DT,OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateSolver = SteadyStateDirectSolver(), kwargs..., -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, c_ops) return _steadystate(L, solver; kwargs...) @@ -179,20 +179,20 @@ _steadystate( kwargs..., ) where {T} = throw( ArgumentError( - "The initial state ψ0 is required for SteadyStateODESolver, use the following call instead: `steadystate(H, ψ0, tspan, c_ops)`.", + "The initial state ψ0 is required for SteadyStateODESolver, use the following call instead: `steadystate(H, ψ0, tmax, c_ops)`.", ), ) @doc raw""" steadystate( - H::QuantumObject, - ψ0::QuantumObject, - tspan::Real = Inf, + H::QuantumObject{DT1,HOpType}, + ψ0::QuantumObject{DT2,StateOpType}, + tmax::Real = Inf, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateODESolver = SteadyStateODESolver(), reltol::Real = 1.0e-8, abstol::Real = 1.0e-10, - kwargs... + kwargs..., ) Solve the stationary state based on time evolution (ordinary differential equations; `OrdinaryDiffEq.jl`) with a given initial state. @@ -210,50 +210,45 @@ or ``` # Parameters -- `H::QuantumObject`: The Hamiltonian or the Liouvillian of the system. -- `ψ0::QuantumObject`: The initial state of the system. -- `tspan::Real=Inf`: The final time step for the steady state problem. -- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators. -- `solver::SteadyStateODESolver=SteadyStateODESolver()`: see [`SteadyStateODESolver`](@ref) for more details. -- `reltol::Real=1.0e-8`: Relative tolerance in steady state terminate condition and solver adaptive timestepping. -- `abstol::Real=1.0e-10`: Absolute tolerance in steady state terminate condition and solver adaptive timestepping. -- `kwargs...`: The keyword arguments for the ODEProblem. +- `H`: The Hamiltonian or the Liouvillian of the system. +- `ψ0`: The initial state of the system. +- `tmax=Inf`: The final time step for the steady state problem. +- `c_ops=nothing`: The list of the collapse operators. +- `solver`: see [`SteadyStateODESolver`](@ref) for more details. +- `reltol=1.0e-8`: Relative tolerance in steady state terminate condition and solver adaptive timestepping. +- `abstol=1.0e-10`: Absolute tolerance in steady state terminate condition and solver adaptive timestepping. +- `kwargs`: The keyword arguments for the ODEProblem. """ function steadystate( - H::QuantumObject{MT1,HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, - tspan::Real = Inf, + H::QuantumObject{DT1,HOpType}, + ψ0::QuantumObject{DT2,StateOpType}, + tmax::Real = Inf, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateODESolver = SteadyStateODESolver(), reltol::Real = 1.0e-8, abstol::Real = 1.0e-10, kwargs..., ) where { - MT1<:AbstractMatrix, - T2, + DT1, + DT2, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } - (H.dims != ψ0.dims) && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) - - N = prod(H.dims) - u0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) - - L = MatrixOperator(liouvillian(H, c_ops).data) - ftype = _FType(ψ0) - prob = ODEProblem{true}(L, u0, (ftype(0), ftype(tspan))) # Convert tspan to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl - sol = solve( - prob, - solver.alg; - callback = TerminateSteadyState(abstol, reltol, _steadystate_ode_condition), - reltol = reltol, - abstol = abstol, - kwargs..., + cb = TerminateSteadyState(abstol, reltol, _steadystate_ode_condition) + sol = mesolve( + H, + ψ0, + [ftype(0), ftype(tmax)], + c_ops, + progress_bar = Val(false), + save_everystep = false, + saveat = ftype[], + callback = cb, ) - ρss = reshape(sol.u[end], N, N) - return QuantumObject(ρss, Operator, H.dims) + ρss = sol.states[end] + return ρss end function _steadystate_ode_condition(integrator, abstol, reltol, min_t) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index fb08a28d1..31d5b1ecb 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -81,7 +81,9 @@ function _mcsolve_prob_func(prob, i, repeat) ), ) - return remake(prob, p = prm) + f = deepcopy(prob.f.f) + + return remake(prob, f = f, p = prm) end # Standard output function @@ -117,20 +119,33 @@ function _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, j return jump_which[i] = sol_i.prob.p.jump_which end +function _mcsolve_make_Heff_QobjEvo(H::QuantumObject, c_ops) + c_ops isa Nothing && return QobjEvo(H) + return QobjEvo(H - 1im * mapreduce(op -> op' * op, +, c_ops) / 2) +end +function _mcsolve_make_Heff_QobjEvo(H::Tuple, c_ops) + c_ops isa Nothing && return QobjEvo(H) + return QobjEvo((H..., -1im * mapreduce(op -> op' * op, +, c_ops) / 2)) +end +function _mcsolve_make_Heff_QobjEvo(H::QuantumObjectEvolution, c_ops) + c_ops isa Nothing && return H + return H + QobjEvo(mapreduce(op -> op' * op, +, c_ops), -1im / 2) +end + @doc raw""" - mcsolveProblem(H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + mcsolveProblem( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - rng::AbstractRNG=default_rng(), - jump_callback::TJC=ContinuousLindbladJumpCallback(), - kwargs...) + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + jump_callback::TJC = ContinuousLindbladJumpCallback(), + kwargs..., + ) -Generates the ODEProblem for a single trajectory of the Monte Carlo wave function time evolution of an open quantum system. +Generate the ODEProblem for a single trajectory of the Monte Carlo wave function time evolution of an open quantum system. Given a system Hamiltonian ``\hat{H}`` and a list of collapse (jump) operators ``\{\hat{C}_n\}_n``, the evolution of the state ``|\psi(t)\rangle`` is governed by the Schrodinger equation: @@ -166,24 +181,21 @@ If the environmental measurements register a quantum jump, the wave function und # Arguments -- `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: List of times at which to save the state of the system. -- `c_ops::Union{Nothing,AbstractVector,Tuple}`: List of collapse operators ``\{\hat{C}_n\}_n``. -- `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. -- `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `rng::AbstractRNG`: Random number generator for reproducibility. -- `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. -- `kwargs...`: Additional keyword arguments to pass to the solver. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. -- For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) # Returns @@ -191,41 +203,37 @@ If the environmental measurements register a quantum jump, the wave function und - `prob::ODEProblem`: The ODEProblem for the Monte Carlo wave function time evolution. """ function mcsolveProblem( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray,KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), rng::AbstractRNG = default_rng(), jump_callback::TJC = ContinuousLindbladJumpCallback(), kwargs..., -) where {MT1<:AbstractMatrix,TJC<:LindbladJumpCallbackType} - H.dims != ψ0.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) - +) where {DT1,DT2,TJC<:LindbladJumpCallbackType} haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) c_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl - H_eff = H - 1im * mapreduce(op -> op' * op, +, c_ops) / 2 + H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, c_ops) if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(t_l)) + expvals = Array{ComplexF64}(undef, 0, length(tlist)) is_empty_e_ops_mc = true - e_ops2 = MT1[] + e_ops_data = () else - expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) - e_ops2 = get_data.(e_ops) + expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) + e_ops_data = get_data.(e_ops) is_empty_e_ops_mc = isempty(e_ops) end - saveat = is_empty_e_ops_mc ? t_l : [t_l[end]] + saveat = is_empty_e_ops_mc ? tlist : [tlist[end]] # We disable the progress bar of the sesolveProblem because we use a global progress bar for all the trajectories default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat, progress_bar = Val(false)) kwargs2 = merge(default_values, kwargs) @@ -243,9 +251,9 @@ function mcsolveProblem( params2 = ( expvals = expvals, - e_ops_mc = e_ops2, + e_ops_mc = e_ops_data, is_empty_e_ops_mc = is_empty_e_ops_mc, - progr_mc = ProgressBar(length(t_l), enable = false), + progr_mc = ProgressBar(length(tlist), enable = false), traj_rng = rng, c_ops = c_ops_data, c_ops_herm = c_ops_herm_data, @@ -259,39 +267,35 @@ function mcsolveProblem( params..., ) - return mcsolveProblem(H_eff, ψ0, t_l, alg, H_t, params2, jump_callback; kwargs2...) + return mcsolveProblem(H_eff_evo, ψ0, tlist, params2, jump_callback; kwargs2...) end function mcsolveProblem( - H_eff::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, - t_l::AbstractVector, - alg::OrdinaryDiffEqAlgorithm, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}, + H_eff_evo::QuantumObjectEvolution{DT1,OperatorQuantumObject}, + ψ0::QuantumObject{DT2,KetQuantumObject}, + tlist::AbstractVector, params::NamedTuple, jump_callback::DiscreteLindbladJumpCallback; kwargs..., -) where {T1,T2} +) where {DT1,DT2} cb1 = DiscreteCallback(LindbladJumpDiscreteCondition, LindbladJumpAffect!, save_positions = (false, false)) - cb2 = PresetTimeCallback(t_l, _save_func_mcsolve, save_positions = (false, false)) + cb2 = PresetTimeCallback(tlist, _save_func_mcsolve, save_positions = (false, false)) kwargs2 = (; kwargs...) kwargs2 = haskey(kwargs2, :callback) ? merge(kwargs2, (callback = CallbackSet(cb1, cb2, kwargs2.callback),)) : merge(kwargs2, (callback = CallbackSet(cb1, cb2),)) - return sesolveProblem(H_eff, ψ0, t_l; alg = alg, H_t = H_t, params = params, kwargs2...) + return sesolveProblem(H_eff_evo, ψ0, tlist; params = params, kwargs2...) end function mcsolveProblem( - H_eff::QuantumObject{<:AbstractArray,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray,KetQuantumObject}, - t_l::AbstractVector, - alg::OrdinaryDiffEqAlgorithm, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}, + H_eff_evo::QuantumObjectEvolution{DT1,OperatorQuantumObject}, + ψ0::QuantumObject{DT2,KetQuantumObject}, + tlist::AbstractVector, params::NamedTuple, jump_callback::ContinuousLindbladJumpCallback; kwargs..., -) +) where {DT1,DT2} cb1 = ContinuousCallback( LindbladJumpContinuousCondition, LindbladJumpAffect!, @@ -299,33 +303,34 @@ function mcsolveProblem( interp_points = jump_callback.interp_points, save_positions = (false, false), ) - cb2 = PresetTimeCallback(t_l, _save_func_mcsolve, save_positions = (false, false)) + cb2 = PresetTimeCallback(tlist, _save_func_mcsolve, save_positions = (false, false)) kwargs2 = (; kwargs...) kwargs2 = haskey(kwargs2, :callback) ? merge(kwargs2, (callback = CallbackSet(cb1, cb2, kwargs2.callback),)) : merge(kwargs2, (callback = CallbackSet(cb1, cb2),)) - return sesolveProblem(H_eff, ψ0, t_l; alg = alg, H_t = H_t, params = params, kwargs2...) + return sesolveProblem(H_eff_evo, ψ0, tlist; params = params, kwargs2...) end @doc raw""" - mcsolveEnsembleProblem(H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + mcsolveEnsembleProblem( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - ntraj::Int=1, - ensemble_method=EnsembleThreads(), - jump_callback::TJC=ContinuousLindbladJumpCallback(), - prob_func::Function=_mcsolve_prob_func, - output_func::Function=_mcsolve_output_func, - progress_bar::Union{Val,Bool}=Val(true), - kwargs...) - -Generates the `EnsembleProblem` of `ODEProblem`s for the ensemble of trajectories of the Monte Carlo wave function time evolution of an open quantum system. + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + jump_callback::TJC = ContinuousLindbladJumpCallback(), + prob_func::Function = _mcsolve_prob_func, + output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) + +Generate the `EnsembleProblem` of `ODEProblem`s for the ensemble of trajectories of the Monte Carlo wave function time evolution of an open quantum system. Given a system Hamiltonian ``\hat{H}`` and a list of collapse (jump) operators ``\{\hat{C}_n\}_n``, the evolution of the state ``|\psi(t)\rangle`` is governed by the Schrodinger equation: @@ -361,29 +366,26 @@ If the environmental measurements register a quantum jump, the wave function und # Arguments -- `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: List of times at which to save the state of the system. -- `c_ops::Union{Nothing,AbstractVector,Tuple}`: List of collapse operators ``\{\hat{C}_n\}_n``. -- `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. -- `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `rng::AbstractRNG`: Random number generator for reproducibility. -- `ntraj::Int`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. -- `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. -- `prob_func::Function`: Function to use for generating the ODEProblem. -- `output_func::Function`: Function to use for generating the output of a single trajectory. -- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. -- `kwargs...`: Additional keyword arguments to pass to the solver. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `ntraj`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: Function to use for generating the output of a single trajectory. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. -- For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) # Returns @@ -391,13 +393,11 @@ If the environmental measurements register a quantum jump, the wave function und - `prob::EnsembleProblem with ODEProblem`: The Ensemble ODEProblem for the Monte Carlo wave function time evolution. """ function mcsolveEnsembleProblem( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, @@ -407,7 +407,7 @@ function mcsolveEnsembleProblem( output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {MT1<:AbstractMatrix,T2,TJC<:LindbladJumpCallbackType} +) where {DT1,DT2,TJC<:LindbladJumpCallbackType} progr = ProgressBar(ntraj, enable = getVal(progress_bar)) if ensemble_method isa EnsembleDistributed progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) @@ -427,9 +427,7 @@ function mcsolveEnsembleProblem( ψ0, tlist, c_ops; - alg = alg, e_ops = e_ops, - H_t = H_t, params = merge(params, (global_rng = rng, seeds = seeds)), rng = rng, jump_callback = jump_callback, @@ -448,13 +446,13 @@ function mcsolveEnsembleProblem( end @doc raw""" - mcsolve(H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + mcsolve( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, @@ -502,22 +500,21 @@ If the environmental measurements register a quantum jump, the wave function und # Arguments -- `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: List of times at which to save the state of the system. -- `c_ops::Union{Nothing,AbstractVector,Tuple}`: List of collapse operators ``\{\hat{C}_n\}_n``. -- `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. -- `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `rng::AbstractRNG`: Random number generator for reproducibility. -- `ntraj::Int`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. -- `jump_callback::LindbladJumpCallbackType`: The Jump Callback type: Discrete or Continuous. -- `prob_func::Function`: Function to use for generating the ODEProblem. -- `output_func::Function`: Function to use for generating the output of a single trajectory. -- `kwargs...`: Additional keyword arguments to pass to the solver. -- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `alg`: The algorithm to use for the ODE solver. Default to `Tsit5()`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `ntraj`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: Function to use for generating the output of a single trajectory. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -530,16 +527,15 @@ If the environmental measurements register a quantum jump, the wave function und # Returns -- `sol::TimeEvolutionMCSol`: The solution of the time evolution. See also [`TimeEvolutionMCSol`](@ref) +- `sol::TimeEvolutionMCSol`: The solution of the time evolution. See also [`TimeEvolutionMCSol`](@ref). """ function mcsolve( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, @@ -549,7 +545,7 @@ function mcsolve( output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {MT1<:AbstractMatrix,T2,TJC<:LindbladJumpCallbackType} +) where {DT1,DT2,TJC<:LindbladJumpCallbackType} ens_prob_mc = mcsolveEnsembleProblem( H, ψ0, @@ -557,7 +553,6 @@ function mcsolve( c_ops; alg = alg, e_ops = e_ops, - H_t = H_t, params = params, rng = rng, ntraj = ntraj, diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 994bdb08c..21b8170ab 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -19,17 +19,10 @@ function _save_func_mesolve(integrator) return u_modified!(integrator, false) end -mesolve_ti_dudt!(du, u, p, t) = mul!(du, p.L, u) -function mesolve_td_dudt!(du, u, p, t) - mul!(du, p.L, u) - L_t = p.H_t(t, p) - return mul!(du, L_t, u, 1, 1) -end - _generate_mesolve_e_op(op) = mat2vec(adjoint(get_data(op))) -function _generate_mesolve_kwargs_with_callback(t_l, kwargs) - cb1 = PresetTimeCallback(t_l, _save_func_mesolve, save_positions = (false, false)) +function _generate_mesolve_kwargs_with_callback(tlist, kwargs) + cb1 = PresetTimeCallback(tlist, _save_func_mesolve, save_positions = (false, false)) kwargs2 = haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(kwargs.callback, cb1),)) : merge(kwargs, (callback = cb1,)) @@ -37,30 +30,42 @@ function _generate_mesolve_kwargs_with_callback(t_l, kwargs) return kwargs2 end -function _generate_mesolve_kwargs(e_ops, progress_bar::Val{true}, t_l, kwargs) - return _generate_mesolve_kwargs_with_callback(t_l, kwargs) +function _generate_mesolve_kwargs(e_ops, progress_bar::Val{true}, tlist, kwargs) + return _generate_mesolve_kwargs_with_callback(tlist, kwargs) end -function _generate_mesolve_kwargs(e_ops, progress_bar::Val{false}, t_l, kwargs) +function _generate_mesolve_kwargs(e_ops, progress_bar::Val{false}, tlist, kwargs) if e_ops isa Nothing return kwargs end - return _generate_mesolve_kwargs_with_callback(t_l, kwargs) + return _generate_mesolve_kwargs_with_callback(tlist, kwargs) +end + +_mesolve_make_L_QobjEvo(H::QuantumObject, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator) +function _mesolve_make_L_QobjEvo(H::Tuple, c_ops) + c_ops isa Nothing && return QobjEvo(H) + return QobjEvo((H..., mapreduce(op -> lindblad_dissipator(op), +, c_ops)); type = SuperOperator, f = liouvillian) +end +# TODO: Add support for Operator type QobEvo +function _mesolve_make_L_QobjEvo(H::QuantumObjectEvolution, c_ops) + issuper(H) || throw(ArgumentError("The time-dependent Hamiltonian must be a SuperOperator.")) + c_ops isa Nothing && return H + return H + QobjEvo((mapreduce(op -> lindblad_dissipator(op), +, c_ops))) end @doc raw""" - mesolveProblem(H::QuantumObject, - ψ0::QuantumObject, - tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - progress_bar::Union{Val,Bool}=Val(true), - kwargs...) - -Generates the ODEProblem for the master equation time evolution of an open quantum system: + mesolveProblem( + H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, + ψ0::QuantumObject{DT2,StateOpType}, + tlist, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) + +Generate the ODEProblem for the master equation time evolution of an open quantum system: ```math \frac{\partial \hat{\rho}(t)}{\partial t} = -i[\hat{H}, \hat{\rho}(t)] + \sum_n \mathcal{D}(\hat{C}_n) [\hat{\rho}(t)] @@ -74,23 +79,20 @@ where # Arguments -- `H::QuantumObject`: The Hamiltonian ``\hat{H}`` or the Liouvillian of the system. -- `ψ0::QuantumObject`: The initial state of the system. -- `tlist::AbstractVector`: The time list of the evolution. -- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators ``\{\hat{C}_n\}_n``. -- `alg::OrdinaryDiffEqAlgorithm=Tsit5()`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the operators for which the expectation values are calculated. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing`: The time-dependent Hamiltonian or Liouvillian. -- `params::NamedTuple=NamedTuple()`: The parameters of the time evolution. -- `progress_bar::Union{Val,Bool}=Val(true)`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. -- `kwargs...`: The keyword arguments for the ODEProblem. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. -- For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) # Returns @@ -98,84 +100,74 @@ where - `prob::ODEProblem`: The ODEProblem for the master equation time evolution. """ function mesolveProblem( - H::QuantumObject{MT1,HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, + ψ0::QuantumObject{DT2,StateOpType}, tlist, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where { - MT1<:AbstractMatrix, - T2, + DT1, + DT2, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } - H.dims != ψ0.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) - haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) - is_time_dependent = !(H_t isa Nothing) + tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl - ρ0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + L_evo = _mesolve_make_L_QobjEvo(H, c_ops) + check_dims(L_evo, ψ0) - t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + ρ0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + L = L_evo.data - L = liouvillian(H, c_ops).data - progr = ProgressBar(length(t_l), enable = getVal(progress_bar)) + progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(t_l)) - e_ops2 = mat2vec(MT1)[] + expvals = Array{ComplexF64}(undef, 0, length(tlist)) + e_ops_data = () is_empty_e_ops = true else - expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) - e_ops2 = [_generate_mesolve_e_op(op) for op in e_ops] + expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) + e_ops_data = [_generate_mesolve_e_op(op) for op in e_ops] is_empty_e_ops = isempty(e_ops) end - if H_t isa TimeDependentOperatorSum - H_t = liouvillian(H_t) - end - p = ( - L = L, - progr = progr, - Hdims = H.dims, - e_ops = e_ops2, + e_ops = e_ops_data, expvals = expvals, - H_t = H_t, - times = t_l, + progr = progr, + times = tlist, + Hdims = L_evo.dims, is_empty_e_ops = is_empty_e_ops, params..., ) - saveat = is_empty_e_ops ? t_l : [t_l[end]] + saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_mesolve_kwargs(e_ops, makeVal(progress_bar), t_l, kwargs2) - - dudt! = is_time_dependent ? mesolve_td_dudt! : mesolve_ti_dudt! + kwargs3 = _generate_mesolve_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2) - tspan = (t_l[1], t_l[end]) - return ODEProblem{true,FullSpecialize}(dudt!, ρ0, tspan, p; kwargs3...) + tspan = (tlist[1], tlist[end]) + return ODEProblem{true,FullSpecialize}(L, ρ0, tspan, p; kwargs3...) end @doc raw""" - mesolve(H::QuantumObject, - ψ0::QuantumObject, - tlist::AbstractVector, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - alg::OrdinaryDiffEqAlgorithm=Tsit5(), - e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - progress_bar::Union{Val,Bool}=Val(true), - kwargs...) + mesolve( + H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, + ψ0::QuantumObject{DT2,StateOpType}, + tlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + alg::OrdinaryDiffEqAlgorithm = Tsit5(), + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) Time evolution of an open quantum system using Lindblad master equation: @@ -191,16 +183,15 @@ where # Arguments -- `H::QuantumObject`: The Hamiltonian ``\hat{H}`` or the Liouvillian of the system. -- `ψ0::QuantumObject`: The initial state of the system. -- `tlist::AbstractVector`: The time list of the evolution. -- `c_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of the collapse operators ``\{\hat{C}_n\}_n``. -- `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. -- `params::NamedTuple`: Named Tuple of parameters to pass to the solver. -- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. -- `kwargs...`: Additional keyword arguments to pass to the solver. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `alg`: The algorithm for the ODE solver. The default value is `Tsit5()`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -215,19 +206,18 @@ where - `sol::TimeEvolutionSol`: The solution of the time evolution. See also [`TimeEvolutionSol`](@ref) """ function mesolve( - H::QuantumObject{MT1,HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, + ψ0::QuantumObject{DT2,StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) where { - MT1<:AbstractMatrix, - T2, + DT1, + DT2, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } @@ -238,7 +228,6 @@ function mesolve( c_ops; alg = alg, e_ops = e_ops, - H_t = H_t, params = params, progress_bar = progress_bar, kwargs..., diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index f2699776e..87d9535b1 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -16,15 +16,8 @@ function _save_func_sesolve(integrator) return u_modified!(integrator, false) end -sesolve_ti_dudt!(du, u, p, t) = mul!(du, p.U, u) -function sesolve_td_dudt!(du, u, p, t) - mul!(du, p.U, u) - H_t = p.H_t(t, p) - return mul!(du, H_t, u, -1im, 1) -end - -function _generate_sesolve_kwargs_with_callback(t_l, kwargs) - cb1 = PresetTimeCallback(t_l, _save_func_sesolve, save_positions = (false, false)) +function _generate_sesolve_kwargs_with_callback(tlist, kwargs) + cb1 = PresetTimeCallback(tlist, _save_func_sesolve, save_positions = (false, false)) kwargs2 = haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(kwargs.callback, cb1),)) : merge(kwargs, (callback = cb1,)) @@ -32,29 +25,29 @@ function _generate_sesolve_kwargs_with_callback(t_l, kwargs) return kwargs2 end -function _generate_sesolve_kwargs(e_ops, progress_bar::Val{true}, t_l, kwargs) - return _generate_sesolve_kwargs_with_callback(t_l, kwargs) +function _generate_sesolve_kwargs(e_ops, progress_bar::Val{true}, tlist, kwargs) + return _generate_sesolve_kwargs_with_callback(tlist, kwargs) end -function _generate_sesolve_kwargs(e_ops, progress_bar::Val{false}, t_l, kwargs) +function _generate_sesolve_kwargs(e_ops, progress_bar::Val{false}, tlist, kwargs) if e_ops isa Nothing return kwargs end - return _generate_sesolve_kwargs_with_callback(t_l, kwargs) + return _generate_sesolve_kwargs_with_callback(tlist, kwargs) end @doc raw""" - sesolveProblem(H::QuantumObject, - ψ0::QuantumObject, + sesolveProblem( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector; - alg::OrdinaryDiffEqAlgorithm=Tsit5() e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - progress_bar::Union{Val,Bool}=Val(true), - kwargs...) + params::NamedTuple = NamedTuple(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) -Generates the ODEProblem for the Schrödinger time evolution of a quantum system: +Generate the ODEProblem for the Schrödinger time evolution of a quantum system: ```math \frac{\partial}{\partial t} |\psi(t)\rangle = -i \hat{H} |\psi(t)\rangle @@ -62,22 +55,19 @@ Generates the ODEProblem for the Schrödinger time evolution of a quantum system # Arguments -- `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: The time list of the evolution. -- `alg::OrdinaryDiffEqAlgorithm`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}`: The list of operators to be evaluated during the evolution. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. -- `params::NamedTuple`: The parameters of the system. -- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. -- `kwargs...`: The keyword arguments passed to the `ODEProblem` constructor. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. -- For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) # Returns @@ -85,73 +75,68 @@ Generates the ODEProblem for the Schrödinger time evolution of a quantum system - `prob`: The `ODEProblem` for the Schrödinger time evolution of the system. """ function sesolveProblem( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractVector{T2},KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector; - alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {MT1<:AbstractMatrix,T2} - H.dims != ψ0.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) - +) where {DT1,DT2} haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) - is_time_dependent = !(H_t isa Nothing) + tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl - ϕ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) # Convert it to dense vector with complex element type + H_evo = QobjEvo(H, -1im) # pre-multiply by -i + isoper(H_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) + check_dims(H_evo, ψ0) - t_l = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl + ψ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) # Convert it to dense vector with complex element type + U = H_evo.data - U = -1im * get_data(H) - progr = ProgressBar(length(t_l), enable = getVal(progress_bar)) + progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(t_l)) - e_ops2 = MT1[] + expvals = Array{ComplexF64}(undef, 0, length(tlist)) + e_ops_data = () is_empty_e_ops = true else - expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) - e_ops2 = get_data.(e_ops) + expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) + e_ops_data = get_data.(e_ops) is_empty_e_ops = isempty(e_ops) end p = ( - U = U, - e_ops = e_ops2, + e_ops = e_ops_data, expvals = expvals, progr = progr, - Hdims = H.dims, - H_t = H_t, - times = t_l, + times = tlist, + Hdims = H_evo.dims, is_empty_e_ops = is_empty_e_ops, params..., ) - saveat = is_empty_e_ops ? t_l : [t_l[end]] + saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_sesolve_kwargs(e_ops, makeVal(progress_bar), t_l, kwargs2) - - dudt! = is_time_dependent ? sesolve_td_dudt! : sesolve_ti_dudt! + kwargs3 = _generate_sesolve_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2) - tspan = (t_l[1], t_l[end]) - return ODEProblem{true,FullSpecialize}(dudt!, ϕ0, tspan, p; kwargs3...) + tspan = (tlist[1], tlist[end]) + return ODEProblem{true,FullSpecialize}(U, ψ0, tspan, p; kwargs3...) end @doc raw""" - sesolve(H::QuantumObject, - ψ0::QuantumObject, + sesolve( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector; - alg::OrdinaryDiffEqAlgorithm=Tsit5(), + alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - progress_bar::Union{Val,Bool}=Val(true), - kwargs...) + params::NamedTuple = NamedTuple(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) Time evolution of a closed quantum system using the Schrödinger equation: @@ -161,15 +146,14 @@ Time evolution of a closed quantum system using the Schrödinger equation: # Arguments -- `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: List of times at which to save the state of the system. -- `alg::OrdinaryDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. -- `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `progress_bar::Union{Val,Bool}`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. -- `kwargs...`: Additional keyword arguments to pass to the solver. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `alg`: The algorithm for the ODE solver. The default is `Tsit5()`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -184,27 +168,16 @@ Time evolution of a closed quantum system using the Schrödinger equation: - `sol::TimeEvolutionSol`: The solution of the time evolution. See also [`TimeEvolutionSol`](@ref) """ function sesolve( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractVector{T2},KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {MT1<:AbstractMatrix,T2} - prob = sesolveProblem( - H, - ψ0, - tlist; - alg = alg, - e_ops = e_ops, - H_t = H_t, - params = params, - progress_bar = progress_bar, - kwargs..., - ) +) where {DT1,DT2} + prob = sesolveProblem(H, ψ0, tlist; e_ops = e_ops, params = params, progress_bar = progress_bar, kwargs...) return sesolve(prob, alg) end diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 6482f433c..a691ff0ef 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -1,32 +1,48 @@ export ssesolveProblem, ssesolveEnsembleProblem, ssesolve -#TODO: Check if works in GPU -function _ssesolve_update_coefficients!(ψ, coefficients, sc_ops) - _get_en = op -> real(dot(ψ, op, ψ)) #this is en/2: /2 = Re - @. coefficients[2:end-1] = _get_en(sc_ops) #coefficients of the OperatorSum: Σ Sn * en/2 - coefficients[end] = -sum(x -> x^2, coefficients[2:end-1]) / 2 #this last coefficient is -Σen^2/8 - return nothing +#= + struct DiffusionOperator + +A struct to represent the diffusion operator. This is used to perform the diffusion process on N different Wiener processes. +=# +struct DiffusionOperator{T,OT<:Tuple{Vararg{AbstractSciMLOperator}}} <: AbstractSciMLOperator{T} + ops::OT + function DiffusionOperator(ops::OT) where {OT} + T = mapreduce(eltype, promote_type, ops) + return new{T,OT}(ops) + end end -function ssesolve_drift!(du, u, p, t) - _ssesolve_update_coefficients!(u, p.K.coefficients, p.sc_ops) - - mul!(du, p.K, u) +@generated function update_coefficients!(L::DiffusionOperator, u, p, t) + ops_types = L.parameters[2].parameters + N = length(ops_types) + quote + Base.@nexprs $N i -> begin + update_coefficients!(L.ops[i], u, p, t) + end - return nothing + nothing + end end -function ssesolve_diffusion!(du, u, p, t) - @inbounds en = @view(p.K.coefficients[2:end-1]) - - # du:(H,W). du_reshaped:(H*W,). - # H:Hilbert space dimension, W: number of sc_ops - du_reshaped = reshape(du, :) - mul!(du_reshaped, p.D, u) #du[:,i] = D[i] * u - - du .-= u .* reshape(en, 1, :) #du[:,i] -= en[i] * u +@generated function LinearAlgebra.mul!(v::AbstractVecOrMat, L::DiffusionOperator, u::AbstractVecOrMat) + ops_types = L.parameters[2].parameters + N = length(ops_types) + quote + M = length(u) + S = size(v) + (S[1] == M && S[2] == $N) || throw(DimensionMismatch("The size of the output vector is incorrect.")) + Base.@nexprs $N i -> begin + mul!(@view(v[:, i]), L.ops[i], u) + end + v + end +end - return nothing +# TODO: Implement the three-argument dot function for SciMLOperators.jl +# Currently, we are assuming a time-independent MatrixOperator +function _ssesolve_update_coeff(u, p, t, op) + return real(dot(u, op.A, u)) #this is en/2: /2 = Re end function _ssesolve_prob_func(prob, i, repeat) @@ -37,25 +53,15 @@ function _ssesolve_prob_func(prob, i, repeat) traj_rng = typeof(global_rng)() seed!(traj_rng, seed) - noise = RealWienerProcess( + noise = RealWienerProcess!( prob.tspan[1], - zeros(length(internal_params.sc_ops)), - zeros(length(internal_params.sc_ops)), + zeros(internal_params.n_sc_ops), + zeros(internal_params.n_sc_ops), save_everystep = false, rng = traj_rng, ) - noise_rate_prototype = similar(prob.u0, length(prob.u0), length(internal_params.sc_ops)) - - prm = merge( - internal_params, - ( - expvals = similar(internal_params.expvals), - progr = ProgressBar(size(internal_params.expvals, 2), enable = false), - ), - ) - - return remake(prob, p = prm, noise = noise, noise_rate_prototype = noise_rate_prototype, seed = seed) + return remake(prob, noise = noise, seed = seed) end # Standard output function @@ -86,58 +92,62 @@ function _ssesolve_generate_statistics!(sol, i, states, expvals_all) return nothing end +_ScalarOperator_e(op, f = +) = ScalarOperator(one(eltype(op)), (a, u, p, t) -> f(_ssesolve_update_coeff(u, p, t, op))) + +_ScalarOperator_e2_2(op, f = +) = + ScalarOperator(one(eltype(op)), (a, u, p, t) -> f(_ssesolve_update_coeff(u, p, t, op)^2 / 2)) + @doc raw""" - ssesolveProblem(H::QuantumObject, - ψ0::QuantumObject, - tlist::AbstractVector; - sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - alg::StochasticDiffEqAlgorithm=SRA1() + ssesolveProblem( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, + tlist::AbstractVector, + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - rng::AbstractRNG=default_rng(), - kwargs...) + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) -Generates the SDEProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: +Generate the SDEProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: - ```math - d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) - ``` +```math +d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) +``` where ```math - K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), - ``` - ```math - M_n = C_n - \frac{e_n}{2}, - ``` - and - ```math - e_n = \langle C_n + C_n^\dagger \rangle. - ``` +K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), +``` +```math +M_n = C_n - \frac{e_n}{2}, +``` +and +```math +e_n = \langle C_n + C_n^\dagger \rangle. +``` -Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. +Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. # Arguments -- `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: The time list of the evolution. -- `sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. -- `alg::StochasticDiffEqAlgorithm`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. -- `params::NamedTuple`: The parameters of the system. -- `rng::AbstractRNG`: The random number generator for reproducibility. -- `kwargs...`: The keyword arguments passed to the `SDEProblem` constructor. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `sc_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. -- For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) # Returns @@ -145,146 +155,139 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `prob`: The `SDEProblem` for the Stochastic Schrödinger time evolution of the system. """ function ssesolveProblem( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::StochasticDiffEqAlgorithm = SRA1(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), rng::AbstractRNG = default_rng(), + progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {MT1<:AbstractMatrix,T2} - H.dims != ψ0.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension.")) - +) where {DT1,DT2} haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) sc_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - !(H_t isa Nothing) && throw(ArgumentError("Time-dependent Hamiltonians are not currently supported in ssesolve.")) + tlist = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for StochasticDiffEq.jl - t_l = convert(Vector{Float64}, tlist) # Convert it into Float64 to avoid type instabilities for StochasticDiffEq.jl + H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, sc_ops) + isoper(H_eff_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) + check_dims(H_eff_evo, ψ0) + dims = H_eff_evo.dims - ϕ0 = get_data(ψ0) + ψ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) - H_eff = get_data(H - T2(0.5im) * mapreduce(op -> op' * op, +, sc_ops)) - sc_ops2 = get_data.(sc_ops) - - coefficients = [1.0, fill(0.0, length(sc_ops) + 1)...] - operators = [-1im * H_eff, sc_ops2..., MT1(I(prod(H.dims)))] - K = OperatorSum(coefficients, operators) - _ssesolve_update_coefficients!(ϕ0, K.coefficients, sc_ops2) - - D = reduce(vcat, sc_ops2) + progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(t_l)) - e_ops2 = MT1[] + expvals = Array{ComplexF64}(undef, 0, length(tlist)) + e_ops_data = () is_empty_e_ops = true else - expvals = Array{ComplexF64}(undef, length(e_ops), length(t_l)) - e_ops2 = get_data.(e_ops) + expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) + e_ops_data = get_data.(e_ops) is_empty_e_ops = isempty(e_ops) end + sc_ops_evo_data = Tuple(map(get_data ∘ QobjEvo, sc_ops)) + + # Here the coefficients depend on the state, so this is a non-linear operator, which should be implemented with FunctionOperator instead. However, the nonlinearity is only on the coefficients, and it should be safe. + K_l = sum( + op -> _ScalarOperator_e(op, +) * op + _ScalarOperator_e2_2(op, -) * IdentityOperator(prod(dims)), + sc_ops_evo_data, + ) + + K = -1im * get_data(H_eff_evo) + K_l + + D_l = map(op -> op + _ScalarOperator_e(op, -) * IdentityOperator(prod(dims)), sc_ops_evo_data) + D = DiffusionOperator(D_l) + p = ( - K = K, - D = D, - e_ops = e_ops2, - sc_ops = sc_ops2, + e_ops = e_ops_data, expvals = expvals, - Hdims = H.dims, - H_t = H_t, - times = t_l, + progr = progr, + times = tlist, + Hdims = dims, is_empty_e_ops = is_empty_e_ops, + n_sc_ops = length(sc_ops), params..., ) - saveat = is_empty_e_ops ? t_l : [t_l[end]] + saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_sesolve_kwargs(e_ops, Val(false), t_l, kwargs2) - - tspan = (t_l[1], t_l[end]) - noise = RealWienerProcess(t_l[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) - noise_rate_prototype = similar(ϕ0, length(ϕ0), length(sc_ops)) - return SDEProblem{true}( - ssesolve_drift!, - ssesolve_diffusion!, - ϕ0, - tspan, - p; - noise_rate_prototype = noise_rate_prototype, - noise = noise, - kwargs3..., - ) + kwargs3 = _generate_sesolve_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2) + + tspan = (tlist[1], tlist[end]) + noise = + RealWienerProcess!(tlist[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) + noise_rate_prototype = similar(ψ0, length(ψ0), length(sc_ops)) + return SDEProblem{true}(K, D, ψ0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, kwargs3...) end @doc raw""" - ssesolveEnsembleProblem(H::QuantumObject, - ψ0::QuantumObject, - tlist::AbstractVector; + ssesolveEnsembleProblem( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, + tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::StochasticDiffEqAlgorithm=SRA1() e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - rng::AbstractRNG=default_rng(), - ntraj::Int=1, - ensemble_method=EnsembleThreads(), - prob_func::Function=_mcsolve_prob_func, - output_func::Function=_ssesolve_dispatch_output_func(ensemble_method), - progress_bar::Union{Val,Bool}=Val(true), - kwargs...) - -Generates the SDE EnsembleProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + prob_func::Function = _ssesolve_prob_func, + output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) + +Generate the SDE EnsembleProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: - ```math - d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) - ``` +```math +d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) +``` where ```math - K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), - ``` - ```math - M_n = C_n - \frac{e_n}{2}, - ``` - and - ```math - e_n = \langle C_n + C_n^\dagger \rangle. - ``` +K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), +``` +```math +M_n = C_n - \frac{e_n}{2}, +``` +and +```math +e_n = \langle C_n + C_n^\dagger \rangle. +``` Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. # Arguments -- `H::QuantumObject`: The Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: The initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: The time list of the evolution. -- `sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. -- `alg::StochasticDiffEqAlgorithm`: The algorithm used for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: The list of operators to be evaluated during the evolution. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: The time-dependent Hamiltonian of the system. If `nothing`, the Hamiltonian is time-independent. -- `params::NamedTuple`: The parameters of the system. -- `rng::AbstractRNG`: The random number generator for reproducibility. -- `ntraj::Int`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. -- `prob_func::Function`: Function to use for generating the SDEProblem. -- `output_func::Function`: Function to use for generating the output of a single trajectory. -- `progress_bar::Union{Val,Bool}`: Whether to show a progress bar. -- `kwargs...`: The keyword arguments passed to the `SDEProblem` constructor. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `sc_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `ntraj`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: Function to use for generating the output of a single trajectory. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. -- For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) # Returns @@ -292,13 +295,11 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `prob::EnsembleProblem with SDEProblem`: The Ensemble SDEProblem for the Stochastic Shrödinger time evolution. """ function ssesolveEnsembleProblem( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::StochasticDiffEqAlgorithm = SRA1(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, @@ -307,7 +308,7 @@ function ssesolveEnsembleProblem( output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {MT1<:AbstractMatrix,T2} +) where {DT1,DT2} progr = ProgressBar(ntraj, enable = getVal(progress_bar)) if ensemble_method isa EnsembleDistributed progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) @@ -327,14 +328,15 @@ function ssesolveEnsembleProblem( ψ0, tlist, sc_ops; - alg = alg, e_ops = e_ops, - H_t = H_t, params = merge(params, (global_rng = rng, seeds = seeds)), rng = rng, + progress_bar = Val(false), kwargs..., ) + # safetycopy is set to true because the K and D functions cannot be currently deepcopied. + # the memory overhead shouldn't be too large, compared to the safetycopy=false case. ensemble_prob = EnsembleProblem(prob_sse, prob_func = prob_func, output_func = output_func, safetycopy = true) return ensemble_prob @@ -347,67 +349,66 @@ function ssesolveEnsembleProblem( end @doc raw""" - ssesolve(H::QuantumObject, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + ssesolve( + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing, AbstractVector}=nothing; - alg::StochasticDiffEqAlgorithm=SRA1(), - e_ops::Union{Nothing,AbstractVector,Tuple}=nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, - params::NamedTuple=NamedTuple(), - rng::AbstractRNG=default_rng(), - ntraj::Int=1, - ensemble_method=EnsembleThreads(), - prob_func::Function=_ssesolve_prob_func, - output_func::Function=_ssesolve_dispatch_output_func(ensemble_method), + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + alg::StochasticDiffEqAlgorithm = SRA1(), + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + prob_func::Function = _ssesolve_prob_func, + output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), - kwargs...) + kwargs..., + ) Stochastic Schrödinger equation evolution of a quantum system given the system Hamiltonian ``\hat{H}`` and a list of stochadtic collapse (jump) operators ``\{\hat{C}_n\}_n``. The stochastic evolution of the state ``|\psi(t)\rangle`` is defined by: - ```math - d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) - ``` +```math +d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) +``` where ```math - K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), - ``` - ```math - M_n = C_n - \frac{e_n}{2}, - ``` - and - ```math - e_n = \langle C_n + C_n^\dagger \rangle. - ``` +K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), +``` +```math +M_n = C_n - \frac{e_n}{2}, +``` +and +```math +e_n = \langle C_n + C_n^\dagger \rangle. +``` -Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. +Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. # Arguments -- `H::QuantumObject`: Hamiltonian of the system ``\hat{H}``. -- `ψ0::QuantumObject`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist::AbstractVector`: List of times at which to save the state of the system. -- `sc_ops::Union{Nothing,AbstractVector,Tuple}=nothing`: List of stochastic collapse operators ``\{\hat{C}_n\}_n``. -- `alg::StochasticDiffEqAlgorithm`: Algorithm to use for the time evolution. -- `e_ops::Union{Nothing,AbstractVector,Tuple}`: List of operators for which to calculate expectation values. -- `H_t::Union{Nothing,Function,TimeDependentOperatorSum}`: Time-dependent part of the Hamiltonian. -- `params::NamedTuple`: Dictionary of parameters to pass to the solver. -- `rng::AbstractRNG`: Random number generator for reproducibility. -- `ntraj::Int`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. -- `prob_func::Function`: Function to use for generating the SDEProblem. -- `output_func::Function`: Function to use for generating the output of a single trajectory. -- `progress_bar::Union{Val,Bool}`: Whether to show a progress bar. -- `kwargs...`: Additional keyword arguments to pass to the solver. +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `sc_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `ntraj`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: Function to use for generating the output of a single trajectory. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. # Notes -- `ensemble_method` can be one of `EnsembleThreads()`, `EnsembleSerial()`, `EnsembleDistributed()` - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. @@ -416,16 +417,15 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i # Returns -- `sol::TimeEvolutionSSESol`: The solution of the time evolution. See also [`TimeEvolutionSSESol`](@ref) +- `sol::TimeEvolutionSSESol`: The solution of the time evolution. See also [`TimeEvolutionSSESol`](@ref). """ function ssesolve( - H::QuantumObject{MT1,OperatorQuantumObject}, - ψ0::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, + H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, @@ -434,21 +434,13 @@ function ssesolve( output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {MT1<:AbstractMatrix,T2} - progr = ProgressBar(ntraj, enable = getVal(progress_bar)) - progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) - @async while take!(progr_channel) - next!(progr) - end - +) where {DT1,DT2} ens_prob = ssesolveEnsembleProblem( H, ψ0, tlist, sc_ops; - alg = alg, e_ops = e_ops, - H_t = H_t, params = params, rng = rng, ntraj = ntraj, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 971d53c63..90284b50c 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -1,4 +1,3 @@ -export TimeDependentOperatorSum export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionSSESol export liouvillian, liouvillian_floquet, liouvillian_generalized @@ -21,14 +20,22 @@ A structure storing the results and some information from solving time evolution - `abstol::Real`: The absolute tolerance which is used during the solving process. - `reltol::Real`: The relative tolerance which is used during the solving process. """ -struct TimeEvolutionSol{TT<:Vector{<:Real},TS<:AbstractVector,TE<:Matrix{ComplexF64}} +struct TimeEvolutionSol{ + TT<:AbstractVector{<:Real}, + TS<:AbstractVector, + TE<:Matrix, + RETT<:Enum, + AlgT<:OrdinaryDiffEqAlgorithm, + AT<:Real, + RT<:Real, +} times::TT states::TS expect::TE - retcode::Enum - alg::OrdinaryDiffEqAlgorithm - abstol::Real - reltol::Real + retcode::RETT + alg::AlgT + abstol::AT + reltol::RT end function Base.show(io::IO, sol::TimeEvolutionSol) @@ -63,12 +70,15 @@ A structure storing the results and some information from solving quantum trajec - `reltol::Real`: The relative tolerance which is used during the solving process. """ struct TimeEvolutionMCSol{ - TT<:Vector{<:Real}, + TT<:AbstractVector{<:Real}, TS<:AbstractVector, TE<:Matrix{ComplexF64}, TEA<:Array{ComplexF64,3}, TJT<:Vector{<:Vector{<:Real}}, TJW<:Vector{<:Vector{<:Integer}}, + AlgT<:OrdinaryDiffEqAlgorithm, + AT<:Real, + RT<:Real, } ntraj::Int times::TT @@ -78,9 +88,9 @@ struct TimeEvolutionMCSol{ jump_times::TJT jump_which::TJW converged::Bool - alg::OrdinaryDiffEqAlgorithm - abstol::Real - reltol::Real + alg::AlgT + abstol::AT + reltol::RT end function Base.show(io::IO, sol::TimeEvolutionMCSol) @@ -114,12 +124,13 @@ A structure storing the results and some information from solving trajectories o - `reltol::Real`: The relative tolerance which is used during the solving process. """ struct TimeEvolutionSSESol{ - TT<:Vector{<:Real}, + TT<:AbstractVector{<:Real}, TS<:AbstractVector, TE<:Matrix{ComplexF64}, TEA<:Array{ComplexF64,3}, - T1<:Real, - T2<:Real, + AlgT<:StochasticDiffEqAlgorithm, + AT<:Real, + RT<:Real, } ntraj::Int times::TT @@ -127,9 +138,9 @@ struct TimeEvolutionSSESol{ expect::TE expect_all::TEA converged::Bool - alg::StochasticDiffEqAlgorithm - abstol::T1 - reltol::T2 + alg::AlgT + abstol::AT + reltol::RT end function Base.show(io::IO, sol::TimeEvolutionSSESol) @@ -155,86 +166,8 @@ struct DiscreteLindbladJumpCallback <: LindbladJumpCallbackType end ContinuousLindbladJumpCallback(; interp_points::Int = 10) = ContinuousLindbladJumpCallback(interp_points) -## Time-dependent sum of operators - -struct TimeDependentOperatorSum{CFT,OST<:OperatorSum} - coefficient_functions::CFT - operator_sum::OST -end - -function TimeDependentOperatorSum( - coefficient_functions, - operators::Union{AbstractVector{<:QuantumObject},Tuple}; - params = nothing, - init_time = 0.0, -) - # promote the type of the coefficients and the operators. Remember that the coefficient_functions si a vector of functions and the operators is a vector of QuantumObjects - coefficients = [f(init_time, params) for f in coefficient_functions] - operator_sum = OperatorSum(coefficients, operators) - return TimeDependentOperatorSum(coefficient_functions, operator_sum) -end - -Base.size(A::TimeDependentOperatorSum) = size(A.operator_sum) -Base.size(A::TimeDependentOperatorSum, inds...) = size(A.operator_sum, inds...) -Base.length(A::TimeDependentOperatorSum) = length(A.operator_sum) - -function update_coefficients!(A::TimeDependentOperatorSum, t, params) - @inbounds @simd for i in 1:length(A.coefficient_functions) - A.operator_sum.coefficients[i] = A.coefficient_functions[i](t, params) - end -end - -(A::TimeDependentOperatorSum)(t, params) = (update_coefficients!(A, t, params); A) - -@inline function LinearAlgebra.mul!(y::AbstractVector, A::TimeDependentOperatorSum, x::AbstractVector, α, β) - return mul!(y, A.operator_sum, x, α, β) -end - -function liouvillian(A::TimeDependentOperatorSum, Id_cache = I(prod(A.operator_sum.operators[1].dims))) - return TimeDependentOperatorSum(A.coefficient_functions, liouvillian(A.operator_sum, Id_cache)) -end - ####################################### -### LIOUVILLIAN ### -@doc raw""" - liouvillian(H::QuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims))) - -Construct the Liouvillian [`SuperOperator`](@ref) for a system Hamiltonian ``\hat{H}`` and a set of collapse operators ``\{\hat{C}_n\}_n``: - -```math -\mathcal{L} [\cdot] = -i[\hat{H}, \cdot] + \sum_n \mathcal{D}(\hat{C}_n) [\cdot] -``` - -where - -```math -\mathcal{D}(\hat{C}_n) [\cdot] = \hat{C}_n [\cdot] \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n [\cdot] - \frac{1}{2} [\cdot] \hat{C}_n^\dagger \hat{C}_n -``` - -The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. - -See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref). -""" -function liouvillian( - H::QuantumObject{MT1,OpType1}, - c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - Id_cache = I(prod(H.dims)), -) where {MT1<:AbstractMatrix,OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} - L = liouvillian(H, Id_cache) - if !(c_ops isa Nothing) - for c_op in c_ops - L += lindblad_dissipator(c_op, Id_cache) - end - end - return L -end - -liouvillian(H::QuantumObject{<:AbstractMatrix,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dims))) = - -1im * (spre(H, Id_cache) - spost(H, Id_cache)) - -liouvillian(H::QuantumObject{<:AbstractMatrix,SuperOperatorQuantumObject}, Id_cache::Diagonal) = H - function liouvillian_floquet( L₀::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, Lₚ::QuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 4ffe31873..8830de405 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -129,25 +129,25 @@ function _DFDIncreaseReduceAffect!(integrator) resize!(integrator, size(L, 1)) copyto!(integrator.u, mat2vec(ρt)) - integrator.p = merge(internal_params, (L = L, e_ops = e_ops2, dfd_ρt_cache = similar(integrator.u))) + # By doing this, we are assuming that the system is time-independent and f is a MatrixOperator + integrator.f = ODEFunction{true,FullSpecialize}(MatrixOperator(L)) + integrator.p = merge(internal_params, (e_ops = e_ops2, dfd_ρt_cache = similar(integrator.u))) return nothing end function dfd_mesolveProblem( H::Function, - ψ0::QuantumObject{<:AbstractArray{T1},StateOpType}, - t_l::AbstractVector, + ψ0::QuantumObject{DT1,StateOpType}, + tlist::AbstractVector, c_ops::Function, maxdims::Vector{T2}, dfd_params::NamedTuple = NamedTuple(); - alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Function = (dim_list) -> Vector{Vector{T1}}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + e_ops::Function = (dim_list) -> Vector{Vector{DT1}}([]), params::NamedTuple = NamedTuple(), tol_list::Vector{<:Number} = fill(1e-8, length(maxdims)), kwargs..., -) where {T1,T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {DT1,T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} length(ψ0.dims) != length(maxdims) && throw(DimensionMismatch("'dim_list' and 'maxdims' do not have the same dimension.")) @@ -158,8 +158,8 @@ function dfd_mesolveProblem( dim_list_evo_times = [0.0] dim_list_evo = [dim_list] - reduce_list = MVector(ntuple(i -> false, length(dim_list))) - increase_list = MVector(ntuple(i -> false, length(dim_list))) + reduce_list = MVector(ntuple(i -> false, Val(length(dim_list)))) + increase_list = MVector(ntuple(i -> false, Val(length(dim_list)))) pillow_list = _dfd_set_pillow.(dim_list) params2 = merge( @@ -187,16 +187,15 @@ function dfd_mesolveProblem( haskey(kwargs2, :callback) ? merge(kwargs2, (callback = CallbackSet(cb_dfd, kwargs2.callback),)) : merge(kwargs2, (callback = cb_dfd,)) - return mesolveProblem(H₀, ψ0, t_l, c_ops₀; e_ops = e_ops₀, alg = alg, H_t = H_t, params = params2, kwargs2...) + return mesolveProblem(H₀, ψ0, tlist, c_ops₀; e_ops = e_ops₀, params = params2, kwargs2...) end @doc raw""" dfd_mesolve(H::Function, ψ0::QuantumObject, - t_l::AbstractVector, c_ops::Function, maxdims::AbstractVector, + tlist::AbstractVector, c_ops::Function, maxdims::AbstractVector, dfd_params::NamedTuple=NamedTuple(); alg::OrdinaryDiffEqAlgorithm=Tsit5(), e_ops::Function=(dim_list) -> Vector{Vector{T1}}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), tol_list::Vector{<:Number}=fill(1e-8, length(maxdims)), kwargs...) @@ -210,13 +209,12 @@ Time evolution of an open quantum system using master equation, dynamically chan function dfd_mesolve( H::Function, ψ0::QuantumObject{<:AbstractArray{T1},StateOpType}, - t_l::AbstractVector, + tlist::AbstractVector, c_ops::Function, maxdims::Vector{T2}, dfd_params::NamedTuple = NamedTuple(); alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Function = (dim_list) -> Vector{Vector{T1}}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, params::NamedTuple = NamedTuple(), tol_list::Vector{<:Number} = fill(1e-8, length(maxdims)), kwargs..., @@ -224,13 +222,11 @@ function dfd_mesolve( dfd_prob = dfd_mesolveProblem( H, ψ0, - t_l, + tlist, c_ops, maxdims, dfd_params; - alg = alg, e_ops = e_ops, - H_t = H_t, params = params, tol_list = tol_list, kwargs..., @@ -279,7 +275,7 @@ end function _DSF_mesolve_Affect!(integrator) internal_params = integrator.p - op_l = internal_params.op_l + op_list = internal_params.op_list op_l_vec = internal_params.op_l_vec αt_list = internal_params.αt_list δα_list = internal_params.δα_list @@ -293,11 +289,10 @@ function _DSF_mesolve_Affect!(integrator) dsf_identity = internal_params.dsf_identity dsf_displace_cache_full = internal_params.dsf_displace_cache_full - op_l_length = length(op_l) - fill!(dsf_displace_cache_full.coefficients, 0) + op_l_length = length(op_list) - for i in eachindex(op_l) - # op = op_l[i] + for i in eachindex(op_list) + # op = op_list[i] op_vec = op_l_vec[i] αt = αt_list[i] δα = δα_list[i] @@ -318,12 +313,17 @@ function _DSF_mesolve_Affect!(integrator) # arnoldi!(expv_cache, Aᵢ, dsf_cache) # expv!(integrator.u, expv_cache, one(αt), dsf_cache) - dsf_displace_cache_full.coefficients[i] = Δα - dsf_displace_cache_full.coefficients[i+op_l_length] = -conj(Δα) - dsf_displace_cache_full.coefficients[i+2*op_l_length] = conj(Δα) - dsf_displace_cache_full.coefficients[i+3*op_l_length] = -Δα + dsf_displace_cache_full.ops[i].λ.val = Δα + dsf_displace_cache_full.ops[i+op_l_length].λ.val = -conj(Δα) + dsf_displace_cache_full.ops[i+2*op_l_length].λ.val = conj(Δα) + dsf_displace_cache_full.ops[i+3*op_l_length].λ.val = -Δα αt_list[i] += Δα + else + dsf_displace_cache_full.ops[i].λ.val = 0 + dsf_displace_cache_full.ops[i+op_l_length].λ.val = 0 + dsf_displace_cache_full.ops[i+2*op_l_length].λ.val = 0 + dsf_displace_cache_full.ops[i+3*op_l_length].λ.val = 0 end end @@ -331,52 +331,47 @@ function _DSF_mesolve_Affect!(integrator) arnoldi!(expv_cache, dsf_displace_cache_full, dsf_cache) expv!(integrator.u, expv_cache, 1, dsf_cache) - op_l2 = op_l .+ αt_list + op_l2 = op_list .+ αt_list e_ops2 = e_ops(op_l2, dsf_params) _mat2vec_data = op -> mat2vec(get_data(op)') @. e_ops_vec = _mat2vec_data(e_ops2) - return copyto!(internal_params.L, liouvillian(H(op_l2, dsf_params), c_ops(op_l2, dsf_params), dsf_identity).data) + # By doing this, we are assuming that the system is time-independent and f is a MatrixOperator + copyto!(integrator.f.f.A, liouvillian(H(op_l2, dsf_params), c_ops(op_l2, dsf_params), dsf_identity).data) + return u_modified!(integrator, true) end function dsf_mesolveProblem( H::Function, - ψ0::QuantumObject{<:AbstractArray{T},StateOpType}, - t_l::AbstractVector, + ψ0::QuantumObject{<:AbstractVector{T},StateOpType}, + tlist::AbstractVector, c_ops::Function, - op_list::Vector{TOl}, + op_list::Union{AbstractVector,Tuple}, α0_l::Vector{<:Number} = zeros(length(op_list)), dsf_params::NamedTuple = NamedTuple(); - alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Function = (op_list, p) -> Vector{TOl}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + e_ops::Function = (op_list, p) -> (), params::NamedTuple = NamedTuple(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject},TOl} - op_l = op_list - H₀ = H(op_l .+ α0_l, dsf_params) - c_ops₀ = c_ops(op_l .+ α0_l, dsf_params) - e_ops₀ = e_ops(op_l .+ α0_l, dsf_params) +) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} + op_list = deepcopy(op_list) + H₀ = H(op_list .+ α0_l, dsf_params) + c_ops₀ = c_ops(op_list .+ α0_l, dsf_params) + e_ops₀ = e_ops(op_list .+ α0_l, dsf_params) αt_list = convert(Vector{T}, α0_l) - op_l_vec = map(op -> mat2vec(get_data(op)'), op_l) + op_l_vec = map(op -> mat2vec(get_data(op)'), op_list) # Create the Krylov subspace with kron(H₀.data, H₀.data) just for initialize expv_cache = arnoldi(kron(H₀.data, H₀.data), mat2vec(ket2dm(ψ0).data), krylov_dim) dsf_identity = I(prod(H₀.dims)) - dsf_displace_cache_left = map(op -> Qobj(kron(op.data, dsf_identity)), op_l) - dsf_displace_cache_left_dag = map(op -> Qobj(kron(sparse(op.data'), dsf_identity)), op_l) - dsf_displace_cache_right = map(op -> Qobj(kron(dsf_identity, op.data)), op_l) - dsf_displace_cache_right_dag = map(op -> Qobj(kron(dsf_identity, sparse(op.data'))), op_l) - dsf_displace_cache_full = OperatorSum( - zeros(length(op_l) * 4), - vcat( - dsf_displace_cache_left, - dsf_displace_cache_left_dag, - dsf_displace_cache_right, - dsf_displace_cache_right_dag, - ), - ) + dsf_displace_cache_left = sum(op -> ScalarOperator(one(T)) * MatrixOperator(kron(op.data, dsf_identity)), op_list) + dsf_displace_cache_left_dag = + sum(op -> ScalarOperator(one(T)) * MatrixOperator(kron(sparse(op.data'), dsf_identity)), op_list) + dsf_displace_cache_right = sum(op -> ScalarOperator(one(T)) * MatrixOperator(kron(dsf_identity, op.data)), op_list) + dsf_displace_cache_right_dag = + sum(op -> ScalarOperator(one(T)) * MatrixOperator(kron(dsf_identity, sparse(op.data'))), op_list) + dsf_displace_cache_full = + dsf_displace_cache_left + dsf_displace_cache_left_dag + dsf_displace_cache_right + dsf_displace_cache_right_dag params2 = params params2 = merge( @@ -385,7 +380,7 @@ function dsf_mesolveProblem( H_fun = H, c_ops_fun = c_ops, e_ops_fun = e_ops, - op_l = op_l, + op_list = op_list, op_l_vec = op_l_vec, αt_list = αt_list, δα_list = δα_list, @@ -403,19 +398,18 @@ function dsf_mesolveProblem( haskey(kwargs2, :callback) ? merge(kwargs2, (callback = CallbackSet(cb_dsf, kwargs2.callback),)) : merge(kwargs2, (callback = cb_dsf,)) - return mesolveProblem(H₀, ψ0, t_l, c_ops₀; e_ops = e_ops₀, alg = alg, H_t = H_t, params = params2, kwargs2...) + return mesolveProblem(H₀, ψ0, tlist, c_ops₀; e_ops = e_ops₀, params = params2, kwargs2...) end @doc raw""" dsf_mesolve(H::Function, ψ0::QuantumObject, - t_l::AbstractVector, c_ops::Function, + tlist::AbstractVector, c_ops::Function, op_list::Vector{TOl}, α0_l::Vector{<:Number}=zeros(length(op_list)), dsf_params::NamedTuple=NamedTuple(); alg::OrdinaryDiffEqAlgorithm=Tsit5(), e_ops::Function=(op_list,p) -> Vector{TOl}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), δα_list::Vector{<:Number}=fill(0.2, length(op_list)), krylov_dim::Int=max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), @@ -429,31 +423,28 @@ Time evolution of an open quantum system using master equation and the Dynamical """ function dsf_mesolve( H::Function, - ψ0::QuantumObject{<:AbstractArray{T},StateOpType}, - t_l::AbstractVector, + ψ0::QuantumObject{<:AbstractVector{T},StateOpType}, + tlist::AbstractVector, c_ops::Function, - op_list::Vector{TOl}, + op_list::Union{AbstractVector,Tuple}, α0_l::Vector{<:Number} = zeros(length(op_list)), dsf_params::NamedTuple = NamedTuple(); alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Function = (op_list, p) -> Vector{TOl}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + e_ops::Function = (op_list, p) -> (), params::NamedTuple = NamedTuple(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject},TOl} +) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} dsf_prob = dsf_mesolveProblem( H, ψ0, - t_l, + tlist, c_ops, op_list, α0_l, dsf_params; - alg = alg, e_ops = e_ops, - H_t = H_t, params = params, δα_list = δα_list, krylov_dim = krylov_dim, @@ -465,31 +456,29 @@ end function dsf_mesolve( H::Function, - ψ0::QuantumObject{<:AbstractArray{T},StateOpType}, - t_l::AbstractVector, - op_list::Vector{TOl}, + ψ0::QuantumObject{<:AbstractVector{T},StateOpType}, + tlist::AbstractVector, + op_list::Union{AbstractVector,Tuple}, α0_l::Vector{<:Number} = zeros(length(op_list)), dsf_params::NamedTuple = NamedTuple(); alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Function = (op_list, p) -> Vector{TOl}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + e_ops::Function = (op_list, p) -> (), params::NamedTuple = NamedTuple(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject},TOl} - c_ops = op_list -> Vector{TOl}([]) +) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} + c_ops = op_list -> () return dsf_mesolve( H, ψ0, - t_l, + tlist, c_ops, op_list, α0_l, dsf_params; alg = alg, e_ops = e_ops, - H_t = H_t, params = params, δα_list = δα_list, krylov_dim = krylov_dim, @@ -501,14 +490,14 @@ end function _DSF_mcsolve_Condition(u, t, integrator) internal_params = integrator.p - op_l = internal_params.op_l + op_list = internal_params.op_list δα_list = internal_params.δα_list ψt = u condition = false - @inbounds for i in eachindex(op_l) - op = op_l[i] + @inbounds for i in eachindex(op_list) + op = op_list[i] δα = δα_list[i] Δα = dot(ψt, op.data, ψt) / dot(ψt, ψt) if δα < abs(Δα) @@ -520,7 +509,7 @@ end function _DSF_mcsolve_Affect!(integrator) internal_params = integrator.p - op_l = internal_params.op_l + op_list = internal_params.op_list αt_list = internal_params.αt_list δα_list = internal_params.δα_list H = internal_params.H_fun @@ -537,11 +526,10 @@ function _DSF_mcsolve_Affect!(integrator) copyto!(ψt, integrator.u) normalize!(ψt) - op_l_length = length(op_l) - fill!(dsf_displace_cache_full.coefficients, 0) + op_l_length = length(op_list) - for i in eachindex(op_l) - op = op_l[i] + for i in eachindex(op_list) + op = op_list[i] αt = αt_list[i] δα = δα_list[i] Δα = dot(ψt, op.data, ψt) @@ -556,10 +544,13 @@ function _DSF_mcsolve_Affect!(integrator) # arnoldi!(expv_cache, Aᵢ, dsf_cache) # expv!(integrator.u, expv_cache, one(αt), dsf_cache) - dsf_displace_cache_full.coefficients[i] = conj(Δα) - dsf_displace_cache_full.coefficients[i+op_l_length] = -Δα + dsf_displace_cache_full.ops[i].λ.val = conj(Δα) + dsf_displace_cache_full.ops[i+op_l_length].λ.val = -Δα αt_list[i] += Δα + else + dsf_displace_cache_full.ops[i].λ.val = 0 + dsf_displace_cache_full.ops[i+op_l_length].λ.val = 0 end end @@ -567,13 +558,15 @@ function _DSF_mcsolve_Affect!(integrator) arnoldi!(expv_cache, dsf_displace_cache_full, dsf_cache) expv!(integrator.u, expv_cache, 1, dsf_cache) - op_l2 = op_l .+ αt_list + op_l2 = op_list .+ αt_list e_ops2 = e_ops(op_l2, dsf_params) c_ops2 = c_ops(op_l2, dsf_params) @. e_ops0 = get_data(e_ops2) @. c_ops0 = get_data(c_ops2) - H_eff = H(op_l2, dsf_params).data - lmul!(convert(eltype(ψt), 0.5im), mapreduce(op -> op' * op, +, c_ops0)) - return mul!(internal_params.U, -1im, H_eff) + H_nh = lmul!(convert(eltype(ψt), 0.5im), mapreduce(op -> op' * op, +, c_ops0)) + # By doing this, we are assuming that the system is time-independent and f is a ScaledOperator + copyto!(integrator.f.f.L.A, H(op_l2, dsf_params).data - H_nh) + return u_modified!(integrator, true) end function _dsf_mcsolve_prob_func(prob, i, repeat) @@ -582,9 +575,8 @@ function _dsf_mcsolve_prob_func(prob, i, repeat) prm = merge( internal_params, ( - U = copy(internal_params.U), - e_ops_mc = copy(internal_params.e_ops_mc), - c_ops = copy(internal_params.c_ops), + e_ops_mc = deepcopy(internal_params.e_ops_mc), + c_ops = deepcopy(internal_params.c_ops), expvals = similar(internal_params.expvals), cache_mc = similar(internal_params.cache_mc), weights_mc = similar(internal_params.weights_mc), @@ -598,27 +590,24 @@ function _dsf_mcsolve_prob_func(prob, i, repeat) dsf_cache1 = similar(internal_params.dsf_cache1), dsf_cache2 = similar(internal_params.dsf_cache2), expv_cache = copy(internal_params.expv_cache), - dsf_displace_cache_full = OperatorSum( - copy(internal_params.dsf_displace_cache_full.coefficients), - internal_params.dsf_displace_cache_full.operators, - ), + dsf_displace_cache_full = deepcopy(internal_params.dsf_displace_cache_full), # This brutally copies also the MatrixOperators, and it is inefficient. ), ) - return remake(prob, p = prm) + f = deepcopy(prob.f.f) + + return remake(prob, f = f, p = prm) end function dsf_mcsolveEnsembleProblem( H::Function, - ψ0::QuantumObject{<:AbstractArray{T},KetQuantumObject}, - t_l::AbstractVector, + ψ0::QuantumObject{<:AbstractVector{T},KetQuantumObject}, + tlist::AbstractVector, c_ops::Function, - op_list::Vector{TOl}, + op_list::Union{AbstractVector,Tuple}, α0_l::Vector{<:Number} = zeros(length(op_list)), dsf_params::NamedTuple = NamedTuple(); - alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Function = (op_list, p) -> Vector{TOl}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + e_ops::Function = (op_list, p) -> (), params::NamedTuple = NamedTuple(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), @@ -627,18 +616,18 @@ function dsf_mcsolveEnsembleProblem( krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), progress_bar::Union{Bool,Val} = Val(true), kwargs..., -) where {T,TOl,TJC<:LindbladJumpCallbackType} - op_l = op_list - H₀ = H(op_l .+ α0_l, dsf_params) - c_ops₀ = c_ops(op_l .+ α0_l, dsf_params) - e_ops₀ = e_ops(op_l .+ α0_l, dsf_params) +) where {T,TJC<:LindbladJumpCallbackType} + op_list = deepcopy(op_list) + H₀ = H(op_list .+ α0_l, dsf_params) + c_ops₀ = c_ops(op_list .+ α0_l, dsf_params) + e_ops₀ = e_ops(op_list .+ α0_l, dsf_params) αt_list = convert(Vector{T}, α0_l) expv_cache = arnoldi(H₀.data, ψ0.data, krylov_dim) - dsf_displace_cache = map(op -> Qobj(op.data), op_l) - dsf_displace_cache_dag = map(op -> Qobj(sparse(op.data')), op_l) - dsf_displace_cache_full = OperatorSum(zeros(length(op_l) * 2), vcat(dsf_displace_cache, dsf_displace_cache_dag)) + dsf_displace_cache = sum(op -> ScalarOperator(one(T)) * MatrixOperator(op.data), op_list) + dsf_displace_cache_dag = sum(op -> ScalarOperator(one(T)) * MatrixOperator(sparse(op.data')), op_list) + dsf_displace_cache_full = dsf_displace_cache + dsf_displace_cache_dag params2 = merge( params, @@ -646,7 +635,7 @@ function dsf_mcsolveEnsembleProblem( H_fun = H, c_ops_fun = c_ops, e_ops_fun = e_ops, - op_l = op_l, + op_list = op_list, αt_list = αt_list, δα_list = δα_list, dsf_cache1 = similar(ψ0.data), @@ -666,11 +655,9 @@ function dsf_mcsolveEnsembleProblem( return mcsolveEnsembleProblem( H₀, ψ0, - t_l, + tlist, c_ops₀; e_ops = e_ops₀, - alg = alg, - H_t = H_t, params = params2, ntraj = ntraj, ensemble_method = ensemble_method, @@ -684,13 +671,12 @@ end @doc raw""" dsf_mcsolve(H::Function, ψ0::QuantumObject, - t_l::AbstractVector, c_ops::Function, + tlist::AbstractVector, c_ops::Function, op_list::Vector{TOl}, α0_l::Vector{<:Number}=zeros(length(op_list)), dsf_params::NamedTuple=NamedTuple(); alg::OrdinaryDiffEqAlgorithm=Tsit5(), e_ops::Function=(op_list,p) -> Vector{TOl}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum}=nothing, params::NamedTuple=NamedTuple(), δα_list::Vector{<:Real}=fill(0.2, length(op_list)), ntraj::Int=1, @@ -708,15 +694,14 @@ Time evolution of a quantum system using the Monte Carlo wave function method an """ function dsf_mcsolve( H::Function, - ψ0::QuantumObject{<:AbstractArray{T},KetQuantumObject}, - t_l::AbstractVector, + ψ0::QuantumObject{<:AbstractVector{T},KetQuantumObject}, + tlist::AbstractVector, c_ops::Function, - op_list::Vector{TOl}, + op_list::Union{AbstractVector,Tuple}, α0_l::Vector{<:Number} = zeros(length(op_list)), dsf_params::NamedTuple = NamedTuple(); alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Function = (op_list, p) -> Vector{TOl}([]), - H_t::Union{Nothing,Function,TimeDependentOperatorSum} = nothing, + e_ops::Function = (op_list, p) -> (), params::NamedTuple = NamedTuple(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), ntraj::Int = 1, @@ -725,18 +710,17 @@ function dsf_mcsolve( krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), progress_bar::Union{Bool,Val} = Val(true), kwargs..., -) where {T,TOl,TJC<:LindbladJumpCallbackType} +) where {T,TJC<:LindbladJumpCallbackType} ens_prob_mc = dsf_mcsolveEnsembleProblem( H, ψ0, - t_l, + tlist, c_ops, op_list, α0_l, dsf_params; alg = alg, e_ops = e_ops, - H_t = H_t, params = params, ntraj = ntraj, ensemble_method = ensemble_method, diff --git a/src/utilities.jl b/src/utilities.jl index 67f62697f..00cf0866c 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -141,6 +141,7 @@ getVal(x) = x # getVal for any other type _get_size(A::AbstractMatrix) = size(A) _get_size(A::AbstractVector) = (length(A), 1) +_get_size(A::AbstractSciMLOperator) = size(A) _non_static_array_warning(argname, arg::Tuple{}) = throw(ArgumentError("The argument $argname must be a Tuple or a StaticVector of non-zero length.")) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 8ef46b783..b472e0fef 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -120,6 +120,24 @@ @test_throws DimensionMismatch Qobj(ρ_bra.data, type = OperatorBra, dims = 4) end + @testset "Checks on non-QuantumObjects" begin + x = 1 + @test isket(x) == false + @test isbra(x) == false + @test isoper(x) == false + @test issuper(x) == false + @test isoperket(x) == false + @test isoperbra(x) == false + + x = rand(ComplexF64, 2) + @test isket(x) == false + @test isbra(x) == false + @test isoper(x) == false + @test issuper(x) == false + @test isoperket(x) == false + @test isoperbra(x) == false + end + @testset "arithmetic" begin a = sprand(ComplexF64, 100, 100, 0.1) a2 = Qobj(a) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl new file mode 100644 index 000000000..933590e67 --- /dev/null +++ b/test/core-test/quantum_objects_evo.jl @@ -0,0 +1,180 @@ +@testset "Quantum Objects Evolution" verbose = true begin + # DomainError: incompatible between size of array and type + @testset "Thrown Errors" begin + a = MatrixOperator(rand(ComplexF64, 3, 2)) + for t in [Operator, SuperOperator] + @test_throws DomainError QobjEvo(a, type = t) + end + + a = MatrixOperator(rand(ComplexF64, 3, 2)) + for t in (Ket, Bra, OperatorKet, OperatorBra) + @test_throws ArgumentError QobjEvo(a, type = t) + end + + a = QobjEvo(destroy(20)) + @test_throws ArgumentError QobjEvo(a, type = SuperOperator) + + a = MatrixOperator(rand(ComplexF64, 5, 5)) + @test_throws DimensionMismatch QobjEvo(a, type = SuperOperator) + + ψ = fock(10, 3) + @test_throws TypeError QobjEvo(ψ) + end + + # unsupported type of dims + @testset "unsupported dims" begin + a = MatrixOperator(rand(2, 2)) + @test_throws ArgumentError QobjEvo(a, dims = 2.0) + @test_throws ArgumentError QobjEvo(a, dims = 2.0 + 0.0im) + @test_throws DomainError QobjEvo(a, dims = 0) + @test_throws DomainError QobjEvo(a, dims = (2, -2)) + @test_logs ( + :warn, + "The argument dims should be a Tuple or a StaticVector for better performance. Try to use `dims = (2, 2)` or `dims = SVector(2, 2)` instead of `dims = [2, 2]`.", + ) QobjEvo(MatrixOperator(rand(4, 4)), dims = [2, 2]) + end + + @testset "Operator and SuperOperator" begin + a = MatrixOperator(sprand(ComplexF64, 100, 100, 0.1)) + a2 = QobjEvo(a) + a3 = QobjEvo(a, type = SuperOperator) + + @test isket(a2) == false + @test isbra(a2) == false + @test isoper(a2) == true + @test issuper(a2) == false + @test isoperket(a2) == false + @test isoperbra(a2) == false + @test isket(a3) == false + @test isbra(a3) == false + @test isoper(a3) == false + @test issuper(a3) == true + @test isoperket(a3) == false + @test isoperbra(a3) == false + @test_throws DimensionMismatch QobjEvo(a, dims = 2) + end + + @testset "Promote Operators Type" begin + a = destroy(20) + A = QobjEvo(a) + @test QuantumToolbox.promote_op_type(a, A) == QuantumObjectEvolution + @test QuantumToolbox.promote_op_type(A, a) == QuantumObjectEvolution + @test QuantumToolbox.promote_op_type(A, A) == QuantumObjectEvolution + @test QuantumToolbox.promote_op_type(a, a) == QuantumObject + end + + @testset "arithmetic" begin + a = MatrixOperator(sprand(ComplexF64, 100, 100, 0.1)) + a2 = QobjEvo(a) + a3 = QobjEvo(a, type = SuperOperator) + + @test +a2 == a2 + @test -(-a2) == a2 + @test a2 + 2 == 2 + a2 + @test (a2 + 2).data == a2.data + 2 * I + @test a2 * 2 == 2 * a2 + + @test trans(trans(a2)) == a2 + @test trans(a2).data == transpose(a2.data) + # @test adjoint(a2) ≈ trans(conj(a2)) # Currently doesn't work + @test adjoint(adjoint(a2)) == a2 + @test adjoint(a2).data == adjoint(a2.data) + + N = 10 + # We use MatrixOperator instead of directly using a Qobj to increase coverage + a = QobjEvo(MatrixOperator(sprand(ComplexF64, N, N, 5 / N)), Operator, N) + a_d = a' + X = a + a_d + # Y = 1im * (a - a_d) # Currently doesn't work. Fix in SciMLOperators.jl + Z = a + trans(a) + @test isherm(X) == true + # @test isherm(Y) == true + # @test issymmetric(Y) == false + @test issymmetric(Z) == true + end + + # TODO: Implement a new show method for QuantumObjectEvolution + # @testset "REPL show" begin + # N = 10 + # a = QobjEvo(destroy(N)) + + # opstring = sprint((t, s) -> show(t, "text/plain", s), a) + # datastring = sprint((t, s) -> show(t, "text/plain", s), a.data) + # a_dims = a.dims + # a_size = size(a) + # a_isherm = isherm(a) + # @test opstring == + # "Quantum Object: type=Operator dims=$a_dims size=$a_size ishermitian=$a_isherm\n$datastring" + + # a = spre(a) + # opstring = sprint((t, s) -> show(t, "text/plain", s), a) + # datastring = sprint((t, s) -> show(t, "text/plain", s), a.data) + # a_dims = a.dims + # a_size = size(a) + # a_isherm = isherm(a) + # @test opstring == "Quantum Object: type=SuperOperator dims=$a_dims size=$a_size\n$datastring" + # end + + @testset "Type Inference (QuantumObject)" begin + for T in [ComplexF32, ComplexF64] + N = 4 + a = MatrixOperator(rand(T, N, N)) + @inferred QobjEvo(a) + for type in [Operator, SuperOperator] + @inferred QobjEvo(a, type = type) + end + end + + @testset "Math Operation" begin + a = QobjEvo(destroy(20)) + σx = QobjEvo(sigmax()) + @inferred a + a + @inferred a + a' + # @inferred a + 2 # TODO fix in SciMLOperators.jl + @inferred 2 * a + @inferred a / 2 + @inferred a * a + @inferred a * a' + + # TODO: kron is currently not supported + # @inferred kron(a) + # @inferred kron(a, σx) + # @inferred kron(a, eye(2)) + end + end + + # TODO: tensor is currently not supported + # @testset "tensor" begin + # σx = sigmax() + # X3 = kron(σx, σx, σx) + # @test tensor(σx) == kron(σx) + # @test tensor(fill(σx, 3)...) == X3 + # X_warn = @test_logs ( + # :warn, + # "`tensor(A)` or `kron(A)` with `A` is a `Vector` can hurt performance. Try to use `tensor(A...)` or `kron(A...)` instead.", + # ) tensor(fill(σx, 3)) + # @test X_warn == X3 + # end + + @testset "Time Dependent Operators" begin + N = 10 + a = destroy(N) + coef1(p, t) = exp(-1im * p.ω1 * t) + coef2(p, t) = sin(p.ω2 * t) + + @test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]]) + + op1 = QobjEvo(((a, coef1), a' * a, (a', coef2))) + op1 = QobjEvo(((a, coef1), a' * a, (a', coef2))) + + p = (ω1 = 1, ω2 = 2) + @test op1(p, 0.1) ≈ coef1(p, 0.1) * a + a' * a + coef2(p, 0.1) * a' + + ψ = fock(N, 1) + @test op1(ψ, p, 0.1) ≈ (coef1(p, 0.1) * a + a' * a + coef2(p, 0.1) * a') * ψ + + @test isconstant(a) == true + @test isconstant(op1) == false + @test isconstant(Qobj(a)) == true + end +end diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index 1b3df4e79..507f1227a 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -64,8 +64,11 @@ e_ops = [a_d * a] psi0 = fock(N, 3) t_l = LinRange(0, 100 * 2π, 1000) - H_t_f = TimeDependentOperatorSum((((t, p) -> sin(t)),), (H_t,)) # It will be converted to liouvillian internally - sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, H_t = H_t_f, progress_bar = Val(false)) + + coeff(p, t) = sin(t) + H_td = (H, (H_t, coeff)) + + sol_me = mesolve(H_td, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) ρ_ss1 = steadystate_floquet(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetLinearSystem())[1] ρ_ss2 = steadystate_floquet(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetEffectiveLiouvillian()) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 50d3306e9..666aa38ab 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -59,18 +59,44 @@ sol_me2 = mesolve(H, psi0, t_l, c_ops, progress_bar = Val(false)) sol_me3 = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) sol_mc = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_mc2 = mcsolve( + H, + psi0, + t_l, + c_ops, + ntraj = 500, + e_ops = e_ops, + progress_bar = Val(false), + jump_callback = DiscreteLindbladJumpCallback(), + ) sol_mc_states = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, saveat = t_l, progress_bar = Val(false)) + sol_mc_states2 = mcsolve( + H, + psi0, + t_l, + c_ops, + ntraj = 500, + saveat = t_l, + progress_bar = Val(false), + jump_callback = DiscreteLindbladJumpCallback(), + ) sol_sse = ssesolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) expect_mc_states_mean = sum(expect_mc_states, dims = 2) / size(expect_mc_states, 2) + ρt_mc2 = [ket2dm.(normalize.(states)) for states in sol_mc_states2.states] + expect_mc_states2 = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc2) + expect_mc_states_mean2 = sum(expect_mc_states2, dims = 2) / size(expect_mc_states2, 2) + sol_me_string = sprint((t, s) -> show(t, "text/plain", s), sol_me) sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(t_l) < 0.1 + @test sum(abs.(sol_mc2.expect .- sol_me.expect)) / length(t_l) < 0.1 @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect))) / length(t_l) < 0.1 + @test sum(abs.(vec(expect_mc_states_mean2) .- vec(sol_me.expect))) / length(t_l) < 0.1 @test sum(abs.(sol_sse.expect .- sol_me.expect)) / length(t_l) < 0.1 @test length(sol_me.times) == length(t_l) @test length(sol_me.states) == 1 @@ -117,37 +143,219 @@ "abstol = $(sol_sse.abstol)\n" * "reltol = $(sol_sse.reltol)\n" + # 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. + + N = 10 + a = tensor(destroy(N), qeye(2)) + σm = tensor(qeye(N), sigmam()) + σz = tensor(qeye(N), sigmaz()) + ω = 1.0 + ωd = 1.02 + Δ = ω - ωd + F = 0.05 + g = 0.1 + γ = 0.1 + nth = 0.001 + + # Time Evolution in the drive frame + + H = Δ * a' * a + Δ * σz / 2 + g * (a' * σm + a * σm') + F * (a + a') + c_ops = [sqrt(γ * (1 + nth)) * a, sqrt(γ * nth) * a', sqrt(γ * (1 + nth)) * σm, sqrt(γ * nth) * σm'] + e_ops = [a' * a, σz] + + ψ0 = tensor(basis(N, 0), basis(2, 1)) + tlist = range(0, 2 / γ, 1000) + + rng = MersenneTwister(12) + + sol_se = sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + sol_me = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_mc = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + # sol_sse = ssesolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + # Time Evolution in the lab frame + + H = ω * a' * a + ω * σz / 2 + g * (a' * σm + a * σm') + + coef1(p, t) = p.F * exp(1im * p.ωd * t) + coef2(p, t) = p.F * exp(-1im * p.ωd * t) + + H_td = (H, (a, coef1), (a', coef2)) + p = (F = F, ωd = ωd) + + sol_se_td = sesolve(H_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) + sol_me_td = mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + sol_mc_td = mcsolve( + H_td, + ψ0, + tlist, + c_ops, + ntraj = 500, + e_ops = e_ops, + progress_bar = Val(false), + params = p, + rng = rng, + ) + # sol_sse_td = ssesolve(H_td, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + + @test sol_se.expect ≈ sol_se_td.expect atol = 1e-6 * length(tlist) + @test sol_me.expect ≈ sol_me_td.expect atol = 1e-6 * length(tlist) + @test sol_mc.expect ≈ sol_mc_td.expect atol = 1e-2 * length(tlist) + # @test sol_sse.expect ≈ sol_sse_td.expect atol = 1e-2 * length(tlist) + + H_td2 = QobjEvo(H_td) + L_td = QobjEvo(H_td, type = SuperOperator, f = liouvillian) + + sol_se_td2 = sesolve(H_td2, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) + @test_throws ArgumentError mesolve( + H_td2, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + progress_bar = Val(false), + params = p, + ) + sol_me_td2 = mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + sol_mc_td2 = mcsolve( + H_td2, + ψ0, + tlist, + c_ops, + ntraj = 500, + e_ops = e_ops, + progress_bar = Val(false), + params = p, + rng = rng, + ) + # sol_sse_td2 = + # ssesolve(H_td2, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + + @test sol_se.expect ≈ sol_se_td2.expect atol = 1e-6 * length(tlist) + @test sol_me.expect ≈ sol_me_td2.expect atol = 1e-6 * length(tlist) + @test sol_mc.expect ≈ sol_mc_td2.expect atol = 1e-2 * length(tlist) + # @test sol_sse.expect ≈ sol_sse_td2.expect atol = 1e-2 * length(tlist) + @testset "Type Inference mesolve" begin - @inferred mesolveProblem(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolveProblem(H, psi0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolveProblem(H, Qobj(zeros(Int64, N)), t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolve(H, psi0, t_l, c_ops, progress_bar = Val(false)) - @inferred mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) - @inferred mesolve(H, psi0, t_l, (a, a'), e_ops = (a_d * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types + @inferred mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolveProblem(H, ψ0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolveProblem( + H, + tensor(Qobj(zeros(Int64, N)), Qobj([0, 1])), + tlist, + c_ops, + e_ops = e_ops, + progress_bar = Val(false), + ) + @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) + @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) + @inferred mesolve(H, ψ0, tlist, (a, a'), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple + @inferred mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + @inferred mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) end @testset "Type Inference mcsolve" begin - @inferred mcsolveEnsembleProblem(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) - @inferred mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) - @inferred mcsolve(H, psi0, t_l, c_ops, ntraj = 500, progress_bar = Val(true)) - @inferred mcsolve(H, psi0, [0, 10], c_ops, ntraj = 500, progress_bar = Val(false)) - @inferred mcsolve(H, Qobj(zeros(Int64, N)), t_l, c_ops, ntraj = 500, progress_bar = Val(false)) - @inferred mcsolve(H, psi0, t_l, (a, a'), e_ops = (a_d * a, a'), ntraj = 500, progress_bar = Val(false)) # We test the type inference for Tuple of different types + @inferred mcsolveEnsembleProblem( + H, + ψ0, + tlist, + c_ops, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) + @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, e_ops = e_ops, progress_bar = Val(false), rng = rng) + @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, progress_bar = Val(true), rng = rng) + @inferred mcsolve(H, ψ0, [0, 10], c_ops, ntraj = 5, progress_bar = Val(false), rng = rng) + @inferred mcsolve( + H, + tensor(Qobj(zeros(Int64, N)), Qobj([0, 1])), + tlist, + c_ops, + ntraj = 5, + progress_bar = Val(false), + rng = rng, + ) + @inferred mcsolve( + H, + ψ0, + tlist, + (a, a'), + e_ops = (a' * a, a'), + ntraj = 5, + progress_bar = Val(false), + rng = rng, + ) # We test the type inference for Tuple of different types + @inferred mcsolve( + H_td, + ψ0, + tlist, + c_ops, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + params = p, + rng = rng, + ) end @testset "Type Inference ssesolve" begin + c_ops_tuple = Tuple(c_ops) # To avoid type instability, we must have a Tuple instead of a Vector @inferred ssesolveEnsembleProblem( H, - psi0, - t_l, - c_ops, - ntraj = 500, + ψ0, + tlist, + c_ops_tuple, + ntraj = 5, e_ops = e_ops, progress_bar = Val(false), + rng = rng, + ) + @inferred ssesolve( + H, + ψ0, + tlist, + c_ops_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) + @inferred ssesolve(H, ψ0, tlist, c_ops_tuple, ntraj = 5, progress_bar = Val(true), rng = rng) + @inferred ssesolve(H, ψ0, [0, 10], c_ops_tuple, ntraj = 5, progress_bar = Val(false), rng = rng) + @inferred ssesolve( + H, + tensor(Qobj(zeros(Int64, N)), Qobj([0, 1])), + tlist, + c_ops_tuple, + ntraj = 5, + progress_bar = Val(false), + rng = rng, + ) + @inferred ssesolve( + H, + ψ0, + tlist, + c_ops_tuple, + ntraj = 5, + e_ops = (a' * a, a'), + progress_bar = Val(false), + rng = rng, + ) # We test the type inference for Tuple of different types + @inferred ssesolve( + H_td, + ψ0, + tlist, + c_ops_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + params = p, + rng = rng, ) - @inferred ssesolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) - @inferred ssesolve(H, psi0, t_l, c_ops, ntraj = 500, progress_bar = Val(true)) end @testset "mcsolve and ssesolve reproducibility" begin @@ -170,17 +378,14 @@ tlist = range(0, 20 / γ, 1000) rng = MersenneTwister(1234) - sleep(0.1) # If we don't sleep, we get an error (why?) sol_mc1 = mcsolve(H, psi0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) sol_sse1 = ssesolve(H, psi0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) - sleep(0.1) sol_mc2 = mcsolve(H, psi0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) sol_sse2 = ssesolve(H, psi0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) - sleep(0.1) sol_mc3 = mcsolve(H, psi0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 @@ -199,15 +404,16 @@ N = 10 a = destroy(N) H = a' * a + c_ops = [sqrt(0.1) * a] psi0 = basis(N, 3) t_l = LinRange(0, 100, 1000) psi_wrong = basis(N - 1, 3) @test_throws DimensionMismatch sesolve(H, psi_wrong, t_l) - @test_throws DimensionMismatch mesolve(H, psi_wrong, t_l) - @test_throws DimensionMismatch mcsolve(H, psi_wrong, t_l) + @test_throws DimensionMismatch mesolve(H, psi_wrong, t_l, c_ops) + @test_throws DimensionMismatch mcsolve(H, psi_wrong, t_l, c_ops) @test_throws ArgumentError sesolve(H, psi0, t_l, save_idxs = [1, 2]) - @test_throws ArgumentError mesolve(H, psi0, t_l, save_idxs = [1, 2]) - @test_throws ArgumentError mcsolve(H, psi0, t_l, save_idxs = [1, 2]) + @test_throws ArgumentError mesolve(H, psi0, t_l, c_ops, save_idxs = [1, 2]) + @test_throws ArgumentError mcsolve(H, psi0, t_l, c_ops, save_idxs = [1, 2]) end @testset "example" begin diff --git a/test/runtests.jl b/test/runtests.jl index 566104ebb..57407fb03 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,7 @@ using Pkg using QuantumToolbox using QuantumToolbox: position, momentum using Random +using SciMLOperators const GROUP = get(ENV, "GROUP", "All") @@ -21,6 +22,7 @@ core_tests = [ "permutation.jl", "progress_bar.jl", "quantum_objects.jl", + "quantum_objects_evo.jl", "states_and_operators.jl", "steady_state.jl", "time_evolution.jl", From 9589073662faf19c50dce58f13e09437c9ffab8e Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 25 Oct 2024 10:39:15 +0900 Subject: [PATCH 061/329] add `show` method for `QobjEvo` --- src/QuantumToolbox.jl | 1 + src/qobj/quantum_object.jl | 2 +- src/qobj/quantum_object_evo.jl | 22 ++++++++++++ test/core-test/quantum_objects_evo.jl | 48 +++++++++++++++------------ 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 549df5b01..424105b2c 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -35,6 +35,7 @@ import SciMLBase: import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 import SciMLOperators: AbstractSciMLOperator, + AddedOperator, MatrixOperator, ScalarOperator, IdentityOperator, diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 84e347851..3c0121dc3 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -140,7 +140,7 @@ function Base.show( return show(io, MIME("text/plain"), op_data) end -function Base.show(io::IO, QO::AbstractQuantumObject) +function Base.show(io::IO, QO::QuantumObject) op_data = QO.data println( io, diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 9d25858e0..aa43598dd 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -112,6 +112,28 @@ struct QuantumObjectEvolution{ end end +function Base.show(io::IO, QO::QuantumObjectEvolution) + op_data = QO.data + println( + io, + "Quantum Object Evo.: type=", + QO.type, + " dims=", + QO.dims, + " size=", + size(op_data), + " ishermitian=", + ishermitian(op_data), + " isconstant=", + isconstant(op_data), + " num_elements=", + _get_SciMLOperator_num_elements(op_data), + ) + return show(io, MIME("text/plain"), op_data) +end +_get_SciMLOperator_num_elements(A::AddedOperator) = length(A.ops) +_get_SciMLOperator_num_elements(A::AbstractSciMLOperator) = 1 + function QuantumObjectEvolution(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) return QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) end diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 933590e67..df22d6249 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -93,27 +93,33 @@ @test issymmetric(Z) == true end - # TODO: Implement a new show method for QuantumObjectEvolution - # @testset "REPL show" begin - # N = 10 - # a = QobjEvo(destroy(N)) - - # opstring = sprint((t, s) -> show(t, "text/plain", s), a) - # datastring = sprint((t, s) -> show(t, "text/plain", s), a.data) - # a_dims = a.dims - # a_size = size(a) - # a_isherm = isherm(a) - # @test opstring == - # "Quantum Object: type=Operator dims=$a_dims size=$a_size ishermitian=$a_isherm\n$datastring" - - # a = spre(a) - # opstring = sprint((t, s) -> show(t, "text/plain", s), a) - # datastring = sprint((t, s) -> show(t, "text/plain", s), a.data) - # a_dims = a.dims - # a_size = size(a) - # a_isherm = isherm(a) - # @test opstring == "Quantum Object: type=SuperOperator dims=$a_dims size=$a_size\n$datastring" - # end + @testset "REPL show" begin + N = 10 + a = destroy(N) + coef(p, t) = exp(-1im * t) + H = QobjEvo((a' * a, (a, coef))) + + opstring = sprint((t, s) -> show(t, "text/plain", s), H) + datastring = sprint((t, s) -> show(t, "text/plain", s), H.data) + H_dims = H.dims + H_size = size(H) + H_isherm = isherm(H) + H_isconst = isconstant(H) + H_elements = 2 + @test opstring == + "Quantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst num_elements=$$H_elements\n$datastring" + + L = QobjEvo(spre(a)) + opstring = sprint((t, s) -> show(t, "text/plain", s), L) + datastring = sprint((t, s) -> show(t, "text/plain", s), L.data) + L_dims = L.dims + L_size = size(L) + L_isherm = isherm(L) + L_isconst = isconstant(L) + H_elements = 1 + @test opstring == + "Quantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst num_elements=$$L_elements\n$datastring" + end @testset "Type Inference (QuantumObject)" begin for T in [ComplexF32, ComplexF64] From 6755f4486ada2ccc771508e7d5bfb5625924c0a7 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 25 Oct 2024 11:04:50 +0900 Subject: [PATCH 062/329] fix typo --- test/core-test/quantum_objects_evo.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index df22d6249..ff797f345 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -107,7 +107,7 @@ H_isconst = isconstant(H) H_elements = 2 @test opstring == - "Quantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst num_elements=$$H_elements\n$datastring" + "Quantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst num_elements=$H_elements\n$datastring" L = QobjEvo(spre(a)) opstring = sprint((t, s) -> show(t, "text/plain", s), L) @@ -118,7 +118,7 @@ L_isconst = isconstant(L) H_elements = 1 @test opstring == - "Quantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst num_elements=$$L_elements\n$datastring" + "Quantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst num_elements=$L_elements\n$datastring" end @testset "Type Inference (QuantumObject)" begin From 3efabd8d7d43b6978e9039857e633122120b3c43 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 25 Oct 2024 13:15:15 +0900 Subject: [PATCH 063/329] remove `num_elements` in showing `QobjEvo` --- src/qobj/quantum_object_evo.jl | 4 ---- test/core-test/quantum_objects_evo.jl | 6 ++---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index aa43598dd..8786669b5 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -126,13 +126,9 @@ function Base.show(io::IO, QO::QuantumObjectEvolution) ishermitian(op_data), " isconstant=", isconstant(op_data), - " num_elements=", - _get_SciMLOperator_num_elements(op_data), ) return show(io, MIME("text/plain"), op_data) end -_get_SciMLOperator_num_elements(A::AddedOperator) = length(A.ops) -_get_SciMLOperator_num_elements(A::AbstractSciMLOperator) = 1 function QuantumObjectEvolution(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) return QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index ff797f345..4d41df619 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -105,9 +105,8 @@ H_size = size(H) H_isherm = isherm(H) H_isconst = isconstant(H) - H_elements = 2 @test opstring == - "Quantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst num_elements=$H_elements\n$datastring" + "Quantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst\n$datastring" L = QobjEvo(spre(a)) opstring = sprint((t, s) -> show(t, "text/plain", s), L) @@ -116,9 +115,8 @@ L_size = size(L) L_isherm = isherm(L) L_isconst = isconstant(L) - H_elements = 1 @test opstring == - "Quantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst num_elements=$L_elements\n$datastring" + "Quantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst\n$datastring" end @testset "Type Inference (QuantumObject)" begin From f19741e478a23be9961e10de97542fdcc05b855f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:53:35 +0900 Subject: [PATCH 064/329] add deprecate file (#274) --- src/QuantumToolbox.jl | 3 +++ src/deprecated.jl | 15 +++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 src/deprecated.jl diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 549df5b01..a589600a4 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -102,4 +102,7 @@ include("metrics.jl") include("negativity.jl") include("steadystate.jl") +# deprecated functions +include("deprecated.jl") + end diff --git a/src/deprecated.jl b/src/deprecated.jl new file mode 100644 index 000000000..8cc4db3e5 --- /dev/null +++ b/src/deprecated.jl @@ -0,0 +1,15 @@ +#= +This file gathers all the deprecated names (structures, functions, or variables) which will be removed in the future major release. + +- Before the major release, the deprecated names will just throw errors when they are called. +- If the deprecated names were once exported, we will still export them here until next major release. +- If we decide to push a major release, cleanup this file. + +Example: + +export deprecated_foo + +function deprecated_foo(args...; kwargs...) + error("`deprecated_foo` has been deprecated and will be removed in next major release, please use `new_foo` instead.") +end +=# From f5b87fdb92de9119c8e6ce1025d694e332417c5d Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 25 Oct 2024 21:00:36 +0900 Subject: [PATCH 065/329] fix error message in `mesolve` (#273) --- src/time_evolution/mesolve.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 21b8170ab..9c7254ba4 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -46,9 +46,16 @@ function _mesolve_make_L_QobjEvo(H::Tuple, c_ops) c_ops isa Nothing && return QobjEvo(H) return QobjEvo((H..., mapreduce(op -> lindblad_dissipator(op), +, c_ops)); type = SuperOperator, f = liouvillian) end -# TODO: Add support for Operator type QobEvo -function _mesolve_make_L_QobjEvo(H::QuantumObjectEvolution, c_ops) - issuper(H) || throw(ArgumentError("The time-dependent Hamiltonian must be a SuperOperator.")) +_mesolve_make_L_QobjEvo(H::QuantumObjectEvolution{DT,OperatorQuantumObject}, c_ops) where {DT<:AbstractSciMLOperator} = + throw( + ArgumentError( + "This function does not support the data type of time-dependent Operator `H` currently. Try to provide `H` as a time-dependent SuperOperator or Tuple instead.", + ), + ) +function _mesolve_make_L_QobjEvo( + H::QuantumObjectEvolution{DT,SuperOperatorQuantumObject}, + c_ops, +) where {DT<:AbstractSciMLOperator} c_ops isa Nothing && return H return H + QobjEvo((mapreduce(op -> lindblad_dissipator(op), +, c_ops))) end From b36d18c468238f736719a48bad572e2a37399256 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 25 Oct 2024 21:01:46 +0900 Subject: [PATCH 066/329] remove unnecessary import --- src/QuantumToolbox.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 424105b2c..549df5b01 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -35,7 +35,6 @@ import SciMLBase: import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 import SciMLOperators: AbstractSciMLOperator, - AddedOperator, MatrixOperator, ScalarOperator, IdentityOperator, From b5ce558695260b2b38030ee826fa7cbd1541ccac Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 25 Oct 2024 21:28:36 +0900 Subject: [PATCH 067/329] bump version to `0.19.0` --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index acfb92ee0..5423b9627 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.18.0" +version = "0.19.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 5820791493c66813774edacc52339859976dc1ed Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 27 Oct 2024 23:04:10 +0900 Subject: [PATCH 068/329] add `SciMLOperators` to `versioninfo` (#275) --- .github/ISSUE_TEMPLATE/bug_report.yaml | 4 ++-- src/versioninfo.jl | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index ef9045062..cc2245f6e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -16,7 +16,7 @@ body: label: Code to Reproduce the Bug description: Please provide a minimal working example. Paste your code directly (It will be automatically formatted, so there's no need for backticks) placeholder: "using QuantumToolbox\nprint(qeye(2))" - render: shell + render: julia - type: textarea id: bug-output attributes: @@ -37,7 +37,7 @@ body: attributes: label: Your Environment description: Please use `QuantumToolbox.about()` or `QuantumToolbox.versioninfo()` to get the information about your environment and paste it here (automatically formatted) - placeholder: "Julia Ver. ***\nQuantumToolbox Ver. ***\nLinearSolve Ver. ***\nOrdinaryDiffEqCore Ver. ***\nOS : ***\nWORD_SIZE: ***\nLIBM : ***\nLLVM : ***\nBLAS : ***" + placeholder: "Julia Ver. ***\nQuantumToolbox Ver. ***\nSciMLOperators Ver. ***\nLinearSolve Ver. ***\nOrdinaryDiffEqCore Ver. ***\nOS : ***\nWORD_SIZE: ***\nLIBM : ***\nLLVM : ***\nBLAS : ***" render: shell validations: required: true diff --git a/src/versioninfo.jl b/src/versioninfo.jl index abe725f59..fe288536e 100644 --- a/src/versioninfo.jl +++ b/src/versioninfo.jl @@ -23,18 +23,19 @@ function versioninfo(io::IO = stdout) " Alberto Mercurio and Yi-Te Huang\n", ) - # print package informations + # print package information println( io, "Package information:\n", "====================================\n", "Julia Ver. $(VERSION)\n", "QuantumToolbox Ver. $(_get_pkg_version("QuantumToolbox"))\n", + "SciMLOperators Ver. $(_get_pkg_version("SciMLOperators"))\n", "LinearSolve Ver. $(_get_pkg_version("LinearSolve"))\n", "OrdinaryDiffEqCore Ver. $(_get_pkg_version("OrdinaryDiffEqCore"))\n", ) - # print System informations + # print System information println(io, "System information:") println(io, "====================================") println(io, """OS : $(OS_name) ($(Sys.MACHINE))""") From 8bb014ee64ed4b77f37ec1d5de272b2110639e5b Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 28 Oct 2024 22:56:02 +0100 Subject: [PATCH 069/329] Fix issue in mesolve for nothing c_ops --- src/time_evolution/mesolve.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 9c7254ba4..f191921ae 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -43,7 +43,7 @@ end _mesolve_make_L_QobjEvo(H::QuantumObject, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator) function _mesolve_make_L_QobjEvo(H::Tuple, c_ops) - c_ops isa Nothing && return QobjEvo(H) + c_ops isa Nothing && return QobjEvo(H; type = SuperOperator, f = liouvillian) return QobjEvo((H..., mapreduce(op -> lindblad_dissipator(op), +, c_ops)); type = SuperOperator, f = liouvillian) end _mesolve_make_L_QobjEvo(H::QuantumObjectEvolution{DT,OperatorQuantumObject}, c_ops) where {DT<:AbstractSciMLOperator} = From 3207e188de07cdd1e4d8f31b2d596d89a2e505dd Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:52:09 +0900 Subject: [PATCH 070/329] bump version to `0.19.1` --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5423b9627..64ddcf7ea 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.19.0" +version = "0.19.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 25bee9fed9c106ccb8e5ffa515e1eb9b1598a055 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:44:40 +0800 Subject: [PATCH 071/329] Support time-dependent `liouvillian` (#277) * support time-dependent `liouvillian` * improve codecov * format files * minor changes --- src/QuantumToolbox.jl | 2 + src/qobj/superoperators.jl | 59 +++++++++++++++------------ src/time_evolution/mesolve.jl | 18 +------- src/time_evolution/time_evolution.jl | 2 +- test/core-test/quantum_objects.jl | 7 +++- test/core-test/quantum_objects_evo.jl | 40 +++++++++++------- test/core-test/time_evolution.jl | 12 +----- 7 files changed, 72 insertions(+), 68 deletions(-) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index a589600a4..098cfb376 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -37,6 +37,8 @@ import SciMLOperators: AbstractSciMLOperator, MatrixOperator, ScalarOperator, + ScaledOperator, + AddedOperator, IdentityOperator, cache_operator, update_coefficients!, diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 08f3672a6..2408683f2 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -2,10 +2,10 @@ Functions for generating (common) quantum super-operators. =# -export spre, spost, sprepost, lindblad_dissipator +export spre, spost, sprepost, liouvillian, lindblad_dissipator # intrinsic functions for super-operators -# (keep these because they take AbstractMatrix as input) +# (keep these because they take AbstractMatrix as input and ensure the output is sparse matrix) _spre(A::AbstractMatrix, Id::AbstractMatrix) = kron(Id, sparse(A)) _spre(A::AbstractSparseMatrix, Id::AbstractMatrix) = kron(Id, A) _spost(B::AbstractMatrix, Id::AbstractMatrix) = kron(transpose(sparse(B)), Id) @@ -14,9 +14,22 @@ _sprepost(A::AbstractMatrix, B::AbstractMatrix) = kron(transpose(sparse(B)), spa _sprepost(A::AbstractMatrix, B::AbstractSparseMatrix) = kron(transpose(B), sparse(A)) _sprepost(A::AbstractSparseMatrix, B::AbstractMatrix) = kron(transpose(sparse(B)), A) _sprepost(A::AbstractSparseMatrix, B::AbstractSparseMatrix) = kron(transpose(B), A) +_liouvillian(H::AbstractMatrix, Id::AbstractMatrix) = -1im * (_spre(H, Id) - _spost(H, Id)) + +# (if input is AbstractSciMLOperator) +_spre(A::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spre(A.A, Id)) +_spre(A::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(A.λ, _spre(A.L, Id)) +_spre(A::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spre(op, Id), +, A.ops) +_spost(B::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spost(B.A, Id)) +_spost(B::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(B.λ, _spost(B.L, Id)) +_spost(B::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spost(op, Id), +, B.ops) +_liouvillian(H::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_liouvillian(H.A, Id)) +_liouvillian(H::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(H.λ, _liouvillian(H.L, Id)) +_liouvillian(H::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _liouvillian(op, Id), +, H.ops) +# TODO: support `_sprepost`, `sprepost`, and `lindblad_dissipator` for AbstractSciMLOperator (allow c_ops with Vector{QobjEvo}) @doc raw""" - spre(A::QuantumObject, Id_cache=I(size(A,1))) + spre(A::AbstractQuantumObject, Id_cache=I(size(A,1))) Returns the [`SuperOperator`](@ref) form of `A` acting on the left of the density matrix operator: ``\mathcal{O} \left(\hat{A}\right) \left[ \hat{\rho} \right] = \hat{A} \hat{\rho}``. @@ -27,14 +40,13 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r ``` (see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) -The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when -the same function is applied multiple times with a known Hilbert space dimension. +The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. """ -spre(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {T} = - QuantumObject(_spre(A.data, Id_cache), SuperOperator, A.dims) +spre(A::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {DT} = + get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator, A.dims) @doc raw""" - spost(B::QuantumObject, Id_cache=I(size(B,1))) + spost(B::AbstractQuantumObject, Id_cache=I(size(B,1))) Returns the [`SuperOperator`](@ref) form of `B` acting on the right of the density matrix operator: ``\mathcal{O} \left(\hat{B}\right) \left[ \hat{\rho} \right] = \hat{\rho} \hat{B}``. @@ -45,11 +57,10 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r ``` (see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) -The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when -the same function is applied multiple times with a known Hilbert space dimension. +The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. """ -spost(B::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {T} = - QuantumObject(_spost(B.data, Id_cache), SuperOperator, B.dims) +spost(B::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {DT} = + get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator, B.dims) @doc raw""" sprepost(A::QuantumObject, B::QuantumObject) @@ -84,21 +95,21 @@ Returns the Lindblad [`SuperOperator`](@ref) defined as \hat{O}^\dagger \hat{O} \hat{\rho} - \hat{\rho} \hat{O}^\dagger \hat{O} \right) ``` -The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when -the same function is applied multiple times with a known Hilbert space dimension. +The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. -See also [`spre`](@ref) and [`spost`](@ref). +See also [`spre`](@ref), [`spost`](@ref), and [`sprepost`](@ref). """ function lindblad_dissipator(O::QuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(O, 1))) where {DT} Od_O = O' * O - return sprepost(O, O') - spre(Od_O, Id_cache) / 2 - spost(Od_O, Id_cache) / 2 + return sprepost(O, O') - (spre(Od_O, Id_cache) + spost(Od_O, Id_cache)) / 2 end +# TODO: suppport collapse operator given as QobjEvo-type # It is already a SuperOperator lindblad_dissipator(O::QuantumObject{DT,SuperOperatorQuantumObject}, Id_cache = nothing) where {DT} = O @doc raw""" - liouvillian(H::QuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims))) + liouvillian(H::AbstractQuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims))) Construct the Liouvillian [`SuperOperator`](@ref) for a system Hamiltonian ``\hat{H}`` and a set of collapse operators ``\{\hat{C}_n\}_n``: @@ -117,20 +128,18 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref). """ function liouvillian( - H::QuantumObject{MT1,OpType1}, + H::AbstractQuantumObject{DT,OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, Id_cache = I(prod(H.dims)), -) where {MT1<:AbstractMatrix,OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, Id_cache) if !(c_ops isa Nothing) - for c_op in c_ops - L += lindblad_dissipator(c_op, Id_cache) - end + L += mapreduce(lindblad_dissipator, +, c_ops) end return L end -liouvillian(H::QuantumObject{<:AbstractMatrix,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dims))) = - -1im * (spre(H, Id_cache) - spost(H, Id_cache)) +liouvillian(H::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dims))) where {DT} = + get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator, H.dims) -liouvillian(H::QuantumObject{<:AbstractMatrix,SuperOperatorQuantumObject}, Id_cache::Diagonal) = H +liouvillian(H::AbstractQuantumObject{DT,SuperOperatorQuantumObject}, Id_cache::Diagonal) where {DT} = H diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index f191921ae..76714c579 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -42,23 +42,7 @@ function _generate_mesolve_kwargs(e_ops, progress_bar::Val{false}, tlist, kwargs end _mesolve_make_L_QobjEvo(H::QuantumObject, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator) -function _mesolve_make_L_QobjEvo(H::Tuple, c_ops) - c_ops isa Nothing && return QobjEvo(H; type = SuperOperator, f = liouvillian) - return QobjEvo((H..., mapreduce(op -> lindblad_dissipator(op), +, c_ops)); type = SuperOperator, f = liouvillian) -end -_mesolve_make_L_QobjEvo(H::QuantumObjectEvolution{DT,OperatorQuantumObject}, c_ops) where {DT<:AbstractSciMLOperator} = - throw( - ArgumentError( - "This function does not support the data type of time-dependent Operator `H` currently. Try to provide `H` as a time-dependent SuperOperator or Tuple instead.", - ), - ) -function _mesolve_make_L_QobjEvo( - H::QuantumObjectEvolution{DT,SuperOperatorQuantumObject}, - c_ops, -) where {DT<:AbstractSciMLOperator} - c_ops isa Nothing && return H - return H + QobjEvo((mapreduce(op -> lindblad_dissipator(op), +, c_ops))) -end +_mesolve_make_L_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}, c_ops) = liouvillian(QobjEvo(H), c_ops) @doc raw""" mesolveProblem( diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 90284b50c..9297cbedd 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -1,6 +1,6 @@ export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionSSESol -export liouvillian, liouvillian_floquet, liouvillian_generalized +export liouvillian_floquet, liouvillian_generalized const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true) const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index b472e0fef..be24e3bfb 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -65,10 +65,15 @@ end @testset "Operator and SuperOperator" begin + N = 10 + A = Qobj(rand(ComplexF64, N, N)) + B = Qobj(rand(ComplexF64, N, N)) + ρ = rand_dm(N) # random density matrix + @test mat2vec(A * ρ * B) ≈ spre(A) * spost(B) * mat2vec(ρ) ≈ sprepost(A, B) * mat2vec(ρ) # we must make sure this equality holds ! + a = sprand(ComplexF64, 100, 100, 0.1) a2 = Qobj(a) a3 = Qobj(a, type = SuperOperator) - @test isket(a2) == false @test isbra(a2) == false @test isoper(a2) == true diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 4d41df619..4cad02526 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -160,25 +160,37 @@ # @test X_warn == X3 # end - @testset "Time Dependent Operators" begin + @testset "Time Dependent Operators and SuperOperators" begin N = 10 a = destroy(N) coef1(p, t) = exp(-1im * p.ω1 * t) coef2(p, t) = sin(p.ω2 * t) + t = rand() + p = (ω1 = rand(), ω2 = rand()) + + # Operator + H_td = QobjEvo(((a, coef1), a' * a, (a', coef2))) + H_ti = coef1(p, t) * a + a' * a + coef2(p, t) * a' + ψ = rand_ket(N) + @test H_td(p, t) ≈ H_ti + @test H_td(ψ, p, t) ≈ H_ti * ψ + @test isconstant(a) == true + @test isconstant(H_td) == false + @test isconstant(QobjEvo(a)) == true + @test isoper(H_td) == true + + # SuperOperator + c_ops = [sqrt(rand()) * a] + L_td = liouvillian(H_td, c_ops) + L_td2 = -1im * spre(H_td) + 1im * spost(H_td) + lindblad_dissipator(c_ops[1]) + ρvec = mat2vec(rand_dm(N)) + @test L_td(p, t) ≈ L_td2(p, t) + @test L_td(ρvec, p, t) ≈ L_td2(ρvec, p, t) + @test isconstant(L_td) == false + @test issuper(L_td) == true @test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]]) - - op1 = QobjEvo(((a, coef1), a' * a, (a', coef2))) - op1 = QobjEvo(((a, coef1), a' * a, (a', coef2))) - - p = (ω1 = 1, ω2 = 2) - @test op1(p, 0.1) ≈ coef1(p, 0.1) * a + a' * a + coef2(p, 0.1) * a' - - ψ = fock(N, 1) - @test op1(ψ, p, 0.1) ≈ (coef1(p, 0.1) * a + a' * a + coef2(p, 0.1) * a') * ψ - - @test isconstant(a) == true - @test isconstant(op1) == false - @test isconstant(Qobj(a)) == true + @test_throws ArgumentError H_td(ρvec, p, t) + @test_throws ArgumentError L_td(ψ, p, t) end end diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 666aa38ab..fd7a33f91 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -205,18 +205,9 @@ # @test sol_sse.expect ≈ sol_sse_td.expect atol = 1e-2 * length(tlist) H_td2 = QobjEvo(H_td) - L_td = QobjEvo(H_td, type = SuperOperator, f = liouvillian) + L_td = liouvillian(H_td2) sol_se_td2 = sesolve(H_td2, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) - @test_throws ArgumentError mesolve( - H_td2, - ψ0, - tlist, - c_ops, - e_ops = e_ops, - progress_bar = Val(false), - params = p, - ) sol_me_td2 = mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) sol_mc_td2 = mcsolve( H_td2, @@ -253,6 +244,7 @@ @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, (a, a'), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple @inferred mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + @inferred mesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) @inferred mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) end From dea0bd7d3d55e3da7ae9dbb2ef3c3ed54bfb5ae2 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:15:39 +0100 Subject: [PATCH 072/329] Clean low-rank mesolve and add Documentation bibliography (#269) * change Lattice structure * Change lr_mesolve and add documentation * Fix Documentation errors --- docs/Project.toml | 1 + docs/make.jl | 5 + docs/src/api.md | 38 ++- docs/src/bibliography.bib | 14 + docs/src/bibliography.md | 8 + docs/src/tutorials/lowrank.md | 77 ++--- src/spin_lattice.jl | 115 +++++--- src/time_evolution/lr_mesolve.jl | 425 +++++++++++++++------------- test/core-test/low_rank_dynamics.jl | 77 ++--- 9 files changed, 447 insertions(+), 313 deletions(-) create mode 100644 docs/src/bibliography.bib create mode 100644 docs/src/bibliography.md diff --git a/docs/Project.toml b/docs/Project.toml index 88173fe01..b61136e89 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -2,4 +2,5 @@ BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" diff --git a/docs/make.jl b/docs/make.jl index d0a631ad6..375ffec53 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -3,11 +3,14 @@ using QuantumToolbox using Documenter +using DocumenterCitations DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) const DRAFT = false # set `true` to disable cell evaluation +bib = CitationBibliography(joinpath(@__DIR__, "src", "bibliography.bib"), style=:authoryear) + const MathEngine = MathJax3( Dict( :loader => Dict("load" => ["[tex]/physics"]), @@ -58,6 +61,7 @@ const PAGES = [ ], ], "API" => "api.md", + "Bibliography" => "bibliography.md", # "Change Log" => "changelog.md", ] @@ -76,6 +80,7 @@ makedocs(; size_threshold_ignore = ["api.md"], ), draft = DRAFT, + plugins = [bib], ) deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", devbranch = "main") diff --git a/docs/src/api.md b/docs/src/api.md index 554d1e8b0..21cc00a6c 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -190,17 +190,18 @@ mcsolveProblem mcsolveEnsembleProblem ssesolveProblem ssesolveEnsembleProblem -lr_mesolveProblem sesolve mesolve mcsolve ssesolve dfd_mesolve -dsf_mesolve -dsf_mcsolve -lr_mesolve liouvillian liouvillian_generalized +``` + +### [Steady State Solvers](@id doc-API:Steady-State-Solvers) + +```@docs steadystate steadystate_floquet SteadyStateDirectSolver @@ -209,6 +210,21 @@ SteadyStateLinearSolver SteadyStateODESolver ``` +### [Dynamical Shifted Fock method](@id doc-API:Dynamical-Shifted-Fock-method) + +```@docs +dsf_mesolve +dsf_mcsolve +``` + +### [Low-rank time evolution](@id doc-API:Low-rank-time-evolution) + +```@docs +TimeEvolutionLRSol +lr_mesolveProblem +lr_mesolve +``` + ## [Correlations and Spectrum](@id doc-API:Correlations-and-Spectrum) ```@docs @@ -227,6 +243,14 @@ tracedist fidelity ``` +## [Spin Lattice](@id doc-API:Spin-Lattice) + +```@docs +Lattice +SingleSiteOperator +DissipativeIsing +``` + ## [Miscellaneous](@id doc-API:Miscellaneous) ```@docs @@ -251,10 +275,4 @@ PhysicalConstants convert_unit row_major_reshape meshgrid -_calculate_expectation! -_adjM_condition_variational -_adjM_affect! -_adjM_condition_ratio -_pinv! -dBdz! ``` diff --git a/docs/src/bibliography.bib b/docs/src/bibliography.bib new file mode 100644 index 000000000..727766d5f --- /dev/null +++ b/docs/src/bibliography.bib @@ -0,0 +1,14 @@ +@article{gravina2024adaptive, + title = {{Adaptive variational low-rank dynamics for open quantum systems}}, + author = {Gravina, Luca and Savona, Vincenzo}, + journal = {Phys. Rev. Res.}, + volume = {6}, + issue = {2}, + pages = {023072}, + numpages = {18}, + year = {2024}, + month = {Apr}, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevResearch.6.023072}, + url = {https://link.aps.org/doi/10.1103/PhysRevResearch.6.023072} +} diff --git a/docs/src/bibliography.md b/docs/src/bibliography.md new file mode 100644 index 000000000..74d56db57 --- /dev/null +++ b/docs/src/bibliography.md @@ -0,0 +1,8 @@ +```@meta +CurrentModule = QuantumToolbox +``` + +# [Bibliography](@id doc:Bibliography) + +```@bibliography +``` diff --git a/docs/src/tutorials/lowrank.md b/docs/src/tutorials/lowrank.md index 030446dd4..0a3c6c86b 100644 --- a/docs/src/tutorials/lowrank.md +++ b/docs/src/tutorials/lowrank.md @@ -1,5 +1,19 @@ # [Low rank master equation](@id doc-tutor:Low-rank-master-equation) +In this tutorial, we will show how to solve the master equation using the low-rank method. For a detailed explaination of the method, we recommend to read the article [gravina2024adaptive](@cite). + +As a test, we will consider the dissipative Ising model with a transverse field. The Hamiltonian is given by + +```math +\hat{H} = \frac{J_x}{2} \sum_{\langle i,j \rangle} \sigma_i^x \sigma_j^x + \frac{J_y}{2} \sum_{\langle i,j \rangle} \sigma_i^y \sigma_j^y + \frac{J_z}{2} \sum_{\langle i,j \rangle} \sigma_i^z \sigma_j^z - \sum_i h_i \sigma_i^z + h_x \sum_i \sigma_i^x + h_y \sum_i \sigma_i^y + h_z \sum_i \sigma_i^z, +``` + +where the sums are over nearest neighbors, and the collapse operators are given by + +```math +c_i = \sqrt{\gamma} \sigma_i^-. +``` + We start by importing the packages ```@example lowrank @@ -21,42 +35,42 @@ Define lr-space dimensions N_cut = 2 # Number of states of each mode N_modes = latt.N # Number of modes N = N_cut^N_modes # Total number of states -M = Nx * Ny + 1 # Number of states in the LR basis +M = latt.N + 1 # Number of states in the LR basis ``` Define lr states. Take as initial state all spins up. All other N states are taken as those with miniman Hamming distance to the initial state. ```@example lowrank -ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject}}(undef, M) -ϕ[1] = kron(repeat([basis(2, 0)], N_modes)...) +ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject,M-1}}(undef, M) +ϕ[1] = kron(fill(basis(2, 1), N_modes)...) -global i = 1 +i = 1 for j in 1:N_modes global i += 1 - i <= M && (ϕ[i] = mb(sp, j, latt) * ϕ[1]) + i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), j, latt) * ϕ[1]) end for k in 1:N_modes-1 for l in k+1:N_modes global i += 1 - i <= M && (ϕ[i] = mb(sp, k, latt) * mb(sp, l, latt) * ϕ[1]) + i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), k, latt) * SingleSiteOperator(sigmap(), l, latt) * ϕ[1]) end end for i in i+1:M ϕ[i] = QuantumObject(rand(ComplexF64, size(ϕ[1])[1]), dims = ϕ[1].dims) normalize!(ϕ[i]) end +nothing # hide ``` Define the initial state ```@example lowrank -z = hcat(broadcast(x -> x.data, ϕ)...) -p0 = 0.0 # Population of the lr states other than the initial state -B = Matrix(Diagonal([1 + 0im; p0 * ones(M - 1)])) +z = hcat(get_data.(ϕ)...) +B = Matrix(Diagonal([1 + 0im; zeros(M - 1)])) S = z' * z # Overlap matrix B = B / tr(S * B) # Normalize B -ρ = QuantumObject(z * B * z', dims = ones(Int, N_modes) * N_cut); # Full density matrix +ρ = QuantumObject(z * B * z', dims = ntuple(i->N_cut, Val(N_modes))); # Full density matrix ``` Define the Hamiltonian and collapse operators @@ -67,26 +81,26 @@ Jx = 0.9 Jy = 1.04 Jz = 1.0 hx = 0.0 +hy = 0.0 +hz = 0.0 γ = 1 -Sx = sum([mb(sx, i, latt) for i in 1:latt.N]) -Sy = sum([mb(sy, i, latt) for i in 1:latt.N]) -Sz = sum([mb(sz, i, latt) for i in 1:latt.N]) -SFxx = sum([mb(sx, i, latt) * mb(sx, j, latt) for i in 1:latt.N for j in 1:latt.N]) +Sx = mapreduce(i->SingleSiteOperator(sigmax(), i, latt), +, 1:latt.N) +Sy = mapreduce(i->SingleSiteOperator(sigmay(), i, latt), +, 1:latt.N) +Sz = mapreduce(i->SingleSiteOperator(sigmaz(), i, latt), +, 1:latt.N) -H, c_ops = TFIM(Jx, Jy, Jz, hx, γ, latt; bc = pbc, order = 1) -e_ops = (Sx, Sy, Sz, SFxx) +H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) +e_ops = (Sx, Sy, Sz) -tl = LinRange(0, 10, 100); +tl = range(0, 10, 100) +nothing # hide ``` ### Full evolution ```@example lowrank -@time mesol = mesolve(H, ρ, tl, c_ops; e_ops = [e_ops...]); -A = Matrix(mesol.states[end].data) -λ = eigvals(Hermitian(A)) -Strue = -sum(λ .* log2.(λ)) / latt.N; +sol_me = mesolve(H, ρ, tl, c_ops; e_ops = [e_ops...]); +Strue = entropy_vn(sol_me.states[end], base=2) / latt.N ``` ### Low Rank evolution @@ -120,26 +134,23 @@ function f_entropy(p, z, B) mul!(C, z, sqrt(B)) mul!(σ, C', C) - λ = eigvals(Hermitian(σ)) - λ = λ[λ.>1e-10] - return -sum(λ .* log2.(λ)) -end; + return entropy_vn(Qobj(Hermitian(σ), type=Operator), base=2) +end ``` Define the options for the low-rank evolution ```@example lowrank -opt = - LRMesolveOptions(err_max = 1e-3, p0 = 0.0, atol_inv = 1e-6, adj_condition = "variational", Δt = 0.0); +opt = (err_max = 1e-3, p0 = 0.0, atol_inv = 1e-6, adj_condition = "variational", Δt = 0.0); -@time lrsol = lr_mesolve(H, z, B, tl, c_ops; e_ops = e_ops, f_ops = (f_purity, f_entropy, f_trace), opt = opt); +sol_lr = lr_mesolve(H, z, B, tl, c_ops; e_ops = e_ops, f_ops = (f_purity, f_entropy, f_trace), opt = opt); ``` Plot the results ```@example lowrank -m_me = real(mesol.expect[3, :]) / Nx / Ny -m_lr = real(lrsol.expvals[3, :]) / Nx / Ny +m_me = real(sol_me.expect[3, :]) / Nx / Ny +m_lr = real(sol_lr.expect[3, :]) / Nx / Ny fig = Figure(size = (500, 350), fontsize = 15) ax = Axis(fig[1, 1], xlabel = L"\gamma t", ylabel = L"M_{z}", xlabelsize = 20, ylabelsize = 20) @@ -148,17 +159,17 @@ lines!(ax, tl, m_me, label = "Fock", linewidth = 2, linestyle = :dash) axislegend(ax, position = :rb) ax2 = Axis(fig[1, 2], xlabel = L"\gamma t", ylabel = "Value", xlabelsize = 20, ylabelsize = 20) -lines!(ax2, tl, 1 .- real(lrsol.funvals[1, :]), label = L"$1-P$", linewidth = 2) +lines!(ax2, tl, 1 .- real(sol_lr.fexpect[1, :]), label = L"$1-P$", linewidth = 2) lines!( ax2, tl, - 1 .- real(lrsol.funvals[3, :]), + 1 .- real(sol_lr.fexpect[3, :]), label = L"$1-\mathrm{Tr}(\rho)$", linewidth = 2, linestyle = :dash, color = :orange, ) -lines!(ax2, tl, real(lrsol.funvals[2, :]) / Nx / Ny, color = :blue, label = L"S", linewidth = 2) +lines!(ax2, tl, real(sol_lr.fexpect[2, :]) / Nx / Ny, color = :blue, label = L"S", linewidth = 2) hlines!(ax2, [Strue], color = :blue, linestyle = :dash, linewidth = 2, label = L"S^{\,\mathrm{true}}_{\mathrm{ss}}") axislegend(ax2, position = :rb) diff --git a/src/spin_lattice.jl b/src/spin_lattice.jl index 26aa48ae0..192422e72 100644 --- a/src/spin_lattice.jl +++ b/src/spin_lattice.jl @@ -1,12 +1,10 @@ -export Lattice, mb, TFIM, nn, sx, sy, sz, sm, sp, pbc, obc +export Lattice, SingleSiteOperator, DissipativeIsing -sx = sigmax() -sy = -sigmay() -sz = -sigmaz() -sm = (sx - 1im * sy) / 2 -sp = (sx + 1im * sy) / 2 +@doc raw""" + Lattice -#Lattice structure +A Julia constructor for a lattice object. The lattice object is used to define the geometry of the lattice. `Nx` and `Ny` are the number of sites in the x and y directions, respectively. `N` is the total number of sites. `lin_idx` is a `LinearIndices` object and `car_idx` is a `CartesianIndices` object, and they are used to efficiently select sites on the lattice. +""" Base.@kwdef struct Lattice{TN<:Integer,TLI<:LinearIndices,TCI<:CartesianIndices} Nx::TN Ny::TN @@ -16,47 +14,100 @@ Base.@kwdef struct Lattice{TN<:Integer,TLI<:LinearIndices,TCI<:CartesianIndices} end #Definition of many-body operators -function mb(s::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, i::Integer, N::Integer) where {T1} - T = s.dims[1] - return QuantumObject(kron(eye(T^(i - 1)), s, eye(T^(N - i))); dims = ntuple(j -> 2, Val(N))) +@doc raw""" + SingleSiteOperator(O::QuantumObject, i::Integer, N::Integer) + +A Julia constructor for a single-site operator. `s` is the operator acting on the site. `i` is the site index, and `N` is the total number of sites. The function returns a `QuantumObject` given by ``\\mathbb{1}^{\\otimes (i - 1)} \\otimes \hat{O} \\otimes \\mathbb{1}^{\\otimes (N - i)}``. +""" +function SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, i::Integer, N::Integer) where {DT} + T = O.dims[1] + return QuantumObject(kron(eye(T^(i - 1)), O, eye(T^(N - i))); dims = ntuple(j -> 2, Val(N))) end -mb(s::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, i::Integer, latt::Lattice) where {T1} = mb(s, i, latt.N) -mb(s::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, row::Integer, col::Integer, latt::Lattice) where {T1} = - mb(s, latt.idx[row, col], latt.N) -mb(s::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, x::CartesianIndex, latt::Lattice) where {T1} = - mb(s, latt.idx[x], latt.N) +SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, i::Integer, latt::Lattice) where {DT} = + SingleSiteOperator(O, i, latt.N) +SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, row::Integer, col::Integer, latt::Lattice) where {DT} = + SingleSiteOperator(O, latt.idx[row, col], latt.N) +SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, x::CartesianIndex, latt::Lattice) where {DT} = + SingleSiteOperator(O, latt.idx[x], latt.N) #Definition of nearest-neighbour sites on lattice -pbc(i::Integer, N::Integer) = 1 + (i - 1 + N) % N -obc(i::Integer, N::Integer) = (i >= 1 && i <= N) -pbc(i::Vector{Int}, N::Integer) = pbc.(i, N) -obc(i::Vector{Int}, N::Integer) = filter(x -> obc(x, N), i) - -function nn(i::CartesianIndex, latt::Lattice, bc::Function; order::Integer = 1) - row = bc([i[1] + order, i[1] - order], latt.Nx) - col = bc([i[2] + order, i[2] - order], latt.Ny) +periodic_boundary_conditions(i::Integer, N::Integer) = 1 + (i - 1 + N) % N +open_boundary_conditions(i::Integer, N::Integer) = (i >= 1 && i <= N) +periodic_boundary_conditions(i::Vector{Int}, N::Integer) = periodic_boundary_conditions.(i, N) +open_boundary_conditions(i::Vector{Int}, N::Integer) = filter(x -> open_boundary_conditions(x, N), i) + +function nearest_neighbor(i::CartesianIndex, latt::Lattice, ::Val{:periodic_bc}; order::Integer = 1) + row = periodic_boundary_conditions([i[1] + order, i[1] - order], latt.Nx) + col = periodic_boundary_conditions([i[2] + order, i[2] - order], latt.Ny) + return vcat([CartesianIndex(r, i[2]) for r in row], [CartesianIndex(i[1], c) for c in col]) +end + +function nearest_neighbor(i::CartesianIndex, latt::Lattice, ::Val{:open_bc}; order::Integer = 1) + row = periodic_boundary_conditions([i[1] + order, i[1] - order], latt.Nx) + col = periodic_boundary_conditions([i[2] + order, i[2] - order], latt.Ny) return vcat([CartesianIndex(r, i[2]) for r in row], [CartesianIndex(i[1], c) for c in col]) end -function TFIM(Jx::Real, Jy::Real, Jz::Real, hx::Real, γ::Real, latt::Lattice; bc::Function = pbc, order::Integer = 1) - S = [mb(sm, i, latt) for i in 1:latt.N] +@doc """ + DissipativeIsing(Jx::Real, Jy::Real, Jz::Real, hx::Real, hy::Real, hz::Real, γ::Real, latt::Lattice; boundary_condition::Union{Symbol, Val} = Val(:periodic_bc), order::Integer = 1) + +A Julia constructor for a dissipative Ising model. The function returns the Hamiltonian + +```math +\\hat{H} = \\frac{J_x}{2} \\sum_{\\langle i, j \\rangle} \\hat{\\sigma}_i^x \\hat{\\sigma}_j^x + \\frac{J_y}{2} \\sum_{\\langle i, j \\rangle} \\hat{\\sigma}_i^y \\hat{\\sigma}_j^y + \\frac{J_z}{2} \\sum_{\\langle i, j \\rangle} \\hat{\\sigma}_i^z \\hat{\\sigma}_j^z + h_x \\sum_i \\hat{\\sigma}_i^x +``` + +and the collapse operators + +```math +\\hat{c}_i = \\sqrt{\\gamma} \\hat{\\sigma}_i^- +``` + +# Arguments +- `Jx::Real`: The coupling constant in the x-direction. +- `Jy::Real`: The coupling constant in the y-direction. +- `Jz::Real`: The coupling constant in the z-direction. +- `hx::Real`: The magnetic field in the x-direction. +- `hy::Real`: The magnetic field in the y-direction. +- `hz::Real`: The magnetic field in the z-direction. +- `γ::Real`: The local dissipation rate. +- `latt::Lattice`: A [`Lattice`](@ref) object that defines the geometry of the lattice. +- `boundary_condition::Union{Symbol, Val}`: The boundary conditions of the lattice. The possible inputs are `periodic_bc` and `open_bc`, for periodic or open boundary conditions, respectively. The default value is `Val(:periodic_bc)`. +- `order::Integer`: The order of the nearest-neighbour sites. The default value is 1. +""" +function DissipativeIsing( + Jx::Real, + Jy::Real, + Jz::Real, + hx::Real, + hy::Real, + hz::Real, + γ::Real, + latt::Lattice; + boundary_condition::Union{Symbol,Val} = Val(:periodic_bc), + order::Integer = 1, +) + S = [SingleSiteOperator(sigmam(), i, latt) for i in 1:latt.N] c_ops = sqrt(γ) .* S - op_sum(S, i::CartesianIndex) = S[latt.lin_idx[i]] * sum(S[latt.lin_idx[nn(i, latt, bc; order = order)]]) + op_sum(S, i::CartesianIndex) = + S[latt.lin_idx[i]] * sum(S[latt.lin_idx[nearest_neighbor(i, latt, makeVal(boundary_condition); order = order)]]) H = 0 if (Jx != 0 || hx != 0) - S .= [mb(sx, i, latt) for i in 1:latt.N] + S = [SingleSiteOperator(sigmax(), i, latt) for i in 1:latt.N] H += Jx / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) #/2 because we are double counting H += hx * sum(S) end - if Jy != 0 - S .= [mb(sy, i, latt) for i in 1:latt.N] + if (Jy != 0 || hy != 0) + S = [SingleSiteOperator(sigmay(), i, latt) for i in 1:latt.N] H += Jy / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) + H += hy * sum(S) end - if Jz != 0 - S .= [mb(sz, i, latt) for i in 1:latt.N] + if (Jz != 0 || hz != 0) + S = [SingleSiteOperator(sigmaz(), i, latt) for i in 1:latt.N] H += Jz / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) + H += hz * sum(S) end return H, c_ops -end; +end diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index 4d530bb36..8b71e94d9 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -1,56 +1,59 @@ -export lr_mesolve, lr_mesolveProblem, LRTimeEvolutionSol, LRMesolveOptions +export lr_mesolve, lr_mesolveProblem, TimeEvolutionLRSol -#=======================================================# -# STRUCT DEFINITIONS -#=======================================================# - -struct LRTimeEvolutionSol{TT<:Vector{<:Real},TS<:AbstractVector,TE<:Matrix{ComplexF64},TM<:Vector{<:Integer}} +@doc raw""" + struct TimeEvolutionLRSol + +A structure storing the results and some information from solving low-rank master equation time evolution. + +# Fields (Attributes) + +- `times::AbstractVector`: The time list of the evolution. +- `states::Vector{QuantumObject}`: The list of result states. +- `expect::Matrix`: The expectation values corresponding to each time point in `times`. +- `fexpect::Matrix`: The function values at each time point. +- `retcode`: The return code from the solver. +- `alg`: The algorithm which is used during the solving process. +- `abstol::Real`: The absolute tolerance which is used during the solving process. +- `reltol::Real`: The relative tolerance which is used during the solving process. +- `z::Vector{QuantumObject}`: The `z`` matrix of the low-rank algorithm at each time point. +- `B::Vector{QuantumObject}`: The `B` matrix of the low-rank algorithm at each time point. +""" +struct TimeEvolutionLRSol{ + TT<:AbstractVector{<:Real}, + TS<:AbstractVector, + TE<:Matrix{ComplexF64}, + RetT<:Enum, + AlgT<:OrdinaryDiffEqAlgorithm, + AT<:Real, + RT<:Real, + TSZB<:AbstractVector, + TM<:Vector{<:Integer}, +} times::TT - z::TS - B::TS - expvals::TE - funvals::TE + states::TS + expect::TE + fexpect::TE + retcode::RetT + alg::AlgT + abstol::AT + reltol::RT + z::TSZB + B::TSZB M::TM end -struct LRMesolveOptions{AlgType<:OrdinaryDiffEqAlgorithm} - alg::AlgType - progress::Bool - err_max::Real - p0::Real - atol_inv::Real - M_max::Integer - compute_Si::Bool - is_dynamical::Bool - adj_condition::String - Δt::Real -end - -function LRMesolveOptions(; - alg::OrdinaryDiffEqAlgorithm = Tsit5(), - progress::Bool = true, - err_max::Real = 0.0, - p0::Real = 0.0, - atol_inv::Real = 1e-4, - M_max::Integer = typemax(Int), - compute_Si::Bool = true, - _is_dynamical::Bool = err_max > 0, - adj_condition::String = "variational", - Δt::Real = 0.0, +lr_mesolve_options_default = ( + alg = Tsit5(), + progress = true, + err_max = 0.0, + p0 = 0.0, + atol_inv = 1e-4, + M_max = typemax(Int), + compute_Si = true, + is_dynamical = false, + adj_condition = "variational", + Δt = 0.0, ) - return LRMesolveOptions{typeof(alg)}( - alg, - progress, - err_max, - p0, - atol_inv, - M_max, - compute_Si, - _is_dynamical, - adj_condition, - Δt, - ) -end #=======================================================# # ADDITIONAL FUNCTIONS @@ -58,25 +61,22 @@ end select(x::Real, xarr::AbstractArray, retval = false) = retval ? xarr[argmin(abs.(x .- xarr))] : argmin(abs.(x .- xarr)) -@doc raw""" - _pinv!(A, T1, T2; atol::Real=0.0, rtol::Real=(eps(real(float(oneunit(T))))*min(size(A)...))*iszero(atol)) where T - Computes the pseudo-inverse of a matrix A, and stores it in T1. If T2 is provided, it is used as a temporary matrix. - The algorithm is based on the SVD decomposition of A, and is taken from the Julia package LinearAlgebra. - The difference with respect to the original function is that the cutoff is done with a smooth function instead of a step function. - - Parameters - ---------- - A : AbstractMatrix{T} - The matrix to be inverted. - T1 : AbstractMatrix{T} - T2 : AbstractMatrix{T} - Temporary matrices used in the calculation. - atol : Real - Absolute tolerance for the calculation of the pseudo-inverse. - rtol : Real - Relative tolerance for the calculation of the pseudo-inverse. -""" -function _pinv!( +#= + _pinv_smooth!(A, T1, T2; atol::Real=0.0, rtol::Real=(eps(real(float(oneunit(T))))*min(size(A)...))*iszero(atol)) where T + +Computes the pseudo-inverse of a matrix A, and stores it in T1. If T2 is provided, it is used as a temporary matrix. +The algorithm is based on the SVD decomposition of A, and is taken from the Julia package LinearAlgebra. +The difference with respect to the original function is that the cutoff is done with a smooth function instead of a step function. + +# Arguments + + - `A::AbstractMatrix`: The matrix to be inverted. + - `T1::AbstractMatrix`: The matrix where the pseudo-inverse is stored. + - `T2::AbstractMatrix`: A temporary matrix. + - `atol::Real`: The absolute tolerance. + - `rtol::Real`: The relative tolerance. +=# +function _pinv_smooth!( A::AbstractMatrix{T}, T1::AbstractMatrix{T}, T2::AbstractMatrix{T}; @@ -98,22 +98,19 @@ function _pinv!( return mul!(T1, SVD.Vt', T2) end -@doc raw""" - _calculate_expectation!(p,z,B,idx) where T - Calculates the expectation values and function values of the operators and functions in p.e_ops and p.f_ops, respectively, and stores them in p.expvals and p.funvals. - The function is called by the callback _save_affect_lr_mesolve!. - - Parameters - ---------- - p : NamedTuple - The parameters of the problem. - z : AbstractMatrix{T} - The z matrix. - B : AbstractMatrix{T} - The B matrix. - idx : Integer - The index of the current time step. -""" +#= + _calculate_expectation!(p,z,B,idx) + +Calculates the expectation values and function values of the operators and functions in p.e_ops and p.f_ops, respectively, and stores them in p.expvals and p.funvals. +The function is called by the callback _save_affect_lr_mesolve!. + +# Arguments + + - `p::NamedTuple`: The parameters of the problem. + - `z::AbstractMatrix`: The z matrix of the low-rank algorithm. + - `B::AbstractMatrix`: The B matrix of the low-rank algorithm. + - `idx::Integer`: The index of the current time step. +=# function _calculate_expectation!(p, z, B, idx) e_ops = p.e_ops f_ops = p.f_ops @@ -169,20 +166,18 @@ end # CALLBACK FUNCTIONS #=======================================================# -@doc raw""" - _adjM_condition_ratio(u, t, integrator) where T - Condition for the dynamical rank adjustment based on the ratio between the smallest and largest eigenvalues of the density matrix. - The spectrum of the density matrix is calculated efficiently using the properties of the SVD decomposition of the matrix. - - Parameters - ---------- - u : AbstractVector{T} - The current state of the system. - t : Real - The current time. - integrator : ODEIntegrator - The integrator of the problem. -""" +#= + _adjM_condition_ratio(u, t, integrator) + +Condition for the dynamical rank adjustment based on the ratio between the smallest and largest eigenvalues of the density matrix. +The spectrum of the density matrix is calculated efficiently using the properties of the SVD decomposition of the matrix. + +# Arguments + + - `u::AbstractVector`: The current state of the system. + - `t::Real`: The current time. + - `integrator::ODEIntegrator`: The integrator of the problem. +=# function _adjM_condition_ratio(u, t, integrator) ip = integrator.p opt = ip.opt @@ -200,19 +195,17 @@ function _adjM_condition_ratio(u, t, integrator) return (err >= opt.err_max && M < N && M < opt.M_max) end -@doc raw""" - _adjM_condition_variational(u, t, integrator) where T - Condition for the dynamical rank adjustment based on the leakage out of the low-rank manifold. - - Parameters - ---------- - u : AbstractVector{T} - The current state of the system. - t : Real - The current time. - integrator : ODEIntegrator - The integrator of the problem. -""" +#= + _adjM_condition_variational(u, t, integrator) + +Condition for the dynamical rank adjustment based on the leakage out of the low-rank manifold. + +# Arguments + + - `u::AbstractVector`: The current state of the system. + - `t::Real`: The current time. + - `integrator::ODEIntegrator`: The integrator of the problem. +=# function _adjM_condition_variational(u, t, integrator) ip = integrator.p opt = ip.opt @@ -222,16 +215,16 @@ function _adjM_condition_variational(u, t, integrator) return (err >= opt.err_max && M < N && M < opt.M_max) end -@doc raw""" +#= _adjM_affect!(integrator) - Affect function for the dynamical rank adjustment. It increases the rank of the low-rank manifold by one, and updates the matrices accordingly. - If Δt>0, it rewinds the integrator to the previous time step. - Parameters - ---------- - integrator : ODEIntegrator - The integrator of the problem. -""" +Affect function for the dynamical rank adjustment. It increases the rank of the low-rank manifold by one, and updates the matrices accordingly. +If Δt>0, it rewinds the integrator to the previous time step. + +# Arguments + + - `integrator::ODEIntegrator`: The integrator of the problem. +=# function _adjM_affect!(integrator) ip = integrator.p opt = ip.opt @@ -265,8 +258,10 @@ function _adjM_affect!(integrator) ), ) mul!(integrator.p.S, z', z) - !(opt.compute_Si) && - (integrator.p.Si .= _pinv!(Hermitian(integrator.p.S), integrator.temp_MM, integrator.L, atol = opt.atol_inv)) + !(opt.compute_Si) && ( + integrator.p.Si .= + _pinv_smooth!(Hermitian(integrator.p.S), integrator.temp_MM, integrator.L, atol = opt.atol_inv) + ) if Δt > 0 integrator.p = merge(integrator.p, (u_save = copy(integrator.u),)) @@ -286,21 +281,18 @@ end # DYNAMICAL EVOLUTION EQUATIONS #=======================================================# -@doc raw""" - dBdz!(du, u, p, t) where T - Dynamical evolution equations for the low-rank manifold. The function is called by the ODEProblem. - - Parameters - ---------- - du : AbstractVector{T} - The derivative of the state of the system. - u : AbstractVector{T} - The current state of the system. - p : NamedTuple - The parameters of the problem. - t : Real - The current time. -""" +#= + dBdz!(du, u, p, t) + +Dynamical evolution equations for the low-rank manifold. The function is called by the ODEProblem. + +# Arguments + + - `du::AbstractVector`: The derivative of the state vector. + - `u::AbstractVector`: The state vector. + - `p::NamedTuple`: The parameters of the problem. + - `t::Real`: The current time. +=# function dBdz!(du, u, p, t) #NxN H, Γ = p.H, p.Γ @@ -326,8 +318,8 @@ function dBdz!(du, u, p, t) mul!(A0, z, B) # Calculate inverse - opt.compute_Si && (Si .= _pinv!(Hermitian(S), temp_MM, L, atol = opt.atol_inv)) - Bi .= _pinv!(Hermitian(B), temp_MM, L, atol = opt.atol_inv) + opt.compute_Si && (Si .= _pinv_smooth!(Hermitian(S), temp_MM, L, atol = opt.atol_inv)) + Bi .= _pinv_smooth!(Hermitian(B), temp_MM, L, atol = opt.atol_inv) # Calculate the effective Hamiltonian part of L_tilde mul!(dz, H, A0) @@ -360,49 +352,59 @@ function dBdz!(du, u, p, t) return dB .-= temp_MM end +#=======================================================# +# OUTPUT GENNERATION +#=======================================================# + +get_z(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, 1:M*N), N, M) + +get_B(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, (M*N+1):length(u)), M, M) + #=======================================================# # PROBLEM FORMULATION #=======================================================# @doc raw""" - lr_mesolveProblem(H, z, B, tlist, c_ops; e_ops=(), f_ops=(), opt=LRMesolveOptions(), kwargs...) where T - Formulates the ODEproblem for the low-rank time evolution of the system. The function is called by lr_mesolve. - - Parameters - ---------- - H : QuantumObject - The Hamiltonian of the system. - z : AbstractMatrix{T} - The initial z matrix. - B : AbstractMatrix{T} - The initial B matrix. - tlist : AbstractVector{T} - The time steps at which the expectation values and function values are calculated. - c_ops : AbstractVector{QuantumObject} - The jump operators of the system. - e_ops : Tuple{QuantumObject} - The operators whose expectation values are calculated. - f_ops : Tuple{Function} - The functions whose values are calculated. - opt : LRMesolveOptions - The options of the problem. - kwargs : NamedTuple - Additional keyword arguments for the ODEProblem. + lr_mesolveProblem( + H::QuantumObject{DT1,OperatorQuantumObject}, + z::AbstractArray{T2,2}, + B::AbstractArray{T2,2}, + tlist::AbstractVector, + c_ops::Union{AbstractVector,Tuple}=(); + e_ops::Union{AbstractVector,Tuple}=(), + f_ops::Union{AbstractVector,Tuple}=(), + opt::NamedTuple = lr_mesolve_options_default, + kwargs..., + ) + +Formulates the ODEproblem for the low-rank time evolution of the system. The function is called by [`lr_mesolve`](@ref). For more information about the low-rank master equation, see [gravina2024adaptive](@cite). + +# Arguments +- `H::QuantumObject`: The Hamiltonian of the system. +- `z::AbstractArray`: The initial z matrix of the low-rank algorithm. +- `B::AbstractArray`: The initial B matrix of the low-rank algorithm. +- `tlist::AbstractVector`: The time steps at which the expectation values and function values are calculated. +- `c_ops::Union{AbstractVector,Tuple}`: The list of the collapse operators. +- `e_ops::Union{AbstractVector,Tuple}`: The list of the operators for which the expectation values are calculated. +- `f_ops::Union{AbstractVector,Tuple}`: The list of the functions for which the function values are calculated. +- `opt::NamedTuple`: The options of the low-rank master equation. +- `kwargs`: Additional keyword arguments. """ function lr_mesolveProblem( - H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, + H::QuantumObject{DT1,OperatorQuantumObject}, z::AbstractArray{T2,2}, B::AbstractArray{T2,2}, tlist::AbstractVector, - c_ops::AbstractVector = []; - e_ops::Tuple = (), - f_ops::Tuple = (), - opt::LRMesolveOptions{AlgType} = LRMesolveOptions(), + c_ops::Union{AbstractVector,Tuple} = (); + e_ops::Union{AbstractVector,Tuple} = (), + f_ops::Union{AbstractVector,Tuple} = (), + opt::NamedTuple = lr_mesolve_options_default, kwargs..., -) where {T1,T2,AlgType<:OrdinaryDiffEqAlgorithm} +) where {DT1,T2} + Hdims = H.dims # Formulation of problem - H -= 0.5im * sum([Γ' * Γ for Γ in c_ops]) + H -= 0.5im * mapreduce(op -> op' * op, +, c_ops) H = get_data(H) c_ops = get_data.(c_ops) e_ops = get_data.(e_ops) @@ -414,6 +416,11 @@ function lr_mesolveProblem( funvals = Array{ComplexF64}(undef, length(f_ops), length(t_l)) Ml = Array{Int64}(undef, length(t_l)) + opt = merge(lr_mesolve_options_default, opt) + if opt.err_max > 0 + opt = merge(opt, (is_dynamical = true,)) + end + # Initialization of parameters. Scalars represents in order: Tr(S^{-1}L), t0 p = ( N = size(z, 1), @@ -436,6 +443,7 @@ function lr_mesolveProblem( Si = similar(B), u_save = vcat(vec(z), vec(B)), scalars = [0.0, t_l[1]], + Hdims = Hdims, ) mul!(p.S, z', z) @@ -484,43 +492,50 @@ function lr_mesolveProblem( # Initialization of ODEProblem tspan = (t_l[1], t_l[end]) - return ODEProblem(dBdz!, p.u_save, tspan, p; kwargs2...) + return ODEProblem{true}(dBdz!, p.u_save, tspan, p; kwargs2...) end +@doc raw""" + lr_mesolve( + H::QuantumObject{DT1,OperatorQuantumObject}, + z::AbstractArray{T2,2}, + B::AbstractArray{T2,2}, + tlist::AbstractVector, + c_ops::Union{AbstractVector,Tuple}=(); + e_ops::Union{AbstractVector,Tuple}=(), + f_ops::Union{AbstractVector,Tuple}=(), + opt::NamedTuple = lr_mesolve_options_default, + kwargs..., + ) + +Time evolution of an open quantum system using the low-rank master equation. For more information about the low-rank master equation, see [gravina2024adaptive](@cite). + +# Arguments +- `H::QuantumObject`: The Hamiltonian of the system. +- `z::AbstractArray`: The initial z matrix of the low-rank algorithm. +- `B::AbstractArray`: The initial B matrix of the low-rank algorithm. +- `tlist::AbstractVector`: The time steps at which the expectation values and function values are calculated. +- `c_ops::Union{AbstractVector,Tuple}`: The list of the collapse operators. +- `e_ops::Union{AbstractVector,Tuple}`: The list of the operators for which the expectation values are calculated. +- `f_ops::Union{AbstractVector,Tuple}`: The list of the functions for which the function values are calculated. +- `opt::NamedTuple`: The options of the low-rank master equation. +- `kwargs`: Additional keyword arguments. +""" function lr_mesolve( - H::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, + H::QuantumObject{DT1,OperatorQuantumObject}, z::AbstractArray{T2,2}, B::AbstractArray{T2,2}, tlist::AbstractVector, - c_ops::AbstractVector = []; - e_ops::Tuple = (), - f_ops::Tuple = (), - opt::LRMesolveOptions{AlgType} = LRMesolveOptions(), + c_ops::Union{AbstractVector,Tuple} = (); + e_ops::Union{AbstractVector,Tuple} = (), + f_ops::Union{AbstractVector,Tuple} = (), + opt::NamedTuple = lr_mesolve_options_default, kwargs..., -) where {T1,T2,AlgType<:OrdinaryDiffEqAlgorithm} +) where {DT1,T2} prob = lr_mesolveProblem(H, z, B, tlist, c_ops; e_ops = e_ops, f_ops = f_ops, opt = opt, kwargs...) return lr_mesolve(prob; kwargs...) end -#=======================================================# -# OUTPUT GENNERATION -#=======================================================# - -get_z(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, 1:M*N), N, M) - -get_B(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, (M*N+1):length(u)), M, M) - -@doc raw""" - lr_mesolve(prob::ODEProblem; kwargs...) - Solves the ODEProblem formulated by lr_mesolveProblem. The function is called by lr_mesolve. - - Parameters - ---------- - prob : ODEProblem - The ODEProblem formulated by lr_mesolveProblem. - kwargs : NamedTuple - Additional keyword arguments for the ODEProblem. -""" function lr_mesolve(prob::ODEProblem; kwargs...) sol = solve(prob, prob.p.opt.alg, tstops = prob.p.times) prob.p.opt.progress && print("\n") @@ -529,13 +544,21 @@ function lr_mesolve(prob::ODEProblem; kwargs...) Ll = length.(sol.u) Ml = @. Int((sqrt(N^2 + 4 * Ll) - N) / 2) - if !haskey(kwargs, :saveat) - Bt = map(x -> get_B(x[1], N, x[2]), zip(sol.u, Ml)) - zt = map(x -> get_z(x[1], N, x[2]), zip(sol.u, Ml)) - else - Bt = get_B(sol.u, N, Ml) - zt = get_z(sol.u, N, Ml) - end - - return LRTimeEvolutionSol(sol.prob.p.times, zt, Bt, prob.p.expvals, prob.p.funvals, prob.p.Ml) + Bt = map(x -> get_B(x[1], N, x[2]), zip(sol.u, Ml)) + zt = map(x -> get_z(x[1], N, x[2]), zip(sol.u, Ml)) + ρt = map(x -> Qobj(x[1] * x[2] * x[1]', type = Operator, dims = prob.p.Hdims), zip(zt, Bt)) + + return TimeEvolutionLRSol( + sol.t, + ρt, + prob.p.expvals, + prob.p.funvals, + sol.retcode, + prob.p.opt.alg, + sol.prob.kwargs[:abstol], + sol.prob.kwargs[:reltol], + zt, + Bt, + Ml, + ) end diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index 99dd33050..2da7dc686 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -2,79 +2,82 @@ # Define lattice Nx, Ny = 2, 3 latt = Lattice(Nx = Nx, Ny = Ny) - N_cut = 2 - N_modes = latt.N - N = N_cut^N_modes - M = Nx * Ny + 1 + ## + N_cut = 2 # Number of states of each mode + N_modes = latt.N # Number of modes + N = N_cut^N_modes # Total number of states + M = latt.N + 1 # Number of states in the LR basis # Define initial state ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject,M - 1}}(undef, M) - ϕ[1] = tensor(repeat([basis(2, 0)], N_modes)...) + ϕ[1] = kron(fill(basis(2, 1), N_modes)...) + i = 1 for j in 1:N_modes i += 1 - i <= M && (ϕ[i] = mb(sp, j, latt) * ϕ[1]) + i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), j, latt) * ϕ[1]) end for k in 1:N_modes-1 for l in k+1:N_modes i += 1 - i <= M && (ϕ[i] = mb(sp, k, latt) * mb(sp, l, latt) * ϕ[1]) + i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), k, latt) * SingleSiteOperator(sigmap(), l, latt) * ϕ[1]) end end for i in i+1:M - ϕ[i] = Qobj(rand(ComplexF64, size(ϕ[1])[1]), dims = ϕ[1].dims) + ϕ[i] = QuantumObject(rand(ComplexF64, size(ϕ[1])[1]), dims = ϕ[1].dims) normalize!(ϕ[i]) end - z = hcat(broadcast(x -> x.data, ϕ)...) + + z = hcat(get_data.(ϕ)...) B = Matrix(Diagonal([1 + 0im; zeros(M - 1)])) - S = z' * z - B = B / tr(S * B) - ρ = Qobj(z * B * z', dims = ntuple(i -> 1, Val(N_modes)) .* N_cut) + S = z' * z # Overlap matrix + B = B / tr(S * B) # Normalize B + ρ = Qobj(z * B * z', dims = ntuple(i -> N_cut, Val(N_modes))) # Full density matrix # Define Hamiltonian and collapse operators Jx = 0.9 - Jy = 1.02 + Jy = 1.04 Jz = 1.0 hx = 0.0 + hy = 0.0 + hz = 0.0 γ = 1 - Sz = sum([mb(sz, i, latt) for i in 1:latt.N]) - tl = LinRange(0, 10, 100) - H, c_ops = TFIM(Jx, Jy, Jz, hx, γ, latt; bc = pbc, order = 1) + Sx = mapreduce(i -> SingleSiteOperator(sigmax(), i, latt), +, 1:latt.N) + Sy = mapreduce(i -> SingleSiteOperator(sigmay(), i, latt), +, 1:latt.N) + Sz = mapreduce(i -> SingleSiteOperator(sigmaz(), i, latt), +, 1:latt.N) + SFxx = mapreduce( + x -> SingleSiteOperator(sigmax(), x[1], latt) * SingleSiteOperator(sigmax(), x[2], latt), + +, + Iterators.product(1:latt.N, 1:latt.N), + ) + + H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) e_ops = (Sz,) + tl = range(0, 10, 100) + # Full solution - mesol = mesolve(H, ρ, tl, c_ops; e_ops = [e_ops...], progress_bar = Val(false)) - A = Matrix(mesol.states[end].data) - λ = eigvals(Hermitian(A)) - Strue = -sum(λ .* log2.(λ)) + sol_me = mesolve(H, ρ, tl, c_ops; e_ops = [e_ops...]) + Strue = entropy_vn(sol_me.states[end], base = 2) / latt.N # Low rank solution function f_entropy(p, z, B) C = p.A0 σ = p.Bi + mul!(C, z, sqrt(B)) mul!(σ, C', C) - λ = eigvals(Hermitian(σ)) - λ = λ[λ.>1e-10] - return -sum(λ .* log2.(λ)) + return entropy_vn(Qobj(Hermitian(σ), type = Operator), base = 2) end - opt = LRMesolveOptions( - err_max = 1e-3, - p0 = 0.0, - atol_inv = 1e-6, - adj_condition = "variational", - Δt = 0.2, - progress = false, - ) - lrsol = lr_mesolve(H, z, B, tl, c_ops; e_ops = e_ops, f_ops = (f_entropy,), opt = opt) + opt = (err_max = 1e-3, p0 = 0.0, atol_inv = 1e-6, adj_condition = "variational", Δt = 0.0, progress = false) + + sol_lr = lr_mesolve(H, z, B, tl, c_ops; e_ops = e_ops, f_ops = (f_entropy,), opt = opt) # Test - m_me = real(mesol.expect[1, :]) - m_lr = real(lrsol.expvals[1, :]) - @test all(abs.((m_me .- m_lr) ./ m_me) .< 0.1) + S_lr = real(sol_lr.fexpect[1, end]) / latt.N - S_lr = real(lrsol.funvals[1, end]) - @test abs((S_lr - Strue) / Strue) < 0.5 + @test real(sol_me.expect[1, :]) ≈ real(sol_lr.expect[1, :]) atol = 1e-1 + @test S_lr ≈ Strue atol = 1e-1 end From 892c496803c9e097e9461f5426ada4438234bb70 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:15:56 +0100 Subject: [PATCH 073/329] Make clenshaw method as default for wigner (#279) * Make clenshaw method as default for wigner * Minor changes --- src/wigner.jl | 116 +++++++++++++++++++++++++++++---------- test/core-test/wigner.jl | 16 +++--- 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/src/wigner.jl b/src/wigner.jl index 466cbe376..c64bf8181 100644 --- a/src/wigner.jl +++ b/src/wigner.jl @@ -12,34 +12,90 @@ end WignerLaguerre(; parallel = false, tol = 1e-14) = WignerLaguerre(parallel, tol) @doc raw""" - wigner(state::QuantumObject, xvec::AbstractVector, yvec::AbstractVector; g::Real=√2, - solver::WignerSolver=WignerLaguerre()) - -Generates the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) -of `state` at points `xvec + 1im * yvec`. The `g` parameter is a scaling factor related to the value of ``\hbar`` in the -commutation relation ``[x, y] = i \hbar`` via ``\hbar=2/g^2`` giving the default value ``\hbar=1``. - -The `solver` parameter can be either `WignerLaguerre()` or `WignerClenshaw()`. The former uses the Laguerre polynomial -expansion of the Wigner function, while the latter uses the Clenshaw algorithm. The Laguerre expansion is faster for -sparse matrices, while the Clenshaw algorithm is faster for dense matrices. The `WignerLaguerre` solver has an optional -`parallel` parameter which defaults to `true` and uses multithreading to speed up the calculation. + wigner( + state::QuantumObject{DT,OpType}, + xvec::AbstractVector, + yvec::AbstractVector; + g::Real = √2, + method::WignerSolver = WignerClenshaw(), + ) + +Generates the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` at points `xvec + 1im * yvec` in phase space. The `g` parameter is a scaling factor related to the value of ``\hbar`` in the commutation relation ``[x, y] = i \hbar`` via ``\hbar=2/g^2`` giving the default value ``\hbar=1``. + +The `method` parameter can be either `WignerLaguerre()` or `WignerClenshaw()`. The former uses the Laguerre polynomial expansion of the Wigner function, while the latter uses the Clenshaw algorithm. The Laguerre expansion is faster for sparse matrices, while the Clenshaw algorithm is faster for dense matrices. The `WignerLaguerre` method has an optional `parallel` parameter which defaults to `true` and uses multithreading to speed up the calculation. + +# Arguments +- `state::QuantumObject`: The quantum state for which the Wigner function is calculated. It can be either a [`KetQuantumObject`](@ref), [`BraQuantumObject`](@ref), or [`OperatorQuantumObject`](@ref). +- `xvec::AbstractVector`: The x-coordinates of the phase space grid. +- `yvec::AbstractVector`: The y-coordinates of the phase space grid. +- `g::Real`: The scaling factor related to the value of ``\hbar`` in the commutation relation ``[x, y] = i \hbar`` via ``\hbar=2/g^2``. +- `method::WignerSolver`: The method used to calculate the Wigner function. It can be either `WignerLaguerre()` or `WignerClenshaw()`, with `WignerClenshaw()` as default. The `WignerLaguerre` method has the optional `parallel` and `tol` parameters, with default values `true` and `1e-14`, respectively. + +# Returns +- `W::Matrix`: The Wigner function of the state at the points `xvec + 1im * yvec` in phase space. + +# Example +``` +julia> ψ = fock(10, 0) + fock(10, 1) |> normalize +Quantum Object: type=Ket dims=[10] size=(10,) +10-element Vector{ComplexF64}: + 0.7071067811865475 + 0.0im + 0.7071067811865475 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + +julia> xvec = range(-5, 5, 200) +-5.0:0.05025125628140704:5.0 + +julia> wig = wigner(ψ, xvec, xvec) +200×200 Matrix{Float64}: + 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 … 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 + 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 + 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 + 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 + 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 + ⋮ ⋱ + 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 … 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 + 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 + 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 + 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 + 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 +``` + +or taking advantage of the parallel computation of the `WignerLaguerre` method + +``` +julia> wig = wigner(ρ, xvec, xvec, method=WignerLaguerre(parallel=true)) +200×200 Matrix{Float64}: + 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 … 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 + 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 + 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 + 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 + 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 + ⋮ ⋱ + 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 … 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 + 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 + 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 + 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 + 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 +``` """ function wigner( - state::QuantumObject{<:AbstractArray{T},OpType}, + state::QuantumObject{DT,OpType}, xvec::AbstractVector, yvec::AbstractVector; g::Real = √2, - solver::MySolver = WignerLaguerre(), -) where {T,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject},MySolver<:WignerSolver} - if isket(state) - ρ = (state * state').data - elseif isbra(state) - ρ = (state' * state).data - else - ρ = state.data - end + method::WignerSolver = WignerClenshaw(), +) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} + ρ = ket2dm(state).data - return _wigner(ρ, xvec, yvec, g, solver) + return _wigner(ρ, xvec, yvec, g, method) end function _wigner( @@ -47,7 +103,7 @@ function _wigner( xvec::AbstractVector{T}, yvec::AbstractVector{T}, g::Real, - solver::WignerLaguerre, + method::WignerLaguerre, ) where {T<:BlasFloat} g = convert(T, g) X, Y = meshgrid(xvec, yvec) @@ -55,7 +111,7 @@ function _wigner( W = similar(A, T) W .= 0 - return _wigner_laguerre(ρ, A, W, g, solver) + return _wigner_laguerre(ρ, A, W, g, method) end function _wigner( @@ -63,7 +119,7 @@ function _wigner( xvec::AbstractVector{T}, yvec::AbstractVector{T}, g::Real, - solver::WignerClenshaw, + method::WignerClenshaw, ) where {T1<:BlasFloat,T<:BlasFloat} g = convert(T, g) M = size(ρ, 1) @@ -90,11 +146,11 @@ function _wigner( return @. real(W) * exp(-B / 2) * g^2 / 2 / π end -function _wigner_laguerre(ρ::AbstractSparseArray, A::AbstractArray, W::AbstractArray, g::Real, solver::WignerLaguerre) +function _wigner_laguerre(ρ::AbstractSparseArray, A::AbstractArray, W::AbstractArray, g::Real, method::WignerLaguerre) rows, cols, vals = findnz(ρ) B = @. 4 * abs2(A) - if solver.parallel + if method.parallel iter = filter(x -> x[2] >= x[1], collect(zip(rows, cols, vals))) Wtot = similar(B, size(B)..., length(iter)) Threads.@threads for i in eachindex(iter) @@ -122,12 +178,12 @@ function _wigner_laguerre(ρ::AbstractSparseArray, A::AbstractArray, W::Abstract return @. W * g^2 * exp(-B / 2) / 2 / π end -function _wigner_laguerre(ρ::AbstractArray, A::AbstractArray, W::AbstractArray, g::Real, solver::WignerLaguerre) - tol = solver.tol +function _wigner_laguerre(ρ::AbstractArray, A::AbstractArray, W::AbstractArray, g::Real, method::WignerLaguerre) + tol = method.tol M = size(ρ, 1) B = @. 4 * abs2(A) - if solver.parallel + if method.parallel throw(ArgumentError("Parallel version is not implemented for dense matrices")) else for m in 0:M-1 diff --git a/test/core-test/wigner.jl b/test/core-test/wigner.jl index ae30b188e..e12a12c2e 100644 --- a/test/core-test/wigner.jl +++ b/test/core-test/wigner.jl @@ -5,10 +5,10 @@ xvec = LinRange(-3, 3, 300) yvec = LinRange(-3, 3, 300) - wig = wigner(ψ, xvec, yvec, solver = WignerLaguerre(tol = 1e-6)) - wig2 = wigner(ρ, xvec, yvec, solver = WignerLaguerre(parallel = false)) - wig3 = wigner(ρ, xvec, yvec, solver = WignerLaguerre(parallel = true)) - wig4 = wigner(ψ, xvec, yvec, solver = WignerClenshaw()) + wig = wigner(ψ, xvec, yvec, method = WignerLaguerre(tol = 1e-6)) + wig2 = wigner(ρ, xvec, yvec, method = WignerLaguerre(parallel = false)) + wig3 = wigner(ρ, xvec, yvec, method = WignerLaguerre(parallel = true)) + wig4 = wigner(ψ, xvec, yvec, method = WignerClenshaw()) @test sqrt(sum(abs.(wig2 .- wig)) / length(wig)) < 1e-3 @test sqrt(sum(abs.(wig3 .- wig)) / length(wig)) < 1e-3 @@ -22,9 +22,9 @@ @test sqrt(sum(abs.(wig2 .- wig)) / length(wig)) < 0.1 @testset "Type Inference (wigner)" begin - @inferred wigner(ψ, xvec, yvec, solver = WignerLaguerre(tol = 1e-6)) - @inferred wigner(ρ, xvec, yvec, solver = WignerLaguerre(parallel = false)) - @inferred wigner(ρ, xvec, yvec, solver = WignerLaguerre(parallel = true)) - @inferred wigner(ψ, xvec, yvec, solver = WignerClenshaw()) + @inferred wigner(ψ, xvec, yvec, method = WignerLaguerre(tol = 1e-6)) + @inferred wigner(ρ, xvec, yvec, method = WignerLaguerre(parallel = false)) + @inferred wigner(ρ, xvec, yvec, method = WignerLaguerre(parallel = true)) + @inferred wigner(ψ, xvec, yvec, method = WignerClenshaw()) end end From 33944ac66fceba16584f2acaddbb54d15c148717 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:16:39 +0100 Subject: [PATCH 074/329] Bump to v0.20.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 64ddcf7ea..5cb642766 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.19.1" +version = "0.20.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 9e7e0e35593fd1a3ca83581e90440a5b378d2118 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 31 Oct 2024 20:09:13 +0900 Subject: [PATCH 075/329] move `get_typename_wrapper` to utilities --- src/qobj/quantum_object_base.jl | 2 -- src/utilities.jl | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index 0190e4c44..ea4f9df29 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -209,8 +209,6 @@ function _check_QuantumObject(type::OperatorBraQuantumObject, dims, m::Int, n::I return nothing end -get_typename_wrapper(A::AbstractQuantumObject) = Base.typename(typeof(A)).wrapper - # functions for getting Float or Complex element type _FType(A::AbstractQuantumObject) = _FType(eltype(A)) _CType(A::AbstractQuantumObject) = _CType(eltype(A)) diff --git a/src/utilities.jl b/src/utilities.jl index 00cf0866c..6d1c0ea8e 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -128,6 +128,8 @@ function convert_unit(value::T, unit1::Symbol, unit2::Symbol) where {T<:Real} return _FType(T)(value * (_energy_units[unit1] / _energy_units[unit2])) end +get_typename_wrapper(A) = Base.typename(typeof(A)).wrapper + _get_dense_similar(A::AbstractArray, args...) = similar(A, args...) _get_dense_similar(A::AbstractSparseMatrix, args...) = similar(nonzeros(A), args...) From 6755bd86f3cadc138842b17a4c2412f91967fc37 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 31 Oct 2024 20:10:11 +0900 Subject: [PATCH 076/329] support time-dependent `lindblad_dissipator` --- src/qobj/superoperators.jl | 78 +++++++++++++++++++++------ test/core-test/quantum_objects_evo.jl | 22 +++++--- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 2408683f2..1bc3d7105 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -5,7 +5,7 @@ Functions for generating (common) quantum super-operators. export spre, spost, sprepost, liouvillian, lindblad_dissipator # intrinsic functions for super-operators -# (keep these because they take AbstractMatrix as input and ensure the output is sparse matrix) +## keep these because they take AbstractMatrix as input and ensure the output is sparse matrix _spre(A::AbstractMatrix, Id::AbstractMatrix) = kron(Id, sparse(A)) _spre(A::AbstractSparseMatrix, Id::AbstractMatrix) = kron(Id, A) _spost(B::AbstractMatrix, Id::AbstractMatrix) = kron(transpose(sparse(B)), Id) @@ -14,19 +14,53 @@ _sprepost(A::AbstractMatrix, B::AbstractMatrix) = kron(transpose(sparse(B)), spa _sprepost(A::AbstractMatrix, B::AbstractSparseMatrix) = kron(transpose(B), sparse(A)) _sprepost(A::AbstractSparseMatrix, B::AbstractMatrix) = kron(transpose(sparse(B)), A) _sprepost(A::AbstractSparseMatrix, B::AbstractSparseMatrix) = kron(transpose(B), A) +function _sprepost(A, B) # for any other input types + Id_cache = I(size(A, 1)) + return _spre(A, Id_cache) * _spost(B, Id_cache) +end + _liouvillian(H::AbstractMatrix, Id::AbstractMatrix) = -1im * (_spre(H, Id) - _spost(H, Id)) +function _lindblad_dissipator(O::AbstractMatrix, Id::AbstractMatrix) + Od_O = O' * O + return _sprepost(O, O') - (_spre(Od_O, Id) + _spost(Od_O, Id)) / 2 +end -# (if input is AbstractSciMLOperator) +## if input is AbstractSciMLOperator +_lazy_tensor_warning(func_name::String, data::AbstractSciMLOperator) = + @warn "The function `$func_name` uses lazy tensor (which can hurt performance) for data type: $(get_typename_wrapper(data))" _spre(A::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spre(A.A, Id)) _spre(A::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(A.λ, _spre(A.L, Id)) _spre(A::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spre(op, Id), +, A.ops) +function _spre(A::AbstractSciMLOperator, Id::AbstractMatrix) + _lazy_tensor_warning("spre", A) + return kron(Id, A) +end + _spost(B::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spost(B.A, Id)) _spost(B::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(B.λ, _spost(B.L, Id)) _spost(B::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spost(op, Id), +, B.ops) +function _spost(B::AbstractSciMLOperator, Id::AbstractMatrix) + _lazy_tensor_warning("spost", B) + return kron(transpose(B), Id) +end + _liouvillian(H::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_liouvillian(H.A, Id)) _liouvillian(H::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(H.λ, _liouvillian(H.L, Id)) _liouvillian(H::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _liouvillian(op, Id), +, H.ops) -# TODO: support `_sprepost`, `sprepost`, and `lindblad_dissipator` for AbstractSciMLOperator (allow c_ops with Vector{QobjEvo}) +_liouvillian(H::AbstractSciMLOperator, Id::AbstractMatrix) = -1im * (_spre(H, Id) - _spost(H, Id)) +function _lindblad_dissipator(O::MatrixOperator, Id::AbstractMatrix) + _O = O.A + Od_O = _O' * _O + return MatrixOperator(_sprepost(_O, _O') - (_spre(Od_O, Id) + _spost(Od_O, Id)) / 2) +end +function _lindblad_dissipator(O::ScaledOperator, Id::AbstractMatrix) + λc_λ = conj(O.λ) * O.λ + return ScaledOperator(λc_λ, _lindblad_dissipator(O.L, Id)) +end +function _lindblad_dissipator(O::AbstractSciMLOperator, Id::AbstractMatrix) + Od_O = O' * O + return _sprepost(O, O') - (_spre(Od_O, Id) + _spost(Od_O, Id)) / 2 +end @doc raw""" spre(A::AbstractQuantumObject, Id_cache=I(size(A,1))) @@ -41,6 +75,8 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r (see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. + +See also [`spost`](@ref) and [`sprepost`](@ref). """ spre(A::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {DT} = get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator, A.dims) @@ -58,12 +94,14 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r (see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details) The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension. + +See also [`spre`](@ref) and [`sprepost`](@ref). """ spost(B::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {DT} = get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator, B.dims) @doc raw""" - sprepost(A::QuantumObject, B::QuantumObject) + sprepost(A::AbstractQuantumObject, B::AbstractQuantumObject) Returns the [`SuperOperator`](@ref) form of `A` and `B` acting on the left and right of the density matrix operator, respectively: ``\mathcal{O} \left( \hat{A}, \hat{B} \right) \left[ \hat{\rho} \right] = \hat{A} \hat{\rho} \hat{B}``. @@ -77,16 +115,15 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r See also [`spre`](@ref) and [`spost`](@ref). """ function sprepost( - A::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, -) where {T1,T2} + A::AbstractQuantumObject{DT1,OperatorQuantumObject}, + B::AbstractQuantumObject{DT2,OperatorQuantumObject}, +) where {DT1,DT2} check_dims(A, B) - - return QuantumObject(_sprepost(A.data, B.data), SuperOperator, A.dims) + return promote_op_type(A, B)(_sprepost(A.data, B.data), SuperOperator, A.dims) end @doc raw""" - lindblad_dissipator(O::QuantumObject, Id_cache=I(size(O,1)) + lindblad_dissipator(O::AbstractQuantumObject, Id_cache=I(size(O,1)) Returns the Lindblad [`SuperOperator`](@ref) defined as @@ -99,14 +136,11 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref), [`spost`](@ref), and [`sprepost`](@ref). """ -function lindblad_dissipator(O::QuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(O, 1))) where {DT} - Od_O = O' * O - return sprepost(O, O') - (spre(Od_O, Id_cache) + spost(Od_O, Id_cache)) / 2 -end -# TODO: suppport collapse operator given as QobjEvo-type +lindblad_dissipator(O::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(O, 1))) where {DT} = + get_typename_wrapper(O)(_lindblad_dissipator(O.data, Id_cache), SuperOperator, O.dims) # It is already a SuperOperator -lindblad_dissipator(O::QuantumObject{DT,SuperOperatorQuantumObject}, Id_cache = nothing) where {DT} = O +lindblad_dissipator(O::AbstractQuantumObject{DT,SuperOperatorQuantumObject}, Id_cache = nothing) where {DT} = O @doc raw""" liouvillian(H::AbstractQuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims))) @@ -134,7 +168,17 @@ function liouvillian( ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, Id_cache) if !(c_ops isa Nothing) - L += mapreduce(lindblad_dissipator, +, c_ops) + # sum all the Qobj first + c_ops_ti = filter(op -> isa(op, QuantumObject), c_ops) + if !isempty(c_ops_ti) + L += mapreduce(op -> lindblad_dissipator(op, Id_cache), +, c_ops_ti) + end + + # sum rest of the QobjEvo together + c_ops_td = filter(op -> isa(op, QuantumObjectEvolution), c_ops) + if !isempty(c_ops_td) + L += mapreduce(op -> lindblad_dissipator(op, Id_cache), +, c_ops_td) + end end return L end diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 4cad02526..057324c5c 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -165,8 +165,9 @@ a = destroy(N) coef1(p, t) = exp(-1im * p.ω1 * t) coef2(p, t) = sin(p.ω2 * t) + coef3(p, t) = sin(p.ω3 * t) t = rand() - p = (ω1 = rand(), ω2 = rand()) + p = (ω1 = rand(), ω2 = rand(), ω3 = rand()) # Operator H_td = QobjEvo(((a, coef1), a' * a, (a', coef2))) @@ -180,12 +181,21 @@ @test isoper(H_td) == true # SuperOperator - c_ops = [sqrt(rand()) * a] - L_td = liouvillian(H_td, c_ops) - L_td2 = -1im * spre(H_td) + 1im * spost(H_td) + lindblad_dissipator(c_ops[1]) + c_op1 = QobjEvo(((a', coef1),)) + c_op2 = QobjEvo(((a, coef2), (a * a', coef3))) + c_ops = [c_op1, c_op2] + D1_ti = abs2(coef1(p, t)) * lindblad_dissipator(a') + D2_ti = @test_logs (:warn,) (:warn,) lindblad_dissipator(c_op2)(p, t) + L_ti = liouvillian(H_ti) + D1_ti + D2_ti + L_td = @test_logs (:warn,) (:warn,) liouvillian(H_td, c_ops) ρvec = mat2vec(rand_dm(N)) - @test L_td(p, t) ≈ L_td2(p, t) - @test L_td(ρvec, p, t) ≈ L_td2(ρvec, p, t) + @test L_td(p, t) ≈ L_ti + # TODO: L_td here is ComposedOperator and need to setup cache first for the following test + # TODO: (maybe can support `iscached` and `cache_operator` for QobjEvo in the future) + # @test iscached(L_td) == false + # @test L_td = cache_operator(L_td, ρvec) + # @test iscached(L_td) == true + # @test L_td(ρvec, p, t) ≈ L_ti * ρvec @test isconstant(L_td) == false @test issuper(L_td) == true From 734bb8ba1173943f1cde7c01714dc80eb1a509ef Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 11:10:02 +0900 Subject: [PATCH 077/329] fix type check in `QobjEvo` --- src/qobj/quantum_object_evo.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 8786669b5..46aaadee8 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -194,7 +194,7 @@ function QuantumObjectEvolution( f !== identity && throw(ArgumentError("The function `f` is not supported for QuantumObjectEvolution inputs.")) if type isa Nothing type = op.type - else + elseif type != op.type throw( ArgumentError( "The type of the QuantumObjectEvolution object cannot be changed when using another QuantumObjectEvolution object as input.", From 2dec733ad6c13e6df9a8bd2cd807ecc7fd8a6ed2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 11:10:43 +0900 Subject: [PATCH 078/329] minor changes --- src/qobj/superoperators.jl | 26 ++++++++++++-------------- src/utilities.jl | 3 +++ test/core-test/quantum_objects_evo.jl | 12 +++++++++--- test/core-test/time_evolution.jl | 3 ++- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 1bc3d7105..fbb51b8f3 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -19,15 +19,9 @@ function _sprepost(A, B) # for any other input types return _spre(A, Id_cache) * _spost(B, Id_cache) end -_liouvillian(H::AbstractMatrix, Id::AbstractMatrix) = -1im * (_spre(H, Id) - _spost(H, Id)) -function _lindblad_dissipator(O::AbstractMatrix, Id::AbstractMatrix) - Od_O = O' * O - return _sprepost(O, O') - (_spre(Od_O, Id) + _spost(Od_O, Id)) / 2 -end - -## if input is AbstractSciMLOperator -_lazy_tensor_warning(func_name::String, data::AbstractSciMLOperator) = - @warn "The function `$func_name` uses lazy tensor (which can hurt performance) for data type: $(get_typename_wrapper(data))" +## if input is AbstractSciMLOperator +## some of them are optimzed to speed things up +## the rest of the SciMLOperators will just use lazy tensor (and prompt a warning) _spre(A::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spre(A.A, Id)) _spre(A::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(A.λ, _spre(A.L, Id)) _spre(A::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spre(op, Id), +, A.ops) @@ -44,10 +38,18 @@ function _spost(B::AbstractSciMLOperator, Id::AbstractMatrix) return kron(transpose(B), Id) end +## intrinsic liouvillian +_liouvillian(H::MT, Id::AbstractMatrix) where {MT<:Union{AbstractMatrix,AbstractSciMLOperator}} = + -1im * (_spre(H, Id) - _spost(H, Id)) _liouvillian(H::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_liouvillian(H.A, Id)) _liouvillian(H::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(H.λ, _liouvillian(H.L, Id)) _liouvillian(H::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _liouvillian(op, Id), +, H.ops) -_liouvillian(H::AbstractSciMLOperator, Id::AbstractMatrix) = -1im * (_spre(H, Id) - _spost(H, Id)) + +# intrinsic lindblad_dissipator +function _lindblad_dissipator(O::MT, Id::AbstractMatrix) where {MT<:Union{AbstractMatrix,AbstractSciMLOperator}} + Od_O = O' * O + return _sprepost(O, O') - (_spre(Od_O, Id) + _spost(Od_O, Id)) / 2 +end function _lindblad_dissipator(O::MatrixOperator, Id::AbstractMatrix) _O = O.A Od_O = _O' * _O @@ -57,10 +59,6 @@ function _lindblad_dissipator(O::ScaledOperator, Id::AbstractMatrix) λc_λ = conj(O.λ) * O.λ return ScaledOperator(λc_λ, _lindblad_dissipator(O.L, Id)) end -function _lindblad_dissipator(O::AbstractSciMLOperator, Id::AbstractMatrix) - Od_O = O' * O - return _sprepost(O, O') - (_spre(Od_O, Id) + _spost(Od_O, Id)) / 2 -end @doc raw""" spre(A::AbstractQuantumObject, Id_cache=I(size(A,1))) diff --git a/src/utilities.jl b/src/utilities.jl index 6d1c0ea8e..150751e6a 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -153,6 +153,9 @@ _non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = join(arg, ", ") * ")` instead of `$argname = $arg`." maxlog = 1 +_lazy_tensor_warning(func_name::String, data::AbstractSciMLOperator) = + @warn "The function `$func_name` uses lazy tensor (which can hurt performance) for data type: $(get_typename_wrapper(data))" + # functions for getting Float or Complex element type _FType(::AbstractArray{T}) where {T<:Number} = _FType(T) _FType(::Type{Int32}) = Float32 diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 057324c5c..18698bf5a 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -181,13 +181,18 @@ @test isoper(H_td) == true # SuperOperator + X = a * a' c_op1 = QobjEvo(((a', coef1),)) - c_op2 = QobjEvo(((a, coef2), (a * a', coef3))) + c_op2 = QobjEvo(((a, coef2), (X, coef3))) c_ops = [c_op1, c_op2] D1_ti = abs2(coef1(p, t)) * lindblad_dissipator(a') - D2_ti = @test_logs (:warn,) (:warn,) lindblad_dissipator(c_op2)(p, t) + D2_ti = + abs2(coef2(p, t)) * lindblad_dissipator(a) + # normal dissipator for first element in c_op2 + abs2(coef3(p, t)) * lindblad_dissipator(X) + # normal dissipator for second element in c_op2 + coef2(p, t) * conj(coef3(p, t)) * (spre(a) * spost(X') - 0.5 * spre(X' * a) - 0.5 * spost(X' * a)) + # cross terms + conj(coef2(p, t)) * coef3(p, t) * (spre(X) * spost(a') - 0.5 * spre(a' * X) - 0.5 * spost(a' * X)) # cross terms L_ti = liouvillian(H_ti) + D1_ti + D2_ti - L_td = @test_logs (:warn,) (:warn,) liouvillian(H_td, c_ops) + L_td = @test_logs (:warn,) (:warn,) liouvillian(H_td, c_ops) # warnings from lazy tensor in `lindblad_dissipator(c_op2)` ρvec = mat2vec(rand_dm(N)) @test L_td(p, t) ≈ L_ti # TODO: L_td here is ComposedOperator and need to setup cache first for the following test @@ -199,6 +204,7 @@ @test isconstant(L_td) == false @test issuper(L_td) == true + @test_logs (:warn,) (:warn,) liouvillian(H_td * H_td) # warnings from lazy tensor @test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]]) @test_throws ArgumentError H_td(ρvec, p, t) @test_throws ArgumentError L_td(ψ, p, t) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index fd7a33f91..623497cb2 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -229,6 +229,7 @@ # @test sol_sse.expect ≈ sol_sse_td2.expect atol = 1e-2 * length(tlist) @testset "Type Inference mesolve" begin + coef(p, t) = exp(-t) @inferred mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolveProblem(H, ψ0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolveProblem( @@ -242,7 +243,7 @@ @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) - @inferred mesolve(H, ψ0, tlist, (a, a'), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple + @inferred mesolve(H, ψ0, tlist, (a, QobjEvo(((a', coef),))), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple @inferred mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) @inferred mesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) @inferred mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) From 19d1fd08d68189e15d98e383906a0b8d213def5a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 11:12:19 +0900 Subject: [PATCH 079/329] format files --- test/core-test/time_evolution.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 623497cb2..dcfb5f665 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -230,6 +230,7 @@ @testset "Type Inference mesolve" begin coef(p, t) = exp(-t) + ad_t = QobjEvo(((a', coef),)) @inferred mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolveProblem(H, ψ0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolveProblem( @@ -243,7 +244,7 @@ @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) - @inferred mesolve(H, ψ0, tlist, (a, QobjEvo(((a', coef),))), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple + @inferred mesolve(H, ψ0, tlist, (a, ad_t), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple @inferred mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) @inferred mesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) @inferred mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) From 79b441c5604c27096e7dcdb8588f382772be04a4 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 13:20:22 +0900 Subject: [PATCH 080/329] remove keyword argument `f` in `QobjEvo` --- src/qobj/quantum_object_evo.jl | 34 +++++++++++++++------------------- src/qobj/synonyms.jl | 8 +++----- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 46aaadee8..e49d5f061 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -157,10 +157,9 @@ end function QuantumObjectEvolution( op_func_list::Tuple, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, - f::Function = identity, + type::Union{Nothing,QuantumObjectType} = nothing ) - op, data = _QobjEvo_generate_data(op_func_list, α; f = f) + op, data = _QobjEvo_generate_data(op_func_list, α) dims = op.dims if type isa Nothing type = op.type @@ -176,22 +175,19 @@ end function QuantumObjectEvolution( op::QuantumObject, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, - f::Function = identity, + type::Union{Nothing,QuantumObjectType} = nothing ) if type isa Nothing type = op.type end - return QuantumObjectEvolution(_make_SciMLOperator(op, α, f = f), type, op.dims) + return QuantumObjectEvolution(_make_SciMLOperator(op, α), type, op.dims) end function QuantumObjectEvolution( op::QuantumObjectEvolution, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, - f::Function = identity, + type::Union{Nothing,QuantumObjectType} = nothing ) - f !== identity && throw(ArgumentError("The function `f` is not supported for QuantumObjectEvolution inputs.")) if type isa Nothing type = op.type elseif type != op.type @@ -217,7 +213,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` - `α`: A scalar to pre-multiply the operators. - `f::Function=identity`: A function to pre-apply to the operators. =# -@generated function _QobjEvo_generate_data(op_func_list::Tuple, α; f::Function = identity) +@generated function _QobjEvo_generate_data(op_func_list::Tuple, α) op_func_list_types = op_func_list.parameters N = length(op_func_list_types) @@ -242,9 +238,9 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` data_type = op_type.parameters[1] dims_expr = (dims_expr..., :($op.dims)) if i == 1 - first_op = :(f($op)) + first_op = :($op) end - data_expr = :($data_expr + _make_SciMLOperator(op_func_list[$i], α, f = f)) + data_expr = :($data_expr + _make_SciMLOperator(op_func_list[$i], α)) else op_type = op_func_type (isoper(op_type) || issuper(op_type)) || @@ -255,7 +251,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` if i == 1 first_op = :(op_func_list[$i]) end - qobj_expr_const = :($qobj_expr_const + f(op_func_list[$i])) + qobj_expr_const = :($qobj_expr_const + op_func_list[$i]) end end @@ -272,20 +268,20 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` end end -function _make_SciMLOperator(op_func::Tuple, α; f::Function = identity) +function _make_SciMLOperator(op_func::Tuple, α) T = eltype(op_func[1]) update_func = (a, u, p, t) -> op_func[2](p, t) if α isa Nothing - return ScalarOperator(zero(T), update_func) * MatrixOperator(f(op_func[1]).data) + return ScalarOperator(zero(T), update_func) * MatrixOperator(op_func[1].data) end - return ScalarOperator(zero(T), update_func) * MatrixOperator(α * f(op_func[1]).data) + return ScalarOperator(zero(T), update_func) * MatrixOperator(α * op_func[1].data) end -function _make_SciMLOperator(op::QuantumObject, α; f::Function = identity) +function _make_SciMLOperator(op::QuantumObject, α) if α isa Nothing - return MatrixOperator(f(op).data) + return MatrixOperator(op.data) end - return MatrixOperator(α * f(op.data)) + return MatrixOperator(α * op.data) end @doc raw""" diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 8155ba3fa..92e409992 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -18,7 +18,7 @@ Note that this functions is same as `QuantumObject(A; type=type, dims=dims)`. Qobj(A; kwargs...) = QuantumObject(A; kwargs...) @doc raw""" - QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing, f::Function=identity) + QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing) Generate [`QuantumObjectEvolution`](@ref). @@ -27,7 +27,6 @@ Note that this functions is same as `QuantumObjectEvolution(op_func_list)`. If ` # Arguments - `op_func_list::Union{Tuple,AbstractQuantumObject}`: A tuple of tuples or operators. - `α::Union{Nothing,Number}=nothing`: A scalar to pre-multiply the operators. -- `f::Function=identity`: A function to pre-apply to the operators. !!! warning "Beware of type-stability!" 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. @@ -104,9 +103,8 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal QobjEvo( op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, - f::Function = identity, -) = QuantumObjectEvolution(op_func_list, α; type = type, f = f) + type::Union{Nothing,QuantumObjectType} = nothing +) = QuantumObjectEvolution(op_func_list, α; type = type) QobjEvo(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) = QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) From c90120028f8e666d2e4b9f2863807a24cb014d06 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 14:52:54 +0900 Subject: [PATCH 081/329] format files --- src/qobj/quantum_object_evo.jl | 14 +++++--------- src/qobj/synonyms.jl | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index e49d5f061..2b698cfc1 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -157,7 +157,7 @@ end function QuantumObjectEvolution( op_func_list::Tuple, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing + type::Union{Nothing,QuantumObjectType} = nothing, ) op, data = _QobjEvo_generate_data(op_func_list, α) dims = op.dims @@ -175,7 +175,7 @@ end function QuantumObjectEvolution( op::QuantumObject, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing + type::Union{Nothing,QuantumObjectType} = nothing, ) if type isa Nothing type = op.type @@ -186,7 +186,7 @@ end function QuantumObjectEvolution( op::QuantumObjectEvolution, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing + type::Union{Nothing,QuantumObjectType} = nothing, ) if type isa Nothing type = op.type @@ -371,15 +371,11 @@ function (A::QuantumObjectEvolution)( check_dims(ψout, A) if isoper(A) && isoperket(ψin) - throw( - ArgumentError( - "The input state must be a KetQuantumObject if the QuantumObjectEvolution object is an Operator.", - ), - ) + throw(ArgumentError("The input state must be a Ket if the QuantumObjectEvolution object is an Operator.")) elseif issuper(A) && isket(ψin) throw( ArgumentError( - "The input state must be an OperatorKetQuantumObject if the QuantumObjectEvolution object is a SuperOperator.", + "The input state must be an OperatorKet if the QuantumObjectEvolution object is a SuperOperator.", ), ) end diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 92e409992..8026ad7d2 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -103,7 +103,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal QobjEvo( op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing + type::Union{Nothing,QuantumObjectType} = nothing, ) = QuantumObjectEvolution(op_func_list, α; type = type) QobjEvo(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) = From 6f12aa1aa28524eac0d1b390680fe78b70b8927d Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 15:22:42 +0900 Subject: [PATCH 082/329] re-export some functions in `SciMLOperators` --- docs/src/api.md | 10 +++++--- src/QuantumToolbox.jl | 25 ++++++++++--------- src/qobj/boolean_functions.jl | 13 +++++++--- src/qobj/quantum_object.jl | 36 +++++++++++++++++++++++++++ test/core-test/quantum_objects_evo.jl | 15 ++++++----- 5 files changed, 74 insertions(+), 25 deletions(-) diff --git a/docs/src/api.md b/docs/src/api.md index 554d1e8b0..e81e0b4e8 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -28,9 +28,10 @@ SuperOperatorQuantumObject SuperOperator QuantumObject QuantumObjectEvolution -size -eltype -length +Base.size +Base.eltype +Base.length +SciMLOperators.cache_operator ``` ## [Qobj boolean functions](@id doc-API:Qobj-boolean-functions) @@ -46,7 +47,8 @@ LinearAlgebra.ishermitian LinearAlgebra.issymmetric LinearAlgebra.isposdef isunitary -isconstant +SciMLOperators.iscached +SciMLOperators.isconstant ``` ## [Qobj arithmetic and attributes](@id doc-API:Qobj-arithmetic-and-attributes) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 098cfb376..413f30c1c 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -3,17 +3,28 @@ module QuantumToolbox # Re-export: # 1. StaticArraysCore.SVector for the type of dims # 2. basic functions in LinearAlgebra and SparseArrays +# 3. some functions in SciMLOperators import Reexport: @reexport @reexport import StaticArraysCore: SVector @reexport using LinearAlgebra @reexport using SparseArrays +@reexport import SciMLOperators: + AbstractSciMLOperator, + MatrixOperator, + ScalarOperator, + ScaledOperator, + AddedOperator, + IdentityOperator, + cache_operator, + iscached, + isconstant # other functions in LinearAlgebra import LinearAlgebra: BlasReal, BlasInt, BlasFloat, BlasComplex, checksquare import LinearAlgebra.BLAS: @blasfunc import LinearAlgebra.LAPACK: hseqr! -# SciML packages (for OrdinaryDiffEq and LinearSolve) +# SciML packages (for QobjEvo, OrdinaryDiffEq, and LinearSolve) import SciMLBase: solve, solve!, @@ -33,17 +44,7 @@ import SciMLBase: ContinuousCallback, DiscreteCallback import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 -import SciMLOperators: - AbstractSciMLOperator, - MatrixOperator, - ScalarOperator, - ScaledOperator, - AddedOperator, - IdentityOperator, - cache_operator, - update_coefficients!, - concretize, - isconstant +import SciMLOperators: SciMLOperators, update_coefficients!, concretize import LinearSolve: LinearProblem, SciMLLinearSolveAlgorithm, KrylovJL_MINRES, KrylovJL_GMRES import DiffEqBase: get_tstops import DiffEqCallbacks: PeriodicCallback, PresetTimeCallback, TerminateSteadyState diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index cccea5321..5262d7e90 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -3,7 +3,7 @@ All boolean functions for checking the data or type in `QuantumObject` =# export isket, isbra, isoper, isoperbra, isoperket, issuper -export isunitary, isconstant +export isunitary @doc raw""" isbra(A) @@ -91,8 +91,15 @@ isunitary(U::QuantumObject{<:AbstractArray{T}}; kwargs...) where {T} = isoper(U) ? isapprox(U.data * U.data', I(size(U, 1)); kwargs...) : false @doc raw""" - isconstant(A::AbstractQuantumObject) + SciMLOperators.iscached(A::AbstractQuantumObject) + +Test whether the [`AbstractQuantumObject`](@ref) `A` has preallocated caches for inplace evaluations. +""" +SciMLOperators.iscached(A::AbstractQuantumObject) = iscached(A.data) + +@doc raw""" + SciMLOperators.isconstant(A::AbstractQuantumObject) Test whether the [`AbstractQuantumObject`](@ref) `A` is constant in time. For a [`QuantumObject`](@ref), this function returns `true`, while for a [`QuantumObjectEvolution`](@ref), this function returns `true` if the operator is contant in time. """ -isconstant(A::AbstractQuantumObject) = isconstant(A.data) +SciMLOperators.isconstant(A::AbstractQuantumObject) = isconstant(A.data) diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 3c0121dc3..d31f95ac3 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -3,6 +3,7 @@ This file defines the QuantumObject (Qobj) structure. It also implements the fundamental functions in Julia standard library: - Base: show, real, imag, Vector, Matrix - SparseArrays: sparse, nnz, nonzeros, rowvals, droptol!, dropzeros, dropzeros!, SparseVector, SparseMatrixCSC + - SciMLOperators: cache_operator =# export QuantumObject @@ -167,6 +168,41 @@ SparseArrays.droptol!(A::QuantumObject{<:AbstractSparseArray}, tol::Real) = (dro SparseArrays.dropzeros(A::QuantumObject{<:AbstractSparseArray}) = QuantumObject(dropzeros(A.data), A.type, A.dims) SparseArrays.dropzeros!(A::QuantumObject{<:AbstractSparseArray}) = (dropzeros!(A.data); return A) +@doc raw""" + SciMLOperators.cached_operator(L::AbstractQuantumObject, u) + +Allocate caches for [`AbstractQuantumObject`](@ref) `L` for in-place evaluation with `u`-like input vectors. + +Here, `u` can be in either the following types: +- `AbstractVector` +- [`Ket`](@ref)-type [`QuantumObject`](@ref) (if `L` is an [`Operator`](@ref)) +- [`OperatorKet`](@ref)-type [`QuantumObject`](@ref) (if `L` is a [`SuperOperator`](@ref)) +""" +SciMLOperators.cache_operator( + L::AbstractQuantumObject{DT,OpType}, + u::AbstractVector, +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + get_typename_wrapper(L)(cache_operator(L.data, sparse_to_dense(similar(u))), L.type, L.dims) + +function SciMLOperators.cache_operator( + L::AbstractQuantumObject{DT1,OpType}, + u::QuantumObject{DT2,SType}, +) where { + DT1, + DT2, + OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + SType<:Union{KetQuantumObject,OperatorKetQuantumObject}, +} + check_dims(L, u) + + if isoper(L) && isoperket(u) + throw(ArgumentError("The input state `u` must be a Ket if `L` is an Operator.")) + elseif issuper(L) && isket(u) + throw(ArgumentError("The input state `u` must be an OperatorKet if `L` is a SuperOperator.")) + end + return cache_operator(L, u.data) +end + # data type conversions Base.Vector(A::QuantumObject{<:AbstractVector}) = QuantumObject(Vector(A.data), A.type, A.dims) Base.Vector{T}(A::QuantumObject{<:AbstractVector}) where {T<:Number} = QuantumObject(Vector{T}(A.data), A.type, A.dims) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 18698bf5a..5d5ce68cd 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -174,6 +174,9 @@ H_ti = coef1(p, t) * a + a' * a + coef2(p, t) * a' ψ = rand_ket(N) @test H_td(p, t) ≈ H_ti + @test iscached(H_td) == true + H_td = cache_operator(H_td, ψ) + @test iscached(H_td) == true @test H_td(ψ, p, t) ≈ H_ti * ψ @test isconstant(a) == true @test isconstant(H_td) == false @@ -195,18 +198,18 @@ L_td = @test_logs (:warn,) (:warn,) liouvillian(H_td, c_ops) # warnings from lazy tensor in `lindblad_dissipator(c_op2)` ρvec = mat2vec(rand_dm(N)) @test L_td(p, t) ≈ L_ti - # TODO: L_td here is ComposedOperator and need to setup cache first for the following test - # TODO: (maybe can support `iscached` and `cache_operator` for QobjEvo in the future) - # @test iscached(L_td) == false - # @test L_td = cache_operator(L_td, ρvec) - # @test iscached(L_td) == true - # @test L_td(ρvec, p, t) ≈ L_ti * ρvec + @test iscached(L_td) == false + L_td = cache_operator(L_td, ρvec) + @test iscached(L_td) == true + @test L_td(ρvec, p, t) ≈ L_ti * ρvec @test isconstant(L_td) == false @test issuper(L_td) == true @test_logs (:warn,) (:warn,) liouvillian(H_td * H_td) # warnings from lazy tensor @test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]]) @test_throws ArgumentError H_td(ρvec, p, t) + @test_throws ArgumentError cache_operator(H_td, ρvec) @test_throws ArgumentError L_td(ψ, p, t) + @test_throws ArgumentError cache_operator(L_td, ψ) end end From 8bb34c5bef120ff5b5018a300ee80261d80e7c0b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 23:36:26 +0900 Subject: [PATCH 083/329] minor changes --- src/QuantumToolbox.jl | 22 +++++++++++----------- test/core-test/quantum_objects.jl | 5 +++++ test/core-test/quantum_objects_evo.jl | 5 ++++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 413f30c1c..4077ed050 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -8,16 +8,7 @@ import Reexport: @reexport @reexport import StaticArraysCore: SVector @reexport using LinearAlgebra @reexport using SparseArrays -@reexport import SciMLOperators: - AbstractSciMLOperator, - MatrixOperator, - ScalarOperator, - ScaledOperator, - AddedOperator, - IdentityOperator, - cache_operator, - iscached, - isconstant +@reexport import SciMLOperators: cache_operator, iscached, isconstant # other functions in LinearAlgebra import LinearAlgebra: BlasReal, BlasInt, BlasFloat, BlasComplex, checksquare @@ -44,7 +35,16 @@ import SciMLBase: ContinuousCallback, DiscreteCallback import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 -import SciMLOperators: SciMLOperators, update_coefficients!, concretize +import SciMLOperators: + SciMLOperators, + AbstractSciMLOperator, + MatrixOperator, + ScalarOperator, + ScaledOperator, + AddedOperator, + IdentityOperator, + update_coefficients!, + concretize import LinearSolve: LinearProblem, SciMLLinearSolveAlgorithm, KrylovJL_MINRES, KrylovJL_GMRES import DiffEqBase: get_tstops import DiffEqCallbacks: PeriodicCallback, PresetTimeCallback, TerminateSteadyState diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index be24e3bfb..36e720b51 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -80,12 +80,17 @@ @test issuper(a2) == false @test isoperket(a2) == false @test isoperbra(a2) == false + @test iscached(a2) == true + @test isconstant(a2) == true + @test isunitary(a2) == false @test isket(a3) == false @test isbra(a3) == false @test isoper(a3) == false @test issuper(a3) == true @test isoperket(a3) == false @test isoperbra(a3) == false + @test iscached(a3) == true + @test isconstant(a3) == true @test isunitary(a3) == false @test_throws DimensionMismatch Qobj(a, dims = 2) end diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 5d5ce68cd..966dd981a 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -205,11 +205,14 @@ @test isconstant(L_td) == false @test issuper(L_td) == true - @test_logs (:warn,) (:warn,) liouvillian(H_td * H_td) # warnings from lazy tensor @test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]]) @test_throws ArgumentError H_td(ρvec, p, t) @test_throws ArgumentError cache_operator(H_td, ρvec) @test_throws ArgumentError L_td(ψ, p, t) @test_throws ArgumentError cache_operator(L_td, ψ) + + @testset "Type Inference" begin + @inferred liouvillian(H_td, (a, QobjEvo(((a', coef1),)))) + end end end From 66566e7952a7671e86804da213f1a54c84a9306b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Fri, 1 Nov 2024 23:39:13 +0900 Subject: [PATCH 084/329] minor changes --- test/core-test/quantum_objects_evo.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 966dd981a..bf9f6116d 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -205,6 +205,7 @@ @test isconstant(L_td) == false @test issuper(L_td) == true + @test_logs (:warn,) (:warn,) liouvillian(H_td * H_td) # warnings from lazy tensor @test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]]) @test_throws ArgumentError H_td(ρvec, p, t) @test_throws ArgumentError cache_operator(H_td, ρvec) From 54ded38dc069dd7e3c4e30533003636a4a185ccd Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:16:23 +0900 Subject: [PATCH 085/329] A lazy method to construct `QobjEvo` (#282) * add a lazy method to construct `QobjEvo` * format files * improve tests to make sure time-independent problems are `MatrixOperator` * modify docstrings * fix typo * fix extra `ScalarOperator` in `mcsolve` * rebase to `ScaledOperator` in time-independent `mcsolve` * add missing import in test --- src/qobj/quantum_object_evo.jl | 19 ++++++++++++++---- src/qobj/synonyms.jl | 29 +++++++++++++++++++++++++++ test/core-test/quantum_objects_evo.jl | 4 ++-- test/core-test/time_evolution.jl | 12 ++++++++--- test/runtests.jl | 1 + 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 2b698cfc1..d2acb544a 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -27,6 +27,17 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ ⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ +julia> coef1(p, t) = exp(-1im * t) +coef1 (generic function with 1 method) + +julia> op = QuantumObjectEvolution(a, coef1) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) +``` + +If there are more than 2 operators, we need to put each set of operator and coefficient function into a two-element `Tuple`, and put all these `Tuple`s together in a larger `Tuple`: + +``` julia> σm = tensor(qeye(10), sigmam()) Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: @@ -36,9 +47,6 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ⎢⠀⠀⠀⠀⠀⠀⠂⡀⠀⠀⎥ ⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡀⎦ -julia> coef1(p, t) = exp(-1im * t) -coef1 (generic function with 1 method) - julia> coef2(p, t) = sin(t) coef2 (generic function with 1 method) @@ -134,7 +142,7 @@ function QuantumObjectEvolution(data::AbstractSciMLOperator, type::QuantumObject return QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) end -""" +@doc raw""" QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) Generate a [`QuantumObjectEvolution`](@ref) object from a [`SciMLOperator`](https://github.com/SciML/SciMLOperators.jl), in the same way as [`QuantumObject`](@ref) for `AbstractArray` inputs. @@ -172,6 +180,9 @@ function QuantumObjectEvolution( return QuantumObjectEvolution(data, type, dims) end +QuantumObjectEvolution(op::QuantumObject, f::Function; type::Union{Nothing,QuantumObjectType} = nothing) = + QuantumObjectEvolution(((op, f),); type = type) + function QuantumObjectEvolution( op::QuantumObject, α::Union{Nothing,Number} = nothing; diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 8026ad7d2..05a3a95a1 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -17,6 +17,35 @@ Note that this functions is same as `QuantumObject(A; type=type, dims=dims)`. """ Qobj(A; kwargs...) = QuantumObject(A; kwargs...) +@doc raw""" + QobjEvo(op::QuantumObject, f::Function; type::Union{Nothing,QuantumObjectType} = nothing) + +Generate [`QuantumObjectEvolution`](@ref). + +Note that this functions is same as `QuantumObjectEvolution(op, f; type = type)`. 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`. + +# Examples +``` +julia> a = tensor(destroy(10), qeye(2)) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: +⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ + +julia> coef(p, t) = exp(-1im * t) +coef1 (generic function with 1 method) + +julia> op = QobjEvo(a, coef) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) +``` +""" +QobjEvo(op::QuantumObject, f::Function; type::Union{Nothing,QuantumObjectType} = nothing) = + QuantumObjectEvolution(op, f; type = type) + @doc raw""" QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index bf9f6116d..fe7760b58 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -185,7 +185,7 @@ # SuperOperator X = a * a' - c_op1 = QobjEvo(((a', coef1),)) + c_op1 = QobjEvo(a', coef1) c_op2 = QobjEvo(((a, coef2), (X, coef3))) c_ops = [c_op1, c_op2] D1_ti = abs2(coef1(p, t)) * lindblad_dissipator(a') @@ -213,7 +213,7 @@ @test_throws ArgumentError cache_operator(L_td, ψ) @testset "Type Inference" begin - @inferred liouvillian(H_td, (a, QobjEvo(((a', coef1),)))) + @inferred liouvillian(H_td, (a, QobjEvo(a', coef1))) end end end diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index dcfb5f665..64c42a6a2 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -11,10 +11,12 @@ psi0 = kron(fock(N, 0), fock(2, 0)) t_l = LinRange(0, 1000, 1000) e_ops = [a_d * a] - sol = sesolve(H, psi0, t_l, e_ops = e_ops, progress_bar = Val(false)) + prob = sesolveProblem(H, psi0, t_l, e_ops = e_ops, progress_bar = Val(false)) + sol = sesolve(prob) sol2 = sesolve(H, psi0, t_l, progress_bar = Val(false)) sol3 = sesolve(H, psi0, t_l, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) sol_string = sprint((t, s) -> show(t, "text/plain", s), sol) + @test prob.f.f isa MatrixOperator @test sum(abs.(sol.expect[1, :] .- sin.(η * t_l) .^ 2)) / length(t_l) < 0.1 @test length(sol.times) == length(t_l) @test length(sol.states) == 1 @@ -55,9 +57,11 @@ e_ops = [a_d * a] psi0 = basis(N, 3) t_l = LinRange(0, 100, 1000) - sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) + prob_me = mesolveProblem(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_me = mesolve(prob_me) sol_me2 = mesolve(H, psi0, t_l, c_ops, progress_bar = Val(false)) sol_me3 = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) + prob_mc = mcsolveProblem(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) sol_mc = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) sol_mc2 = mcsolve( H, @@ -93,6 +97,8 @@ sol_me_string = sprint((t, s) -> show(t, "text/plain", s), sol_me) sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) + @test prob_me.f.f isa MatrixOperator + @test prob_mc.f.f isa ScaledOperator # TODO: can be optimized as MatrixOperator @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(t_l) < 0.1 @test sum(abs.(sol_mc2.expect .- sol_me.expect)) / length(t_l) < 0.1 @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect))) / length(t_l) < 0.1 @@ -230,7 +236,7 @@ @testset "Type Inference mesolve" begin coef(p, t) = exp(-t) - ad_t = QobjEvo(((a', coef),)) + ad_t = QobjEvo(a', coef) @inferred mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolveProblem(H, ψ0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolveProblem( diff --git a/test/runtests.jl b/test/runtests.jl index 57407fb03..e188cd8a5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,7 @@ using QuantumToolbox using QuantumToolbox: position, momentum using Random using SciMLOperators +import SciMLOperators: ScaledOperator const GROUP = get(ENV, "GROUP", "All") From 7a0806ebba041b7fc361fd73e269192bcda82ecf Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sat, 2 Nov 2024 19:19:30 +0100 Subject: [PATCH 086/329] Improve SciMLOperator handling of sesolveProblem --- src/time_evolution/sesolve.jl | 6 +++++- src/time_evolution/time_evolution_dynamical.jl | 4 ++-- test/core-test/time_evolution.jl | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 87d9535b1..a7218d981 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -36,6 +36,10 @@ function _generate_sesolve_kwargs(e_ops, progress_bar::Val{false}, tlist, kwargs return _generate_sesolve_kwargs_with_callback(tlist, kwargs) end +_sesolve_make_U_QobjEvo(H::QuantumObjectEvolution{<:MatrixOperator}) = + QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dims, type = Operator) +_sesolve_make_U_QobjEvo(H) = QobjEvo(H, -1im) + @doc raw""" sesolveProblem( H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, @@ -88,7 +92,7 @@ function sesolveProblem( tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl - H_evo = QobjEvo(H, -1im) # pre-multiply by -i + H_evo = _sesolve_make_U_QobjEvo(H) # Multiply by -i isoper(H_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) check_dims(H_evo, ψ0) diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 8830de405..aa9660b01 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -564,8 +564,8 @@ function _DSF_mcsolve_Affect!(integrator) @. e_ops0 = get_data(e_ops2) @. c_ops0 = get_data(c_ops2) H_nh = lmul!(convert(eltype(ψt), 0.5im), mapreduce(op -> op' * op, +, c_ops0)) - # By doing this, we are assuming that the system is time-independent and f is a ScaledOperator - copyto!(integrator.f.f.L.A, H(op_l2, dsf_params).data - H_nh) + # By doing this, we are assuming that the system is time-independent and f is a MatrixOperator + copyto!(integrator.f.f.A, lmul!(-1im, H(op_l2, dsf_params).data - H_nh)) return u_modified!(integrator, true) end diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 64c42a6a2..83adb9615 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -98,7 +98,7 @@ sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) @test prob_me.f.f isa MatrixOperator - @test prob_mc.f.f isa ScaledOperator # TODO: can be optimized as MatrixOperator + @test prob_mc.f.f isa MatrixOperator @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(t_l) < 0.1 @test sum(abs.(sol_mc2.expect .- sol_me.expect)) / length(t_l) < 0.1 @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect))) / length(t_l) < 0.1 From c63e627568f4349baa783d7efa32ff4f49f74f39 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:54:27 +0900 Subject: [PATCH 087/329] Bump version to `0.21.0` --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5cb642766..8921bce98 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.20.0" +version = "0.21.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From f4187be2fa26c8d429a70ec3476e4e5b51a6732b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 4 Nov 2024 15:13:21 +0900 Subject: [PATCH 088/329] optimize precompilation during runtests --- .buildkite/pipeline.yml | 2 +- Project.toml | 6 +++++- test/ext-test/gpu/Project.toml | 6 ++++++ test/ext-test/{ => gpu}/cuda_ext.jl | 0 test/runtests.jl | 8 ++++---- 5 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 test/ext-test/gpu/Project.toml rename test/ext-test/{ => gpu}/cuda_ext.jl (100%) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 83595dff4..58abe6921 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -9,7 +9,7 @@ steps: - "src/**" - "ext/QuantumToolboxCUDAExt.jl" - "test/runtests.jl" - - "test/ext-test/cuda_ext.jl" + - "test/ext-test/gpu/**" - "Project.toml" target: ".buildkite/CUDA_Ext.yml" agents: diff --git a/Project.toml b/Project.toml index 8921bce98..22b034219 100644 --- a/Project.toml +++ b/Project.toml @@ -33,6 +33,7 @@ CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" QuantumToolboxCUDAExt = "CUDA" [compat] +Aqua = "0.8" ArrayInterface = "6, 7" CUDA = "5" DiffEqBase = "6" @@ -42,6 +43,7 @@ Distributed = "1" FFTW = "1.5" Graphs = "1.7" IncompleteLU = "0.2" +JET = "0.9" LinearAlgebra = "1" LinearSolve = "2" OrdinaryDiffEqCore = "1" @@ -59,8 +61,10 @@ Test = "1" julia = "1.10" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Aqua", "JET", "Test"] diff --git a/test/ext-test/gpu/Project.toml b/test/ext-test/gpu/Project.toml new file mode 100644 index 000000000..63ad2aa77 --- /dev/null +++ b/test/ext-test/gpu/Project.toml @@ -0,0 +1,6 @@ +[deps] +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" + +[compat] +CUDA = "5" \ No newline at end of file diff --git a/test/ext-test/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl similarity index 100% rename from test/ext-test/cuda_ext.jl rename to test/ext-test/gpu/cuda_ext.jl diff --git a/test/runtests.jl b/test/runtests.jl index e188cd8a5..72ec00b89 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,7 +4,6 @@ using QuantumToolbox using QuantumToolbox: position, momentum using Random using SciMLOperators -import SciMLOperators: ScaledOperator const GROUP = get(ENV, "GROUP", "All") @@ -32,7 +31,6 @@ core_tests = [ ] if (GROUP == "All") || (GROUP == "Code-Quality") - Pkg.add(["Aqua", "JET"]) include(joinpath(testdir, "core-test", "code_quality.jl")) end @@ -45,6 +43,8 @@ if (GROUP == "All") || (GROUP == "Core") end if (GROUP == "CUDA_Ext")# || (GROUP == "All") - Pkg.add("CUDA") - include(joinpath(testdir, "ext-test", "cuda_ext.jl")) + Pkg.activate("gpu") + Pkg.develop(PackageSpec(path = dirname(@__DIR__))) + Pkg.instantiate() + include(joinpath(testdir, "ext-test", "gpu", "cuda_ext.jl")) end From 3ce7483248265565466ae0e5e4f6bf3c0e9cc711 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 4 Nov 2024 15:27:10 +0900 Subject: [PATCH 089/329] fix `CUDA` tests --- Project.toml | 1 - test/runtests.jl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 22b034219..35b9ff0f1 100644 --- a/Project.toml +++ b/Project.toml @@ -62,7 +62,6 @@ julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index 72ec00b89..8d1979d17 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -43,7 +43,7 @@ if (GROUP == "All") || (GROUP == "Core") end if (GROUP == "CUDA_Ext")# || (GROUP == "All") - Pkg.activate("gpu") + Pkg.activate("ext-test/gpu") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) Pkg.instantiate() include(joinpath(testdir, "ext-test", "gpu", "cuda_ext.jl")) From 4c6ad99a34397aeb25303b781307779bf1b2424f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 4 Nov 2024 15:44:15 +0900 Subject: [PATCH 090/329] move imports into `if`-condition in tests --- test/core-test/code_quality.jl | 2 -- test/ext-test/gpu/cuda_ext.jl | 7 ------- test/runtests.jl | 21 +++++++++++++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/test/core-test/code_quality.jl b/test/core-test/code_quality.jl index 724639a31..5effb97d1 100644 --- a/test/core-test/code_quality.jl +++ b/test/core-test/code_quality.jl @@ -1,5 +1,3 @@ -using Aqua, JET - @testset "Code quality" verbose = true begin @testset "Aqua.jl" begin Aqua.test_all(QuantumToolbox; ambiguities = false, unbound_args = false) diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index f8c7f1f8d..20219b424 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -1,10 +1,3 @@ -using CUDA -using CUDA.CUSPARSE -CUDA.allowscalar(false) # Avoid unexpected scalar indexing - -QuantumToolbox.about() -CUDA.versioninfo() - @testset "CUDA Extension" verbose = true begin ψdi = Qobj(Int64[1, 0]) ψdf = Qobj(Float64[1, 0]) diff --git a/test/runtests.jl b/test/runtests.jl index 8d1979d17..f737ad6ad 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,5 @@ using Test using Pkg -using QuantumToolbox -using QuantumToolbox: position, momentum -using Random -using SciMLOperators const GROUP = get(ENV, "GROUP", "All") @@ -31,10 +27,18 @@ core_tests = [ ] if (GROUP == "All") || (GROUP == "Code-Quality") + using QuantumToolbox + using Aqua, JET + include(joinpath(testdir, "core-test", "code_quality.jl")) end if (GROUP == "All") || (GROUP == "Core") + using QuantumToolbox + import QuantumToolbox: position, momentum + import Random + import SciMLOperators: MatrixOperator + QuantumToolbox.about() for test in core_tests @@ -46,5 +50,14 @@ if (GROUP == "CUDA_Ext")# || (GROUP == "All") Pkg.activate("ext-test/gpu") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) Pkg.instantiate() + + using QuantumToolbox + using CUDA + using CUDA.CUSPARSE + CUDA.allowscalar(false) # Avoid unexpected scalar indexing + + QuantumToolbox.about() + CUDA.versioninfo() + include(joinpath(testdir, "ext-test", "gpu", "cuda_ext.jl")) end From 00c6a921a2fb0d90b8abb673f55a52c53f1172ff Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 4 Nov 2024 15:58:17 +0900 Subject: [PATCH 091/329] add missing imports --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index f737ad6ad..20f72b057 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -36,7 +36,7 @@ end if (GROUP == "All") || (GROUP == "Core") using QuantumToolbox import QuantumToolbox: position, momentum - import Random + import Random: MersenneTwister import SciMLOperators: MatrixOperator QuantumToolbox.about() From 9b23c6f10a71bbf6c90574ad5b5dab8261e6f61b Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:32:49 +0100 Subject: [PATCH 092/329] Add normalize_states option in mcsolve (#285) --- src/time_evolution/mcsolve.jl | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 31d5b1ecb..068457103 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -109,10 +109,16 @@ _mcsolve_dispatch_output_func() = _mcsolve_output_func _mcsolve_dispatch_output_func(::ET) where {ET<:Union{EnsembleSerial,EnsembleThreads}} = _mcsolve_output_func_progress _mcsolve_dispatch_output_func(::EnsembleDistributed) = _mcsolve_output_func_distributed -function _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which) +function _normalize_state!(u, dims, normalize_states) + getVal(normalize_states) && normalize!(u) + return QuantumObject(u, dims = dims) +end + +function _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which, normalize_states) sol_i = sol[:, i] - !isempty(sol_i.prob.kwargs[:saveat]) ? - states[i] = [QuantumObject(normalize!(sol_i.u[i]), dims = sol_i.prob.p.Hdims) for i in 1:length(sol_i.u)] : nothing + dims = sol_i.prob.p.Hdims + !isempty(sol_i.prob.kwargs[:saveat]) ? states[i] = map(u -> _normalize_state!(u, dims, normalize_states), sol_i.u) : + nothing copyto!(view(expvals_all, i, :, :), sol_i.prob.p.expvals) jump_times[i] = sol_i.prob.p.jump_times @@ -461,6 +467,7 @@ end prob_func::Function = _mcsolve_prob_func, output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), + normalize_states::Union{Val,Bool} = Val(true), kwargs..., ) @@ -514,6 +521,7 @@ If the environmental measurements register a quantum jump, the wave function und - `prob_func`: Function to use for generating the ODEProblem. - `output_func`: Function to use for generating the output of a single trajectory. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `normalize_states`: Whether to normalize the states. Default to `Val(true)`. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -544,6 +552,7 @@ function mcsolve( prob_func::Function = _mcsolve_prob_func, output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), + normalize_states::Union{Val,Bool} = Val(true), kwargs..., ) where {DT1,DT2,TJC<:LindbladJumpCallbackType} ens_prob_mc = mcsolveEnsembleProblem( @@ -564,7 +573,13 @@ function mcsolve( kwargs..., ) - return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + return mcsolve( + ens_prob_mc; + alg = alg, + ntraj = ntraj, + ensemble_method = ensemble_method, + normalize_states = normalize_states, + ) end function mcsolve( @@ -572,6 +587,7 @@ function mcsolve( alg::OrdinaryDiffEqAlgorithm = Tsit5(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), + normalize_states::Union{Val,Bool} = Val(true), ) try sol = solve(ens_prob_mc, alg, ensemble_method, trajectories = ntraj) @@ -589,7 +605,10 @@ function mcsolve( jump_times = Vector{Vector{Float64}}(undef, length(sol)) jump_which = Vector{Vector{Int16}}(undef, length(sol)) - foreach(i -> _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which), eachindex(sol)) + foreach( + i -> _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which, normalize_states), + eachindex(sol), + ) expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) return TimeEvolutionMCSol( From 54e85c9fd2e26870395e4e50712c12a6635568d0 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:55:46 +0900 Subject: [PATCH 093/329] Add spell check CI and fix typos (#286) * add spell check CI * fix version number * fix version number * add typo ignore * fix typos * minor changes --- .github/workflows/SpellCheck.yml | 13 ++++ .typos.toml | 2 + README.md | 2 +- docs/src/index.md | 2 +- docs/src/tutorials/lowrank.md | 2 +- docs/src/users_guide/states_and_operators.md | 2 +- src/linear_maps.jl | 2 +- src/qobj/arithmetic_and_attributes.jl | 72 +++++++++---------- src/qobj/boolean_functions.jl | 2 +- src/qobj/superoperators.jl | 2 +- src/steadystate.jl | 12 ++-- src/time_evolution/mcsolve.jl | 6 +- src/time_evolution/ssesolve.jl | 8 +-- .../time_evolution_dynamical.jl | 12 ++-- 14 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/SpellCheck.yml create mode 100644 .typos.toml diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml new file mode 100644 index 000000000..604011f0f --- /dev/null +++ b/.github/workflows/SpellCheck.yml @@ -0,0 +1,13 @@ +name: Spell Check + +on: [pull_request] + +jobs: + typos-check: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Check spelling + uses: crate-ci/typos@v1.27.0 \ No newline at end of file diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 000000000..73648865c --- /dev/null +++ b/.typos.toml @@ -0,0 +1,2 @@ +[default.extend-words] +ket = "ket" \ No newline at end of file diff --git a/README.md b/README.md index f3bfceee4..2661116be 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ QuantumToolbox.jl is equipped with a robust set of features: - **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as QuTiP. - **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) package. -- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation direclty on the GPU with the same syntax as the CPU case. +- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation directly on the GPU with the same syntax as the CPU case. - **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. - **Easy Extension:** Easily extend the package, taking advantage of the Julia language features, like multiple dispatch and metaprogramming. diff --git a/docs/src/index.md b/docs/src/index.md index 4d1642c6f..7447af11b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -14,7 +14,7 @@ QuantumToolbox.jl is equipped with a robust set of features: - **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as QuTiP. - **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) package. -- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation direclty on the GPU with the same syntax as the CPU case. +- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation directly on the GPU with the same syntax as the CPU case. - **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run undreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. - **Easy Extension:** Easily extend the package, taking advantage of the Julia language features, like multiple dispatch and metaprogramming. diff --git a/docs/src/tutorials/lowrank.md b/docs/src/tutorials/lowrank.md index 0a3c6c86b..59e292bfd 100644 --- a/docs/src/tutorials/lowrank.md +++ b/docs/src/tutorials/lowrank.md @@ -1,6 +1,6 @@ # [Low rank master equation](@id doc-tutor:Low-rank-master-equation) -In this tutorial, we will show how to solve the master equation using the low-rank method. For a detailed explaination of the method, we recommend to read the article [gravina2024adaptive](@cite). +In this tutorial, we will show how to solve the master equation using the low-rank method. For a detailed explanation of the method, we recommend to read the article [gravina2024adaptive](@cite). As a test, we will consider the dissipative Ising model with a transverse field. The Hamiltonian is given by diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index 3cd714820..85993c2c9 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -99,7 +99,7 @@ We can also create superpositions of states: ket = normalize(basis(5, 0) + basis(5, 1)) ``` -where we have used the `normalize` function again to normalize the state. Apply the number opeartor again: +where we have used the `normalize` function again to normalize the state. Apply the number operator again: ```@example states_and_operators n * ket diff --git a/src/linear_maps.jl b/src/linear_maps.jl index 8afd88e70..61ac4c96c 100644 --- a/src/linear_maps.jl +++ b/src/linear_maps.jl @@ -18,7 +18,7 @@ A **linear map** is a transformation `L` that satisfies: L(cu) = cL(u) ``` -It is typically represented as a matrix with dimensions given by `size`, and this abtract type helps to define this map when the matrix is not explicitly available. +It is typically represented as a matrix with dimensions given by `size`, and this abstract type helps to define this map when the matrix is not explicitly available. ## Methods diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 96947bb2c..5314d9156 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -511,17 +511,17 @@ Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true function ptrace(QO::QuantumObject{<:AbstractArray,KetQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) _non_static_array_warning("sel", sel) - ns = length(sel) - if ns == 0 # return full trace for empty sel + n_s = length(sel) + if n_s == 0 # return full trace for empty sel return tr(ket2dm(QO)) else - nd = length(QO.dims) + n_d = length(QO.dims) - (any(>(nd), sel) || any(<(1), sel)) && throw( - ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(nd) sub-systems"), + (any(>(n_d), sel) || any(<(1), sel)) && throw( + ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(n_d) sub-systems"), ) - (ns != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) - (nd == 1) && return ket2dm(QO) # ptrace should always return Operator + (n_s != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) + (n_d == 1) && return ket2dm(QO) # ptrace should always return Operator end _sort_sel = sort(SVector{length(sel),Int}(sel)) @@ -534,17 +534,17 @@ ptrace(QO::QuantumObject{<:AbstractArray,BraQuantumObject}, sel::Union{AbstractV function ptrace(QO::QuantumObject{<:AbstractArray,OperatorQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) _non_static_array_warning("sel", sel) - ns = length(sel) - if ns == 0 # return full trace for empty sel + n_s = length(sel) + if n_s == 0 # return full trace for empty sel return tr(QO) else - nd = length(QO.dims) + n_d = length(QO.dims) - (any(>(nd), sel) || any(<(1), sel)) && throw( - ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(nd) sub-systems"), + (any(>(n_d), sel) || any(<(1), sel)) && throw( + ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(n_d) sub-systems"), ) - (ns != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) - (nd == 1) && return QO + (n_s != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) + (n_d == 1) && return QO end _sort_sel = sort(SVector{length(sel),Int}(sel)) @@ -554,27 +554,27 @@ end ptrace(QO::QuantumObject, sel::Int) = ptrace(QO, SVector(sel)) function _ptrace_ket(QO::AbstractArray, dims::Union{SVector,MVector}, sel) - nd = length(dims) + n_d = length(dims) - nd == 1 && return QO, dims + n_d == 1 && return QO, dims - qtrace = filter(i -> i ∉ sel, 1:nd) + qtrace = filter(i -> i ∉ sel, 1:n_d) dkeep = dims[sel] dtrace = dims[qtrace] - nt = length(dtrace) + n_t = length(dtrace) # Concatenate qtrace and sel without losing the length information # Tuple(qtrace..., sel...) - qtrace_sel = ntuple(Val(nd)) do i - if i <= nt + qtrace_sel = ntuple(Val(n_d)) do i + if i <= n_t @inbounds qtrace[i] else - @inbounds sel[i-nt] + @inbounds sel[i-n_t] end end vmat = reshape(QO, reverse(dims)...) - topermute = reverse(nd + 1 .- qtrace_sel) + topermute = reverse(n_d + 1 .- qtrace_sel) vmat = permutedims(vmat, topermute) # TODO: use PermutedDimsArray when Julia v1.11.0 is released vmat = reshape(vmat, prod(dkeep), prod(dtrace)) @@ -582,33 +582,33 @@ function _ptrace_ket(QO::AbstractArray, dims::Union{SVector,MVector}, sel) end function _ptrace_oper(QO::AbstractArray, dims::Union{SVector,MVector}, sel) - nd = length(dims) + n_d = length(dims) - nd == 1 && return QO, dims + n_d == 1 && return QO, dims - qtrace = filter(i -> i ∉ sel, 1:nd) + qtrace = filter(i -> i ∉ sel, 1:n_d) dkeep = dims[sel] dtrace = dims[qtrace] - nk = length(dkeep) - nt = length(dtrace) - _2_nt = 2 * nt + n_k = length(dkeep) + n_t = length(dtrace) + _2_n_t = 2 * n_t # Concatenate qtrace and sel without losing the length information # Tuple(qtrace..., sel...) - qtrace_sel = ntuple(Val(2 * nd)) do i - if i <= nt + qtrace_sel = ntuple(Val(2 * n_d)) do i + if i <= n_t @inbounds qtrace[i] - elseif i <= _2_nt - @inbounds qtrace[i-nt] + nd - elseif i <= _2_nt + nk - @inbounds sel[i-_2_nt] + elseif i <= _2_n_t + @inbounds qtrace[i-n_t] + n_d + elseif i <= _2_n_t + n_k + @inbounds sel[i-_2_n_t] else - @inbounds sel[i-_2_nt-nk] + nd + @inbounds sel[i-_2_n_t-n_k] + n_d end end ρmat = reshape(QO, reverse(vcat(dims, dims))...) - topermute = reverse(2 * nd + 1 .- qtrace_sel) + topermute = reverse(2 * n_d + 1 .- qtrace_sel) ρmat = permutedims(ρmat, topermute) # TODO: use PermutedDimsArray when Julia v1.11.0 is released ρmat = reshape(ρmat, prod(dkeep), prod(dkeep), prod(dtrace), prod(dtrace)) res = map(tr, eachslice(ρmat, dims = (1, 2))) diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index 5262d7e90..869d14642 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -100,6 +100,6 @@ SciMLOperators.iscached(A::AbstractQuantumObject) = iscached(A.data) @doc raw""" SciMLOperators.isconstant(A::AbstractQuantumObject) -Test whether the [`AbstractQuantumObject`](@ref) `A` is constant in time. For a [`QuantumObject`](@ref), this function returns `true`, while for a [`QuantumObjectEvolution`](@ref), this function returns `true` if the operator is contant in time. +Test whether the [`AbstractQuantumObject`](@ref) `A` is constant in time. For a [`QuantumObject`](@ref), this function returns `true`, while for a [`QuantumObjectEvolution`](@ref), this function returns `true` if the operator is constant in time. """ SciMLOperators.isconstant(A::AbstractQuantumObject) = isconstant(A.data) diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index fbb51b8f3..85491c6cc 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -20,7 +20,7 @@ function _sprepost(A, B) # for any other input types end ## if input is AbstractSciMLOperator -## some of them are optimzed to speed things up +## some of them are optimized to speed things up ## the rest of the SciMLOperators will just use lazy tensor (and prompt a warning) _spre(A::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spre(A.A, Id)) _spre(A::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(A.λ, _spre(A.L, Id)) diff --git a/src/steadystate.jl b/src/steadystate.jl index 097611d9a..606b0907e 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -106,11 +106,11 @@ function _steadystate( idx_range = collect(1:N) rows = _get_dense_similar(L_tmp, N) cols = _get_dense_similar(L_tmp, N) - datas = _get_dense_similar(L_tmp, N) + vals = _get_dense_similar(L_tmp, N) fill!(rows, 1) copyto!(cols, N .* (idx_range .- 1) .+ idx_range) - fill!(datas, weight) - Tn = sparse(rows, cols, datas, N^2, N^2) + fill!(vals, weight) + Tn = sparse(rows, cols, vals, N^2, N^2) L_tmp = L_tmp + Tn (haskey(kwargs, :Pl) || haskey(kwargs, :Pr)) && error("The use of preconditioners must be defined in the solver.") @@ -160,11 +160,11 @@ function _steadystate( idx_range = collect(1:N) rows = _get_dense_similar(L_tmp, N) cols = _get_dense_similar(L_tmp, N) - datas = _get_dense_similar(L_tmp, N) + vals = _get_dense_similar(L_tmp, N) fill!(rows, 1) copyto!(cols, N .* (idx_range .- 1) .+ idx_range) - fill!(datas, weight) - Tn = sparse(rows, cols, datas, N^2, N^2) + fill!(vals, weight) + Tn = sparse(rows, cols, vals, N^2, N^2) L_tmp = L_tmp + Tn ρss_vec = L_tmp \ v0 # This is still not supported on GPU, yet diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 068457103..0c58b0425 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -38,14 +38,14 @@ function LindbladJumpAffect!(integrator) end cumsum!(cumsum_weights_mc, weights_mc) r = rand(traj_rng) * sum(weights_mc) - collaps_idx = getindex(1:length(weights_mc), findfirst(>(r), cumsum_weights_mc)) - mul!(cache_mc, c_ops[collaps_idx], ψ) + collapse_idx = getindex(1:length(weights_mc), findfirst(>(r), cumsum_weights_mc)) + mul!(cache_mc, c_ops[collapse_idx], ψ) normalize!(cache_mc) copyto!(integrator.u, cache_mc) random_n[] = rand(traj_rng) jump_times[internal_params.jump_times_which_idx[]] = integrator.t - jump_which[internal_params.jump_times_which_idx[]] = collaps_idx + jump_which[internal_params.jump_times_which_idx[]] = collapse_idx internal_params.jump_times_which_idx[] += 1 if internal_params.jump_times_which_idx[] > length(jump_times) resize!(jump_times, length(jump_times) + internal_params.jump_times_which_init_size) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index a691ff0ef..3d126b9f9 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -5,11 +5,11 @@ export ssesolveProblem, ssesolveEnsembleProblem, ssesolve A struct to represent the diffusion operator. This is used to perform the diffusion process on N different Wiener processes. =# -struct DiffusionOperator{T,OT<:Tuple{Vararg{AbstractSciMLOperator}}} <: AbstractSciMLOperator{T} - ops::OT - function DiffusionOperator(ops::OT) where {OT} +struct DiffusionOperator{T,OpType<:Tuple{Vararg{AbstractSciMLOperator}}} <: AbstractSciMLOperator{T} + ops::OpType + function DiffusionOperator(ops::OpType) where {OpType} T = mapreduce(eltype, promote_type, ops) - return new{T,OT}(ops) + return new{T,OpType}(ops) end end diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index aa9660b01..270168dfc 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -8,12 +8,12 @@ function _reduce_dims( sel, reduce, ) where {T,N,DT<:Integer} - nd = length(dims) + n_d = length(dims) dims_new = zero(dims) dims_new[sel] .= reduce @. dims_new = dims - dims_new - if nd == 1 + if n_d == 1 ρmat = similar(QO, dims_new[1], dims_new[1]) copyto!(ρmat, view(QO, 1:dims_new[1], 1:dims_new[1])) else @@ -32,12 +32,12 @@ function _increase_dims( sel, increase, ) where {T,N,DT<:Integer} - nd = length(dims) + n_d = length(dims) dims_new = MVector(zero(dims)) # Mutable SVector dims_new[sel] .= increase @. dims_new = dims + dims_new - if nd == 1 + if n_d == 1 ρmat = similar(QO, dims_new[1], dims_new[1]) fill!(selectdim(ρmat, 1, dims[1]+1:dims_new[1]), 0) fill!(selectdim(ρmat, 2, dims[1]+1:dims_new[1]), 0) @@ -46,8 +46,8 @@ function _increase_dims( ρmat2 = similar(QO, reverse(vcat(dims_new, dims_new))...) ρmat = reshape(QO, reverse(vcat(dims, dims))...) for i in eachindex(sel) - fill!(selectdim(ρmat2, nd - sel[i] + 1, dims[sel[i]]+1:dims_new[sel[i]]), 0) - fill!(selectdim(ρmat2, 2 * nd - sel[i] + 1, dims[sel[i]]+1:dims_new[sel[i]]), 0) + fill!(selectdim(ρmat2, n_d - sel[i] + 1, dims[sel[i]]+1:dims_new[sel[i]]), 0) + fill!(selectdim(ρmat2, 2 * n_d - sel[i] + 1, dims[sel[i]]+1:dims_new[sel[i]]), 0) end copyto!(view(ρmat2, reverse!(repeat([1:n for n in dims], 2))...), ρmat) ρmat = reshape(ρmat2, prod(dims_new), prod(dims_new)) From c8c9479fa868b0ac4c2eb687a8eba15b20174bd5 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:56:30 +0100 Subject: [PATCH 094/329] Bump version to v0.21.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 35b9ff0f1..fb54ce8ab 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.21.0" +version = "0.21.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 9776f5fa3dbb9b066e1c11cf0946113f38f4bae6 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 9 Nov 2024 09:46:22 +0100 Subject: [PATCH 095/329] Convert Documentation to Vitepress (#287) --- .github/workflows/documentation.yml | 48 +++-- .gitignore | 1 - docs/.gitignore | 4 + docs/Project.toml | 1 + docs/make.jl | 49 +++-- docs/src/.vitepress/config.mts | 49 +++++ docs/src/.vitepress/theme/index.ts | 19 ++ docs/src/.vitepress/theme/style.css | 266 ++++++++++++++++++++++++++++ 8 files changed, 400 insertions(+), 37 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/src/.vitepress/config.mts create mode 100644 docs/src/.vitepress/theme/index.ts create mode 100644 docs/src/.vitepress/theme/style.css diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index beaa3fc02..87d82251a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -21,27 +21,39 @@ on: - synchronize - ready_for_review +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: write + pages: write + id-token: write + statuses: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + jobs: + # Build job build: runs-on: ubuntu-latest - permissions: - contents: write - statuses: write if: ${{ !github.event.pull_request.draft }} steps: - - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v2 - with: - version: '1' - - uses: julia-actions/cache@v2 - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-docdeploy@v1 + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Julia + uses: julia-actions/setup-julia@v2 + - name: Pull Julia cache + uses: julia-actions/cache@v2 + - name: Install documentation dependencies + run: julia --project=docs -e 'using Pkg; pkg"dev ."; Pkg.instantiate(); Pkg.precompile(); Pkg.status()' + #- name: Creating new mds from src + - name: Build and deploy docs + uses: julia-actions/julia-docdeploy@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} - - run: | - julia --project=docs -e ' - using Documenter: DocMeta, doctest - using QuantumToolbox - DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive=true) - doctest(QuantumToolbox)' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + GKSwstype: "100" # for Plots.jl plots (if you have them) + JULIA_DEBUG: "Documenter" + DATADEPS_ALWAYS_ACCEPT: true diff --git a/.gitignore b/.gitignore index edb4c3a5e..900000a03 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ *.jl.cov *.jl.mem Manifest.toml -docs/build/ .vscode *.json diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..778ffdc18 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,4 @@ +build/ +node_modules/ +package-lock.json +Manifest.toml diff --git a/docs/Project.toml b/docs/Project.toml index b61136e89..ef98b807e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,4 +3,5 @@ BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" diff --git a/docs/make.jl b/docs/make.jl index 375ffec53..9e69887e4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -3,6 +3,7 @@ using QuantumToolbox using Documenter +using DocumenterVitepress using DocumenterCitations DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) @@ -11,16 +12,16 @@ const DRAFT = false # set `true` to disable cell evaluation bib = CitationBibliography(joinpath(@__DIR__, "src", "bibliography.bib"), style=:authoryear) -const MathEngine = MathJax3( - Dict( - :loader => Dict("load" => ["[tex]/physics"]), - :tex => Dict( - "inlineMath" => [["\$", "\$"], ["\\(", "\\)"]], - "tags" => "ams", - "packages" => ["base", "ams", "autoload", "physics"], - ), - ) -) +# const MathEngine = MathJax3( +# Dict( +# :loader => Dict("load" => ["[tex]/physics"]), +# :tex => Dict( +# "inlineMath" => [["\$", "\$"], ["\\(", "\\)"]], +# "tags" => "ams", +# "packages" => ["base", "ams", "autoload", "physics"], +# ), +# ) +# ) const PAGES = [ "Getting Started" => [ @@ -71,16 +72,28 @@ makedocs(; repo = Remotes.GitHub("qutip", "QuantumToolbox.jl"), sitename = "QuantumToolbox.jl", pages = PAGES, - format = Documenter.HTML(; - prettyurls = get(ENV, "CI", "false") == "true", - canonical = "https://qutip.github.io/QuantumToolbox.jl", - edit_link = "main", - assets = ["assets/favicon.ico"], - mathengine = MathEngine, - size_threshold_ignore = ["api.md"], + # format = Documenter.HTML(; + # prettyurls = get(ENV, "CI", "false") == "true", + # canonical = "https://qutip.github.io/QuantumToolbox.jl", + # edit_link = "main", + # assets = ["assets/favicon.ico"], + # mathengine = MathEngine, + # size_threshold_ignore = ["api.md"], + # ), + format = DocumenterVitepress.MarkdownVitepress( + repo = "https://qutip.github.io/QuantumToolbox.jl", + # deploy_url = "https://qutip.org/QuantumToolbox.jl/", ), draft = DRAFT, plugins = [bib], ) -deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", devbranch = "main") +# deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", devbranch = "main") + +deploydocs(; + repo = "github.com/qutip/QuantumToolbox.jl", + target = "build", # this is where Vitepress stores its output + devbranch = "main", + branch = "gh-pages", + push_preview = true, +) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts new file mode 100644 index 000000000..38dbec64f --- /dev/null +++ b/docs/src/.vitepress/config.mts @@ -0,0 +1,49 @@ +import { defineConfig } from 'vitepress' +import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' +import mathjax3 from "markdown-it-mathjax3"; +import footnote from "markdown-it-footnote"; + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! + title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + description: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + lastUpdated: true, + cleanUrls: true, + outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... + head: [['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }]], + ignoreDeadLinks: true, + + markdown: { + math: true, + config(md) { + md.use(tabsMarkdownPlugin), + md.use(mathjax3), + md.use(footnote) + }, + theme: { + light: "github-light", + dark: "github-dark" + } + }, + themeConfig: { + outline: 'deep', + logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + search: { + provider: 'local', + options: { + detailedView: true + } + }, + nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + socialLinks: [ + { icon: 'github', link: 'REPLACE_ME_DOCUMENTER_VITEPRESS' } + ], + footer: { + message: 'Made with DocumenterVitepress.jl
', + copyright: `© Copyright ${new Date().getUTCFullYear()}.` + } + } +}) diff --git a/docs/src/.vitepress/theme/index.ts b/docs/src/.vitepress/theme/index.ts new file mode 100644 index 000000000..1072d80a5 --- /dev/null +++ b/docs/src/.vitepress/theme/index.ts @@ -0,0 +1,19 @@ +// .vitepress/theme/index.ts +import { h } from 'vue' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' + +import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' +import './style.css' + +export default { + extends: DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }) + }, + enhanceApp({ app, router, siteData }) { + enhanceAppWithTabs(app) + } +} satisfies Theme diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css new file mode 100644 index 000000000..2617bf119 --- /dev/null +++ b/docs/src/.vitepress/theme/style.css @@ -0,0 +1,266 @@ +/* Customize default theme styling by overriding CSS variables: +https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/* Layouts */ + +/* + :root { + --vp-layout-max-width: 1440px; +} */ + +.VPHero .clip { + white-space: pre; + max-width: 500px; +} + +/* Fonts */ + +@font-face { + font-family: JuliaMono-Regular; + src: url("https://cdn.jsdelivr.net/gh/cormullion/juliamono/webfonts/JuliaMono-Regular.woff2"); +} + +:root { + /* Typography */ + --vp-font-family-base: "Barlow", "Inter var experimental", "Inter var", + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + + /* Code Snippet font */ + --vp-font-family-mono: JuliaMono-Regular, monospace; + +} + +/* + Disable contextual alternates (kind of like ligatures but different) in monospace, + which turns `/>` to an up arrow and `|>` (the Julia pipe symbol) to an up arrow as well. + This is pretty bad for Julia folks reading even though copy+paste retains the same text. + */ +/* Target elements with class 'mono' */ +.mono-no-substitutions { + font-family: "JuliaMono-Light", monospace; + font-feature-settings: "calt" off; +} + +/* Alternatively, you can use the following if you prefer: */ +.mono-no-substitutions-alt { + font-family: "JuliaMono-Light", monospace; + font-variant-ligatures: none; +} + +/* If you want to apply this globally to all monospace text: */ +pre, +code { + font-family: "JuliaMono-Light", monospace; + font-feature-settings: "calt" off; +} + +/* Colors */ + +:root { + --julia-blue: #4063D8; + --julia-purple: #9558B2; + --julia-red: #CB3C33; + --julia-green: #389826; + + --vp-c-brand: #389826; + --vp-c-brand-light: #3dd027; + --vp-c-brand-lighter: #9499ff; + --vp-c-brand-lightest: #bcc0ff; + --vp-c-brand-dark: #535bf2; + --vp-c-brand-darker: #454ce1; + --vp-c-brand-dimm: #212425; +} + +/* Component: Button */ + +:root { + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand); + --vp-button-brand-hover-border: var(--vp-c-brand-light); + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-light); + --vp-button-brand-active-border: var(--vp-c-brand-light); + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); +} + +/* Component: Home */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient(120deg, + #9558B2 30%, + #CB3C33); + + --vp-home-hero-image-background-image: linear-gradient(-45deg, + #9558B2 30%, + #389826 30%, + #CB3C33); + --vp-home-hero-image-filter: blur(40px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } +} + +/* Component: Custom Block */ + +:root.dark { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-lightest); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); + + /* // Tweak the color palette for blacks and dark grays */ + --vp-c-black: hsl(220 20% 9%); + --vp-c-black-pure: hsl(220, 24%, 4%); + --vp-c-black-soft: hsl(220 16% 13%); + --vp-c-black-mute: hsl(220 14% 17%); + --vp-c-gray: hsl(220 8% 56%); + --vp-c-gray-dark-1: hsl(220 10% 39%); + --vp-c-gray-dark-2: hsl(220 12% 28%); + --vp-c-gray-dark-3: hsl(220 12% 23%); + --vp-c-gray-dark-4: hsl(220 14% 17%); + --vp-c-gray-dark-5: hsl(220 16% 13%); + + /* // Backgrounds */ + /* --vp-c-bg: hsl(240, 2%, 11%); */ + --vp-custom-block-info-bg: hsl(220 14% 17%); + /* --vp-c-gutter: hsl(220 20% 9%); + + --vp-c-bg-alt: hsl(220 20% 9%); + --vp-c-bg-soft: hsl(220 14% 17%); + --vp-c-bg-mute: hsl(220 12% 23%); + */ +} + +/* Component: Algolia */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand) !important; +} + +/* Component: MathJax */ + +mjx-container>svg { + display: block; + margin: auto; +} + +mjx-container { + padding: 0.5rem 0; +} + +mjx-container { + display: inline; + margin: auto 2px -2px; +} + +mjx-container>svg { + margin: auto; + display: inline-block; +} + +/** + * Colors links + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-brand-1: #CB3C33; + --vp-c-brand-2: #CB3C33; + --vp-c-brand-3: #CB3C33; + --vp-c-sponsor: #ca2971; + --vitest-c-sponsor-hover: #c13071; +} + +.dark { + --vp-c-brand-1: #91dd33; + --vp-c-brand-2: #91dd33; + --vp-c-brand-3: #91dd33; + --vp-c-sponsor: #91dd33; + --vitest-c-sponsor-hover: #e51370; +} + +/** + * Change images from light to dark theme + * -------------------------------------------------------------------------- */ + +:root:not(.dark) .dark-only { + display: none; +} + +:root:is(.dark) .light-only { + display: none; +} + +/* https://bddxg.top/article/note/vitepress优化/一些细节上的优化.html#文档页面调整-加宽 */ + +.VPDoc.has-aside .content-container { + max-width: 100% !important; +} + +.aside { + max-width: 200px !important; + padding-left: 0 !important; +} + +.VPDoc { + padding-top: 15px !important; + padding-left: 5px !important; + +} + +/* This one does the right menu */ + +.VPDocOutlineItem li { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 200px; +} + +.VPNavBar .title { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +@media (max-width: 960px) { + .VPDoc { + padding-left: 25px !important; + } +} + +/* This one does the left menu */ + +/* .VPSidebarItem .VPLink p { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 200px; + } */ + + +/* Component: Docstring Custom Block */ + +.jldocstring.custom-block { + border: 1px solid var(--vp-c-gray-2); + color: var(--vp-c-text-1) +} + +.jldocstring.custom-block summary { + font-weight: 700; + cursor: pointer; + user-select: none; + margin: 0 0 8px; +} \ No newline at end of file From 3d01f3107709bf28dd5755538cf491f5ea46850b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Sat, 9 Nov 2024 19:08:32 +0900 Subject: [PATCH 096/329] build docs locally --- .github/workflows/documentation.yml | 20 ++++++++------------ .gitignore | 3 ++- docs/README.md | 20 ++++++++++++++++++++ docs/package.json | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 docs/package.json diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 87d82251a..3bc705b0e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -40,20 +40,16 @@ jobs: runs-on: ubuntu-latest if: ${{ !github.event.pull_request.draft }} steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Julia - uses: julia-actions/setup-julia@v2 - - name: Pull Julia cache - uses: julia-actions/cache@v2 - - name: Install documentation dependencies - run: julia --project=docs -e 'using Pkg; pkg"dev ."; Pkg.instantiate(); Pkg.precompile(); Pkg.status()' - #- name: Creating new mds from src - - name: Build and deploy docs - uses: julia-actions/julia-docdeploy@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-docdeploy@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key - GKSwstype: "100" # for Plots.jl plots (if you have them) JULIA_DEBUG: "Documenter" DATADEPS_ALWAYS_ACCEPT: true + # GKSwstype: "100" # for Plots.jl plots (if you have them) diff --git a/.gitignore b/.gitignore index 900000a03..b07886a94 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,8 @@ Manifest.toml .vscode -*.json + +benchmarks/benchmarks_output.json .ipynb_checkpoints *.ipynb \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 9fe1e761c..59b6db243 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,3 +25,23 @@ Run the following command: ```shell julia --project=docs docs/make.jl ``` + +# How to start a local Vitepress site ? + +> [!NOTE] +> You need to install `Node.js` and `npm` first. + +Enter `docs` directory first: +```shell +cd /path/to/QuantumToolbox.jl/docs +``` + +Install `npm` dependencies: +```shell +npm i +``` + +Run the following command: +```shell +npm run docs:dev +``` \ No newline at end of file diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..5633b4976 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,15 @@ +{ + "scripts": { + "docs:dev": "vitepress dev build/.documenter", + "docs:build": "vitepress build build/.documenter", + "docs:preview": "vitepress preview build/.documenter" + }, + "dependencies": { + "@shikijs/transformers": "^1.1.7", + "markdown-it": "^14.1.0", + "markdown-it-footnote": "^4.0.0", + "markdown-it-mathjax3": "^4.3.2", + "vitepress": "^1.1.4", + "vitepress-plugin-tabs": "^0.5.0" + } +} From 4e54a4c9acfd009027611d853803e71889b70621 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sat, 9 Nov 2024 12:08:17 +0100 Subject: [PATCH 097/329] Change Homepage --- docs/make.jl | 25 +----- docs/src/getting_started.md | 99 +++++++++++++++++++++++ docs/src/index.md | 157 ++++++++++-------------------------- 3 files changed, 143 insertions(+), 138 deletions(-) create mode 100644 docs/src/getting_started.md diff --git a/docs/make.jl b/docs/make.jl index 9e69887e4..6e57eaddd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -12,20 +12,10 @@ const DRAFT = false # set `true` to disable cell evaluation bib = CitationBibliography(joinpath(@__DIR__, "src", "bibliography.bib"), style=:authoryear) -# const MathEngine = MathJax3( -# Dict( -# :loader => Dict("load" => ["[tex]/physics"]), -# :tex => Dict( -# "inlineMath" => [["\$", "\$"], ["\\(", "\\)"]], -# "tags" => "ams", -# "packages" => ["base", "ams", "autoload", "physics"], -# ), -# ) -# ) - const PAGES = [ + "Home" => "index.md", "Getting Started" => [ - "Introduction" => "index.md", + "Introduction" => "getting_started.md", "Key differences from QuTiP" => "qutip_differences.md", "The Importance of Type-Stability" => "type_stability.md", # "Cite QuantumToolbox.jl" => "cite.md", @@ -72,24 +62,13 @@ makedocs(; repo = Remotes.GitHub("qutip", "QuantumToolbox.jl"), sitename = "QuantumToolbox.jl", pages = PAGES, - # format = Documenter.HTML(; - # prettyurls = get(ENV, "CI", "false") == "true", - # canonical = "https://qutip.github.io/QuantumToolbox.jl", - # edit_link = "main", - # assets = ["assets/favicon.ico"], - # mathengine = MathEngine, - # size_threshold_ignore = ["api.md"], - # ), format = DocumenterVitepress.MarkdownVitepress( repo = "https://qutip.github.io/QuantumToolbox.jl", - # deploy_url = "https://qutip.org/QuantumToolbox.jl/", ), draft = DRAFT, plugins = [bib], ) -# deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", devbranch = "main") - deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", target = "build", # this is where Vitepress stores its output diff --git a/docs/src/getting_started.md b/docs/src/getting_started.md new file mode 100644 index 000000000..b5995e96a --- /dev/null +++ b/docs/src/getting_started.md @@ -0,0 +1,99 @@ +```@meta +CurrentModule = QuantumToolbox +``` + +## [Installation](@id doc:Installation) + +!!! note "Requirements" + `QuantumToolbox.jl` requires `Julia 1.10+`. + +To install `QuantumToolbox.jl`, run the following commands inside Julia's interactive session (also known as REPL): +```julia +using Pkg +Pkg.add("QuantumToolbox") +``` +Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: +```julia-REPL +(1.10) pkg> add QuantumToolbox +``` +More information about `Julia`'s package manager can be found at [`Pkg.jl`](https://julialang.github.io/Pkg.jl/v1/). + +To load the package and check the version information, use either [`QuantumToolbox.versioninfo()`](@ref) or [`QuantumToolbox.about()`](@ref), namely +```julia +using QuantumToolbox +QuantumToolbox.versioninfo() +QuantumToolbox.about() +``` + +## Brief Example + +We now provide a brief example to demonstrate the similarity between [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) and [QuTiP](https://github.com/qutip/qutip). + +Let's consider a quantum harmonic oscillator with a Hamiltonian given by: + +```math +\hat{H} = \omega \hat{a}^\dagger \hat{a} +``` + +where ``\hat{a}`` and ``\hat{a}^\dagger`` are the annihilation and creation operators, respectively. We can define the Hamiltonian as follows: + +```julia +using QuantumToolbox + +N = 20 # cutoff of the Hilbert space dimension +ω = 1.0 # frequency of the harmonic oscillator + +a = destroy(N) # annihilation operator + +H = ω * a' * a +``` + +We now introduce some losses in a thermal environment, described by the Lindblad master equation: + +```math +\frac{d \hat{\rho}}{dt} = -i [\hat{H}, \hat{\rho}] + \gamma \mathcal{D}[\hat{a}] \hat{\rho} +``` + +where ``\hat{\rho}`` is the density matrix, ``\gamma`` is the damping rate, and ``\mathcal{D}[\hat{a}]`` is the Lindblad dissipator, defined as: + +```math +\mathcal{D}[\hat{a}]\hat{\rho} = \hat{a}\hat{\rho}\hat{a}^\dagger - \frac{1}{2}\hat{a}^\dagger\hat{a}\hat{\rho} - \frac{1}{2}\hat{\rho}\hat{a}^\dagger\hat{a} +``` + +We now compute the time evolution of the system using the [`mesolve`](@ref) function, starting from the initial state ``\ket{\psi (0)} = \ket{3}``: + +```julia +γ = 0.1 # damping rate + +ψ0 = fock(N, 3) # initial state + +tlist = range(0, 10, 100) # time list + +c_ops = [sqrt(γ) * a] +e_ops = [a' * a] + +sol = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops) +``` + +We can extract the expectation value of the number operator ``\hat{a}^\dagger \hat{a}`` with the command `sol.expect`, and the states with the command `sol.states`. + +### Support for GPU calculation + +We can easily pass the computation to the GPU, by simply passing all the `Qobj`s to the GPU: + +```julia +using QuantumToolbox +using CUDA +CUDA.allowscalar(false) # Avoid unexpected scalar indexing + +a_gpu = cu(destroy(N)) # The only difference in the code is the cu() function + +H_gpu = ω * a_gpu' * a_gpu + +ψ0_gpu = cu(fock(N, 3)) + +c_ops = [sqrt(γ) * a_gpu] +e_ops = [a_gpu' * a_gpu] + +sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) +``` \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 7447af11b..4aac26a55 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,115 +1,42 @@ -```@meta -CurrentModule = QuantumToolbox -``` - -# QuantumToolbox.jl Documentation - -[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge Julia package designed for quantum physics simulations, closely emulating the popular Python [QuTiP](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. - -*With this package, moving from Python to Julia for quantum physics simulations has never been easier*, due to the similar syntax and functionalities. - -## Features - -QuantumToolbox.jl is equipped with a robust set of features: - -- **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as QuTiP. -- **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) package. -- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation directly on the GPU with the same syntax as the CPU case. -- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run undreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. -- **Easy Extension:** Easily extend the package, taking advantage of the Julia language features, like multiple dispatch and metaprogramming. - -## [Installation](@id doc:Installation) - -!!! note "Requirements" - `QuantumToolbox.jl` requires `Julia 1.10+`. - -To install `QuantumToolbox.jl`, run the following commands inside Julia's interactive session (also known as REPL): -```julia -using Pkg -Pkg.add("QuantumToolbox") -``` -Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: -```julia-REPL -(1.10) pkg> add QuantumToolbox -``` -More information about `Julia`'s package manager can be found at [`Pkg.jl`](https://julialang.github.io/Pkg.jl/v1/). - -To load the package and check the version information, use either [`QuantumToolbox.versioninfo()`](@ref) or [`QuantumToolbox.about()`](@ref), namely -```julia -using QuantumToolbox -QuantumToolbox.versioninfo() -QuantumToolbox.about() -``` - -## Brief Example - -We now provide a brief example to demonstrate the similarity between [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) and [QuTiP](https://github.com/qutip/qutip). - -Let's consider a quantum harmonic oscillator with a Hamiltonian given by: - -```math -\hat{H} = \omega \hat{a}^\dagger \hat{a} -``` - -where ``\hat{a}`` and ``\hat{a}^\dagger`` are the annihilation and creation operators, respectively. We can define the Hamiltonian as follows: - -```julia -using QuantumToolbox - -N = 20 # cutoff of the Hilbert space dimension -ω = 1.0 # frequency of the harmonic oscillator - -a = destroy(N) # annihilation operator - -H = ω * a' * a -``` - -We now introduce some losses in a thermal environment, described by the Lindblad master equation: - -```math -\frac{d \hat{\rho}}{dt} = -i [\hat{H}, \hat{\rho}] + \gamma \mathcal{D}[\hat{a}] \hat{\rho} -``` - -where ``\hat{\rho}`` is the density matrix, ``\gamma`` is the damping rate, and ``\mathcal{D}[\hat{a}]`` is the Lindblad dissipator, defined as: - -```math -\mathcal{D}[\hat{a}]\hat{\rho} = \hat{a}\hat{\rho}\hat{a}^\dagger - \frac{1}{2}\hat{a}^\dagger\hat{a}\hat{\rho} - \frac{1}{2}\hat{\rho}\hat{a}^\dagger\hat{a} -``` - -We now compute the time evolution of the system using the [`mesolve`](@ref) function, starting from the initial state ``\ket{\psi (0)} = \ket{3}``: - -```julia -γ = 0.1 # damping rate - -ψ0 = fock(N, 3) # initial state - -tlist = range(0, 10, 100) # time list - -c_ops = [sqrt(γ) * a] -e_ops = [a' * a] - -sol = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops) -``` - -We can extract the expectation value of the number operator ``\hat{a}^\dagger \hat{a}`` with the command `sol.expect`, and the states with the command `sol.states`. - -### Support for GPU calculation - -We can easily pass the computation to the GPU, by simply passing all the `Qobj`s to the GPU: - -```julia -using QuantumToolbox -using CUDA -CUDA.allowscalar(false) # Avoid unexpected scalar indexing - -a_gpu = cu(destroy(N)) # The only difference in the code is the cu() function - -H_gpu = ω * a_gpu' * a_gpu - -ψ0_gpu = cu(fock(N, 3)) - -c_ops = [sqrt(γ) * a_gpu] -e_ops = [a_gpu' * a_gpu] - -sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) -``` +```@raw html +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "QuantumToolbox.jl" + tagline: High-performance quantum simulations made simple + image: + src: /logo.png + alt: QuantumToolbox + actions: + - theme: brand + text: Getting Started + link: /getting_started + - theme: alt + text: View on Github + link: https://github.com/qutip/QuantumToolbox.jl + - theme: alt + text: API + link: /api + + +features: + - icon: markdown + title: Dynamical Evolution + details: Advanced solvers for time evolution of quantum systems, thanks to the powerful DifferentialEquations.jl package. + link: /users_guide/time_evolution/intro + - icon: + title: GPU Computing + details: Leverage GPU resources for high-performance computing. Simulate the master equation directly on the GPU with the same syntax as the CPU case. + link: /getting_started + - icon: + title: Distributed Computing + details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate undreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. + link: /users_guide/time_evolution/mcsolve +--- +``` + +[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge Julia package designed for quantum physics simulations, closely emulating the popular Python [QuTiP](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. Taking advantage of the Julia language features (like multiple dispatch and metaprogramming), QuantumToolbox.jl is designed to be easily extendable, allowing users to build upon the existing functionalities. + +*__With this package, moving from Python to Julia for quantum physics simulations has never been easier__*, due to the similar syntax and functionalities. From 5d4acec4c25ddbe27ff348ab6fe9d1d46ec580dc Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 10 Nov 2024 00:15:19 +0900 Subject: [PATCH 098/329] Improve Documentation (#291) * Move favicon and logo to where `DocumenterVitepress` expects them * modify nav-bar and add `VersionPicker` * modify documentation --- README.md | 2 +- docs/make.jl | 15 +++-- docs/src/.vitepress/config.mts | 14 +++- docs/src/.vitepress/theme/VersionPicker.vue | 64 +++++++++++++++++++ docs/src/.vitepress/theme/index.ts | 4 +- docs/src/.vitepress/theme/style.css | 3 + docs/src/getting_started.md | 23 ------- docs/src/index.md | 28 ++++++++ docs/src/{assets => public}/favicon.ico | Bin docs/src/{assets => public}/logo.png | Bin docs/src/{ => resources}/api.md | 4 +- docs/src/{ => resources}/bibliography.bib | 0 docs/src/{ => resources}/bibliography.md | 2 - docs/src/users_guide/time_evolution/intro.md | 2 + 14 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 docs/src/.vitepress/theme/VersionPicker.vue rename docs/src/{assets => public}/favicon.ico (100%) rename docs/src/{assets => public}/logo.png (100%) rename docs/src/{ => resources}/api.md (97%) rename docs/src/{ => resources}/bibliography.bib (100%) rename docs/src/{ => resources}/bibliography.md (62%) diff --git a/README.md b/README.md index 2661116be..fbe84b5dd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- QuantumToolbox.jl logo + QuantumToolbox.jl logo
# QuantumToolbox.jl diff --git a/docs/make.jl b/docs/make.jl index 6e57eaddd..fbf82852f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,12 +10,15 @@ DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recu const DRAFT = false # set `true` to disable cell evaluation -bib = CitationBibliography(joinpath(@__DIR__, "src", "bibliography.bib"), style=:authoryear) +bib = CitationBibliography( + joinpath(@__DIR__, "src", "resources", "bibliography.bib"), + style=:authoryear, +) const PAGES = [ "Home" => "index.md", "Getting Started" => [ - "Introduction" => "getting_started.md", + "Brief Example" => "getting_started.md", "Key differences from QuTiP" => "qutip_differences.md", "The Importance of Type-Stability" => "type_stability.md", # "Cite QuantumToolbox.jl" => "cite.md", @@ -51,9 +54,11 @@ const PAGES = [ "tutorials/logo.md", ], ], - "API" => "api.md", - "Bibliography" => "bibliography.md", - # "Change Log" => "changelog.md", + "Resources" => [ + "API" => "resources/api.md", + # "Change Log" => "resources/changelog.md", + "Bibliography" => "resources/bibliography.md", + ], ] makedocs(; diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 38dbec64f..b5f5ad01f 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -3,6 +3,18 @@ import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import mathjax3 from "markdown-it-mathjax3"; import footnote from "markdown-it-footnote"; +const navTemp = { + nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS', +} + +const nav = [ + ...navTemp.nav, + { text: 'Benchmarks', link: 'https://qutip.org/QuantumToolbox.jl/benchmarks/' }, + { + component: 'VersionPicker' + } +] + // https://vitepress.dev/reference/site-config export default defineConfig({ base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! @@ -35,7 +47,7 @@ export default defineConfig({ detailedView: true } }, - nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + nav, sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS', socialLinks: [ diff --git a/docs/src/.vitepress/theme/VersionPicker.vue b/docs/src/.vitepress/theme/VersionPicker.vue new file mode 100644 index 000000000..ba744d8f4 --- /dev/null +++ b/docs/src/.vitepress/theme/VersionPicker.vue @@ -0,0 +1,64 @@ + + + + + + + \ No newline at end of file diff --git a/docs/src/.vitepress/theme/index.ts b/docs/src/.vitepress/theme/index.ts index 1072d80a5..ae0c3d3a8 100644 --- a/docs/src/.vitepress/theme/index.ts +++ b/docs/src/.vitepress/theme/index.ts @@ -2,6 +2,7 @@ import { h } from 'vue' import type { Theme } from 'vitepress' import DefaultTheme from 'vitepress/theme' +import VersionPicker from "./VersionPicker.vue" import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' import './style.css' @@ -14,6 +15,7 @@ export default { }) }, enhanceApp({ app, router, siteData }) { - enhanceAppWithTabs(app) + enhanceAppWithTabs(app); + app.component('VersionPicker', VersionPicker); } } satisfies Theme diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css index 2617bf119..a520b4a11 100644 --- a/docs/src/.vitepress/theme/style.css +++ b/docs/src/.vitepress/theme/style.css @@ -95,10 +95,13 @@ code { #9558B2 30%, #CB3C33); + --vp-home-hero-image-background-image: none; /* remove the blur background */ + /* (default setting) --vp-home-hero-image-background-image: linear-gradient(-45deg, #9558B2 30%, #389826 30%, #CB3C33); + */ --vp-home-hero-image-filter: blur(40px); } diff --git a/docs/src/getting_started.md b/docs/src/getting_started.md index b5995e96a..377f33ec9 100644 --- a/docs/src/getting_started.md +++ b/docs/src/getting_started.md @@ -2,29 +2,6 @@ CurrentModule = QuantumToolbox ``` -## [Installation](@id doc:Installation) - -!!! note "Requirements" - `QuantumToolbox.jl` requires `Julia 1.10+`. - -To install `QuantumToolbox.jl`, run the following commands inside Julia's interactive session (also known as REPL): -```julia -using Pkg -Pkg.add("QuantumToolbox") -``` -Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: -```julia-REPL -(1.10) pkg> add QuantumToolbox -``` -More information about `Julia`'s package manager can be found at [`Pkg.jl`](https://julialang.github.io/Pkg.jl/v1/). - -To load the package and check the version information, use either [`QuantumToolbox.versioninfo()`](@ref) or [`QuantumToolbox.about()`](@ref), namely -```julia -using QuantumToolbox -QuantumToolbox.versioninfo() -QuantumToolbox.about() -``` - ## Brief Example We now provide a brief example to demonstrate the similarity between [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) and [QuTiP](https://github.com/qutip/qutip). diff --git a/docs/src/index.md b/docs/src/index.md index 4aac26a55..f69e0bdf4 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -13,6 +13,9 @@ hero: - theme: brand text: Getting Started link: /getting_started + - theme: alt + text: Users Guide + link: /users_guide/QuantumObject/QuantumObject - theme: alt text: View on Github link: https://github.com/qutip/QuantumToolbox.jl @@ -37,6 +40,31 @@ features: --- ``` +# [Introduction](@id doc:Introduction) + [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge Julia package designed for quantum physics simulations, closely emulating the popular Python [QuTiP](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. Taking advantage of the Julia language features (like multiple dispatch and metaprogramming), QuantumToolbox.jl is designed to be easily extendable, allowing users to build upon the existing functionalities. *__With this package, moving from Python to Julia for quantum physics simulations has never been easier__*, due to the similar syntax and functionalities. + +# [Installation](@id doc:Installation) + +!!! note "Requirements" + `QuantumToolbox.jl` requires `Julia 1.10+`. + +To install `QuantumToolbox.jl`, run the following commands inside Julia's interactive session (also known as REPL): +```julia +using Pkg +Pkg.add("QuantumToolbox") +``` +Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: +```julia-REPL +(1.10) pkg> add QuantumToolbox +``` +More information about `Julia`'s package manager can be found at [`Pkg.jl`](https://julialang.github.io/Pkg.jl/v1/). + +To load the package and check the version information, use either [`QuantumToolbox.versioninfo()`](@ref) or [`QuantumToolbox.about()`](@ref), namely +```julia +using QuantumToolbox +QuantumToolbox.versioninfo() +QuantumToolbox.about() +``` diff --git a/docs/src/assets/favicon.ico b/docs/src/public/favicon.ico similarity index 100% rename from docs/src/assets/favicon.ico rename to docs/src/public/favicon.ico diff --git a/docs/src/assets/logo.png b/docs/src/public/logo.png similarity index 100% rename from docs/src/assets/logo.png rename to docs/src/public/logo.png diff --git a/docs/src/api.md b/docs/src/resources/api.md similarity index 97% rename from docs/src/api.md rename to docs/src/resources/api.md index 4dab36aff..4280411db 100644 --- a/docs/src/api.md +++ b/docs/src/resources/api.md @@ -4,11 +4,13 @@ CurrentModule = QuantumToolbox # [API](@id doc-API) -## Contents + ## [Quantum object (Qobj) and type](@id doc-API:Quantum-object-and-type) diff --git a/docs/src/bibliography.bib b/docs/src/resources/bibliography.bib similarity index 100% rename from docs/src/bibliography.bib rename to docs/src/resources/bibliography.bib diff --git a/docs/src/bibliography.md b/docs/src/resources/bibliography.md similarity index 62% rename from docs/src/bibliography.md rename to docs/src/resources/bibliography.md index 74d56db57..45632c00e 100644 --- a/docs/src/bibliography.md +++ b/docs/src/resources/bibliography.md @@ -2,7 +2,5 @@ CurrentModule = QuantumToolbox ``` -# [Bibliography](@id doc:Bibliography) - ```@bibliography ``` diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index 37488c956..4b099af0f 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -1,5 +1,6 @@ # [Time Evolution and Quantum System Dynamics](@id doc:Time-Evolution-and-Quantum-System-Dynamics) + # [Introduction](@id doc-TE:Introduction) From 87a99326a4f030be2b5123143f9b47f64449b748 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sun, 10 Nov 2024 03:32:52 +0100 Subject: [PATCH 099/329] Add doctest in all docstrings --- src/linear_maps.jl | 2 +- src/metrics.jl | 4 +- src/negativity.jl | 6 +- src/qobj/arithmetic_and_attributes.jl | 35 +++++++----- src/qobj/eigsolve.jl | 52 ++++++++++-------- src/qobj/functions.jl | 47 +++++----------- src/qobj/operators.jl | 8 +-- src/qobj/quantum_object.jl | 2 +- src/qobj/quantum_object_base.jl | 2 +- src/qobj/quantum_object_evo.jl | 64 +++++----------------- src/qobj/states.jl | 2 +- src/qobj/synonyms.jl | 79 ++++++++++----------------- src/utilities.jl | 8 +-- src/wigner.jl | 35 +++--------- 14 files changed, 132 insertions(+), 214 deletions(-) diff --git a/src/linear_maps.jl b/src/linear_maps.jl index 61ac4c96c..a5e4082aa 100644 --- a/src/linear_maps.jl +++ b/src/linear_maps.jl @@ -30,7 +30,7 @@ It is typically represented as a matrix with dimensions given by `size`, and thi As an example, we now define the linear map used in the [`eigsolve_al`](@ref) function for Arnoldi-Lindblad eigenvalue solver: -```julia-repl +```julia struct ArnoldiLindbladIntegratorMap{T,TS,TI} <: AbstractLinearMap{T,TS} elty::Type{T} size::TS diff --git a/src/metrics.jl b/src/metrics.jl index d8fd4f5ef..0e2e5fd24 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -19,7 +19,7 @@ matrix ``\hat{\rho}``. # Examples Pure state: -``` +```jldoctest julia> ψ = fock(2,0) Quantum Object: type=Ket dims=[2] size=(2,) 2-element Vector{ComplexF64}: @@ -37,7 +37,7 @@ julia> entropy_vn(ρ, base=2) ``` Mixed state: -``` +```jldoctest julia> ρ = maximally_mixed_dm(2) Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 Diagonal{ComplexF64, Vector{ComplexF64}}: diff --git a/src/negativity.jl b/src/negativity.jl index 35d91c75f..416020c87 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -17,7 +17,7 @@ and ``\Vert \hat{X} \Vert_1=\textrm{Tr}\sqrt{\hat{X}^\dagger \hat{X}}`` is the t # Examples -``` +```jldoctest julia> Ψ = bell_state(0, 0) Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: @@ -34,8 +34,8 @@ Quantum Object: type=Operator dims=[2, 2] size=(4, 4) ishermitian=true 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.5+0.0im 0.0+0.0im 0.0+0.0im 0.5+0.0im -julia> negativity(ρ, 2) -0.4999999999999998 +julia> negativity(ρ, 2) |> round +0.0 ``` """ function negativity(ρ::QuantumObject, subsys::Int; logarithmic::Bool = false) diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 5314d9156..5a9c7d981 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -217,15 +217,15 @@ Note that this function only supports for [`Operator`](@ref) and [`SuperOperator # Examples -``` +```jldoctest julia> a = destroy(20) Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: -⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢ +⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⎦ julia> tr(a' * a) 190.0 + 0.0im @@ -257,7 +257,7 @@ Return the standard vector `p`-norm or [Schatten](https://en.wikipedia.org/wiki/ # Examples -``` +```jldoctest julia> ψ = fock(10, 2) Quantum Object: type=Ket dims=[10] size=(10,) 10-element Vector{ComplexF64}: @@ -474,8 +474,9 @@ proj(ψ::QuantumObject{<:AbstractArray{T},BraQuantumObject}) where {T} = ψ' * Note that this function will always return [`Operator`](@ref). No matter the input [`QuantumObject`](@ref) is a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). # Examples + Two qubits in the state ``\ket{\psi} = \ket{e,g}``: -``` +```jldoctest julia> ψ = kron(fock(2,0), fock(2,1)) Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: @@ -492,7 +493,7 @@ Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true ``` or in an entangled state ``\ket{\psi} = \frac{1}{\sqrt{2}} \left( \ket{e,e} + \ket{g,g} \right)``: -``` +```jldoctest julia> ψ = 1 / √2 * (kron(fock(2,0), fock(2,0)) + kron(fock(2,1), fock(2,1))) Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: @@ -693,12 +694,16 @@ Note that this method currently works for [`Ket`](@ref), [`Bra`](@ref), and [`Op If `order = [2, 1, 3]`, the Hilbert space structure will be re-arranged: ``\mathcal{H}_1 \otimes \mathcal{H}_2 \otimes \mathcal{H}_3 \rightarrow \mathcal{H}_2 \otimes \mathcal{H}_1 \otimes \mathcal{H}_3``. -``` -julia> ψ1 = fock(2, 0) -julia> ψ2 = fock(3, 1) -julia> ψ3 = fock(4, 2) -julia> ψ_123 = tensor(ψ1, ψ2, ψ3) -julia> permute(ψ_123, [2, 1, 3]) ≈ tensor(ψ2, ψ1, ψ3) +```jldoctest +julia> ψ1 = fock(2, 0); + +julia> ψ2 = fock(3, 1); + +julia> ψ3 = fock(4, 2); + +julia> ψ_123 = tensor(ψ1, ψ2, ψ3); + +julia> permute(ψ_123, (2, 1, 3)) ≈ tensor(ψ2, ψ1, ψ3) true ``` diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 0c972a3f7..63b4be999 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -30,10 +30,19 @@ A struct containing the eigenvalues, the eigenvectors, and some information from # Examples One can obtain the eigenvalues and the corresponding [`QuantumObject`](@ref)-type eigenvectors by: -``` -julia> result = eigenstates(sigmax()); +```jldoctest +julia> result = eigenstates(sigmax()) +EigsolveResult: type=Operator dims=[2] +values: +2-element Vector{ComplexF64}: + -1.0 + 0.0im + 1.0 + 0.0im +vectors: +2×2 Matrix{ComplexF64}: + -0.707107+0.0im 0.707107+0.0im + 0.707107+0.0im 0.707107+0.0im -julia> λ, ψ, T = result; +julia> λ, ψ, U = result; julia> λ 2-element Vector{ComplexF64}: @@ -41,11 +50,17 @@ julia> λ 1.0 + 0.0im julia> ψ -2-element Vector{QuantumObject{Vector{ComplexF64}, KetQuantumObject}}: - QuantumObject{Vector{ComplexF64}, KetQuantumObject}(ComplexF64[-0.7071067811865475 + 0.0im, 0.7071067811865475 + 0.0im], KetQuantumObject(), [2]) - QuantumObject{Vector{ComplexF64}, KetQuantumObject}(ComplexF64[0.7071067811865475 + 0.0im, 0.7071067811865475 + 0.0im], KetQuantumObject(), [2]) +2-element Vector{QuantumObject{Vector{ComplexF64}, KetQuantumObject, 1}}: + Quantum Object: type=Ket dims=[2] size=(2,) +2-element Vector{ComplexF64}: + -0.7071067811865475 + 0.0im + 0.7071067811865475 + 0.0im + Quantum Object: type=Ket dims=[2] size=(2,) +2-element Vector{ComplexF64}: + 0.7071067811865475 + 0.0im + 0.7071067811865475 + 0.0im -julia> T +julia> U 2×2 Matrix{ComplexF64}: -0.707107+0.0im 0.707107+0.0im 0.707107+0.0im 0.707107+0.0im @@ -411,27 +426,20 @@ end Calculates the eigenvalues and eigenvectors of the [`QuantumObject`](@ref) `A` using the Julia [LinearAlgebra](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/) package. -``` +```jldoctest julia> a = destroy(5); -julia> H = a + a' -Quantum Object: type=Operator dims=[5] size=(5, 5) ishermitian=true -5×5 SparseMatrixCSC{ComplexF64, Int64} with 8 stored entries: - ⋅ 1.0+0.0im ⋅ ⋅ ⋅ - 1.0+0.0im ⋅ 1.41421+0.0im ⋅ ⋅ - ⋅ 1.41421+0.0im ⋅ 1.73205+0.0im ⋅ - ⋅ ⋅ 1.73205+0.0im ⋅ 2.0+0.0im - ⋅ ⋅ ⋅ 2.0+0.0im ⋅ +julia> H = a + a'; julia> E, ψ, U = eigen(H) EigsolveResult: type=Operator dims=[5] values: -5-element Vector{Float64}: - -2.8569700138728 - -1.3556261799742608 - 1.3322676295501878e-15 - 1.3556261799742677 - 2.8569700138728056 +5-element Vector{ComplexF64}: + -2.8569700138728 + 0.0im + -1.3556261799742608 + 0.0im + 1.3322676295501878e-15 + 0.0im + 1.3556261799742677 + 0.0im + 2.8569700138728056 + 0.0im vectors: 5×5 Matrix{ComplexF64}: 0.106101+0.0im -0.471249-0.0im … 0.471249-0.0im 0.106101-0.0im diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index ef98af4d9..1cc463913 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -31,7 +31,7 @@ Note that `ψ` can also be given as a list of [`QuantumObject`](@ref), it return # Examples -``` +```jldoctest julia> ψ = 1 / √2 * (fock(10,2) + fock(10,4)); julia> a = destroy(10); @@ -153,40 +153,23 @@ Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) # Examples -``` +```jldoctest julia> a = destroy(20) Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: -⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢ - -julia> kron(a, a) -Quantum Object: type=Operator dims=[20, 20] size=(400, 400) ishermitian=false -400×400 SparseMatrixCSC{ComplexF64, Int64} with 361 stored entries: -⠀⠀⠘⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦ +⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⎦ + +julia> O = kron(a, a); + +julia> size(a), size(O) +((20, 20), (400, 400)) + +julia> a.dims, O.dims +([20], [20, 20]) ``` """ function LinearAlgebra.kron( diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 9ce680012..b2036dce7 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -88,7 +88,7 @@ This operator acts on a fock state as ``\hat{a} \ket{n} = \sqrt{n} \ket{n-1}``. # Examples -``` +```jldoctest julia> a = destroy(20) Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: @@ -113,7 +113,7 @@ This operator acts on a fock state as ``\hat{a}^\dagger \ket{n} = \sqrt{n+1} \ke # Examples -``` +```jldoctest julia> a_d = create(20) Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: @@ -242,14 +242,14 @@ The parameter `which` specifies which of the following operator to return. Note that if the parameter `which` is not specified, returns a set of Spin-`j` operators: ``(\hat{S}_x, \hat{S}_y, \hat{S}_z)`` # Examples -``` +```jldoctest julia> jmat(0.5, :x) Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries: ⋅ 0.5+0.0im 0.5+0.0im ⋅ -julia> jmat(0.5, :-) +julia> jmat(0.5, Val(:-)) Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false 2×2 SparseMatrixCSC{ComplexF64, Int64} with 1 stored entry: ⋅ ⋅ diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index d31f95ac3..46a38d7d2 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -19,7 +19,7 @@ Julia struct representing any quantum objects. # Examples -``` +```jldoctest julia> a = destroy(20) Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index ea4f9df29..e4cb9644f 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -19,7 +19,7 @@ export Bra, Ket, Operator, OperatorBra, OperatorKet, SuperOperator Abstract type for all quantum objects like [`QuantumObject`](@ref) and [`QuantumObjectEvolution`](@ref). # Example -``` +```jldoctest julia> sigmax() isa AbstractQuantumObject true ``` diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index d2acb544a..8abadb3b0 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -17,7 +17,7 @@ where ``c_i(p, t)`` is a function that depends on the parameters `p` and time `t # Examples This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example -``` +```jldoctest qobjevo julia> a = tensor(destroy(10), qeye(2)) Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: @@ -31,13 +31,13 @@ julia> coef1(p, t) = exp(-1im * t) coef1 (generic function with 1 method) julia> op = QuantumObjectEvolution(a, coef1) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) ``` If there are more than 2 operators, we need to put each set of operator and coefficient function into a two-element `Tuple`, and put all these `Tuple`s together in a larger `Tuple`: -``` +```jldoctest qobjevo julia> σm = tensor(qeye(10), sigmam()) Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: @@ -51,12 +51,12 @@ julia> coef2(p, t) = sin(t) coef2 (generic function with 1 method) julia> op1 = QuantumObjectEvolution(((a, coef1), (σm, coef2))) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) ``` We can also concretize the operator at a specific time `t` -``` +```jldoctest qobjevo julia> op1(0.1) Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: @@ -68,7 +68,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ``` It also supports parameter-dependent time evolution -``` +```jldoctest qobjevo julia> coef1(p, t) = exp(-1im * p.ω1 * t) coef1 (generic function with 1 method) @@ -76,7 +76,7 @@ julia> coef2(p, t) = sin(p.ω2 * t) coef2 (generic function with 1 method) julia> op1 = QuantumObjectEvolution(((a, coef1), (σm, coef2))) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) julia> p = (ω1 = 1.0, ω2 = 0.5) @@ -310,7 +310,7 @@ Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the inpu - `ψout::QuantumObject`: The output state. # Examples -``` +```jldoctest julia> a = destroy(20) Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: @@ -327,49 +327,15 @@ julia> coef2(p, t) = cos(t) coef2 (generic function with 1 method) julia> A = QobjEvo(((a, coef1), (a', coef2))) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=true +Quantum Object Evo.: type=Operator dims=[20] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) -julia> ψ1 = fock(20, 3) -Quantum Object: type=Ket dims=[20] size=(20,) -20-element Vector{ComplexF64}: - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - 1.0 + 0.0im - 0.0 + 0.0im - ⋮ - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - -julia> ψ2 = zero_ket(20) -Quantum Object: type=Ket dims=[20] size=(20,) -20-element Vector{ComplexF64}: - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - ⋮ - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - -julia> A(ψ2, ψ1, nothing, 0.1) -20-element Vector{ComplexF64}: - 0.0 + 0.0im - 0.0 + 0.0im - 0.1729165499254989 + 0.0im - 0.0 + 0.0im - 1.9900083305560516 + 0.0im - ⋮ - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im - 0.0 + 0.0im +julia> ψ1 = fock(20, 3); + +julia> ψ2 = zero_ket(20); + +julia> A(ψ2, ψ1, nothing, 0.1) ≈ A(0.1) * ψ1 +true ``` """ function (A::QuantumObjectEvolution)( diff --git a/src/qobj/states.jl b/src/qobj/states.jl index 92c5ff205..8634c2788 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -252,7 +252,7 @@ Here, `x = 1` (`z = 1`) means applying Pauli-``X`` ( Pauli-``Z``) unitary transf # Example -``` +```jldoctest julia> bell_state(0, 0) Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 05a3a95a1..6518d00af 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -25,7 +25,7 @@ Generate [`QuantumObjectEvolution`](@ref). Note that this functions is same as `QuantumObjectEvolution(op, f; type = type)`. 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`. # Examples -``` +```jldoctest julia> a = tensor(destroy(10), qeye(2)) Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: @@ -36,10 +36,10 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ julia> coef(p, t) = exp(-1im * t) -coef1 (generic function with 1 method) +coef (generic function with 1 method) julia> op = QobjEvo(a, coef) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) ``` """ @@ -62,7 +62,7 @@ Note that this functions is same as `QuantumObjectEvolution(op_func_list)`. If ` # Examples This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example -``` +```jldoctest qobjevo julia> a = tensor(destroy(10), qeye(2)) Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: @@ -88,12 +88,12 @@ julia> coef2(p, t) = sin(t) coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) ``` We can also concretize the operator at a specific time `t` -``` +```jldoctest qobjevo julia> op1(0.1) Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: @@ -105,7 +105,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ``` It also supports parameter-dependent time evolution -``` +```jldoctest qobjevo julia> coef1(p, t) = exp(-1im * p.ω1 * t) coef1 (generic function with 1 method) @@ -113,7 +113,7 @@ julia> coef2(p, t) = sin(p.ω2 * t) coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) julia> p = (ω1 = 1.0, ω2 = 0.5) @@ -290,7 +290,7 @@ Note that this function is same as `kron(A, B, ...)`. # Examples -``` +```jldoctest julia> x = sigmax() Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries: @@ -299,17 +299,11 @@ Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true julia> x_list = fill(x, 3); -julia> tensor(x_list...) -Quantum Object: type=Operator dims=[2, 2, 2] size=(8, 8) ishermitian=true -8×8 SparseMatrixCSC{ComplexF64, Int64} with 8 stored entries: - ⋅ ⋅ ⋅ … ⋅ ⋅ 1.0+0.0im - ⋅ ⋅ ⋅ ⋅ 1.0+0.0im ⋅ - ⋅ ⋅ ⋅ 1.0+0.0im ⋅ ⋅ - ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ - ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ - ⋅ ⋅ 1.0+0.0im … ⋅ ⋅ ⋅ - ⋅ 1.0+0.0im ⋅ ⋅ ⋅ ⋅ - 1.0+0.0im ⋅ ⋅ ⋅ ⋅ ⋅ +julia> tensor(x_list...).dims +3-element SVector{3, Int64} with indices SOneTo(3): + 2 + 2 + 2 ``` """ tensor(A...) = kron(A...) @@ -323,40 +317,23 @@ Note that this function is same as `kron(A, B)`. # Examples -``` +```jldoctest julia> a = destroy(20) Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: -⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢ - -julia> a ⊗ a -Quantum Object: type=Operator dims=[20, 20] size=(400, 400) ishermitian=false -400×400 SparseMatrixCSC{ComplexF64, Int64} with 361 stored entries: -⠀⠀⠘⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦ +⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⎦ + +julia> O = a ⊗ a; + +julia> size(a), size(O) +((20, 20), (400, 400)) + +julia> a.dims, O.dims +([20], [20, 20]) ``` """ ⊗(A::QuantumObject, B::QuantumObject) = kron(A, B) diff --git a/src/utilities.jl b/src/utilities.jl index 150751e6a..5a426bd17 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -67,7 +67,7 @@ The current stored constants are: # Examples -``` +```jldoctest julia> PhysicalConstants.ħ 1.0545718176461565e-34 ``` @@ -111,15 +111,15 @@ Note that we use the values stored in [`PhysicalConstants`](@ref) to do the conv # Examples -``` +```jldoctest julia> convert_unit(1, :eV, :J) 1.602176634e-19 julia> convert_unit(1, :GHz, :J) 6.62607015e-25 -julia> convert_unit(1, :meV, :mK) -11604.518121550082 +julia> round(convert_unit(1, :meV, :mK), digits=4) +11604.5181 ``` """ function convert_unit(value::T, unit1::Symbol, unit2::Symbol) where {T<:Real} diff --git a/src/wigner.jl b/src/wigner.jl index c64bf8181..4c8ee7fac 100644 --- a/src/wigner.jl +++ b/src/wigner.jl @@ -35,7 +35,7 @@ The `method` parameter can be either `WignerLaguerre()` or `WignerClenshaw()`. T - `W::Matrix`: The Wigner function of the state at the points `xvec + 1im * yvec` in phase space. # Example -``` +```jldoctest wigner julia> ψ = fock(10, 0) + fock(10, 1) |> normalize Quantum Object: type=Ket dims=[10] size=(10,) 10-element Vector{ComplexF64}: @@ -53,37 +53,16 @@ Quantum Object: type=Ket dims=[10] size=(10,) julia> xvec = range(-5, 5, 200) -5.0:0.05025125628140704:5.0 -julia> wig = wigner(ψ, xvec, xvec) -200×200 Matrix{Float64}: - 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 … 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 - 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 - 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 - 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 - 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 - ⋮ ⋱ - 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 … 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 - 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 - 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 - 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 - 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 +julia> wig = wigner(ψ, xvec, xvec); ``` or taking advantage of the parallel computation of the `WignerLaguerre` method -``` -julia> wig = wigner(ρ, xvec, xvec, method=WignerLaguerre(parallel=true)) -200×200 Matrix{Float64}: - 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 … 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 - 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 - 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 - 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 - 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 - ⋮ ⋱ - 1.80254e-20 2.94073e-20 4.77351e-20 7.70963e-20 1.23892e-19 … 1.0374e-19 6.42289e-20 3.95652e-20 2.42491e-20 - 1.12314e-20 1.83256e-20 2.97505e-20 4.80558e-20 7.72344e-20 6.4463e-20 3.99074e-20 2.45808e-20 1.50639e-20 - 6.96278e-21 1.13621e-20 1.8448e-20 2.98026e-20 4.79043e-20 3.98553e-20 2.46711e-20 1.51947e-20 9.31096e-21 - 4.29467e-21 7.00905e-21 1.13816e-20 1.83891e-20 2.9562e-20 2.45173e-20 1.51752e-20 9.3454e-21 5.72614e-21 - 2.63558e-21 4.30187e-21 6.98638e-21 1.12892e-20 1.81505e-20 1.50062e-20 9.28736e-21 5.71895e-21 3.50382e-21 +```jldoctest wigner +julia> ρ = ket2dm(ψ) |> dense_to_sparse; + +julia> wig = wigner(ρ, xvec, xvec, method=WignerLaguerre(parallel=true)); + ``` """ function wigner( From f220410018979335f45cf0367300d996bfbc9eb9 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 10 Nov 2024 18:39:30 +0900 Subject: [PATCH 100/329] Add Makefile command for starting Vitepress locally and some minor changes (#293) * add Makefile command for starting Vitepress locally * minor changes --- Makefile | 9 +++++++-- README.md | 2 +- docs/README.md | 27 ++++++++++++++------------- docs/src/index.md | 10 +++++++--- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 8255db3fd..25cf0265e 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,18 @@ docs: ${JULIA} --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' ${JULIA} --project=docs docs/make.jl -all: format test docs +vitepress: + npm --prefix docs i + npm --prefix docs run docs:dev + +all: format test docs vitepress help: @echo "The following make commands are available:" @echo " - make format: format codes with JuliaFormatter" @echo " - make test: run the tests" @echo " - make docs: instantiate and build the documentation" + @echo " - make vitepress: start Vitepress site of documentation" @echo " - make all: run every commands in the above order" -.PHONY: default format test docs all help +.PHONY: default format test docs vitepress all help diff --git a/README.md b/README.md index fbe84b5dd..8f01caf31 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ QuantumToolbox.jl is equipped with a robust set of features: - **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as QuTiP. - **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) package. -- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation directly on the GPU with the same syntax as the CPU case. +- **GPU Computing:** Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. - **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. - **Easy Extension:** Easily extend the package, taking advantage of the Julia language features, like multiple dispatch and metaprogramming. diff --git a/docs/README.md b/docs/README.md index 59b6db243..488aa85fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# How to build documentation locally ? +# How to build documentation and start Vitepress site locally ? ## Working Directory All the commands should be run under the root folder of the package: `/path/to/QuantumToolbox.jl/` @@ -6,12 +6,19 @@ All the commands should be run under the root folder of the package: `/path/to/Q The document pages will be generated in the directory: `/path/to/QuantumToolbox.jl/docs/build/` (which is ignored by git). ## Method 1: Run with `make` command -Run the following command: +Run the following command to instantiate and build the documentation: ```shell make docs ``` -## Method 2: Run `julia` command manually +Run the following command to start Vitepress site of documentation: +> [!NOTE] +> You need to install `Node.js` and `npm` first. +```shell +make vitepress +``` + +## Method 2: Run commands manually ### Build Pkg Run the following command: @@ -26,22 +33,16 @@ Run the following command: julia --project=docs docs/make.jl ``` -# How to start a local Vitepress site ? - +### Start a local Vitepress site > [!NOTE] -> You need to install `Node.js` and `npm` first. - -Enter `docs` directory first: -```shell -cd /path/to/QuantumToolbox.jl/docs -``` +> You need to install `Node.js` and `npm` first. Install `npm` dependencies: ```shell -npm i +npm --prefix docs i ``` Run the following command: ```shell -npm run docs:dev +npm --prefix docs run docs:dev ``` \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index f69e0bdf4..33b714a53 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,7 +5,7 @@ layout: home hero: name: "QuantumToolbox.jl" - tagline: High-performance quantum simulations made simple + tagline: A pure Julia framework designed for High-performance quantum physics simulations image: src: /logo.png alt: QuantumToolbox @@ -25,17 +25,21 @@ hero: features: + - icon: markdown + title: QuTiP + details: Easily handle quantum states and operators with a rich set of tools, with the same functionalities as Python QuTiP. + link: https://qutip.org/ - icon: markdown title: Dynamical Evolution details: Advanced solvers for time evolution of quantum systems, thanks to the powerful DifferentialEquations.jl package. link: /users_guide/time_evolution/intro - icon: title: GPU Computing - details: Leverage GPU resources for high-performance computing. Simulate the master equation directly on the GPU with the same syntax as the CPU case. + details: Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. link: /getting_started - icon: title: Distributed Computing - details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate undreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. + details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. link: /users_guide/time_evolution/mcsolve --- ``` From b3bde8676697562cdf771135e14462282c4a6d35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 05:14:32 +0000 Subject: [PATCH 101/329] Bump crate-ci/typos from 1.27.0 to 1.27.3 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.27.0 to 1.27.3. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/v1.27.0...v1.27.3) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 604011f0f..3f3de3604 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.27.0 \ No newline at end of file + uses: crate-ci/typos@v1.27.3 \ No newline at end of file From c21855b461945f31b4463863c2d794f6a2899a90 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 11 Nov 2024 09:04:08 +0100 Subject: [PATCH 102/329] Fix round error --- src/negativity.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/negativity.jl b/src/negativity.jl index 416020c87..c59844879 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -34,8 +34,8 @@ Quantum Object: type=Operator dims=[2, 2] size=(4, 4) ishermitian=true 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.5+0.0im 0.0+0.0im 0.0+0.0im 0.5+0.0im -julia> negativity(ρ, 2) |> round -0.0 +julia> round(negativity(ρ, 2), digits=2) +0.5 ``` """ function negativity(ρ::QuantumObject, subsys::Int; logarithmic::Bool = false) From cad8e14d8eabc14fddf45fc706a3ab1e0fd6bc71 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:42:51 +0900 Subject: [PATCH 103/329] updates for documentation (#296) * fix repo link * modify qutip hyper-link * update bib * update footer and fix toc * fix latex `eqref` --- docs/make.jl | 2 +- docs/src/.vitepress/config.mts | 9 ++++-- docs/src/index.md | 9 +++--- docs/src/resources/api.md | 6 +--- docs/src/resources/bibliography.bib | 22 +++++++++++++ docs/src/users_guide/states_and_operators.md | 4 +-- docs/src/users_guide/time_evolution/intro.md | 31 ++++++++++--------- .../src/users_guide/time_evolution/mesolve.md | 25 +++++++-------- .../src/users_guide/time_evolution/sesolve.md | 4 +-- .../users_guide/time_evolution/solution.md | 6 ++-- 10 files changed, 70 insertions(+), 48 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index fbf82852f..a60d1c39b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -68,7 +68,7 @@ makedocs(; sitename = "QuantumToolbox.jl", pages = PAGES, format = DocumenterVitepress.MarkdownVitepress( - repo = "https://qutip.github.io/QuantumToolbox.jl", + repo = "github.com/qutip/QuantumToolbox.jl", ), draft = DRAFT, plugins = [bib], diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index b5f5ad01f..42118b3df 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -28,6 +28,11 @@ export default defineConfig({ markdown: { math: true, + + // options for @mdit-vue/plugin-toc + // https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-toc#options + toc: { level: [1, 2] }, + config(md) { md.use(tabsMarkdownPlugin), md.use(mathjax3), @@ -54,8 +59,8 @@ export default defineConfig({ { icon: 'github', link: 'REPLACE_ME_DOCUMENTER_VITEPRESS' } ], footer: { - message: 'Made with DocumenterVitepress.jl
', - copyright: `© Copyright ${new Date().getUTCFullYear()}.` + message: 'Made with Documenter.jl, VitePress and DocumenterVitepress.jl
Released under the BSD 3-Clause License. Powered by the Julia Programming Language.
', + copyright: `© Copyright ${new Date().getUTCFullYear()} QuTiP.org.` } } }) diff --git a/docs/src/index.md b/docs/src/index.md index 33b714a53..47585fe81 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -22,13 +22,12 @@ hero: - theme: alt text: API link: /api + - theme: alt + text: Visit QuTiP.org + link: https://qutip.org/ features: - - icon: markdown - title: QuTiP - details: Easily handle quantum states and operators with a rich set of tools, with the same functionalities as Python QuTiP. - link: https://qutip.org/ - icon: markdown title: Dynamical Evolution details: Advanced solvers for time evolution of quantum systems, thanks to the powerful DifferentialEquations.jl package. @@ -61,7 +60,7 @@ using Pkg Pkg.add("QuantumToolbox") ``` Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: -```julia-REPL +```julia-repl (1.10) pkg> add QuantumToolbox ``` More information about `Julia`'s package manager can be found at [`Pkg.jl`](https://julialang.github.io/Pkg.jl/v1/). diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 4280411db..59f4d22d6 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -4,13 +4,9 @@ CurrentModule = QuantumToolbox # [API](@id doc-API) - +[[toc]] ## [Quantum object (Qobj) and type](@id doc-API:Quantum-object-and-type) diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index 727766d5f..931ea3bfb 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -1,3 +1,25 @@ +@book{Nielsen-Chuang2011, + Author = {Michael A. Nielsen and Isaac L. Chuang}, + Title = {Quantum Computation and Quantum Information: 10th Anniversary Edition}, + Publisher = {Cambridge University Press}, + Year = {2011}, + ISBN = {9781107002173}, + URL = {https://www.amazon.com/Quantum-Computation-Information-10th-Anniversary/dp/1107002176?SubscriptionId=AKIAIOBINVZYXZQZ2U3A&tag=chimbori05-20&linkCode=xm2&camp=2025&creative=165953&creativeASIN=1107002176} +} + +@article{Jozsa1994, + author = {Richard Jozsa}, + title = {Fidelity for Mixed Quantum States}, + journal = {Journal of Modern Optics}, + volume = {41}, + number = {12}, + pages = {2315--2323}, + year = {1994}, + publisher = {Taylor \& Francis}, + doi = {10.1080/09500349414552171}, + URL = {https://doi.org/10.1080/09500349414552171}, +} + @article{gravina2024adaptive, title = {{Adaptive variational low-rank dynamics for open quantum systems}}, author = {Gravina, Luca and Savona, Vincenzo}, diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index 85993c2c9..eaae89727 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -20,7 +20,7 @@ and then create a lowering operator ``\hat{a}`` corresponding to `5` number stat a = destroy(5) ``` -Now lets apply the lowering operator `\hat{a}` to our vacuum state `vac`: +Now lets apply the lowering operator ``\hat{a}`` to our vacuum state `vac`: ```@example states_and_operators a * vac @@ -172,7 +172,7 @@ z = thermal_dm(5, 0.125) fidelity(x, y) ``` -Note that the definition of [`fidelity`](@ref) here is from **Nielsen & Chuang, "Quantum Computation and Quantum Information"**. It is the square root of the fidelity defined in **R. Jozsa, Journal of Modern Optics, 41:12, 2315 (1994)**. We also know that for two pure states, the trace distance (``T``) and the fidelity (``F``) are related by ``T = \sqrt{1-F^2}``: +Note that the definition of [`fidelity`](@ref) here is from [Nielsen-Chuang2011](@cite). It is the square root of the fidelity defined in [Jozsa1994](@cite). We also know that for two pure states, the trace distance (``T``) and the fidelity (``F``) are related by ``T = \sqrt{1-F^2}``: ```@example states_and_operators tracedist(x, y) ≈ sqrt(1 - (fidelity(x, y))^2) diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index 4b099af0f..2aaf3eeb0 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -1,21 +1,24 @@ # [Time Evolution and Quantum System Dynamics](@id doc:Time-Evolution-and-Quantum-System-Dynamics) - +- [Introduction](@ref doc-TE:Introduction) +- [Time Evolution Solutions](@ref doc-TE:Time-Evolution-Solutions) + - [Solution](@ref doc-TE:Solution) + - [Multiple trajectories solution](@ref doc-TE:Multiple-trajectories-solution) + - [Accessing data in solutions](@ref doc-TE:Accessing-data-in-solutions) +- [Schrödinger Equation Solver](@ref doc-TE:Schrödinger-Equation-Solver) + - [Unitary evolution](@ref doc-TE:Unitary-evolution) + - [Example: Spin dynamics](@ref doc-TE:Example:Spin-dynamics) +- [Lindblad Master Equation Solver](@ref doc-TE:Lindblad-Master-Equation-Solver) + - [Von Neumann equation](@ref doc-TE:Von-Neumann-equation) + - [The Lindblad master equation](@ref doc-TE:The-Lindblad-master-equation) + - [Example: Dissipative Spin dynamics](@ref doc-TE:Example:Dissipative-Spin-dynamics) + - [Example: Harmonic oscillator in thermal bath](@ref doc-TE:Example:Harmonic-oscillator-in-thermal-bath) + - [Example: Two-level atom coupled to dissipative single-mode cavity](@ref doc-TE:Example:Two-level-atom-coupled-to-dissipative-single-mode-cavity) +- [Monte-Carlo Solver](@ref doc-TE:Monte-Carlo-Solver) +- [Stochastic Solver](@ref doc-TE:Stochastic-Solver) +- [Solving Problems with Time-dependent Hamiltonians](@ref doc-TE:Solving-Problems-with-Time-dependent-Hamiltonians) # [Introduction](@id doc-TE:Introduction) diff --git a/docs/src/users_guide/time_evolution/mesolve.md b/docs/src/users_guide/time_evolution/mesolve.md index fdda3e9bb..6c06c1568 100644 --- a/docs/src/users_guide/time_evolution/mesolve.md +++ b/docs/src/users_guide/time_evolution/mesolve.md @@ -4,7 +4,7 @@ using QuantumToolbox ``` -## Von Neumann equation +## [Von Neumann equation](@id doc-TE:Von-Neumann-equation) While the evolution of the state vector in a closed quantum system is deterministic (as we discussed in the previous section: [Schrödinger Equation Solver](@ref doc-TE:Schrödinger-Equation-Solver)), open quantum systems are stochastic in nature. The effect of an environment on the system of interest is to induce stochastic transitions between energy levels, and to introduce uncertainty in the phase difference between states of the system. The state of an open quantum system is therefore described in terms of ensemble averaged states using the density matrix formalism. A density matrix ``\hat{\rho}`` describes a probability distribution of quantum states ``|\psi_n\rangle`` in a matrix representation, namely @@ -18,7 +18,6 @@ The time evolution of the density matrix ``\hat{\rho}(t)`` under closed system d ```math \begin{equation} -\label{von-Neumann-Eq} \frac{d}{dt}\hat{\rho}(t) = -\frac{i}{\hbar}\left[\hat{H}, \hat{\rho}(t)\right], \end{equation} ``` @@ -62,13 +61,12 @@ sol.expect sol.states ``` -## The Lindblad master equation +## [The Lindblad master equation](@id doc-TE:The-Lindblad-master-equation) -The standard approach for deriving the equations of motion for a system interacting with its environment is to expand the scope of the system to include the environment. The combined quantum system is then closed, and its evolution is governed by the von Neumann equation given in Eq. \eqref{von-Neumann-Eq} +The standard approach for deriving the equations of motion for a system interacting with its environment is to expand the scope of the system to include the environment. The combined quantum system is then closed, and its evolution is also governed by the von Neumann equation ```math \begin{equation} -\label{tot-von-Neumann-Eq} \frac{d}{dt}\hat{\rho}_{\textrm{tot}}(t) = -\frac{i}{\hbar}\left[\hat{H}_{\textrm{tot}}, \hat{\rho}_{\textrm{tot}}(t)\right]. \end{equation} ``` @@ -79,27 +77,26 @@ Here, the total Hamiltonian \hat{H}_{\textrm{tot}} = \hat{H}_{\textrm{sys}} + \hat{H}_{\textrm{env}} + \hat{H}_{\textrm{int}}, ``` -includes the original system Hamiltonian ``\hat{H}_{\textrm{sys}}``, the Hamiltonian for the environment ``\hat{H}_{\textrm{env}}``, and a term representing the interaction between the system and its environment ``\hat{H}_{\textrm{int}}``. Since we are only interested in the dynamics of the system, we can, perform a partial trace over the environmental degrees of freedom in Eq. \eqref{tot-von-Neumann-Eq}, and thereby obtain a master equation for the motion of the original system density matrix ``\hat{\rho}_{\textrm{sys}}(t)=\textrm{Tr}_{\textrm{env}}[\hat{\rho}_{\textrm{tot}}(t)]``. The most general trace-preserving and completely positive form of this evolution is the Lindblad master equation for the reduced density matrix, namely +includes the original system Hamiltonian ``\hat{H}_{\textrm{sys}}``, the Hamiltonian for the environment ``\hat{H}_{\textrm{env}}``, and a term representing the interaction between the system and its environment ``\hat{H}_{\textrm{int}}``. Since we are only interested in the dynamics of the system, we can, perform a partial trace over the environmental degrees of freedom, and thereby obtain a master equation for the motion of the original system density matrix ``\hat{\rho}_{\textrm{sys}}(t)=\textrm{Tr}_{\textrm{env}}[\hat{\rho}_{\textrm{tot}}(t)]``. The most general trace-preserving and completely positive form of this evolution is the Lindblad master equation for the reduced density matrix, namely ```math \begin{equation} -\label{Lindblad-master-Eq} \frac{d}{dt}\hat{\rho}_{\textrm{sys}}(t) = -\frac{i}{\hbar}\left[\hat{H}_{\textrm{sys}}, \hat{\rho}_{\textrm{sys}}(t)\right] + \sum_n \hat{C}_n \hat{\rho}_{\textrm{sys}}(t) \hat{C}_n^\dagger - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n \hat{\rho}_{\textrm{sys}}(t) - \frac{1}{2} \hat{\rho}_{\textrm{sys}}(t) \hat{C}_n^\dagger \hat{C}_n \end{equation} ``` -where ``\hat{C}_n \equiv \sqrt{\gamma_n}\hat{A}_n`` are the collapse operators, ``\hat{A}_n`` are the operators acting on the system in ``\hat{H}_{\textrm{int}}`` which describes the system-environment interaction, and ``\gamma_n`` are the corresponding rates. The derivation of Eq. \eqref{Lindblad-master-Eq} may be found in several sources, and will not be reproduced here. Instead, we emphasize the approximations that are required to arrive at the master equation in the form of Eq. \eqref{Lindblad-master-Eq} from physical arguments, and hence perform a calculation in `QuantumToolbox`: +where ``\hat{C}_n \equiv \sqrt{\gamma_n}\hat{A}_n`` are the collapse operators, ``\hat{A}_n`` are the operators acting on the system in ``\hat{H}_{\textrm{int}}`` which describes the system-environment interaction, and ``\gamma_n`` are the corresponding rates. The derivation of Lindblad master equation may be found in several sources, and will not be reproduced here. Instead, we emphasize the approximations that are required to arrive at the above Lindblad master equation from physical arguments, and hence perform a calculation in `QuantumToolbox`: - **Separability:** At ``t = 0``, there are no correlations between the system and environment, such that the total density matrix can be written as a tensor product, namely ``\hat{\rho}_{\textrm{tot}}(0)=\hat{\rho}_{\textrm{sys}}(0)\otimes\hat{\rho}_{\textrm{env}}(0)``. - **Born approximation:** Requires: (i) the state of the environment does not significantly change as a result of the interaction with the system; (ii) the system and the environment remain separable throughout the evolution. These assumptions are justified if the interaction is weak, and if the environment is much larger than the system. In summary, ``\hat{\rho}_{\textrm{tot}}(t)\approx\hat{\rho}_{\textrm{sys}}(t)\otimes\hat{\rho}_{\textrm{env}}(0)``. - **Markov approximation:** The time-scale of decay for the environment ``\tau_{\textrm{env}}`` is much shorter than the smallest time-scale of the system dynamics, i.e., ``\tau_{\textrm{sys}}\gg\tau_{\textrm{env}}``. This approximation is often deemed a “short-memory environment” as it requires the environmental correlation functions decay in a fast time-scale compared to those of the system. -- **Secular approximation:** Stipulates that elements in the master equation corresponding to transition frequencies satisfy ``|\omega_{ab}-\omega_{cd}| \ll 1/\tau_{\textrm{sys}}``, i.e., all fast rotating terms in the interaction picture can be neglected. It also ignores terms that lead to a small renormalization of the system energy levels. This approximation is not strictly necessary for all master-equation formalisms (e.g., the Block-Redfield master equation), but it is required for arriving at the Lindblad form in Eq. \eqref{Lindblad-master-Eq} which is used in [`mesolve`](@ref). +- **Secular approximation:** Stipulates that elements in the master equation corresponding to transition frequencies satisfy ``|\omega_{ab}-\omega_{cd}| \ll 1/\tau_{\textrm{sys}}``, i.e., all fast rotating terms in the interaction picture can be neglected. It also ignores terms that lead to a small renormalization of the system energy levels. This approximation is not strictly necessary for all master-equation formalisms (e.g., the Block-Redfield master equation), but it is required for arriving at the Lindblad form in the above equation which is used in [`mesolve`](@ref). -For systems with environments satisfying the conditions outlined above, the Lindblad master equation in Eq. \eqref{Lindblad-master-Eq} governs the time-evolution of the system density matrix, giving an ensemble average of the system dynamics. In order to ensure that these approximations are not violated, it is important that the decay rates ``\gamma_n`` be smaller than the minimum energy splitting in the system Hamiltonian. Situations that demand special attention therefore include, for example, systems strongly coupled to their environment, and systems with degenerate or nearly degenerate energy levels. +For systems with environments satisfying the conditions outlined above, the Lindblad master equation governs the time-evolution of the system density matrix, giving an ensemble average of the system dynamics. In order to ensure that these approximations are not violated, it is important that the decay rates ``\gamma_n`` be smaller than the minimum energy splitting in the system Hamiltonian. Situations that demand special attention therefore include, for example, systems strongly coupled to their environment, and systems with degenerate or nearly degenerate energy levels. What is new in the master equation compared to the Schrödinger equation (or von Neumann equation) are processes that describe dissipation in the quantum system due to its interaction with an environment. For example, evolution that includes incoherent processes such as relaxation and dephasing. These environmental interactions are defined by the operators ``\hat{A}_n`` through which the system couples to the environment, and rates ``\gamma_n`` that describe the strength of the processes. -In `QuantumToolbox`, the function [`mesolve`](@ref) can also be used for solving the master equation. This is done by passing a list of collapse operators (`c_ops`) as the fourth argument of the [`mesolve`](@ref) function in order to define the dissipation processes of the master equation in Eq. \eqref{Lindblad-master-Eq}. As we mentioned above, each collapse operator ``\hat{C}_n`` is the product of ``\sqrt{\gamma_n}`` (the square root of the rate) and ``\hat{A}_n`` (operator which describes the dissipation process). +In `QuantumToolbox`, the function [`mesolve`](@ref) can also be used for solving the master equation. This is done by passing a list of collapse operators (`c_ops`) as the fourth argument of the [`mesolve`](@ref) function in order to define the dissipation processes of the Lindblad master equation. As we mentioned above, each collapse operator ``\hat{C}_n`` is the product of ``\sqrt{\gamma_n}`` (the square root of the rate) and ``\hat{A}_n`` (operator which describes the dissipation process). Furthermore, `QuantumToolbox` solves the master equation in the [`SuperOperator`](@ref) formalism. That is, a Liouvillian [`SuperOperator`](@ref) will be generated internally in [`mesolve`](@ref) by the input system Hamiltonian ``\hat{H}_{\textrm{sys}}`` and the collapse operators ``\hat{C}_n``. One can also generate the Liouvillian [`SuperOperator`](@ref) manually for special purposes, and pass it as the first argument of the [`mesolve`](@ref) function. To do so, it is useful to read the section [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators), and also the docstrings of the following functions: - [`spre`](@ref) @@ -108,7 +105,7 @@ Furthermore, `QuantumToolbox` solves the master equation in the [`SuperOperator` - [`liouvillian`](@ref) - [`lindblad_dissipator`](@ref) -## Example: Spin dynamics +## [Example: Dissipative Spin dynamics](@id doc-TE:Example:Dissipative-Spin-dynamics) Using the example with the dynamics of spin-``\frac{1}{2}`` from the previous section ([Schrödinger Equation Solver](@ref doc-TE:Schrödinger-Equation-Solver)), we can easily add a relaxation process (describing the dissipation of energy from the spin to the environment), by adding `[sqrt(γ) * sigmax()]` in the fourth parameter of the [`mesolve`](@ref) function. @@ -143,7 +140,7 @@ axislegend(ax, position = :rt) fig ``` -## Example: Harmonic oscillator in thermal bath +## [Example: Harmonic oscillator in thermal bath](@id doc-TE:Example:Harmonic-oscillator-in-thermal-bath) Consider a harmonic oscillator (single-mode cavity) couples to a thermal bath. If the single-mode cavity initially is in a `10`-photon [`fock`](@ref) state, the dynamics is calculated with the following code: @@ -181,7 +178,7 @@ lines!(ax, tlist, Num) fig ``` -## Example: Two-level atom coupled to dissipative single-mode cavity +## [Example: Two-level atom coupled to dissipative single-mode cavity](@id doc-TE:Example:Two-level-atom-coupled-to-dissipative-single-mode-cavity) Consider a two-level atom coupled to a dissipative single-mode cavity through a dipole-type interaction, which supports a coherent exchange of quanta between the two systems. If the atom initially is in its ground state and the cavity in a `5`-photon [`fock`](@ref) state, the dynamics is calculated with the following code: diff --git a/docs/src/users_guide/time_evolution/sesolve.md b/docs/src/users_guide/time_evolution/sesolve.md index fce63f7ac..f41df647d 100644 --- a/docs/src/users_guide/time_evolution/sesolve.md +++ b/docs/src/users_guide/time_evolution/sesolve.md @@ -1,6 +1,6 @@ # [Schrödinger Equation Solver](@id doc-TE:Schrödinger-Equation-Solver) -## Unitary evolution +## [Unitary evolution](@id doc-TE:Unitary-evolution) The dynamics of a closed (pure) quantum system is governed by the Schrödinger equation @@ -19,7 +19,7 @@ where ``|\psi(t)\rangle`` is the state vector, and the Hamiltonian ``\hat{H}`` i The Schrödinger equation, which governs the time-evolution of closed quantum systems, is defined by its Hamiltonian and state vector. In the previous sections, [Manipulating States and Operators](@ref doc:Manipulating-States-and-Operators) and [Tensor Products and Partial Traces](@ref doc:Tensor-products-and-Partial-Traces), we showed how Hamiltonians and state vectors are constructed in `QuantumToolbox.jl`. Given a Hamiltonian, we can calculate the unitary (non-dissipative) time-evolution of an arbitrary initial state vector ``|\psi(0)\rangle`` using the `QuantumToolbox` time evolution problem [`sesolveProblem`](@ref) or directly call the function [`sesolve`](@ref). It evolves the state vector ``|\psi(t)\rangle`` and evaluates the expectation values for a set of operators `e_ops` at each given time points, using an ordinary differential equation solver provided by the powerful julia package [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/). -## Example: Spin dynamics +## [Example: Spin dynamics](@id doc-TE:Example:Spin-dynamics) ```@setup sesolve using QuantumToolbox diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index 4ed7d1c37..455c94991 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -4,7 +4,7 @@ using QuantumToolbox ``` -## Solution +## [Solution](@id doc-TE:Solution) `QuantumToolbox` utilizes the powerful [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/) to simulate different kinds of quantum system dynamics. Thus, we will first look at the data structure used for returning the solution (`sol`) from [`DifferentialEquation.jl`](https://docs.sciml.ai/DiffEqDocs/stable/). The solution stores all the crucial data needed for analyzing and plotting the results of a simulation. A generic structure [`TimeEvolutionSol`](@ref) contains the following properties for storing simulation data: | **Fields (Attributes)** | **Description** | @@ -17,7 +17,7 @@ using QuantumToolbox | `sol.reltol` | The relative tolerance which is used during the solving process. | | `sol.retcode` (or `sol.converged`) | The returned status from the solver. | -## Accessing data in solutions +## [Accessing data in solutions](@id doc-TE:Accessing-data-in-solutions) To understand how to access the data in solution, we will use an example as a guide, although we do not worry about the simulation details at this stage. The Schrödinger equation solver ([`sesolve`](@ref)) used in this example returns [`TimeEvolutionSol`](@ref): @@ -86,6 +86,6 @@ Here, the solution contains only one (final) state. Because the `states` will be Some other solvers can have other output. -## Multiple trajectories solution +## [Multiple trajectories solution](@id doc-TE:Multiple-trajectories-solution) This part is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file From d11841dff0b3cc551e77563a27597910316e4817 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:25:57 +0100 Subject: [PATCH 104/329] Bump to v0.21.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index fb54ce8ab..95b3eff58 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.21.1" +version = "0.21.2" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 2559a7ac0f3d898bee65396324358f93dadcc6ba Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:44:30 +0100 Subject: [PATCH 105/329] Fix version picker in navbar --- docs/src/.vitepress/config.mts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 42118b3df..fa49c508f 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -3,6 +3,10 @@ import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import mathjax3 from "markdown-it-mathjax3"; import footnote from "markdown-it-footnote"; +const baseTemp = { + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! +} + const navTemp = { nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS', } @@ -17,13 +21,17 @@ const nav = [ // https://vitepress.dev/reference/site-config export default defineConfig({ - base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! + base: baseTemp.base, title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', description: 'REPLACE_ME_DOCUMENTER_VITEPRESS', lastUpdated: true, cleanUrls: true, outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... - head: [['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }]], + head: [ + ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], + ['script', {src: `/versions.js`}], + ['script', {src: `${baseTemp.base}siteinfo.js`}] + ], ignoreDeadLinks: true, markdown: { From 064df6b01ef3530e1ee06f4df912c520049b5c60 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 20:53:56 +0900 Subject: [PATCH 106/329] Fix `VersionPicker.vue` (#297) --- docs/src/.vitepress/theme/VersionPicker.vue | 148 +++++++++++++++----- 1 file changed, 113 insertions(+), 35 deletions(-) diff --git a/docs/src/.vitepress/theme/VersionPicker.vue b/docs/src/.vitepress/theme/VersionPicker.vue index ba744d8f4..b65054027 100644 --- a/docs/src/.vitepress/theme/VersionPicker.vue +++ b/docs/src/.vitepress/theme/VersionPicker.vue @@ -1,64 +1,142 @@ \ No newline at end of file + From 3cde23b559e508a9f127a95f21025eb867c57aba Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:20:11 +0900 Subject: [PATCH 107/329] add `deploy_url` --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index a60d1c39b..f49f35b57 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -69,6 +69,7 @@ makedocs(; pages = PAGES, format = DocumenterVitepress.MarkdownVitepress( repo = "github.com/qutip/QuantumToolbox.jl", + deploy_url = "https://qutip.org/QuantumToolbox.jl", ), draft = DRAFT, plugins = [bib], From d2b19339d6bbb567d5c125de0b227ee25df4680a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:12:56 +0900 Subject: [PATCH 108/329] Fix documentation (#298) * Fix documentation * fix typo --- docs/make.jl | 1 - docs/src/.vitepress/theme/VersionPicker.vue | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index f49f35b57..a60d1c39b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -69,7 +69,6 @@ makedocs(; pages = PAGES, format = DocumenterVitepress.MarkdownVitepress( repo = "github.com/qutip/QuantumToolbox.jl", - deploy_url = "https://qutip.org/QuantumToolbox.jl", ), draft = DRAFT, plugins = [bib], diff --git a/docs/src/.vitepress/theme/VersionPicker.vue b/docs/src/.vitepress/theme/VersionPicker.vue index b65054027..ae2b25121 100644 --- a/docs/src/.vitepress/theme/VersionPicker.vue +++ b/docs/src/.vitepress/theme/VersionPicker.vue @@ -31,7 +31,7 @@ const getBaseRepository = () => { if (typeof window === 'undefined') return ''; // Handle server-side rendering (SSR) const { origin, pathname } = window.location; // Check if it's a GitHub Pages (or similar) setup - if (origin.includes('github.io')) { + if ((origin.includes('qutip.org')) || (origin.includes('github.io'))) { // Extract the first part of the path as the repository name const pathParts = pathname.split('/').filter(Boolean); const baseRepo = pathParts.length > 0 ? `/${pathParts[0]}/` : '/'; From eaddad0f2a504dbf0eb33b3bcd9e182a1f4bcd1a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:39:42 +0900 Subject: [PATCH 109/329] Bump version to `0.21.3` --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 95b3eff58..1722f70c2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.21.2" +version = "0.21.3" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From afc70e4a5b8f48b0f0e2f4c231119d5a6be528dc Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:58:57 +0900 Subject: [PATCH 110/329] fix the location of `versions.js` --- docs/src/.vitepress/config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index fa49c508f..07c86952b 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -29,7 +29,7 @@ export default defineConfig({ outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... head: [ ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], - ['script', {src: `/versions.js`}], + ['script', {src: `/QuantumToolbox.jl/versions.js`}], ['script', {src: `${baseTemp.base}siteinfo.js`}] ], ignoreDeadLinks: true, From 0229de307095f7b632d92d6bd26b4631e9bc194c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:12:12 +0900 Subject: [PATCH 111/329] Create CI pipeline to clean preview documentation --- .github/workflows/CleanPreviewDoc.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/CleanPreviewDoc.yml diff --git a/.github/workflows/CleanPreviewDoc.yml b/.github/workflows/CleanPreviewDoc.yml new file mode 100644 index 000000000..b942904d1 --- /dev/null +++ b/.github/workflows/CleanPreviewDoc.yml @@ -0,0 +1,26 @@ +name: Cleanup Preview Documentation + +on: + pull_request: + types: [closed] + +jobs: + doc-preview-cleanup: + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages + - name: Delete preview and history + push changes + run: | + if [ -d "previews/PR$PRNUM" ]; then + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "previews/PR$PRNUM" + git commit -m "delete preview" + git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) + git push --force origin gh-pages-new:gh-pages + fi + env: + PRNUM: ${{ github.event.number }} From 0889007b680907444035cc475e9be6e0dce4002f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:22:35 +0900 Subject: [PATCH 112/329] minor changes --- .github/workflows/CleanPreviewDoc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CleanPreviewDoc.yml b/.github/workflows/CleanPreviewDoc.yml index b942904d1..d3e4de25e 100644 --- a/.github/workflows/CleanPreviewDoc.yml +++ b/.github/workflows/CleanPreviewDoc.yml @@ -5,7 +5,7 @@ on: types: [closed] jobs: - doc-preview-cleanup: + cleanup-preview-doc: runs-on: ubuntu-latest steps: - name: Checkout gh-pages branch From 2d248261ac4d9a2091ace93e2b93d0c975586b58 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 12 Nov 2024 03:15:08 +0900 Subject: [PATCH 113/329] Fix bad link for API button in home page (#301) * fix bad link for API button in home page * fix spelling typo --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index 47585fe81..e864a0b95 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -21,7 +21,7 @@ hero: link: https://github.com/qutip/QuantumToolbox.jl - theme: alt text: API - link: /api + link: /resources/api - theme: alt text: Visit QuTiP.org link: https://qutip.org/ From 2b05fa4017a93c0feae958525220b4d21846d363 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:10:09 +0900 Subject: [PATCH 114/329] update documentation (#302) --- README.md | 14 +++++++------- docs/make.jl | 8 ++++---- docs/src/.vitepress/config.mts | 2 +- .../brief_example.md} | 18 ++++++++++++++---- .../{ => getting_started}/qutip_differences.md | 0 .../{ => getting_started}/type_stability.md | 0 docs/src/index.md | 8 ++++---- docs/src/resources/api.md | 2 +- docs/src/users_guide/extensions/cuda.md | 2 +- 9 files changed, 32 insertions(+), 22 deletions(-) rename docs/src/{getting_started.md => getting_started/brief_example.md} (75%) rename docs/src/{ => getting_started}/qutip_differences.md (100%) rename docs/src/{ => getting_started}/type_stability.md (100%) diff --git a/README.md b/README.md index 8f01caf31..b75ed3fe1 100644 --- a/README.md +++ b/README.md @@ -55,19 +55,19 @@ and [Y.-T. Huang](https://github.com/ytdHuang). ## Introduction -[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge Julia package designed for quantum physics simulations, closely emulating the popular Python [QuTiP](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. +[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge [`Julia`](https://julialang.org/) package designed for quantum physics simulations, closely emulating the popular Python [`QuTiP`](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of [`Julia`](https://julialang.org/) with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. *With this package, moving from Python to Julia for quantum physics simulations has never been easier*, due to the similar syntax and functionalities. ## Features -QuantumToolbox.jl is equipped with a robust set of features: +`QuantumToolbox.jl` is equipped with a robust set of features: -- **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as QuTiP. -- **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) package. +- **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as `QuTiP`. +- **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [`DifferentialEquations.jl`](https://github.com/SciML/DifferentialEquations.jl) package. - **GPU Computing:** Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. - **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. -- **Easy Extension:** Easily extend the package, taking advantage of the Julia language features, like multiple dispatch and metaprogramming. +- **Easy Extension:** Easily extend the package, taking advantage of the `Julia` language features, like multiple dispatch and metaprogramming. ## Installation @@ -79,7 +79,7 @@ To install `QuantumToolbox.jl`, run the following commands inside Julia's intera using Pkg Pkg.add("QuantumToolbox") ``` -Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: +Alternatively, this can also be done in `Julia`'s [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: ```julia-repl (1.10) pkg> add QuantumToolbox ``` @@ -94,7 +94,7 @@ QuantumToolbox.about() ## Brief Example -We now provide a brief example to demonstrate the similarity between [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) and [QuTiP](https://github.com/qutip/qutip). +We now provide a brief example to demonstrate the similarity between [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) and [`QuTiP`](https://github.com/qutip/qutip). Let's consider a quantum harmonic oscillator with a Hamiltonian given by: diff --git a/docs/make.jl b/docs/make.jl index a60d1c39b..1066c84d7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,10 +18,10 @@ bib = CitationBibliography( const PAGES = [ "Home" => "index.md", "Getting Started" => [ - "Brief Example" => "getting_started.md", - "Key differences from QuTiP" => "qutip_differences.md", - "The Importance of Type-Stability" => "type_stability.md", - # "Cite QuantumToolbox.jl" => "cite.md", + "Brief Example" => "getting_started/brief_example.md", + "Key differences from QuTiP" => "getting_started/qutip_differences.md", + "The Importance of Type-Stability" => "getting_started/type_stability.md", + # "Cite QuantumToolbox.jl" => "getting_started/cite.md", ], "Users Guide" => [ "Basic Operations on Quantum Objects" => [ diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 07c86952b..8ba7258d6 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -39,7 +39,7 @@ export default defineConfig({ // options for @mdit-vue/plugin-toc // https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-toc#options - toc: { level: [1, 2] }, + toc: { level: [2, 3, 4] }, // for API page, triggered by: [[toc]] config(md) { md.use(tabsMarkdownPlugin), diff --git a/docs/src/getting_started.md b/docs/src/getting_started/brief_example.md similarity index 75% rename from docs/src/getting_started.md rename to docs/src/getting_started/brief_example.md index 377f33ec9..b2a280753 100644 --- a/docs/src/getting_started.md +++ b/docs/src/getting_started/brief_example.md @@ -2,9 +2,11 @@ CurrentModule = QuantumToolbox ``` -## Brief Example +# Brief Example -We now provide a brief example to demonstrate the similarity between [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) and [QuTiP](https://github.com/qutip/qutip). +We now provide a brief example to demonstrate the similarity between [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) and [`QuTiP`](https://github.com/qutip/qutip). + +## CPU Computation Let's consider a quantum harmonic oscillator with a Hamiltonian given by: @@ -37,6 +39,9 @@ where ``\hat{\rho}`` is the density matrix, ``\gamma`` is the damping rate, and \mathcal{D}[\hat{a}]\hat{\rho} = \hat{a}\hat{\rho}\hat{a}^\dagger - \frac{1}{2}\hat{a}^\dagger\hat{a}\hat{\rho} - \frac{1}{2}\hat{\rho}\hat{a}^\dagger\hat{a} ``` +!!! note "Lindblad master equation" + See [here](@ref doc-TE:Lindblad-Master-Equation-Solver) for more details about Lindblad master equation. + We now compute the time evolution of the system using the [`mesolve`](@ref) function, starting from the initial state ``\ket{\psi (0)} = \ket{3}``: ```julia @@ -54,15 +59,20 @@ sol = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops) We can extract the expectation value of the number operator ``\hat{a}^\dagger \hat{a}`` with the command `sol.expect`, and the states with the command `sol.states`. -### Support for GPU calculation +## GPU Computation -We can easily pass the computation to the GPU, by simply passing all the `Qobj`s to the GPU: +!!! note "Extension for CUDA.jl" + `QuantumToolbox.jl` provides an extension to support GPU computation. To trigger the extension, you need to install and import [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl) together with `QuantumToolbox.jl`. See [here](@ref doc:CUDA) for more details. ```julia using QuantumToolbox using CUDA CUDA.allowscalar(false) # Avoid unexpected scalar indexing +``` +We can easily pass the computation to the GPU, by simply passing all the [`QuantumObject`](@ref)s to the GPU: + +```julia a_gpu = cu(destroy(N)) # The only difference in the code is the cu() function H_gpu = ω * a_gpu' * a_gpu diff --git a/docs/src/qutip_differences.md b/docs/src/getting_started/qutip_differences.md similarity index 100% rename from docs/src/qutip_differences.md rename to docs/src/getting_started/qutip_differences.md diff --git a/docs/src/type_stability.md b/docs/src/getting_started/type_stability.md similarity index 100% rename from docs/src/type_stability.md rename to docs/src/getting_started/type_stability.md diff --git a/docs/src/index.md b/docs/src/index.md index e864a0b95..f30f0a96d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -12,7 +12,7 @@ hero: actions: - theme: brand text: Getting Started - link: /getting_started + link: /getting_started/brief_example - theme: alt text: Users Guide link: /users_guide/QuantumObject/QuantumObject @@ -35,7 +35,7 @@ features: - icon: title: GPU Computing details: Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. - link: /getting_started + link: /users_guide/extensions/cuda - icon: title: Distributed Computing details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. @@ -45,7 +45,7 @@ features: # [Introduction](@id doc:Introduction) -[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge Julia package designed for quantum physics simulations, closely emulating the popular Python [QuTiP](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. Taking advantage of the Julia language features (like multiple dispatch and metaprogramming), QuantumToolbox.jl is designed to be easily extendable, allowing users to build upon the existing functionalities. +[`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge [`Julia`](https://julialang.org/) package designed for quantum physics simulations, closely emulating the popular [`Python QuTiP`](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. Taking advantage of the [`Julia`](https://julialang.org/) language features (like multiple dispatch and metaprogramming), [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) is designed to be easily extendable, allowing users to build upon the existing functionalities. *__With this package, moving from Python to Julia for quantum physics simulations has never been easier__*, due to the similar syntax and functionalities. @@ -59,7 +59,7 @@ To install `QuantumToolbox.jl`, run the following commands inside Julia's intera using Pkg Pkg.add("QuantumToolbox") ``` -Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: +Alternatively, this can also be done in `Julia`'s [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: ```julia-repl (1.10) pkg> add QuantumToolbox ``` diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 59f4d22d6..c12753b06 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -6,7 +6,7 @@ CurrentModule = QuantumToolbox **Table of contents** -[[toc]] +[[toc]] ## [Quantum object (Qobj) and type](@id doc-API:Quantum-object-and-type) diff --git a/docs/src/users_guide/extensions/cuda.md b/docs/src/users_guide/extensions/cuda.md index 11290c6f7..228a8acf1 100644 --- a/docs/src/users_guide/extensions/cuda.md +++ b/docs/src/users_guide/extensions/cuda.md @@ -4,7 +4,7 @@ This is an extension to support `QuantumObject.data` conversion from standard dense and sparse CPU arrays to GPU ([`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl)) arrays. -This extension will be automatically loaded if user imports both `QuantumToolbox` and [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl): +This extension will be automatically loaded if user imports both `QuantumToolbox.jl` and [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl): ```julia using QuantumToolbox From 42c4e0789a038198455b67db12276c9f99164220 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:59:47 +0900 Subject: [PATCH 115/329] add contribute doc page (#303) --- .github/pull_request_template.md | 6 +-- README.md | 6 +++ docs/make.jl | 1 + docs/src/resources/contributing.md | 72 ++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 docs/src/resources/contributing.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dbb5b2596..7a2a6fd83 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,11 @@ ## Checklist Thank you for contributing to `QuantumToolbox.jl`! Please make sure you have finished the following tasks before opening the PR. -- [ ] Please read [Contributor Covenant Code of Conduct](https://github.com/qutip/QuantumToolbox.jl/blob/main/CODE_OF_CONDUCT.md) -- [ ] Any code changes were done in a way that does not break public API +- [ ] Please read [Contributing to QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/stable/resources/contribute). +- [ ] Any code changes were done in a way that does not break public API. - [ ] Appropriate tests were added and tested locally by running: `make test`. - [ ] Any code changes should be `julia` formatted by running: `make format`. -- [ ] All documents (in `docs/` folder) related to code changes were updated and able to build locally by running `make docs`. +- [ ] All documents (in `docs/` folder) related to code changes were updated and able to build locally by running: `make docs`. Request for a review after you have completed all the tasks. If you have not finished them all, you can also open a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) to let the others know this on-going work. diff --git a/README.md b/README.md index b75ed3fe1..c82c158ac 100644 --- a/README.md +++ b/README.md @@ -164,3 +164,9 @@ e_ops = [a_gpu' * a_gpu] sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) ``` + +## Contributing to QuantumToolbox.jl + +You are most welcome to contribute to `QuantumToolbox.jl` development by forking this repository and sending pull requests (PRs), or filing bug reports at the issues page. You can also help out with users' questions, or discuss proposed changes in the [QuTiP discussion group](https://groups.google.com/g/qutip). + +For more information about contribution, including technical advice, please see the [Contributing to QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing) section of the documentation. diff --git a/docs/make.jl b/docs/make.jl index 1066c84d7..c5a097b51 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -58,6 +58,7 @@ const PAGES = [ "API" => "resources/api.md", # "Change Log" => "resources/changelog.md", "Bibliography" => "resources/bibliography.md", + "Contributing to QuantumToolbox.jl" => "resources/contributing.md", ], ] diff --git a/docs/src/resources/contributing.md b/docs/src/resources/contributing.md new file mode 100644 index 000000000..c9070964e --- /dev/null +++ b/docs/src/resources/contributing.md @@ -0,0 +1,72 @@ +# [Contributing to QuantumToolbox.jl](@id doc-Contribute) + +## [Quick Start](@id doc-Contribute:Quick-Start) + +`QuantumToolbox.jl` is developed using the [`git`](https://git-scm.com/) version-control system, with the [main repository](https://github.com/qutip/QuantumToolbox.jl) hosted in the [qutip organisation on GitHub](https://github.com/qutip). You will need to be familiar with [`git`](https://git-scm.com/) as a tool, and the [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow) workflow for branching and making pull requests. The exact details of environment set-up, build process, and runtests vary by repository are discussed below. In overview, the steps to contribute are: + +1. Consider creating an issue on the GitHub page of the relevant repository, describing the change you think should be made and why, so we can discuss details with you and make sure it is appropriate. +2. *__If this is your first contribution__*, make a fork of the relevant repository on GitHub and clone it to your local computer. Also add our copy as a remote called `qutip`: `git remote add qutip https://github.com/qutip/`. +3. Start from the `main` branch in your local computer (`git checkout main`), and pull all the changes from the remote repository (on GitHub) to make sure you have an up-to-date copy: `git pull qutip main`. +4. Switch to a new `git` branch: `git checkout -b `. +5. Make the changes you want +6. Go through the following build processes (if the changes you made relates to any of them) for the repository to build the final result so you can check your changes work sensibly: + - Write and make sure all the runtests pass. See [here](@ref doc-Contribute:Runtests) for more details. + - Make sure all the changes match the `Julia` code format (style). See [here](@ref doc-Contribute:Julia-Code-Format) for more details. + - Improve and make sure the documentation can be built successfully. See [here](@ref doc-Contribute:Documentation) for more details. + - Update changelog. See [here](@ref doc-Contribute:Update-ChangeLog) for more details. +7. Add the changed files to the `git` staging area `git add ...`, and then create some commits with short-but-descriptive names `git commit`. +8. Push the changes to your fork (`origin`) `git push -u origin `. You won’t be able to push to the remote `qutip` repositories directly. +9. Go to the GitHub website for the repository you are contributing to, click on the “Pull Requests” tab, click the “New Pull Request” button, and follow the instructions there. + +Once the pull request (PR) is created, some members of the QuTiP admin team will review the code to make sure it is suitable for inclusion in the library, to check the programming, and to ensure everything meets our standards. For some repositories, several automated CI pipelines will run whenever you create or modify a PR. In general, these will basically be the same ones which you can run locally, and all CI pipelines are required to pass online before your changes are merged to the remote `main` branch. There might be some feedbacks and requested changes. You can add more commits to address these, and push them to the branch (``) of your fork (`origin`) to update the PR. + +The rest of this document covers programming standards. + +## [Runtests](@id doc-Contribute:Runtests) + +All the test scripts should be located in the folder `test` in the repository. To run the test, use the following command under the *__root directory of the repository__* you are working on: + +```shell +make test +``` + +This command will automatically rebuild `Julia` and run the script located in `test/runtests.jl` (should cover both the original tests and the new test(s) you add). + +## [Julia Code Format](@id doc-Contribute:Julia-Code-Format) + +We use [`JuliaFormatter.jl`](https://github.com/domluna/JuliaFormatter.jl) to format all the source codes. The code style and extra formatting options is defined in the file `.JuliaFormatter.toml` in the repository. + +To format the changed codes, use the following command under the *__root directory of the repository__* you are working on: + +```shell +make format +``` + +## [Documentation](@id doc-Contribute:Documentation) + +All the documentation source files [in markdown (`.md`) format] and build scripts should be located in the folder `docs` in the repository. + +The document pages will be generated in the folder `docs/build` (which is ignored by `git`) in the repository. + +To instantiate and build the documentation, run the following command under the *__root directory of the repository__* you are working on: + +```shell +make docs +``` + +This command will automatically rebuild `Julia` and run the script located in `docs/make.jl` (should be able to build the necessary files for the documentation). + +To read the documentation in a browser, you can run the following command: + +!!! note "Requirements" + You need to install `Node.js` and `npm` first. + +```shell +make vitepress +``` + +This will start a local Vitepress site of documentation at `http://localhost:5173/QuantumToolbox.jl/` + +## [Update ChangeLog](@id doc-Contribute:Update-ChangeLog) + +(TBA) From e4cf3948381855dad527840992594bd3ec652e45 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:18:06 +0900 Subject: [PATCH 116/329] fix contributing page (#305) --- docs/src/resources/contributing.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/resources/contributing.md b/docs/src/resources/contributing.md index c9070964e..31b80092a 100644 --- a/docs/src/resources/contributing.md +++ b/docs/src/resources/contributing.md @@ -4,19 +4,19 @@ `QuantumToolbox.jl` is developed using the [`git`](https://git-scm.com/) version-control system, with the [main repository](https://github.com/qutip/QuantumToolbox.jl) hosted in the [qutip organisation on GitHub](https://github.com/qutip). You will need to be familiar with [`git`](https://git-scm.com/) as a tool, and the [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow) workflow for branching and making pull requests. The exact details of environment set-up, build process, and runtests vary by repository are discussed below. In overview, the steps to contribute are: -1. Consider creating an issue on the GitHub page of the relevant repository, describing the change you think should be made and why, so we can discuss details with you and make sure it is appropriate. -2. *__If this is your first contribution__*, make a fork of the relevant repository on GitHub and clone it to your local computer. Also add our copy as a remote called `qutip`: `git remote add qutip https://github.com/qutip/`. -3. Start from the `main` branch in your local computer (`git checkout main`), and pull all the changes from the remote repository (on GitHub) to make sure you have an up-to-date copy: `git pull qutip main`. -4. Switch to a new `git` branch: `git checkout -b `. -5. Make the changes you want -6. Go through the following build processes (if the changes you made relates to any of them) for the repository to build the final result so you can check your changes work sensibly: +- Consider creating an issue on the GitHub page of the relevant repository, describing the change you think should be made and why, so we can discuss details with you and make sure it is appropriate. +- *__If this is your first contribution__*, make a fork of the relevant repository on GitHub (which will be called as `origin`) and clone it to your local computer. Also add our copy as a remote (let's call it `qutip` here): `git remote add qutip https://github.com/qutip/`. +- Start from the `main` branch in your local computer (`git checkout main`), and pull all the changes from the remote (`qutip`) repository (on GitHub) to make sure you have an up-to-date copy: `git pull qutip main`. +- Switch to a new `git` branch in your local computer: `git checkout -b `. +- Make the changes you want. +- Go through the following build processes (if the changes you made relates to any of them) locally in your computer to build the final result so you can check your changes work sensibly: - Write and make sure all the runtests pass. See [here](@ref doc-Contribute:Runtests) for more details. - Make sure all the changes match the `Julia` code format (style). See [here](@ref doc-Contribute:Julia-Code-Format) for more details. - Improve and make sure the documentation can be built successfully. See [here](@ref doc-Contribute:Documentation) for more details. - Update changelog. See [here](@ref doc-Contribute:Update-ChangeLog) for more details. -7. Add the changed files to the `git` staging area `git add ...`, and then create some commits with short-but-descriptive names `git commit`. -8. Push the changes to your fork (`origin`) `git push -u origin `. You won’t be able to push to the remote `qutip` repositories directly. -9. Go to the GitHub website for the repository you are contributing to, click on the “Pull Requests” tab, click the “New Pull Request” button, and follow the instructions there. +- Add the changed files to the `git` staging area `git add ...`, and then create some commits with short-but-descriptive names: `git commit`. +- Push the changes to your fork (`origin`): `git push -u origin `. You won’t be able to push to the remote (`qutip`) repositories directly. +- Go to the GitHub website for the repository you are contributing to, click on the “Pull Requests” tab, click the “New Pull Request” button, and follow the instructions there. Once the pull request (PR) is created, some members of the QuTiP admin team will review the code to make sure it is suitable for inclusion in the library, to check the programming, and to ensure everything meets our standards. For some repositories, several automated CI pipelines will run whenever you create or modify a PR. In general, these will basically be the same ones which you can run locally, and all CI pipelines are required to pass online before your changes are merged to the remote `main` branch. There might be some feedbacks and requested changes. You can add more commits to address these, and push them to the branch (``) of your fork (`origin`) to update the PR. @@ -65,7 +65,7 @@ To read the documentation in a browser, you can run the following command: make vitepress ``` -This will start a local Vitepress site of documentation at `http://localhost:5173/QuantumToolbox.jl/` +This will start a local Vitepress site of documentation at `http://localhost:5173/QuantumToolbox.jl/` in your computer. ## [Update ChangeLog](@id doc-Contribute:Update-ChangeLog) From 8d1717a9fddb06d691f84028900dc2d3a109ca53 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:50:52 +0900 Subject: [PATCH 117/329] Add `CHANGELOG.md` (#306) * add CHANGELOG.md * fix typos * remove subsubsections in `CHANGELOG.md` * update PR template * rename `change_log` as `changelog` * fix typo * fix typo * minor changes --- .github/pull_request_template.md | 3 ++- .github/workflows/ChangeLogCheck.yml | 14 ++++++++++++++ CHANGELOG.md | 21 +++++++++++++++++++++ Makefile | 12 ++++++++++-- docs/.gitignore | 1 + docs/Project.toml | 1 + docs/make.jl | 12 +++++++++++- docs/src/resources/contributing.md | 24 +++++++++++++++++++++++- 8 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/ChangeLogCheck.yml create mode 100644 CHANGELOG.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7a2a6fd83..c1c968ca3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,12 @@ ## Checklist Thank you for contributing to `QuantumToolbox.jl`! Please make sure you have finished the following tasks before opening the PR. -- [ ] Please read [Contributing to QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/stable/resources/contribute). +- [ ] Please read [Contributing to QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing). - [ ] Any code changes were done in a way that does not break public API. - [ ] Appropriate tests were added and tested locally by running: `make test`. - [ ] Any code changes should be `julia` formatted by running: `make format`. - [ ] All documents (in `docs/` folder) related to code changes were updated and able to build locally by running: `make docs`. +- [ ] (If necessary) the `CHANGELOG.md` should be updated (regarding to the code changes) and built by running: `make changelog`. Request for a review after you have completed all the tasks. If you have not finished them all, you can also open a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) to let the others know this on-going work. diff --git a/.github/workflows/ChangeLogCheck.yml b/.github/workflows/ChangeLogCheck.yml new file mode 100644 index 000000000..634583251 --- /dev/null +++ b/.github/workflows/ChangeLogCheck.yml @@ -0,0 +1,14 @@ +# Enforces the update of the file CHANGELOG.md on every pull request +# Can be skipped with the `Skip ChangeLog` label +name: ChangeLog Update Check +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - uses: dangoslen/changelog-enforcer@v3 + with: + skipLabels: 'Skip ChangeLog' \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..df6e81ce8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# ChangeLog + +All notable changes to [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- *__We will start to write changelog once we have the first standard release.__* + +## [v0.21.4] (2024-11-13) + +- This is just a demonstration about [`Changelog.jl`](https://github.com/JuliaDocs/Changelog.jl). ([#139], [#306]) + + + + +[v0.21.4]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.4 +[#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 +[#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 diff --git a/Makefile b/Makefile index 25cf0265e..4fd24cfcb 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,15 @@ JULIA:=julia default: help +setup: + ${JULIA} -e 'import Pkg; Pkg.add(["JuliaFormatter", "Changelog"])' + format: ${JULIA} -e 'using JuliaFormatter; format(".")' +changelog: + ${JULIA} -e 'using Changelog; Changelog.generate(Changelog.CommonMark(), "CHANGELOG.md"; repo = "qutip/QuantumToolbox.jl")' + test: ${JULIA} --project -e 'using Pkg; Pkg.resolve(); Pkg.test()' @@ -16,14 +22,16 @@ vitepress: npm --prefix docs i npm --prefix docs run docs:dev -all: format test docs vitepress +all: setup format changelog test docs vitepress help: @echo "The following make commands are available:" + @echo " - make setup: install the dependencies for make command" @echo " - make format: format codes with JuliaFormatter" + @echo " - make changelog: generate changelog" @echo " - make test: run the tests" @echo " - make docs: instantiate and build the documentation" @echo " - make vitepress: start Vitepress site of documentation" @echo " - make all: run every commands in the above order" -.PHONY: default format test docs vitepress all help +.PHONY: default setup format changelog test docs vitepress all help diff --git a/docs/.gitignore b/docs/.gitignore index 778ffdc18..b3c4f480f 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -2,3 +2,4 @@ build/ node_modules/ package-lock.json Manifest.toml +src/resources/changelog.md \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index ef98b807e..078f2ad18 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,7 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Changelog = "5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" diff --git a/docs/make.jl b/docs/make.jl index c5a097b51..70d95af79 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -5,16 +5,26 @@ using QuantumToolbox using Documenter using DocumenterVitepress using DocumenterCitations +using Changelog DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) const DRAFT = false # set `true` to disable cell evaluation +# generate bibliography bib = CitationBibliography( joinpath(@__DIR__, "src", "resources", "bibliography.bib"), style=:authoryear, ) +# generate changelog +Changelog.generate( + Changelog.Documenter(), + joinpath(@__DIR__, "..", "CHANGELOG.md"), + joinpath(@__DIR__, "src", "resources", "changelog.md"); + repo = "qutip/QuantumToolbox.jl", +) + const PAGES = [ "Home" => "index.md", "Getting Started" => [ @@ -56,8 +66,8 @@ const PAGES = [ ], "Resources" => [ "API" => "resources/api.md", - # "Change Log" => "resources/changelog.md", "Bibliography" => "resources/bibliography.md", + "ChangeLog" => "resources/changelog.md", "Contributing to QuantumToolbox.jl" => "resources/contributing.md", ], ] diff --git a/docs/src/resources/contributing.md b/docs/src/resources/contributing.md index 31b80092a..517191c07 100644 --- a/docs/src/resources/contributing.md +++ b/docs/src/resources/contributing.md @@ -38,6 +38,9 @@ We use [`JuliaFormatter.jl`](https://github.com/domluna/JuliaFormatter.jl) to fo To format the changed codes, use the following command under the *__root directory of the repository__* you are working on: +!!! note "Requirements" + If this is your first time running `make` command in the local repository you are working on or you just had reinstalled `Julia`, you should run `make setup` first. + ```shell make format ``` @@ -69,4 +72,23 @@ This will start a local Vitepress site of documentation at `http://localhost:517 ## [Update ChangeLog](@id doc-Contribute:Update-ChangeLog) -(TBA) +The changelog is written in the file `CHANGELOG.md` in the repository. If you add some changes to the repository and made a PR, you should also add some messages or release notes together with the related PRs/issues entries to `CHANGELOG.md`. For example, add a new line in `CHANGELOG.md`: + +```markdown +- some messages to describe the changes. ([#issue-ID], [#PR-ID]) +``` + +See also the [ChangeLog page](@ref ChangeLog) for more examples. + +After that, you can run the following command under the *__root directory of the repository__* you are working on: + +!!! note "Requirements" + If this is your first time running `make` command in the local repository you are working on or you just had reinstalled `Julia`, you should run `make setup` first. + +```shell +make changelog +``` + +This will automatically generate the full URLs for the references to PRs/issues by utilizing [`Changelog.jl`](https://github.com/JuliaDocs/Changelog.jl). + +If the changes you made are not necessary to be recorded in `CHANGELOG.md`, you can add the label `[Skip ChangeLog]` to the PR you made in the GitHub repository. From 4deb2f1885dc0a73d6907502851ef960d5d57e82 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:51:22 +0900 Subject: [PATCH 118/329] fix incorrect bibliography (#307) --- docs/src/resources/bibliography.bib | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index 931ea3bfb..d83932eed 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -1,10 +1,12 @@ @book{Nielsen-Chuang2011, - Author = {Michael A. Nielsen and Isaac L. Chuang}, - Title = {Quantum Computation and Quantum Information: 10th Anniversary Edition}, - Publisher = {Cambridge University Press}, - Year = {2011}, - ISBN = {9781107002173}, - URL = {https://www.amazon.com/Quantum-Computation-Information-10th-Anniversary/dp/1107002176?SubscriptionId=AKIAIOBINVZYXZQZ2U3A&tag=chimbori05-20&linkCode=xm2&camp=2025&creative=165953&creativeASIN=1107002176} + title = {Quantum Computation and Quantum Information: 10th Anniversary Edition}, + ISBN = {9780511976667}, + url = {http://dx.doi.org/10.1017/CBO9780511976667}, + DOI = {10.1017/cbo9780511976667}, + publisher = {Cambridge University Press}, + author = {Nielsen, Michael A. and Chuang, Isaac L.}, + year = {2012}, + month = jun } @article{Jozsa1994, From 3fd5a6266615e26482dbc8b5bc5c20abdca10c2d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:51:39 +0100 Subject: [PATCH 119/329] add allocations tests and clean time evolution tests (#304) * add allocations tests and clean time evolution tests * Format Document * Remove global tlist * Fix format --- test/core-test/time_evolution.jl | 293 +++++++++++++++++++------------ 1 file changed, 181 insertions(+), 112 deletions(-) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 83adb9615..c760b4106 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -1,32 +1,48 @@ @testset "Time Evolution and Partial Trace" verbose = true begin + # Global definition of the system + N = 10 + a = kron(destroy(N), qeye(2)) + σm = kron(qeye(N), sigmam()) + σz = qeye(N) ⊗ sigmaz() + + g = 0.01 + ωc = 1 + ωq = 0.99 + γ = 0.1 + nth = 0.001 + + # Jaynes-Cummings Hamiltonian + H = ωc * a' * a + ωq / 2 * σz + g * (a' * σm + a * σm') + ψ0 = kron(fock(N, 0), fock(2, 0)) + + e_ops = [a' * a, σz] + c_ops = [sqrt(γ * (1 + nth)) * a, sqrt(γ * nth) * a', sqrt(γ * (1 + nth)) * σm, sqrt(γ * nth) * σm'] + @testset "sesolve" begin - N = 10 - a_d = kron(create(N), qeye(2)) - a = a_d' - sx = kron(qeye(N), sigmax()) - sy = tensor(qeye(N), sigmay()) - sz = qeye(N) ⊗ sigmaz() - η = 0.01 - H = a_d * a + 0.5 * sz - 1im * η * (a - a_d) * sx - psi0 = kron(fock(N, 0), fock(2, 0)) - t_l = LinRange(0, 1000, 1000) - e_ops = [a_d * a] - prob = sesolveProblem(H, psi0, t_l, e_ops = e_ops, progress_bar = Val(false)) + tlist = range(0, 20 * 2π / g, 1000) + + prob = sesolveProblem(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) sol = sesolve(prob) - sol2 = sesolve(H, psi0, t_l, progress_bar = Val(false)) - sol3 = sesolve(H, psi0, t_l, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) + sol2 = sesolve(H, ψ0, tlist, progress_bar = Val(false)) + sol3 = sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) sol_string = sprint((t, s) -> show(t, "text/plain", s), sol) + + ## Analytical solution for the expectation value of a' * a + Ω_rabi = sqrt(g^2 + ((ωc - ωq) / 2)^2) + amp_rabi = g^2 / Ω_rabi^2 + ## + @test prob.f.f isa MatrixOperator - @test sum(abs.(sol.expect[1, :] .- sin.(η * t_l) .^ 2)) / length(t_l) < 0.1 - @test length(sol.times) == length(t_l) + @test sum(abs.(sol.expect[1, :] .- amp_rabi .* sin.(Ω_rabi * tlist) .^ 2)) / length(tlist) < 0.1 + @test length(sol.times) == length(tlist) @test length(sol.states) == 1 - @test size(sol.expect) == (length(e_ops), length(t_l)) - @test length(sol2.times) == length(t_l) - @test length(sol2.states) == length(t_l) - @test size(sol2.expect) == (0, length(t_l)) - @test length(sol3.times) == length(t_l) - @test length(sol3.states) == length(t_l) - @test size(sol3.expect) == (length(e_ops), length(t_l)) + @test size(sol.expect) == (length(e_ops), length(tlist)) + @test length(sol2.times) == length(tlist) + @test length(sol2.states) == length(tlist) + @test size(sol2.expect) == (0, length(tlist)) + @test length(sol3.times) == length(tlist) + @test length(sol3.states) == length(tlist) + @test size(sol3.expect) == (length(e_ops), length(tlist)) @test sol_string == "Solution of time evolution\n" * "(return code: $(sol.retcode))\n" * @@ -37,54 +53,58 @@ "abstol = $(sol.abstol)\n" * "reltol = $(sol.reltol)\n" + @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)) + @test allocs_tot < 150 + + allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) + @test allocs_tot < 100 + end + @testset "Type Inference sesolve" begin - @inferred sesolveProblem(H, psi0, t_l, progress_bar = Val(false)) - @inferred sesolveProblem(H, psi0, [0, 10], progress_bar = Val(false)) - @inferred sesolveProblem(H, Qobj(zeros(Int64, N * 2); dims = (N, 2)), t_l, progress_bar = Val(false)) - @inferred sesolve(H, psi0, t_l, e_ops = e_ops, progress_bar = Val(false)) - @inferred sesolve(H, psi0, t_l, progress_bar = Val(false)) - @inferred sesolve(H, psi0, t_l, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) - @inferred sesolve(H, psi0, t_l, e_ops = (a_d * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types + @inferred sesolveProblem(H, ψ0, tlist, progress_bar = Val(false)) + @inferred sesolveProblem(H, ψ0, [0, 10], progress_bar = Val(false)) + @inferred sesolveProblem(H, Qobj(zeros(Int64, N * 2); dims = (N, 2)), tlist, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types end end @testset "mesolve, mcsolve, and ssesolve" begin - N = 10 - a = destroy(N) - a_d = a' - H = a_d * a - c_ops = [sqrt(0.1) * a] - e_ops = [a_d * a] - psi0 = basis(N, 3) - t_l = LinRange(0, 100, 1000) - prob_me = mesolveProblem(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) + tlist = range(0, 10 / γ, 100) + + prob_me = mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) sol_me = mesolve(prob_me) - sol_me2 = mesolve(H, psi0, t_l, c_ops, progress_bar = Val(false)) - sol_me3 = mesolve(H, psi0, t_l, c_ops, e_ops = e_ops, saveat = t_l, progress_bar = Val(false)) - prob_mc = mcsolveProblem(H, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_mc = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_me2 = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) + sol_me3 = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) + prob_mc = mcsolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_mc = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) sol_mc2 = mcsolve( H, - psi0, - t_l, + ψ0, + tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), ) - sol_mc_states = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, saveat = t_l, progress_bar = Val(false)) + sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, saveat = tlist, progress_bar = Val(false)) sol_mc_states2 = mcsolve( H, - psi0, - t_l, + ψ0, + tlist, c_ops, ntraj = 500, - saveat = t_l, + saveat = tlist, progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), ) - sol_sse = ssesolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_sse = ssesolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) @@ -99,26 +119,26 @@ sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) @test prob_me.f.f isa MatrixOperator @test prob_mc.f.f isa MatrixOperator - @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(t_l) < 0.1 - @test sum(abs.(sol_mc2.expect .- sol_me.expect)) / length(t_l) < 0.1 - @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect))) / length(t_l) < 0.1 - @test sum(abs.(vec(expect_mc_states_mean2) .- vec(sol_me.expect))) / length(t_l) < 0.1 - @test sum(abs.(sol_sse.expect .- sol_me.expect)) / length(t_l) < 0.1 - @test length(sol_me.times) == length(t_l) + @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(tlist) < 0.1 + @test sum(abs.(sol_mc2.expect .- sol_me.expect)) / length(tlist) < 0.1 + @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect[1, :]))) / length(tlist) < 0.1 + @test sum(abs.(vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, :]))) / length(tlist) < 0.1 + @test sum(abs.(sol_sse.expect .- sol_me.expect)) / length(tlist) < 0.1 + @test length(sol_me.times) == length(tlist) @test length(sol_me.states) == 1 - @test size(sol_me.expect) == (length(e_ops), length(t_l)) - @test length(sol_me2.times) == length(t_l) - @test length(sol_me2.states) == length(t_l) - @test size(sol_me2.expect) == (0, length(t_l)) - @test length(sol_me3.times) == length(t_l) - @test length(sol_me3.states) == length(t_l) - @test size(sol_me3.expect) == (length(e_ops), length(t_l)) - @test length(sol_mc.times) == length(t_l) - @test size(sol_mc.expect) == (length(e_ops), length(t_l)) - @test length(sol_mc_states.times) == length(t_l) - @test size(sol_mc_states.expect) == (0, length(t_l)) - @test length(sol_sse.times) == length(t_l) - @test size(sol_sse.expect) == (length(e_ops), length(t_l)) + @test size(sol_me.expect) == (length(e_ops), length(tlist)) + @test length(sol_me2.times) == length(tlist) + @test length(sol_me2.states) == length(tlist) + @test size(sol_me2.expect) == (0, length(tlist)) + @test length(sol_me3.times) == length(tlist) + @test length(sol_me3.states) == length(tlist) + @test size(sol_me3.expect) == (length(e_ops), length(tlist)) + @test length(sol_mc.times) == length(tlist) + @test size(sol_mc.expect) == (length(e_ops), length(tlist)) + @test length(sol_mc_states.times) == length(tlist) + @test size(sol_mc_states.expect) == (0, length(tlist)) + @test length(sol_sse.times) == length(tlist) + @test size(sol_sse.expect) == (length(e_ops), length(tlist)) @test sol_me_string == "Solution of time evolution\n" * "(return code: $(sol_me.retcode))\n" * @@ -152,38 +172,24 @@ # 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. - N = 10 - a = tensor(destroy(N), qeye(2)) - σm = tensor(qeye(N), sigmam()) - σz = tensor(qeye(N), sigmaz()) - ω = 1.0 ωd = 1.02 - Δ = ω - ωd F = 0.05 - g = 0.1 - γ = 0.1 - nth = 0.001 # Time Evolution in the drive frame - H = Δ * a' * a + Δ * σz / 2 + g * (a' * σm + a * σm') + F * (a + a') - c_ops = [sqrt(γ * (1 + nth)) * a, sqrt(γ * nth) * a', sqrt(γ * (1 + nth)) * σm, sqrt(γ * nth) * σm'] - e_ops = [a' * a, σz] - - ψ0 = tensor(basis(N, 0), basis(2, 1)) - tlist = range(0, 2 / γ, 1000) + H_dr_fr = H - ωd * a' * a - ωd * σz / 2 + F * (a + a') rng = MersenneTwister(12) - sol_se = sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) - sol_me = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_mc = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + tlist = range(0, 10 / γ, 1000) + + sol_se = sesolve(H_dr_fr, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + sol_me = mesolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_mc = mcsolve(H_dr_fr, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) # sol_sse = ssesolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) # Time Evolution in the lab frame - H = ω * a' * a + ω * σz / 2 + g * (a' * σm + a * σm') - coef1(p, t) = p.F * exp(1im * p.ωd * t) coef2(p, t) = p.F * exp(-1im * p.ωd * t) @@ -234,6 +240,87 @@ @test sol_mc.expect ≈ sol_mc_td2.expect atol = 1e-2 * length(tlist) # @test sol_sse.expect ≈ sol_sse_td2.expect atol = 1e-2 * length(tlist) + @testset "Memory Allocations (mesolve)" begin + # We predefine the Liouvillian to avoid to count the allocations of the liouvillian function + L = liouvillian(H, c_ops) + L_td = QobjEvo((liouvillian(H, c_ops), (liouvillian(a), coef1), (liouvillian(a'), coef2))) + + allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + @test allocs_tot < 210 + + allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) + @test allocs_tot < 120 + + allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) # Warm-up + allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) + @test allocs_tot < 210 + + allocs_tot = + @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) # Warm-up + allocs_tot = + @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) + @test allocs_tot < 120 + end + + @testset "Memory Allocations (mcsolve)" begin + ntraj = 100 + allocs_tot = + @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up + allocs_tot = + @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) + @test allocs_tot < 160 * ntraj + 500 # 150 allocations per trajectory + 500 for initialization + + allocs_tot = @allocations mcsolve( + H, + ψ0, + tlist, + c_ops, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations mcsolve( + H, + ψ0, + tlist, + c_ops, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + ) + @test allocs_tot < 160 * ntraj + 300 # 100 allocations per trajectory + 300 for initialization + end + + @testset "Memory Allocations (ssesolve)" begin + allocs_tot = + @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = 100, progress_bar = Val(false)) # Warm-up + allocs_tot = + @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = 100, progress_bar = Val(false)) + @test allocs_tot < 1950000 # TODO: Fix this high number of allocations + + allocs_tot = @allocations ssesolve( + H, + ψ0, + tlist, + c_ops, + ntraj = 100, + saveat = [tlist[end]], + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations ssesolve( + H, + ψ0, + tlist, + c_ops, + ntraj = 100, + saveat = [tlist[end]], + progress_bar = Val(false), + ) + @test allocs_tot < 570000 # TODO: Fix this high number of allocations + end + @testset "Type Inference mesolve" begin coef(p, t) = exp(-t) ad_t = QobjEvo(a', coef) @@ -359,34 +446,16 @@ end @testset "mcsolve and ssesolve reproducibility" begin - N = 10 - a = tensor(destroy(N), qeye(2)) - σm = tensor(qeye(N), sigmam()) - σp = σm' - σz = tensor(qeye(N), sigmaz()) - - ω = 1.0 - g = 0.1 - γ = 0.01 - nth = 0.1 - - H = ω * a' * a + ω * σz / 2 + g * (a' * σm + a * σp) - c_ops = [sqrt(γ * (1 + nth)) * a, sqrt(γ * nth) * a', sqrt(γ * (1 + nth)) * σm, sqrt(γ * nth) * σp] - e_ops = [a' * a, σz] - - psi0 = tensor(basis(N, 0), basis(2, 0)) - tlist = range(0, 20 / γ, 1000) - rng = MersenneTwister(1234) - sol_mc1 = mcsolve(H, psi0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) - sol_sse1 = ssesolve(H, psi0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sse1 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) - sol_mc2 = mcsolve(H, psi0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) - sol_sse2 = ssesolve(H, psi0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sse2 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) - sol_mc3 = mcsolve(H, psi0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc3 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 @test sol_mc1.expect_all ≈ sol_mc2.expect_all atol = 1e-10 From 43535b01d625ce171c83c0c1eccf2720a97a384e Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:52:58 +0100 Subject: [PATCH 120/329] Bump version to v0.21.4 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1722f70c2..a930b4d31 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.21.3" +version = "0.21.4" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 796d8f176716fa7f894c2ab85aefb71c6848e5c3 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:31:50 +0900 Subject: [PATCH 121/329] remove extra `/` in URL (#308) --- docs/src/.vitepress/theme/VersionPicker.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/.vitepress/theme/VersionPicker.vue b/docs/src/.vitepress/theme/VersionPicker.vue index ae2b25121..a38c51a5a 100644 --- a/docs/src/.vitepress/theme/VersionPicker.vue +++ b/docs/src/.vitepress/theme/VersionPicker.vue @@ -34,7 +34,7 @@ const getBaseRepository = () => { if ((origin.includes('qutip.org')) || (origin.includes('github.io'))) { // Extract the first part of the path as the repository name const pathParts = pathname.split('/').filter(Boolean); - const baseRepo = pathParts.length > 0 ? `/${pathParts[0]}/` : '/'; + const baseRepo = pathParts.length > 0 ? `/${pathParts[0]}` : ''; return `${origin}${baseRepo}`; } else { // For custom domains, use just the origin (e.g., https://docs.makie.org) From afded9ad739b13ad3e35e26bf6ccff239f361c46 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 15 Nov 2024 00:39:46 +0900 Subject: [PATCH 122/329] Bump version to `v0.21.5` (#309) * bump version to `v0.21.5` * bump version to `v0.21.5` --- CHANGELOG.md | 5 +++++ Project.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df6e81ce8..ef0741623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - *__We will start to write changelog once we have the first standard release.__* +## [v0.21.5] (2024-11-15) + +- This is a demonstration of how to bump version number and also modify `CHANGELOG.md` before new release. ([#309]) + ## [v0.21.4] (2024-11-13) - This is just a demonstration about [`Changelog.jl`](https://github.com/JuliaDocs/Changelog.jl). ([#139], [#306]) @@ -19,3 +23,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [v0.21.4]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.4 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 +[#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 diff --git a/Project.toml b/Project.toml index a930b4d31..97827fd11 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.21.4" +version = "0.21.5" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 9567c45a7a62b7042c02102faa11309e43138fda Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:18:01 +0900 Subject: [PATCH 123/329] Try to fix several missing favicons in doc (#310) * try to fix several missing favicons in doc * fix indent --- docs/src/.vitepress/config.mts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 8ba7258d6..bad15a5ab 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -28,9 +28,10 @@ export default defineConfig({ cleanUrls: true, outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... head: [ - ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], - ['script', {src: `/QuantumToolbox.jl/versions.js`}], - ['script', {src: `${baseTemp.base}siteinfo.js`}] + ['link', { rel: 'icon', href: '/QuantumToolbox.jl/favicon.ico' }], + ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], + ['script', {src: `/QuantumToolbox.jl/versions.js`}], + ['script', {src: `${baseTemp.base}siteinfo.js`}] ], ignoreDeadLinks: true, From 283669648d286d20f24568fc1ed7143dd3a7d60d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:07:28 +0100 Subject: [PATCH 124/329] Make time evolution solvers compatible with automatic differentiation (#311) * Working sesolve * add `inplace` keywork argument * add SciMLStructures and relax params type * Working mcsolve (no type-stability) * Fix type-instabilities for mcsolve * Add SciMLStructures.jl methods * Add callbacks helpers * Fix dsf_mcsolve * Remove ProgressBar from ODE parameters * Fix abstol and reltol extraction * Use Base allequal function * Remove expvals from TimeEvolutionParameters * Make NullParameters as default for params * Remove custom PresetTimeCallback * Update description of `inplace` argument * Working mesolve * Fix dfd_mesolve and dsf_mesolve * Remove TimeEvolutionParameters (type-unstable) * Fix type instabilities * Fix type instabilities on Julia v1.10 * Format document --- CHANGELOG.md | 5 +- docs/src/resources/api.md | 1 + src/QuantumToolbox.jl | 11 +- src/correlations.jl | 3 +- src/qobj/eigsolve.jl | 17 +- src/qobj/quantum_object_evo.jl | 2 +- .../callback_helpers/callback_helpers.jl | 89 ++++ .../mcsolve_callback_helpers.jl | 340 +++++++++++++ .../mesolve_callback_helpers.jl | 39 ++ .../sesolve_callback_helpers.jl | 25 + src/time_evolution/mcsolve.jl | 445 ++++++------------ src/time_evolution/mesolve.jl | 105 ++--- src/time_evolution/sesolve.jl | 112 ++--- src/time_evolution/ssesolve.jl | 54 +-- src/time_evolution/time_evolution.jl | 60 ++- .../time_evolution_dynamical.jl | 94 ++-- test/core-test/time_evolution.jl | 35 +- 17 files changed, 876 insertions(+), 561 deletions(-) create mode 100644 src/time_evolution/callback_helpers/callback_helpers.jl create mode 100644 src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl create mode 100644 src/time_evolution/callback_helpers/mesolve_callback_helpers.jl create mode 100644 src/time_evolution/callback_helpers/sesolve_callback_helpers.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index ef0741623..df5f42045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- *__We will start to write changelog once we have the first standard release.__* +- Change the parameters structure of `sesolve`, `mesolve` and `mcsolve` functions to possibly support automatic differentiation. ([#311]) + ## [v0.21.5] (2024-11-15) @@ -21,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [v0.21.4]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.4 +[v0.21.5]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.5 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 [#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 +[#311]: https://github.com/qutip/QuantumToolbox.jl/issues/311 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index c12753b06..0b0c37a59 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -181,6 +181,7 @@ qeye ## [Time evolution](@id doc-API:Time-evolution) ```@docs +TimeEvolutionProblem TimeEvolutionSol TimeEvolutionMCSol TimeEvolutionSSESol diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 4077ed050..676c61e32 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -23,17 +23,22 @@ import SciMLBase: reinit!, remake, u_modified!, + NullParameters, ODEFunction, ODEProblem, SDEProblem, EnsembleProblem, EnsembleSerial, EnsembleThreads, + EnsembleSplitThreads, EnsembleDistributed, FullSpecialize, CallbackSet, ContinuousCallback, - DiscreteCallback + DiscreteCallback, + AbstractSciMLProblem, + AbstractODEIntegrator, + AbstractODESolution import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 import SciMLOperators: SciMLOperators, @@ -88,6 +93,10 @@ include("qobj/synonyms.jl") # time evolution include("time_evolution/time_evolution.jl") +include("time_evolution/callback_helpers/sesolve_callback_helpers.jl") +include("time_evolution/callback_helpers/mesolve_callback_helpers.jl") +include("time_evolution/callback_helpers/mcsolve_callback_helpers.jl") +include("time_evolution/callback_helpers/callback_helpers.jl") include("time_evolution/mesolve.jl") include("time_evolution/lr_mesolve.jl") include("time_evolution/sesolve.jl") diff --git a/src/correlations.jl b/src/correlations.jl index 38d7cb994..cbec3da95 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -49,8 +49,7 @@ function correlation_3op_2t( (H.dims == ψ0.dims && H.dims == A.dims && H.dims == B.dims && H.dims == C.dims) || throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) - kwargs2 = (; kwargs...) - kwargs2 = merge(kwargs2, (saveat = collect(t_l),)) + kwargs2 = merge((saveat = collect(t_l),), (; kwargs...)) ρt = mesolve(H, ψ0, t_l, c_ops; kwargs2...).states corr = map((t, ρ) -> mesolve(H, C * ρ * A, τ_l .+ t, c_ops, e_ops = [B]; kwargs...).expect[1, :], t_l, ρt) diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 63b4be999..47bbeeccb 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -391,14 +391,15 @@ function eigsolve_al( kwargs..., ) where {DT1,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L_evo = _mesolve_make_L_QobjEvo(H, c_ops) - prob = mesolveProblem( - L_evo, - QuantumObject(ρ0, type = Operator, dims = H.dims), - [zero(T), T]; - params = params, - progress_bar = Val(false), - kwargs..., - ) + prob = + mesolveProblem( + L_evo, + QuantumObject(ρ0, type = Operator, dims = H.dims), + [zero(T), T]; + params = params, + progress_bar = Val(false), + kwargs..., + ).prob integrator = init(prob, alg) # prog = ProgressUnknown(desc="Applications:", showspeed = true, enabled=progress) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 8abadb3b0..cec0010e5 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -269,7 +269,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` quote dims = tuple($(dims_expr...)) - length(unique(dims)) == 1 || throw(ArgumentError("The dimensions of the operators must be the same.")) + allequal(dims) || throw(ArgumentError("The dimensions of the operators must be the same.")) data_expr_const = $qobj_expr_const isa Integer ? $qobj_expr_const : _make_SciMLOperator($qobj_expr_const, α) diff --git a/src/time_evolution/callback_helpers/callback_helpers.jl b/src/time_evolution/callback_helpers/callback_helpers.jl new file mode 100644 index 000000000..d1180afe3 --- /dev/null +++ b/src/time_evolution/callback_helpers/callback_helpers.jl @@ -0,0 +1,89 @@ +#= +This file contains helper functions for callbacks. The affect! function are defined taking advantage of the Julia struct, which allows to store some cache exclusively for the callback. +=# + +## + +# Multiple dispatch depending on the progress_bar and e_ops types +function _generate_se_me_kwargs(e_ops, progress_bar, tlist, kwargs, method) + cb = _generate_save_callback(e_ops, tlist, progress_bar, method) + return _merge_kwargs_with_callback(kwargs, cb) +end +_generate_se_me_kwargs(e_ops::Nothing, progress_bar::Val{false}, tlist, kwargs, method) = kwargs + +function _merge_kwargs_with_callback(kwargs, cb) + kwargs2 = + haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(cb, kwargs.callback),)) : + merge(kwargs, (callback = cb,)) + + return kwargs2 +end + +function _generate_save_callback(e_ops, tlist, progress_bar, method) + e_ops_data = e_ops isa Nothing ? nothing : _get_e_ops_data(e_ops, method) + + progr = getVal(progress_bar) ? ProgressBar(length(tlist), enable = getVal(progress_bar)) : nothing + + expvals = e_ops isa Nothing ? nothing : Array{ComplexF64}(undef, length(e_ops), length(tlist)) + + _save_affect! = method(e_ops_data, progr, Ref(1), expvals) + return PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)) +end + +_get_e_ops_data(e_ops, ::Type{SaveFuncSESolve}) = get_data.(e_ops) +_get_e_ops_data(e_ops, ::Type{SaveFuncMESolve}) = [_generate_mesolve_e_op(op) for op in e_ops] # Broadcasting generates type instabilities on Julia v1.10 + +_generate_mesolve_e_op(op) = mat2vec(adjoint(get_data(op))) + +## + +# When e_ops is Nothing. Common for both mesolve and sesolve +function _save_func(integrator, progr) + next!(progr) + u_modified!(integrator, false) + return nothing +end + +# When progr is Nothing. Common for both mesolve and sesolve +function _save_func(integrator, progr::Nothing) + u_modified!(integrator, false) + return nothing +end + +## + +# Get the e_ops from a given AbstractODESolution. Valid for `sesolve`, `mesolve` and `ssesolve`. +function _se_me_sse_get_expvals(sol::AbstractODESolution) + cb = _se_me_sse_get_save_callback(sol) + if cb isa Nothing + return nothing + else + return cb.affect!.expvals + end +end + +function _se_me_sse_get_save_callback(sol::AbstractODESolution) + kwargs = NamedTuple(sol.prob.kwargs) # Convert to NamedTuple to support Zygote.jl + if hasproperty(kwargs, :callback) + return _se_me_sse_get_save_callback(kwargs.callback) + else + return nothing + end +end +_se_me_sse_get_save_callback(integrator::AbstractODEIntegrator) = _se_me_sse_get_save_callback(integrator.opts.callback) +function _se_me_sse_get_save_callback(cb::CallbackSet) + cbs_discrete = cb.discrete_callbacks + if length(cbs_discrete) > 0 + _cb = cb.discrete_callbacks[1] + return _se_me_sse_get_save_callback(_cb) + else + return nothing + end +end +_se_me_sse_get_save_callback(cb::DiscreteCallback) = + if (cb.affect! isa SaveFuncSESolve) || (cb.affect! isa SaveFuncMESolve) + return cb + else + return nothing + end +_se_me_sse_get_save_callback(cb::ContinuousCallback) = nothing diff --git a/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl new file mode 100644 index 000000000..c27b8cbcb --- /dev/null +++ b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl @@ -0,0 +1,340 @@ +#= +Helper functions for the mcsolve callbacks. +=# + +struct SaveFuncMCSolve{TE,IT,TEXPV} + e_ops::TE + iter::IT + expvals::TEXPV +end + +(f::SaveFuncMCSolve)(integrator) = _save_func_mcsolve(integrator, f.e_ops, f.iter, f.expvals) + +struct LindbladJump{ + T1, + T2, + RNGType<:AbstractRNG, + RandT, + CT<:AbstractVector, + WT<:AbstractVector, + JTT<:AbstractVector, + JWT<:AbstractVector, + JTWIT, +} + c_ops::T1 + c_ops_herm::T2 + traj_rng::RNGType + random_n::RandT + cache_mc::CT + weights_mc::WT + cumsum_weights_mc::WT + jump_times::JTT + jump_which::JWT + jump_times_which_idx::JTWIT +end + +(f::LindbladJump)(integrator) = _lindblad_jump_affect!( + integrator, + f.c_ops, + f.c_ops_herm, + f.traj_rng, + f.random_n, + f.cache_mc, + f.weights_mc, + f.cumsum_weights_mc, + f.jump_times, + f.jump_which, + f.jump_times_which_idx, +) + +## + +function _save_func_mcsolve(integrator, e_ops, iter, expvals) + cache_mc = _mc_get_jump_callback(integrator).affect!.cache_mc + + copyto!(cache_mc, integrator.u) + normalize!(cache_mc) + ψ = cache_mc + _expect = op -> dot(ψ, op, ψ) + @. expvals[:, iter[]] = _expect(e_ops) + iter[] += 1 + + u_modified!(integrator, false) + return nothing +end + +function _generate_mcsolve_kwargs(ψ0, T, e_ops, tlist, c_ops, jump_callback, rng, kwargs) + c_ops_data = get_data.(c_ops) + c_ops_herm_data = map(op -> op' * op, c_ops_data) + + cache_mc = similar(ψ0.data, T) + weights_mc = Vector{Float64}(undef, length(c_ops)) + cumsum_weights_mc = similar(weights_mc) + + jump_times = Vector{Float64}(undef, JUMP_TIMES_WHICH_INIT_SIZE) + jump_which = Vector{Int}(undef, JUMP_TIMES_WHICH_INIT_SIZE) + jump_times_which_idx = Ref(1) + + random_n = Ref(rand(rng)) + + _affect! = LindbladJump( + c_ops_data, + c_ops_herm_data, + rng, + random_n, + cache_mc, + weights_mc, + cumsum_weights_mc, + jump_times, + jump_which, + jump_times_which_idx, + ) + + if jump_callback isa DiscreteLindbladJumpCallback + cb1 = DiscreteCallback(_mcsolve_discrete_condition, _affect!, save_positions = (false, false)) + else + cb1 = ContinuousCallback( + _mcsolve_continuous_condition, + _affect!, + nothing, + interp_points = jump_callback.interp_points, + save_positions = (false, false), + ) + end + + if e_ops isa Nothing + # We are implicitly saying that we don't have a `ProgressBar` + kwargs2 = + haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(cb1, kwargs.callback),)) : + merge(kwargs, (callback = cb1,)) + return kwargs2 + else + expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) + + _save_affect! = SaveFuncMCSolve(get_data.(e_ops), Ref(1), expvals) + cb2 = PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)) + kwargs2 = + haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(cb1, cb2, kwargs.callback),)) : + merge(kwargs, (callback = CallbackSet(cb1, cb2),)) + return kwargs2 + end +end + +function _lindblad_jump_affect!( + integrator, + c_ops, + c_ops_herm, + traj_rng, + random_n, + cache_mc, + weights_mc, + cumsum_weights_mc, + jump_times, + jump_which, + jump_times_which_idx, +) + ψ = integrator.u + + @inbounds for i in eachindex(weights_mc) + weights_mc[i] = real(dot(ψ, c_ops_herm[i], ψ)) + end + cumsum!(cumsum_weights_mc, weights_mc) + r = rand(traj_rng) * sum(weights_mc) + collapse_idx = getindex(1:length(weights_mc), findfirst(>(r), cumsum_weights_mc)) + mul!(cache_mc, c_ops[collapse_idx], ψ) + normalize!(cache_mc) + copyto!(integrator.u, cache_mc) + + random_n[] = rand(traj_rng) + + idx = jump_times_which_idx[] + @inbounds jump_times[idx] = integrator.t + @inbounds jump_which[idx] = collapse_idx + jump_times_which_idx[] += 1 + if jump_times_which_idx[] > length(jump_times) + resize!(jump_times, length(jump_times) + JUMP_TIMES_WHICH_INIT_SIZE) + resize!(jump_which, length(jump_which) + JUMP_TIMES_WHICH_INIT_SIZE) + end + u_modified!(integrator, true) + return nothing +end + +_mcsolve_continuous_condition(u, t, integrator) = + @inbounds _mc_get_jump_callback(integrator).affect!.random_n[] - real(dot(u, u)) + +_mcsolve_discrete_condition(u, t, integrator) = + @inbounds real(dot(u, u)) < _mc_get_jump_callback(integrator).affect!.random_n[] + +## + +#= + _mc_get_save_callback + +Return the Callback that is responsible for saving the expectation values of the system. +=# +function _mc_get_save_callback(sol::AbstractODESolution) + kwargs = NamedTuple(sol.prob.kwargs) # Convert to NamedTuple to support Zygote.jl + return _mc_get_save_callback(kwargs.callback) # There is always the Jump callback +end +_mc_get_save_callback(integrator::AbstractODEIntegrator) = _mc_get_save_callback(integrator.opts.callback) +function _mc_get_save_callback(cb::CallbackSet) + cbs_discrete = cb.discrete_callbacks + + if length(cbs_discrete) > 0 + idx = _mcsolve_has_continuous_jump(cb) ? 1 : 2 + _cb = cb.discrete_callbacks[idx] + return _mc_get_save_callback(_cb) + else + return nothing + end +end +_mc_get_save_callback(cb::DiscreteCallback) = + if cb.affect! isa SaveFuncMCSolve + return cb + else + return nothing + end +_mc_get_save_callback(cb::ContinuousCallback) = nothing + +## + +function _mc_get_jump_callback(sol::AbstractODESolution) + kwargs = NamedTuple(sol.prob.kwargs) # Convert to NamedTuple to support Zygote.jl + return _mc_get_jump_callback(kwargs.callback) # There is always the Jump callback +end +_mc_get_jump_callback(integrator::AbstractODEIntegrator) = _mc_get_jump_callback(integrator.opts.callback) +_mc_get_jump_callback(cb::CallbackSet) = + if _mcsolve_has_continuous_jump(cb) + return cb.continuous_callbacks[1] + else + return cb.discrete_callbacks[1] + end +_mc_get_jump_callback(cb::ContinuousCallback) = cb +_mc_get_jump_callback(cb::DiscreteCallback) = cb + +## + +#= +With this function we extract the c_ops and c_ops_herm from the LindbladJump `affect!` function of the callback of the integrator. +This callback can be a DiscreteLindbladJumpCallback or a ContinuousLindbladJumpCallback. +=# +function _mcsolve_get_c_ops(integrator::AbstractODEIntegrator) + cb = _mc_get_jump_callback(integrator) + if cb isa Nothing + return nothing + else + return cb.affect!.c_ops, cb.affect!.c_ops_herm + end +end + +#= +With this function we extract the e_ops from the SaveFuncMCSolve `affect!` function of the callback of the integrator. +This callback can only be a PresetTimeCallback (DiscreteCallback). +=# +function _mcsolve_get_e_ops(integrator::AbstractODEIntegrator) + cb = _mc_get_save_callback(integrator) + if cb isa Nothing + return nothing + else + return cb.affect!.e_ops + end +end + +function _mcsolve_get_expvals(sol::AbstractODESolution) + cb = _mc_get_save_callback(sol) + if cb isa Nothing + return nothing + else + return cb.affect!.expvals + end +end + +#= + _mcsolve_initialize_callbacks(prob, tlist) + +Return the same callbacks of the `prob`, but with the `iter` variable reinitialized to 1 and the `expvals` variable reinitialized to a new matrix. +=# +function _mcsolve_initialize_callbacks(prob, tlist, traj_rng) + cb = prob.kwargs[:callback] + return _mcsolve_initialize_callbacks(cb, tlist, traj_rng) +end +function _mcsolve_initialize_callbacks(cb::CallbackSet, tlist, traj_rng) + cb_continuous = cb.continuous_callbacks + cb_discrete = cb.discrete_callbacks + + if _mcsolve_has_continuous_jump(cb) + idx = 1 + if cb_discrete[idx].affect! isa SaveFuncMCSolve + e_ops = cb_discrete[idx].affect!.e_ops + expvals = similar(cb_discrete[idx].affect!.expvals) + _save_affect! = SaveFuncMCSolve(e_ops, Ref(1), expvals) + cb_save = (PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)),) + else + cb_save = () + end + + _jump_affect! = _similar_affect!(cb_continuous[1].affect!, traj_rng) + cb_jump = _modify_field(cb_continuous[1], :affect!, _jump_affect!) + + return CallbackSet((cb_jump, cb_continuous[2:end]...), (cb_save..., cb_discrete[2:end]...)) + else + idx = 2 + if cb_discrete[idx].affect! isa SaveFuncMCSolve + e_ops = cb_discrete[idx].affect!.e_ops + expvals = similar(cb_discrete[idx].affect!.expvals) + _save_affect! = SaveFuncMCSolve(e_ops, Ref(1), expvals) + cb_save = (PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)),) + else + cb_save = () + end + + _jump_affect! = _similar_affect!(cb_discrete[1].affect!, traj_rng) + cb_jump = _modify_field(cb_discrete[1], :affect!, _jump_affect!) + + return CallbackSet(cb_continuous, (cb_jump, cb_save..., cb_discrete[3:end]...)) + end +end +function _mcsolve_initialize_callbacks(cb::CBT, tlist, traj_rng) where {CBT<:Union{ContinuousCallback,DiscreteCallback}} + _jump_affect! = _similar_affect!(cb.affect!, traj_rng) + return _modify_field(cb, :affect!, _jump_affect!) +end + +#= + _similar_affect! + +Return a new LindbladJump with the same fields as the input LindbladJump but with new memory. +=# +function _similar_affect!(affect::LindbladJump, traj_rng) + random_n = Ref(rand(traj_rng)) + cache_mc = similar(affect.cache_mc) + weights_mc = similar(affect.weights_mc) + cumsum_weights_mc = similar(affect.cumsum_weights_mc) + jump_times = similar(affect.jump_times) + jump_which = similar(affect.jump_which) + jump_times_which_idx = Ref(1) + + return LindbladJump( + affect.c_ops, + affect.c_ops_herm, + traj_rng, + random_n, + cache_mc, + weights_mc, + cumsum_weights_mc, + jump_times, + jump_which, + jump_times_which_idx, + ) +end + +Base.@constprop :aggressive function _modify_field(obj::T, field_name::Symbol, field_val) where {T} + # Create a NamedTuple of fields, deepcopying only the selected ones + fields = (name != field_name ? (getfield(obj, name)) : field_val for name in fieldnames(T)) + # Reconstruct the struct with the updated fields + return Base.typename(T).wrapper(fields...) +end + +_mcsolve_has_continuous_jump(cb::CallbackSet) = + (length(cb.continuous_callbacks) > 0) && (cb.continuous_callbacks[1].affect! isa LindbladJump) +_mcsolve_has_continuous_jump(cb::ContinuousCallback) = true +_mcsolve_has_continuous_jump(cb::DiscreteCallback) = false diff --git a/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl new file mode 100644 index 000000000..449e2645a --- /dev/null +++ b/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl @@ -0,0 +1,39 @@ +#= +Helper functions for the mesolve callbacks. +=# + +struct SaveFuncMESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} + e_ops::TE + progr::PT + iter::IT + expvals::TEXPV +end + +(f::SaveFuncMESolve)(integrator) = _save_func_mesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) +(f::SaveFuncMESolve{Nothing})(integrator) = _save_func(integrator, f.progr) + +## + +# When e_ops is a list of operators +function _save_func_mesolve(integrator, e_ops, progr, iter, expvals) + # This is equivalent to tr(op * ρ), when both are matrices. + # The advantage of using this convention is that We don't need + # to reshape u to make it a matrix, but we reshape the e_ops once. + + ρ = integrator.u + _expect = op -> dot(op, ρ) + @. expvals[:, iter[]] = _expect(e_ops) + iter[] += 1 + + return _save_func(integrator, progr) +end + +function _mesolve_callbacks_new_e_ops!(integrator::AbstractODEIntegrator, e_ops) + cb = _se_me_sse_get_save_callback(integrator) + if cb isa Nothing + return nothing + else + cb.affect!.e_ops .= e_ops # Only works if e_ops is a Vector of operators + return nothing + end +end diff --git a/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl new file mode 100644 index 000000000..54f0945f9 --- /dev/null +++ b/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl @@ -0,0 +1,25 @@ +#= +Helper functions for the sesolve callbacks. +=# + +struct SaveFuncSESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} + e_ops::TE + progr::PT + iter::IT + expvals::TEXPV +end + +(f::SaveFuncSESolve)(integrator) = _save_func_sesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) +(f::SaveFuncSESolve{Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both mesolve and sesolve + +## + +# When e_ops is a list of operators +function _save_func_sesolve(integrator, e_ops, progr, iter, expvals) + ψ = integrator.u + _expect = op -> dot(ψ, op, ψ) + @. expvals[:, iter[]] = _expect(e_ops) + iter[] += 1 + + return _save_func(integrator, progr) +end diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 0c58b0425..db8ab0319 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -1,128 +1,70 @@ export mcsolveProblem, mcsolveEnsembleProblem, mcsolve export ContinuousLindbladJumpCallback, DiscreteLindbladJumpCallback -function _save_func_mcsolve(integrator) - internal_params = integrator.p - progr = internal_params.progr_mc - - if !internal_params.is_empty_e_ops_mc - e_ops = internal_params.e_ops_mc - expvals = internal_params.expvals - cache_mc = internal_params.cache_mc - - copyto!(cache_mc, integrator.u) - normalize!(cache_mc) - ψ = cache_mc - _expect = op -> dot(ψ, op, ψ) - @. expvals[:, progr.counter[]+1] = _expect(e_ops) - end - next!(progr) - return u_modified!(integrator, false) -end - -function LindbladJumpAffect!(integrator) - internal_params = integrator.p - c_ops = internal_params.c_ops - c_ops_herm = internal_params.c_ops_herm - cache_mc = internal_params.cache_mc - weights_mc = internal_params.weights_mc - cumsum_weights_mc = internal_params.cumsum_weights_mc - random_n = internal_params.random_n - jump_times = internal_params.jump_times - jump_which = internal_params.jump_which - traj_rng = internal_params.traj_rng - ψ = integrator.u - - @inbounds for i in eachindex(weights_mc) - weights_mc[i] = real(dot(ψ, c_ops_herm[i], ψ)) - end - cumsum!(cumsum_weights_mc, weights_mc) - r = rand(traj_rng) * sum(weights_mc) - collapse_idx = getindex(1:length(weights_mc), findfirst(>(r), cumsum_weights_mc)) - mul!(cache_mc, c_ops[collapse_idx], ψ) - normalize!(cache_mc) - copyto!(integrator.u, cache_mc) - - random_n[] = rand(traj_rng) - jump_times[internal_params.jump_times_which_idx[]] = integrator.t - jump_which[internal_params.jump_times_which_idx[]] = collapse_idx - internal_params.jump_times_which_idx[] += 1 - if internal_params.jump_times_which_idx[] > length(jump_times) - resize!(jump_times, length(jump_times) + internal_params.jump_times_which_init_size) - resize!(jump_which, length(jump_which) + internal_params.jump_times_which_init_size) - end -end - -LindbladJumpContinuousCondition(u, t, integrator) = integrator.p.random_n[] - real(dot(u, u)) - -LindbladJumpDiscreteCondition(u, t, integrator) = real(dot(u, u)) < integrator.p.random_n[] - -function _mcsolve_prob_func(prob, i, repeat) - internal_params = prob.p - - global_rng = internal_params.global_rng - seed = internal_params.seeds[i] +function _mcsolve_prob_func(prob, i, repeat, global_rng, seeds, tlist) + seed = seeds[i] traj_rng = typeof(global_rng)() seed!(traj_rng, seed) - prm = merge( - internal_params, - ( - expvals = similar(internal_params.expvals), - cache_mc = similar(internal_params.cache_mc), - weights_mc = similar(internal_params.weights_mc), - cumsum_weights_mc = similar(internal_params.weights_mc), - traj_rng = traj_rng, - random_n = Ref(rand(traj_rng)), - progr_mc = ProgressBar(size(internal_params.expvals, 2), enable = false), - jump_times_which_idx = Ref(1), - jump_times = similar(internal_params.jump_times), - jump_which = similar(internal_params.jump_which), - ), - ) - f = deepcopy(prob.f.f) + cb = _mcsolve_initialize_callbacks(prob, tlist, traj_rng) + + return remake(prob, f = f, callback = cb) +end - return remake(prob, f = f, p = prm) +function _mcsolve_dispatch_prob_func(rng, ntraj, tlist) + seeds = map(i -> rand(rng, UInt64), 1:ntraj) + return (prob, i, repeat) -> _mcsolve_prob_func(prob, i, repeat, rng, seeds, tlist) end # Standard output function function _mcsolve_output_func(sol, i) - resize!(sol.prob.p.jump_times, sol.prob.p.jump_times_which_idx[] - 1) - resize!(sol.prob.p.jump_which, sol.prob.p.jump_times_which_idx[] - 1) + idx = _mc_get_jump_callback(sol).affect!.jump_times_which_idx[] + resize!(_mc_get_jump_callback(sol).affect!.jump_times, idx - 1) + resize!(_mc_get_jump_callback(sol).affect!.jump_which, idx - 1) return (sol, false) end # Output function with progress bar update -function _mcsolve_output_func_progress(sol, i) - next!(sol.prob.p.progr_trajectories) +function _mcsolve_output_func_progress(sol, i, progr) + next!(progr) return _mcsolve_output_func(sol, i) end # Output function with distributed channel update for progress bar -function _mcsolve_output_func_distributed(sol, i) - put!(sol.prob.p.progr_channel, true) +function _mcsolve_output_func_distributed(sol, i, channel) + put!(channel, true) return _mcsolve_output_func(sol, i) end -_mcsolve_dispatch_output_func() = _mcsolve_output_func -_mcsolve_dispatch_output_func(::ET) where {ET<:Union{EnsembleSerial,EnsembleThreads}} = _mcsolve_output_func_progress -_mcsolve_dispatch_output_func(::EnsembleDistributed) = _mcsolve_output_func_distributed - -function _normalize_state!(u, dims, normalize_states) - getVal(normalize_states) && normalize!(u) - return QuantumObject(u, dims = dims) +function _mcsolve_dispatch_output_func(::ET, progress_bar, ntraj) where {ET<:Union{EnsembleSerial,EnsembleThreads}} + if getVal(progress_bar) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + f = (sol, i) -> _mcsolve_output_func_progress(sol, i, progr) + return (f, progr, nothing) + else + return (_mcsolve_output_func, nothing, nothing) + end end +function _mcsolve_dispatch_output_func( + ::ET, + progress_bar, + ntraj, +) where {ET<:Union{EnsembleSplitThreads,EnsembleDistributed}} + if getVal(progress_bar) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) -function _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which, normalize_states) - sol_i = sol[:, i] - dims = sol_i.prob.p.Hdims - !isempty(sol_i.prob.kwargs[:saveat]) ? states[i] = map(u -> _normalize_state!(u, dims, normalize_states), sol_i.u) : - nothing + f = (sol, i) -> _mcsolve_output_func_distributed(sol, i, progr_channel) + return (f, progr, progr_channel) + else + return (_mcsolve_output_func, nothing, nothing) + end +end - copyto!(view(expvals_all, i, :, :), sol_i.prob.p.expvals) - jump_times[i] = sol_i.prob.p.jump_times - return jump_which[i] = sol_i.prob.p.jump_which +function _normalize_state!(u, dims, normalize_states) + getVal(normalize_states) && normalize!(u) + return QuantumObject(u, type = Ket, dims = dims) end function _mcsolve_make_Heff_QobjEvo(H::QuantumObject, c_ops) @@ -145,7 +87,7 @@ end tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), jump_callback::TJC = ContinuousLindbladJumpCallback(), kwargs..., @@ -192,7 +134,7 @@ If the environmental measurements register a quantum jump, the wave function und - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `rng`: Random number generator for reproducibility. - `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. - `kwargs`: The keyword arguments for the ODEProblem. @@ -206,7 +148,7 @@ If the environmental measurements register a quantum jump, the wave function und # Returns -- `prob::ODEProblem`: The ODEProblem for the Monte Carlo wave function time evolution. +- `prob`: The [`TimeEvolutionProblem`](@ref) containing the `ODEProblem` for the Monte Carlo wave function time evolution. """ function mcsolveProblem( H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, @@ -214,7 +156,7 @@ function mcsolveProblem( tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), jump_callback::TJC = ContinuousLindbladJumpCallback(), kwargs..., @@ -229,93 +171,17 @@ function mcsolveProblem( H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, c_ops) - if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(tlist)) - is_empty_e_ops_mc = true - e_ops_data = () - else - expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) - e_ops_data = get_data.(e_ops) - is_empty_e_ops_mc = isempty(e_ops) - end + T = Base.promote_eltype(H_eff_evo, ψ0) - saveat = is_empty_e_ops_mc ? tlist : [tlist[end]] + is_empty_e_ops = e_ops isa Nothing ? true : isempty(e_ops) + + saveat = is_empty_e_ops ? tlist : [tlist[end]] # We disable the progress bar of the sesolveProblem because we use a global progress bar for all the trajectories default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat, progress_bar = Val(false)) kwargs2 = merge(default_values, kwargs) + kwargs3 = _generate_mcsolve_kwargs(ψ0, T, e_ops, tlist, c_ops, jump_callback, rng, kwargs2) - cache_mc = similar(ψ0.data) - weights_mc = Array{Float64}(undef, length(c_ops)) - cumsum_weights_mc = similar(weights_mc) - - jump_times_which_init_size = 200 - jump_times = Vector{Float64}(undef, jump_times_which_init_size) - jump_which = Vector{Int16}(undef, jump_times_which_init_size) - - c_ops_data = get_data.(c_ops) - c_ops_herm_data = map(op -> op' * op, c_ops_data) - - params2 = ( - expvals = expvals, - e_ops_mc = e_ops_data, - is_empty_e_ops_mc = is_empty_e_ops_mc, - progr_mc = ProgressBar(length(tlist), enable = false), - traj_rng = rng, - c_ops = c_ops_data, - c_ops_herm = c_ops_herm_data, - cache_mc = cache_mc, - weights_mc = weights_mc, - cumsum_weights_mc = cumsum_weights_mc, - jump_times = jump_times, - jump_which = jump_which, - jump_times_which_init_size = jump_times_which_init_size, - jump_times_which_idx = Ref(1), - params..., - ) - - return mcsolveProblem(H_eff_evo, ψ0, tlist, params2, jump_callback; kwargs2...) -end - -function mcsolveProblem( - H_eff_evo::QuantumObjectEvolution{DT1,OperatorQuantumObject}, - ψ0::QuantumObject{DT2,KetQuantumObject}, - tlist::AbstractVector, - params::NamedTuple, - jump_callback::DiscreteLindbladJumpCallback; - kwargs..., -) where {DT1,DT2} - cb1 = DiscreteCallback(LindbladJumpDiscreteCondition, LindbladJumpAffect!, save_positions = (false, false)) - cb2 = PresetTimeCallback(tlist, _save_func_mcsolve, save_positions = (false, false)) - kwargs2 = (; kwargs...) - kwargs2 = - haskey(kwargs2, :callback) ? merge(kwargs2, (callback = CallbackSet(cb1, cb2, kwargs2.callback),)) : - merge(kwargs2, (callback = CallbackSet(cb1, cb2),)) - - return sesolveProblem(H_eff_evo, ψ0, tlist; params = params, kwargs2...) -end - -function mcsolveProblem( - H_eff_evo::QuantumObjectEvolution{DT1,OperatorQuantumObject}, - ψ0::QuantumObject{DT2,KetQuantumObject}, - tlist::AbstractVector, - params::NamedTuple, - jump_callback::ContinuousLindbladJumpCallback; - kwargs..., -) where {DT1,DT2} - cb1 = ContinuousCallback( - LindbladJumpContinuousCondition, - LindbladJumpAffect!, - nothing, - interp_points = jump_callback.interp_points, - save_positions = (false, false), - ) - cb2 = PresetTimeCallback(tlist, _save_func_mcsolve, save_positions = (false, false)) - kwargs2 = (; kwargs...) - kwargs2 = - haskey(kwargs2, :callback) ? merge(kwargs2, (callback = CallbackSet(cb1, cb2, kwargs2.callback),)) : - merge(kwargs2, (callback = CallbackSet(cb1, cb2),)) - - return sesolveProblem(H_eff_evo, ψ0, tlist; params = params, kwargs2...) + return sesolveProblem(H_eff_evo, ψ0, tlist; params = params, kwargs3...) end @doc raw""" @@ -325,14 +191,14 @@ end tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), - prob_func::Function = _mcsolve_prob_func, - output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), + prob_func::Union{Function, Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, kwargs..., ) @@ -377,14 +243,14 @@ If the environmental measurements register a quantum jump, the wave function und - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. - `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. -- `prob_func`: Function to use for generating the ODEProblem. -- `output_func`: Function to use for generating the output of a single trajectory. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -396,7 +262,7 @@ If the environmental measurements register a quantum jump, the wave function und # Returns -- `prob::EnsembleProblem with ODEProblem`: The Ensemble ODEProblem for the Monte Carlo wave function time evolution. +- `prob`: The [`TimeEvolutionProblem`](@ref) containing the Ensemble `ODEProblem` for the Monte Carlo wave function time evolution. """ function mcsolveEnsembleProblem( H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, @@ -404,51 +270,40 @@ function mcsolveEnsembleProblem( tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), - prob_func::Function = _mcsolve_prob_func, - output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), + prob_func::Union{Function,Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, kwargs..., ) where {DT1,DT2,TJC<:LindbladJumpCallbackType} - progr = ProgressBar(ntraj, enable = getVal(progress_bar)) - if ensemble_method isa EnsembleDistributed - progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) - @async while take!(progr_channel) - next!(progr) - end - params = merge(params, (progr_channel = progr_channel,)) - else - params = merge(params, (progr_trajectories = progr,)) - end + _prob_func = prob_func isa Nothing ? _mcsolve_dispatch_prob_func(rng, ntraj, tlist) : prob_func + _output_func = + output_func isa Nothing ? _mcsolve_dispatch_output_func(ensemble_method, progress_bar, ntraj) : output_func - # Stop the async task if an error occurs - try - seeds = map(i -> rand(rng, UInt64), 1:ntraj) - prob_mc = mcsolveProblem( - H, - ψ0, - tlist, - c_ops; - e_ops = e_ops, - params = merge(params, (global_rng = rng, seeds = seeds)), - rng = rng, - jump_callback = jump_callback, - kwargs..., - ) - - ensemble_prob = EnsembleProblem(prob_mc, prob_func = prob_func, output_func = output_func, safetycopy = false) - - return ensemble_prob - catch e - if ensemble_method isa EnsembleDistributed - put!(progr_channel, false) - end - rethrow() - end + prob_mc = mcsolveProblem( + H, + ψ0, + tlist, + c_ops; + e_ops = e_ops, + params = params, + rng = rng, + jump_callback = jump_callback, + kwargs..., + ) + + ensemble_prob = TimeEvolutionProblem( + EnsembleProblem(prob_mc.prob, prob_func = _prob_func, output_func = _output_func[1], safetycopy = false), + prob_mc.times, + prob_mc.dims, + (progr = _output_func[2], channel = _output_func[3]), + ) + + return ensemble_prob end @doc raw""" @@ -459,14 +314,14 @@ end c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), - prob_func::Function = _mcsolve_prob_func, - output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), + prob_func::Union{Function, Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, normalize_states::Union{Val,Bool} = Val(true), kwargs..., ) @@ -513,14 +368,14 @@ If the environmental measurements register a quantum jump, the wave function und - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm to use for the ODE solver. Default to `Tsit5()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. - `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. -- `prob_func`: Function to use for generating the ODEProblem. -- `output_func`: Function to use for generating the output of a single trajectory. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `normalize_states`: Whether to normalize the states. Default to `Val(true)`. - `kwargs`: The keyword arguments for the ODEProblem. @@ -544,14 +399,14 @@ function mcsolve( c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), - prob_func::Function = _mcsolve_prob_func, - output_func::Function = _mcsolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), + prob_func::Union{Function,Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, normalize_states::Union{Val,Bool} = Val(true), kwargs..., ) where {DT1,DT2,TJC<:LindbladJumpCallbackType} @@ -567,67 +422,79 @@ function mcsolve( ntraj = ntraj, ensemble_method = ensemble_method, jump_callback = jump_callback, + progress_bar = progress_bar, prob_func = prob_func, output_func = output_func, - progress_bar = progress_bar, kwargs..., ) - return mcsolve( - ens_prob_mc; - alg = alg, - ntraj = ntraj, - ensemble_method = ensemble_method, - normalize_states = normalize_states, - ) + return mcsolve(ens_prob_mc, alg, ntraj, ensemble_method, normalize_states) +end + +function _mcsolve_solve_ens( + ens_prob_mc::TimeEvolutionProblem, + alg::OrdinaryDiffEqAlgorithm, + ensemble_method::ET, + ntraj::Int, +) where {ET<:Union{EnsembleSplitThreads,EnsembleDistributed}} + sol = nothing + + @sync begin + @async while take!(ens_prob_mc.kwargs.channel) + next!(ens_prob_mc.kwargs.progr) + end + + @async begin + sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) + put!(ens_prob_mc.kwargs.channel, false) + end + end + + return sol +end + +function _mcsolve_solve_ens( + ens_prob_mc::TimeEvolutionProblem, + alg::OrdinaryDiffEqAlgorithm, + ensemble_method, + ntraj::Int, +) + sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) + return sol end function mcsolve( - ens_prob_mc::EnsembleProblem; + ens_prob_mc::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), - normalize_states::Union{Val,Bool} = Val(true), + normalize_states = Val(true), ) - try - sol = solve(ens_prob_mc, alg, ensemble_method, trajectories = ntraj) - - if ensemble_method isa EnsembleDistributed - put!(sol[:, 1].prob.p.progr_channel, false) - end - - _sol_1 = sol[:, 1] - - expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) - states = - isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : - Vector{Vector{QuantumObject}}(undef, length(sol)) - jump_times = Vector{Vector{Float64}}(undef, length(sol)) - jump_which = Vector{Vector{Int16}}(undef, length(sol)) - - foreach( - i -> _mcsolve_generate_statistics(sol, i, states, expvals_all, jump_times, jump_which, normalize_states), - eachindex(sol), - ) - expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) - - return TimeEvolutionMCSol( - ntraj, - _sol_1.prob.p.times, - states, - expvals, - expvals_all, - jump_times, - jump_which, - sol.converged, - _sol_1.alg, - _sol_1.prob.kwargs[:abstol], - _sol_1.prob.kwargs[:reltol], - ) - catch e - if ensemble_method isa EnsembleDistributed - put!(ens_prob_mc.prob.p.progr_channel, false) - end - rethrow() - end + sol = _mcsolve_solve_ens(ens_prob_mc, alg, ensemble_method, ntraj) + + dims = ens_prob_mc.dims + _sol_1 = sol[:, 1] + _expvals_sol_1 = _mcsolve_get_expvals(_sol_1) + + _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _mcsolve_get_expvals(sol[:, i]), eachindex(sol)) + expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) + states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) + jump_times = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.jump_times, eachindex(sol)) + jump_which = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.jump_which, eachindex(sol)) + + expvals = _expvals_sol_1 isa Nothing ? nothing : dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) + + return TimeEvolutionMCSol( + ntraj, + ens_prob_mc.times, + states, + expvals, + expvals_all, + jump_times, + jump_which, + sol.converged, + _sol_1.alg, + NamedTuple(_sol_1.prob.kwargs).abstol, + NamedTuple(_sol_1.prob.kwargs).reltol, + ) end diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 76714c579..2d9f42efc 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -1,46 +1,5 @@ export mesolveProblem, mesolve -function _save_func_mesolve(integrator) - internal_params = integrator.p - progr = internal_params.progr - - if !internal_params.is_empty_e_ops - expvals = internal_params.expvals - e_ops = internal_params.e_ops - # This is equivalent to tr(op * ρ), when both are matrices. - # The advantage of using this convention is that I don't need - # to reshape u to make it a matrix, but I reshape the e_ops once. - - ρ = integrator.u - _expect = op -> dot(op, ρ) - @. expvals[:, progr.counter[]+1] = _expect(e_ops) - end - next!(progr) - return u_modified!(integrator, false) -end - -_generate_mesolve_e_op(op) = mat2vec(adjoint(get_data(op))) - -function _generate_mesolve_kwargs_with_callback(tlist, kwargs) - cb1 = PresetTimeCallback(tlist, _save_func_mesolve, save_positions = (false, false)) - kwargs2 = - haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(kwargs.callback, cb1),)) : - merge(kwargs, (callback = cb1,)) - - return kwargs2 -end - -function _generate_mesolve_kwargs(e_ops, progress_bar::Val{true}, tlist, kwargs) - return _generate_mesolve_kwargs_with_callback(tlist, kwargs) -end - -function _generate_mesolve_kwargs(e_ops, progress_bar::Val{false}, tlist, kwargs) - if e_ops isa Nothing - return kwargs - end - return _generate_mesolve_kwargs_with_callback(tlist, kwargs) -end - _mesolve_make_L_QobjEvo(H::QuantumObject, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator) _mesolve_make_L_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}, c_ops) = liouvillian(QobjEvo(H), c_ops) @@ -51,8 +10,9 @@ _mesolve_make_L_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}, c_ops) = liouvil tlist, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) @@ -75,8 +35,9 @@ where - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `inplace`: Whether to use the inplace version of the ODEProblem. The default is `Val(true)`. It is recommended to use `Val(true)` for better performance, but it is sometimes necessary to use `Val(false)`, for example when performing automatic differentiation using [Zygote.jl](https://github.com/FluxML/Zygote.jl). - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -96,8 +57,9 @@ function mesolveProblem( tlist, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) where { DT1, @@ -113,38 +75,21 @@ function mesolveProblem( L_evo = _mesolve_make_L_QobjEvo(H, c_ops) check_dims(L_evo, ψ0) - ρ0 = sparse_to_dense(_CType(ψ0), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + T = Base.promote_eltype(L_evo, ψ0) + ρ0 = sparse_to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type L = L_evo.data - progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) - - if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(tlist)) - e_ops_data = () - is_empty_e_ops = true - else - expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) - e_ops_data = [_generate_mesolve_e_op(op) for op in e_ops] - is_empty_e_ops = isempty(e_ops) - end - - p = ( - e_ops = e_ops_data, - expvals = expvals, - progr = progr, - times = tlist, - Hdims = L_evo.dims, - is_empty_e_ops = is_empty_e_ops, - params..., - ) + is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_mesolve_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2) + kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncMESolve) tspan = (tlist[1], tlist[end]) - return ODEProblem{true,FullSpecialize}(L, ρ0, tspan, p; kwargs3...) + prob = ODEProblem{getVal(inplace),FullSpecialize}(L, ρ0, tspan, params; kwargs3...) + + return TimeEvolutionProblem(prob, tlist, L_evo.dims) end @doc raw""" @@ -155,8 +100,9 @@ end c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) @@ -180,8 +126,9 @@ where - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm for the ODE solver. The default value is `Tsit5()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `inplace`: Whether to use the inplace version of the ODEProblem. The default is `Val(true)`. It is recommended to use `Val(true)` for better performance, but it is sometimes necessary to use `Val(false)`, for example when performing automatic differentiation using [Zygote.jl](https://github.com/FluxML/Zygote.jl). - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -203,8 +150,9 @@ function mesolve( c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) where { DT1, @@ -221,24 +169,25 @@ function mesolve( e_ops = e_ops, params = params, progress_bar = progress_bar, + inplace = inplace, kwargs..., ) return mesolve(prob, alg) end -function mesolve(prob::ODEProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) - sol = solve(prob, alg) +function mesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) + sol = solve(prob.prob, alg) - ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = sol.prob.p.Hdims), sol.u) + ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = prob.dims), sol.u) return TimeEvolutionSol( - sol.prob.p.times, + prob.times, ρt, - sol.prob.p.expvals, + _se_me_sse_get_expvals(sol), sol.retcode, sol.alg, - sol.prob.kwargs[:abstol], - sol.prob.kwargs[:reltol], + NamedTuple(sol.prob.kwargs).abstol, + NamedTuple(sol.prob.kwargs).reltol, ) end diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index a7218d981..0c5a3305f 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -1,41 +1,5 @@ export sesolveProblem, sesolve -function _save_func_sesolve(integrator) - internal_params = integrator.p - progr = internal_params.progr - - if !internal_params.is_empty_e_ops - e_ops = internal_params.e_ops - expvals = internal_params.expvals - - ψ = integrator.u - _expect = op -> dot(ψ, op, ψ) - @. expvals[:, progr.counter[]+1] = _expect(e_ops) - end - next!(progr) - return u_modified!(integrator, false) -end - -function _generate_sesolve_kwargs_with_callback(tlist, kwargs) - cb1 = PresetTimeCallback(tlist, _save_func_sesolve, save_positions = (false, false)) - kwargs2 = - haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(kwargs.callback, cb1),)) : - merge(kwargs, (callback = cb1,)) - - return kwargs2 -end - -function _generate_sesolve_kwargs(e_ops, progress_bar::Val{true}, tlist, kwargs) - return _generate_sesolve_kwargs_with_callback(tlist, kwargs) -end - -function _generate_sesolve_kwargs(e_ops, progress_bar::Val{false}, tlist, kwargs) - if e_ops isa Nothing - return kwargs - end - return _generate_sesolve_kwargs_with_callback(tlist, kwargs) -end - _sesolve_make_U_QobjEvo(H::QuantumObjectEvolution{<:MatrixOperator}) = QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dims, type = Operator) _sesolve_make_U_QobjEvo(H) = QobjEvo(H, -1im) @@ -46,8 +10,9 @@ _sesolve_make_U_QobjEvo(H) = QobjEvo(H, -1im) ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) @@ -63,8 +28,9 @@ Generate the ODEProblem for the Schrödinger time evolution of a quantum system: - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. - `tlist`: List of times at which to save either the state or the expectation values of the system. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `inplace`: Whether to use the inplace version of the ODEProblem. The default is `Val(true)`. It is recommended to use `Val(true)` for better performance, but it is sometimes necessary to use `Val(false)`, for example when performing automatic differentiation using [Zygote.jl](https://github.com/FluxML/Zygote.jl). - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -76,15 +42,16 @@ Generate the ODEProblem for the Schrödinger time evolution of a quantum system: # Returns -- `prob`: The `ODEProblem` for the Schrödinger time evolution of the system. +- `prob`: The [`TimeEvolutionProblem`](@ref) containing the `ODEProblem` for the Schrödinger time evolution of the system. """ function sesolveProblem( H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, ψ0::QuantumObject{DT2,KetQuantumObject}, tlist::AbstractVector; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) where {DT1,DT2} haskey(kwargs, :save_idxs) && @@ -96,38 +63,21 @@ function sesolveProblem( isoper(H_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) check_dims(H_evo, ψ0) - ψ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) # Convert it to dense vector with complex element type + T = Base.promote_eltype(H_evo, ψ0) + ψ0 = sparse_to_dense(_CType(T), get_data(ψ0)) # Convert it to dense vector with complex element type U = H_evo.data - progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) - - if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(tlist)) - e_ops_data = () - is_empty_e_ops = true - else - expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) - e_ops_data = get_data.(e_ops) - is_empty_e_ops = isempty(e_ops) - end - - p = ( - e_ops = e_ops_data, - expvals = expvals, - progr = progr, - times = tlist, - Hdims = H_evo.dims, - is_empty_e_ops = is_empty_e_ops, - params..., - ) + is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_sesolve_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2) + kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSESolve) tspan = (tlist[1], tlist[end]) - return ODEProblem{true,FullSpecialize}(U, ψ0, tspan, p; kwargs3...) + prob = ODEProblem{getVal(inplace),FullSpecialize}(U, ψ0, tspan, params; kwargs3...) + + return TimeEvolutionProblem(prob, tlist, H_evo.dims) end @doc raw""" @@ -137,8 +87,9 @@ end tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) @@ -155,8 +106,9 @@ Time evolution of a closed quantum system using the Schrödinger equation: - `tlist`: List of times at which to save either the state or the expectation values of the system. - `alg`: The algorithm for the ODE solver. The default is `Tsit5()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `inplace`: Whether to use the inplace version of the ODEProblem. The default is `Val(true)`. It is recommended to use `Val(true)` for better performance, but it is sometimes necessary to use `Val(false)`, for example when performing automatic differentiation using [Zygote.jl](https://github.com/FluxML/Zygote.jl). - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -177,27 +129,37 @@ function sesolve( tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), + inplace::Union{Val,Bool} = Val(true), kwargs..., ) where {DT1,DT2} - prob = sesolveProblem(H, ψ0, tlist; e_ops = e_ops, params = params, progress_bar = progress_bar, kwargs...) + prob = sesolveProblem( + H, + ψ0, + tlist; + e_ops = e_ops, + params = params, + progress_bar = progress_bar, + inplace = inplace, + kwargs..., + ) return sesolve(prob, alg) end -function sesolve(prob::ODEProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) - sol = solve(prob, alg) +function sesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) + sol = solve(prob.prob, alg) - ψt = map(ϕ -> QuantumObject(ϕ, type = Ket, dims = sol.prob.p.Hdims), sol.u) + ψt = map(ϕ -> QuantumObject(ϕ, type = Ket, dims = prob.dims), sol.u) return TimeEvolutionSol( - sol.prob.p.times, + prob.times, ψt, - sol.prob.p.expvals, + _se_me_sse_get_expvals(sol), sol.retcode, sol.alg, - sol.prob.kwargs[:abstol], - sol.prob.kwargs[:reltol], + NamedTuple(sol.prob.kwargs).abstol, + NamedTuple(sol.prob.kwargs).reltol, ) end diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 3d126b9f9..2dc7fac16 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -83,15 +83,6 @@ _ssesolve_dispatch_output_func() = _ssesolve_output_func _ssesolve_dispatch_output_func(::ET) where {ET<:Union{EnsembleSerial,EnsembleThreads}} = _ssesolve_output_func_progress _ssesolve_dispatch_output_func(::EnsembleDistributed) = _ssesolve_output_func_distributed -function _ssesolve_generate_statistics!(sol, i, states, expvals_all) - sol_i = sol[:, i] - !isempty(sol_i.prob.kwargs[:saveat]) ? - states[i] = [QuantumObject(sol_i.u[i], dims = sol_i.prob.p.Hdims) for i in 1:length(sol_i.u)] : nothing - - copyto!(view(expvals_all, i, :, :), sol_i.prob.p.expvals) - return nothing -end - _ScalarOperator_e(op, f = +) = ScalarOperator(one(eltype(op)), (a, u, p, t) -> f(_ssesolve_update_coeff(u, p, t, op))) _ScalarOperator_e2_2(op, f = +) = @@ -182,16 +173,6 @@ function ssesolveProblem( progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) - if e_ops isa Nothing - expvals = Array{ComplexF64}(undef, 0, length(tlist)) - e_ops_data = () - is_empty_e_ops = true - else - expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) - e_ops_data = get_data.(e_ops) - is_empty_e_ops = isempty(e_ops) - end - sc_ops_evo_data = Tuple(map(get_data ∘ QobjEvo, sc_ops)) # Here the coefficients depend on the state, so this is a non-linear operator, which should be implemented with FunctionOperator instead. However, the nonlinearity is only on the coefficients, and it should be safe. @@ -205,21 +186,14 @@ function ssesolveProblem( D_l = map(op -> op + _ScalarOperator_e(op, -) * IdentityOperator(prod(dims)), sc_ops_evo_data) D = DiffusionOperator(D_l) - p = ( - e_ops = e_ops_data, - expvals = expvals, - progr = progr, - times = tlist, - Hdims = dims, - is_empty_e_ops = is_empty_e_ops, - n_sc_ops = length(sc_ops), - params..., - ) + p = (progr = progr, times = tlist, Hdims = dims, n_sc_ops = length(sc_ops), params...) + + is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_sesolve_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2) + kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSESolve) tspan = (tlist[1], tlist[end]) noise = @@ -469,14 +443,18 @@ function ssesolve( end _sol_1 = sol[:, 1] - - expvals_all = Array{ComplexF64}(undef, length(sol), size(_sol_1.prob.p.expvals)...) - states = - isempty(_sol_1.prob.kwargs[:saveat]) ? fill(QuantumObject[], length(sol)) : - Vector{Vector{QuantumObject}}(undef, length(sol)) - - foreach(i -> _ssesolve_generate_statistics!(sol, i, states, expvals_all), eachindex(sol)) - expvals = dropdims(sum(expvals_all, dims = 1), dims = 1) ./ length(sol) + _expvals_sol_1 = _se_me_sse_get_expvals(_sol_1) + + normalize_states = Val(false) + dims = _sol_1.prob.p.Hdims + _expvals_all = + _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) + expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) + states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) + + expvals = + _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : + dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) return TimeEvolutionSSESol( ntraj, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 9297cbedd..b0f701f3f 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -4,6 +4,28 @@ export liouvillian_floquet, liouvillian_generalized const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true) const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true) +const JUMP_TIMES_WHICH_INIT_SIZE = 200 + +@doc raw""" + struct TimeEvolutionProblem + +A Julia constructor for handling the `ODEProblem` of the time evolution of quantum systems. + +# Fields (Attributes) + +- `prob::AbstractSciMLProblem`: The `ODEProblem` of the time evolution. +- `times::Abstractvector`: The time list of the evolution. +- `dims::Abstractvector`: The dimensions of the Hilbert space. +- `kwargs::KWT`: Generic keyword arguments. +""" +struct TimeEvolutionProblem{PT<:AbstractSciMLProblem,TT<:AbstractVector,DT<:AbstractVector,KWT} + prob::PT + times::TT + dims::DT + kwargs::KWT +end + +TimeEvolutionProblem(prob, times, dims) = TimeEvolutionProblem(prob, times, dims, nothing) @doc raw""" struct TimeEvolutionSol @@ -14,7 +36,7 @@ A structure storing the results and some information from solving time evolution - `times::AbstractVector`: The time list of the evolution. - `states::Vector{QuantumObject}`: The list of result states. -- `expect::Matrix`: The expectation values corresponding to each time point in `times`. +- `expect::Union{AbstractMatrix,Nothing}`: The expectation values corresponding to each time point in `times`. - `retcode`: The return code from the solver. - `alg`: The algorithm which is used during the solving process. - `abstol::Real`: The absolute tolerance which is used during the solving process. @@ -23,7 +45,7 @@ A structure storing the results and some information from solving time evolution struct TimeEvolutionSol{ TT<:AbstractVector{<:Real}, TS<:AbstractVector, - TE<:Matrix, + TE<:Union{AbstractMatrix,Nothing}, RETT<:Enum, AlgT<:OrdinaryDiffEqAlgorithm, AT<:Real, @@ -43,7 +65,11 @@ function Base.show(io::IO, sol::TimeEvolutionSol) print(io, "(return code: $(sol.retcode))\n") print(io, "--------------------------\n") print(io, "num_states = $(length(sol.states))\n") - print(io, "num_expect = $(size(sol.expect, 1))\n") + if sol.expect isa Nothing + print(io, "num_expect = 0\n") + else + print(io, "num_expect = $(size(sol.expect, 1))\n") + end print(io, "ODE alg.: $(sol.alg)\n") print(io, "abstol = $(sol.abstol)\n") print(io, "reltol = $(sol.reltol)\n") @@ -60,8 +86,8 @@ A structure storing the results and some information from solving quantum trajec - `ntraj::Int`: Number of trajectories - `times::AbstractVector`: The time list of the evolution. - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. -- `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `expect_all::Array`: The expectation values corresponding to each trajectory and each time point in `times` +- `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. +- `expect_all::Union{AbstractMatrix,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` - `jump_times::Vector{Vector{Real}}`: The time records of every quantum jump occurred in each trajectory. - `jump_which::Vector{Vector{Int}}`: The indices of the jump operators in `c_ops` that describe the corresponding quantum jumps occurred in each trajectory. - `converged::Bool`: Whether the solution is converged or not. @@ -72,8 +98,8 @@ A structure storing the results and some information from solving quantum trajec struct TimeEvolutionMCSol{ TT<:AbstractVector{<:Real}, TS<:AbstractVector, - TE<:Matrix{ComplexF64}, - TEA<:Array{ComplexF64,3}, + TE<:Union{AbstractMatrix,Nothing}, + TEA<:Union{AbstractArray,Nothing}, TJT<:Vector{<:Vector{<:Real}}, TJW<:Vector{<:Vector{<:Integer}}, AlgT<:OrdinaryDiffEqAlgorithm, @@ -99,7 +125,11 @@ function Base.show(io::IO, sol::TimeEvolutionMCSol) print(io, "--------------------------------\n") print(io, "num_trajectories = $(sol.ntraj)\n") print(io, "num_states = $(length(sol.states[1]))\n") - print(io, "num_expect = $(size(sol.expect, 1))\n") + if sol.expect isa Nothing + print(io, "num_expect = 0\n") + else + print(io, "num_expect = $(size(sol.expect, 1))\n") + end print(io, "ODE alg.: $(sol.alg)\n") print(io, "abstol = $(sol.abstol)\n") print(io, "reltol = $(sol.reltol)\n") @@ -116,8 +146,8 @@ A structure storing the results and some information from solving trajectories o - `ntraj::Int`: Number of trajectories - `times::AbstractVector`: The time list of the evolution. - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. -- `expect::Matrix`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `expect_all::Array`: The expectation values corresponding to each trajectory and each time point in `times` +- `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. +- `expect_all::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` - `converged::Bool`: Whether the solution is converged or not. - `alg`: The algorithm which is used during the solving process. - `abstol::Real`: The absolute tolerance which is used during the solving process. @@ -126,8 +156,8 @@ A structure storing the results and some information from solving trajectories o struct TimeEvolutionSSESol{ TT<:AbstractVector{<:Real}, TS<:AbstractVector, - TE<:Matrix{ComplexF64}, - TEA<:Array{ComplexF64,3}, + TE<:Union{AbstractMatrix,Nothing}, + TEA<:Union{AbstractArray,Nothing}, AlgT<:StochasticDiffEqAlgorithm, AT<:Real, RT<:Real, @@ -149,7 +179,11 @@ function Base.show(io::IO, sol::TimeEvolutionSSESol) print(io, "--------------------------------\n") print(io, "num_trajectories = $(sol.ntraj)\n") print(io, "num_states = $(length(sol.states[1]))\n") - print(io, "num_expect = $(size(sol.expect, 1))\n") + if sol.expect isa Nothing + print(io, "num_expect = 0\n") + else + print(io, "num_expect = $(size(sol.expect, 1))\n") + end print(io, "SDE alg.: $(sol.alg)\n") print(io, "abstol = $(sol.abstol)\n") print(io, "reltol = $(sol.reltol)\n") diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 270168dfc..650f406e0 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -131,7 +131,8 @@ function _DFDIncreaseReduceAffect!(integrator) copyto!(integrator.u, mat2vec(ρt)) # By doing this, we are assuming that the system is time-independent and f is a MatrixOperator integrator.f = ODEFunction{true,FullSpecialize}(MatrixOperator(L)) - integrator.p = merge(internal_params, (e_ops = e_ops2, dfd_ρt_cache = similar(integrator.u))) + integrator.p = merge(internal_params, (dfd_ρt_cache = similar(integrator.u),)) + _mesolve_callbacks_new_e_ops!(integrator, e_ops2) return nothing end @@ -232,7 +233,7 @@ function dfd_mesolve( kwargs..., ) - sol = solve(dfd_prob, alg) + sol = solve(dfd_prob.prob, alg) ρt = map( i -> QuantumObject( @@ -244,13 +245,13 @@ function dfd_mesolve( ) return TimeEvolutionSol( - sol.prob.p.times, + dfd_prob.times, ρt, - sol.prob.p.expvals, + _se_me_sse_get_expvals(sol), sol.retcode, sol.alg, - sol.prob.kwargs[:abstol], - sol.prob.kwargs[:reltol], + NamedTuple(sol.prob.kwargs).abstol, + NamedTuple(sol.prob.kwargs).reltol, ) end @@ -282,7 +283,6 @@ function _DSF_mesolve_Affect!(integrator) H = internal_params.H_fun c_ops = internal_params.c_ops_fun e_ops = internal_params.e_ops_fun - e_ops_vec = internal_params.e_ops dsf_cache = internal_params.dsf_cache dsf_params = internal_params.dsf_params expv_cache = internal_params.expv_cache @@ -333,8 +333,7 @@ function _DSF_mesolve_Affect!(integrator) op_l2 = op_list .+ αt_list e_ops2 = e_ops(op_l2, dsf_params) - _mat2vec_data = op -> mat2vec(get_data(op)') - @. e_ops_vec = _mat2vec_data(e_ops2) + _mesolve_callbacks_new_e_ops!(integrator, [_generate_mesolve_e_op(op) for op in e_ops2]) # By doing this, we are assuming that the system is time-independent and f is a MatrixOperator copyto!(integrator.f.f.A, liouvillian(H(op_l2, dsf_params), c_ops(op_l2, dsf_params), dsf_identity).data) return u_modified!(integrator, true) @@ -373,7 +372,6 @@ function dsf_mesolveProblem( dsf_displace_cache_full = dsf_displace_cache_left + dsf_displace_cache_left_dag + dsf_displace_cache_right + dsf_displace_cache_right_dag - params2 = params params2 = merge( params, ( @@ -489,9 +487,9 @@ end # Dynamical Shifted Fock mcsolve function _DSF_mcsolve_Condition(u, t, integrator) - internal_params = integrator.p - op_list = internal_params.op_list - δα_list = internal_params.δα_list + params = integrator.p + op_list = params.op_list + δα_list = params.δα_list ψt = u @@ -508,20 +506,24 @@ function _DSF_mcsolve_Condition(u, t, integrator) end function _DSF_mcsolve_Affect!(integrator) - internal_params = integrator.p - op_list = internal_params.op_list - αt_list = internal_params.αt_list - δα_list = internal_params.δα_list - H = internal_params.H_fun - c_ops = internal_params.c_ops_fun - e_ops = internal_params.e_ops_fun - e_ops0 = internal_params.e_ops_mc - c_ops0 = internal_params.c_ops - ψt = internal_params.dsf_cache1 - dsf_cache = internal_params.dsf_cache2 - expv_cache = internal_params.expv_cache - dsf_params = internal_params.dsf_params - dsf_displace_cache_full = internal_params.dsf_displace_cache_full + params = integrator.p + op_list = params.op_list + αt_list = params.αt_list + δα_list = params.δα_list + H = params.H_fun + c_ops = params.c_ops_fun + e_ops = params.e_ops_fun + ψt = params.dsf_cache1 + dsf_cache = params.dsf_cache2 + expv_cache = params.expv_cache + dsf_params = params.dsf_params + dsf_displace_cache_full = params.dsf_displace_cache_full + + # e_ops0 = params.e_ops + # c_ops0 = params.c_ops + + e_ops0 = _mcsolve_get_e_ops(integrator) + c_ops0, c_ops0_herm = _mcsolve_get_c_ops(integrator) copyto!(ψt, integrator.u) normalize!(ψt) @@ -561,42 +563,38 @@ function _DSF_mcsolve_Affect!(integrator) op_l2 = op_list .+ αt_list e_ops2 = e_ops(op_l2, dsf_params) c_ops2 = c_ops(op_l2, dsf_params) + + ## By copying the data, we are assuming that the variables are Vectors and not Tuple @. e_ops0 = get_data(e_ops2) @. c_ops0 = get_data(c_ops2) - H_nh = lmul!(convert(eltype(ψt), 0.5im), mapreduce(op -> op' * op, +, c_ops0)) + c_ops0_herm .= map(op -> op' * op, c_ops0) + + H_nh = convert(eltype(ψt), 0.5im) * sum(c_ops0_herm) # By doing this, we are assuming that the system is time-independent and f is a MatrixOperator copyto!(integrator.f.f.A, lmul!(-1im, H(op_l2, dsf_params).data - H_nh)) return u_modified!(integrator, true) end function _dsf_mcsolve_prob_func(prob, i, repeat) - internal_params = prob.p + params = prob.p prm = merge( - internal_params, + params, ( - e_ops_mc = deepcopy(internal_params.e_ops_mc), - c_ops = deepcopy(internal_params.c_ops), - expvals = similar(internal_params.expvals), - cache_mc = similar(internal_params.cache_mc), - weights_mc = similar(internal_params.weights_mc), - cumsum_weights_mc = similar(internal_params.weights_mc), - random_n = Ref(rand()), - progr_mc = ProgressBar(size(internal_params.expvals, 2), enable = false), - jump_times_which_idx = Ref(1), - jump_times = similar(internal_params.jump_times), - jump_which = similar(internal_params.jump_which), - αt_list = copy(internal_params.αt_list), - dsf_cache1 = similar(internal_params.dsf_cache1), - dsf_cache2 = similar(internal_params.dsf_cache2), - expv_cache = copy(internal_params.expv_cache), - dsf_displace_cache_full = deepcopy(internal_params.dsf_displace_cache_full), # This brutally copies also the MatrixOperators, and it is inefficient. + αt_list = copy(params.αt_list), + dsf_cache1 = similar(params.dsf_cache1), + dsf_cache2 = similar(params.dsf_cache2), + expv_cache = copy(params.expv_cache), + dsf_displace_cache_full = deepcopy(params.dsf_displace_cache_full), # This brutally copies also the MatrixOperators, and it is inefficient. ), ) f = deepcopy(prob.f.f) - return remake(prob, f = f, p = prm) + # We need to deepcopy the callbacks because they contain the c_ops and e_ops, which are modified in the affect function. They also contain all the cache variables needed for mcsolve. + cb = deepcopy(prob.kwargs[:callback]) + + return remake(prob, f = f, p = prm, callback = cb) end function dsf_mcsolveEnsembleProblem( @@ -731,5 +729,5 @@ function dsf_mcsolve( kwargs..., ) - return mcsolve(ens_prob_mc; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + return mcsolve(ens_prob_mc, alg, ntraj, ensemble_method) end diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index c760b4106..9695c20cd 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -26,20 +26,21 @@ sol2 = sesolve(H, ψ0, tlist, progress_bar = Val(false)) sol3 = sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) sol_string = sprint((t, s) -> show(t, "text/plain", s), sol) + sol_string2 = sprint((t, s) -> show(t, "text/plain", s), sol2) ## Analytical solution for the expectation value of a' * a Ω_rabi = sqrt(g^2 + ((ωc - ωq) / 2)^2) amp_rabi = g^2 / Ω_rabi^2 ## - @test prob.f.f isa MatrixOperator + @test prob.prob.f.f isa MatrixOperator @test sum(abs.(sol.expect[1, :] .- amp_rabi .* sin.(Ω_rabi * tlist) .^ 2)) / length(tlist) < 0.1 @test length(sol.times) == length(tlist) @test length(sol.states) == 1 @test size(sol.expect) == (length(e_ops), length(tlist)) @test length(sol2.times) == length(tlist) @test length(sol2.states) == length(tlist) - @test size(sol2.expect) == (0, length(tlist)) + @test sol2.expect === nothing @test length(sol3.times) == length(tlist) @test length(sol3.states) == length(tlist) @test size(sol3.expect) == (length(e_ops), length(tlist)) @@ -52,6 +53,15 @@ "ODE alg.: $(sol.alg)\n" * "abstol = $(sol.abstol)\n" * "reltol = $(sol.reltol)\n" + @test sol_string2 == + "Solution of time evolution\n" * + "(return code: $(sol2.retcode))\n" * + "--------------------------\n" * + "num_states = $(length(sol2.states))\n" * + "num_expect = 0\n" * + "ODE alg.: $(sol2.alg)\n" * + "abstol = $(sol2.abstol)\n" * + "reltol = $(sol2.reltol)\n" @testset "Memory Allocations" begin allocs_tot = @allocations sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) # Warm-up @@ -116,9 +126,10 @@ sol_me_string = sprint((t, s) -> show(t, "text/plain", s), sol_me) sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) + sol_mc_string_states = sprint((t, s) -> show(t, "text/plain", s), sol_mc_states) sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) - @test prob_me.f.f isa MatrixOperator - @test prob_mc.f.f isa MatrixOperator + @test prob_me.prob.f.f isa MatrixOperator + @test prob_mc.prob.f.f isa MatrixOperator @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(tlist) < 0.1 @test sum(abs.(sol_mc2.expect .- sol_me.expect)) / length(tlist) < 0.1 @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect[1, :]))) / length(tlist) < 0.1 @@ -129,14 +140,14 @@ @test size(sol_me.expect) == (length(e_ops), length(tlist)) @test length(sol_me2.times) == length(tlist) @test length(sol_me2.states) == length(tlist) - @test size(sol_me2.expect) == (0, length(tlist)) + @test sol_me2.expect === nothing @test length(sol_me3.times) == length(tlist) @test length(sol_me3.states) == length(tlist) @test size(sol_me3.expect) == (length(e_ops), length(tlist)) @test length(sol_mc.times) == length(tlist) @test size(sol_mc.expect) == (length(e_ops), length(tlist)) @test length(sol_mc_states.times) == length(tlist) - @test size(sol_mc_states.expect) == (0, length(tlist)) + @test sol_mc_states.expect === nothing @test length(sol_sse.times) == length(tlist) @test size(sol_sse.expect) == (length(e_ops), length(tlist)) @test sol_me_string == @@ -158,6 +169,16 @@ "ODE alg.: $(sol_mc.alg)\n" * "abstol = $(sol_mc.abstol)\n" * "reltol = $(sol_mc.reltol)\n" + @test sol_mc_string_states == + "Solution of quantum trajectories\n" * + "(converged: $(sol_mc_states.converged))\n" * + "--------------------------------\n" * + "num_trajectories = $(sol_mc_states.ntraj)\n" * + "num_states = $(length(sol_mc_states.states[1]))\n" * + "num_expect = 0\n" * + "ODE alg.: $(sol_mc_states.alg)\n" * + "abstol = $(sol_mc_states.abstol)\n" * + "reltol = $(sol_mc_states.reltol)\n" @test sol_sse_string == "Solution of quantum trajectories\n" * "(converged: $(sol_sse.converged))\n" * @@ -462,7 +483,7 @@ @test sol_mc1.jump_times ≈ sol_mc2.jump_times atol = 1e-10 @test sol_mc1.jump_which ≈ sol_mc2.jump_which atol = 1e-10 - @test sol_mc1.expect_all ≈ sol_mc3.expect_all[1:500, :, :] atol = 1e-10 + @test sol_mc1.expect_all ≈ sol_mc3.expect_all[:, :, 1:500] atol = 1e-10 @test sol_sse1.expect ≈ sol_sse2.expect atol = 1e-10 @test sol_sse1.expect_all ≈ sol_sse2.expect_all atol = 1e-10 From 204fd4b2affe72555b0ab873834c9c0750052e52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:08:12 +0100 Subject: [PATCH 125/329] Bump codecov/codecov-action from 4 to 5 (#312) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 66b845727..309afadea 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -75,7 +75,7 @@ jobs: - uses: julia-actions/julia-processcoverage@v1 with: directories: src,ext - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: From c90a1db98a3346d6e0468bcfbc6e89bfd407496c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:18:51 +0900 Subject: [PATCH 126/329] Update check changelog CI (#313) --- .github/workflows/ChangeLogCheck.yml | 31 +++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ChangeLogCheck.yml b/.github/workflows/ChangeLogCheck.yml index 634583251..466e81793 100644 --- a/.github/workflows/ChangeLogCheck.yml +++ b/.github/workflows/ChangeLogCheck.yml @@ -8,7 +8,32 @@ on: jobs: changelog: runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} steps: - - uses: dangoslen/changelog-enforcer@v3 - with: - skipLabels: 'Skip ChangeLog' \ No newline at end of file + # check whether CHANGELOG.md is updated + - uses: dangoslen/changelog-enforcer@v3 + with: + skipLabels: 'Skip ChangeLog' + + # check whether the format of CHANGELOG.md is correct + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - name: Install and Run Changelog + run: | + julia -e 'import Pkg; Pkg.add("Changelog")' + julia -e 'using Changelog; Changelog.generate(Changelog.CommonMark(), "CHANGELOG.md"; repo = "qutip/QuantumToolbox.jl")' + + - name: CHANGELOG Format Check + run: | + julia -e ' + output = Cmd(`git diff --name-only`) |> read |> String + if output == "" + exit(0) + else + @error "The format of CHANGELOG.md is not correct !!!" + write(stdout, "Please format it by running the following command:\n") + write(stdout, "make changelog") + exit(1) + end' From e9493a767a7ae8cb6bddec281006fc81444426ec Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:02:14 +0100 Subject: [PATCH 127/329] Make a tutorial on HPC simulations (#316) --- README.md | 2 +- docs/make.jl | 1 + docs/src/index.md | 2 +- docs/src/tutorials/cluster.md | 256 ++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 docs/src/tutorials/cluster.md diff --git a/README.md b/README.md index c82c158ac..905243c8e 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ and [Y.-T. Huang](https://github.com/ytdHuang). - **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as `QuTiP`. - **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [`DifferentialEquations.jl`](https://github.com/SciML/DifferentialEquations.jl) package. - **GPU Computing:** Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. -- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. +- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. See [this tutorial](https://qutip.org/QuantumToolbox.jl/stable/tutorials/cluster) for more information. - **Easy Extension:** Easily extend the package, taking advantage of the `Julia` language features, like multiple dispatch and metaprogramming. ## Installation diff --git a/docs/make.jl b/docs/make.jl index 70d95af79..3aca7289f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -62,6 +62,7 @@ const PAGES = [ ], "Miscellaneous Tutorials" => [ "tutorials/logo.md", + "tutorials/cluster.md", ], ], "Resources" => [ diff --git a/docs/src/index.md b/docs/src/index.md index f30f0a96d..bf4d70b12 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -39,7 +39,7 @@ features: - icon: title: Distributed Computing details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. - link: /users_guide/time_evolution/mcsolve + link: /tutorials/cluster --- ``` diff --git a/docs/src/tutorials/cluster.md b/docs/src/tutorials/cluster.md new file mode 100644 index 000000000..e14c86a28 --- /dev/null +++ b/docs/src/tutorials/cluster.md @@ -0,0 +1,256 @@ +# [Intensive parallelization on a Cluster](@id doc-tutor:Intensive-parallelization-on-a-Cluster) + +## Introduction + +In this tutorial, we will demonstrate how to seamlessly perform intensive parallelization on a cluster using the **QuantumToolbox.jl** package. Indeed, thanks to the [**Distributed.jl**](https://docs.julialang.org/en/v1/manual/distributed-computing/) and [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) packages, it is possible to parallelize on a cluster with minimal effort. The following examples are applied to a cluster with the [SLURM](https://slurm.schedmd.com/documentation.html) workload manager, but the same principles can be applied to other workload managers, as the [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) package is very versatile. + +We will consider two examples: + +1. **Parallelization of a Monte Carlo quantum trajectories** +2. **Parallelization of a Master Equation by sweeping over parameters** + +### Monte Carlo Quantum Trajectories + +Let's consider a 2-dimensional transferse field Ising model with 4x3 spins. The Hamiltonian is given by + +```math +\hat{H} = \frac{J_z}{2} \sum_{\langle i,j \rangle} \hat{\sigma}_i^z \hat{\sigma}_j^z + h_x \sum_i \hat{\sigma}_i^x \, , +``` + +where the sums are over nearest neighbors, and the collapse operators are given by + +```math +\hat{c}_i = \sqrt{\gamma} \hat{\sigma}_i^- \, . +``` + +We start by creating a file named `run.batch` with the following content: + +```bash +#!/bin/bash +#SBATCH --job-name=tutorial +#SBATCH --output=output.out +#SBATCH --account=your_account +#SBATCH --nodes=10 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=72 +#SBATCH --mem=128GB +#SBATCH --time=0:10:00 +#SBATCH --qos=parallel + +# Set PATH to include the directory of your custom Julia installation +export PATH=/home/username/.juliaup/bin:$PATH + +# Now run Julia +julia --project script.jl +``` + +where we have to replace `your_account` with the name of your account. This script will be used to submit the job to the cluster. Here, we are requesting 10 nodes with 72 threads each (720 parallel jobs). The `--time` flag specifies the maximum time that the job can run. To see all the available options, you can check the [SLURM documentation](https://slurm.schedmd.com/documentation.html). We also export the path to the custom Julia installation, which is necessary to run the script (replace `username` with your username). Finally, we run the script `script.jl` with the command `julia --project script.jl`. + +The `script.jl` contains the following content: + +```julia +using Distributed +using ClusterManagers + +const SLURM_NUM_TASKS = parse(Int, ENV["SLURM_NTASKS"]) +const SLURM_CPUS_PER_TASK = parse(Int, ENV["SLURM_CPUS_PER_TASK"]) + +exeflags = ["--project=.", "-t $SLURM_CPUS_PER_TASK"] +addprocs(SlurmManager(SLURM_NUM_TASKS); exeflags=exeflags, topology=:master_worker) + + +println("################") +println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.nthreads, 2)) threads each.") + +println("----------------") + + +println("################") + +flush(stdout) + +@everywhere begin + using QuantumToolbox + using OrdinaryDiffEq + + BLAS.set_num_threads(1) +end + +# Define lattice + +Nx = 4 +Ny = 3 +latt = Lattice(Nx = Nx, Ny = Ny) + +# Define Hamiltonian and collapse operators +Jx = 0.0 +Jy = 0.0 +Jz = 1.0 +hx = 0.2 +hy = 0.0 +hz = 0.0 +γ = 1 + +Sx = mapreduce(i->SingleSiteOperator(sigmax(), i, latt), +, 1:latt.N) +Sy = mapreduce(i->SingleSiteOperator(sigmay(), i, latt), +, 1:latt.N) +Sz = mapreduce(i->SingleSiteOperator(sigmaz(), i, latt), +, 1:latt.N) + +H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) +e_ops = [Sx, Sy, Sz] + +# Time Evolution + +ψ0 = fock(2^latt.N, 0, dims = ntuple(i->2, Val(latt.N))) + +tlist = range(0, 10, 100) + +sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj=5000, ensemble_method=EnsembleSplitThreads()) + +## + +println("FINISH!") + +rmprocs(workers()) +``` + +In this script, we first load the necessary packages for distributed computing on the cluster (`Distributed.jl` and `ClusterManagers.jl`). Thanks to the environment variables (previously defined in the SLURM script), we can define the number of tasks and the number of CPUs per task. Then, we initialize the distributed network with the `addprocs(SlurmManager(SLURM_NUM_TASKS); exeflags=exeflags, topology=:master_worker)` command. We then import the packages with the `@everywhere` macro, meaning to load them in all the workers. Moreover, in order to avoid conflicts between the multithreading of the BLAS library and the native Julia multithreading, we set the number of threads of the BLAS library to 1 with the `BLAS.set_num_threads(1)` command. More information about this can be found [here](https://docs.julialang.org/en/v1/manual/performance-tips/#man-multithreading-linear-algebra). + +With the + +```julia +println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.nthreads, 2)) threads each.") +``` + +command, we test that the distributed network is correctly initialized. The `remotecall_fetch(Threads.nthreads, 2)` command returns the number of threads of the worker with ID 2. + +We then write the main part of the script, where we define the lattice through the [`Lattice`](@ref) function. We set the parameters and define the Hamiltonian and collapse operators with the [`DissipativeIsing`](@ref) function. We also define the expectation operators `e_ops` and the initial state `ψ0`. Finally, we perform the Monte Carlo quantum trajectories with the [`mcsolve`](@ref) function. The `ensemble_method=EnsembleSplitThreads()` argument is used to parallelize the Monte Carlo quantum trajectories, by splitting the ensemble of trajectories among the workers. For a more detailed explanation of the different ensemble methods, you can check the [official documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/) of the [**DifferentialEquations.jl**](https://github.com/SciML/DifferentialEquations.jl/) package. Finally, the `rmprocs(workers())` command is used to remove the workers after the computation is finished. + +The output of the script will be printed in the `output.out` file, which contains an output similar to the following: + +``` +################ +Hello! You have 10 workers with 72 threads each. +---------------- +################ + +Progress: [==============================] 100.0% --- Elapsed Time: 0h 00m 21s (ETA: 0h 00m 00s) + +FINISH! +``` + +where we can see that the computation **lasted only 21 seconds**. + +### Master Equation by Sweeping Over Parameters + +In this example, we will consider a driven Jaynes-Cummings model, describing a two-level atom interacting with a driven cavity mode. The Hamiltonian is given by + +```math +\hat{H} = \omega_c \hat{a}^\dagger \hat{a} + \frac{\omega_q}{2} \hat{\sigma}_z + g (\hat{a} \hat{\sigma}_+ + \hat{a}^\dagger \hat{\sigma}_-) + F \cos(\omega_d t) (\hat{a} + \hat{a}^\dagger) \, , +``` + +and the collapse operators are given by + +```math +\hat{c}_1 = \sqrt{\gamma} \hat{a} \, , \quad \hat{c}_2 = \sqrt{\gamma} \hat{\sigma}_- \, . +``` + +The SLURM file is the same as before, but the `script.jl` file now contains the following content: + +```julia +using Distributed +using ClusterManagers + +const SLURM_NUM_TASKS = parse(Int, ENV["SLURM_NTASKS"]) +const SLURM_CPUS_PER_TASK = parse(Int, ENV["SLURM_CPUS_PER_TASK"]) + +exeflags = ["--project=.", "-t $SLURM_CPUS_PER_TASK"] +addprocs(SlurmManager(SLURM_NUM_TASKS); exeflags=exeflags, topology=:master_worker) + + +println("################") +println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.nthreads, 2)) threads each.") + +println("----------------") + + +println("################") + +flush(stdout) + +@everywhere begin + using QuantumToolbox + using OrdinaryDiffEq + + BLAS.set_num_threads(1) +end + +@everywhere begin + const Nc = 20 + const ωc = 1.0 + const g = 0.05 + const γ = 0.01 + const F = 0.01 + + const a = tensor(destroy(Nc), qeye(2)) + + const σm = tensor(qeye(Nc), sigmam()) + const σp = tensor(qeye(Nc), sigmap()) + + H(ωq) = ωc * a' * a + ωq * tensor(num(Nc), qeye(2)) + g * (a' * σm + a * σp) + + coef(p, t) = p.F * cos(p.ωd * t) # coefficient for the time-dependent term + + const c_ops = [sqrt(γ) * a, sqrt(γ) * σm] + const e_ops = [a' * a] +end + +# Define the ODE problem and the EnsembleProblem generation function + +@everywhere begin + ωq_list = range(ωc - 3*g, ωc + 3*g, 100) + ωd_list = range(ωc - 3*g, ωc + 3*g, 100) + + const iter = collect(Iterators.product(ωq_list, ωd_list)) + + function my_prob_func(prob, i, repeat, channel) + ωq, ωd = iter[i] + H_i = H(ωq) + H_d_i = H_i + QobjEvo(a + a', coef) # Hamiltonian with a driving term + + L = liouvillian(H_d_i, c_ops).data # Make the Liouvillian + + put!(channel, true) # Update the progress bar channel + + remake(prob, f=L, p=(F = F, ωd = ωd)) + end +end + +ωq, ωd = iter[1] +H0 = H(ωq) + QobjEvo(a + a', coef) +ψ0 = tensor(fock(Nc, 0), basis(2, 1)) # Ground State +tlist = range(0, 20 / γ, 1000) + +prob = mesolveProblem(H0, ψ0, tlist, c_ops, e_ops=e_ops, progress_bar=Val(false), params=(F = F, ωd = ωd)) + +### Just to print the progress bar +progr = ProgressBar(length(iter)) +progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) +### +ens_prob = EnsembleProblem(prob.prob, prob_func=(prob, i, repeat) -> my_prob_func(prob, i, repeat, progr_channel)) + + +@sync begin + @async while take!(progr_channel) + next!(progr) + end + + @async begin + sol = solve(ens_prob, Tsit5(), EnsembleSplitThreads(), trajectories = length(iter)) + put!(progr_channel, false) + end +end +``` + +We are using the [`mesolveProblem`](@ref) function to define the master equation problem. We added some code to manage the progress bar, which is updated through a `RemoteChannel`. The `prob_func` argument of the `EnsembleProblem` function is used to define the function that generates the problem for each iteration. The `iter` variable contains the product of the `ωq_list` and `ωd_list` lists, which are used to sweep over the parameters. The `sol = solve(ens_prob, Tsit5(), EnsembleDistributed(), trajectories=length(iter))` command is used to solve the problem with the distributed ensemble method. The output of the script will be printed in the `output.out` file, which contains an output similar to the previous example. + + From 88d38fa2a7b2b2aeb33fd75adfedfd6cb0f4a9ae Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Wed, 20 Nov 2024 00:19:00 +0100 Subject: [PATCH 128/329] Add benchmark comparison in the README file --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 905243c8e..f7348a2a8 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,12 @@ e_ops = [a_gpu' * a_gpu] sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) ``` +## Performance comparison with other packages + +Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://albertomercurio.github.io/QuantumToolbox-Benchmarks/package_comparison.html). + +![](https://raw.githubusercontent.com/albertomercurio/QuantumToolbox-Benchmarks/refs/heads/gh-pages/package_comparison_files/figure-html/cell-12-output-1.svg) + ## Contributing to QuantumToolbox.jl You are most welcome to contribute to `QuantumToolbox.jl` development by forking this repository and sending pull requests (PRs), or filing bug reports at the issues page. You can also help out with users' questions, or discuss proposed changes in the [QuTiP discussion group](https://groups.google.com/g/qutip). From 9810db4732d5f9bcefbc73ea2dfd72121b8b5e14 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:23:34 +0900 Subject: [PATCH 129/329] Fix type instability for `liouvillian` (#318) * add type stability tests for liouvillian * fix type instability and reduce memory alloc in `liouvillian` * fix type instability * minor changes * update changelog --- CHANGELOG.md | 3 +++ src/qobj/superoperators.jl | 8 ++++---- test/core-test/quantum_objects_evo.jl | 10 +++++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df5f42045..36fa7f2cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Change the parameters structure of `sesolve`, `mesolve` and `mcsolve` functions to possibly support automatic differentiation. ([#311]) +- Fix type instability and reduce extra memory allocation in `liouvillian`. ([#315], [#318]) ## [v0.21.5] (2024-11-15) @@ -27,3 +28,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 [#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 [#311]: https://github.com/qutip/QuantumToolbox.jl/issues/311 +[#315]: https://github.com/qutip/QuantumToolbox.jl/issues/315 +[#318]: https://github.com/qutip/QuantumToolbox.jl/issues/318 diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 85491c6cc..010855122 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -24,7 +24,7 @@ end ## the rest of the SciMLOperators will just use lazy tensor (and prompt a warning) _spre(A::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spre(A.A, Id)) _spre(A::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(A.λ, _spre(A.L, Id)) -_spre(A::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spre(op, Id), +, A.ops) +_spre(A::AddedOperator, Id::AbstractMatrix) = AddedOperator(map(op -> _spre(op, Id), A.ops)) function _spre(A::AbstractSciMLOperator, Id::AbstractMatrix) _lazy_tensor_warning("spre", A) return kron(Id, A) @@ -32,7 +32,7 @@ end _spost(B::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spost(B.A, Id)) _spost(B::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(B.λ, _spost(B.L, Id)) -_spost(B::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spost(op, Id), +, B.ops) +_spost(B::AddedOperator, Id::AbstractMatrix) = AddedOperator(map(op -> _spost(op, Id), B.ops)) function _spost(B::AbstractSciMLOperator, Id::AbstractMatrix) _lazy_tensor_warning("spost", B) return kron(transpose(B), Id) @@ -43,7 +43,7 @@ _liouvillian(H::MT, Id::AbstractMatrix) where {MT<:Union{AbstractMatrix,Abstract -1im * (_spre(H, Id) - _spost(H, Id)) _liouvillian(H::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_liouvillian(H.A, Id)) _liouvillian(H::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(H.λ, _liouvillian(H.L, Id)) -_liouvillian(H::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _liouvillian(op, Id), +, H.ops) +_liouvillian(H::AddedOperator, Id::AbstractMatrix) = AddedOperator(map(op -> _liouvillian(op, Id), H.ops)) # intrinsic lindblad_dissipator function _lindblad_dissipator(O::MT, Id::AbstractMatrix) where {MT<:Union{AbstractMatrix,AbstractSciMLOperator}} @@ -166,7 +166,7 @@ function liouvillian( ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, Id_cache) if !(c_ops isa Nothing) - # sum all the Qobj first + # sum all the (time-independent) c_ops first c_ops_ti = filter(op -> isa(op, QuantumObject), c_ops) if !isempty(c_ops_ti) L += mapreduce(op -> lindblad_dissipator(op, Id_cache), +, c_ops_ti) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index fe7760b58..d714abb6e 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -213,7 +213,15 @@ @test_throws ArgumentError cache_operator(L_td, ψ) @testset "Type Inference" begin - @inferred liouvillian(H_td, (a, QobjEvo(a', coef1))) + # we use destroy and create here because they somehow causes type instability before + H_td2 = H_td + QobjEvo(destroy(N) + create(N), coef3) + c_ops1 = (destroy(N), create(N)) + c_ops2 = (destroy(N), QobjEvo(create(N), coef1)) + + @inferred liouvillian(H_td, c_ops1) + @inferred liouvillian(H_td, c_ops2) + @inferred liouvillian(H_td2, c_ops1) + @inferred liouvillian(H_td2, c_ops2) end end end From 9d1cb6b183751df92f34d0ac032f5beb43b12ca8 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:54:50 +0100 Subject: [PATCH 130/329] Bump to v0.22.0 (#319) * Bump to v0.22.0 * Add Unreleased section --- CHANGELOG.md | 3 +++ Project.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36fa7f2cf..daa4b6a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [v0.22.0] (2024-11-20) + - Change the parameters structure of `sesolve`, `mesolve` and `mcsolve` functions to possibly support automatic differentiation. ([#311]) - Fix type instability and reduce extra memory allocation in `liouvillian`. ([#315], [#318]) @@ -24,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [v0.21.4]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.4 [v0.21.5]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.5 +[v0.22.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.22.0 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 [#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 diff --git a/Project.toml b/Project.toml index 97827fd11..b90e80a89 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.21.5" +version = "0.22.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From ab66af58a6be14debf8c58f56bb1ebafc59127de Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:56:22 +0900 Subject: [PATCH 131/329] extend methods for `_FType` and `_CType` (#320) --- src/utilities.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 5a426bd17..3e967c2d7 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -162,12 +162,16 @@ _FType(::Type{Int32}) = Float32 _FType(::Type{Int64}) = Float64 _FType(::Type{Float32}) = Float32 _FType(::Type{Float64}) = Float64 -_FType(::Type{ComplexF32}) = Float32 -_FType(::Type{ComplexF64}) = Float64 +_FType(::Type{Complex{Int32}}) = Float32 +_FType(::Type{Complex{Int64}}) = Float64 +_FType(::Type{Complex{Float32}}) = Float32 +_FType(::Type{Complex{Float64}}) = Float64 _CType(::AbstractArray{T}) where {T<:Number} = _CType(T) _CType(::Type{Int32}) = ComplexF32 _CType(::Type{Int64}) = ComplexF64 _CType(::Type{Float32}) = ComplexF32 _CType(::Type{Float64}) = ComplexF64 -_CType(::Type{ComplexF32}) = ComplexF32 -_CType(::Type{ComplexF64}) = ComplexF64 +_CType(::Type{Complex{Int32}}) = ComplexF32 +_CType(::Type{Complex{Int64}}) = ComplexF64 +_CType(::Type{Complex{Float32}}) = ComplexF32 +_CType(::Type{Complex{Float64}}) = ComplexF64 From 3bb97e6263c3919e73c1a42e7c8d71b23f597ff1 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 28 Nov 2024 08:01:51 +0100 Subject: [PATCH 132/329] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f7348a2a8..c14be2c1a 100644 --- a/README.md +++ b/README.md @@ -167,9 +167,9 @@ sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) ## Performance comparison with other packages -Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://albertomercurio.github.io/QuantumToolbox-Benchmarks/package_comparison.html). +Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://albertomercurio.github.io/Lectures/QuantumToolbox.jl/package_comparison.html). -![](https://raw.githubusercontent.com/albertomercurio/QuantumToolbox-Benchmarks/refs/heads/gh-pages/package_comparison_files/figure-html/cell-12-output-1.svg) +![](https://albertomercurio.github.io/Lectures/QuantumToolbox.jl/package_comparison_files/figure-html/cell-12-output-1.svg) ## Contributing to QuantumToolbox.jl From aee73e6065aa043f6cb81999e01d82b476fe3e3c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 30 Nov 2024 00:04:56 +0900 Subject: [PATCH 133/329] fix latex in docstrings (#323) --- src/correlations.jl | 12 ++++++------ src/metrics.jl | 2 +- src/qobj/functions.jl | 2 +- src/qobj/operators.jl | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/correlations.jl b/src/correlations.jl index cbec3da95..efc9fcaa2 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -23,7 +23,7 @@ ExponentialSeries(; tol = 1e-14, calc_steadystate = false) = ExponentialSeries(t c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; kwargs...) -Returns the two-times correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\expval{\hat{A}(t) \hat{B}(t + \tau) \hat{C}(t)}`` +Returns the two-times correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \hat{C}(t) \right\rangle`` for a given initial state ``\ket{\psi_0}``. """ @@ -69,9 +69,9 @@ end kwargs...) Returns the two-times correlation function of two operators ``\hat{A}`` and ``\hat{B}`` -at different times: ``\expval{\hat{A}(t + \tau) \hat{B}(t)}``. +at different times: ``\left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle``. -When `reverse=true`, the correlation function is calculated as ``\expval{\hat{A}(t) \hat{B}(t + \tau)}``. +When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \right\rangle``. """ function correlation_2op_2t( H::QuantumObject{<:AbstractArray{T1},HOpType}, @@ -111,9 +111,9 @@ end reverse::Bool=false, kwargs...) -Returns the one-time correlation function of two operators ``\hat{A}`` and ``\hat{B}`` at different times ``\expval{\hat{A}(\tau) \hat{B}(0)}``. +Returns the one-time correlation function of two operators ``\hat{A}`` and ``\hat{B}`` at different times ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle``. -When `reverse=true`, the correlation function is calculated as ``\expval{\hat{A}(0) \hat{B}(\tau)}``. +When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(0) \hat{B}(\tau) \right\rangle``. """ function correlation_2op_1t( H::QuantumObject{<:AbstractArray{T1},HOpType}, @@ -149,7 +149,7 @@ end Returns the emission spectrum ```math -S(\omega) = \int_{-\infty}^\infty \expval{\hat{A}(\tau) \hat{B}(0)} e^{-i \omega \tau} d \tau +S(\omega) = \int_{-\infty}^\infty \left\langle \hat{A}(\tau) \hat{B}(0)} e^{-i \omega \tau \right\rangle d \tau ``` """ function spectrum( diff --git a/src/metrics.jl b/src/metrics.jl index 0e2e5fd24..879d9ea7d 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -8,7 +8,7 @@ export entropy_vn, entanglement, tracedist, fidelity entropy_vn(ρ::QuantumObject; base::Int=0, tol::Real=1e-15) Calculates the [Von Neumann entropy](https://en.wikipedia.org/wiki/Von_Neumann_entropy) -``S = - \Tr \left[ \hat{\rho} \log \left( \hat{\rho} \right) \right]`` where ``\hat{\rho}`` +``S = - \textrm{Tr} \left[ \hat{\rho} \log \left( \hat{\rho} \right) \right]`` where ``\hat{\rho}`` is the density matrix of the system. The `base` parameter specifies the base of the logarithm to use, and when using the default value 0, diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 1cc463913..846316881 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -10,7 +10,7 @@ export vec2mat, mat2vec @doc raw""" ket2dm(ψ::QuantumObject) -Transform the ket state ``\ket{\psi}`` into a pure density matrix ``\hat{\rho} = \dyad{\psi}``. +Transform the ket state ``\ket{\psi}`` into a pure density matrix ``\hat{\rho} = |\psi\rangle\langle\psi|``. """ ket2dm(ψ::QuantumObject{<:AbstractArray{T},KetQuantumObject}) where {T} = ψ * ψ' diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index b2036dce7..ccb989dee 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -408,7 +408,7 @@ sigmay() = rmul!(jmat(0.5, Val(:y)), 2) @doc raw""" sigmaz() -Pauli operator ``\hat{\sigma}_z = \comm{\hat{\sigma}_+}{\hat{\sigma}_-}``. +Pauli operator ``\hat{\sigma}_z = \left[ \hat{\sigma}_+ , \hat{\sigma}_- \right]``. See also [`jmat`](@ref). """ @@ -487,7 +487,7 @@ end @doc raw""" projection(N::Int, i::Int, j::Int) -Generates the projection operator ``\hat{O} = \dyad{i}{j}`` with Hilbert space dimension `N`. +Generates the projection operator ``\hat{O} = |i \rangle\langle j|`` with Hilbert space dimension `N`. """ projection(N::Int, i::Int, j::Int) = QuantumObject(sparse([i + 1], [j + 1], [1.0 + 0.0im], N, N), type = Operator) From b314479ce4e49b52bc25a4816f0d0ba67054ce83 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:05:29 +0100 Subject: [PATCH 134/329] Change SingleSiteOperator with MultiSiteOperator (#324) * Change SingleSiteOperator with MultiSiteOperator * Update changelog --- CHANGELOG.md | 3 ++ docs/src/resources/api.md | 2 +- docs/src/tutorials/cluster.md | 8 +-- docs/src/tutorials/lowrank.md | 10 ++-- src/spin_lattice.jl | 79 +++++++++++++++++++++++------ test/core-test/low_rank_dynamics.jl | 12 ++--- 6 files changed, 82 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daa4b6a13..9c59465b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324]) + ## [v0.22.0] (2024-11-20) - Change the parameters structure of `sesolve`, `mesolve` and `mcsolve` functions to possibly support automatic differentiation. ([#311]) @@ -33,3 +35,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#311]: https://github.com/qutip/QuantumToolbox.jl/issues/311 [#315]: https://github.com/qutip/QuantumToolbox.jl/issues/315 [#318]: https://github.com/qutip/QuantumToolbox.jl/issues/318 +[#324]: https://github.com/qutip/QuantumToolbox.jl/issues/324 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 0b0c37a59..d19c4b86c 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -248,7 +248,7 @@ fidelity ```@docs Lattice -SingleSiteOperator +MultiSiteOperator DissipativeIsing ``` diff --git a/docs/src/tutorials/cluster.md b/docs/src/tutorials/cluster.md index e14c86a28..5bd752b9c 100644 --- a/docs/src/tutorials/cluster.md +++ b/docs/src/tutorials/cluster.md @@ -11,7 +11,7 @@ We will consider two examples: ### Monte Carlo Quantum Trajectories -Let's consider a 2-dimensional transferse field Ising model with 4x3 spins. The Hamiltonian is given by +Let's consider a 2-dimensional transverse field Ising model with 4x3 spins. The Hamiltonian is given by ```math \hat{H} = \frac{J_z}{2} \sum_{\langle i,j \rangle} \hat{\sigma}_i^z \hat{\sigma}_j^z + h_x \sum_i \hat{\sigma}_i^x \, , @@ -91,9 +91,9 @@ hy = 0.0 hz = 0.0 γ = 1 -Sx = mapreduce(i->SingleSiteOperator(sigmax(), i, latt), +, 1:latt.N) -Sy = mapreduce(i->SingleSiteOperator(sigmay(), i, latt), +, 1:latt.N) -Sz = mapreduce(i->SingleSiteOperator(sigmaz(), i, latt), +, 1:latt.N) +Sx = mapreduce(i -> MultiSiteOperator(latt, i=>sigmax()), +, 1:latt.N) +Sy = mapreduce(i -> MultiSiteOperator(latt, i=>sigmay()), +, 1:latt.N) +Sz = mapreduce(i -> MultiSiteOperator(latt, i=>sigmaz()), +, 1:latt.N) H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) e_ops = [Sx, Sy, Sz] diff --git a/docs/src/tutorials/lowrank.md b/docs/src/tutorials/lowrank.md index 59e292bfd..d70846008 100644 --- a/docs/src/tutorials/lowrank.md +++ b/docs/src/tutorials/lowrank.md @@ -47,12 +47,12 @@ Define lr states. Take as initial state all spins up. All other N states are tak i = 1 for j in 1:N_modes global i += 1 - i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), j, latt) * ϕ[1]) + i <= M && (ϕ[i] = MultiSiteOperator(latt, j=>sigmap()) * ϕ[1]) end for k in 1:N_modes-1 for l in k+1:N_modes global i += 1 - i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), k, latt) * SingleSiteOperator(sigmap(), l, latt) * ϕ[1]) + i <= M && (ϕ[i] = MultiSiteOperator(latt, k=>sigmap(), l=>sigmap()) * ϕ[1]) end end for i in i+1:M @@ -85,9 +85,9 @@ hy = 0.0 hz = 0.0 γ = 1 -Sx = mapreduce(i->SingleSiteOperator(sigmax(), i, latt), +, 1:latt.N) -Sy = mapreduce(i->SingleSiteOperator(sigmay(), i, latt), +, 1:latt.N) -Sz = mapreduce(i->SingleSiteOperator(sigmaz(), i, latt), +, 1:latt.N) +Sx = mapreduce(i->MultiSiteOperator(latt, i=>sigmax()), +, 1:latt.N) +Sy = mapreduce(i->MultiSiteOperator(latt, i=>sigmay()), +, 1:latt.N) +Sz = mapreduce(i->MultiSiteOperator(latt, i=>sigmaz()), +, 1:latt.N) H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) e_ops = (Sx, Sy, Sz) diff --git a/src/spin_lattice.jl b/src/spin_lattice.jl index 192422e72..9ce0094a3 100644 --- a/src/spin_lattice.jl +++ b/src/spin_lattice.jl @@ -1,4 +1,4 @@ -export Lattice, SingleSiteOperator, DissipativeIsing +export Lattice, MultiSiteOperator, DissipativeIsing @doc raw""" Lattice @@ -15,20 +15,60 @@ end #Definition of many-body operators @doc raw""" - SingleSiteOperator(O::QuantumObject, i::Integer, N::Integer) + MultiSiteOperator(dims::Union{AbstractVector, Tuple}, pairs::Pair{<:Integer,<:QuantumObject}...) -A Julia constructor for a single-site operator. `s` is the operator acting on the site. `i` is the site index, and `N` is the total number of sites. The function returns a `QuantumObject` given by ``\\mathbb{1}^{\\otimes (i - 1)} \\otimes \hat{O} \\otimes \\mathbb{1}^{\\otimes (N - i)}``. +A Julia function for generating a multi-site operator ``\\hat{O} = \\hat{O}_i \\hat{O}_j \\cdots \\hat{O}_k``. The function takes a vector of dimensions `dims` and a list of pairs `pairs` where the first element of the pair is the site index and the second element is the operator acting on that site. + +# Arguments +- `dims::Union{AbstractVector, Tuple}`: A vector of dimensions of the lattice. +- `pairs::Pair{<:Integer,<:QuantumObject}...`: A list of pairs where the first element of the pair is the site index and the second element is the operator acting on that site. + +# Returns +`QuantumObject`: A `QuantumObject` representing the multi-site operator. + +# Example +```jldoctest +julia> op = MultiSiteOperator(Val(8), 5=>sigmax(), 7=>sigmaz()); + +julia> op.dims +8-element SVector{8, Int64} with indices SOneTo(8): + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 +``` """ -function SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, i::Integer, N::Integer) where {DT} - T = O.dims[1] - return QuantumObject(kron(eye(T^(i - 1)), O, eye(T^(N - i))); dims = ntuple(j -> 2, Val(N))) +function MultiSiteOperator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Integer,<:QuantumObject}...) + sites_unsorted = collect(first.(pairs)) + idxs = sortperm(sites_unsorted) + _sites = sites_unsorted[idxs] + _ops = collect(last.(pairs))[idxs] + _dims = collect(dims) # Use this instead of a Tuple, to avoid type instability when indexing on a slice + + sites, ops = _get_unique_sites_ops(_sites, _ops) + + collect(dims)[sites] == [op.dims[1] for op in ops] || throw(ArgumentError("The dimensions of the operators do not match the dimensions of the lattice.")) + + data = kron(I(prod(_dims[1:sites[1]-1])), ops[1].data) + for i in 2:length(sites) + data = kron(data, I(prod(_dims[sites[i-1]+1:sites[i]-1])), ops[i].data) + end + data = kron(data, I(prod(_dims[sites[end]+1:end]))) + + return QuantumObject(data; type = Operator, dims = dims) +end +function MultiSiteOperator(N::Union{Integer,Val}, pairs::Pair{<:Integer,<:QuantumObject}...) + dims = ntuple(j -> 2, makeVal(N)) + + return MultiSiteOperator(dims, pairs...) +end +function MultiSiteOperator(latt::Lattice, pairs::Pair{<:Integer,<:QuantumObject}...) + return MultiSiteOperator(makeVal(latt.N), pairs...) end -SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, i::Integer, latt::Lattice) where {DT} = - SingleSiteOperator(O, i, latt.N) -SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, row::Integer, col::Integer, latt::Lattice) where {DT} = - SingleSiteOperator(O, latt.idx[row, col], latt.N) -SingleSiteOperator(O::QuantumObject{DT,OperatorQuantumObject}, x::CartesianIndex, latt::Lattice) where {DT} = - SingleSiteOperator(O, latt.idx[x], latt.N) #Definition of nearest-neighbour sites on lattice periodic_boundary_conditions(i::Integer, N::Integer) = 1 + (i - 1 + N) % N @@ -87,7 +127,7 @@ function DissipativeIsing( boundary_condition::Union{Symbol,Val} = Val(:periodic_bc), order::Integer = 1, ) - S = [SingleSiteOperator(sigmam(), i, latt) for i in 1:latt.N] + S = [MultiSiteOperator(latt, i => sigmam()) for i in 1:latt.N] c_ops = sqrt(γ) .* S op_sum(S, i::CartesianIndex) = @@ -95,19 +135,26 @@ function DissipativeIsing( H = 0 if (Jx != 0 || hx != 0) - S = [SingleSiteOperator(sigmax(), i, latt) for i in 1:latt.N] + S = [MultiSiteOperator(latt, i => sigmax()) for i in 1:latt.N] H += Jx / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) #/2 because we are double counting H += hx * sum(S) end if (Jy != 0 || hy != 0) - S = [SingleSiteOperator(sigmay(), i, latt) for i in 1:latt.N] + S = [MultiSiteOperator(latt, i => sigmay()) for i in 1:latt.N] H += Jy / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) H += hy * sum(S) end if (Jz != 0 || hz != 0) - S = [SingleSiteOperator(sigmaz(), i, latt) for i in 1:latt.N] + S = [MultiSiteOperator(latt, i => sigmaz()) for i in 1:latt.N] H += Jz / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) H += hz * sum(S) end return H, c_ops end + +function _get_unique_sites_ops(sites, ops) + unique_sites = unique(sites) + unique_ops = map(i -> prod(ops[findall(==(i), sites)]), unique_sites) + + return unique_sites, unique_ops +end diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index 2da7dc686..ef767f304 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -15,12 +15,12 @@ i = 1 for j in 1:N_modes i += 1 - i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), j, latt) * ϕ[1]) + i <= M && (ϕ[i] = MultiSiteOperator(latt, j => sigmap()) * ϕ[1]) end for k in 1:N_modes-1 for l in k+1:N_modes i += 1 - i <= M && (ϕ[i] = SingleSiteOperator(sigmap(), k, latt) * SingleSiteOperator(sigmap(), l, latt) * ϕ[1]) + i <= M && (ϕ[i] = MultiSiteOperator(latt, k => sigmap(), l => sigmap()) * ϕ[1]) end end for i in i+1:M @@ -43,11 +43,11 @@ hz = 0.0 γ = 1 - Sx = mapreduce(i -> SingleSiteOperator(sigmax(), i, latt), +, 1:latt.N) - Sy = mapreduce(i -> SingleSiteOperator(sigmay(), i, latt), +, 1:latt.N) - Sz = mapreduce(i -> SingleSiteOperator(sigmaz(), i, latt), +, 1:latt.N) + Sx = mapreduce(i -> MultiSiteOperator(latt, i => sigmax()), +, 1:latt.N) + Sy = mapreduce(i -> MultiSiteOperator(latt, i => sigmay()), +, 1:latt.N) + Sz = mapreduce(i -> MultiSiteOperator(latt, i => sigmaz()), +, 1:latt.N) SFxx = mapreduce( - x -> SingleSiteOperator(sigmax(), x[1], latt) * SingleSiteOperator(sigmax(), x[2], latt), + x -> MultiSiteOperator(latt, x[1] => sigmax(), x[2] => sigmax()), +, Iterators.product(1:latt.N, 1:latt.N), ) From 9a9a80d7593c46d60607de3daa3cfe1c31ed0168 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 30 Nov 2024 00:52:05 +0900 Subject: [PATCH 135/329] fix typo in `spectrum` docstrings (#325) --- src/correlations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/correlations.jl b/src/correlations.jl index efc9fcaa2..ebd3c4666 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -149,7 +149,7 @@ end Returns the emission spectrum ```math -S(\omega) = \int_{-\infty}^\infty \left\langle \hat{A}(\tau) \hat{B}(0)} e^{-i \omega \tau \right\rangle d \tau +S(\omega) = \int_{-\infty}^\infty \left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle e^{-i \omega \tau} d \tau ``` """ function spectrum( From e56afac54b56e6878244d01da5f3eedd939b7889 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 30 Nov 2024 14:19:32 +0900 Subject: [PATCH 136/329] dummy change for benchmark CI --- .github/workflows/Benchmarks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Benchmarks.yml b/.github/workflows/Benchmarks.yml index e73652ebd..736111387 100644 --- a/.github/workflows/Benchmarks.yml +++ b/.github/workflows/Benchmarks.yml @@ -49,6 +49,7 @@ jobs: Pkg.instantiate(); include("runbenchmarks.jl")' + # this will update benchmarks/data.js in gh-pages branch - name: Parse & Upload Benchmark Results uses: benchmark-action/github-action-benchmark@v1 with: From 0243bfa5c9b53d71e3a32fb3e896b7a2ba296a26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 05:55:23 +0000 Subject: [PATCH 137/329] Bump crate-ci/typos from 1.27.3 to 1.28.1 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.27.3 to 1.28.1. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/v1.27.3...v1.28.1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 3f3de3604..40772cfdb 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.27.3 \ No newline at end of file + uses: crate-ci/typos@v1.28.1 \ No newline at end of file From cfe79439bac2327f5ae85be0db08da48c5b20eaf Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:08:55 +0900 Subject: [PATCH 138/329] Make `spectrum` and `correlation` align with QuTiP (#330) * remove `MySolver` and don't return frequency list from `spectrum` * remove `FFTCorrelation` and introduce `spectrum_correlation_fft` * format files * introduce `PseudoInverse<:SpectrumSolver` * update changelog * fix benchmark * introduce `correlation_3op_1t` * format files * fix missing links in docstring of `correlation` functions * update documentation page for two-time correlation function * update doc page of two-time correlation functions * make the output correlation `Matrix` align with `QuTiP` * minor changes * add deprecated methods to `deprecated.jl` * update changelog * improve runtests for `spectrum_correlation_fft` --- CHANGELOG.md | 2 + benchmarks/correlations_and_spectrum.jl | 17 +- docs/make.jl | 3 +- docs/src/resources/api.md | 4 + docs/src/resources/bibliography.bib | 10 + docs/src/users_guide/two_time_corr_func.md | 266 +++++++++++++++++++ src/QuantumToolbox.jl | 3 +- src/correlations.jl | 278 ++++++++------------ src/deprecated.jl | 90 +++++++ src/spectrum.jl | 177 +++++++++++++ test/core-test/correlations_and_spectrum.jl | 48 +++- 11 files changed, 718 insertions(+), 180 deletions(-) create mode 100644 docs/src/users_guide/two_time_corr_func.md create mode 100644 src/spectrum.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c59465b1..1363bc0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324]) +- Make `spectrum` and `correlation` functions align with `Python QuTiP`, introduce spectrum solver `PseudoInverse`, remove spectrum solver `FFTCorrelation`, and introduce `spectrum_correlation_fft`. ([#330]) ## [v0.22.0] (2024-11-20) @@ -36,3 +37,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#315]: https://github.com/qutip/QuantumToolbox.jl/issues/315 [#318]: https://github.com/qutip/QuantumToolbox.jl/issues/318 [#324]: https://github.com/qutip/QuantumToolbox.jl/issues/324 +[#330]: https://github.com/qutip/QuantumToolbox.jl/issues/330 diff --git a/benchmarks/correlations_and_spectrum.jl b/benchmarks/correlations_and_spectrum.jl index a5eab65a8..04f63997f 100644 --- a/benchmarks/correlations_and_spectrum.jl +++ b/benchmarks/correlations_and_spectrum.jl @@ -1,3 +1,9 @@ +function _calculate_fft_spectrum(H, tlist, c_ops, A, B) + corr = correlation_2op_1t(H, nothing, tlist, c_ops, A, B; progress_bar = Val(false)) + ωlist, spec = spectrum_correlation_fft(tlist, corr) + return nothing +end + function benchmark_correlations_and_spectrum!(SUITE) N = 15 ω = 1 @@ -9,11 +15,18 @@ function benchmark_correlations_and_spectrum!(SUITE) c_ops = [sqrt(γ * (nth + 1)) * a, sqrt(γ * nth) * a'] ω_l = range(0, 3, length = 1000) + t_l = range(0, 333 * π, length = 1000) + + PI_solver = PseudoInverse() SUITE["Correlations and Spectrum"]["FFT Correlation"] = - @benchmarkable spectrum($H, $ω_l, $(a'), $a, $c_ops, solver = FFTCorrelation(), progress_bar = false) + @benchmarkable _calculate_fft_spectrum($H, $t_l, $c_ops, $(a'), $a) + + SUITE["Correlations and Spectrum"]["Spectrum"]["Exponential Series"] = + @benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a) - SUITE["Correlations and Spectrum"]["Exponential Series"] = @benchmarkable spectrum($H, $ω_l, $(a'), $a, $c_ops) + SUITE["Correlations and Spectrum"]["Spectrum"]["Pseudo Inverse"] = + @benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $PI_solver) return nothing end diff --git a/docs/make.jl b/docs/make.jl index 3aca7289f..5512e8621 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -50,8 +50,7 @@ const PAGES = [ "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", ], "Solving for Steady-State Solutions" => "users_guide/steadystate.md", - "Symmetries" => [], - "Two-time correlation functions" => [], + "Two-time correlation functions" => "users_guide/two_time_corr_func.md", "Extensions" => [ "users_guide/extensions/cuda.md", ], diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index d19c4b86c..d20fc6805 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -230,9 +230,13 @@ lr_mesolve ```@docs correlation_3op_2t +correlation_3op_1t correlation_2op_2t correlation_2op_1t +spectrum_correlation_fft spectrum +ExponentialSeries +PseudoInverse ``` ## [Metrics](@id doc-API:Metrics) diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index d83932eed..3cddf83b5 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -1,3 +1,13 @@ +@book{Gardiner-Zoller2004, + title = {Quantum Noise}, + ISBN = {9783540223016}, + url = {https://link.springer.com/book/9783540223016}, + publisher = {Springer Berlin, Heidelberg}, + author = {Gardiner, Crispin and Zoller, Peter}, + year = {2004}, + month = aug +} + @book{Nielsen-Chuang2011, title = {Quantum Computation and Quantum Information: 10th Anniversary Edition}, ISBN = {9780511976667}, diff --git a/docs/src/users_guide/two_time_corr_func.md b/docs/src/users_guide/two_time_corr_func.md new file mode 100644 index 000000000..b3ad59730 --- /dev/null +++ b/docs/src/users_guide/two_time_corr_func.md @@ -0,0 +1,266 @@ +# [Two-time Correlation Functions](@id doc:Two-time-Correlation-Functions) + +## Introduction + +With the `QuantumToolbox.jl` time-evolution function [`mesolve`](@ref), a state vector ([`Ket`](@ref)) or density matrix ([`Operator`](@ref)) can be evolved from an initial state at ``t_0`` to an arbitrary time ``t``, namely + +```math +\hat{\rho}(t) = \mathcal{G}(t, t_0)\{\hat{\rho}(t_0)\}, +``` +where ``\mathcal{G}(t, t_0)\{\cdot\}`` is the propagator defined by the equation of motion. The resulting density matrix can then be used to evaluate the expectation values of arbitrary combinations of same-time operators. + +To calculate two-time correlation functions on the form ``\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle``, we can use the quantum regression theorem [see, e.g., [Gardiner-Zoller2004](@cite)] to write + +```math +\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B}\hat{\rho}(t)\} \right] = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B} \mathcal{G}(t, 0)\{\hat{\rho}(0)\}\} \right], +``` + +We therefore first calculate ``\hat{\rho}(t) = \mathcal{G}(t, 0)\{\hat{\rho}(0)\}`` using [`mesolve`](@ref) with ``\hat{\rho}(0)`` as initial state, and then again use [`mesolve`](@ref) to calculate ``\mathcal{G}(t+\tau, t)\{\hat{B}\hat{\rho}(t)\}`` using ``\hat{B}\hat{\rho}(t)`` as initial state. + +Note that if the initial state is the steady state, then ``\hat{\rho}(t) = \mathcal{G}(t, 0)\{\hat{\rho}_{\textrm{ss}}\} = \hat{\rho}_{\textrm{ss}}`` and + +```math +\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B}\hat{\rho}_{\textrm{ss}}\} \right] = \textrm{Tr} \left[\hat{A} \mathcal{G}(\tau, 0)\{\hat{B} \hat{\rho}_{\textrm{ss}}\} \right] = \left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle, +``` +which is independent of ``t``, so that we only have one time coordinate ``\tau``. + +`QuantumToolbox.jl` provides a family of functions that assists in the process of calculating two-time correlation functions. The available functions and their usage is shown in the table below. + +| **Function call** | **Correlation function** | +|:------------------|:-------------------------| +| [`correlation_2op_2t`](@ref) | ``\left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle`` or ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \right\rangle`` | +| [`correlation_2op_1t`](@ref) | ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle`` or ``\left\langle \hat{A}(0) \hat{B}(\tau) \right\rangle`` | +| [`correlation_3op_1t`](@ref) | ``\left\langle \hat{A}(0) \hat{B}(\tau) \hat{C}(0) \right\rangle`` | +| [`correlation_3op_2t`](@ref) | ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \hat{C}(t) \right\rangle`` | + +The most common used case is to calculate the two time correlation function ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle``, which can be done by [`correlation_2op_1t`](@ref). + +```@setup correlation_and_spectrum +using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) +``` + +## Steadystate correlation function + +The following code demonstrates how to calculate the ``\langle \hat{x}(t) \hat{x}(0)\rangle`` correlation for a leaky cavity with three different relaxation rates ``\gamma``. + +```@example correlation_and_spectrum +tlist = LinRange(0, 10, 200) +a = destroy(10) +x = a' + a +H = a' * a + +# if the initial state is specified as `nothing`, the steady state will be calculated and used as the initial state. +corr1 = correlation_2op_1t(H, nothing, tlist, [sqrt(0.5) * a], x, x) +corr2 = correlation_2op_1t(H, nothing, tlist, [sqrt(1.0) * a], x, x) +corr3 = correlation_2op_1t(H, nothing, tlist, [sqrt(2.0) * a], x, x) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = L"Time $t$", ylabel = L"\langle \hat{x}(t) \hat{x}(0) \rangle") +lines!(ax, tlist, real(corr1), label = L"\gamma = 0.5", linestyle = :solid) +lines!(ax, tlist, real(corr2), label = L"\gamma = 1.0", linestyle = :dash) +lines!(ax, tlist, real(corr3), label = L"\gamma = 2.0", linestyle = :dashdot) + +axislegend(ax, position = :rt) + +fig +``` + +## Emission spectrum + +Given a correlation function ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle``, we can define the corresponding power spectrum as + +```math +S(\omega) = \int_{-\infty}^\infty \left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle e^{-i \omega \tau} d \tau +``` + +In `QuantumToolbox.jl`, we can calculate ``S(\omega)`` using either [`spectrum`](@ref), which provides several solvers to perform the Fourier transform semi-analytically, or we can use the function [`spectrum_correlation_fft`](@ref) to numerically calculate the fast Fourier transform (FFT) of a given correlation data. + +The following example demonstrates how these methods can be used to obtain the emission (``\hat{A} = \hat{a}^\dagger`` and ``\hat{B} = \hat{a}``) power spectrum. + +```@example correlation_and_spectrum +N = 4 # number of cavity fock states +ωc = 1.0 * 2 * π # cavity frequency +ωa = 1.0 * 2 * π # atom frequency +g = 0.1 * 2 * π # coupling strength +κ = 0.75 # cavity dissipation rate +γ = 0.25 # atom dissipation rate + +# Jaynes-Cummings Hamiltonian +a = tensor(destroy(N), qeye(2)) +sm = tensor(qeye(N), destroy(2)) +H = ωc * a' * a + ωa * sm' * sm + g * (a' * sm + a * sm') + +# collapse operators +n_th = 0.25 +c_ops = [ + sqrt(κ * (1 + n_th)) * a, + sqrt(κ * n_th) * a', + sqrt(γ) * sm, +]; + +# calculate the correlation function using mesolve, and then FFT to obtain the spectrum. +# Here we need to make sure to evaluate the correlation function for a sufficient long time and +# sufficiently high sampling rate so that FFT captures all the features in the resulting spectrum. +tlist = LinRange(0, 100, 5000) +corr = correlation_2op_1t(H, nothing, tlist, c_ops, a', a; progress_bar = Val(false)) +ωlist1, spec1 = spectrum_correlation_fft(tlist, corr) + +# calculate the power spectrum using spectrum +# using Exponential Series (default) method +ωlist2 = LinRange(0.25, 1.75, 200) * 2 * π +spec2 = spectrum(H, ωlist2, c_ops, a', a; solver = ExponentialSeries()) + +# calculate the power spectrum using spectrum +# using Pseudo-Inverse method +spec3 = spectrum(H, ωlist2, c_ops, a', a; solver = PseudoInverse()) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], title = "Vacuum Rabi splitting", xlabel = "Frequency", ylabel = "Emission power spectrum") +lines!(ax, ωlist1 / (2 * π), spec1, label = "mesolve + FFT", linestyle = :solid) +lines!(ax, ωlist2 / (2 * π), spec2, label = "Exponential Series", linestyle = :dash) +lines!(ax, ωlist2 / (2 * π), spec3, label = "Pseudo-Inverse", linestyle = :dashdot) + +xlims!(ax, ωlist2[1] / (2 * π), ωlist2[end] / (2 * π)) +axislegend(ax, position = :rt) + +fig +``` + +## Non-steadystate correlation function + +More generally, we can also calculate correlation functions of the kind ``\left\langle \hat{A}(t_1 + t_2) \hat{B}(t_1) \right\rangle``, i.e., the correlation function of a system that is not in its steady state. In `QuantumToolbox.jl`, we can evaluate such correlation functions using the function [`correlation_2op_2t`](@ref). The default behavior of this function is to return a matrix with the correlations as a function of the two time coordinates (``t_1`` and ``t_2``). + +```@example correlation_and_spectrum +t1_list = LinRange(0, 10.0, 200) +t2_list = LinRange(0, 10.0, 200) + +N = 10 +a = destroy(N) +x = a' + a +H = a' * a + +c_ops = [sqrt(0.25) * a] + +α = 2.5 +ρ0 = coherent_dm(N, α) + +corr = correlation_2op_2t(H, ρ0, t1_list, t2_list, c_ops, x, x; progress_bar = Val(false)) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 400)) + +ax = Axis(fig[1, 1], title = L"\langle \hat{x}(t_1 + t_2) \hat{x}(t_1) \rangle", xlabel = L"Time $t_1$", ylabel = L"Time $t_2$") + +heatmap!(ax, t1_list, t2_list, real(corr)) + +fig +``` + +### Example: first-order optical coherence function + +This example demonstrates how to calculate a correlation function on the form ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle`` for a non-steady initial state. Consider an oscillator that is interacting with a thermal environment. If the oscillator initially is in a coherent state, it will gradually decay to a thermal (incoherent) state. The amount of coherence can be quantified using the first-order optical coherence function + +```math +g^{(1)}(\tau) = \frac{\left\langle \hat{a}^\dagger(\tau) \hat{a}(0) \right\rangle}{\sqrt{\left\langle \hat{a}^\dagger(\tau) \hat{a}(\tau) \right\rangle \left\langle \hat{a}^\dagger(0) \hat{a}(0)\right\rangle}}. +``` +For a coherent state ``\vert g^{(1)}(\tau) \vert = 1``, and for a completely incoherent (thermal) state ``g^{(1)}(\tau) = 0``. The following code calculates and plots ``g^{(1)}(\tau)`` as a function of ``\tau``: + +```@example correlation_and_spectrum +τlist = LinRange(0, 10, 200) + +# Hamiltonian +N = 15 +a = destroy(N) +H = 2 * π * a' * a + +# collapse operator +G1 = 0.75 +n_th = 2.00 # bath temperature in terms of excitation number +c_ops = [ + sqrt(G1 * (1 + n_th)) * a, + sqrt(G1 * n_th) * a' +] + +# start with a coherent state of α = 2.0 +ρ0 = coherent_dm(N, 2.0) + +# first calculate the occupation number as a function of time +n = mesolve(H, ρ0, τlist, c_ops, e_ops = [a' * a], progress_bar = Val(false)).expect[1,:] +n0 = n[1] # occupation number at τ = 0 + +# calculate the correlation function G1 and normalize with n to obtain g1 +g1 = correlation_2op_1t(H, ρ0, τlist, c_ops, a', a, progress_bar = Val(false)) +g1 = g1 ./ sqrt.(n .* n0) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], title = "Decay of a coherent state to an incoherent (thermal) state", xlabel = L"Time $\tau$") +lines!(ax, τlist, real(g1), label = L"g^{(1)}(\tau)", linestyle = :solid) +lines!(ax, τlist, real(n), label = L"n(\tau)", linestyle = :dash) + +axislegend(ax, position = :rt) + +fig +``` + +### Example: second-order optical coherence function + +The second-order optical coherence function, with time-delay ``\tau``, is defined as + +```math +g^{(2)}(\tau) = \frac{\left\langle \hat{a}^\dagger(0) \hat{a}^\dagger(\tau) \hat{a}(\tau) \hat{a}(0) \right\rangle}{\left\langle \hat{a}^\dagger(0) \hat{a}(0) \right\rangle^2}. +``` + +For a coherent state ``g^{(2)}(\tau) = 1``, for a thermal state ``g^{(2)}(\tau = 0) = 2`` and it decreases as a function of time (bunched photons, they tend to appear together), and for a Fock state with ``n``-photons ``g^{(2)}(\tau = 0) = n(n-1)/n^2 < 1`` and it increases with time (anti-bunched photons, more likely to arrive separated in time). + +To calculate this type of correlation function with `QuantumToolbox.jl`, we can use [`correlation_3op_1t`](@ref), which computes a correlation function on the form ``\left\langle \hat{A}(0) \hat{B}(\tau) \hat{C}(0) \right\rangle`` (three operators and one delay-time vector). We first have to combine the central two operators into one single one as they are evaluated at the same time, e.g. here we do ``\hat{B}(\tau) = \hat{a}^\dagger(\tau) \hat{a}(\tau) = (\hat{a}^\dagger\hat{a})(\tau)``. + +The following code calculates and plots ``g^{(2)}(\tau)`` as a function of ``\tau`` for a coherent, thermal and Fock state: + +```@example correlation_and_spectrum +τlist = LinRange(0, 25, 200) + +# Hamiltonian +N = 25 +a = destroy(N) +H = 2 * π * a' * a + +κ = 0.25 +n_th = 2.0 # bath temperature in terms of excitation number +c_ops = [ + sqrt(κ * (1 + n_th)) * a, + sqrt(κ * n_th) * a' +] + +cases = [ + Dict("state" => coherent_dm(N, sqrt(2)), "label" => "coherent state", "lstyle" => :solid), + Dict("state" => thermal_dm(N, 2), "label" => "thermal state", "lstyle" => :dash), + Dict("state" => fock_dm(N, 2), "label" => "Fock state", "lstyle" => :dashdot), +] + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = L"Time $\tau$", ylabel = L"g^{(2)}(\tau)") + +for case in cases + ρ0 = case["state"] + + # calculate the occupation number at τ = 0 + n0 = expect(a' * a, ρ0) + + # calculate the correlation function g2 + g2 = correlation_3op_1t(H, ρ0, τlist, c_ops, a', a' * a, a, progress_bar = Val(false)) + g2 = g2 ./ n0^2 + + lines!(ax, τlist, real(g2), label = case["label"], linestyle = case["lstyle"]) +end + +axislegend(ax, position = :rt) + +fig +``` diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 676c61e32..6b711223d 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -60,7 +60,7 @@ import DiffEqNoiseProcess: RealWienerProcess! # other dependencies (in alphabetical order) import ArrayInterface: allowed_getindex, allowed_setindex! import Distributed: RemoteChannel -import FFTW: fft, fftshift +import FFTW: fft, ifft, fftfreq, fftshift import Graphs: connected_components, DiGraph import IncompleteLU: ilu import Pkg @@ -107,6 +107,7 @@ include("time_evolution/time_evolution_dynamical.jl") # Others include("permutation.jl") include("correlations.jl") +include("spectrum.jl") include("wigner.jl") include("spin_lattice.jl") include("arnoldi.jl") diff --git a/src/correlations.jl b/src/correlations.jl index ebd3c4666..5b7068d9e 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -1,89 +1,95 @@ -export SpectrumSolver, FFTCorrelation, ExponentialSeries -export correlation_3op_2t, correlation_2op_2t, correlation_2op_1t, spectrum - -abstract type SpectrumSolver end - -struct FFTCorrelation <: SpectrumSolver end - -struct ExponentialSeries{T<:Real,CALC_SS} <: SpectrumSolver - tol::T - ExponentialSeries(tol::T, calc_steadystate::Bool = false) where {T} = new{T,calc_steadystate}(tol) +export correlation_3op_2t, correlation_3op_1t, correlation_2op_2t, correlation_2op_1t + +function _check_correlation_time_list(tlist::AbstractVector) + any(t -> t == 0, tlist) || + throw(ArgumentError("The time list for calculating correlation function must contain the element `0`")) + all(>=(0), tlist) || + throw(ArgumentError("All the elements in the time list for calculating correlation function must be positive.")) + return nothing end -ExponentialSeries(; tol = 1e-14, calc_steadystate = false) = ExponentialSeries(tol, calc_steadystate) - @doc raw""" - correlation_3op_2t(H::QuantumObject, - ψ0::QuantumObject, - t_l::AbstractVector, - τ_l::AbstractVector, + correlation_3op_2t(H::AbstractQuantumObject, + ψ0::Union{Nothing,QuantumObject}, + tlist::AbstractVector, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, A::QuantumObject, B::QuantumObject, - C::QuantumObject, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; + C::QuantumObject; kwargs...) -Returns the two-times correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \hat{C}(t) \right\rangle`` +Returns the two-times correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \hat{C}(t) \right\rangle`` for a given initial state ``|\psi_0\rangle``. -for a given initial state ``\ket{\psi_0}``. +If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). """ function correlation_3op_2t( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, - t_l::AbstractVector, - τ_l::AbstractVector, - A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, - C::QuantumObject{<:AbstractArray{T5},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + H::AbstractQuantumObject{DataType,HOpType}, + ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + tlist::AbstractVector, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, + A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + C::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}; kwargs..., ) where { + DataType, T1, T2, T3, T4, - T5, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } - (H.dims == ψ0.dims && H.dims == A.dims && H.dims == B.dims && H.dims == C.dims) || + # check tlist and τlist + _check_correlation_time_list(tlist) + _check_correlation_time_list(τlist) + + L = liouvillian(H, c_ops) + if ψ0 isa Nothing + ψ0 = steadystate(L) + end + + allequal((L.dims, ψ0.dims, A.dims, B.dims, C.dims)) || throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) - kwargs2 = merge((saveat = collect(t_l),), (; kwargs...)) - ρt = mesolve(H, ψ0, t_l, c_ops; kwargs2...).states + kwargs2 = merge((saveat = collect(tlist),), (; kwargs...)) + ρt_list = mesolve(L, ψ0, tlist; kwargs2...).states - corr = map((t, ρ) -> mesolve(H, C * ρ * A, τ_l .+ t, c_ops, e_ops = [B]; kwargs...).expect[1, :], t_l, ρt) + corr = map((t, ρt) -> mesolve(L, C * ρt * A, τlist .+ t, e_ops = [B]; kwargs...).expect[1, :], tlist, ρt_list) - return corr + # make the output correlation Matrix align with QuTiP + # 1st dimension corresponds to tlist + # 2nd dimension corresponds to τlist + return reduce(vcat, transpose.(corr)) end @doc raw""" - correlation_2op_2t(H::QuantumObject, - ψ0::QuantumObject, - t_l::AbstractVector, - τ_l::AbstractVector, + correlation_3op_1t(H::AbstractQuantumObject, + ψ0::Union{Nothing,QuantumObject}, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, A::QuantumObject, B::QuantumObject, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - reverse::Bool=false, + C::QuantumObject; kwargs...) -Returns the two-times correlation function of two operators ``\hat{A}`` and ``\hat{B}`` -at different times: ``\left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle``. +Returns the one-time correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(0) \hat{B}(\tau) \hat{C}(0) \right\rangle`` for a given initial state ``|\psi_0\rangle``. -When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \right\rangle``. +If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). """ -function correlation_2op_2t( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, - t_l::AbstractVector, - τ_l::AbstractVector, - A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - reverse::Bool = false, +function correlation_3op_1t( + H::AbstractQuantumObject{DataType,HOpType}, + ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, + A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + C::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}; kwargs..., ) where { + DataType, T1, T2, T3, @@ -91,156 +97,90 @@ function correlation_2op_2t( HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } - C = eye(prod(H.dims), dims = H.dims) - if reverse - corr = correlation_3op_2t(H, ψ0, t_l, τ_l, A, B, C, c_ops; kwargs...) - else - corr = correlation_3op_2t(H, ψ0, t_l, τ_l, C, A, B, c_ops; kwargs...) - end + corr = correlation_3op_2t(H, ψ0, [0], τlist, c_ops, A, B, C; kwargs...) - return reduce(hcat, corr) + return corr[1, :] # 1 means tlist[1] = 0 end @doc raw""" - correlation_2op_1t(H::QuantumObject, - ψ0::QuantumObject, - τ_l::AbstractVector, + correlation_2op_2t(H::AbstractQuantumObject, + ψ0::Union{Nothing,QuantumObject}, + tlist::AbstractVector, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, A::QuantumObject, - B::QuantumObject, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; + B::QuantumObject; reverse::Bool=false, kwargs...) -Returns the one-time correlation function of two operators ``\hat{A}`` and ``\hat{B}`` at different times ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle``. +Returns the two-times correlation function of two operators ``\hat{A}`` and ``\hat{B}`` : ``\left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle`` for a given initial state ``|\psi_0\rangle``. -When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(0) \hat{B}(\tau) \right\rangle``. +If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). + +When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \right\rangle``. """ -function correlation_2op_1t( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, - τ_l::AbstractVector, - A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; +function correlation_2op_2t( + H::AbstractQuantumObject{DataType,HOpType}, + ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + tlist::AbstractVector, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, + A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; reverse::Bool = false, kwargs..., ) where { + DataType, T1, T2, T3, - T4, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } - corr = correlation_2op_2t(H, ψ0, [0], τ_l, A, B, c_ops; reverse = reverse, kwargs...) + C = eye(prod(H.dims), dims = H.dims) + if reverse + corr = correlation_3op_2t(H, ψ0, tlist, τlist, c_ops, A, B, C; kwargs...) + else + corr = correlation_3op_2t(H, ψ0, tlist, τlist, c_ops, C, A, B; kwargs...) + end - return corr[:, 1] + return corr end @doc raw""" - spectrum(H::QuantumObject, - ω_list::AbstractVector, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector,Tuple}=nothing; - solver::MySolver=ExponentialSeries(), + correlation_2op_1t(H::AbstractQuantumObject, + ψ0::Union{Nothing,QuantumObject}, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, + A::QuantumObject, + B::QuantumObject; + reverse::Bool=false, kwargs...) -Returns the emission spectrum +Returns the one-time correlation function of two operators ``\hat{A}`` and ``\hat{B}`` : ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle`` for a given initial state ``|\psi_0\rangle``. -```math -S(\omega) = \int_{-\infty}^\infty \left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle e^{-i \omega \tau} d \tau -``` +If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). + +When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(0) \hat{B}(\tau) \right\rangle``. """ -function spectrum( - H::QuantumObject{MT1,HOpType}, - ω_list::AbstractVector, +function correlation_2op_1t( + H::AbstractQuantumObject{DataType,HOpType}, + ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + τlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - solver::MySolver = ExponentialSeries(), + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; + reverse::Bool = false, kwargs..., ) where { - MT1<:AbstractMatrix, + DataType, + T1, T2, T3, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - MySolver<:SpectrumSolver, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } - return _spectrum(H, ω_list, A, B, c_ops, solver; kwargs...) -end - -function _spectrum( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ω_list::AbstractVector, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - c_ops, - solver::FFTCorrelation; - kwargs..., -) where {T1,T2,T3,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} - Nsamples = length(ω_list) - ω_max = abs(maximum(ω_list)) - dω = 2 * ω_max / (Nsamples - 1) - ω_l = -ω_max:dω:ω_max - - T = 2π / (ω_l[2] - ω_l[1]) - τ_l = range(0, T, length = length(ω_l)) - - ρss = steadystate(H, c_ops) - corr = correlation_2op_1t(H, ρss, τ_l, A, B, c_ops; kwargs...) - - S = fftshift(fft(corr)) / length(τ_l) - - return ω_l, 2 .* real.(S) -end - -function _spectrum_get_rates_vecs_ss(L, solver::ExponentialSeries{T,true}) where {T} - result = eigen(L) - rates, vecs = result.values, result.vectors - - return rates, vecs, steadystate(L).data -end - -function _spectrum_get_rates_vecs_ss(L, solver::ExponentialSeries{T,false}) where {T} - result = eigen(L) - rates, vecs = result.values, result.vectors - - ss_idx = findmin(abs2, rates)[2] - ρss = vec2mat(@view(vecs[:, ss_idx])) - ρss = (ρss + ρss') / 2 - ρss ./= tr(ρss) - - return rates, vecs, ρss -end - -function _spectrum( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ω_list::AbstractVector, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - c_ops, - solver::ExponentialSeries; - kwargs..., -) where {T1,T2,T3,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} - (H.dims == A.dims == B.dims) || throw(DimensionMismatch("The dimensions of H, A and B must be the same")) - - L = liouvillian(H, c_ops) - - ω_l = ω_list - - rates, vecs, ρss = _spectrum_get_rates_vecs_ss(L, solver) - - ρ0 = B.data * ρss - v = vecs \ mat2vec(ρ0) - - amps = map(i -> v[i] * tr(A.data * vec2mat(@view(vecs[:, i]))), eachindex(rates)) - idxs = findall(x -> abs(x) > solver.tol, amps) - amps, rates = amps[idxs], rates[idxs] - - # spec = map(ω -> 2 * real(sum(@. amps * (1 / (1im * ω - rates)))), ω_l) - amps_rates = zip(amps, rates) - spec = map(ω -> 2 * real(sum(x -> x[1] / (1im * ω - x[2]), amps_rates)), ω_l) + corr = correlation_2op_2t(H, ψ0, [0], τlist, c_ops, A, B; reverse = reverse, kwargs...) - return ω_l, spec + return corr[1, :] # 1 means tlist[1] = 0 end diff --git a/src/deprecated.jl b/src/deprecated.jl index 8cc4db3e5..98d92193d 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -13,3 +13,93 @@ function deprecated_foo(args...; kwargs...) error("`deprecated_foo` has been deprecated and will be removed in next major release, please use `new_foo` instead.") end =# + +export FFTCorrelation + +FFTCorrelation() = error( + "`FFTCorrelation` has been deprecated and will be removed in next major release, please use `spectrum_correlation_fft` to calculate the spectrum with FFT method instead.", +) + +correlation_3op_2t( + H::QuantumObject{<:AbstractArray{T1},HOpType}, + ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + t_l::AbstractVector, + τ_l::AbstractVector, + A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, + C::QuantumObject{<:AbstractArray{T5},OperatorQuantumObject}, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + kwargs..., +) where { + T1, + T2, + T3, + T4, + T5, + HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, +} = error( + "The parameter order of `correlation_3op_2t` has been changed, please use `?correlation_3op_2t` to check the updated docstring.", +) + +correlation_3op_1t( + H::QuantumObject{<:AbstractArray{T1},HOpType}, + ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + τ_l::AbstractVector, + A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, + C::QuantumObject{<:AbstractArray{T5},OperatorQuantumObject}, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + kwargs..., +) where { + T1, + T2, + T3, + T4, + T5, + HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, +} = error( + "The parameter order of `correlation_3op_1t` has been changed, please use `?correlation_3op_1t` to check the updated docstring.", +) + +correlation_2op_2t( + H::QuantumObject{<:AbstractArray{T1},HOpType}, + ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + t_l::AbstractVector, + τ_l::AbstractVector, + A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + reverse::Bool = false, + kwargs..., +) where { + T1, + T2, + T3, + T4, + HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, +} = error( + "The parameter order of `correlation_2op_2t` has been changed, please use `?correlation_2op_2t` to check the updated docstring.", +) + +correlation_2op_1t( + H::QuantumObject{<:AbstractArray{T1},HOpType}, + ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + τ_l::AbstractVector, + A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + reverse::Bool = false, + kwargs..., +) where { + T1, + T2, + T3, + T4, + HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, +} = error( + "The parameter order of `correlation_2op_1t` has been changed, please use `?correlation_2op_1t` to check the updated docstring.", +) diff --git a/src/spectrum.jl b/src/spectrum.jl new file mode 100644 index 000000000..a62a69ca6 --- /dev/null +++ b/src/spectrum.jl @@ -0,0 +1,177 @@ +export spectrum, spectrum_correlation_fft +export SpectrumSolver, ExponentialSeries, PseudoInverse + +abstract type SpectrumSolver end + +@doc raw""" + ExponentialSeries(; tol = 1e-14, calc_steadystate = false) + +A solver which solves [`spectrum`](@ref) by finding the eigen decomposition of the Liouvillian [`SuperOperator`](@ref) and calculate the exponential series. +""" +struct ExponentialSeries{T<:Real,CALC_SS} <: SpectrumSolver + tol::T + ExponentialSeries(tol::T, calc_steadystate::Bool = false) where {T} = new{T,calc_steadystate}(tol) +end + +ExponentialSeries(; tol = 1e-14, calc_steadystate = false) = ExponentialSeries(tol, calc_steadystate) + +@doc raw""" + PseudoInverse(; alg::SciMLLinearSolveAlgorithm = KrylovJL_GMRES()) + +A solver which solves [`spectrum`](@ref) by finding the inverse of Liouvillian [`SuperOperator`](@ref) using the `alg`orithms given in [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/). +""" +struct PseudoInverse{MT<:SciMLLinearSolveAlgorithm} <: SpectrumSolver + alg::MT +end + +PseudoInverse(; alg::SciMLLinearSolveAlgorithm = KrylovJL_GMRES()) = PseudoInverse(alg) + +@doc raw""" + spectrum(H::QuantumObject, + ωlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, + A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; + solver::SpectrumSolver=ExponentialSeries(), + kwargs...) + +Calculate the spectrum of the correlation function + +```math +S(\omega) = \int_{-\infty}^\infty \lim_{t \rightarrow \infty} \left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle e^{-i \omega \tau} d \tau +``` + +See also the following list for `SpectrumSolver` docstrings: +- [`ExponentialSeries`](@ref) +- [`PseudoInverse`](@ref) +""" +function spectrum( + H::QuantumObject{MT1,HOpType}, + ωlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple}, + A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; + solver::SpectrumSolver = ExponentialSeries(), + kwargs..., +) where {MT1<:AbstractMatrix,T2,T3,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + return _spectrum(liouvillian(H, c_ops), ωlist, A, B, solver; kwargs...) +end + +function _spectrum_get_rates_vecs_ss(L, solver::ExponentialSeries{T,true}) where {T} + result = eigen(L) + rates, vecs = result.values, result.vectors + + return rates, vecs, steadystate(L).data +end + +function _spectrum_get_rates_vecs_ss(L, solver::ExponentialSeries{T,false}) where {T} + result = eigen(L) + rates, vecs = result.values, result.vectors + + ss_idx = findmin(abs2, rates)[2] + ρss = vec2mat(@view(vecs[:, ss_idx])) + ρss = (ρss + ρss') / 2 + ρss ./= tr(ρss) + + return rates, vecs, ρss +end + +function _spectrum( + L::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, + ωlist::AbstractVector, + A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + solver::ExponentialSeries; + kwargs..., +) where {T1,T2,T3} + allequal((L.dims, A.dims, B.dims)) || + throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + + rates, vecs, ρss = _spectrum_get_rates_vecs_ss(L, solver) + + ρ0 = B.data * ρss + v = vecs \ mat2vec(ρ0) + + amps = map(i -> v[i] * tr(A.data * vec2mat(@view(vecs[:, i]))), eachindex(rates)) + idxs = findall(x -> abs(x) > solver.tol, amps) + amps, rates = amps[idxs], rates[idxs] + + # spec = map(ω -> 2 * real(sum(@. amps * (1 / (1im * ω - rates)))), ωlist) + amps_rates = zip(amps, rates) + spec = map(ω -> 2 * real(sum(x -> x[1] / (1im * ω - x[2]), amps_rates)), ωlist) + + return spec +end + +function _spectrum( + L::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, + ωlist::AbstractVector, + A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, + B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + solver::PseudoInverse; + kwargs..., +) where {T1,T2,T3} + allequal((L.dims, A.dims, B.dims)) || + throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + + ωList = convert(Vector{_FType(L)}, ωlist) # Convert it to support GPUs and avoid type instabilities + Length = length(ωList) + spec = Vector{_FType(L)}(undef, Length) + + # calculate vectorized steadystate, multiply by operator B on the left (spre) + ρss = mat2vec(steadystate(L)) + b = (spre(B) * ρss).data + + # multiply by operator A on the left (spre) and then perform trace operation + D = prod(L.dims) + _tr = SparseVector(D^2, [1 + n * (D + 1) for n in 0:(D-1)], ones(_CType(L), D)) # same as vec(system_identity_matrix) + _tr_A = transpose(_tr) * spre(A).data + + cache = nothing + I_cache = I(D^2) + for (idx, ω) in enumerate(ωList) + if idx == 1 + cache = init(LinearProblem(L.data - 1im * ω * I_cache, b), solver.alg, kwargs...) + sol = solve!(cache) + else + cache.A = L.data - 1im * ω * I_cache + sol = solve!(cache) + end + + # trace over the Hilbert space of system (expectation value) + spec[idx] = -2 * real(dot(_tr_A, sol.u)) + end + + return spec +end + +@doc raw""" + spectrum_correlation_fft(tlist, corr; inverse=false) + +Calculate the power spectrum corresponding to a two-time correlation function using fast Fourier transform (FFT). + +# Parameters +- `tlist::AbstractVector`: List of times at which the two-time correlation function is given. +- `corr::AbstractVector`: List of two-time correlations corresponding to the given time point in `tlist`. +- `inverse::Bool`: Whether to use the inverse Fourier transform or not. Default to `false`. + +# Returns +- `ωlist`: the list of angular frequencies ``\omega``. +- `Slist`: the list of the power spectrum corresponding to the angular frequencies in `ωlist`. +""" +function spectrum_correlation_fft(tlist::AbstractVector, corr::AbstractVector; inverse::Bool = false) + N = length(tlist) + dt_list = diff(tlist) + dt = dt_list[1] + + all(≈(dt), dt_list) || throw(ArgumentError("tlist must be equally spaced for FFT.")) + + # power spectrum list + F = inverse ? N * ifft(corr) : fft(corr) + Slist = 2 * dt * real(fftshift(F)) + + # angular frequency list + ωlist = 2 * π * fftshift(fftfreq(N, 1 / dt)) + + return ωlist, Slist +end diff --git a/test/core-test/correlations_and_spectrum.jl b/test/core-test/correlations_and_spectrum.jl index 5c5079096..381cd9e08 100644 --- a/test/core-test/correlations_and_spectrum.jl +++ b/test/core-test/correlations_and_spectrum.jl @@ -1,13 +1,22 @@ @testset "Correlations and Spectrum" begin - a = destroy(10) + N = 10 + Id = qeye(N) + a = destroy(N) H = a' * a c_ops = [sqrt(0.1 * (0.01 + 1)) * a, sqrt(0.1 * (0.01)) * a'] - ω_l = range(0, 3, length = 1000) - ω_l1, spec1 = spectrum(H, ω_l, a', a, c_ops, solver = FFTCorrelation(), progress_bar = Val(false)) - ω_l2, spec2 = spectrum(H, ω_l, a', a, c_ops) + t_l = range(0, 333 * π, length = 1000) + corr1 = correlation_2op_1t(H, nothing, t_l, c_ops, a', a; progress_bar = Val(false)) + corr2 = correlation_3op_1t(H, nothing, t_l, c_ops, Id, a', a; progress_bar = Val(false)) + ω_l1, spec1 = spectrum_correlation_fft(t_l, corr1) + + ω_l2 = range(0, 3, length = 1000) + spec2 = spectrum(H, ω_l2, c_ops, a', a) + spec3 = spectrum(H, ω_l2, c_ops, a', a; solver = PseudoInverse()) + spec1 = spec1 ./ maximum(spec1) spec2 = spec2 ./ maximum(spec2) + spec3 = spec3 ./ maximum(spec3) test_func1 = maximum(real.(spec1)) * (0.1 / 2)^2 ./ ((ω_l1 .- 1) .^ 2 .+ (0.1 / 2)^2) test_func2 = maximum(real.(spec2)) * (0.1 / 2)^2 ./ ((ω_l2 .- 1) .^ 2 .+ (0.1 / 2)^2) @@ -15,9 +24,36 @@ idxs2 = test_func2 .> 0.05 @test sum(abs2.(spec1[idxs1] .- test_func1[idxs1])) / sum(abs2.(test_func1[idxs1])) < 0.01 @test sum(abs2.(spec2[idxs2] .- test_func2[idxs2])) / sum(abs2.(test_func2[idxs2])) < 0.01 + @test all(corr1 .≈ corr2) + @test all(spec2 .≈ spec3) @testset "Type Inference spectrum" begin - @inferred spectrum(H, ω_l, a', a, c_ops, solver = FFTCorrelation(), progress_bar = Val(false)) - @inferred spectrum(H, ω_l, a', a, c_ops) + @inferred correlation_2op_1t(H, nothing, t_l, c_ops, a', a; progress_bar = Val(false)) + @inferred spectrum_correlation_fft(t_l, corr1) + @inferred spectrum(H, ω_l2, c_ops, a', a) + @inferred spectrum(H, ω_l2, c_ops, a', a; solver = PseudoInverse()) + end + + # tlist and τlist checks + t_fft_wrong = [0, 1, 10] + t_wrong1 = [1, 2, 3] + t_wrong2 = [-1, 0, 1] + @test_throws ArgumentError spectrum_correlation_fft(t_fft_wrong, corr1) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_l, t_wrong1, c_ops, Id, a', a) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_l, t_wrong2, c_ops, Id, a', a) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_wrong1, t_l, c_ops, Id, a', a) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_wrong2, t_l, c_ops, Id, a', a) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_wrong1, t_wrong1, c_ops, Id, a', a) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_wrong1, t_wrong2, c_ops, Id, a', a) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_wrong2, t_wrong1, c_ops, Id, a', a) + @test_throws ArgumentError correlation_3op_2t(H, nothing, t_wrong2, t_wrong2, c_ops, Id, a', a) + + @testset "Deprecated Errors" begin + ρ0 = rand_dm(N) + @test_throws ErrorException FFTCorrelation() + @test_throws ErrorException correlation_3op_2t(H, ρ0, t_l, t_l, a, a', a, c_ops) + @test_throws ErrorException correlation_3op_1t(H, ρ0, t_l, a, a', a, c_ops) + @test_throws ErrorException correlation_2op_2t(H, ρ0, t_l, t_l, a', a, c_ops) + @test_throws ErrorException correlation_2op_1t(H, ρ0, t_l, a', a, c_ops) end end From bb1be0a8413da0465b23c695be92cf183f213167 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:04:20 +0900 Subject: [PATCH 139/329] bump version to `v0.23.0` --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b90e80a89..35957b5c8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.22.0" +version = "0.23.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 59f2722dc1c36dc9a3f3b37ca674f62e7d72cd68 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:06:19 +0900 Subject: [PATCH 140/329] bump version to `v0.23.0` --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1363bc0c4..4562de32f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [v0.23.0] (2024-12-04) + - Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324]) - Make `spectrum` and `correlation` functions align with `Python QuTiP`, introduce spectrum solver `PseudoInverse`, remove spectrum solver `FFTCorrelation`, and introduce `spectrum_correlation_fft`. ([#330]) @@ -15,7 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change the parameters structure of `sesolve`, `mesolve` and `mcsolve` functions to possibly support automatic differentiation. ([#311]) - Fix type instability and reduce extra memory allocation in `liouvillian`. ([#315], [#318]) - ## [v0.21.5] (2024-11-15) - This is a demonstration of how to bump version number and also modify `CHANGELOG.md` before new release. ([#309]) @@ -30,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [v0.21.4]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.4 [v0.21.5]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.5 [v0.22.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.22.0 +[v0.23.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.0 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 [#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 From 1b68de468c7ea9fa50dd24d308009d9b8318bfbe Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:43:54 +0900 Subject: [PATCH 141/329] add HEOM documentation page (#333) --- docs/make.jl | 1 + docs/src/resources/bibliography.bib | 29 +++++++++++++++++++++++++ docs/src/users_guide/HEOM.md | 33 +++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 docs/src/users_guide/HEOM.md diff --git a/docs/make.jl b/docs/make.jl index 5512e8621..25d910cc1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -49,6 +49,7 @@ const PAGES = [ "Stochastic Solver" => "users_guide/time_evolution/stochastic.md", "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", ], + "Hierarchical Equations of Motion" => "users_guide/HEOM.md", "Solving for Steady-State Solutions" => "users_guide/steadystate.md", "Two-time correlation functions" => "users_guide/two_time_corr_func.md", "Extensions" => [ diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index 3cddf83b5..3327c024b 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -46,3 +46,32 @@ @article{gravina2024adaptive doi = {10.1103/PhysRevResearch.6.023072}, url = {https://link.aps.org/doi/10.1103/PhysRevResearch.6.023072} } + +@article{Tanimura1989, + title = {Time Evolution of a Quantum System in Contact with a Nearly Gaussian-Markoffian Noise Bath}, + volume = {58}, + ISSN = {1347-4073}, + url = {http://dx.doi.org/10.1143/JPSJ.58.101}, + DOI = {10.1143/jpsj.58.101}, + number = {1}, + journal = {Journal of the Physical Society of Japan}, + publisher = {Physical Society of Japan}, + author = {Tanimura, Yoshitaka and Kubo, Ryogo}, + year = {1989}, + month = jan, + pages = {101–114} +} + +@article{Huang2023, + doi = {10.1038/s42005-023-01427-2}, + url = {https://doi.org/10.1038/s42005-023-01427-2}, + year = {2023}, + month = {Oct}, + publisher = {Nature Portfolio}, + volume = {6}, + number = {1}, + pages = {313}, + author = {Huang, Yi-Te and Kuo, Po-Chen and Lambert, Neill and Cirio, Mauro and Cross, Simon and Yang, Shen-Liang and Nori, Franco and Chen, Yueh-Nan}, + title = {An efficient {J}ulia framework for hierarchical equations of motion in open quantum systems}, + journal = {Communications Physics} +} diff --git a/docs/src/users_guide/HEOM.md b/docs/src/users_guide/HEOM.md new file mode 100644 index 000000000..7844af929 --- /dev/null +++ b/docs/src/users_guide/HEOM.md @@ -0,0 +1,33 @@ +# [Hierarchical Equations of Motion](@id doc:Hierarchical-Equations-of-Motion) + +The hierarchical equations of motion (HEOM) approach was originally developed by Tanimura and Kubo [Tanimura1989](@cite) in the context of physical chemistry to "exactly" solve a quantum system (labeled as ``\textrm{s}``) in contact with a bosonic environment, encapsulated in the following total Hamiltonian: + +```math +\hat{H}_{\textrm{total}} = \hat{H}_{\textrm{s}} + \sum_k \omega_k \hat{b}^\dagger_k \hat{b}_k + \hat{V}_{\textrm{s}} \sum_k g_k \left(\hat{b}_k + \hat{b}^\dagger_k\right), +``` + +where ``\hat{b}_k`` (``\hat{b}^\dagger_k``) is the bosonic annihilation (creation) operator associated to the ``k``th mode (with frequency ``\omega_k``), ``\hat{V}_{\textrm{s}}`` refer to the coupling operator acting on the system's degree of freedom, and ``g_k`` are the coupling strengths. + +As in other solutions to this problem, the properties of the bath are encapsulated by its temperature and its spectral density, + +```math +J(\omega) = 2 \pi \sum_k g^2_k \delta(\omega - \omega_k). +``` + +In the HEOM approach, for bosonic baths, one typically chooses a Drude-Lorentz spectral density: + +```math +J_{\textrm{DL}}(\omega) = \frac{4 \Delta W \omega}{\omega^2 + W^2}, +``` + +or an under-damped Brownian motion spectral density, + +```math +J_{\textrm{U}}(\omega)=\frac{2 \Delta^2 W \omega}{(\omega^2 - \omega_0^2)^2 + \omega^2 W^2}. +``` + +Here, ``\Delta`` represents the coupling strength between the system and the bosonic bath with band-width ``W`` and resonance frequency ``\omega_0``. + +We introduce an efficient `Julia` framework for HEOM approach called [`HierarchicalEOM.jl`](https://github.com/qutip/HierarchicalEOM.jl). This package is built upon `QuantumToolbox.jl` and provides a user-friendly and efficient tool to simulate complex open quantum systems based on HEOM approach. For a detailed explanation of this package, we recommend to read its [documentation](https://qutip.org/HierarchicalEOM.jl/) and also the article [Huang2023](@cite). + +Given the spectral density, the HEOM approach requires a decomposition of the bath correlation functions in terms of exponentials. In the [documentation of `HierarchicalEOM.jl`](https://qutip.org/HierarchicalEOM.jl/), we not only describe how this is done for both bosonic and fermionic environments with code examples, but also describe how to solve the time evolution (dynamics), steady-states, and spectra based on HEOM approach. From 5911791cf322f3f8fa00088a9680bec88cb5c557 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:45:47 +0900 Subject: [PATCH 142/329] [Doc] `QobjEvo` documentation page (#334) * add `QobjEvo` documentation page * move some import of `CairoMakie.jl` to `@setup` block * fix typos --- .../QuantumObject/QuantumObject.md | 1 + docs/src/users_guide/steadystate.md | 2 +- docs/src/users_guide/time_evolution/intro.md | 3 + .../src/users_guide/time_evolution/mesolve.md | 10 +- .../src/users_guide/time_evolution/sesolve.md | 7 +- .../users_guide/time_evolution/solution.md | 7 +- .../time_evolution/time_dependent.md | 352 +++++++++++++++++- 7 files changed, 370 insertions(+), 12 deletions(-) diff --git a/docs/src/users_guide/QuantumObject/QuantumObject.md b/docs/src/users_guide/QuantumObject/QuantumObject.md index 01a86914f..58377dcd7 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject.md @@ -174,6 +174,7 @@ println(isoper(a)) # operator println(isoperket(a)) # operator-ket println(isoperbra(a)) # operator-bra println(issuper(a)) # super operator +println(isconstant(a)) # time-independent or not println(ishermitian(a)) # Hermitian println(isherm(a)) # synonym of ishermitian(a) println(issymmetric(a)) # symmetric diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index 2c53efb03..52e64419c 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -104,7 +104,7 @@ exp_mc = real(sol_mc.expect[1, :]) sol_me = mesolve(H, ψ0, tlist, c_ops, e_ops=e_ops, progress_bar=false) exp_me = real(sol_me.expect[1, :]) -# plot the results +# plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], title = L"Decay of Fock state $|10\rangle$ in a thermal environment with $\langle n\rangle=2$", diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index 2aaf3eeb0..3b9ccf985 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -19,6 +19,9 @@ - [Monte-Carlo Solver](@ref doc-TE:Monte-Carlo-Solver) - [Stochastic Solver](@ref doc-TE:Stochastic-Solver) - [Solving Problems with Time-dependent Hamiltonians](@ref doc-TE:Solving-Problems-with-Time-dependent-Hamiltonians) + - [Generate QobjEvo](@ref doc-TE:Generate-QobjEvo) + - [QobjEvo fields (attributes)](@ref doc-TE:QobjEvo-fields-(attributes)) + - [Using parameters](@ref doc-TE:Using-parameters) # [Introduction](@id doc-TE:Introduction) diff --git a/docs/src/users_guide/time_evolution/mesolve.md b/docs/src/users_guide/time_evolution/mesolve.md index 6c06c1568..9ad924b96 100644 --- a/docs/src/users_guide/time_evolution/mesolve.md +++ b/docs/src/users_guide/time_evolution/mesolve.md @@ -2,6 +2,9 @@ ```@setup mesolve using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) ``` ## [Von Neumann equation](@id doc-TE:Von-Neumann-equation) @@ -123,13 +126,11 @@ sol = mesolve(H, ψ0, tlist, c_ops, e_ops = [sigmaz(), sigmay()]) We can therefore plot the expectation values: ```@example mesolve -using CairoMakie -CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) - times = sol.times expt_z = real(sol.expect[1,:]) expt_y = real(sol.expect[2,:]) +# plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") lines!(ax, times, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) @@ -166,7 +167,7 @@ e_ops = [a' * a] sol = mesolve(H, ψ0, tlist, c_ops, e_ops=e_ops) Num = real(sol.expect[1, :]) -# plot the results +# plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], title = L"Decay of Fock state $|10\rangle$ in a thermal environment with $\langle n\rangle=2$", @@ -213,6 +214,7 @@ times = sol.times N_atom = real(sol.expect[1,:]) N_cavity = real(sol.expect[2,:]) +# plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") lines!(ax, times, N_atom, label = "atom excitation probability", linestyle = :solid) diff --git a/docs/src/users_guide/time_evolution/sesolve.md b/docs/src/users_guide/time_evolution/sesolve.md index f41df647d..d3ba8a21c 100644 --- a/docs/src/users_guide/time_evolution/sesolve.md +++ b/docs/src/users_guide/time_evolution/sesolve.md @@ -23,6 +23,9 @@ The Schrödinger equation, which governs the time-evolution of closed quantum sy ```@setup sesolve using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) ``` For example, the time evolution of a quantum spin-``\frac{1}{2}`` system (initialized in spin-``\uparrow``) with tunneling rate ``0.1`` is calculated, and the expectation values of the Pauli-Z operator ``\hat{\sigma}_z`` is also evaluated, with the following code @@ -68,12 +71,10 @@ print(size(expt)) We can therefore plot the expectation values: ```@example sesolve -using CairoMakie -CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) - expt_z = real(expt[1,:]) expt_y = real(expt[2,:]) +# plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") lines!(ax, times, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index 455c94991..5294ef214 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -2,6 +2,9 @@ ```@setup TE-solution using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) ``` ## [Solution](@id doc-TE:Solution) @@ -61,9 +64,7 @@ nothing # hide we can plot the resulting expectation values: ```@example TE-solution -using CairoMakie -CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) - +# plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = L"t") lines!(ax, times, expt1, label = L"\langle 0 | \rho(t) | 0 \rangle") diff --git a/docs/src/users_guide/time_evolution/time_dependent.md b/docs/src/users_guide/time_evolution/time_dependent.md index f9346e884..ba9c0042c 100644 --- a/docs/src/users_guide/time_evolution/time_dependent.md +++ b/docs/src/users_guide/time_evolution/time_dependent.md @@ -1,3 +1,353 @@ # [Solving Problems with Time-dependent Hamiltonians](@id doc-TE:Solving-Problems-with-Time-dependent-Hamiltonians) -This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file +```@setup QobjEvo +using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) +``` + +## [Generate QobjEvo](@id doc-TE:Generate-QobjEvo) + +In the previous examples of solving time evolution, we assumed that the systems under consideration were described by time-independent Hamiltonians. However, many systems have explicit time dependence in either the Hamiltonian, or the collapse operators (`c_ops`) describing coupling to the environment, and sometimes both components might depend on time. The time evolution solvers such as [`sesolve`](@ref), [`mesolve`](@ref), etc., are all capable of handling time-dependent Hamiltonians and collapse operators. + +`QuantumToolbox.jl` uses [`QuantumObjectEvolution`](@ref) (or the abbreviated synonym: [`QobjEvo`](@ref)) to represent time-dependent quantum operators, and its `data` field (attribute) takes advantage of [`SciMLOperators.jl`](https://docs.sciml.ai/SciMLOperators/stable/). + +To generate a time-dependent operator [`QobjEvo`](@ref), one needs to specify a time-independent [`Qobj`](@ref) together with a time-dependent coefficient function with the form `coef(p, t)`, namely + +```@example QobjEvo +coef(p, t) = sin(t) + +H_t = QobjEvo(sigmax(), coef) +``` + +!!! warning "The inputs of coefficient function" + Please note that although we didn't use the argument `p` in the definition of `coef`, we still need to put a dummy input `p` in the declaration of `coef`. We will describe how to use the parameter `p` in the section [Using parameters](@ref doc-TE:Using-parameters). + +The [`QobjEvo`](@ref) can also be generated by specifying many pairs of time-independent [`Qobj`](@ref) and time-dependent coefficient function. For instance, we will look at a case with the total Hamiltonian ``\hat{H}(t)`` can be separate into time-independent part (``\hat{H}_0``) and a summation of many time-dependent operators, which takes the form: + +```math +\hat{H}(t) = \hat{H}_0 + \sum_j f_j(t) \hat{H}_j. +``` + +The following code sets up this problem by using a `Tuple` which contains many time-dependent pairs `(H_j, f_j)`, namely + +```@example QobjEvo +H0 = qeye(2) + +H1 = sigmax() +f1(p, t) = sin(t) + +H2 = sigmay() +f2(p, t) = cos(t) + +H3 = sigmaz() +f3(p, t) = 9 * exp(-(t / 5)^2) + +H_tuple = ( + H0, + (H1, f1), + (H2, f2), + (H3, f3), +) + +H_t = QobjEvo(H_tuple) +``` + +This is equivalent by generating the [`QobjEvo`](@ref) separately and `+` (or `sum`) them up: + +```@example QobjEvo +H_t == H0 + QobjEvo(H1, f1) + QobjEvo(H2, f2) + QobjEvo(H3, f3) +``` + +Most solvers will accept any format that could be made into a [`QobjEvo`](@ref) for the Hamiltonian. All of the followings are equivalent: + +```julia +sol = mesolve(H_t, ...) +sol = mesolve(H_tuple, ...) +``` + +Collapse operators `c_ops` only accept a list where each element is either a [`Qobj`](@ref) or a [`QobjEvo`](@ref). For example, in the following call: + +```julia +γ1 = sqrt(0.1) +γ2(p, t) = sqrt(sin(t)) +c_ops = ( + γ1 * sigmaz(), + QobjEvo(sigmax() + sigmay(), γ2) +) + +sol = mesolve(H_t, ..., c_ops, ...) +``` + +[`mesolve`](@ref) will see `2` collapse operators: ``\sqrt{0.1} \hat{\sigma}_z`` and ``\sqrt{\sin(t)} (\hat{\sigma}_x + \hat{\sigma}_y)``. + +As an example, we will look at a case with a time-dependent Hamiltonian of the form ``\hat{H}(t) = \hat{H}_0 + f_1(t) \hat{H}_1``, where ``f_1(t) = A \exp \left[ -(t / \sigma)^2 \right]`` is the time-dependent driving strength. The following code sets up the problem + +```@example QobjEvo +N = 2 # Set where to truncate Fock state for cavity + +# basis states for Atom +u = basis(3, 0) # u state +e = basis(3, 1) # excited state +g = basis(3, 2) # ground state + +# operators +g_e = tensor(qeye(N), g * e') # |g> g + sqrt(5 * γ0 / 9) * u_e # 5/9 e -> u +] + +tlist = LinRange(0, 4, 200) # Define time vector +ψ0 = tensor(basis(N, 0), u) # Define initial state + +# Build observables +e_ops = [ + a' * a, + u_u, + g_g +] + +# solve dynamics +exp_me = mesolve(H_t, ψ0, tlist, c_ops, e_ops = e_ops; progress_bar = Val(false)).expect +exp_mc = mcsolve(H_t, ψ0, tlist, c_ops, e_ops = e_ops; progress_bar = Val(false)).expect + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = L"Time $t$", ylabel = "expectation value") +lines!(ax, tlist, real(exp_me[1,:]), label = L"$\hat{a}^\dagger \hat{a}$ (mesolve)", color = :red, linestyle = :solid) +lines!(ax, tlist, real(exp_mc[1,:]), label = L"$\hat{a}^\dagger \hat{a}$ (mcsolve)", color = :red, linestyle = :dash) +lines!(ax, tlist, real(exp_me[2,:]), label = L"$|u \rangle\langle u|$ (mesolve)", color = :green, linestyle = :solid) +lines!(ax, tlist, real(exp_mc[2,:]), label = L"$|u \rangle\langle u|$ (mcsolve)", color = :green, linestyle = :dash) +lines!(ax, tlist, real(exp_me[3,:]), label = L"$|g \rangle\langle g|$ (mesolve)", color = :blue, linestyle = :solid) +lines!(ax, tlist, real(exp_mc[3,:]), label = L"$|g \rangle\langle g|$ (mcsolve)", color = :blue, linestyle = :dash) + +axislegend(ax, position = :rc) + +fig +``` + +The result from [`mesolve`](@ref) is identical to that shown in the examples, the [`mcsolve`](@ref) however will be noticeably off, suggesting we should increase the number of trajectories `ntraj` for this example. + +In addition, we can also consider the decay of a simple Harmonic oscillator with time-varying decay rate ``\gamma_1(t)`` + +```@example QobjEvo +N = 10 # number of basis states +a = destroy(N) +H = a' * a + +ψ0 = basis(N, 9) # initial state + +γ0 = 0.5 +γ1(p, t) = sqrt(γ0 * exp(-t)) +c_ops_ti = [sqrt(γ0) * a] # time-independent collapse term +c_ops_td = [QobjEvo(a, γ1)] # time-dependent collapse term + +e_ops = [a' * a] + +tlist = LinRange(0, 10, 100) +exp_ti = mesolve(H, ψ0, tlist, c_ops_ti, e_ops = e_ops; progress_bar = Val(false)).expect[1,:] +exp_td = mesolve(H, ψ0, tlist, c_ops_td, e_ops = e_ops; progress_bar = Val(false)).expect[1,:] + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = L"Time $t$", ylabel = L"\langle \hat{a}^\dagger \hat{a} \rangle") +lines!(ax, tlist, real(exp_ti), label = L"\gamma_0", linestyle = :solid) +lines!(ax, tlist, real(exp_td), label = L"\gamma_1(t) = \gamma_0 e^{-t}", linestyle = :dash) + +axislegend(ax, position = :rt) + +fig +``` + +## [QobjEvo fields (attributes)](@id doc-TE:QobjEvo-fields-(attributes)) + +[`QuantumObjectEvolution`](@ref) as a time-dependent quantum system, as its main functionality creates a [`QuantumObject`](@ref) at a specified time ``t``: + +```@example QobjEvo +coef(p, t) = sin(π * t) +Ht = QobjEvo(sigmaz(), coef) + +Ht(0.25) +``` + +[`QuantumObjectEvolution`](@ref) shares a lot of properties with the [`QuantumObject`](@ref): + +```@example QobjEvo +Ht.data +``` + +```@example QobjEvo +Ht.type +``` + +```@example QobjEvo +Ht.dims +``` + +The properties of a [`QuantumObjectEvolution`](@ref) can also be retrieved using several functions with inputting [`QuantumObjectEvolution`](@ref): + +```@example QobjEvo +size(Ht) +``` + +```@example QobjEvo +shape(Ht) # synonym of size(H) +``` + +```@example QobjEvo +length(Ht) +``` + +```@example QobjEvo +eltype(Ht) # element type +``` + +```@example QobjEvo +println(isket(Ht)) # ket +println(isbra(Ht)) # bra +println(isoper(Ht)) # operator +println(isoperket(Ht)) # operator-ket +println(isoperbra(Ht)) # operator-bra +println(issuper(Ht)) # super operator +println(isconstant(Ht)) # time-independent or not +println(ishermitian(Ht)) # Hermitian +println(isherm(Ht)) # synonym of ishermitian(a) +println(issymmetric(Ht)) # symmetric +println(isposdef(Ht)) # positive definite (and Hermitian) +``` + +[`QobjEvo`](@ref) follow the same mathematical operations rules as [`Qobj`](@ref). They can be added, subtracted and multiplied with scalar, [`Qobj`](@ref) and [`QobjEvo`](@ref). They also support [`adjoint`](@ref), [`transpose`](@ref), and [`conj`](@ref) methods, and can be used for [`SuperOperator`](@ref) transformation: + +```@example QobjEvo +coef(p, t) = sin(π * t) +Ht = QobjEvo(sigmaz(), coef) + +coef2(p, t) = exp(-t) +c_op = QobjEvo(destroy(2), coef2) + +L1 = -1im * (spre(Ht) - spost(Ht')) +L1 += lindblad_dissipator(c_op) +``` + +Or equivalently: +```@example QobjEvo +L2 = liouvillian(Ht, [c_op]) +``` + +```@example QobjEvo +t = rand() +L1(t) == L2(t) +``` + +!!! note "Optimization for superoperator transformation" + Although the value of `L1` and `L2` here is equivalent, one can observe that the structure of `L2.data` is much simpler than `L1.data`, which means that it will be more efficient to solve the time evolution using `L2` instead of `L1`. Therefore, we recommend to use [`liouvillian`](@ref) or [`lindblad_dissipator`](@ref) to generate time-dependent [`SuperOperator`](@ref) because these functions have been optimized. + +## [Using parameters](@id doc-TE:Using-parameters) + +Until now, the coefficients were only functions of time `t`. In the definition of ``f_1(t) = A \exp \left[ -(t / \sigma)^2 \right]`` in the previous example, the driving amplitude ``A`` and width of the gaussian driving term ``\sigma`` were hardcoded with their numerical values. This is fine for problems that are specialized, or that we only want to run once. However, in many cases, we would like to study the same problem with a range of parameters and don't have to worry about manually changing the values on each run. `QuantumToolbox.jl` allows you to accomplish this by adding extra parameters `p` to coefficient functions that make the [`QobjEvo`](@ref). For instance, instead of explicitly writing `9` for ``A`` and `5` for ``\sigma``, we can either specify them with `p` as a `Vector` or `NamedTuple`: + +```@example QobjEvo +# specify p as a Vector +f1_v(p, t) = p[1] * exp(-(t / p[2])^2) + +H_t_v = QobjEvo(sigmaz(), f1_v) + +p_v = [9, 5] # 1st element represents A; 2nd element represents σ +t = 1 +H_t_v(p_v, t) +``` + +```@example QobjEvo +# specify p as a NamedTuple +f1_n(p, t) = p.A * exp(-(t / p.σ)^2) + +H_t_n = QobjEvo(sigmaz(), f1_n) + +p_n = (A = 9, σ = 5) +t = 1 +H_t_n(p_n, t) +``` + +```@example QobjEvo +t = rand() +H_t_v(p_v, t) == H_t_n(p_n, t) +``` + +!!! note "Custom structure of parameter" + For more advanced usage, any custom `struct` of parameter `p` can be used. + +When solving time evolutions, the solvers take an single keyword argument `params`, which will be directly passed to input `p` of the coefficient functions to build the time dependent Hamiltonian and collapse operators. For example, with `p::Vector`: + +```@example QobjEvo +f1(p, t) = p[1] * cos(p[2] * t) +f2(p, t) = p[3] * sin(p[4] * t) +γ(p, t) = sqrt(p[5] * exp(-p[6] * t)) +p_total = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] + +H_t = sigmaz() + QobjEvo(sigmax(), f1) + QobjEvo(sigmay(), f2) + +c_ops = [ + QobjEvo(destroy(2), γ) +] + +e_ops = [sigmaz()] + +ψ0 = basis(2, 0) +tlist = LinRange(0, 10, 20) +sol1 = mesolve(H_t, ψ0, tlist, c_ops, params = p_total, e_ops = e_ops; progress_bar = Val(false)) +``` + +Similarly, with `p::NamedTuple`: + +```@example QobjEvo +f1(p, t) = p.A1 * cos(p.ω1 * t) +f2(p, t) = p.A2 * sin(p.ω2 * t) +γ(p, t) = sqrt(p.A3 * exp(-p.γ0 * t)) +p_total = ( + A1 = 0.1, + ω1 = 0.2, + A2 = 0.3, + ω2 = 0.4, + A3 = 0.5, + γ0 = 0.6 +) + +H_t = sigmaz() + QobjEvo(sigmax(), f1) + QobjEvo(sigmay(), f2) + +c_ops = [ + QobjEvo(destroy(2), γ) +] + +e_ops = [sigmaz()] + +ψ0 = basis(2, 0) +tlist = LinRange(0, 10, 20) +sol2 = mesolve(H_t, ψ0, tlist, c_ops, params = p_total, e_ops = e_ops; progress_bar = Val(false)) +``` + +```@example QobjEvo +sol1.expect == sol2.expect +``` From 0f2c4a1978d4697abcbf2ec61b98470943f735dd Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:55:38 +0900 Subject: [PATCH 143/329] Fix `[compat]` of `DiffEqCallbacks.jl` (#335) * fix `[compat]` of `DiffEqCallbacks` * fix changelog --- CHANGELOG.md | 10 +++++++++- Project.toml | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4562de32f..ed5af6525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,13 @@ All notable changes to [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToo The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) + + + +## [v0.23.1] (2024-12-06) + +- Update `[compat]` to fix the incompatibility between `QuantumToolbox v0.22.0+` and `DiffEqCallbacks < v4.2.1`. ([#335]) ## [v0.23.0] (2024-12-04) @@ -32,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [v0.21.5]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.5 [v0.22.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.22.0 [v0.23.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.0 +[v0.23.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.1 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 [#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 @@ -40,3 +47,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#318]: https://github.com/qutip/QuantumToolbox.jl/issues/318 [#324]: https://github.com/qutip/QuantumToolbox.jl/issues/324 [#330]: https://github.com/qutip/QuantumToolbox.jl/issues/330 +[#335]: https://github.com/qutip/QuantumToolbox.jl/issues/335 diff --git a/Project.toml b/Project.toml index 35957b5c8..711c4828d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.23.0" +version = "0.23.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" @@ -37,7 +37,7 @@ Aqua = "0.8" ArrayInterface = "6, 7" CUDA = "5" DiffEqBase = "6" -DiffEqCallbacks = "2 - 3.1, 3.8, 4" +DiffEqCallbacks = "4.2.1 - 4" DiffEqNoiseProcess = "5" Distributed = "1" FFTW = "1.5" From ff73a5a26872997b441e6b20eab2974f89524ce7 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:56:13 +0900 Subject: [PATCH 144/329] fix typos in documentation (#336) --- docs/src/getting_started/type_stability.md | 2 +- .../users_guide/time_evolution/time_dependent.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/getting_started/type_stability.md b/docs/src/getting_started/type_stability.md index b9a31f20d..1daabb063 100644 --- a/docs/src/getting_started/type_stability.md +++ b/docs/src/getting_started/type_stability.md @@ -144,7 +144,7 @@ nothing # hide Thus, we highly recommend using `Vector` only when we are sure that it contains elements of the same type, and only when we don't need to know its size at compile time. On the other hand, `Tuple`s are less flexible but more efficient in terms of performance. A third option is to use the `SVector` type from the [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) package. This is similar to `Vector`, where the elements should have the same type, but it is fixed-size and immutable. One may ask when it is necessary to know the array size at compile time. A practical example is the case of [`ptrace`](@ref), where it internally reshapes the quantum state into a tensor whose dimensions depend on the number of subsystems. We will see this in more detail in the next section. -## The [`QuantumObject`](@ref) internal structure +## The `QuantumObject` internal structure Before making a practical example, let's see the internal structure of the [`QuantumObject`](@ref) type. As an example, we consider the case of three qubits, and we study the internal structure of the ``\hat{\sigma}_x^{(2)}`` operator: diff --git a/docs/src/users_guide/time_evolution/time_dependent.md b/docs/src/users_guide/time_evolution/time_dependent.md index ba9c0042c..5c5a0b111 100644 --- a/docs/src/users_guide/time_evolution/time_dependent.md +++ b/docs/src/users_guide/time_evolution/time_dependent.md @@ -22,7 +22,7 @@ H_t = QobjEvo(sigmax(), coef) ``` !!! warning "The inputs of coefficient function" - Please note that although we didn't use the argument `p` in the definition of `coef`, we still need to put a dummy input `p` in the declaration of `coef`. We will describe how to use the parameter `p` in the section [Using parameters](@ref doc-TE:Using-parameters). + Please note that although we didn't use the argument `p` in the definition of `coef`, we still need to put a dummy input `p` (in front of `t`) in the declaration of `coef`. We will describe how to use the parameter `p` in the section [Using parameters](@ref doc-TE:Using-parameters). The [`QobjEvo`](@ref) can also be generated by specifying many pairs of time-independent [`Qobj`](@ref) and time-dependent coefficient function. For instance, we will look at a case with the total Hamiltonian ``\hat{H}(t)`` can be separate into time-independent part (``\hat{H}_0``) and a summation of many time-dependent operators, which takes the form: @@ -131,8 +131,8 @@ e_ops = [ ] # solve dynamics -exp_me = mesolve(H_t, ψ0, tlist, c_ops, e_ops = e_ops; progress_bar = Val(false)).expect -exp_mc = mcsolve(H_t, ψ0, tlist, c_ops, e_ops = e_ops; progress_bar = Val(false)).expect +exp_me = mesolve(H_t, ψ0, tlist, c_ops; e_ops = e_ops, progress_bar = Val(false)).expect +exp_mc = mcsolve(H_t, ψ0, tlist, c_ops; e_ops = e_ops, ntraj = 100, progress_bar = Val(false)).expect # plot by CairoMakie.jl fig = Figure(size = (500, 350)) @@ -149,7 +149,7 @@ axislegend(ax, position = :rc) fig ``` -The result from [`mesolve`](@ref) is identical to that shown in the examples, the [`mcsolve`](@ref) however will be noticeably off, suggesting we should increase the number of trajectories `ntraj` for this example. +The result from [`mesolve`](@ref) is identical to that shown in the examples, the [`mcsolve`](@ref) however will be noticeably off, suggesting we should increase the number of trajectories `ntraj = 100` for this example. In addition, we can also consider the decay of a simple Harmonic oscillator with time-varying decay rate ``\gamma_1(t)`` @@ -190,7 +190,7 @@ fig coef(p, t) = sin(π * t) Ht = QobjEvo(sigmaz(), coef) -Ht(0.25) +Ht(0.25) # t = 0.25 ``` [`QuantumObjectEvolution`](@ref) shares a lot of properties with the [`QuantumObject`](@ref): @@ -214,7 +214,7 @@ size(Ht) ``` ```@example QobjEvo -shape(Ht) # synonym of size(H) +shape(Ht) # synonym of size(Ht) ``` ```@example QobjEvo @@ -234,7 +234,7 @@ println(isoperbra(Ht)) # operator-bra println(issuper(Ht)) # super operator println(isconstant(Ht)) # time-independent or not println(ishermitian(Ht)) # Hermitian -println(isherm(Ht)) # synonym of ishermitian(a) +println(isherm(Ht)) # synonym of ishermitian(Ht) println(issymmetric(Ht)) # symmetric println(isposdef(Ht)) # positive definite (and Hermitian) ``` From e4ccef54482c99db95c8cbeccd36938d332c9885 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 8 Dec 2024 03:59:17 +0900 Subject: [PATCH 145/329] Fix release date format in `CHANGELOG` (#337) --- CHANGELOG.md | 15 ++++++++++----- docs/make.jl | 5 ++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5af6525..4b61c2caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,25 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 -## [v0.23.1] (2024-12-06) +## [v0.23.1] +Release date: 2024-12-06 - Update `[compat]` to fix the incompatibility between `QuantumToolbox v0.22.0+` and `DiffEqCallbacks < v4.2.1`. ([#335]) -## [v0.23.0] (2024-12-04) +## [v0.23.0] +Release date: 2024-12-04 - Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324]) - Make `spectrum` and `correlation` functions align with `Python QuTiP`, introduce spectrum solver `PseudoInverse`, remove spectrum solver `FFTCorrelation`, and introduce `spectrum_correlation_fft`. ([#330]) -## [v0.22.0] (2024-11-20) +## [v0.22.0] +Release date: 2024-11-20 - Change the parameters structure of `sesolve`, `mesolve` and `mcsolve` functions to possibly support automatic differentiation. ([#311]) - Fix type instability and reduce extra memory allocation in `liouvillian`. ([#315], [#318]) -## [v0.21.5] (2024-11-15) +## [v0.21.5] +Release date: 2024-11-15 - This is a demonstration of how to bump version number and also modify `CHANGELOG.md` before new release. ([#309]) -## [v0.21.4] (2024-11-13) +## [v0.21.4] +Release date: 2024-11-13 - This is just a demonstration about [`Changelog.jl`](https://github.com/JuliaDocs/Changelog.jl). ([#139], [#306]) diff --git a/docs/make.jl b/docs/make.jl index 25d910cc1..9e281d9cd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -9,7 +9,9 @@ using Changelog DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) -const DRAFT = false # set `true` to disable cell evaluation +# some options for `makedocs` +const DRAFT = false # set `true` to disable cell evaluation +const DOCTEST = true # set `false` to skip doc tests # generate bibliography bib = CitationBibliography( @@ -83,6 +85,7 @@ makedocs(; repo = "github.com/qutip/QuantumToolbox.jl", ), draft = DRAFT, + doctest = DOCTEST, plugins = [bib], ) From 0f3ba82601269a9afdcdb66bf0a83270430b4a1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 05:44:18 +0000 Subject: [PATCH 146/329] Bump crate-ci/typos from 1.28.1 to 1.28.2 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.1 to 1.28.2. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/v1.28.1...v1.28.2) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 40772cfdb..195c2c3bc 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.28.1 \ No newline at end of file + uses: crate-ci/typos@v1.28.2 \ No newline at end of file From 5e108e19a52715915e236f37f7a300069d342ef3 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:18:21 +0900 Subject: [PATCH 147/329] Improve the construction of `QobjEvo` (#339) --- CHANGELOG.md | 4 +++- src/qobj/quantum_object_evo.jl | 30 ++++++++++++++++++++++++--- src/time_evolution/sesolve.jl | 3 ++- test/core-test/quantum_objects_evo.jl | 17 +++++++++++++-- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b61c2caa..94fb28eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - +- Improve the construction of `QobjEvo`. ([#338], [#339]) ## [v0.23.1] Release date: 2024-12-06 @@ -53,3 +53,5 @@ Release date: 2024-11-13 [#324]: https://github.com/qutip/QuantumToolbox.jl/issues/324 [#330]: https://github.com/qutip/QuantumToolbox.jl/issues/330 [#335]: https://github.com/qutip/QuantumToolbox.jl/issues/335 +[#338]: https://github.com/qutip/QuantumToolbox.jl/issues/338 +[#339]: https://github.com/qutip/QuantumToolbox.jl/issues/339 diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index cec0010e5..ddef77646 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -180,8 +180,19 @@ function QuantumObjectEvolution( return QuantumObjectEvolution(data, type, dims) end -QuantumObjectEvolution(op::QuantumObject, f::Function; type::Union{Nothing,QuantumObjectType} = nothing) = - QuantumObjectEvolution(((op, f),); type = type) +# this is a extra method if user accidentally specify `QuantumObjectEvolution( (op, func) )` or `QuantumObjectEvolution( ((op, func)) )` +QuantumObjectEvolution( + op_func::Tuple{QuantumObject,Function}, + α::Union{Nothing,Number} = nothing; + type::Union{Nothing,QuantumObjectType} = nothing, +) = QuantumObjectEvolution((op_func,), α; type = type) + +QuantumObjectEvolution( + op::QuantumObject, + f::Function, + α::Union{Nothing,Number} = nothing; + type::Union{Nothing,QuantumObjectType} = nothing, +) = QuantumObjectEvolution(((op, f),), α; type = type) function QuantumObjectEvolution( op::QuantumObject, @@ -229,6 +240,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` N = length(op_func_list_types) dims_expr = () + func_methods_expr = () first_op = nothing data_expr = :(0) qobj_expr_const = :(0) @@ -236,6 +248,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` for i in 1:N op_func_type = op_func_list_types[i] if op_func_type <: Tuple + # check the structure of the tuple length(op_func_type.parameters) == 2 || throw(ArgumentError("The tuple must have two elements.")) op_type = op_func_type.parameters[1] func_type = op_func_type.parameters[2] @@ -248,6 +261,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` op = :(op_func_list[$i][1]) data_type = op_type.parameters[1] dims_expr = (dims_expr..., :($op.dims)) + func_methods_expr = (func_methods_expr..., :(methods(op_func_list[$i][2], [Any, Real]))) # [Any, Real] means each func must accept 2 arguments if i == 1 first_op = :($op) end @@ -267,10 +281,20 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` end quote + # check the dims of the operators dims = tuple($(dims_expr...)) - allequal(dims) || throw(ArgumentError("The dimensions of the operators must be the same.")) + # check if each func accepts 2 arguments + func_methods = tuple($(func_methods_expr...)) + for f_method in func_methods + length(f_method.ms) == 0 && throw( + ArgumentError( + "The following function must accept two arguments: `$(f_method.mt.name)(p, t)` with t<:Real", + ), + ) + end + data_expr_const = $qobj_expr_const isa Integer ? $qobj_expr_const : _make_SciMLOperator($qobj_expr_const, α) data_expr = data_expr_const + $data_expr diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 0c5a3305f..28562e280 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -2,7 +2,8 @@ export sesolveProblem, sesolve _sesolve_make_U_QobjEvo(H::QuantumObjectEvolution{<:MatrixOperator}) = QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dims, type = Operator) -_sesolve_make_U_QobjEvo(H) = QobjEvo(H, -1im) +_sesolve_make_U_QobjEvo(H::QuantumObject) = QobjEvo(MatrixOperator(-1im * H.data), dims = H.dims, type = Operator) +_sesolve_make_U_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}) = QobjEvo(H, -1im) @doc raw""" sesolveProblem( diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index d714abb6e..a10416a84 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -119,9 +119,9 @@ "Quantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst\n$datastring" end - @testset "Type Inference (QuantumObject)" begin + @testset "Type Inference (QobjEvo)" begin + N = 4 for T in [ComplexF32, ComplexF64] - N = 4 a = MatrixOperator(rand(T, N, N)) @inferred QobjEvo(a) for type in [Operator, SuperOperator] @@ -129,6 +129,14 @@ end end + a = destroy(N) + coef1(p, t) = exp(-t) + coef2(p::Vector, t) = sin(p[1] * t) + coef3(p::NamedTuple, t) = cos(p.ω * t) + @inferred QobjEvo(a, coef1) + @inferred QobjEvo((a', coef2)) + @inferred QobjEvo((a' * a, (a, coef1), (a', coef2), (a + a', coef3))) + @testset "Math Operation" begin a = QobjEvo(destroy(20)) σx = QobjEvo(sigmax()) @@ -182,6 +190,7 @@ @test isconstant(H_td) == false @test isconstant(QobjEvo(a)) == true @test isoper(H_td) == true + @test QobjEvo(a, coef1) == QobjEvo((a, coef1)) # SuperOperator X = a * a' @@ -205,7 +214,11 @@ @test isconstant(L_td) == false @test issuper(L_td) == true + coef_wrong1(t) = nothing + coef_wrong2(p, t::ComplexF64) = nothing @test_logs (:warn,) (:warn,) liouvillian(H_td * H_td) # warnings from lazy tensor + @test_throws ArgumentError QobjEvo(a, coef_wrong1) + @test_throws ArgumentError QobjEvo(a, coef_wrong2) @test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]]) @test_throws ArgumentError H_td(ρvec, p, t) @test_throws ArgumentError cache_operator(H_td, ρvec) From e754148481e5fe4d69d276cf00068303b8c20cbd Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:08:32 +0900 Subject: [PATCH 148/329] Support `zero` and `one` for `AbstractQuantumObject` (#346) * support `zero` and `one` for `AbstractQuantumObject` * update changelog * fix changelog --- CHANGELOG.md | 3 +++ docs/src/resources/api.md | 2 ++ .../QuantumObject/QuantumObject_functions.md | 2 ++ src/qobj/arithmetic_and_attributes.jl | 19 +++++++++++++++++++ test/core-test/quantum_objects.jl | 8 ++++++++ test/core-test/quantum_objects_evo.jl | 8 ++++++++ test/runtests.jl | 2 +- 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94fb28eea..21d5d665b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Improve the construction of `QobjEvo`. ([#338], [#339]) +- Support `Base.zero` and `Base.one` for `AbstractQuantumObject`. ([#342], [#346]) ## [v0.23.1] Release date: 2024-12-06 @@ -55,3 +56,5 @@ Release date: 2024-11-13 [#335]: https://github.com/qutip/QuantumToolbox.jl/issues/335 [#338]: https://github.com/qutip/QuantumToolbox.jl/issues/338 [#339]: https://github.com/qutip/QuantumToolbox.jl/issues/339 +[#342]: https://github.com/qutip/QuantumToolbox.jl/issues/342 +[#346]: https://github.com/qutip/QuantumToolbox.jl/issues/346 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index d20fc6805..5e7c0027c 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -52,6 +52,8 @@ SciMLOperators.isconstant ## [Qobj arithmetic and attributes](@id doc-API:Qobj-arithmetic-and-attributes) ```@docs +Base.zero +Base.one Base.conj LinearAlgebra.transpose LinearAlgebra.adjoint diff --git a/docs/src/users_guide/QuantumObject/QuantumObject_functions.md b/docs/src/users_guide/QuantumObject/QuantumObject_functions.md index 698017dce..ac2c62698 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject_functions.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject_functions.md @@ -10,6 +10,8 @@ Here is a table that summarizes all the supported linear algebra functions and a | **Description** | **Function call** | **Synonyms** | |:----------------|:------------------|:-------------| +| zero-like array | [`zero(Q)`](@ref zero) | - | +| identity-like matrix | [`one(Q)`](@ref one) | - | | conjugate | [`conj(Q)`](@ref conj) | - | | transpose | [`transpose(Q)`](@ref transpose) | [`trans(Q)`](@ref trans) | | conjugate transposition | [`adjoint(Q)`](@ref adjoint) | [`Q'`](@ref adjoint), [`dag(Q)`](@ref dag) | diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 5a9c7d981..6a106148d 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -156,6 +156,25 @@ function LinearAlgebra.dot( return LinearAlgebra.dot(i.data, A.data, j.data) end +@doc raw""" + zero(A::AbstractQuantumObject) + +Return a similar [`AbstractQuantumObject`](@ref) with `dims` and `type` are same as `A`, but `data` is a zero-array. +""" +Base.zero(A::AbstractQuantumObject) = get_typename_wrapper(A)(zero(A.data), A.type, A.dims) + +@doc raw""" + one(A::AbstractQuantumObject) + +Return a similar [`AbstractQuantumObject`](@ref) with `dims` and `type` are same as `A`, but `data` is an identity matrix. + +Note that `A` must be [`Operator`](@ref) or [`SuperOperator`](@ref). +""" +Base.one( + A::AbstractQuantumObject{DT,OpType}, +) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + get_typename_wrapper(A)(one(A.data), A.type, A.dims) + @doc raw""" conj(A::AbstractQuantumObject) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 36e720b51..6d26ac4c2 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -166,6 +166,14 @@ @test (a2 + 2).data == a2.data + 2 * I @test a2 * 2 == 2 * a2 + zero_like = zero(a2) + iden_like = one(a3) + zero_array = spzeros(ComplexF64, 100, 100) + iden_array = sparse(1:100, 1:100, ones(ComplexF64, 100)) + @test zero_like == Qobj(zero_array, type = a2.type, dims = a2.dims) + @test typeof(zero_like.data) == typeof(zero_array) + @test iden_like == Qobj(iden_array, type = a3.type, dims = a3.dims) + @test typeof(iden_like.data) == typeof(iden_array) @test trans(trans(a2)) == a2 @test trans(a2).data == transpose(a2.data) @test adjoint(a2) ≈ trans(conj(a2)) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index a10416a84..5910b0e5a 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -74,6 +74,14 @@ @test (a2 + 2).data == a2.data + 2 * I @test a2 * 2 == 2 * a2 + zero_like = zero(a2) + iden_like = one(a3) + zero_array = NullOperator(100) + iden_array = IdentityOperator(100) + @test zero_like == QobjEvo(zero_array, type = a2.type, dims = a2.dims) + @test typeof(zero_like.data) == typeof(zero_array) + @test iden_like == QobjEvo(iden_array, type = a3.type, dims = a3.dims) + @test typeof(iden_like.data) == typeof(iden_array) @test trans(trans(a2)) == a2 @test trans(a2).data == transpose(a2.data) # @test adjoint(a2) ≈ trans(conj(a2)) # Currently doesn't work diff --git a/test/runtests.jl b/test/runtests.jl index 20f72b057..43b4fc75b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,7 +37,7 @@ if (GROUP == "All") || (GROUP == "Core") using QuantumToolbox import QuantumToolbox: position, momentum import Random: MersenneTwister - import SciMLOperators: MatrixOperator + import SciMLOperators: MatrixOperator, NullOperator, IdentityOperator QuantumToolbox.about() From 38a473c91e4854144449e89ccb3079ea8bc3624c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:08:01 +0900 Subject: [PATCH 149/329] Introduce visualization (#347) * Add Wigner plotting via CairoMakie * CairoMakie default lib * docs * formatting * Fix typo * Add error handling for unavailable plotting libraries in plot_wigner function * Docs * Update CairoMakie dependency and add CairoMakie extension tests * Update documentation to reflect changes in Wigner function plotting and computation * formatting * Update changelog * Improve docs * Update CairoMakie imports and change axis labels * fix CHANGELOG * setup CI pipeline config for `CairoMakie` extension * rebase accidently removed lines * fix typo * add `CairoMakie` extension doc page * fix typo * minor change in docs * minor change in docs --------- Co-authored-by: Lorenzo Fioroni Co-authored-by: Alberto Mercurio --- .github/workflows/CI.yml | 11 +- CHANGELOG.md | 4 + Project.toml | 6 +- docs/make.jl | 9 +- docs/src/resources/api.md | 6 + docs/src/tutorials/logo.md | 24 +- docs/src/users_guide/extensions/cairomakie.md | 21 ++ ext/QuantumToolboxCairoMakieExt.jl | 215 ++++++++++++++++++ src/QuantumToolbox.jl | 1 + src/visualization.jl | 37 +++ test/ext-test/cairomakie_ext.jl | 63 +++++ test/runtests.jl | 9 + 12 files changed, 385 insertions(+), 21 deletions(-) create mode 100644 docs/src/users_guide/extensions/cairomakie.md create mode 100644 ext/QuantumToolboxCairoMakieExt.jl create mode 100644 src/visualization.jl create mode 100644 test/ext-test/cairomakie_ext.jl diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 309afadea..0d7e6fe30 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,6 +10,7 @@ on: - 'ext/**' - 'test/runtests.jl' - 'test/core-test/**' + - 'test/ext-test/cairomakie_ext.jl' - 'Project.toml' pull_request: branches: @@ -20,6 +21,7 @@ on: - 'ext/**' - 'test/runtests.jl' - 'test/core-test/**' + - 'test/ext-test/cairomakie_ext.jl' - 'Project.toml' types: - opened @@ -52,7 +54,7 @@ jobs: group: - 'Core' - # include: + include: # for core tests (intermediate versions) # - version: '1.x' # node: @@ -60,6 +62,13 @@ jobs: # arch: 'x64' # group: 'Core' + # for extension tests + - version: '1' + node: + os: 'ubuntu-latest' + arch: 'x64' + group: 'CairoMakie_Ext' + steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d5d665b..0b99342e5 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 - Improve the construction of `QobjEvo`. ([#338], [#339]) - Support `Base.zero` and `Base.one` for `AbstractQuantumObject`. ([#342], [#346]) +- Introduce visualization and function `plot_wigner` for easy plotting of Wigner functions. ([#86], [#292], [#347]) ## [v0.23.1] Release date: 2024-12-06 @@ -45,7 +46,9 @@ Release date: 2024-11-13 [v0.22.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.22.0 [v0.23.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.0 [v0.23.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.1 +[#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 +[#292]: https://github.com/qutip/QuantumToolbox.jl/issues/292 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 [#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 [#311]: https://github.com/qutip/QuantumToolbox.jl/issues/311 @@ -58,3 +61,4 @@ Release date: 2024-11-13 [#339]: https://github.com/qutip/QuantumToolbox.jl/issues/339 [#342]: https://github.com/qutip/QuantumToolbox.jl/issues/342 [#346]: https://github.com/qutip/QuantumToolbox.jl/issues/346 +[#347]: https://github.com/qutip/QuantumToolbox.jl/issues/347 diff --git a/Project.toml b/Project.toml index 711c4828d..9b30f61b7 100644 --- a/Project.toml +++ b/Project.toml @@ -28,14 +28,17 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" [weakdeps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" [extensions] QuantumToolboxCUDAExt = "CUDA" +QuantumToolboxCairoMakieExt = "CairoMakie" [compat] Aqua = "0.8" ArrayInterface = "6, 7" CUDA = "5" +CairoMakie = "0.12" DiffEqBase = "6" DiffEqCallbacks = "4.2.1 - 4" DiffEqNoiseProcess = "5" @@ -62,8 +65,9 @@ julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "JET", "Test"] +test = ["Aqua", "CairoMakie", "JET", "Test"] diff --git a/docs/make.jl b/docs/make.jl index 9e281d9cd..56b374c44 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,6 +7,9 @@ using DocumenterVitepress using DocumenterCitations using Changelog +# Load of packages required to compile the extension documentation +using CairoMakie + DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) # some options for `makedocs` @@ -56,6 +59,7 @@ const PAGES = [ "Two-time correlation functions" => "users_guide/two_time_corr_func.md", "Extensions" => [ "users_guide/extensions/cuda.md", + "users_guide/extensions/cairomakie.md", ], ], "Tutorials" => [ @@ -76,7 +80,10 @@ const PAGES = [ ] makedocs(; - modules = [QuantumToolbox], + modules = [ + QuantumToolbox, + Base.get_extension(QuantumToolbox, :QuantumToolboxCairoMakieExt), + ], authors = "Alberto Mercurio and Yi-Te Huang", repo = Remotes.GitHub("qutip", "QuantumToolbox.jl"), sitename = "QuantumToolbox.jl", diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 5e7c0027c..900fed82d 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -283,3 +283,9 @@ convert_unit row_major_reshape meshgrid ``` + +## [Visualization](@id doc-API:Visualization) + +```@docs +plot_wigner +``` diff --git a/docs/src/tutorials/logo.md b/docs/src/tutorials/logo.md index b68386bce..12be51567 100644 --- a/docs/src/tutorials/logo.md +++ b/docs/src/tutorials/logo.md @@ -67,25 +67,16 @@ Next, we construct the triangular cat state as a normalized superposition of thr normalize!(ψ) ``` -### Defining the Grid and calculating the Wigner function +### Defining the Grid and plotting the Wigner function -We define the grid for the Wigner function and calculate it using the [`wigner`](@ref) function. We shift the grid in the imaginary direction to ensure that the Wigner function is centered around the origin of the figure. The [`wigner`](@ref) function also supports the `g` scaling factor, which we put here equal to ``2``. +We define the grid for the Wigner function and plot it using the [`plot_wigner`](@ref) function. This, internally calls the [`wigner`](@ref) function for the computation. We shift the grid in the imaginary direction to ensure that the Wigner function is centered around the origin of the figure. The [`wigner`](@ref) function also supports the `g` scaling factor, which we put here equal to ``2``. ```@example logo xvec = range(-ρ, ρ, 500) .* 1.5 yvec = xvec .+ (abs(imag(α1)) - abs(imag(α2))) / 2 -wig = wigner(ψ, xvec, yvec, g = 2) -``` - -### Plotting the Wigner function - -Finally, we plot the Wigner function using the `heatmap` function from the `CairoMakie` package. - -```@example logo fig = Figure(size = (250, 250), figure_padding = 0) -ax = Axis(fig[1, 1]) -heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1) +fig, ax, hm = plot_wigner(ψ, xvec = xvec, yvec = yvec, g = 2, library = Val(:CairoMakie), location = fig[1,1]) hidespines!(ax) hidexdecorations!(ax) hideydecorations!(ax) @@ -118,12 +109,8 @@ nothing # hide And the Wigner function becomes more uniform: ```@example logo -wig = wigner(sol.states[end], xvec, yvec, g = 2) - fig = Figure(size = (250, 250), figure_padding = 0) -ax = Axis(fig[1, 1]) - -img_wig = heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1) +fig, ax, hm = plot_wigner(sol.states[end], xvec = xvec, yvec = yvec, g = 2, library = Val(:CairoMakie), location = fig[1,1]) hidespines!(ax) hidexdecorations!(ax) hideydecorations!(ax) @@ -135,7 +122,7 @@ At this stage, we have finished to use the `QuantumToolbox` package. From now on ### Custom Colormap -We define a custom colormap that changes depending on the Wigner function and spatial coordinates. Indeed, we want the three different colormaps, in the regions corresponding to the three coherent states, to match the colors of the Julia logo. We also want the colormap change to be smooth, so we use a Gaussian function to blend the colors. We introduce also a Wigner function dependent transparency to make the logo more appealing. +We define a custom colormap that changes depending on the Wigner function and spatial coordinates. Indeed, we want the three different colormaps, in the regions corresponding to the three coherent states, to match the colors of the Julia logo. We also want the colormap change to be smooth, so we use a Gaussian function to blend the colors. We introduce also a Wigner function dependent transparency to make the logo more appealing. In order to do so, we are going to need the value of the wigner function at each point of the grid, rather than its plot. We will thus call the [`wigner`](@ref) function directly. ```@example logo function set_color_julia(x, y, wig::T, α1, α2, α3, cmap1, cmap2, cmap3, δ) where {T} @@ -156,6 +143,7 @@ function set_color_julia(x, y, wig::T, α1, α2, α3, cmap1, cmap2, cmap3, δ) w return RGBAf(c_tot.r, c_tot.g, c_tot.b, alpha) end +wig = wigner(sol.states[end], xvec, yvec, g = 2) X, Y = meshgrid(xvec, yvec) δ = 1.25 # Smoothing parameter for the Gaussian functions ``` diff --git a/docs/src/users_guide/extensions/cairomakie.md b/docs/src/users_guide/extensions/cairomakie.md new file mode 100644 index 000000000..e847dd36c --- /dev/null +++ b/docs/src/users_guide/extensions/cairomakie.md @@ -0,0 +1,21 @@ +# [Extension for CairoMakie.jl](@id doc:CairoMakie) + +This is an extension to support visualization (plotting functions) using [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) library. + +This extension will be automatically loaded if user imports both `QuantumToolbox.jl` and [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie): + +```julia +using QuantumToolbox +using CairoMakie +``` + +To plot with [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) library, specify the keyword argument `library = Val(:CairoMakie)` for the plotting functions. + +!!! warning "Beware of type-stability!" + If you want to keep type stability, it is recommended to use `Val(:CairoMakie)` instead of `:CairoMakie`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + +The supported plotting functions are listed as follows: + +| **Plotting Function** | **Description** | +|:----------------------|:----------------| +| [`plot_wigner`](@ref) | [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) | \ No newline at end of file diff --git a/ext/QuantumToolboxCairoMakieExt.jl b/ext/QuantumToolboxCairoMakieExt.jl new file mode 100644 index 000000000..f5a7a2272 --- /dev/null +++ b/ext/QuantumToolboxCairoMakieExt.jl @@ -0,0 +1,215 @@ +module QuantumToolboxCairoMakieExt + +using QuantumToolbox +using CairoMakie: Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, GridPosition, @L_str, Reverse + +@doc raw""" + plot_wigner( + library::Val{:CairoMakie}, + state::QuantumObject{DT,OpType}; + xvec::Union{Nothing,AbstractVector} = nothing, + yvec::Union{Nothing,AbstractVector} = nothing, + g::Real = √2, + method::WignerSolver = WignerClenshaw(), + projection::Union{Val,Symbol} = Val(:two_dim), + location::Union{GridPosition,Nothing} = nothing, + colorbar::Bool = false, + kwargs... + ) where {DT,OpType} + +Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) plotting library. + +# Arguments +- `library::Val{:CairoMakie}`: The plotting library to use. +- `state::QuantumObject`: The quantum state for which the Wigner function is calculated. It can be either a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). +- `xvec::AbstractVector`: The x-coordinates of the phase space grid. Defaults to a linear range from -7.5 to 7.5 with 200 points. +- `yvec::AbstractVector`: The y-coordinates of the phase space grid. Defaults to a linear range from -7.5 to 7.5 with 200 points. +- `g::Real`: The scaling factor related to the value of ``\hbar`` in the commutation relation ``[x, y] = i \hbar`` via ``\hbar=2/g^2``. +- `method::WignerSolver`: The method used to calculate the Wigner function. It can be either `WignerLaguerre()` or `WignerClenshaw()`, with `WignerClenshaw()` as default. The `WignerLaguerre` method has the optional `parallel` and `tol` parameters, with default values `true` and `1e-14`, respectively. +- `projection::Union{Val,Symbol}`: Whether to plot the Wigner function in 2D or 3D. It can be either `Val(:two_dim)` or `Val(:three_dim)`, with `Val(:two_dim)` as default. +- `location::Union{GridPosition,Nothing}`: The location of the plot in the layout. If `nothing`, the plot is created in a new figure. Default is `nothing`. +- `colorbar::Bool`: Whether to include a colorbar in the plot. Default is `false`. +- `kwargs...`: Additional keyword arguments to pass to the plotting function. + +# Returns +- `fig`: The figure object. +- `ax`: The axis object. +- `hm`: Either the heatmap or surface object, depending on the projection. + +!!! note "Import library first" + [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) must first be imported before using this function. + +!!! warning "Beware of type-stability!" + If you want to keep type stability, it is recommended to use `Val(:two_dim)` and `Val(:three_dim)` instead of `:two_dim` and `:three_dim`, respectively. Also, specify the library as `Val(:CairoMakie)` See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +function QuantumToolbox.plot_wigner( + library::Val{:CairoMakie}, + state::QuantumObject{DT,OpType}; + xvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200), + yvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200), + g::Real = √2, + method::WignerSolver = WignerClenshaw(), + projection::Union{Val,Symbol} = Val(:two_dim), + location::Union{GridPosition,Nothing} = nothing, + colorbar::Bool = false, + kwargs..., +) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} + QuantumToolbox.getVal(projection) == :two_dim || + QuantumToolbox.getVal(projection) == :three_dim || + throw(ArgumentError("Unsupported projection: $projection")) + + return _plot_wigner( + library, + state, + xvec, + yvec, + QuantumToolbox.makeVal(projection), + g, + method, + location, + colorbar; + kwargs..., + ) +end + +function _plot_wigner( + ::Val{:CairoMakie}, + state::QuantumObject{DT,OpType}, + xvec::AbstractVector, + yvec::AbstractVector, + projection::Val{:two_dim}, + g::Real, + method::WignerSolver, + location::Union{GridPosition,Nothing}, + colorbar::Bool; + kwargs..., +) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} + fig, location = _getFigAndLocation(location) + + lyt = GridLayout(location) + + ax = Axis(lyt[1, 1]) + + wig = wigner(state, xvec, yvec; g = g, method = method) + wlim = maximum(abs, wig) + + kwargs = merge(Dict(:colormap => Reverse(:RdBu), :colorrange => (-wlim, wlim)), kwargs) + hm = heatmap!(ax, xvec, yvec, transpose(wig); kwargs...) + + if colorbar + Colorbar(lyt[1, 2], hm) + end + + ax.xlabel = L"\textrm{Re}(\alpha)" + ax.ylabel = L"\textrm{Im}(\alpha)" + return fig, ax, hm +end + +function _plot_wigner( + ::Val{:CairoMakie}, + state::QuantumObject{DT,OpType}, + xvec::AbstractVector, + yvec::AbstractVector, + projection::Val{:three_dim}, + g::Real, + method::WignerSolver, + location::Union{GridPosition,Nothing}, + colorbar::Bool; + kwargs..., +) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} + fig, location = _getFigAndLocation(location) + + lyt = GridLayout(location) + + ax = Axis3(lyt[1, 1], azimuth = 1.775pi, elevation = pi / 16, protrusions = (30, 90, 30, 30), viewmode = :stretch) + + wig = wigner(state, xvec, yvec; g = g, method = method) + wlim = maximum(abs, wig) + + kwargs = merge(Dict(:colormap => :RdBu, :colorrange => (-wlim, wlim)), kwargs) + surf = surface!(ax, xvec, yvec, transpose(wig); kwargs...) + + if colorbar + Colorbar(lyt[1, 2], surf) + end + + ax.xlabel = L"\textrm{Re}(\alpha)" + ax.ylabel = L"\textrm{Im}(\alpha)" + ax.zlabel = "Wigner function" + return fig, ax, surf +end + +raw""" + _getFigAndLocation(location::Nothing) + + Create a new figure and return it, together with the GridPosition object pointing to the first cell. + + # Arguments + - `location::Nothing` + + # Returns + - `fig`: The figure object. + - `location`: The GridPosition object pointing to the first cell. +""" +function _getFigAndLocation(location::Nothing) + fig = Figure() + return fig, fig[1, 1] +end + +raw""" + _getFigAndLocation(location::GridPosition) + + Compute which figure does the location belong to and return it, together with the location itself. + + # Arguments + - `location::GridPosition` + + # Returns + - `fig`: The figure object. + - `location`: The GridPosition object. +""" +function _getFigAndLocation(location::GridPosition) + fig = _figFromChildren(location.layout) + return fig, location +end + +raw""" + _figFromChildren(children::GridLayout) + + Recursively find the figure object from the children layout. + + # Arguments + - `children::GridLayout` + + # Returns + - Union{Nothing, Figure, GridLayout}: The children's parent object. +""" +_figFromChildren(children) = _figFromChildren(children.parent) + +raw""" + _figFromChildren(fig::Figure) + + Return the figure object + + # Arguments + - `fig::Figure` + + # Returns + - `fig`: The figure object. +""" +_figFromChildren(fig::Figure) = fig + +raw""" + _figFromChildren(::Nothing) + + Throw an error if no figure has been found. + + # Arguments + - `::Nothing` + + # Throws + - `ArgumentError`: If no figure has been found. +""" +_figFromChildren(::Nothing) = throw(ArgumentError("No Figure has been found at the top of the layout hierarchy.")) + +end diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 6b711223d..ad387e261 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -114,6 +114,7 @@ include("arnoldi.jl") include("metrics.jl") include("negativity.jl") include("steadystate.jl") +include("visualization.jl") # deprecated functions include("deprecated.jl") diff --git a/src/visualization.jl b/src/visualization.jl new file mode 100644 index 000000000..92ca2f67e --- /dev/null +++ b/src/visualization.jl @@ -0,0 +1,37 @@ +export plot_wigner + +@doc raw""" + plot_wigner( + state::QuantumObject{DT,OpType}; + library::Union{Val,Symbol}=Val(:CairoMakie), + kwargs... + ) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject} + +Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`wigner`](@ref) function. + +The `library` keyword argument specifies the plotting library to use, defaulting to [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie). + +# Arguments +- `state::QuantumObject{DT,OpType}`: The quantum state for which to plot the Wigner distribution. +- `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:CairoMakie)`. +- `kwargs...`: Additional keyword arguments to pass to the plotting function. See the documentation for the specific plotting library for more information. + +!!! note "Import library first" + The plotting libraries must first be imported before using them with this function. + +!!! warning "Beware of type-stability!" + If you want to keep type stability, it is recommended to use `Val(:CairoMakie)` instead of `:CairoMakie` as the plotting library. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +plot_wigner( + state::QuantumObject{DT,OpType}; + library::Union{Val,Symbol} = Val(:CairoMakie), + kwargs..., +) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = + plot_wigner(makeVal(library), state; kwargs...) + +plot_wigner( + ::Val{T}, + state::QuantumObject{DT,OpType}; + kwargs..., +) where {T,DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = + throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) diff --git a/test/ext-test/cairomakie_ext.jl b/test/ext-test/cairomakie_ext.jl new file mode 100644 index 000000000..2571e02d1 --- /dev/null +++ b/test/ext-test/cairomakie_ext.jl @@ -0,0 +1,63 @@ +@testset "CairoMakie Extension" verbose = true begin + ψ = normalize(coherent(50, 5.0) + coherent(50, -5.0)) + xvec = yvec = -15.0:0.1:15.0 + wig = transpose(wigner(ψ, xvec, yvec)) + + @test_throws ArgumentError plot_wigner(ψ; library = :CairoMakie, xvec = xvec, yvec = yvec) + + using CairoMakie + + fig, ax, hm = plot_wigner( + ψ; + library = Val(:CairoMakie), + xvec = xvec, + yvec = yvec, + projection = Val(:two_dim), + colorbar = true, + ) + @test fig isa Figure + @test ax isa Axis + @test hm isa Heatmap + @test all(isapprox.(hm[3].val, wig, atol = 1e-6)) + + fig, ax, surf = plot_wigner( + ψ; + library = Val(:CairoMakie), + xvec = xvec, + yvec = yvec, + projection = Val(:three_dim), + colorbar = true, + ) + @test fig isa Figure + @test ax isa Axis3 + @test surf isa Surface + @test all(isapprox.(surf[3].val, wig, atol = 1e-6)) + + fig = Figure() + pos = fig[2, 3] + fig1, ax, hm = plot_wigner( + ψ; + library = Val(:CairoMakie), + xvec = xvec, + yvec = yvec, + projection = Val(:two_dim), + colorbar = true, + location = pos, + ) + @test fig1 === fig + @test fig[2, 3].layout.content[1].content[1, 1].layout.content[1].content === ax + + fig = Figure() + pos = fig[2, 3] + fig1, ax, surf = plot_wigner( + ψ; + library = Val(:CairoMakie), + xvec = xvec, + yvec = yvec, + projection = Val(:three_dim), + colorbar = true, + location = pos, + ) + @test fig1 === fig + @test fig[2, 3].layout.content[1].content[1, 1].layout.content[1].content === ax +end diff --git a/test/runtests.jl b/test/runtests.jl index 43b4fc75b..502aa43cc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -46,6 +46,15 @@ if (GROUP == "All") || (GROUP == "Core") end end +if (GROUP == "All") || (GROUP == "CairoMakie_Ext") + using QuantumToolbox + + (GROUP == "CairoMakie_Ext") && QuantumToolbox.about() + + # CarioMakie is imported in the following script + include(joinpath(testdir, "ext-test", "cairomakie_ext.jl")) +end + if (GROUP == "CUDA_Ext")# || (GROUP == "All") Pkg.activate("ext-test/gpu") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) From 91cf21140bc5f67e61efaa5a85695b81b75dcb19 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:12:49 +0100 Subject: [PATCH 150/329] Bump version to v0.24.0 (#348) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b99342e5..a9938d93a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) + +## [v0.24.0] +Release date: 2024-12-13 + - Improve the construction of `QobjEvo`. ([#338], [#339]) - Support `Base.zero` and `Base.one` for `AbstractQuantumObject`. ([#342], [#346]) - Introduce visualization and function `plot_wigner` for easy plotting of Wigner functions. ([#86], [#292], [#347]) diff --git a/Project.toml b/Project.toml index 9b30f61b7..8b71ebaad 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.23.1" +version = "0.24.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 3ffdd3debfdb31e7b21794626467e4f93c5f1586 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 15 Dec 2024 10:19:41 +0100 Subject: [PATCH 151/329] Change block diagonal form structure (#349) * Change block diagonal form structure * Add changelog --- CHANGELOG.md | 4 ++ docs/src/resources/api.md | 7 +++ src/QuantumToolbox.jl | 2 +- src/permutation.jl | 38 ------------ src/qobj/block_diagonal_form.jl | 60 +++++++++++++++++++ ...{permutation.jl => block_diagonal_form.jl} | 16 ++--- test/runtests.jl | 2 +- 7 files changed, 82 insertions(+), 47 deletions(-) delete mode 100644 src/permutation.jl create mode 100644 src/qobj/block_diagonal_form.jl rename test/core-test/{permutation.jl => block_diagonal_form.jl} (62%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9938d93a..d61a642fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Change the structure of block diagonalization functions, using `BlockDiagonalForm` struct and changing the function name from `bdf` to `block_diagonal_form`. ([#349]) + ## [v0.24.0] Release date: 2024-12-13 @@ -50,6 +52,7 @@ Release date: 2024-11-13 [v0.22.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.22.0 [v0.23.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.0 [v0.23.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.1 +[v0.24.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.24.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#292]: https://github.com/qutip/QuantumToolbox.jl/issues/292 @@ -66,3 +69,4 @@ Release date: 2024-11-13 [#342]: https://github.com/qutip/QuantumToolbox.jl/issues/342 [#346]: https://github.com/qutip/QuantumToolbox.jl/issues/346 [#347]: https://github.com/qutip/QuantumToolbox.jl/issues/347 +[#349]: https://github.com/qutip/QuantumToolbox.jl/issues/349 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 900fed82d..b701d8e7f 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -258,6 +258,13 @@ MultiSiteOperator DissipativeIsing ``` +## [Symmetries and Block Diagonalization](@id doc-API:Symmetries-and-Block-Diagonalization) + +```@docs +block_diagonal_form +BlockDiagonalForm +``` + ## [Miscellaneous](@id doc-API:Miscellaneous) ```@docs diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index ad387e261..28a50f251 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -90,6 +90,7 @@ include("qobj/states.jl") include("qobj/operators.jl") include("qobj/superoperators.jl") include("qobj/synonyms.jl") +include("qobj/block_diagonal_form.jl") # time evolution include("time_evolution/time_evolution.jl") @@ -105,7 +106,6 @@ include("time_evolution/ssesolve.jl") include("time_evolution/time_evolution_dynamical.jl") # Others -include("permutation.jl") include("correlations.jl") include("spectrum.jl") include("wigner.jl") diff --git a/src/permutation.jl b/src/permutation.jl deleted file mode 100644 index 32715790f..000000000 --- a/src/permutation.jl +++ /dev/null @@ -1,38 +0,0 @@ -export bdf, get_bdf_blocks - -function bdf(A::SparseMatrixCSC{T,M}) where {T,M} - n = LinearAlgebra.checksquare(A) - - G = DiGraph(abs.(A) .> 0) - idxs = connected_components(G) - P = sparse(1:n, reduce(vcat, idxs), ones(n), n, n) - block_sizes = map(length, idxs) - - return P, P * A * P', block_sizes -end - -function bdf( - A::QuantumObject{SparseMatrixCSC{T,M},OpType}, -) where {T,M,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} - P, A_bd, block_sizes = bdf(A.data) - return P, QuantumObject(A_bd, A.type, A.dims), block_sizes -end - -function get_bdf_blocks(A::SparseMatrixCSC{T,M}, block_sizes::Vector{Int}) where {T,M} - num_blocks = length(block_sizes) - block_indices = M[1] - block_list = [A[1:block_sizes[1], 1:block_sizes[1]]] - for i in 2:num_blocks - idx = sum(view(block_sizes, 1:i-1)) + 1 - push!(block_indices, idx) - push!(block_list, A[idx:idx-1+block_sizes[i], idx:idx-1+block_sizes[i]]) - end - return block_list, block_indices -end - -function get_bdf_blocks( - A::QuantumObject{SparseMatrixCSC{T,M},OpType}, - block_sizes::Vector{Int}, -) where {T,M,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} - return get_bdf_blocks(A.data, block_sizes) -end diff --git a/src/qobj/block_diagonal_form.jl b/src/qobj/block_diagonal_form.jl new file mode 100644 index 000000000..cc1f97eaa --- /dev/null +++ b/src/qobj/block_diagonal_form.jl @@ -0,0 +1,60 @@ +export BlockDiagonalForm, block_diagonal_form + +@doc raw""" + struct BlockDiagonalForm + +A type for storing a block-diagonal form of a matrix. + +# Fields +- `B::DT`: The block-diagonal matrix. It can be a sparse matrix or a [`QuantumObject`](@ref). +- `P::DT`: The permutation matrix. It can be a sparse matrix or a [`QuantumObject`](@ref). +- `blocks::AbstractVector`: The blocks of the block-diagonal matrix. +- `block_sizes::AbstractVector`: The sizes of the blocks. +""" +struct BlockDiagonalForm{DT,BT<:AbstractVector,BST<:AbstractVector} + B::DT + P::DT + blocks::BT + block_sizes::BST +end + +function block_diagonal_form(A::MT) where {MT<:AbstractSparseMatrix} + n = LinearAlgebra.checksquare(A) + + G = DiGraph(abs.(A)) + idxs_list = connected_components(G) + block_sizes = length.(idxs_list) + + P = MT(sparse(1:n, reduce(vcat, idxs_list), ones(n), n, n)) + + blocks = map(eachindex(idxs_list)) do i + m = block_sizes[i] + idxs = idxs_list[i] + P_i = MT(sparse(1:m, idxs, ones(m), m, n)) + return P_i * A * P_i' + end + + B = P * A * P' + + return BlockDiagonalForm(B, P, blocks, block_sizes) +end + +@doc raw""" + block_diagonal_form(A::QuantumObject) + +Return the block-diagonal form of a [`QuantumObject`](@ref). This is very useful in the presence of symmetries. + +# Arguments +- `A::QuantumObject`: The quantum object. + +# Returns +The [`BlockDiagonalForm`](@ref) of `A`. +""" +function block_diagonal_form( + A::QuantumObject{DT,OpType}, +) where {DT<:AbstractSparseMatrix,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + bdf = block_diagonal_form(A.data) + B = QuantumObject(bdf.B, type = A.type, dims = A.dims) + P = QuantumObject(bdf.P, type = A.type, dims = A.dims) + return BlockDiagonalForm(B, P, bdf.blocks, bdf.block_sizes) +end diff --git a/test/core-test/permutation.jl b/test/core-test/block_diagonal_form.jl similarity index 62% rename from test/core-test/permutation.jl rename to test/core-test/block_diagonal_form.jl index 6f0892548..cc96a48d9 100644 --- a/test/core-test/permutation.jl +++ b/test/core-test/block_diagonal_form.jl @@ -1,4 +1,4 @@ -@testset "Permutation" begin +@testset "Block Diagonal Form" begin # Block Diagonal Form N = 20 Δ = 0 @@ -16,15 +16,17 @@ c_ops = [√(κ2) * a^2, √(κϕ) * ad * a] L = liouvillian(H, c_ops) - P, L_bd, block_sizes = bdf(L) - blocks_list, block_indices = get_bdf_blocks(L_bd, block_sizes) + bdf = block_diagonal_form(L) + L_bd = bdf.B + block_sizes = bdf.block_sizes + blocks = bdf.blocks + @test size(L_bd) == size(L) @test length(block_sizes) == 4 - @test length(blocks_list) == 4 - @test length(block_indices) == 4 + @test length(blocks) == 4 @test sum(block_sizes .== 100) == 4 - @testset "Type Inference (bdf)" begin - @inferred bdf(L) + @testset "Type Inference (block_diagonal_form)" begin + @inferred block_diagonal_form(L) end end diff --git a/test/runtests.jl b/test/runtests.jl index 502aa43cc..9081ad842 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,6 +7,7 @@ const testdir = dirname(@__FILE__) # Put core tests in alphabetical order core_tests = [ + "block_diagonal_form.jl", "correlations_and_spectrum.jl", "dynamical_fock_dimension_mesolve.jl", "dynamical-shifted-fock.jl", @@ -15,7 +16,6 @@ core_tests = [ "generalized_master_equation.jl", "low_rank_dynamics.jl", "negativity_and_partial_transpose.jl", - "permutation.jl", "progress_bar.jl", "quantum_objects.jl", "quantum_objects_evo.jl", From 1f857be3d80aff90b5d643820726db1f7790e04e Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 15 Dec 2024 13:53:49 +0100 Subject: [PATCH 152/329] Add `ptrace` support for any `GPUArray` (#350) * Add `ptrace` support for any `GPUArray` * Add changelog --------- Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> --- CHANGELOG.md | 3 +- Project.toml | 5 ++ ext/QuantumToolboxGPUArraysExt.jl | 33 ++++++++++ src/qobj/arithmetic_and_attributes.jl | 4 +- test/ext-test/gpu/cuda_ext.jl | 88 +++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 ext/QuantumToolboxGPUArraysExt.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index d61a642fb..3e3a8c0d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Change the structure of block diagonalization functions, using `BlockDiagonalForm` struct and changing the function name from `bdf` to `block_diagonal_form`. ([#349]) - +- Add **GPUArrays** compatibility for `ptrace` function, by using **KernelAbstractions.jl**. ([#350]) ## [v0.24.0] Release date: 2024-12-13 @@ -70,3 +70,4 @@ Release date: 2024-11-13 [#346]: https://github.com/qutip/QuantumToolbox.jl/issues/346 [#347]: https://github.com/qutip/QuantumToolbox.jl/issues/347 [#349]: https://github.com/qutip/QuantumToolbox.jl/issues/349 +[#350]: https://github.com/qutip/QuantumToolbox.jl/issues/350 diff --git a/Project.toml b/Project.toml index 8b71ebaad..32d1ad8e9 100644 --- a/Project.toml +++ b/Project.toml @@ -29,10 +29,13 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" [weakdeps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" [extensions] QuantumToolboxCUDAExt = "CUDA" QuantumToolboxCairoMakieExt = "CairoMakie" +QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] [compat] Aqua = "0.8" @@ -44,9 +47,11 @@ DiffEqCallbacks = "4.2.1 - 4" DiffEqNoiseProcess = "5" Distributed = "1" FFTW = "1.5" +GPUArrays = "10" Graphs = "1.7" IncompleteLU = "0.2" JET = "0.9" +KernelAbstractions = "0.9.2" LinearAlgebra = "1" LinearSolve = "2" OrdinaryDiffEqCore = "1" diff --git a/ext/QuantumToolboxGPUArraysExt.jl b/ext/QuantumToolboxGPUArraysExt.jl new file mode 100644 index 000000000..d9b686819 --- /dev/null +++ b/ext/QuantumToolboxGPUArraysExt.jl @@ -0,0 +1,33 @@ +module QuantumToolboxGPUArraysExt + +using QuantumToolbox + +import GPUArrays: AbstractGPUArray +import KernelAbstractions +import KernelAbstractions: @kernel, @Const, @index, get_backend, synchronize + +@kernel function tr_kernel!(B, @Const(A)) + # i, j, k = @index(Global, NTuple) + # Atomix.@atomic B[i, j] += A[i, j, k, k] # TODO: use Atomix when it will support Complex types + + i, j = @index(Global, NTuple) + @inbounds B[i, j] = 0 + @inbounds for k in 1:size(A, 3) + B[i, j] += A[i, j, k, k] + end +end + +function QuantumToolbox._map_trace(A::AbstractGPUArray{T,4}) where {T} + B = similar(A, size(A, 1), size(A, 2)) + fill!(B, 0) + + backend = get_backend(A) + kernel! = tr_kernel!(backend) + + kernel!(B, A, ndrange = size(A)[1:2]) + KernelAbstractions.synchronize(backend) + + return B +end + +end diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 6a106148d..a3baf2144 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -631,11 +631,13 @@ function _ptrace_oper(QO::AbstractArray, dims::Union{SVector,MVector}, sel) topermute = reverse(2 * n_d + 1 .- qtrace_sel) ρmat = permutedims(ρmat, topermute) # TODO: use PermutedDimsArray when Julia v1.11.0 is released ρmat = reshape(ρmat, prod(dkeep), prod(dkeep), prod(dtrace), prod(dtrace)) - res = map(tr, eachslice(ρmat, dims = (1, 2))) + res = _map_trace(ρmat) return res, dkeep end +_map_trace(A::AbstractArray{T,4}) where {T} = map(tr, eachslice(A, dims = (1, 2))) + @doc raw""" purity(ρ::QuantumObject) diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index 20219b424..5ed3ce18c 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -106,3 +106,91 @@ @test all([isapprox(sol_cpu.expect[i], sol_gpu64.expect[i]) for i in 1:length(tlist)]) @test all([isapprox(sol_cpu.expect[i], sol_gpu32.expect[i]; atol = 1e-6) for i in 1:length(tlist)]) end + +@testset "CUDA ptrace" begin + g = fock(2, 1) + e = fock(2, 0) + α = sqrt(0.7) + β = sqrt(0.3) * 1im + ψ = α * kron(g, e) + β * kron(e, g) |> cu + + ρ1 = ptrace(ψ, 1) + ρ2 = ptrace(ψ, 2) + @test ρ1.data isa CuArray + @test ρ2.data isa CuArray + @test Array(ρ1.data) ≈ [0.3 0.0; 0.0 0.7] atol = 1e-10 + @test Array(ρ2.data) ≈ [0.7 0.0; 0.0 0.3] atol = 1e-10 + + ψ_d = ψ' + + ρ1 = ptrace(ψ_d, 1) + ρ2 = ptrace(ψ_d, 2) + @test ρ1.data isa CuArray + @test ρ2.data isa CuArray + @test Array(ρ1.data) ≈ [0.3 0.0; 0.0 0.7] atol = 1e-10 + @test Array(ρ2.data) ≈ [0.7 0.0; 0.0 0.3] atol = 1e-10 + + ρ = ket2dm(ψ) + ρ1 = ptrace(ρ, 1) + ρ2 = ptrace(ρ, 2) + @test ρ.data isa CuArray + @test ρ1.data isa CuArray + @test ρ2.data isa CuArray + @test Array(ρ1.data) ≈ [0.3 0.0; 0.0 0.7] atol = 1e-10 + @test Array(ρ2.data) ≈ [0.7 0.0; 0.0 0.3] atol = 1e-10 + + ψ1 = normalize(g + 1im * e) + ψ2 = normalize(g + e) + ρ1 = ket2dm(ψ1) + ρ2 = ket2dm(ψ2) + ρ = kron(ρ1, ρ2) |> cu + ρ1_ptr = ptrace(ρ, 1) + ρ2_ptr = ptrace(ρ, 2) + @test ρ1_ptr.data isa CuArray + @test ρ2_ptr.data isa CuArray + @test ρ1.data ≈ Array(ρ1_ptr.data) atol = 1e-10 + @test ρ2.data ≈ Array(ρ2_ptr.data) atol = 1e-10 + + ψlist = [rand_ket(2), rand_ket(3), rand_ket(4), rand_ket(5)] + ρlist = [rand_dm(2), rand_dm(3), rand_dm(4), rand_dm(5)] + ψtotal = tensor(ψlist...) |> cu + ρtotal = tensor(ρlist...) |> cu + sel_tests = [ + SVector{0,Int}(), + 1, + 2, + 3, + 4, + (1, 2), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 4), + (1, 2, 3), + (1, 2, 4), + (1, 3, 4), + (2, 3, 4), + (1, 2, 3, 4), + ] + for sel in sel_tests + if length(sel) == 0 + @test ptrace(ψtotal, sel) ≈ 1.0 + @test ptrace(ρtotal, sel) ≈ 1.0 + else + @test ptrace(ψtotal, sel) ≈ cu(tensor([ket2dm(ψlist[i]) for i in sel]...)) + @test ptrace(ρtotal, sel) ≈ cu(tensor([ρlist[i] for i in sel]...)) + end + end + @test ptrace(ψtotal, (1, 3, 4)) ≈ ptrace(ψtotal, (4, 3, 1)) # check sort of sel + @test ptrace(ρtotal, (1, 3, 4)) ≈ ptrace(ρtotal, (3, 1, 4)) # check sort of sel + + @testset "Type Inference (ptrace)" begin + @inferred ptrace(ρ, 1) + @inferred ptrace(ρ, 2) + @inferred ptrace(ψ_d, 1) + @inferred ptrace(ψ_d, 2) + @inferred ptrace(ψ, 1) + @inferred ptrace(ψ, 2) + end +end From 887785ee394f4259f19ed56bb25293f3b45d471d Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Mon, 16 Dec 2024 01:06:23 +0000 Subject: [PATCH 153/329] CompatHelper: bump compat for GPUArrays in [weakdeps] to 11, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 32d1ad8e9..8c3759cc3 100644 --- a/Project.toml +++ b/Project.toml @@ -47,7 +47,7 @@ DiffEqCallbacks = "4.2.1 - 4" DiffEqNoiseProcess = "5" Distributed = "1" FFTW = "1.5" -GPUArrays = "10" +GPUArrays = "10, 11" Graphs = "1.7" IncompleteLU = "0.2" JET = "0.9" From 2eee58bfc3c271d80e56aab86f301d1b369d5ab1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 05:07:50 +0000 Subject: [PATCH 154/329] Bump crate-ci/typos from 1.28.2 to 1.28.3 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.2 to 1.28.3. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/v1.28.2...v1.28.3) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 195c2c3bc..d51382625 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.28.2 \ No newline at end of file + uses: crate-ci/typos@v1.28.3 \ No newline at end of file From 30aeb531326d15abe345e0f203970b3c8b8bc218 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:12:08 +0900 Subject: [PATCH 155/329] [Doc] Remove unnecessary URLs in bibtex (#345) --- docs/src/resources/bibliography.bib | 9 ++------- docs/src/resources/bibliography.md | 2 ++ docs/src/users_guide/HEOM.md | 4 ++-- docs/src/users_guide/states_and_operators.md | 2 +- docs/src/users_guide/two_time_corr_func.md | 2 +- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index 3327c024b..7a64ec593 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -11,7 +11,6 @@ @book{Gardiner-Zoller2004 @book{Nielsen-Chuang2011, title = {Quantum Computation and Quantum Information: 10th Anniversary Edition}, ISBN = {9780511976667}, - url = {http://dx.doi.org/10.1017/CBO9780511976667}, DOI = {10.1017/cbo9780511976667}, publisher = {Cambridge University Press}, author = {Nielsen, Michael A. and Chuang, Isaac L.}, @@ -28,8 +27,7 @@ @article{Jozsa1994 pages = {2315--2323}, year = {1994}, publisher = {Taylor \& Francis}, - doi = {10.1080/09500349414552171}, - URL = {https://doi.org/10.1080/09500349414552171}, + doi = {10.1080/09500349414552171} } @article{gravina2024adaptive, @@ -43,15 +41,13 @@ @article{gravina2024adaptive year = {2024}, month = {Apr}, publisher = {American Physical Society}, - doi = {10.1103/PhysRevResearch.6.023072}, - url = {https://link.aps.org/doi/10.1103/PhysRevResearch.6.023072} + doi = {10.1103/PhysRevResearch.6.023072} } @article{Tanimura1989, title = {Time Evolution of a Quantum System in Contact with a Nearly Gaussian-Markoffian Noise Bath}, volume = {58}, ISSN = {1347-4073}, - url = {http://dx.doi.org/10.1143/JPSJ.58.101}, DOI = {10.1143/jpsj.58.101}, number = {1}, journal = {Journal of the Physical Society of Japan}, @@ -64,7 +60,6 @@ @article{Tanimura1989 @article{Huang2023, doi = {10.1038/s42005-023-01427-2}, - url = {https://doi.org/10.1038/s42005-023-01427-2}, year = {2023}, month = {Oct}, publisher = {Nature Portfolio}, diff --git a/docs/src/resources/bibliography.md b/docs/src/resources/bibliography.md index 45632c00e..2c49f8218 100644 --- a/docs/src/resources/bibliography.md +++ b/docs/src/resources/bibliography.md @@ -2,5 +2,7 @@ CurrentModule = QuantumToolbox ``` +This page is generated by [`DocumenterCitations.jl` with author-year style](https://juliadocs.org/DocumenterCitations.jl/stable/gallery/#author_year_style). + ```@bibliography ``` diff --git a/docs/src/users_guide/HEOM.md b/docs/src/users_guide/HEOM.md index 7844af929..9b33a4a5e 100644 --- a/docs/src/users_guide/HEOM.md +++ b/docs/src/users_guide/HEOM.md @@ -1,6 +1,6 @@ # [Hierarchical Equations of Motion](@id doc:Hierarchical-Equations-of-Motion) -The hierarchical equations of motion (HEOM) approach was originally developed by Tanimura and Kubo [Tanimura1989](@cite) in the context of physical chemistry to "exactly" solve a quantum system (labeled as ``\textrm{s}``) in contact with a bosonic environment, encapsulated in the following total Hamiltonian: +The hierarchical equations of motion (HEOM) approach was originally developed by [Tanimura1989](@citet) in the context of physical chemistry to "exactly" solve a quantum system (labeled as ``\textrm{s}``) in contact with a bosonic environment, encapsulated in the following total Hamiltonian: ```math \hat{H}_{\textrm{total}} = \hat{H}_{\textrm{s}} + \sum_k \omega_k \hat{b}^\dagger_k \hat{b}_k + \hat{V}_{\textrm{s}} \sum_k g_k \left(\hat{b}_k + \hat{b}^\dagger_k\right), @@ -28,6 +28,6 @@ J_{\textrm{U}}(\omega)=\frac{2 \Delta^2 W \omega}{(\omega^2 - \omega_0^2)^2 + \o Here, ``\Delta`` represents the coupling strength between the system and the bosonic bath with band-width ``W`` and resonance frequency ``\omega_0``. -We introduce an efficient `Julia` framework for HEOM approach called [`HierarchicalEOM.jl`](https://github.com/qutip/HierarchicalEOM.jl). This package is built upon `QuantumToolbox.jl` and provides a user-friendly and efficient tool to simulate complex open quantum systems based on HEOM approach. For a detailed explanation of this package, we recommend to read its [documentation](https://qutip.org/HierarchicalEOM.jl/) and also the article [Huang2023](@cite). +We introduce an efficient `Julia` framework for HEOM approach called [`HierarchicalEOM.jl`](https://github.com/qutip/HierarchicalEOM.jl). This package is built upon `QuantumToolbox.jl` and provides a user-friendly and efficient tool to simulate complex open quantum systems based on HEOM approach. For a detailed explanation of this package, we recommend to read its [documentation](https://qutip.org/HierarchicalEOM.jl/) and also the article [Huang2023](@citet). Given the spectral density, the HEOM approach requires a decomposition of the bath correlation functions in terms of exponentials. In the [documentation of `HierarchicalEOM.jl`](https://qutip.org/HierarchicalEOM.jl/), we not only describe how this is done for both bosonic and fermionic environments with code examples, but also describe how to solve the time evolution (dynamics), steady-states, and spectra based on HEOM approach. diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index eaae89727..140779240 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -172,7 +172,7 @@ z = thermal_dm(5, 0.125) fidelity(x, y) ``` -Note that the definition of [`fidelity`](@ref) here is from [Nielsen-Chuang2011](@cite). It is the square root of the fidelity defined in [Jozsa1994](@cite). We also know that for two pure states, the trace distance (``T``) and the fidelity (``F``) are related by ``T = \sqrt{1-F^2}``: +Note that the definition of [`fidelity`](@ref) here is from [Nielsen-Chuang2011](@citet). It is the square root of the fidelity defined in [Jozsa1994](@citet). We also know that for two pure states, the trace distance (``T``) and the fidelity (``F``) are related by ``T = \sqrt{1-F^2}``: ```@example states_and_operators tracedist(x, y) ≈ sqrt(1 - (fidelity(x, y))^2) diff --git a/docs/src/users_guide/two_time_corr_func.md b/docs/src/users_guide/two_time_corr_func.md index b3ad59730..ba592d7e3 100644 --- a/docs/src/users_guide/two_time_corr_func.md +++ b/docs/src/users_guide/two_time_corr_func.md @@ -9,7 +9,7 @@ With the `QuantumToolbox.jl` time-evolution function [`mesolve`](@ref), a state ``` where ``\mathcal{G}(t, t_0)\{\cdot\}`` is the propagator defined by the equation of motion. The resulting density matrix can then be used to evaluate the expectation values of arbitrary combinations of same-time operators. -To calculate two-time correlation functions on the form ``\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle``, we can use the quantum regression theorem [see, e.g., [Gardiner-Zoller2004](@cite)] to write +To calculate two-time correlation functions on the form ``\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle``, we can use the quantum regression theorem [see, e.g., [Gardiner-Zoller2004](@citet)] to write ```math \left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B}\hat{\rho}(t)\} \right] = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B} \mathcal{G}(t, 0)\{\hat{\rho}(0)\}\} \right], From 270d3f69f79a171b9c5feec9cfb12191c8553753 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:40:22 +0900 Subject: [PATCH 156/329] Simplify synonyms (#355) --- .github/workflows/Code-Quality.yml | 2 +- docs/src/resources/api.md | 6 +- src/qobj/arithmetic_and_attributes.jl | 19 +- src/qobj/boolean_functions.jl | 4 + src/qobj/functions.jl | 6 + src/qobj/operators.jl | 4 + src/qobj/quantum_object.jl | 14 +- src/qobj/quantum_object_base.jl | 5 + src/qobj/quantum_object_evo.jl | 130 +++++++++++- src/qobj/synonyms.jl | 281 ++------------------------ 10 files changed, 194 insertions(+), 277 deletions(-) diff --git a/.github/workflows/Code-Quality.yml b/.github/workflows/Code-Quality.yml index 29d56c446..b231d2b29 100644 --- a/.github/workflows/Code-Quality.yml +++ b/.github/workflows/Code-Quality.yml @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: version: - - '1' + - 'lts' os: - 'ubuntu-latest' arch: diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index b701d8e7f..ff890fea9 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -170,14 +170,14 @@ trans dag matrix_element unit +tensor +⊗ +qeye sqrtm logm expm sinm cosm -tensor -⊗ -qeye ``` ## [Time evolution](@id doc-API:Time-evolution) diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index a3baf2144..52e6859e1 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -112,13 +112,15 @@ LinearAlgebra.:(/)(A::AbstractQuantumObject{DT}, n::T) where {DT,T<:Number} = get_typename_wrapper(A)(A.data / n, A.type, A.dims) @doc raw""" + A ⋅ B dot(A::QuantumObject, B::QuantumObject) Compute the dot product between two [`QuantumObject`](@ref): ``\langle A | B \rangle`` Note that `A` and `B` should be [`Ket`](@ref) or [`OperatorKet`](@ref) -`A ⋅ B` (where `⋅` can be typed by tab-completing `\cdot` in the REPL) is a synonym for `dot(A, B)` +!!! note + `A ⋅ B` (where `⋅` can be typed by tab-completing `\cdot` in the REPL) is a synonym of `dot(A, B)`. """ function LinearAlgebra.dot( A::QuantumObject{DT1,OpType}, @@ -130,12 +132,16 @@ end @doc raw""" dot(i::QuantumObject, A::AbstractQuantumObject j::QuantumObject) + matrix_element(i::QuantumObject, A::AbstractQuantumObject j::QuantumObject) Compute the generalized dot product `dot(i, A*j)` between a [`AbstractQuantumObject`](@ref) and two [`QuantumObject`](@ref) (`i` and `j`), namely ``\langle i | \hat{A} | j \rangle``. Supports the following inputs: - `A` is in the type of [`Operator`](@ref), with `i` and `j` are both [`Ket`](@ref). - `A` is in the type of [`SuperOperator`](@ref), with `i` and `j` are both [`OperatorKet`](@ref) + +!!! note + `matrix_element(i, A, j)` is a synonym of `dot(i, A, j)`. """ function LinearAlgebra.dot( i::QuantumObject{DT1,KetQuantumObject}, @@ -195,10 +201,12 @@ LinearAlgebra.transpose( @doc raw""" A' adjoint(A::AbstractQuantumObject) + dag(A::AbstractQuantumObject) Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) -Note that `A'` is a synonym for `adjoint(A)` +!!! note + `A'` and `dag(A)` are synonyms of `adjoint(A)`. """ LinearAlgebra.adjoint( A::AbstractQuantumObject{DT,OpType}, @@ -310,6 +318,7 @@ end @doc raw""" normalize(A::QuantumObject, p::Real) + unit(A::QuantumObject, p::Real) Return normalized [`QuantumObject`](@ref) so that its `p`-norm equals to unity, i.e. `norm(A, p) == 1`. @@ -317,6 +326,9 @@ Support for the following types of [`QuantumObject`](@ref): - If `A` is [`Ket`](@ref) or [`Bra`](@ref), default `p = 2` - If `A` is [`Operator`](@ref), default `p = 1` +!!! note + `unit` is a synonym of `normalize`. + Also, see [`norm`](@ref) about its definition for different types of [`QuantumObject`](@ref). """ LinearAlgebra.normalize( @@ -375,7 +387,8 @@ LinearAlgebra.rmul!(B::QuantumObject{<:AbstractArray}, a::Number) = (rmul!(B.dat Matrix square root of [`QuantumObject`](@ref) -Note that `√(A)` is a synonym for `sqrt(A)` +!!! note + `√(A)` (where `√` can be typed by tab-completing `\sqrt` in the REPL) is a synonym of `sqrt(A)`. """ LinearAlgebra.sqrt(A::QuantumObject{<:AbstractArray{T}}) where {T} = QuantumObject(sqrt(sparse_to_dense(A.data)), A.type, A.dims) diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index 869d14642..c67f8fe43 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -61,8 +61,12 @@ issuper(A) = false # default case @doc raw""" ishermitian(A::AbstractQuantumObject) + isherm(A::AbstractQuantumObject) Test whether the [`AbstractQuantumObject`](@ref) is Hermitian. + +!!! note + `isherm` is a synonym of `ishermitian`. """ LinearAlgebra.ishermitian(A::AbstractQuantumObject) = ishermitian(A.data) diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 846316881..2731de8a8 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -148,9 +148,15 @@ end @doc raw""" kron(A::AbstractQuantumObject, B::AbstractQuantumObject, ...) + tensor(A::AbstractQuantumObject, B::AbstractQuantumObject, ...) + ⊗(A::AbstractQuantumObject, B::AbstractQuantumObject, ...) + A ⊗ B Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ``\hat{A} \otimes \hat{B} \otimes \cdots``. +!!! note + `tensor` and `⊗` (where `⊗` can be typed by tab-completing `\otimes` in the REPL) are synonyms of `kron`. + # Examples ```jldoctest diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index ccb989dee..b65865d4e 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -416,12 +416,16 @@ sigmaz() = rmul!(jmat(0.5, Val(:z)), 2) @doc raw""" eye(N::Int; type=Operator, dims=nothing) + qeye(N::Int; type=Operator, dims=nothing) Identity operator ``\hat{\mathbb{1}}`` with size `N`. It is also possible to specify the list of Hilbert dimensions `dims` if different subsystems are present. Note that `type` can only be either [`Operator`](@ref) or [`SuperOperator`](@ref) + +!!! note + `qeye` is a synonym of `eye`. """ eye( N::Int; diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 46a38d7d2..c89826e79 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -50,10 +50,18 @@ struct QuantumObject{MT<:AbstractArray,ObjType<:QuantumObjectType,N} <: Abstract end end -function QuantumObject(A::AbstractArray, type::ObjType, dims::Integer) where {ObjType<:QuantumObjectType} - return QuantumObject(A, type, SVector{1,Int}(dims)) -end +QuantumObject(A::AbstractArray, type::ObjType, dims::Integer) where {ObjType<:QuantumObjectType} = + QuantumObject(A, type, SVector{1,Int}(dims)) + +@doc raw""" + Qobj(A::AbstractArray; type = nothing, dims = nothing) + QuantumObject(A::AbstractArray; type = nothing, dims = nothing) +Generate [`QuantumObject`](@ref) with a given `A::AbstractArray` and specified `type::QuantumObjectType` and `dims`. + +!!! note + `Qobj` is a synonym of `QuantumObject`. +""" function QuantumObject( A::AbstractMatrix{T}; type::ObjType = nothing, diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index e4cb9644f..df259307c 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -121,10 +121,15 @@ const OperatorKet = OperatorKetQuantumObject() @doc raw""" size(A::AbstractQuantumObject) size(A::AbstractQuantumObject, idx::Int) + shape(A::AbstractQuantumObject) + shape(A::AbstractQuantumObject, idx::Int) Returns a tuple containing each dimensions of the array in the [`AbstractQuantumObject`](@ref). Optionally, you can specify an index (`idx`) to just get the corresponding dimension of the array. + +!!! note + `shape` is a synonym of `size`. """ Base.size(A::AbstractQuantumObject) = size(A.data) Base.size(A::AbstractQuantumObject, idx::Int) = size(A.data, idx) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index ddef77646..e9417b8f2 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -1,3 +1,7 @@ +#= +This file defines the QuantumObjectEvolution (QobjEvo) structure. +=# + export QuantumObjectEvolution @doc raw""" @@ -30,7 +34,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal julia> coef1(p, t) = exp(-1im * t) coef1 (generic function with 1 method) -julia> op = QuantumObjectEvolution(a, coef1) +julia> op = QobjEvo(a, coef1) Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) ``` @@ -50,7 +54,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal julia> coef2(p, t) = sin(t) coef2 (generic function with 1 method) -julia> op1 = QuantumObjectEvolution(((a, coef1), (σm, coef2))) +julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) ``` @@ -75,7 +79,7 @@ coef1 (generic function with 1 method) julia> coef2(p, t) = sin(p.ω2 * t) coef2 (generic function with 1 method) -julia> op1 = QuantumObjectEvolution(((a, coef1), (σm, coef2))) +julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) @@ -143,9 +147,12 @@ function QuantumObjectEvolution(data::AbstractSciMLOperator, type::QuantumObject end @doc raw""" + QobjEvo(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) Generate a [`QuantumObjectEvolution`](@ref) object from a [`SciMLOperator`](https://github.com/SciML/SciMLOperators.jl), in the same way as [`QuantumObject`](@ref) for `AbstractArray` inputs. + +Note that `QobjEvo` is a synonym of `QuantumObjectEvolution` """ function QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) _size = _get_size(data) @@ -161,7 +168,93 @@ function QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObject return QuantumObjectEvolution(data, type, dims) end -# Make the QuantumObjectEvolution, with the option to pre-multiply by a scalar +@doc raw""" + QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing) + QuantumObjectEvolution(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing) + +Generate [`QuantumObjectEvolution`](@ref). + +# Arguments +- `op_func_list::Union{Tuple,AbstractQuantumObject}`: A tuple of tuples or operators. +- `α::Union{Nothing,Number}=nothing`: A scalar to pre-multiply the operators. + +!!! warning "Beware of type-stability!" + 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. + +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. + +!!! note + `QobjEvo` is a synonym of `QuantumObjectEvolution`. + +# Examples +This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example +```jldoctest qobjevo +julia> a = tensor(destroy(10), qeye(2)) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: +⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ + +julia> σm = tensor(qeye(10), sigmam()) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: +⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡀⠀⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡀⠀⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡀⎦ + +julia> coef1(p, t) = exp(-1im * t) +coef1 (generic function with 1 method) + +julia> coef2(p, t) = sin(t) +coef2 (generic function with 1 method) + +julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) +``` + +We can also concretize the operator at a specific time `t` +```jldoctest qobjevo +julia> op1(0.1) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: +⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ +``` + +It also supports parameter-dependent time evolution +```jldoctest qobjevo +julia> coef1(p, t) = exp(-1im * p.ω1 * t) +coef1 (generic function with 1 method) + +julia> coef2(p, t) = sin(p.ω2 * t) +coef2 (generic function with 1 method) + +julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) + +julia> p = (ω1 = 1.0, ω2 = 0.5) +(ω1 = 1.0, ω2 = 0.5) + +julia> op1(p, 0.1) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: +⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ +``` +""" function QuantumObjectEvolution( op_func_list::Tuple, α::Union{Nothing,Number} = nothing; @@ -187,6 +280,35 @@ QuantumObjectEvolution( type::Union{Nothing,QuantumObjectType} = nothing, ) = QuantumObjectEvolution((op_func,), α; type = type) +@doc raw""" + QuantumObjectEvolution(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type::Union{Nothing,QuantumObjectType} = nothing) + QobjEvo(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type::Union{Nothing,QuantumObjectType} = nothing) + +Generate [`QuantumObjectEvolution`](@ref). + +# Notes +- 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`. +- `QobjEvo` is a synonym of `QuantumObjectEvolution`. + +# Examples +```jldoctest +julia> a = tensor(destroy(10), qeye(2)) +Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: +⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ +⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⎥ +⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ +⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ + +julia> coef(p, t) = exp(-1im * t) +coef (generic function with 1 method) + +julia> op = QobjEvo(a, coef) +Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) +``` +""" QuantumObjectEvolution( op::QuantumObject, f::Function, diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 6518d00af..7f00dbc13 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -4,184 +4,33 @@ Synonyms of the functions for QuantumObject export Qobj, QobjEvo, shape, isherm export trans, dag, matrix_element, unit -export sqrtm, logm, expm, sinm, cosm export tensor, ⊗ export qeye +export sqrtm, logm, expm, sinm, cosm @doc raw""" - Qobj(A::AbstractArray; type::QuantumObjectType, dims::Vector{Int}) - -Generate [`QuantumObject`](@ref) - -Note that this functions is same as `QuantumObject(A; type=type, dims=dims)`. -""" -Qobj(A; kwargs...) = QuantumObject(A; kwargs...) - -@doc raw""" - QobjEvo(op::QuantumObject, f::Function; type::Union{Nothing,QuantumObjectType} = nothing) - -Generate [`QuantumObjectEvolution`](@ref). - -Note that this functions is same as `QuantumObjectEvolution(op, f; type = type)`. 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`. - -# Examples -```jldoctest -julia> a = tensor(destroy(10), qeye(2)) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false -20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: -⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ -⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ -⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ - -julia> coef(p, t) = exp(-1im * t) -coef (generic function with 1 method) - -julia> op = QobjEvo(a, coef) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false -ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) -``` -""" -QobjEvo(op::QuantumObject, f::Function; type::Union{Nothing,QuantumObjectType} = nothing) = - QuantumObjectEvolution(op, f; type = type) - -@doc raw""" - QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing) - -Generate [`QuantumObjectEvolution`](@ref). - -Note that this functions is same as `QuantumObjectEvolution(op_func_list)`. 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. - -# Arguments -- `op_func_list::Union{Tuple,AbstractQuantumObject}`: A tuple of tuples or operators. -- `α::Union{Nothing,Number}=nothing`: A scalar to pre-multiply the operators. - -!!! warning "Beware of type-stability!" - 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. - -# Examples -This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example -```jldoctest qobjevo -julia> a = tensor(destroy(10), qeye(2)) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false -20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: -⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ -⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⎥ -⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ - -julia> σm = tensor(qeye(10), sigmam()) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false -20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: -⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ -⎢⠀⠀⠂⡀⠀⠀⠀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠂⡀⠀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠀⠂⡀⠀⠀⎥ -⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡀⎦ - -julia> coef1(p, t) = exp(-1im * t) -coef1 (generic function with 1 method) - -julia> coef2(p, t) = sin(t) -coef2 (generic function with 1 method) - -julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false -(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) -``` - -We can also concretize the operator at a specific time `t` -```jldoctest qobjevo -julia> op1(0.1) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false -20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: -⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ -⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ -⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ -``` - -It also supports parameter-dependent time evolution -```jldoctest qobjevo -julia> coef1(p, t) = exp(-1im * p.ω1 * t) -coef1 (generic function with 1 method) - -julia> coef2(p, t) = sin(p.ω2 * t) -coef2 (generic function with 1 method) - -julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false -(ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) - -julia> p = (ω1 = 1.0, ω2 = 0.5) -(ω1 = 1.0, ω2 = 0.5) - -julia> op1(p, 0.1) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false -20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: -⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ -⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠂⡑⢄⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠀⠂⡑⢄⠀⎥ -⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ -``` -""" -QobjEvo( - op_func_list::Union{Tuple,AbstractQuantumObject}, - α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, -) = QuantumObjectEvolution(op_func_list, α; type = type) - -QobjEvo(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) = - QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) - -""" - QobjEvo(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) - -Synonym of [`QuantumObjectEvolution`](@ref) object from a [`SciMLOperator`](https://github.com/SciML/SciMLOperators.jl). See the documentation for [`QuantumObjectEvolution`](@ref) for more information. -""" -QobjEvo(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) = - QuantumObjectEvolution(data; type = type, dims = dims) - -@doc raw""" - shape(A::AbstractQuantumObject) + Qobj(A; kwargs...) -Returns a tuple containing each dimensions of the array in the [`AbstractQuantumObject`](@ref). - -Note that this function is same as `size(A)`. +!!! note + `Qobj` is a synonym for generating [`QuantumObject`](@ref). See the docstring of [`QuantumObject`](@ref) for more details. """ -shape(A::AbstractQuantumObject) = size(A.data) +const Qobj = QuantumObject # we need the docstring here, otherwise the docstring won't be found because QuantumObject is not a public symbol @doc raw""" - isherm(A::AbstractQuantumObject) - -Test whether the [`AbstractQuantumObject`](@ref) is Hermitian. + QobjEvo(args...; kwargs...) -Note that this functions is same as `ishermitian(A)`. +!!! note + `QobjEvo` is a synonym for generating [`QuantumObjectEvolution`](@ref). See the docstrings of [`QuantumObjectEvolution`](@ref) for more details. """ -isherm(A::AbstractQuantumObject) = ishermitian(A) - -@doc raw""" - trans(A::AbstractQuantumObject) +const QobjEvo = QuantumObjectEvolution # we need the docstring here, otherwise the docstring won't be found because QuantumObjectEvolution is not a public symbol -Lazy matrix transpose of the [`AbstractQuantumObject`](@ref). +const shape = size -Note that this function is same as `transpose(A)`. -""" -trans(A::AbstractQuantumObject{DT,OpType}) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - transpose(A) +const isherm = ishermitian -@doc raw""" - dag(A::AbstractQuantumObject) +const trans = transpose -Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) - -Note that this function is same as `adjoint(A)`. -""" -dag(A::AbstractQuantumObject) = adjoint(A) +const dag = adjoint @doc raw""" matrix_element(i::QuantumObject, A::QuantumObject j::QuantumObject) @@ -194,35 +43,14 @@ Supports the following inputs: - `A` is in the type of [`Operator`](@ref), with `i` and `j` are both [`Ket`](@ref). - `A` is in the type of [`SuperOperator`](@ref), with `i` and `j` are both [`OperatorKet`](@ref) """ -matrix_element( - i::QuantumObject{<:AbstractArray{T1},KetQuantumObject}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - j::QuantumObject{<:AbstractArray{T3},KetQuantumObject}, -) where {T1<:Number,T2<:Number,T3<:Number} = dot(i, A, j) -matrix_element( - i::QuantumObject{<:AbstractArray{T1},OperatorKetQuantumObject}, - A::QuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, - j::QuantumObject{<:AbstractArray{T3},OperatorKetQuantumObject}, -) where {T1<:Number,T2<:Number,T3<:Number} = dot(i, A, j) - -@doc raw""" - unit(A::QuantumObject, p::Real) +matrix_element(i, A, j) = dot(i, A, j) -Return normalized [`QuantumObject`](@ref) so that its `p`-norm equals to unity, i.e. `norm(A, p) == 1`. +const unit = normalize -Support for the following types of [`QuantumObject`](@ref): -- If `A` is [`Ket`](@ref) or [`Bra`](@ref), default `p = 2` -- If `A` is [`Operator`](@ref), default `p = 1` +const tensor = kron +const ⊗ = kron -Note that this function is same as `normalize(A, p)` - -Also, see [`norm`](@ref) about its definition for different types of [`QuantumObject`](@ref). -""" -unit( - A::QuantumObject{<:AbstractArray{T},ObjType}, - p::Real = 2, -) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject}} = normalize(A, p) -unit(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, p::Real = 1) where {T} = normalize(A, p) +const qeye = eye @doc raw""" sqrtm(A::QuantumObject) @@ -280,76 +108,3 @@ Note that this function is same as `cos(A)` and only supports for [`Operator`](@ cosm( A::QuantumObject{<:AbstractMatrix{T},ObjType}, ) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = cos(A) - -@doc raw""" - tensor(A::QuantumObject, B::QuantumObject, ...) - -Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ``\hat{A} \otimes \hat{B} \otimes \cdots``. - -Note that this function is same as `kron(A, B, ...)`. - -# Examples - -```jldoctest -julia> x = sigmax() -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true -2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries: - ⋅ 1.0+0.0im - 1.0+0.0im ⋅ - -julia> x_list = fill(x, 3); - -julia> tensor(x_list...).dims -3-element SVector{3, Int64} with indices SOneTo(3): - 2 - 2 - 2 -``` -""" -tensor(A...) = kron(A...) - -@doc raw""" - ⊗(A::QuantumObject, B::QuantumObject) - -Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ``\hat{A} \otimes \hat{B}``. - -Note that this function is same as `kron(A, B)`. - -# Examples - -```jldoctest -julia> a = destroy(20) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false -20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: -⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ -⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⎥ -⎢⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⎥ -⎣⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⎦ - -julia> O = a ⊗ a; - -julia> size(a), size(O) -((20, 20), (400, 400)) - -julia> a.dims, O.dims -([20], [20, 20]) -``` -""" -⊗(A::QuantumObject, B::QuantumObject) = kron(A, B) - -@doc raw""" - qeye(N::Int; type=Operator, dims=nothing) - -Identity operator ``\hat{\mathbb{1}}`` with size `N`. - -It is also possible to specify the list of Hilbert dimensions `dims` if different subsystems are present. - -Note that this function is same as `eye(N, type=type, dims=dims)`, and `type` can only be either [`Operator`](@ref) or [`SuperOperator`](@ref). -""" -qeye( - N::Int; - type::ObjType = Operator, - dims = nothing, -) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(Diagonal(ones(ComplexF64, N)); type = type, dims = dims) From 1b48458065ad4e6addd72623ac4eebc3efa57ac0 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:32:40 +0900 Subject: [PATCH 157/329] Remove `CairoMakie` extension in local tests (#362) * remove `CairoMakie` extension in local tests * format files * remove `CairoMakie` from `[targets]` in Project.toml` --- .github/workflows/CI.yml | 4 ++-- Project.toml | 3 +-- test/ext-test/cairomakie/Project.toml | 6 ++++++ test/ext-test/{ => cairomakie}/cairomakie_ext.jl | 0 test/runtests.jl | 11 +++++++---- 5 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 test/ext-test/cairomakie/Project.toml rename test/ext-test/{ => cairomakie}/cairomakie_ext.jl (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0d7e6fe30..57deaf5e8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,7 +10,7 @@ on: - 'ext/**' - 'test/runtests.jl' - 'test/core-test/**' - - 'test/ext-test/cairomakie_ext.jl' + - 'test/ext-test/cairomakie/**' - 'Project.toml' pull_request: branches: @@ -21,7 +21,7 @@ on: - 'ext/**' - 'test/runtests.jl' - 'test/core-test/**' - - 'test/ext-test/cairomakie_ext.jl' + - 'test/ext-test/cairomakie/**' - 'Project.toml' types: - opened diff --git a/Project.toml b/Project.toml index 8c3759cc3..e591de619 100644 --- a/Project.toml +++ b/Project.toml @@ -70,9 +70,8 @@ julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "CairoMakie", "JET", "Test"] +test = ["Aqua", "JET", "Test"] diff --git a/test/ext-test/cairomakie/Project.toml b/test/ext-test/cairomakie/Project.toml new file mode 100644 index 000000000..06b706a2f --- /dev/null +++ b/test/ext-test/cairomakie/Project.toml @@ -0,0 +1,6 @@ +[deps] +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" + +[compat] +CairoMakie = "0.12" \ No newline at end of file diff --git a/test/ext-test/cairomakie_ext.jl b/test/ext-test/cairomakie/cairomakie_ext.jl similarity index 100% rename from test/ext-test/cairomakie_ext.jl rename to test/ext-test/cairomakie/cairomakie_ext.jl diff --git a/test/runtests.jl b/test/runtests.jl index 9081ad842..ca5c41670 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -46,13 +46,16 @@ if (GROUP == "All") || (GROUP == "Core") end end -if (GROUP == "All") || (GROUP == "CairoMakie_Ext") - using QuantumToolbox +if (GROUP == "CairoMakie_Ext")# || (GROUP == "All") + Pkg.activate("ext-test/cairomakie") + Pkg.develop(PackageSpec(path = dirname(@__DIR__))) + Pkg.instantiate() - (GROUP == "CairoMakie_Ext") && QuantumToolbox.about() + using QuantumToolbox + QuantumToolbox.about() # CarioMakie is imported in the following script - include(joinpath(testdir, "ext-test", "cairomakie_ext.jl")) + include(joinpath(testdir, "ext-test", "cairomakie", "cairomakie_ext.jl")) end if (GROUP == "CUDA_Ext")# || (GROUP == "All") From c30a32199f01b703c3ee620305a69bc1f47d5f1d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:26:39 +0100 Subject: [PATCH 158/329] Add Zulip sponsor (#358) * Add Zulip sponsor * Change structure of acknowledgements * Separate logos --- README.md | 22 ++++++++++++++++++++++ docs/make.jl | 1 + docs/src/resources/acknowledgements.md | 21 +++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 docs/src/resources/acknowledgements.md diff --git a/README.md b/README.md index c14be2c1a..866b6449c 100644 --- a/README.md +++ b/README.md @@ -176,3 +176,25 @@ Here we provide a brief performance comparison between `QuantumToolbox.jl` and o You are most welcome to contribute to `QuantumToolbox.jl` development by forking this repository and sending pull requests (PRs), or filing bug reports at the issues page. You can also help out with users' questions, or discuss proposed changes in the [QuTiP discussion group](https://groups.google.com/g/qutip). For more information about contribution, including technical advice, please see the [Contributing to QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing) section of the documentation. + +## Acknowledgements + +### Fundings + +`QuantumToolbox.jl` is supported by the [Unitary Fund](https://unitary.fund), a grant program for quantum technology projects. + + + +### Other Acknowledgements + +We are also grateful to the [Zulip](https://zulip.com) team for providing a free chat service for open-source projects. + + diff --git a/docs/make.jl b/docs/make.jl index 56b374c44..f49590d55 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -76,6 +76,7 @@ const PAGES = [ "Bibliography" => "resources/bibliography.md", "ChangeLog" => "resources/changelog.md", "Contributing to QuantumToolbox.jl" => "resources/contributing.md", + "Acknowledgements" => "resources/acknowledgements.md", ], ] diff --git a/docs/src/resources/acknowledgements.md b/docs/src/resources/acknowledgements.md new file mode 100644 index 000000000..4a6a76442 --- /dev/null +++ b/docs/src/resources/acknowledgements.md @@ -0,0 +1,21 @@ +# [Acknowledgements](@id doc:Acknowledgements) + +## [Fundings](@id doc:Fundings) + +`QuantumToolbox.jl` is supported by the [Unitary Fund](https://unitary.fund), a grant program for quantum technology projects. + + + +## [Other Acknowledgements](@id doc:Other-Acknowledgements) + +We are also grateful to the [Zulip](https://zulip.com) team for providing a free chat service for open-source projects. + + From 5b08c046bff9a758ceac9d4f0ced77d98afffbdf Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 7 Jan 2025 19:53:27 +0900 Subject: [PATCH 159/329] Improve `show` for `Qobj`/`QobjEvo` lists (#365) * improve `show` for `Qobj` lists * fix doctests --- src/metrics.jl | 3 +++ src/negativity.jl | 2 ++ src/qobj/arithmetic_and_attributes.jl | 6 ++++++ src/qobj/eigsolve.jl | 6 ++++-- src/qobj/functions.jl | 1 + src/qobj/operators.jl | 5 +++++ src/qobj/quantum_object.jl | 5 +++-- src/qobj/quantum_object_evo.jl | 19 ++++++++++++++++++- src/qobj/states.jl | 2 ++ src/wigner.jl | 1 + test/core-test/quantum_objects.jl | 12 ++++++------ test/core-test/quantum_objects_evo.jl | 4 ++-- 12 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/metrics.jl b/src/metrics.jl index 879d9ea7d..6bbcc7cc6 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -21,12 +21,14 @@ matrix ``\hat{\rho}``. Pure state: ```jldoctest julia> ψ = fock(2,0) + Quantum Object: type=Ket dims=[2] size=(2,) 2-element Vector{ComplexF64}: 1.0 + 0.0im 0.0 + 0.0im julia> ρ = ket2dm(ψ) + Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 Matrix{ComplexF64}: 1.0+0.0im 0.0+0.0im @@ -39,6 +41,7 @@ julia> entropy_vn(ρ, base=2) Mixed state: ```jldoctest julia> ρ = maximally_mixed_dm(2) + Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 Diagonal{ComplexF64, Vector{ComplexF64}}: 0.5-0.0im ⋅ diff --git a/src/negativity.jl b/src/negativity.jl index c59844879..d91b76108 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -19,6 +19,7 @@ and ``\Vert \hat{X} \Vert_1=\textrm{Tr}\sqrt{\hat{X}^\dagger \hat{X}}`` is the t ```jldoctest julia> Ψ = bell_state(0, 0) + Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im @@ -27,6 +28,7 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) 0.7071067811865475 + 0.0im julia> ρ = ket2dm(Ψ) + Quantum Object: type=Operator dims=[2, 2] size=(4, 4) ishermitian=true 4×4 Matrix{ComplexF64}: 0.5+0.0im 0.0+0.0im 0.0+0.0im 0.5+0.0im diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 52e6859e1..f9af5aeeb 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -246,6 +246,7 @@ Note that this function only supports for [`Operator`](@ref) and [`SuperOperator ```jldoctest julia> a = destroy(20) + Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ @@ -286,6 +287,7 @@ Return the standard vector `p`-norm or [Schatten](https://en.wikipedia.org/wiki/ ```jldoctest julia> ψ = fock(10, 2) + Quantum Object: type=Ket dims=[10] size=(10,) 10-element Vector{ComplexF64}: 0.0 + 0.0im @@ -510,6 +512,7 @@ Note that this function will always return [`Operator`](@ref). No matter the inp Two qubits in the state ``\ket{\psi} = \ket{e,g}``: ```jldoctest julia> ψ = kron(fock(2,0), fock(2,1)) + Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.0 + 0.0im @@ -518,6 +521,7 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) 0.0 + 0.0im julia> ptrace(ψ, 2) + Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 Matrix{ComplexF64}: 0.0+0.0im 0.0+0.0im @@ -527,6 +531,7 @@ Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true or in an entangled state ``\ket{\psi} = \frac{1}{\sqrt{2}} \left( \ket{e,e} + \ket{g,g} \right)``: ```jldoctest julia> ψ = 1 / √2 * (kron(fock(2,0), fock(2,0)) + kron(fock(2,1), fock(2,1))) + Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im @@ -535,6 +540,7 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) 0.7071067811865475 + 0.0im julia> ptrace(ψ, 1) + Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 Matrix{ComplexF64}: 0.5+0.0im 0.0+0.0im diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 47bbeeccb..3728ffc5d 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -51,11 +51,13 @@ julia> λ julia> ψ 2-element Vector{QuantumObject{Vector{ComplexF64}, KetQuantumObject, 1}}: - Quantum Object: type=Ket dims=[2] size=(2,) + +Quantum Object: type=Ket dims=[2] size=(2,) 2-element Vector{ComplexF64}: -0.7071067811865475 + 0.0im 0.7071067811865475 + 0.0im - Quantum Object: type=Ket dims=[2] size=(2,) + +Quantum Object: type=Ket dims=[2] size=(2,) 2-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im 0.7071067811865475 + 0.0im diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 2731de8a8..158514a4f 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -161,6 +161,7 @@ Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ```jldoctest julia> a = destroy(20) + Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index b65865d4e..d2a0d8fe5 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -90,6 +90,7 @@ This operator acts on a fock state as ``\hat{a} \ket{n} = \sqrt{n} \ket{n-1}``. ```jldoctest julia> a = destroy(20) + Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ @@ -115,6 +116,7 @@ This operator acts on a fock state as ``\hat{a}^\dagger \ket{n} = \sqrt{n+1} \ke ```jldoctest julia> a_d = create(20) + Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ @@ -244,18 +246,21 @@ Note that if the parameter `which` is not specified, returns a set of Spin-`j` o # Examples ```jldoctest julia> jmat(0.5, :x) + Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries: ⋅ 0.5+0.0im 0.5+0.0im ⋅ julia> jmat(0.5, Val(:-)) + Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false 2×2 SparseMatrixCSC{ComplexF64, Int64} with 1 stored entry: ⋅ ⋅ 1.0+0.0im ⋅ julia> jmat(1.5, Val(:z)) + Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=true 4×4 SparseMatrixCSC{ComplexF64, Int64} with 4 stored entries: 1.5+0.0im ⋅ ⋅ ⋅ diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index c89826e79..0b0269c9f 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -21,6 +21,7 @@ Julia struct representing any quantum objects. ```jldoctest julia> a = destroy(20) + Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ @@ -145,7 +146,7 @@ function Base.show( }, } op_data = QO.data - println(io, "Quantum Object: type=", QO.type, " dims=", QO.dims, " size=", size(op_data)) + println(io, "\nQuantum Object: type=", QO.type, " dims=", QO.dims, " size=", size(op_data)) return show(io, MIME("text/plain"), op_data) end @@ -153,7 +154,7 @@ function Base.show(io::IO, QO::QuantumObject) op_data = QO.data println( io, - "Quantum Object: type=", + "\nQuantum Object: type=", QO.type, " dims=", QO.dims, diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index e9417b8f2..ef7f0be60 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -23,6 +23,7 @@ where ``c_i(p, t)`` is a function that depends on the parameters `p` and time `t This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example ```jldoctest qobjevo julia> a = tensor(destroy(10), qeye(2)) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: ⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ @@ -35,6 +36,7 @@ julia> coef1(p, t) = exp(-1im * t) coef1 (generic function with 1 method) julia> op = QobjEvo(a, coef1) + Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) ``` @@ -43,6 +45,7 @@ If there are more than 2 operators, we need to put each set of operator and coef ```jldoctest qobjevo julia> σm = tensor(qeye(10), sigmam()) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: ⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ @@ -55,6 +58,7 @@ julia> coef2(p, t) = sin(t) coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) + Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) ``` @@ -62,6 +66,7 @@ Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitia We can also concretize the operator at a specific time `t` ```jldoctest qobjevo julia> op1(0.1) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ @@ -80,6 +85,7 @@ julia> coef2(p, t) = sin(p.ω2 * t) coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) + Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) @@ -87,6 +93,7 @@ julia> p = (ω1 = 1.0, ω2 = 0.5) (ω1 = 1.0, ω2 = 0.5) julia> op1(p, 0.1) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ @@ -128,7 +135,7 @@ function Base.show(io::IO, QO::QuantumObjectEvolution) op_data = QO.data println( io, - "Quantum Object Evo.: type=", + "\nQuantum Object Evo.: type=", QO.type, " dims=", QO.dims, @@ -190,6 +197,7 @@ Note that if `α` is provided, all the operators in `op_func_list` will be pre-m This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example ```jldoctest qobjevo julia> a = tensor(destroy(10), qeye(2)) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: ⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ @@ -199,6 +207,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⎦ julia> σm = tensor(qeye(10), sigmam()) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: ⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ @@ -214,6 +223,7 @@ julia> coef2(p, t) = sin(t) coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) + Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) ``` @@ -221,6 +231,7 @@ Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitia We can also concretize the operator at a specific time `t` ```jldoctest qobjevo julia> op1(0.1) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ @@ -239,6 +250,7 @@ julia> coef2(p, t) = sin(p.ω2 * t) coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) + Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) @@ -246,6 +258,7 @@ julia> p = (ω1 = 1.0, ω2 = 0.5) (ω1 = 1.0, ω2 = 0.5) julia> op1(p, 0.1) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ @@ -293,6 +306,7 @@ Generate [`QuantumObjectEvolution`](@ref). # Examples ```jldoctest julia> a = tensor(destroy(10), qeye(2)) + Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: ⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ @@ -305,6 +319,7 @@ julia> coef(p, t) = exp(-1im * t) coef (generic function with 1 method) julia> op = QobjEvo(a, coef) + Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) ``` @@ -458,6 +473,7 @@ Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the inpu # Examples ```jldoctest julia> a = destroy(20) + Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ @@ -473,6 +489,7 @@ julia> coef2(p, t) = cos(t) coef2 (generic function with 1 method) julia> A = QobjEvo(((a, coef1), (a', coef2))) + Quantum Object Evo.: type=Operator dims=[20] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) diff --git a/src/qobj/states.jl b/src/qobj/states.jl index 8634c2788..62c4b12c2 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -254,6 +254,7 @@ Here, `x = 1` (`z = 1`) means applying Pauli-``X`` ( Pauli-``Z``) unitary transf ```jldoctest julia> bell_state(0, 0) + Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im @@ -262,6 +263,7 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) 0.7071067811865475 + 0.0im julia> bell_state(Val(1), Val(0)) + Quantum Object: type=Ket dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.0 + 0.0im diff --git a/src/wigner.jl b/src/wigner.jl index 4c8ee7fac..9e3bdbe03 100644 --- a/src/wigner.jl +++ b/src/wigner.jl @@ -37,6 +37,7 @@ The `method` parameter can be either `WignerLaguerre()` or `WignerClenshaw()`. T # Example ```jldoctest wigner julia> ψ = fock(10, 0) + fock(10, 1) |> normalize + Quantum Object: type=Ket dims=[10] size=(10,) 10-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 6d26ac4c2..9ad54a760 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -233,7 +233,7 @@ a_size = size(a) a_isherm = isherm(a) @test opstring == - "Quantum Object: type=Operator dims=$a_dims size=$a_size ishermitian=$a_isherm\n$datastring" + "\nQuantum Object: type=Operator dims=$a_dims size=$a_size ishermitian=$a_isherm\n$datastring" a = spre(a) opstring = sprint((t, s) -> show(t, "text/plain", s), a) @@ -241,34 +241,34 @@ a_dims = a.dims a_size = size(a) a_isherm = isherm(a) - @test opstring == "Quantum Object: type=SuperOperator dims=$a_dims size=$a_size\n$datastring" + @test opstring == "\nQuantum Object: type=SuperOperator dims=$a_dims size=$a_size\n$datastring" opstring = sprint((t, s) -> show(t, "text/plain", s), ψ) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ.data) ψ_dims = ψ.dims ψ_size = size(ψ) - @test opstring == "Quantum Object: type=Ket dims=$ψ_dims size=$ψ_size\n$datastring" + @test opstring == "\nQuantum Object: type=Ket dims=$ψ_dims size=$ψ_size\n$datastring" ψ = ψ' opstring = sprint((t, s) -> show(t, "text/plain", s), ψ) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ.data) ψ_dims = ψ.dims ψ_size = size(ψ) - @test opstring == "Quantum Object: type=Bra dims=$ψ_dims size=$ψ_size\n$datastring" + @test opstring == "\nQuantum Object: type=Bra dims=$ψ_dims size=$ψ_size\n$datastring" ψ2 = Qobj(rand(ComplexF64, 4), type = OperatorKet) opstring = sprint((t, s) -> show(t, "text/plain", s), ψ2) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ2.data) ψ2_dims = ψ2.dims ψ2_size = size(ψ2) - @test opstring == "Quantum Object: type=OperatorKet dims=$ψ2_dims size=$ψ2_size\n$datastring" + @test opstring == "\nQuantum Object: type=OperatorKet dims=$ψ2_dims size=$ψ2_size\n$datastring" ψ2 = ψ2' opstring = sprint((t, s) -> show(t, "text/plain", s), ψ2) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ2.data) ψ2_dims = ψ2.dims ψ2_size = size(ψ2) - @test opstring == "Quantum Object: type=OperatorBra dims=$ψ2_dims size=$ψ2_size\n$datastring" + @test opstring == "\nQuantum Object: type=OperatorBra dims=$ψ2_dims size=$ψ2_size\n$datastring" end @testset "matrix element" begin diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 5910b0e5a..f5b39c557 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -114,7 +114,7 @@ H_isherm = isherm(H) H_isconst = isconstant(H) @test opstring == - "Quantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst\n$datastring" + "\nQuantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst\n$datastring" L = QobjEvo(spre(a)) opstring = sprint((t, s) -> show(t, "text/plain", s), L) @@ -124,7 +124,7 @@ L_isherm = isherm(L) L_isconst = isconstant(L) @test opstring == - "Quantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst\n$datastring" + "\nQuantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst\n$datastring" end @testset "Type Inference (QobjEvo)" begin From 6085242d0377526e4764f8e4abc6873ede0559d7 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:09:45 +0100 Subject: [PATCH 160/329] Fix raw html for Acknowledgements (#366) * Fix raw html in Acknowledgements * Change structure of acknowledgements * Fix raw html for acknowledgements --- docs/src/resources/acknowledgements.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/resources/acknowledgements.md b/docs/src/resources/acknowledgements.md index 4a6a76442..7f6161e73 100644 --- a/docs/src/resources/acknowledgements.md +++ b/docs/src/resources/acknowledgements.md @@ -4,18 +4,22 @@ `QuantumToolbox.jl` is supported by the [Unitary Fund](https://unitary.fund), a grant program for quantum technology projects. +```@raw html +``` ## [Other Acknowledgements](@id doc:Other-Acknowledgements) We are also grateful to the [Zulip](https://zulip.com) team for providing a free chat service for open-source projects. +```@raw html +``` From 52950ae44a7c0941cc3ecd1fe8f691c6871a2fda Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 9 Jan 2025 01:11:35 +0900 Subject: [PATCH 161/329] remove tutorials category in docs --- README.md | 2 +- docs/make.jl | 11 +- docs/src/.vitepress/config.mts | 1 + .../{tutorials => getting_started}/logo.md | 6 +- docs/src/index.md | 9 +- docs/src/tutorials/lowrank.md | 177 ------------------ .../src/{tutorials => users_guide}/cluster.md | 62 +++--- 7 files changed, 48 insertions(+), 220 deletions(-) rename docs/src/{tutorials => getting_started}/logo.md (88%) delete mode 100644 docs/src/tutorials/lowrank.md rename docs/src/{tutorials => users_guide}/cluster.md (80%) diff --git a/README.md b/README.md index 866b6449c..93ab77261 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ and [Y.-T. Huang](https://github.com/ytdHuang). - **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as `QuTiP`. - **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [`DifferentialEquations.jl`](https://github.com/SciML/DifferentialEquations.jl) package. - **GPU Computing:** Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. -- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. See [this tutorial](https://qutip.org/QuantumToolbox.jl/stable/tutorials/cluster) for more information. +- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. See [here](https://qutip.org/QuantumToolbox.jl/stable/users_guide/cluster) for more information. - **Easy Extension:** Easily extend the package, taking advantage of the `Julia` language features, like multiple dispatch and metaprogramming. ## Installation diff --git a/docs/make.jl b/docs/make.jl index f49590d55..0aec9e286 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -36,6 +36,7 @@ const PAGES = [ "Brief Example" => "getting_started/brief_example.md", "Key differences from QuTiP" => "getting_started/qutip_differences.md", "The Importance of Type-Stability" => "getting_started/type_stability.md", + "Example: Create QuantumToolbox.jl Logo" => "getting_started/logo.md", # "Cite QuantumToolbox.jl" => "getting_started/cite.md", ], "Users Guide" => [ @@ -54,6 +55,7 @@ const PAGES = [ "Stochastic Solver" => "users_guide/time_evolution/stochastic.md", "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", ], + "Intensive parallelization on a Cluster" => "users_guide/cluster.md", "Hierarchical Equations of Motion" => "users_guide/HEOM.md", "Solving for Steady-State Solutions" => "users_guide/steadystate.md", "Two-time correlation functions" => "users_guide/two_time_corr_func.md", @@ -62,15 +64,6 @@ const PAGES = [ "users_guide/extensions/cairomakie.md", ], ], - "Tutorials" => [ - "Time Evolution" => [ - "Low Rank Master Equation" => "tutorials/lowrank.md", - ], - "Miscellaneous Tutorials" => [ - "tutorials/logo.md", - "tutorials/cluster.md", - ], - ], "Resources" => [ "API" => "resources/api.md", "Bibliography" => "resources/bibliography.md", diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index bad15a5ab..9a0732bb5 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -13,6 +13,7 @@ const navTemp = { const nav = [ ...navTemp.nav, + { text: 'Tutorials', link: 'https://qutip.org/qutip-julia-tutorials/' }, { text: 'Benchmarks', link: 'https://qutip.org/QuantumToolbox.jl/benchmarks/' }, { component: 'VersionPicker' diff --git a/docs/src/tutorials/logo.md b/docs/src/getting_started/logo.md similarity index 88% rename from docs/src/tutorials/logo.md rename to docs/src/getting_started/logo.md index 12be51567..587e732fa 100644 --- a/docs/src/tutorials/logo.md +++ b/docs/src/getting_started/logo.md @@ -1,8 +1,8 @@ -# [Create QuantumToolbox.jl logo](@id doc-tutor:Create-QuantumToolbox.jl-logo) +# [Example: Create QuantumToolbox.jl logo](@id doc:Create-QuantumToolbox.jl-logo) ## Introduction -In this tutorial, we will demonstrate how to create the logo for the **QuantumToolbox.jl** package. The logo represents the Wigner function of the triangular cat state, which is a linear superposition of three coherent states. The resulting Wigner function has a triangular shape that resembles the Julia logo. We will also define a custom colormap that varies based on the value of the Wigner function and the spatial coordinates, such that the three blobs corresponding to the coherent states have different colors (matching the colors of the Julia logo). +In this example, we will demonstrate how to create the logo for the **QuantumToolbox.jl** package. The logo represents the Wigner function of the triangular cat state, which is a linear superposition of three coherent states. The resulting Wigner function has a triangular shape that resembles the Julia logo. We will also define a custom colormap that varies based on the value of the Wigner function and the spatial coordinates, such that the three blobs corresponding to the coherent states have different colors (matching the colors of the Julia logo). ### Triangular Cat State @@ -196,4 +196,4 @@ fig ## Conclusion -This tutorial demonstrates how to generate the [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) logo using the package itself and [Makie.jl](https://github.com/MakieOrg/Makie.jl) for visualization. The logo is a visualization of the Wigner function of a triangular cat state, with a custom colormap that highlights the different coherent states with colors matching the Julia logo. +This example demonstrates how to generate the [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) logo using the package itself and [Makie.jl](https://github.com/MakieOrg/Makie.jl) for visualization. The logo is a visualization of the Wigner function of a triangular cat state, with a custom colormap that highlights the different coherent states with colors matching the Julia logo. diff --git a/docs/src/index.md b/docs/src/index.md index bf4d70b12..148d29327 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -17,11 +17,14 @@ hero: text: Users Guide link: /users_guide/QuantumObject/QuantumObject - theme: alt - text: View on Github - link: https://github.com/qutip/QuantumToolbox.jl + text: Tutorials + link: https://qutip.org/qutip-julia-tutorials/ - theme: alt text: API link: /resources/api + - theme: alt + text: View on Github + link: https://github.com/qutip/QuantumToolbox.jl - theme: alt text: Visit QuTiP.org link: https://qutip.org/ @@ -39,7 +42,7 @@ features: - icon: title: Distributed Computing details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. - link: /tutorials/cluster + link: /users_guide/cluster --- ``` diff --git a/docs/src/tutorials/lowrank.md b/docs/src/tutorials/lowrank.md deleted file mode 100644 index d70846008..000000000 --- a/docs/src/tutorials/lowrank.md +++ /dev/null @@ -1,177 +0,0 @@ -# [Low rank master equation](@id doc-tutor:Low-rank-master-equation) - -In this tutorial, we will show how to solve the master equation using the low-rank method. For a detailed explanation of the method, we recommend to read the article [gravina2024adaptive](@cite). - -As a test, we will consider the dissipative Ising model with a transverse field. The Hamiltonian is given by - -```math -\hat{H} = \frac{J_x}{2} \sum_{\langle i,j \rangle} \sigma_i^x \sigma_j^x + \frac{J_y}{2} \sum_{\langle i,j \rangle} \sigma_i^y \sigma_j^y + \frac{J_z}{2} \sum_{\langle i,j \rangle} \sigma_i^z \sigma_j^z - \sum_i h_i \sigma_i^z + h_x \sum_i \sigma_i^x + h_y \sum_i \sigma_i^y + h_z \sum_i \sigma_i^z, -``` - -where the sums are over nearest neighbors, and the collapse operators are given by - -```math -c_i = \sqrt{\gamma} \sigma_i^-. -``` - -We start by importing the packages - -```@example lowrank -using QuantumToolbox -using CairoMakie -CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) -``` - -Define lattice - -```@example lowrank -Nx, Ny = 2, 3 -latt = Lattice(Nx = Nx, Ny = Ny) -``` - -Define lr-space dimensions - -```@example lowrank -N_cut = 2 # Number of states of each mode -N_modes = latt.N # Number of modes -N = N_cut^N_modes # Total number of states -M = latt.N + 1 # Number of states in the LR basis -``` - -Define lr states. Take as initial state all spins up. All other N states are taken as those with miniman Hamming distance to the initial state. - -```@example lowrank -ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject,M-1}}(undef, M) -ϕ[1] = kron(fill(basis(2, 1), N_modes)...) - -i = 1 -for j in 1:N_modes - global i += 1 - i <= M && (ϕ[i] = MultiSiteOperator(latt, j=>sigmap()) * ϕ[1]) -end -for k in 1:N_modes-1 - for l in k+1:N_modes - global i += 1 - i <= M && (ϕ[i] = MultiSiteOperator(latt, k=>sigmap(), l=>sigmap()) * ϕ[1]) - end -end -for i in i+1:M - ϕ[i] = QuantumObject(rand(ComplexF64, size(ϕ[1])[1]), dims = ϕ[1].dims) - normalize!(ϕ[i]) -end -nothing # hide -``` - -Define the initial state - -```@example lowrank -z = hcat(get_data.(ϕ)...) -B = Matrix(Diagonal([1 + 0im; zeros(M - 1)])) -S = z' * z # Overlap matrix -B = B / tr(S * B) # Normalize B - -ρ = QuantumObject(z * B * z', dims = ntuple(i->N_cut, Val(N_modes))); # Full density matrix -``` - -Define the Hamiltonian and collapse operators - -```@example lowrank -# Define Hamiltonian and collapse operators -Jx = 0.9 -Jy = 1.04 -Jz = 1.0 -hx = 0.0 -hy = 0.0 -hz = 0.0 -γ = 1 - -Sx = mapreduce(i->MultiSiteOperator(latt, i=>sigmax()), +, 1:latt.N) -Sy = mapreduce(i->MultiSiteOperator(latt, i=>sigmay()), +, 1:latt.N) -Sz = mapreduce(i->MultiSiteOperator(latt, i=>sigmaz()), +, 1:latt.N) - -H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) -e_ops = (Sx, Sy, Sz) - -tl = range(0, 10, 100) -nothing # hide -``` - -### Full evolution - -```@example lowrank -sol_me = mesolve(H, ρ, tl, c_ops; e_ops = [e_ops...]); -Strue = entropy_vn(sol_me.states[end], base=2) / latt.N -``` - -### Low Rank evolution - -Define functions to be evaluated during the low-rank evolution - -```@example lowrank -function f_purity(p, z, B) - N = p.N - M = p.M - S = p.S - T = p.temp_MM - - mul!(T, S, B) - return tr(T^2) -end - -function f_trace(p, z, B) - N = p.N - M = p.M - S = p.S - T = p.temp_MM - - mul!(T, S, B) - return tr(T) -end - -function f_entropy(p, z, B) - C = p.A0 - σ = p.Bi - - mul!(C, z, sqrt(B)) - mul!(σ, C', C) - return entropy_vn(Qobj(Hermitian(σ), type=Operator), base=2) -end -``` - -Define the options for the low-rank evolution - -```@example lowrank -opt = (err_max = 1e-3, p0 = 0.0, atol_inv = 1e-6, adj_condition = "variational", Δt = 0.0); - -sol_lr = lr_mesolve(H, z, B, tl, c_ops; e_ops = e_ops, f_ops = (f_purity, f_entropy, f_trace), opt = opt); -``` - -Plot the results - -```@example lowrank -m_me = real(sol_me.expect[3, :]) / Nx / Ny -m_lr = real(sol_lr.expect[3, :]) / Nx / Ny - -fig = Figure(size = (500, 350), fontsize = 15) -ax = Axis(fig[1, 1], xlabel = L"\gamma t", ylabel = L"M_{z}", xlabelsize = 20, ylabelsize = 20) -lines!(ax, tl, m_lr, label = L"LR $[M=M(t)]$", linewidth = 2) -lines!(ax, tl, m_me, label = "Fock", linewidth = 2, linestyle = :dash) -axislegend(ax, position = :rb) - -ax2 = Axis(fig[1, 2], xlabel = L"\gamma t", ylabel = "Value", xlabelsize = 20, ylabelsize = 20) -lines!(ax2, tl, 1 .- real(sol_lr.fexpect[1, :]), label = L"$1-P$", linewidth = 2) -lines!( - ax2, - tl, - 1 .- real(sol_lr.fexpect[3, :]), - label = L"$1-\mathrm{Tr}(\rho)$", - linewidth = 2, - linestyle = :dash, - color = :orange, -) -lines!(ax2, tl, real(sol_lr.fexpect[2, :]) / Nx / Ny, color = :blue, label = L"S", linewidth = 2) -hlines!(ax2, [Strue], color = :blue, linestyle = :dash, linewidth = 2, label = L"S^{\,\mathrm{true}}_{\mathrm{ss}}") -axislegend(ax2, position = :rb) - -fig -``` diff --git a/docs/src/tutorials/cluster.md b/docs/src/users_guide/cluster.md similarity index 80% rename from docs/src/tutorials/cluster.md rename to docs/src/users_guide/cluster.md index 5bd752b9c..3e1726ffa 100644 --- a/docs/src/tutorials/cluster.md +++ b/docs/src/users_guide/cluster.md @@ -1,33 +1,16 @@ -# [Intensive parallelization on a Cluster](@id doc-tutor:Intensive-parallelization-on-a-Cluster) +# [Intensive parallelization on a Cluster](@id doc:Intensive-parallelization-on-a-Cluster) ## Introduction -In this tutorial, we will demonstrate how to seamlessly perform intensive parallelization on a cluster using the **QuantumToolbox.jl** package. Indeed, thanks to the [**Distributed.jl**](https://docs.julialang.org/en/v1/manual/distributed-computing/) and [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) packages, it is possible to parallelize on a cluster with minimal effort. The following examples are applied to a cluster with the [SLURM](https://slurm.schedmd.com/documentation.html) workload manager, but the same principles can be applied to other workload managers, as the [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) package is very versatile. +In this example, we will demonstrate how to seamlessly perform intensive parallelization on a cluster using the **QuantumToolbox.jl** package. Indeed, thanks to the [**Distributed.jl**](https://docs.julialang.org/en/v1/manual/distributed-computing/) and [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) packages, it is possible to parallelize on a cluster with minimal effort. The following examples are applied to a cluster with the [SLURM](https://slurm.schedmd.com/documentation.html) workload manager, but the same principles can be applied to other workload managers, as the [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) package is very versatile. -We will consider two examples: +## SLURM batch script -1. **Parallelization of a Monte Carlo quantum trajectories** -2. **Parallelization of a Master Equation by sweeping over parameters** - -### Monte Carlo Quantum Trajectories - -Let's consider a 2-dimensional transverse field Ising model with 4x3 spins. The Hamiltonian is given by - -```math -\hat{H} = \frac{J_z}{2} \sum_{\langle i,j \rangle} \hat{\sigma}_i^z \hat{\sigma}_j^z + h_x \sum_i \hat{\sigma}_i^x \, , -``` - -where the sums are over nearest neighbors, and the collapse operators are given by - -```math -\hat{c}_i = \sqrt{\gamma} \hat{\sigma}_i^- \, . -``` - -We start by creating a file named `run.batch` with the following content: +To submit a batch script to [SLURM](https://slurm.schedmd.com/documentation.html), we start by creating a file named `run.batch` with the following content: ```bash #!/bin/bash -#SBATCH --job-name=tutorial +#SBATCH --job-name=example #SBATCH --output=output.out #SBATCH --account=your_account #SBATCH --nodes=10 @@ -44,9 +27,34 @@ export PATH=/home/username/.juliaup/bin:$PATH julia --project script.jl ``` -where we have to replace `your_account` with the name of your account. This script will be used to submit the job to the cluster. Here, we are requesting 10 nodes with 72 threads each (720 parallel jobs). The `--time` flag specifies the maximum time that the job can run. To see all the available options, you can check the [SLURM documentation](https://slurm.schedmd.com/documentation.html). We also export the path to the custom Julia installation, which is necessary to run the script (replace `username` with your username). Finally, we run the script `script.jl` with the command `julia --project script.jl`. +where we have to replace `your_account` with the name of your account. This script will be used to submit the job to the cluster by using the following command in terminal: + +```shell +sbatch run.batch +``` + +Here, we are requesting `10` nodes with `72` threads each (`720` parallel jobs). The `--time` flag specifies the maximum time that the job can run. To see all the available options, you can check the [SLURM documentation](https://slurm.schedmd.com/documentation.html). We also export the path to the custom Julia installation, which is necessary to run the script (replace `username` with your username). Finally, we run the script `script.jl` with the command `julia --project script.jl`. + +In the following, we will consider two examples: + +1. **Parallelization of a Monte Carlo quantum trajectories** +2. **Parallelization of a Master Equation by sweeping over parameters** + +## Monte Carlo Quantum Trajectories + +Let's consider a `2`-dimensional transverse field Ising model with `4x3` spins. The Hamiltonian is given by + +```math +\hat{H} = \frac{J_z}{2} \sum_{\langle i,j \rangle} \hat{\sigma}_i^z \hat{\sigma}_j^z + h_x \sum_i \hat{\sigma}_i^x \, , +``` + +where the sums are over nearest neighbors, and the collapse operators are given by + +```math +\hat{c}_i = \sqrt{\gamma} \hat{\sigma}_i^- \, . +``` -The `script.jl` contains the following content: +In this case, the `script.jl` contains the following content: ```julia using Distributed @@ -121,7 +129,7 @@ With the println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.nthreads, 2)) threads each.") ``` -command, we test that the distributed network is correctly initialized. The `remotecall_fetch(Threads.nthreads, 2)` command returns the number of threads of the worker with ID 2. +command, we test that the distributed network is correctly initialized. The `remotecall_fetch(Threads.nthreads, 2)` command returns the number of threads of the worker with ID `2`. We then write the main part of the script, where we define the lattice through the [`Lattice`](@ref) function. We set the parameters and define the Hamiltonian and collapse operators with the [`DissipativeIsing`](@ref) function. We also define the expectation operators `e_ops` and the initial state `ψ0`. Finally, we perform the Monte Carlo quantum trajectories with the [`mcsolve`](@ref) function. The `ensemble_method=EnsembleSplitThreads()` argument is used to parallelize the Monte Carlo quantum trajectories, by splitting the ensemble of trajectories among the workers. For a more detailed explanation of the different ensemble methods, you can check the [official documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/) of the [**DifferentialEquations.jl**](https://github.com/SciML/DifferentialEquations.jl/) package. Finally, the `rmprocs(workers())` command is used to remove the workers after the computation is finished. @@ -140,7 +148,7 @@ FINISH! where we can see that the computation **lasted only 21 seconds**. -### Master Equation by Sweeping Over Parameters +## Master Equation by Sweeping Over Parameters In this example, we will consider a driven Jaynes-Cummings model, describing a two-level atom interacting with a driven cavity mode. The Hamiltonian is given by @@ -154,7 +162,7 @@ and the collapse operators are given by \hat{c}_1 = \sqrt{\gamma} \hat{a} \, , \quad \hat{c}_2 = \sqrt{\gamma} \hat{\sigma}_- \, . ``` -The SLURM file is the same as before, but the `script.jl` file now contains the following content: +The SLURM batch script file is the same as before, but the `script.jl` file now contains the following content: ```julia using Distributed From 4f81980df99e0f89edb7139aeb6c72fa71cee239 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:30:16 +0900 Subject: [PATCH 162/329] Introduce `Space`, `Dimensions`, and `GeneralDimensions` (#360) --- CHANGELOG.md | 4 + docs/src/resources/api.md | 5 +- ext/QuantumToolboxCUDAExt.jl | 18 +- src/QuantumToolbox.jl | 2 + src/correlations.jl | 5 +- src/negativity.jl | 29 ++- src/qobj/arithmetic_and_attributes.jl | 203 +++++++++++------- src/qobj/block_diagonal_form.jl | 4 +- src/qobj/dimensions.jl | 104 +++++++++ src/qobj/eigsolve.jl | 66 +++--- src/qobj/functions.jl | 63 +++++- src/qobj/operators.jl | 27 ++- src/qobj/quantum_object.jl | 101 +++++---- src/qobj/quantum_object_base.jl | 136 +++++++++--- src/qobj/quantum_object_evo.jl | 66 +++--- src/qobj/space.jl | 41 ++++ src/qobj/states.jl | 16 +- src/qobj/superoperators.jl | 18 +- src/spectrum.jl | 8 +- src/spin_lattice.jl | 2 +- src/steadystate.jl | 19 +- src/time_evolution/lr_mesolve.jl | 2 +- src/time_evolution/mcsolve.jl | 4 +- src/time_evolution/mesolve.jl | 6 +- src/time_evolution/sesolve.jl | 10 +- src/time_evolution/ssesolve.jl | 4 +- src/time_evolution/time_evolution.jl | 30 ++- .../time_evolution_dynamical.jl | 4 +- test/core-test/low_rank_dynamics.jl | 2 +- .../negativity_and_partial_transpose.jl | 1 + test/core-test/quantum_objects.jl | 91 +++++++- test/core-test/quantum_objects_evo.jl | 18 +- test/core-test/steady_state.jl | 4 - 33 files changed, 779 insertions(+), 334 deletions(-) create mode 100644 src/qobj/dimensions.jl create mode 100644 src/qobj/space.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e3a8c0d0..221fac574 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 - Change the structure of block diagonalization functions, using `BlockDiagonalForm` struct and changing the function name from `bdf` to `block_diagonal_form`. ([#349]) - Add **GPUArrays** compatibility for `ptrace` function, by using **KernelAbstractions.jl**. ([#350]) +- Introduce `Space`, `Dimensions`, `GeneralDimensions` structures to support wider definitions and operations of `Qobj/QobjEvo`, and potential functionalities in the future. ([#271], [#353], [#360]) ## [v0.24.0] Release date: 2024-12-13 @@ -55,6 +56,7 @@ Release date: 2024-11-13 [v0.24.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.24.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 +[#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 [#292]: https://github.com/qutip/QuantumToolbox.jl/issues/292 [#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 [#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 @@ -71,3 +73,5 @@ Release date: 2024-11-13 [#347]: https://github.com/qutip/QuantumToolbox.jl/issues/347 [#349]: https://github.com/qutip/QuantumToolbox.jl/issues/349 [#350]: https://github.com/qutip/QuantumToolbox.jl/issues/350 +[#353]: https://github.com/qutip/QuantumToolbox.jl/issues/353 +[#360]: https://github.com/qutip/QuantumToolbox.jl/issues/360 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index ff890fea9..1063f21f3 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -11,6 +11,9 @@ CurrentModule = QuantumToolbox ## [Quantum object (Qobj) and type](@id doc-API:Quantum-object-and-type) ```@docs +Space +Dimensions +GeneralDimensions AbstractQuantumObject BraQuantumObject Bra @@ -73,7 +76,7 @@ LinearAlgebra.diag proj ptrace purity -permute +SparseArrays.permute tidyup tidyup! get_data diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index 7d379c3a1..459356b05 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -10,35 +10,37 @@ import SparseArrays: SparseVector, SparseMatrixCSC If `A.data` is a dense array, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CuArray` for gpu calculations. """ -CuArray(A::QuantumObject{Tq}) where {Tq<:Union{Vector,Matrix}} = QuantumObject(CuArray(A.data), A.type, A.dims) +CuArray(A::QuantumObject{Tq}) where {Tq<:Union{Vector,Matrix}} = QuantumObject(CuArray(A.data), A.type, A.dimensions) @doc raw""" CuArray{T}(A::QuantumObject) If `A.data` is a dense array, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CuArray` with element type `T` for gpu calculations. """ -CuArray{T}(A::QuantumObject{Tq}) where {T,Tq<:Union{Vector,Matrix}} = QuantumObject(CuArray{T}(A.data), A.type, A.dims) +CuArray{T}(A::QuantumObject{Tq}) where {T,Tq<:Union{Vector,Matrix}} = + QuantumObject(CuArray{T}(A.data), A.type, A.dimensions) @doc raw""" CuSparseVector(A::QuantumObject) If `A.data` is a sparse vector, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseVector` for gpu calculations. """ -CuSparseVector(A::QuantumObject{<:SparseVector}) = QuantumObject(CuSparseVector(A.data), A.type, A.dims) +CuSparseVector(A::QuantumObject{<:SparseVector}) = QuantumObject(CuSparseVector(A.data), A.type, A.dimensions) @doc raw""" CuSparseVector{T}(A::QuantumObject) If `A.data` is a sparse vector, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseVector` with element type `T` for gpu calculations. """ -CuSparseVector{T}(A::QuantumObject{<:SparseVector}) where {T} = QuantumObject(CuSparseVector{T}(A.data), A.type, A.dims) +CuSparseVector{T}(A::QuantumObject{<:SparseVector}) where {T} = + QuantumObject(CuSparseVector{T}(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSC(A::QuantumObject) If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSC` for gpu calculations. """ -CuSparseMatrixCSC(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseMatrixCSC(A.data), A.type, A.dims) +CuSparseMatrixCSC(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseMatrixCSC(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSC{T}(A::QuantumObject) @@ -46,14 +48,14 @@ CuSparseMatrixCSC(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseM If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSC` with element type `T` for gpu calculations. """ CuSparseMatrixCSC{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T} = - QuantumObject(CuSparseMatrixCSC{T}(A.data), A.type, A.dims) + QuantumObject(CuSparseMatrixCSC{T}(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSR(A::QuantumObject) If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSR` for gpu calculations. """ -CuSparseMatrixCSR(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseMatrixCSR(A.data), A.type, A.dims) +CuSparseMatrixCSR(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseMatrixCSR(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSR(A::QuantumObject) @@ -61,7 +63,7 @@ CuSparseMatrixCSR(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseM If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSR` with element type `T` for gpu calculations. """ CuSparseMatrixCSR{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T} = - QuantumObject(CuSparseMatrixCSR{T}(A.data), A.type, A.dims) + QuantumObject(CuSparseMatrixCSR{T}(A.data), A.type, A.dimensions) @doc raw""" cu(A::QuantumObject; word_size::Int=64) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 28a50f251..27055e097 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -79,6 +79,8 @@ include("progress_bar.jl") include("linear_maps.jl") # Quantum Object +include("qobj/space.jl") +include("qobj/dimensions.jl") include("qobj/quantum_object_base.jl") include("qobj/quantum_object.jl") include("qobj/quantum_object_evo.jl") diff --git a/src/correlations.jl b/src/correlations.jl index 5b7068d9e..ada51d3da 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -51,8 +51,7 @@ function correlation_3op_2t( ψ0 = steadystate(L) end - allequal((L.dims, ψ0.dims, A.dims, B.dims, C.dims)) || - throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + check_dimensions(L, ψ0, A, B, C) kwargs2 = merge((saveat = collect(tlist),), (; kwargs...)) ρt_list = mesolve(L, ψ0, tlist; kwargs2...).states @@ -137,7 +136,7 @@ function correlation_2op_2t( HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } - C = eye(prod(H.dims), dims = H.dims) + C = eye(prod(H.dimensions), dims = H.dimensions) if reverse corr = correlation_3op_2t(H, ψ0, tlist, τlist, c_ops, A, B, C; kwargs...) else diff --git a/src/negativity.jl b/src/negativity.jl index d91b76108..f9fa2a215 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -41,7 +41,7 @@ julia> round(negativity(ρ, 2), digits=2) ``` """ function negativity(ρ::QuantumObject, subsys::Int; logarithmic::Bool = false) - mask = fill(false, length(ρ.dims)) + mask = fill(false, length(ρ.dimensions)) try mask[subsys] = true catch @@ -70,37 +70,46 @@ Return the partial transpose of a density matrix ``\rho``, where `mask` is an ar # Returns - `ρ_pt::QuantumObject`: The density matrix with the selected subsystems transposed. """ -function partial_transpose(ρ::QuantumObject{T,OperatorQuantumObject}, mask::Vector{Bool}) where {T} - if length(mask) != length(ρ.dims) +function partial_transpose(ρ::QuantumObject{DT,OperatorQuantumObject}, mask::Vector{Bool}) where {DT} + if length(mask) != length(ρ.dimensions) throw(ArgumentError("The length of \`mask\` should be equal to the length of \`ρ.dims\`.")) end return _partial_transpose(ρ, mask) end # for dense matrices -function _partial_transpose(ρ::QuantumObject{<:AbstractArray,OperatorQuantumObject}, mask::Vector{Bool}) +function _partial_transpose(ρ::QuantumObject{DT,OperatorQuantumObject}, mask::Vector{Bool}) where {DT<:AbstractArray} + isa(ρ.dimensions, GeneralDimensions) && + (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && + throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) + mask2 = [1 + Int(i) for i in mask] # mask2 has elements with values equal to 1 or 2 # 1 - the subsystem don't need to be transposed # 2 - the subsystem need be transposed nsys = length(mask2) + dims = dimensions_to_dims(get_dimensions_to(ρ)) pt_dims = reshape(Vector(1:(2*nsys)), (nsys, 2)) pt_idx = [ - [pt_dims[n, mask2[n]] for n in 1:nsys] # origin value in mask2 - [pt_dims[n, 3-mask2[n]] for n in 1:nsys] # opposite value in mask2 (1 -> 2, and 2 -> 1) + [pt_dims[n, mask2[n]] for n in 1:nsys] # origin value in mask2 + [pt_dims[n, 3-mask2[n]] for n in 1:nsys] # opposite value in mask2 (1 -> 2, and 2 -> 1) ] return QuantumObject( - reshape(permutedims(reshape(ρ.data, (ρ.dims..., ρ.dims...)), pt_idx), size(ρ)), + reshape(permutedims(reshape(ρ.data, (dims..., dims...)), pt_idx), size(ρ)), Operator, - ρ.dims, + Dimensions(ρ.dimensions.to), ) end # for sparse matrices function _partial_transpose(ρ::QuantumObject{<:AbstractSparseArray,OperatorQuantumObject}, mask::Vector{Bool}) + isa(ρ.dimensions, GeneralDimensions) && + (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && + throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) + M, N = size(ρ) - dimsTuple = Tuple(ρ.dims) + dimsTuple = Tuple(dimensions_to_dims(get_dimensions_to(ρ))) colptr = ρ.data.colptr rowval = ρ.data.rowval nzval = ρ.data.nzval @@ -132,5 +141,5 @@ function _partial_transpose(ρ::QuantumObject{<:AbstractSparseArray,OperatorQuan end end - return QuantumObject(sparse(I_pt, J_pt, V_pt, M, N), Operator, ρ.dims) + return QuantumObject(sparse(I_pt, J_pt, V_pt, M, N), Operator, ρ.dimensions) end diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index f9af5aeeb..0ae908b6f 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -4,7 +4,7 @@ Arithmetic and Attributes for QuantumObject - export most of the attribute functions in "Python Qobj class" =# -export proj, ptrace, purity, permute +export proj, ptrace, purity export tidyup, tidyup! export get_data, get_coherence @@ -13,23 +13,23 @@ Base.broadcastable(x::QuantumObject) = x.data for op in (:(+), :(-), :(*), :(/), :(^)) @eval begin function Base.Broadcast.broadcasted(::typeof($op), x::QuantumObject, y::QuantumObject) - return QuantumObject(broadcast($op, x.data, y.data), x.type, x.dims) + return QuantumObject(broadcast($op, x.data, y.data), x.type, x.dimensions) end function Base.Broadcast.broadcasted(::typeof($op), x::QuantumObject, y::Number) - return QuantumObject(broadcast($op, x.data, y), x.type, x.dims) + return QuantumObject(broadcast($op, x.data, y), x.type, x.dimensions) end function Base.Broadcast.broadcasted(::typeof($op), x::Number, y::QuantumObject) - return QuantumObject(broadcast($op, x, y.data), y.type, y.dims) + return QuantumObject(broadcast($op, x, y.data), y.type, y.dimensions) end function Base.Broadcast.broadcasted(::typeof($op), x::QuantumObject, y::AbstractArray) - return QuantumObject(broadcast($op, x.data, y), x.type, x.dims) + return QuantumObject(broadcast($op, x.data, y), x.type, x.dimensions) end function Base.Broadcast.broadcasted(::typeof($op), x::AbstractArray, y::QuantumObject) - return QuantumObject(broadcast($op, x, y.data), y.type, y.dims) + return QuantumObject(broadcast($op, x, y.data), y.type, y.dimensions) end end end @@ -37,79 +37,120 @@ end for op in (:(+), :(-), :(*)) @eval begin function LinearAlgebra.$op(A::AbstractQuantumObject, B::AbstractQuantumObject) - check_dims(A, B) + check_dimensions(A, B) QType = promote_op_type(A, B) - return QType($(op)(A.data, B.data), A.type, A.dims) + return QType($(op)(A.data, B.data), A.type, A.dimensions) end - LinearAlgebra.$op(A::AbstractQuantumObject) = get_typename_wrapper(A)($(op)(A.data), A.type, A.dims) + LinearAlgebra.$op(A::AbstractQuantumObject) = get_typename_wrapper(A)($(op)(A.data), A.type, A.dimensions) LinearAlgebra.$op(n::T, A::AbstractQuantumObject) where {T<:Number} = - get_typename_wrapper(A)($(op)(n * I, A.data), A.type, A.dims) + get_typename_wrapper(A)($(op)(n * I, A.data), A.type, A.dimensions) LinearAlgebra.$op(A::AbstractQuantumObject, n::T) where {T<:Number} = - get_typename_wrapper(A)($(op)(A.data, n * I), A.type, A.dims) + get_typename_wrapper(A)($(op)(A.data, n * I), A.type, A.dimensions) + end +end + +function check_mul_dimensions(from::NTuple{NA,AbstractSpace}, to::NTuple{NB,AbstractSpace}) where {NA,NB} + (from != to) && throw( + DimensionMismatch( + "The quantum object with (right) dims = $(dimensions_to_dims(from)) can not multiply a quantum object with (left) dims = $(dimensions_to_dims(to)) on the right-hand side.", + ), + ) + return nothing +end + +for ADimType in (:Dimensions, :GeneralDimensions) + for BDimType in (:Dimensions, :GeneralDimensions) + if ADimType == BDimType == :Dimensions + @eval begin + function LinearAlgebra.:(*)( + A::AbstractQuantumObject{DT1,OperatorQuantumObject,<:$ADimType}, + B::AbstractQuantumObject{DT2,OperatorQuantumObject,<:$BDimType}, + ) where {DT1,DT2} + check_dimensions(A, B) + QType = promote_op_type(A, B) + return QType(A.data * B.data, Operator, A.dimensions) + end + end + else + @eval begin + function LinearAlgebra.:(*)( + A::AbstractQuantumObject{DT1,OperatorQuantumObject,<:$ADimType}, + B::AbstractQuantumObject{DT2,OperatorQuantumObject,<:$BDimType}, + ) where {DT1,DT2} + check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) + QType = promote_op_type(A, B) + return QType( + A.data * B.data, + Operator, + GeneralDimensions(get_dimensions_to(A), get_dimensions_from(B)), + ) + end + end + end end end function LinearAlgebra.:(*)( A::AbstractQuantumObject{DT1,OperatorQuantumObject}, - B::QuantumObject{DT2,KetQuantumObject}, + B::QuantumObject{DT2,KetQuantumObject,<:Dimensions}, ) where {DT1,DT2} - check_dims(A, B) - return QuantumObject(A.data * B.data, Ket, A.dims) + check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) + return QuantumObject(A.data * B.data, Ket, Dimensions(get_dimensions_to(A))) end function LinearAlgebra.:(*)( - A::QuantumObject{DT1,BraQuantumObject}, + A::QuantumObject{DT1,BraQuantumObject,<:Dimensions}, B::AbstractQuantumObject{DT2,OperatorQuantumObject}, ) where {DT1,DT2} - check_dims(A, B) - return QuantumObject(A.data * B.data, Bra, A.dims) + check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) + return QuantumObject(A.data * B.data, Bra, Dimensions(get_dimensions_from(B))) end function LinearAlgebra.:(*)( A::QuantumObject{DT1,KetQuantumObject}, B::QuantumObject{DT2,BraQuantumObject}, ) where {DT1,DT2} - check_dims(A, B) - return QuantumObject(A.data * B.data, Operator, A.dims) + check_dimensions(A, B) + return QuantumObject(A.data * B.data, Operator, A.dimensions) # to align with QuTiP, don't use kron(A, B) to do it. end function LinearAlgebra.:(*)( A::QuantumObject{DT1,BraQuantumObject}, B::QuantumObject{DT2,KetQuantumObject}, ) where {DT1,DT2} - check_dims(A, B) + check_dimensions(A, B) return A.data * B.data end function LinearAlgebra.:(*)( A::AbstractQuantumObject{DT1,SuperOperatorQuantumObject}, B::QuantumObject{DT2,OperatorQuantumObject}, ) where {DT1,DT2} - check_dims(A, B) - return QuantumObject(vec2mat(A.data * mat2vec(B.data)), Operator, A.dims) + check_dimensions(A, B) + return QuantumObject(vec2mat(A.data * mat2vec(B.data)), Operator, A.dimensions) end function LinearAlgebra.:(*)( A::QuantumObject{DT1,OperatorBraQuantumObject}, B::QuantumObject{DT2,OperatorKetQuantumObject}, ) where {DT1,DT2} - check_dims(A, B) + check_dimensions(A, B) return A.data * B.data end function LinearAlgebra.:(*)( A::AbstractQuantumObject{DT1,SuperOperatorQuantumObject}, B::QuantumObject{DT2,OperatorKetQuantumObject}, ) where {DT1,DT2} - check_dims(A, B) - return QuantumObject(A.data * B.data, OperatorKet, A.dims) + check_dimensions(A, B) + return QuantumObject(A.data * B.data, OperatorKet, A.dimensions) end function LinearAlgebra.:(*)( A::QuantumObject{<:AbstractArray{T1},OperatorBraQuantumObject}, B::AbstractQuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, ) where {T1,T2} - check_dims(A, B) - return QuantumObject(A.data * B.data, OperatorBra, A.dims) + check_dimensions(A, B) + return QuantumObject(A.data * B.data, OperatorBra, A.dimensions) end -LinearAlgebra.:(^)(A::QuantumObject{DT}, n::T) where {DT,T<:Number} = QuantumObject(^(A.data, n), A.type, A.dims) +LinearAlgebra.:(^)(A::QuantumObject{DT}, n::T) where {DT,T<:Number} = QuantumObject(^(A.data, n), A.type, A.dimensions) LinearAlgebra.:(/)(A::AbstractQuantumObject{DT}, n::T) where {DT,T<:Number} = - get_typename_wrapper(A)(A.data / n, A.type, A.dims) + get_typename_wrapper(A)(A.data / n, A.type, A.dimensions) @doc raw""" A ⋅ B @@ -126,7 +167,7 @@ function LinearAlgebra.dot( A::QuantumObject{DT1,OpType}, B::QuantumObject{DT2,OpType}, ) where {DT1,DT2,OpType<:Union{KetQuantumObject,OperatorKetQuantumObject}} - A.dims != B.dims && throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + check_dimensions(A, B) return LinearAlgebra.dot(A.data, B.data) end @@ -148,8 +189,7 @@ function LinearAlgebra.dot( A::AbstractQuantumObject{DT2,OperatorQuantumObject}, j::QuantumObject{DT3,KetQuantumObject}, ) where {DT1,DT2,DT3} - ((i.dims != A.dims) || (A.dims != j.dims)) && - throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + check_dimensions(i, A, j) return LinearAlgebra.dot(i.data, A.data, j.data) end function LinearAlgebra.dot( @@ -157,8 +197,7 @@ function LinearAlgebra.dot( A::AbstractQuantumObject{DT2,SuperOperatorQuantumObject}, j::QuantumObject{DT3,OperatorKetQuantumObject}, ) where {DT1,DT2,DT3} - ((i.dims != A.dims) || (A.dims != j.dims)) && - throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + check_dimensions(i, A, j) return LinearAlgebra.dot(i.data, A.data, j.data) end @@ -167,7 +206,7 @@ end Return a similar [`AbstractQuantumObject`](@ref) with `dims` and `type` are same as `A`, but `data` is a zero-array. """ -Base.zero(A::AbstractQuantumObject) = get_typename_wrapper(A)(zero(A.data), A.type, A.dims) +Base.zero(A::AbstractQuantumObject) = get_typename_wrapper(A)(zero(A.data), A.type, A.dimensions) @doc raw""" one(A::AbstractQuantumObject) @@ -179,14 +218,14 @@ Note that `A` must be [`Operator`](@ref) or [`SuperOperator`](@ref). Base.one( A::AbstractQuantumObject{DT,OpType}, ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - get_typename_wrapper(A)(one(A.data), A.type, A.dims) + get_typename_wrapper(A)(one(A.data), A.type, A.dimensions) @doc raw""" conj(A::AbstractQuantumObject) Return the element-wise complex conjugation of the [`AbstractQuantumObject`](@ref). """ -Base.conj(A::AbstractQuantumObject) = get_typename_wrapper(A)(conj(A.data), A.type, A.dims) +Base.conj(A::AbstractQuantumObject) = get_typename_wrapper(A)(conj(A.data), A.type, A.dimensions) @doc raw""" transpose(A::AbstractQuantumObject) @@ -196,7 +235,7 @@ Lazy matrix transpose of the [`AbstractQuantumObject`](@ref). LinearAlgebra.transpose( A::AbstractQuantumObject{DT,OpType}, ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - get_typename_wrapper(A)(transpose(A.data), A.type, A.dims) + get_typename_wrapper(A)(transpose(A.data), A.type, transpose(A.dimensions)) @doc raw""" A' @@ -211,13 +250,15 @@ Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) LinearAlgebra.adjoint( A::AbstractQuantumObject{DT,OpType}, ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - get_typename_wrapper(A)(adjoint(A.data), A.type, A.dims) -LinearAlgebra.adjoint(A::QuantumObject{DT,KetQuantumObject}) where {DT} = QuantumObject(adjoint(A.data), Bra, A.dims) -LinearAlgebra.adjoint(A::QuantumObject{DT,BraQuantumObject}) where {DT} = QuantumObject(adjoint(A.data), Ket, A.dims) + get_typename_wrapper(A)(adjoint(A.data), A.type, adjoint(A.dimensions)) +LinearAlgebra.adjoint(A::QuantumObject{DT,KetQuantumObject}) where {DT} = + QuantumObject(adjoint(A.data), Bra, adjoint(A.dimensions)) +LinearAlgebra.adjoint(A::QuantumObject{DT,BraQuantumObject}) where {DT} = + QuantumObject(adjoint(A.data), Ket, adjoint(A.dimensions)) LinearAlgebra.adjoint(A::QuantumObject{DT,OperatorKetQuantumObject}) where {DT} = - QuantumObject(adjoint(A.data), OperatorBra, A.dims) + QuantumObject(adjoint(A.data), OperatorBra, adjoint(A.dimensions)) LinearAlgebra.adjoint(A::QuantumObject{DT,OperatorBraQuantumObject}) where {DT} = - QuantumObject(adjoint(A.data), OperatorKet, A.dims) + QuantumObject(adjoint(A.data), OperatorKet, adjoint(A.dimensions)) @doc raw""" inv(A::AbstractQuantumObject) @@ -227,13 +268,13 @@ Matrix inverse of the [`AbstractQuantumObject`](@ref). If `A` is a [`QuantumObje LinearAlgebra.inv( A::AbstractQuantumObject{DT,OpType}, ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(sparse(inv(Matrix(A.data))), A.type, A.dims) + QuantumObject(sparse(inv(Matrix(A.data))), A.type, A.dimensions) LinearAlgebra.Hermitian( A::QuantumObject{DT,OpType}, uplo::Symbol = :U, ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(Hermitian(A.data, uplo), A.type, A.dims) + QuantumObject(Hermitian(A.data, uplo), A.type, A.dimensions) @doc raw""" tr(A::QuantumObject) @@ -336,9 +377,9 @@ Also, see [`norm`](@ref) about its definition for different types of [`QuantumOb LinearAlgebra.normalize( A::QuantumObject{<:AbstractArray{T},ObjType}, p::Real = 2, -) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject}} = QuantumObject(A.data / norm(A, p), A.type, A.dims) +) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject}} = QuantumObject(A.data / norm(A, p), A.type, A.dimensions) LinearAlgebra.normalize(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, p::Real = 1) where {T} = - QuantumObject(A.data / norm(A, p), A.type, A.dims) + QuantumObject(A.data / norm(A, p), A.type, A.dimensions) @doc raw""" normalize!(A::QuantumObject, p::Real) @@ -370,12 +411,12 @@ LinearAlgebra.triu( A::QuantumObject{<:AbstractArray{T},OpType}, k::Integer = 0, ) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(triu(A.data, k), A.type, A.dims) + QuantumObject(triu(A.data, k), A.type, A.dimensions) LinearAlgebra.tril( A::QuantumObject{<:AbstractArray{T},OpType}, k::Integer = 0, ) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(tril(A.data, k), A.type, A.dims) + QuantumObject(tril(A.data, k), A.type, A.dimensions) LinearAlgebra.lmul!(a::Number, B::QuantumObject{<:AbstractArray}) = (lmul!(a, B.data); B) LinearAlgebra.rmul!(B::QuantumObject{<:AbstractArray}, a::Number) = (rmul!(B.data, a); B) @@ -393,7 +434,7 @@ Matrix square root of [`QuantumObject`](@ref) `√(A)` (where `√` can be typed by tab-completing `\sqrt` in the REPL) is a synonym of `sqrt(A)`. """ LinearAlgebra.sqrt(A::QuantumObject{<:AbstractArray{T}}) where {T} = - QuantumObject(sqrt(sparse_to_dense(A.data)), A.type, A.dims) + QuantumObject(sqrt(sparse_to_dense(A.data)), A.type, A.dimensions) @doc raw""" log(A::QuantumObject) @@ -405,7 +446,7 @@ Note that this function only supports for [`Operator`](@ref) and [`SuperOperator LinearAlgebra.log( A::QuantumObject{<:AbstractMatrix{T},ObjType}, ) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(log(sparse_to_dense(A.data)), A.type, A.dims) + QuantumObject(log(sparse_to_dense(A.data)), A.type, A.dimensions) @doc raw""" exp(A::QuantumObject) @@ -417,11 +458,11 @@ Note that this function only supports for [`Operator`](@ref) and [`SuperOperator LinearAlgebra.exp( A::QuantumObject{<:AbstractMatrix{T},ObjType}, ) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(dense_to_sparse(exp(A.data)), A.type, A.dims) + QuantumObject(dense_to_sparse(exp(A.data)), A.type, A.dimensions) LinearAlgebra.exp( A::QuantumObject{<:AbstractSparseMatrix{T},ObjType}, ) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(_spexp(A.data), A.type, A.dims) + QuantumObject(_spexp(A.data), A.type, A.dimensions) function _spexp(A::SparseMatrixCSC{T,M}; threshold = 1e-14, nonzero_tol = 1e-20) where {T,M} m = checksquare(A) # Throws exception if not square @@ -554,7 +595,7 @@ function ptrace(QO::QuantumObject{<:AbstractArray,KetQuantumObject}, sel::Union{ if n_s == 0 # return full trace for empty sel return tr(ket2dm(QO)) else - n_d = length(QO.dims) + n_d = length(QO.dimensions) (any(>(n_d), sel) || any(<(1), sel)) && throw( ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(n_d) sub-systems"), @@ -565,19 +606,24 @@ function ptrace(QO::QuantumObject{<:AbstractArray,KetQuantumObject}, sel::Union{ _sort_sel = sort(SVector{length(sel),Int}(sel)) ρtr, dkeep = _ptrace_ket(QO.data, QO.dims, _sort_sel) - return QuantumObject(ρtr, type = Operator, dims = dkeep) + return QuantumObject(ρtr, type = Operator, dims = Dimensions(dkeep)) end ptrace(QO::QuantumObject{<:AbstractArray,BraQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel) function ptrace(QO::QuantumObject{<:AbstractArray,OperatorQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) + # TODO: support for special cases when some of the subsystems have same `to` and `from` space + isa(QO.dimensions, GeneralDimensions) && + (get_dimensions_to(QO) != get_dimensions_from(QO)) && + throw(ArgumentError("Invalid partial trace for dims = $(_get_dims_string(QO.dimensions))")) + _non_static_array_warning("sel", sel) n_s = length(sel) if n_s == 0 # return full trace for empty sel return tr(QO) else - n_d = length(QO.dims) + n_d = length(QO.dimensions) (any(>(n_d), sel) || any(<(1), sel)) && throw( ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(n_d) sub-systems"), @@ -586,9 +632,10 @@ function ptrace(QO::QuantumObject{<:AbstractArray,OperatorQuantumObject}, sel::U (n_d == 1) && return QO end + dims = dimensions_to_dims(get_dimensions_to(QO)) _sort_sel = sort(SVector{length(sel),Int}(sel)) - ρtr, dkeep = _ptrace_oper(QO.data, QO.dims, _sort_sel) - return QuantumObject(ρtr, type = Operator, dims = dkeep) + ρtr, dkeep = _ptrace_oper(QO.data, dims, _sort_sel) + return QuantumObject(ρtr, type = Operator, dims = Dimensions(dkeep)) end ptrace(QO::QuantumObject, sel::Int) = ptrace(QO, SVector(sel)) @@ -674,7 +721,7 @@ purity(ρ::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = Given a [`QuantumObject`](@ref) `A`, check the real and imaginary parts of each element separately. Remove the real or imaginary value if its absolute value is less than `tol`. """ tidyup(A::QuantumObject{<:AbstractArray{T}}, tol::T2 = 1e-14) where {T,T2<:Real} = - QuantumObject(tidyup(A.data, tol), A.type, A.dims) + QuantumObject(tidyup(A.data, tol), A.type, A.dimensions) tidyup(A::AbstractArray{T}, tol::T2 = 1e-14) where {T,T2<:Real} = tidyup!(copy(A), tol) @doc raw""" @@ -698,7 +745,7 @@ tidyup!(A::AbstractArray{T}, tol::T2 = 1e-14) where {T,T2<:Real} = Returns the data of a [`AbstractQuantumObject`](@ref). """ -get_data(A::AbstractQuantumObject) = A.data +get_data(A::AbstractQuantumObject) = getfield(A, :data) @doc raw""" get_coherence(ψ::QuantumObject) @@ -708,7 +755,7 @@ Get the coherence value ``\alpha`` by measuring the expectation value of the des It returns both ``\alpha`` and the corresponding state with the coherence removed: ``\ket{\delta_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \ket{\psi}`` for a pure state, and ``\hat{\rho_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \hat{\rho} \exp ( -\bar{\alpha} \hat{a} + \alpha \hat{a}^\dagger )`` for a density matrix. These states correspond to the quantum fluctuations around the coherent state ``\ket{\alpha}`` or ``|\alpha\rangle\langle\alpha|``. """ function get_coherence(ψ::QuantumObject{<:AbstractArray,KetQuantumObject}) - a = destroy(prod(ψ.dims)) + a = destroy(prod(ψ.dimensions)) α = expect(a, ψ) D = exp(α * a' - conj(α) * a) @@ -716,7 +763,7 @@ function get_coherence(ψ::QuantumObject{<:AbstractArray,KetQuantumObject}) end function get_coherence(ρ::QuantumObject{<:AbstractArray,OperatorQuantumObject}) - a = destroy(prod(ρ.dims)) + a = destroy(prod(ρ.dimensions)) α = expect(a, ρ) D = exp(α * a' - conj(α) * a) @@ -750,11 +797,11 @@ true !!! warning "Beware of type-stability!" It is highly recommended to use `permute(A, order)` with `order` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ -function permute( +function SparseArrays.permute( A::QuantumObject{<:AbstractArray{T},ObjType}, order::Union{AbstractVector{Int},Tuple}, ) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} - (length(order) != length(A.dims)) && + (length(order) != length(A.dimensions)) && throw(ArgumentError("The order list must have the same length as the number of subsystems (A.dims)")) !isperm(order) && throw(ArgumentError("$(order) is not a valid permutation of the subsystems (A.dims)")) @@ -766,22 +813,26 @@ function permute( # obtain the arguments: dims for reshape; perm for PermutedDimsArray dims, perm = _dims_and_perm(A.type, A.dims, order_svector, length(order_svector)) - return QuantumObject( - reshape(permutedims(reshape(A.data, dims...), Tuple(perm)), size(A)), - A.type, - A.dims[order_svector], - ) + order_dimensions = _order_dimensions(A.dimensions, order_svector) + + return QuantumObject(reshape(permutedims(reshape(A.data, dims...), Tuple(perm)), size(A)), A.type, order_dimensions) end -function _dims_and_perm( +_dims_and_perm( ::ObjType, dims::SVector{N,Int}, order::AbstractVector{Int}, L::Int, -) where {ObjType<:Union{KetQuantumObject,BraQuantumObject},N} - return reverse(dims), reverse((L + 1) .- order) -end +) where {ObjType<:Union{KetQuantumObject,BraQuantumObject},N} = reverse(dims), reverse((L + 1) .- order) -function _dims_and_perm(::OperatorQuantumObject, dims::SVector{N,Int}, order::AbstractVector{Int}, L::Int) where {N} - return reverse(vcat(dims, dims)), reverse((2 * L + 1) .- vcat(order, order .+ L)) -end +# if dims originates from Dimensions +_dims_and_perm(::OperatorQuantumObject, dims::SVector{N,Int}, order::AbstractVector{Int}, L::Int) where {N} = + reverse(vcat(dims, dims)), reverse((2 * L + 1) .- vcat(order, order .+ L)) + +# if dims originates from GeneralDimensions +_dims_and_perm(::OperatorQuantumObject, dims::SVector{2,SVector{N,Int}}, order::AbstractVector{Int}, L::Int) where {N} = + reverse(vcat(dims[2], dims[1])), reverse((2 * L + 1) .- vcat(order, order .+ L)) + +_order_dimensions(dimensions::Dimensions, order::AbstractVector{Int}) = Dimensions(dimensions.to[order]) +_order_dimensions(dimensions::GeneralDimensions, order::AbstractVector{Int}) = + GeneralDimensions(dimensions.to[order], dimensions.from[order]) diff --git a/src/qobj/block_diagonal_form.jl b/src/qobj/block_diagonal_form.jl index cc1f97eaa..ca3e89a68 100644 --- a/src/qobj/block_diagonal_form.jl +++ b/src/qobj/block_diagonal_form.jl @@ -54,7 +54,7 @@ function block_diagonal_form( A::QuantumObject{DT,OpType}, ) where {DT<:AbstractSparseMatrix,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} bdf = block_diagonal_form(A.data) - B = QuantumObject(bdf.B, type = A.type, dims = A.dims) - P = QuantumObject(bdf.P, type = A.type, dims = A.dims) + B = QuantumObject(bdf.B, type = A.type, dims = A.dimensions) + P = QuantumObject(bdf.P, type = A.type, dims = A.dimensions) return BlockDiagonalForm(B, P, bdf.blocks, bdf.block_sizes) end diff --git a/src/qobj/dimensions.jl b/src/qobj/dimensions.jl new file mode 100644 index 000000000..0fd19f3a9 --- /dev/null +++ b/src/qobj/dimensions.jl @@ -0,0 +1,104 @@ +#= +This file defines the Dimensions structures, which can describe composite Hilbert spaces. +=# + +export AbstractDimensions, Dimensions, GeneralDimensions + +abstract type AbstractDimensions{N} end + +@doc raw""" + struct Dimensions{N,T<:Tuple} <: AbstractDimensions{N} + to::T + end + +A structure that describes the Hilbert [`Space`](@ref) of each subsystems. +""" +struct Dimensions{N,T<:Tuple} <: AbstractDimensions{N} + to::T + + # make sure the elements in the tuple are all AbstractSpace + Dimensions(to::NTuple{N,T}) where {N,T<:AbstractSpace} = new{N,typeof(to)}(to) +end +function Dimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Integer,N} + _non_static_array_warning("dims", dims) + L = length(dims) + (L > 0) || throw(DomainError(dims, "The argument dims must be of non-zero length")) + + return Dimensions(NTuple{L,Space}(Space.(dims))) +end +Dimensions(dims::Int) = Dimensions(Space(dims)) +Dimensions(dims::DimType) where {DimType<:AbstractSpace} = Dimensions((dims,)) +Dimensions(dims::Any) = throw( + ArgumentError( + "The argument dims must be a Tuple or a StaticVector of non-zero length and contain only positive integers.", + ), +) + +@doc raw""" + struct GeneralDimensions{N,T1<:Tuple,T2<:Tuple} <: AbstractDimensions{N} + to::T1 + from::T2 + end + +A structure that describes the left-hand side (`to`) and right-hand side (`from`) Hilbert [`Space`](@ref) of an [`Operator`](@ref). +""" +struct GeneralDimensions{N,T1<:Tuple,T2<:Tuple} <: AbstractDimensions{N} + # note that the number `N` should be the same for both `to` and `from` + to::T1 # space acting on the left + from::T2 # space acting on the right + + # make sure the elements in the tuple are all AbstractSpace + GeneralDimensions(to::NTuple{N,T1}, from::NTuple{N,T2}) where {N,T1<:AbstractSpace,T2<:AbstractSpace} = + new{N,typeof(to),typeof(from)}(to, from) +end +function GeneralDimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Union{AbstractVector,NTuple},N} + (length(dims) != 2) && throw(ArgumentError("Invalid dims = $dims")) + + _non_static_array_warning("dims[1]", dims[1]) + _non_static_array_warning("dims[2]", dims[2]) + + L1 = length(dims[1]) + L2 = length(dims[2]) + ((L1 > 0) && (L1 == L2)) || throw( + DomainError( + (L1, L2), + "The length of the arguments `dims[1]` and `dims[2]` must be in the same length and have at least one element.", + ), + ) + + return GeneralDimensions(NTuple{L1,Space}(Space.(dims[1])), NTuple{L1,Space}(Space.(dims[2]))) +end + +_gen_dimensions(dims::AbstractDimensions) = dims +_gen_dimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Integer,N} = Dimensions(dims) +_gen_dimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Union{AbstractVector,NTuple},N} = + GeneralDimensions(dims) +_gen_dimensions(dims::Any) = Dimensions(dims) + +# obtain dims in the type of SVector with integers +dimensions_to_dims(dimensions::NTuple{N,AbstractSpace}) where {N} = vcat(map(dimensions_to_dims, dimensions)...) +dimensions_to_dims(dimensions::Dimensions) = dimensions_to_dims(dimensions.to) +dimensions_to_dims(dimensions::GeneralDimensions) = + SVector{2}(dimensions_to_dims(dimensions.to), dimensions_to_dims(dimensions.from)) + +dimensions_to_dims(::Nothing) = nothing # for EigsolveResult.dimensions = nothing + +Base.length(::AbstractDimensions{N}) where {N} = N + +# need to specify return type `Int` for `_get_space_size` +# otherwise the type of `prod(::Dimensions)` will be unstable +_get_space_size(s::AbstractSpace)::Int = s.size +Base.prod(dims::Dimensions) = prod(dims.to) +Base.prod(spaces::NTuple{N,AbstractSpace}) where {N} = prod(_get_space_size, spaces) + +LinearAlgebra.transpose(dimensions::Dimensions) = dimensions +LinearAlgebra.transpose(dimensions::GeneralDimensions) = GeneralDimensions(dimensions.from, dimensions.to) # switch `to` and `from` +LinearAlgebra.adjoint(dimensions::AbstractDimensions) = transpose(dimensions) + +# this is used to show `dims` for Qobj and QobjEvo +_get_dims_string(dimensions::Dimensions) = string(dimensions_to_dims(dimensions)) +function _get_dims_string(dimensions::GeneralDimensions) + dims = dimensions_to_dims(dimensions) + return "[$(string(dims[1])), $(string(dims[2]))]" +end +_get_dims_string(::Nothing) = "nothing" # for EigsolveResult.dimensions = nothing diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 3728ffc5d..cbccbed2d 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -7,27 +7,22 @@ export eigenenergies, eigenstates, eigsolve export eigsolve_al @doc raw""" - struct EigsolveResult{T1<:Vector{<:Number}, T2<:AbstractMatrix{<:Number}, ObjType<:Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject},N} - values::T1 - vectors::T2 - type::ObjType - dims::SVector{N,Int} - iter::Int - numops::Int - converged::Bool - end + struct EigsolveResult A struct containing the eigenvalues, the eigenvectors, and some information from the solver -# Fields +# Fields (Attributes) - `values::AbstractVector`: the eigenvalues - `vectors::AbstractMatrix`: the transformation matrix (eigenvectors) - `type::Union{Nothing,QuantumObjectType}`: the type of [`QuantumObject`](@ref), or `nothing` means solving eigen equation for general matrix -- `dims::SVector`: the `dims` of [`QuantumObject`](@ref) +- `dimensions::Union{Nothing,AbstractDimensions}`: the `dimensions` of [`QuantumObject`](@ref), or `nothing` means solving eigen equation for general matrix - `iter::Int`: the number of iteration during the solving process - `numops::Int` : number of times the linear map was applied in krylov methods - `converged::Bool`: Whether the result is converged +!!! note "`dims` property" + For a given `res::EigsolveResult`, `res.dims` or `getproperty(res, :dims)` returns its `dimensions` in the type of integer-vector. + # Examples One can obtain the eigenvalues and the corresponding [`QuantumObject`](@ref)-type eigenvectors by: ```jldoctest @@ -50,7 +45,7 @@ julia> λ 1.0 + 0.0im julia> ψ -2-element Vector{QuantumObject{Vector{ComplexF64}, KetQuantumObject, 1}}: +2-element Vector{QuantumObject{Vector{ComplexF64}, KetQuantumObject, Dimensions{1, Tuple{Space}}}}: Quantum Object: type=Ket dims=[2] size=(2,) 2-element Vector{ComplexF64}: @@ -72,29 +67,38 @@ struct EigsolveResult{ T1<:Vector{<:Number}, T2<:AbstractMatrix{<:Number}, ObjType<:Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject}, - N, + DimType<:Union{Nothing,AbstractDimensions}, } values::T1 vectors::T2 type::ObjType - dims::SVector{N,Int} + dimensions::DimType iter::Int numops::Int converged::Bool end +function Base.getproperty(res::EigsolveResult, key::Symbol) + # a comment here to avoid bad render by JuliaFormatter + if key === :dims + return dimensions_to_dims(getfield(res, :dimensions)) + else + return getfield(res, key) + end +end + Base.iterate(res::EigsolveResult) = (res.values, Val(:vector_list)) Base.iterate(res::EigsolveResult{T1,T2,Nothing}, ::Val{:vector_list}) where {T1,T2} = ([res.vectors[:, k] for k in 1:length(res.values)], Val(:vectors)) Base.iterate(res::EigsolveResult{T1,T2,OperatorQuantumObject}, ::Val{:vector_list}) where {T1,T2} = - ([QuantumObject(res.vectors[:, k], Ket, res.dims) for k in 1:length(res.values)], Val(:vectors)) + ([QuantumObject(res.vectors[:, k], Ket, res.dimensions) for k in 1:length(res.values)], Val(:vectors)) Base.iterate(res::EigsolveResult{T1,T2,SuperOperatorQuantumObject}, ::Val{:vector_list}) where {T1,T2} = - ([QuantumObject(res.vectors[:, k], OperatorKet, res.dims) for k in 1:length(res.values)], Val(:vectors)) + ([QuantumObject(res.vectors[:, k], OperatorKet, res.dimensions) for k in 1:length(res.values)], Val(:vectors)) Base.iterate(res::EigsolveResult, ::Val{:vectors}) = (res.vectors, Val(:done)) Base.iterate(res::EigsolveResult, ::Val{:done}) = nothing function Base.show(io::IO, res::EigsolveResult) - println(io, "EigsolveResult: type=", res.type, " dims=", res.dims) + println(io, "EigsolveResult: type=", res.type, " dims=", _get_dims_string(res.dimensions)) println(io, "values:") show(io, MIME("text/plain"), res.values) print(io, "\n") @@ -161,7 +165,7 @@ function _eigsolve( A, b::AbstractVector{T}, type::ObjType, - dims::SVector, + dimensions::Union{Nothing,AbstractDimensions}, k::Int = 1, m::Int = max(20, 2 * k + 1); tol::Real = 1e-8, @@ -246,7 +250,7 @@ function _eigsolve( mul!(cache1, Vₘ, M(Uₘ * VR)) vecs = cache1[:, 1:k] - return EigsolveResult(vals, vecs, type, dims, iter, numops, (iter < maxiter)) + return EigsolveResult(vals, vecs, type, dimensions, iter, numops, (iter < maxiter)) end @doc raw""" @@ -283,7 +287,7 @@ function eigsolve( A.data; v0 = v0, type = A.type, - dims = A.dims, + dimensions = A.dimensions, sigma = sigma, k = k, krylovdim = krylovdim, @@ -298,7 +302,7 @@ function eigsolve( A; v0::Union{Nothing,AbstractVector} = nothing, type::Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject} = nothing, - dims = SVector{0,Int}(), + dimensions = nothing, sigma::Union{Nothing,Real} = nothing, k::Int = 1, krylovdim::Int = max(20, 2 * k + 1), @@ -311,10 +315,8 @@ function eigsolve( isH = ishermitian(A) v0 === nothing && (v0 = normalize!(rand(T, size(A, 1)))) - dims = SVector(dims) - if sigma === nothing - res = _eigsolve(A, v0, type, dims, k, krylovdim, tol = tol, maxiter = maxiter) + res = _eigsolve(A, v0, type, dimensions, k, krylovdim, tol = tol, maxiter = maxiter) vals = res.values else Aₛ = A - sigma * I @@ -332,11 +334,11 @@ function eigsolve( Amap = EigsolveInverseMap(T, size(A), linsolve) - res = _eigsolve(Amap, v0, type, dims, k, krylovdim, tol = tol, maxiter = maxiter) + res = _eigsolve(Amap, v0, type, dimensions, k, krylovdim, tol = tol, maxiter = maxiter) vals = @. (1 + sigma * res.values) / res.values end - return EigsolveResult(vals, res.vectors, res.type, res.dims, res.iter, res.numops, res.converged) + return EigsolveResult(vals, res.vectors, res.type, res.dimensions, res.iter, res.numops, res.converged) end @doc raw""" @@ -346,7 +348,7 @@ end c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), params::NamedTuple = NamedTuple(), - ρ0::AbstractMatrix = rand_dm(prod(H.dims)).data, + ρ0::AbstractMatrix = rand_dm(prod(H.dimensions)).data, k::Int = 1, krylovdim::Int = min(10, size(H, 1)), maxiter::Int = 200, @@ -385,7 +387,7 @@ function eigsolve_al( c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), params::NamedTuple = NamedTuple(), - ρ0::AbstractMatrix = rand_dm(prod(H.dims)).data, + ρ0::AbstractMatrix = rand_dm(prod(H.dimensions)).data, k::Int = 1, krylovdim::Int = min(10, size(H, 1)), maxiter::Int = 200, @@ -396,7 +398,7 @@ function eigsolve_al( prob = mesolveProblem( L_evo, - QuantumObject(ρ0, type = Operator, dims = H.dims), + QuantumObject(ρ0, type = Operator, dims = H.dimensions), [zero(T), T]; params = params, progress_bar = Val(false), @@ -408,7 +410,7 @@ function eigsolve_al( Lmap = ArnoldiLindbladIntegratorMap(eltype(DT1), size(L_evo), integrator) - res = _eigsolve(Lmap, mat2vec(ρ0), L_evo.type, L_evo.dims, k, krylovdim, maxiter = maxiter, tol = eigstol) + res = _eigsolve(Lmap, mat2vec(ρ0), L_evo.type, L_evo.dimensions, k, krylovdim, maxiter = maxiter, tol = eigstol) # finish!(prog) vals = similar(res.values) @@ -420,7 +422,7 @@ function eigsolve_al( @. vecs[:, i] = vec * exp(-1im * angle(vec[1])) end - return EigsolveResult(vals, vecs, res.type, res.dims, res.iter, res.numops, res.converged) + return EigsolveResult(vals, vecs, res.type, res.dimensions, res.iter, res.numops, res.converged) end @doc raw""" @@ -464,7 +466,7 @@ function LinearAlgebra.eigen( E::mat2vec(sparse_to_dense(MT)) = F.values U::sparse_to_dense(MT) = F.vectors - return EigsolveResult(E, U, A.type, A.dims, 0, 0, true) + return EigsolveResult(E, U, A.type, A.dimensions, 0, 0, true) end @doc raw""" diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 158514a4f..2b738a800 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -105,7 +105,7 @@ variance(O::QuantumObject{DT1,OperatorQuantumObject}, ψ::Vector{<:QuantumObject Converts a sparse QuantumObject to a dense QuantumObject. """ -sparse_to_dense(A::QuantumObject{<:AbstractVecOrMat}) = QuantumObject(sparse_to_dense(A.data), A.type, A.dims) +sparse_to_dense(A::QuantumObject{<:AbstractVecOrMat}) = QuantumObject(sparse_to_dense(A.data), A.type, A.dimensions) sparse_to_dense(A::MT) where {MT<:AbstractSparseArray} = Array(A) for op in (:Transpose, :Adjoint) @eval sparse_to_dense(A::$op{T,<:AbstractSparseMatrix}) where {T<:BlasFloat} = Array(A) @@ -132,7 +132,7 @@ sparse_to_dense(::Type{M}) where {M<:AbstractMatrix} = M Converts a dense QuantumObject to a sparse QuantumObject. """ dense_to_sparse(A::QuantumObject{<:AbstractVecOrMat}, tol::Real = 1e-10) = - QuantumObject(dense_to_sparse(A.data, tol), A.type, A.dims) + QuantumObject(dense_to_sparse(A.data, tol), A.type, A.dimensions) function dense_to_sparse(A::MT, tol::Real = 1e-10) where {MT<:AbstractMatrix} idxs = findall(@. abs(A) > tol) row_indices = getindex.(idxs, 1) @@ -180,12 +180,61 @@ julia> a.dims, O.dims ``` """ function LinearAlgebra.kron( - A::AbstractQuantumObject{DT1,OpType}, - B::AbstractQuantumObject{DT2,OpType}, + A::AbstractQuantumObject{DT1,OpType,<:Dimensions}, + B::AbstractQuantumObject{DT2,OpType,<:Dimensions}, ) where {DT1,DT2,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} QType = promote_op_type(A, B) - return QType(kron(A.data, B.data), A.type, vcat(A.dims, B.dims)) + return QType(kron(A.data, B.data), A.type, Dimensions((A.dimensions.to..., B.dimensions.to...))) end + +# if A and B are both Operator but either one of them has GeneralDimensions +for ADimType in (:Dimensions, :GeneralDimensions) + for BDimType in (:Dimensions, :GeneralDimensions) + if !(ADimType == BDimType == :Dimensions) # not for this case because it's already implemented + @eval begin + function LinearAlgebra.kron( + A::AbstractQuantumObject{DT1,OperatorQuantumObject,<:$ADimType}, + B::AbstractQuantumObject{DT2,OperatorQuantumObject,<:$BDimType}, + ) where {DT1,DT2} + QType = promote_op_type(A, B) + return QType( + kron(A.data, B.data), + Operator, + GeneralDimensions( + (get_dimensions_to(A)..., get_dimensions_to(B)...), + (get_dimensions_from(A)..., get_dimensions_from(B)...), + ), + ) + end + end + end + end +end + +# if A and B are different type (must return Operator with GeneralDimensions) +for AOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) + for BOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) + if (AOpType != BOpType) + @eval begin + function LinearAlgebra.kron( + A::AbstractQuantumObject{DT1,$AOpType}, + B::AbstractQuantumObject{DT2,$BOpType}, + ) where {DT1,DT2} + QType = promote_op_type(A, B) + return QType( + kron(A.data, B.data), + Operator, + GeneralDimensions( + (get_dimensions_to(A)..., get_dimensions_to(B)...), + (get_dimensions_from(A)..., get_dimensions_from(B)...), + ), + ) + end + end + end + end +end + LinearAlgebra.kron(A::AbstractQuantumObject) = A function LinearAlgebra.kron(A::Vector{<:AbstractQuantumObject}) @warn "`tensor(A)` or `kron(A)` with `A` is a `Vector` can hurt performance. Try to use `tensor(A...)` or `kron(A...)` instead." @@ -208,7 +257,7 @@ end Convert a quantum object from vector ([`OperatorKetQuantumObject`](@ref)-type) to matrix ([`OperatorQuantumObject`](@ref)-type) """ vec2mat(A::QuantumObject{<:AbstractArray{T},OperatorKetQuantumObject}) where {T} = - QuantumObject(vec2mat(A.data), Operator, A.dims) + QuantumObject(vec2mat(A.data), Operator, A.dimensions) @doc raw""" mat2vec(A::QuantumObject) @@ -216,7 +265,7 @@ vec2mat(A::QuantumObject{<:AbstractArray{T},OperatorKetQuantumObject}) where {T} Convert a quantum object from matrix ([`OperatorQuantumObject`](@ref)-type) to vector ([`OperatorKetQuantumObject`](@ref)-type) """ mat2vec(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = - QuantumObject(mat2vec(A.data), OperatorKet, A.dims) + QuantumObject(mat2vec(A.data), OperatorKet, A.dimensions) @doc raw""" mat2vec(A::AbstractMatrix) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index d2a0d8fe5..9b0e1de5f 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -19,7 +19,7 @@ Returns a random unitary [`QuantumObject`](@ref). The `dimensions` can be either the following types: - `dimensions::Int`: Number of basis states in the Hilbert space. -- `dimensions::Union{AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. +- `dimensions::Union{Dimensions,AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. The `distribution` specifies which of the method used to obtain the unitary matrix: - `:haar`: Haar random unitary matrix using the algorithm from reference 1 @@ -33,9 +33,9 @@ The `distribution` specifies which of the method used to obtain the unitary matr """ rand_unitary(dimensions::Int, distribution::Union{Symbol,Val} = Val(:haar)) = rand_unitary(SVector(dimensions), makeVal(distribution)) -rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, distribution::Union{Symbol,Val} = Val(:haar)) = +rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, distribution::Union{Symbol,Val} = Val(:haar)) = rand_unitary(dimensions, makeVal(distribution)) -function rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{:haar}) +function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, ::Val{:haar}) N = prod(dimensions) # generate N x N matrix Z of complex standard normal random variates @@ -50,7 +50,7 @@ function rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{:haar} Λ ./= abs.(Λ) # rescaling the elements return QuantumObject(sparse_to_dense(Q * Diagonal(Λ)); type = Operator, dims = dimensions) end -function rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{:exp}) +function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, ::Val{:exp}) N = prod(dimensions) # generate N x N matrix Z of complex standard normal random variates @@ -61,7 +61,7 @@ function rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{:exp}) return sparse_to_dense(exp(-1.0im * H)) end -rand_unitary(dimensions::Union{AbstractVector{Int},Tuple}, ::Val{T}) where {T} = +rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, ::Val{T}) where {T} = throw(ArgumentError("Invalid distribution: $(T)")) @doc raw""" @@ -432,12 +432,16 @@ Note that `type` can only be either [`Operator`](@ref) or [`SuperOperator`](@ref !!! note `qeye` is a synonym of `eye`. """ -eye( +function eye( N::Int; type::ObjType = Operator, dims = nothing, -) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(Diagonal(ones(ComplexF64, N)); type = type, dims = dims) +) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + if dims isa Nothing + dims = isa(type, OperatorQuantumObject) ? N : isqrt(N) + end + return QuantumObject(Diagonal(ones(ComplexF64, N)); type = type, dims = dims) +end @doc raw""" fdestroy(N::Union{Int,Val}, j::Int) @@ -498,7 +502,8 @@ end Generates the projection operator ``\hat{O} = |i \rangle\langle j|`` with Hilbert space dimension `N`. """ -projection(N::Int, i::Int, j::Int) = QuantumObject(sparse([i + 1], [j + 1], [1.0 + 0.0im], N, N), type = Operator) +projection(N::Int, i::Int, j::Int) = + QuantumObject(sparse([i + 1], [j + 1], [1.0 + 0.0im], N, N), type = Operator, dims = N) @doc raw""" tunneling(N::Int, m::Int=1; sparse::Union{Bool,Val{<:Bool}}=Val(false)) @@ -534,7 +539,7 @@ Generates a discrete Fourier transform matrix ``\hat{F}_N`` for [Quantum Fourier The `dimensions` can be either the following types: - `dimensions::Int`: Number of basis states in the Hilbert space. -- `dimensions::Union{AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. +- `dimensions::Union{Dimensions,AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. ``N`` represents the total dimension, and therefore the matrix is defined as @@ -555,7 +560,7 @@ where ``\omega = \exp(\frac{2 \pi i}{N})``. It is highly recommended to use `qft(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ qft(dimensions::Int) = QuantumObject(_qft_op(dimensions), Operator, dimensions) -qft(dimensions::Union{AbstractVector{T},Tuple}) where {T} = +qft(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) = QuantumObject(_qft_op(prod(dimensions)), Operator, dimensions) function _qft_op(N::Int) ω = exp(2.0im * π / N) diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 0b0269c9f..a2ba3ffdf 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -9,13 +9,16 @@ It also implements the fundamental functions in Julia standard library: export QuantumObject @doc raw""" - struct QuantumObject{MT<:AbstractArray,ObjType<:QuantumObjectType,N} - data::MT + struct QuantumObject{DataType<:AbstractArray,ObjType<:QuantumObjectType,DimType<:AbstractDimensions} <: AbstractQuantumObject{DataType,ObjType,DimType} + data::DataType type::ObjType - dims::SVector{N, Int} + dimensions::DimType end -Julia struct representing any quantum objects. +Julia structure representing any time-independent quantum objects. For time-dependent cases, see [`QuantumObjectEvolution`](@ref). + +!!! note "`dims` property" + For a given `H::QuantumObject`, `H.dims` or `getproperty(H, :dims)` returns its `dimensions` in the type of integer-vector. # Examples @@ -32,28 +35,31 @@ Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false julia> a isa QuantumObject true + +julia> a.dims +1-element SVector{1, Int64} with indices SOneTo(1): + 20 + +julia> a.dimensions +Dimensions{1, Tuple{Space}}((Space(20),)) ``` """ -struct QuantumObject{MT<:AbstractArray,ObjType<:QuantumObjectType,N} <: AbstractQuantumObject{MT,ObjType,N} - data::MT +struct QuantumObject{DataType<:AbstractArray,ObjType<:QuantumObjectType,DimType<:AbstractDimensions} <: + AbstractQuantumObject{DataType,ObjType,DimType} + data::DataType type::ObjType - dims::SVector{N,Int} + dimensions::DimType function QuantumObject(data::MT, type::ObjType, dims) where {MT<:AbstractArray,ObjType<:QuantumObjectType} - _check_dims(dims) + dimensions = _gen_dimensions(dims) _size = _get_size(data) - _check_QuantumObject(type, dims, _size[1], _size[2]) - - N = length(dims) + _check_QuantumObject(type, dimensions, _size[1], _size[2]) - return new{MT,ObjType,N}(data, type, SVector{N,Int}(dims)) + return new{MT,ObjType,typeof(dimensions)}(data, type, dimensions) end end -QuantumObject(A::AbstractArray, type::ObjType, dims::Integer) where {ObjType<:QuantumObjectType} = - QuantumObject(A, type, SVector{1,Int}(dims)) - @doc raw""" Qobj(A::AbstractArray; type = nothing, dims = nothing) QuantumObject(A::AbstractArray; type = nothing, dims = nothing) @@ -81,10 +87,14 @@ function QuantumObject( end if dims isa Nothing - if type isa OperatorQuantumObject || type isa BraQuantumObject - dims = SVector{1,Int}(_size[2]) + if type isa BraQuantumObject + dims = Dimensions(_size[2]) + elseif type isa OperatorQuantumObject + dims = + (_size[1] == _size[2]) ? Dimensions(_size[1]) : + GeneralDimensions(SVector{2}(SVector{1}(_size[1]), SVector{1}(_size[2]))) elseif type isa SuperOperatorQuantumObject || type isa OperatorBraQuantumObject - dims = SVector{1,Int}(isqrt(_size[2])) + dims = Dimensions(isqrt(_size[2])) end end @@ -105,9 +115,9 @@ function QuantumObject( if dims isa Nothing _size = _get_size(A) if type isa KetQuantumObject - dims = SVector{1,Int}(_size[1]) + dims = Dimensions(_size[1]) elseif type isa OperatorKetQuantumObject - dims = SVector{1,Int}(isqrt(_size[1])) + dims = Dimensions(isqrt(_size[1])) end end @@ -125,11 +135,12 @@ end function QuantumObject( A::QuantumObject{<:AbstractArray{T,N}}; type::ObjType = A.type, - dims = A.dims, + dims = A.dimensions, ) where {T,N,ObjType<:QuantumObjectType} _size = N == 1 ? (length(A), 1) : size(A) - _check_QuantumObject(type, dims, _size[1], _size[2]) - return QuantumObject(copy(A.data), type, dims) + dimensions = _gen_dimensions(dims) + _check_QuantumObject(type, dimensions, _size[1], _size[2]) + return QuantumObject(copy(A.data), type, dimensions) end function Base.show( @@ -146,7 +157,15 @@ function Base.show( }, } op_data = QO.data - println(io, "\nQuantum Object: type=", QO.type, " dims=", QO.dims, " size=", size(op_data)) + println( + io, + "\nQuantum Object: type=", + QO.type, + " dims=", + _get_dims_string(QO.dimensions), + " size=", + size(op_data), + ) return show(io, MIME("text/plain"), op_data) end @@ -157,7 +176,7 @@ function Base.show(io::IO, QO::QuantumObject) "\nQuantum Object: type=", QO.type, " dims=", - QO.dims, + _get_dims_string(QO.dimensions), " size=", size(op_data), " ishermitian=", @@ -166,15 +185,16 @@ function Base.show(io::IO, QO::QuantumObject) return show(io, MIME("text/plain"), op_data) end -Base.real(x::QuantumObject) = QuantumObject(real(x.data), x.type, x.dims) -Base.imag(x::QuantumObject) = QuantumObject(imag(x.data), x.type, x.dims) +Base.real(x::QuantumObject) = QuantumObject(real(x.data), x.type, x.dimensions) +Base.imag(x::QuantumObject) = QuantumObject(imag(x.data), x.type, x.dimensions) -SparseArrays.sparse(A::QuantumObject{<:AbstractArray{T}}) where {T} = QuantumObject(sparse(A.data), A.type, A.dims) +SparseArrays.sparse(A::QuantumObject{<:AbstractArray{T}}) where {T} = + QuantumObject(sparse(A.data), A.type, A.dimensions) SparseArrays.nnz(A::QuantumObject{<:AbstractSparseArray}) = nnz(A.data) SparseArrays.nonzeros(A::QuantumObject{<:AbstractSparseArray}) = nonzeros(A.data) SparseArrays.rowvals(A::QuantumObject{<:AbstractSparseArray}) = rowvals(A.data) SparseArrays.droptol!(A::QuantumObject{<:AbstractSparseArray}, tol::Real) = (droptol!(A.data, tol); return A) -SparseArrays.dropzeros(A::QuantumObject{<:AbstractSparseArray}) = QuantumObject(dropzeros(A.data), A.type, A.dims) +SparseArrays.dropzeros(A::QuantumObject{<:AbstractSparseArray}) = QuantumObject(dropzeros(A.data), A.type, A.dimensions) SparseArrays.dropzeros!(A::QuantumObject{<:AbstractSparseArray}) = (dropzeros!(A.data); return A) @doc raw""" @@ -191,7 +211,7 @@ SciMLOperators.cache_operator( L::AbstractQuantumObject{DT,OpType}, u::AbstractVector, ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - get_typename_wrapper(L)(cache_operator(L.data, sparse_to_dense(similar(u))), L.type, L.dims) + get_typename_wrapper(L)(cache_operator(L.data, sparse_to_dense(similar(u))), L.type, L.dimensions) function SciMLOperators.cache_operator( L::AbstractQuantumObject{DT1,OpType}, @@ -202,7 +222,7 @@ function SciMLOperators.cache_operator( OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, SType<:Union{KetQuantumObject,OperatorKetQuantumObject}, } - check_dims(L, u) + check_dimensions(L, u) if isoper(L) && isoperket(u) throw(ArgumentError("The input state `u` must be a Ket if `L` is an Operator.")) @@ -213,14 +233,17 @@ function SciMLOperators.cache_operator( end # data type conversions -Base.Vector(A::QuantumObject{<:AbstractVector}) = QuantumObject(Vector(A.data), A.type, A.dims) -Base.Vector{T}(A::QuantumObject{<:AbstractVector}) where {T<:Number} = QuantumObject(Vector{T}(A.data), A.type, A.dims) -Base.Matrix(A::QuantumObject{<:AbstractMatrix}) = QuantumObject(Matrix(A.data), A.type, A.dims) -Base.Matrix{T}(A::QuantumObject{<:AbstractMatrix}) where {T<:Number} = QuantumObject(Matrix{T}(A.data), A.type, A.dims) -SparseArrays.SparseVector(A::QuantumObject{<:AbstractVector}) = QuantumObject(SparseVector(A.data), A.type, A.dims) +Base.Vector(A::QuantumObject{<:AbstractVector}) = QuantumObject(Vector(A.data), A.type, A.dimensions) +Base.Vector{T}(A::QuantumObject{<:AbstractVector}) where {T<:Number} = + QuantumObject(Vector{T}(A.data), A.type, A.dimensions) +Base.Matrix(A::QuantumObject{<:AbstractMatrix}) = QuantumObject(Matrix(A.data), A.type, A.dimensions) +Base.Matrix{T}(A::QuantumObject{<:AbstractMatrix}) where {T<:Number} = + QuantumObject(Matrix{T}(A.data), A.type, A.dimensions) +SparseArrays.SparseVector(A::QuantumObject{<:AbstractVector}) = + QuantumObject(SparseVector(A.data), A.type, A.dimensions) SparseArrays.SparseVector{T}(A::QuantumObject{<:SparseVector}) where {T<:Number} = - QuantumObject(SparseVector{T}(A.data), A.type, A.dims) + QuantumObject(SparseVector{T}(A.data), A.type, A.dimensions) SparseArrays.SparseMatrixCSC(A::QuantumObject{<:AbstractMatrix}) = - QuantumObject(SparseMatrixCSC(A.data), A.type, A.dims) + QuantumObject(SparseMatrixCSC(A.data), A.type, A.dimensions) SparseArrays.SparseMatrixCSC{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T<:Number} = - QuantumObject(SparseMatrixCSC{T}(A.data), A.type, A.dims) + QuantumObject(SparseMatrixCSC{T}(A.data), A.type, A.dimensions) diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index df259307c..cec0992b3 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -14,7 +14,7 @@ export QuantumObjectType, export Bra, Ket, Operator, OperatorBra, OperatorKet, SuperOperator @doc raw""" - abstract type AbstractQuantumObject{DataType,ObjType,N} + abstract type AbstractQuantumObject{DataType,ObjType,DimType} Abstract type for all quantum objects like [`QuantumObject`](@ref) and [`QuantumObjectEvolution`](@ref). @@ -24,7 +24,7 @@ julia> sigmax() isa AbstractQuantumObject true ``` """ -abstract type AbstractQuantumObject{DataType,ObjType,N} end +abstract type AbstractQuantumObject{DataType,ObjType,DimType} end abstract type QuantumObjectType end @@ -152,68 +152,138 @@ Returns the length of the matrix or vector corresponding to the [`AbstractQuantu Base.length(A::AbstractQuantumObject) = length(A.data) Base.isequal(A::AbstractQuantumObject, B::AbstractQuantumObject) = - isequal(A.type, B.type) && isequal(A.dims, B.dims) && isequal(A.data, B.data) + isequal(A.type, B.type) && isequal(A.dimensions, B.dimensions) && isequal(A.data, B.data) Base.isapprox(A::AbstractQuantumObject, B::AbstractQuantumObject; kwargs...) = - isequal(A.type, B.type) && isequal(A.dims, B.dims) && isapprox(A.data, B.data; kwargs...) + isequal(A.type, B.type) && isequal(A.dimensions, B.dimensions) && isapprox(A.data, B.data; kwargs...) Base.:(==)(A::AbstractQuantumObject, B::AbstractQuantumObject) = - (A.type == B.type) && (A.dims == B.dims) && (A.data == B.data) + (A.type == B.type) && (A.dimensions == B.dimensions) && (A.data == B.data) -function check_dims(A::AbstractQuantumObject, B::AbstractQuantumObject) - A.dims != B.dims && throw(DimensionMismatch("The two quantum objects don't have the same Hilbert dimension.")) +function check_dimensions(dimensions_list::NTuple{N,AbstractDimensions}) where {N} + allequal(dimensions_list) || + throw(DimensionMismatch("The quantum objects should have the same Hilbert `dimensions`.")) return nothing end - -function _check_dims(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Integer,N} - _non_static_array_warning("dims", dims) - return (all(>(0), dims) && length(dims) > 0) || - throw(DomainError(dims, "The argument dims must be of non-zero length and contain only positive integers.")) -end -_check_dims(dims::Any) = throw( - ArgumentError( - "The argument dims must be a Tuple or a StaticVector of non-zero length and contain only positive integers.", +check_dimensions(Qobj_tuple::NTuple{N,AbstractQuantumObject}) where {N} = + check_dimensions(getfield.(Qobj_tuple, :dimensions)) +check_dimensions(A::AbstractQuantumObject...) = check_dimensions(A) + +_check_QuantumObject( + type::ObjType, + dimensions::GeneralDimensions, + m::Int, + n::Int, +) where { + ObjType<:Union{ + KetQuantumObject, + BraQuantumObject, + SuperOperatorQuantumObject, + OperatorBraQuantumObject, + OperatorKetQuantumObject, + }, +} = throw( + DomainError( + _get_dims_string(dimensions), + "The given `dims` is not compatible with type = $type, should be a single list of integers.", ), ) -function _check_QuantumObject(type::KetQuantumObject, dims, m::Int, n::Int) +function _check_QuantumObject(type::KetQuantumObject, dimensions::Dimensions, m::Int, n::Int) (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Ket")) - (prod(dims) != m) && throw(DimensionMismatch("Ket with dims = $(dims) does not fit the array size = $((m, n)).")) + (prod(dimensions) != m) && throw( + DimensionMismatch("Ket with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n))."), + ) return nothing end -function _check_QuantumObject(type::BraQuantumObject, dims, m::Int, n::Int) +function _check_QuantumObject(type::BraQuantumObject, dimensions::Dimensions, m::Int, n::Int) (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Bra")) - (prod(dims) != n) && throw(DimensionMismatch("Bra with dims = $(dims) does not fit the array size = $((m, n)).")) + (prod(dimensions) != n) && throw( + DimensionMismatch("Bra with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n))."), + ) + return nothing +end + +function _check_QuantumObject(type::OperatorQuantumObject, dimensions::Dimensions, m::Int, n::Int) + L = prod(dimensions) + (L == m == n) || throw( + DimensionMismatch( + "Operator with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n)).", + ), + ) return nothing end -function _check_QuantumObject(type::OperatorQuantumObject, dims, m::Int, n::Int) - (m != n) && throw(DomainError((m, n), "The size of the array is not compatible with Operator")) - (prod(dims) != m) && - throw(DimensionMismatch("Operator with dims = $(dims) does not fit the array size = $((m, n)).")) +function _check_QuantumObject(type::OperatorQuantumObject, dimensions::GeneralDimensions, m::Int, n::Int) + ((m == 1) || (n == 1)) && throw(DomainError((m, n), "The size of the array is not compatible with Operator")) + ((prod(dimensions.to) != m) || (prod(dimensions.from) != n)) && throw( + DimensionMismatch( + "Operator with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n)).", + ), + ) return nothing end -function _check_QuantumObject(type::SuperOperatorQuantumObject, dims, m::Int, n::Int) +function _check_QuantumObject(type::SuperOperatorQuantumObject, dimensions::Dimensions, m::Int, n::Int) (m != n) && throw(DomainError((m, n), "The size of the array is not compatible with SuperOperator")) - (prod(dims) != sqrt(m)) && - throw(DimensionMismatch("SuperOperator with dims = $(dims) does not fit the array size = $((m, n)).")) + (prod(dimensions) != sqrt(m)) && throw( + DimensionMismatch( + "SuperOperator with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n)).", + ), + ) return nothing end -function _check_QuantumObject(type::OperatorKetQuantumObject, dims, m::Int, n::Int) +function _check_QuantumObject(type::OperatorKetQuantumObject, dimensions::Dimensions, m::Int, n::Int) (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorKet")) - (prod(dims) != sqrt(m)) && - throw(DimensionMismatch("OperatorKet with dims = $(dims) does not fit the array size = $((m, n)).")) + (prod(dimensions) != sqrt(m)) && throw( + DimensionMismatch( + "OperatorKet with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n)).", + ), + ) return nothing end -function _check_QuantumObject(type::OperatorBraQuantumObject, dims, m::Int, n::Int) +function _check_QuantumObject(type::OperatorBraQuantumObject, dimensions::Dimensions, m::Int, n::Int) (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorBra")) - (prod(dims) != sqrt(n)) && - throw(DimensionMismatch("OperatorBra with dims = $(dims) does not fit the array size = $((m, n)).")) + (prod(dimensions) != sqrt(n)) && throw( + DimensionMismatch( + "OperatorBra with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n)).", + ), + ) return nothing end +function Base.getproperty(A::AbstractQuantumObject, key::Symbol) + # a comment here to avoid bad render by JuliaFormatter + if key === :dims + return dimensions_to_dims(getfield(A, :dimensions)) + else + return getfield(A, key) + end +end + +# this returns `to` in GeneralDimensions representation +get_dimensions_to(A::AbstractQuantumObject{DT,KetQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{DT,BraQuantumObject,<:Dimensions{N}}) where {DT,N} = space_one_list(N) +get_dimensions_to(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:GeneralDimensions{N}}) where {DT,N} = + A.dimensions.to +get_dimensions_to( + A::AbstractQuantumObject{DT,ObjType,<:Dimensions{N}}, +) where {DT,ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = + A.dimensions.to + +# this returns `from` in GeneralDimensions representation +get_dimensions_from(A::AbstractQuantumObject{DT,KetQuantumObject,<:Dimensions{N}}) where {DT,N} = space_one_list(N) +get_dimensions_from(A::AbstractQuantumObject{DT,BraQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:GeneralDimensions{N}}) where {DT,N} = + A.dimensions.from +get_dimensions_from( + A::AbstractQuantumObject{DT,ObjType,<:Dimensions{N}}, +) where {DT,ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = + A.dimensions.to + # functions for getting Float or Complex element type _FType(A::AbstractQuantumObject) = _FType(eltype(A)) _CType(A::AbstractQuantumObject) = _CType(eltype(A)) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index ef7f0be60..f055c61ae 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -5,10 +5,10 @@ This file defines the QuantumObjectEvolution (QobjEvo) structure. export QuantumObjectEvolution @doc raw""" - struct QuantumObjectEvolution{DT<:AbstractSciMLOperator,ObjType<:QuantumObjectType,N} <: AbstractQuantumObject - data::DT + struct QuantumObjectEvolution{DataType<:AbstractSciMLOperator,ObjType<:QuantumObjectType,DimType<:AbstractDimensions} <: AbstractQuantumObject{DataType,ObjType,DimType} + data::DataType type::ObjType - dims::SVector{N,Int} + dimensions::DimType end Julia struct representing any time-dependent quantum object. The `data` field is a `AbstractSciMLOperator` object that represents the time-dependent quantum object. It can be seen as @@ -17,7 +17,12 @@ Julia struct representing any time-dependent quantum object. The `data` field is \hat{O}(t) = \sum_{i} c_i(p, t) \hat{O}_i ``` -where ``c_i(p, t)`` is a function that depends on the parameters `p` and time `t`, and ``\hat{O}_i`` are the operators that form the quantum object. The `type` field is the type of the quantum object, and the `dims` field is the dimensions of the quantum object. For more information about `type` and `dims`, see [`QuantumObject`](@ref). For more information about `AbstractSciMLOperator`, see the [SciML](https://docs.sciml.ai/SciMLOperators/stable/) documentation. +where ``c_i(p, t)`` is a function that depends on the parameters `p` and time `t`, and ``\hat{O}_i`` are the operators that form the quantum object. + +For time-independent cases, see [`QuantumObject`](@ref), and for more information about `AbstractSciMLOperator`, see the [SciML](https://docs.sciml.ai/SciMLOperators/stable/) documentation. + +!!! note "`dims` property" + For a given `H::QuantumObjectEvolution`, `H.dims` or `getproperty(H, :dims)` returns its `dimensions` in the type of integer-vector. # Examples This operator can be initialized in the same way as the QuTiP `QobjEvo` object. For example @@ -104,13 +109,13 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ``` """ struct QuantumObjectEvolution{ - DT<:AbstractSciMLOperator, + DataType<:AbstractSciMLOperator, ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - N, -} <: AbstractQuantumObject{DT,ObjType,N} - data::DT + DimType<:AbstractDimensions, +} <: AbstractQuantumObject{DataType,ObjType,DimType} + data::DataType type::ObjType - dims::SVector{N,Int} + dimensions::DimType function QuantumObjectEvolution( data::DT, @@ -120,14 +125,12 @@ struct QuantumObjectEvolution{ (type == Operator || type == SuperOperator) || throw(ArgumentError("The type $type is not supported for QuantumObjectEvolution.")) - _check_dims(dims) + dimensions = _gen_dimensions(dims) _size = _get_size(data) - _check_QuantumObject(type, dims, _size[1], _size[2]) - - N = length(dims) + _check_QuantumObject(type, dimensions, _size[1], _size[2]) - return new{DT,ObjType,N}(data, type, SVector{N,Int}(dims)) + return new{DT,ObjType,typeof(dimensions)}(data, type, dimensions) end end @@ -138,7 +141,7 @@ function Base.show(io::IO, QO::QuantumObjectEvolution) "\nQuantum Object Evo.: type=", QO.type, " dims=", - QO.dims, + _get_dims_string(QO.dimensions), " size=", size(op_data), " ishermitian=", @@ -149,10 +152,6 @@ function Base.show(io::IO, QO::QuantumObjectEvolution) return show(io, MIME("text/plain"), op_data) end -function QuantumObjectEvolution(data::AbstractSciMLOperator, type::QuantumObjectType, dims::Integer) - return QuantumObjectEvolution(data, type, SVector{1,Int}(dims)) -end - @doc raw""" QobjEvo(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) @@ -165,10 +164,12 @@ function QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObject _size = _get_size(data) if dims isa Nothing - if type == Operator - dims = SVector{1,Int}(_size[2]) - elseif type == SuperOperator - dims = SVector{1,Int}(isqrt(_size[2])) + if type isa OperatorQuantumObject + dims = + (_size[1] == _size[2]) ? Dimensions(_size[1]) : + GeneralDimensions(SVector{2}(SVector{1}(_size[1]), SVector{1}(_size[2]))) + elseif type isa SuperOperatorQuantumObject + dims = Dimensions(isqrt(_size[2])) end end @@ -274,7 +275,7 @@ function QuantumObjectEvolution( type::Union{Nothing,QuantumObjectType} = nothing, ) op, data = _QobjEvo_generate_data(op_func_list, α) - dims = op.dims + dims = op.dimensions if type isa Nothing type = op.type end @@ -339,7 +340,7 @@ function QuantumObjectEvolution( if type isa Nothing type = op.type end - return QuantumObjectEvolution(_make_SciMLOperator(op, α), type, op.dims) + return QuantumObjectEvolution(_make_SciMLOperator(op, α), type, op.dimensions) end function QuantumObjectEvolution( @@ -357,9 +358,9 @@ function QuantumObjectEvolution( ) end if α isa Nothing - return QuantumObjectEvolution(op.data, type, op.dims) + return QuantumObjectEvolution(op.data, type, op.dimensions) end - return QuantumObjectEvolution(α * op.data, type, op.dims) + return QuantumObjectEvolution(α * op.data, type, op.dimensions) end #= @@ -397,7 +398,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` op = :(op_func_list[$i][1]) data_type = op_type.parameters[1] - dims_expr = (dims_expr..., :($op.dims)) + dims_expr = (dims_expr..., :($op.dimensions)) func_methods_expr = (func_methods_expr..., :(methods(op_func_list[$i][2], [Any, Real]))) # [Any, Real] means each func must accept 2 arguments if i == 1 first_op = :($op) @@ -409,7 +410,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` throw(ArgumentError("The element must be a Operator or SuperOperator.")) data_type = op_type.parameters[1] - dims_expr = (dims_expr..., :(op_func_list[$i].dims)) + dims_expr = (dims_expr..., :(op_func_list[$i].dimensions)) if i == 1 first_op = :(op_func_list[$i]) end @@ -507,8 +508,7 @@ function (A::QuantumObjectEvolution)( p, t, ) where {DT1,DT2,QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} - check_dims(ψout, ψin) - check_dims(ψout, A) + check_dimensions(A, ψout, ψin) if isoper(A) && isoperket(ψin) throw(ArgumentError("The input state must be a Ket if the QuantumObjectEvolution object is an Operator.")) @@ -535,7 +535,7 @@ function (A::QuantumObjectEvolution)( p, t, ) where {DT,QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} - ψout = QuantumObject(similar(ψ.data), ψ.type, ψ.dims) + ψout = QuantumObject(similar(ψ.data), ψ.type, ψ.dimensions) return A(ψout, ψ, p, t) end @@ -554,7 +554,7 @@ Calculate the time-dependent [`QuantumObjectEvolution`](@ref) object `A` at time function (A::QuantumObjectEvolution)(p, t) # We put 0 in the place of `u` because the time-dependence doesn't depend on the state update_coefficients!(A.data, 0, p, t) - return QuantumObject(concretize(A.data), A.type, A.dims) + return QuantumObject(concretize(A.data), A.type, A.dimensions) end (A::QuantumObjectEvolution)(t) = A(nothing, t) diff --git a/src/qobj/space.jl b/src/qobj/space.jl new file mode 100644 index 000000000..0b90ecf55 --- /dev/null +++ b/src/qobj/space.jl @@ -0,0 +1,41 @@ +#= +This file defines the Hilbert space structure. +=# + +export AbstractSpace, Space + +abstract type AbstractSpace end + +@doc raw""" + struct Space <: AbstractSpace + size::Int + end + +A structure that describes a single Hilbert space with size = `size`. +""" +struct Space <: AbstractSpace + size::Int + + function Space(size::Int) + (size < 1) && throw(DomainError(size, "The size of Space must be positive integer (≥ 1).")) + return new(size) + end +end + +dimensions_to_dims(s::Space) = SVector{1,Int}(s.size) + +# this creates a list of Space(1), it is used to generate `from` for Ket, and `to` for Bra) +space_one_list(N::Int) = ntuple(i -> Space(1), Val(N)) + +# TODO: introduce energy restricted space +#= +struct EnrSpace{N} <: AbstractSpace + size::Int + dims::SVector{N,Int} + n_excitations::Int + state2idx + idx2state +end + +dimensions_to_dims(s::EnrSpace) = s.dims +=# diff --git a/src/qobj/states.jl b/src/qobj/states.jl index 62c4b12c2..886982f7c 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -14,13 +14,13 @@ Returns a zero [`Ket`](@ref) vector with given argument `dimensions`. The `dimensions` can be either the following types: - `dimensions::Int`: Number of basis states in the Hilbert space. -- `dimensions::Union{AbstractVector{Int}, Tuple}`: list of dimensions representing the each number of basis in the subsystems. +- `dimensions::Union{Dimensions,AbstractVector{Int}, Tuple}`: list of dimensions representing the each number of basis in the subsystems. !!! warning "Beware of type-stability!" It is highly recommended to use `zero_ket(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ zero_ket(dimensions::Int) = QuantumObject(zeros(ComplexF64, dimensions), Ket, dimensions) -zero_ket(dimensions::Union{AbstractVector{Int},Tuple}) = +zero_ket(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) = QuantumObject(zeros(ComplexF64, prod(dimensions)), Ket, dimensions) @doc raw""" @@ -71,13 +71,13 @@ Generate a random normalized [`Ket`](@ref) vector with given argument `dimension The `dimensions` can be either the following types: - `dimensions::Int`: Number of basis states in the Hilbert space. -- `dimensions::Union{AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. +- `dimensions::Union{Dimensions,AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. !!! warning "Beware of type-stability!" If you want to keep type stability, it is recommended to use `rand_ket(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ rand_ket(dimensions::Int) = rand_ket(SVector(dimensions)) -function rand_ket(dimensions::Union{AbstractVector{Int},Tuple}) +function rand_ket(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) N = prod(dimensions) ψ = rand(ComplexF64, N) .- (0.5 + 0.5im) return QuantumObject(normalize!(ψ); type = Ket, dims = dimensions) @@ -144,13 +144,13 @@ Returns the maximally mixed density matrix with given argument `dimensions`. The `dimensions` can be either the following types: - `dimensions::Int`: Number of basis states in the Hilbert space. -- `dimensions::Union{AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. +- `dimensions::Union{Dimensions,AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. !!! warning "Beware of type-stability!" If you want to keep type stability, it is recommended to use `maximally_mixed_dm(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ maximally_mixed_dm(dimensions::Int) = QuantumObject(I(dimensions) / complex(dimensions), Operator, SVector(dimensions)) -function maximally_mixed_dm(dimensions::Union{AbstractVector{Int},Tuple}) +function maximally_mixed_dm(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) N = prod(dimensions) return QuantumObject(I(N) / complex(N), Operator, dimensions) end @@ -162,7 +162,7 @@ Generate a random density matrix from Ginibre ensemble with given argument `dime The `dimensions` can be either the following types: - `dimensions::Int`: Number of basis states in the Hilbert space. -- `dimensions::Union{AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. +- `dimensions::Union{Dimensions,AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. The default keyword argument `rank = prod(dimensions)` (full rank). @@ -174,7 +174,7 @@ The default keyword argument `rank = prod(dimensions)` (full rank). - [K. Życzkowski, et al., Generating random density matrices, Journal of Mathematical Physics 52, 062201 (2011)](http://dx.doi.org/10.1063/1.3595693) """ rand_dm(dimensions::Int; rank::Int = prod(dimensions)) = rand_dm(SVector(dimensions), rank = rank) -function rand_dm(dimensions::Union{AbstractVector{Int},Tuple}; rank::Int = prod(dimensions)) +function rand_dm(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}; rank::Int = prod(dimensions)) N = prod(dimensions) (rank < 1) && throw(DomainError(rank, "The argument rank must be larger than 1.")) (rank > N) && throw(DomainError(rank, "The argument rank cannot exceed dimensions.")) diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 010855122..90eb44ab8 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -77,7 +77,7 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spost`](@ref) and [`sprepost`](@ref). """ spre(A::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {DT} = - get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator, A.dims) + get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator, A.dimensions) @doc raw""" spost(B::AbstractQuantumObject, Id_cache=I(size(B,1))) @@ -96,7 +96,7 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref) and [`sprepost`](@ref). """ spost(B::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {DT} = - get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator, B.dims) + get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator, B.dimensions) @doc raw""" sprepost(A::AbstractQuantumObject, B::AbstractQuantumObject) @@ -116,8 +116,8 @@ function sprepost( A::AbstractQuantumObject{DT1,OperatorQuantumObject}, B::AbstractQuantumObject{DT2,OperatorQuantumObject}, ) where {DT1,DT2} - check_dims(A, B) - return promote_op_type(A, B)(_sprepost(A.data, B.data), SuperOperator, A.dims) + check_dimensions(A, B) + return promote_op_type(A, B)(_sprepost(A.data, B.data), SuperOperator, A.dimensions) end @doc raw""" @@ -135,13 +135,13 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref), [`spost`](@ref), and [`sprepost`](@ref). """ lindblad_dissipator(O::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(O, 1))) where {DT} = - get_typename_wrapper(O)(_lindblad_dissipator(O.data, Id_cache), SuperOperator, O.dims) + get_typename_wrapper(O)(_lindblad_dissipator(O.data, Id_cache), SuperOperator, O.dimensions) # It is already a SuperOperator lindblad_dissipator(O::AbstractQuantumObject{DT,SuperOperatorQuantumObject}, Id_cache = nothing) where {DT} = O @doc raw""" - liouvillian(H::AbstractQuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims))) + liouvillian(H::AbstractQuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dimensions))) Construct the Liouvillian [`SuperOperator`](@ref) for a system Hamiltonian ``\hat{H}`` and a set of collapse operators ``\{\hat{C}_n\}_n``: @@ -162,7 +162,7 @@ See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref). function liouvillian( H::AbstractQuantumObject{DT,OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - Id_cache = I(prod(H.dims)), + Id_cache = I(prod(H.dimensions)), ) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, Id_cache) if !(c_ops isa Nothing) @@ -181,7 +181,7 @@ function liouvillian( return L end -liouvillian(H::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dims))) where {DT} = - get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator, H.dims) +liouvillian(H::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dimensions))) where {DT} = + get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator, H.dimensions) liouvillian(H::AbstractQuantumObject{DT,SuperOperatorQuantumObject}, Id_cache::Diagonal) where {DT} = H diff --git a/src/spectrum.jl b/src/spectrum.jl index a62a69ca6..af4d08b1a 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -84,8 +84,7 @@ function _spectrum( solver::ExponentialSeries; kwargs..., ) where {T1,T2,T3} - allequal((L.dims, A.dims, B.dims)) || - throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + check_dimensions(L, A, B) rates, vecs, ρss = _spectrum_get_rates_vecs_ss(L, solver) @@ -111,8 +110,7 @@ function _spectrum( solver::PseudoInverse; kwargs..., ) where {T1,T2,T3} - allequal((L.dims, A.dims, B.dims)) || - throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + check_dimensions(L, A, B) ωList = convert(Vector{_FType(L)}, ωlist) # Convert it to support GPUs and avoid type instabilities Length = length(ωList) @@ -123,7 +121,7 @@ function _spectrum( b = (spre(B) * ρss).data # multiply by operator A on the left (spre) and then perform trace operation - D = prod(L.dims) + D = prod(L.dimensions) _tr = SparseVector(D^2, [1 + n * (D + 1) for n in 0:(D-1)], ones(_CType(L), D)) # same as vec(system_identity_matrix) _tr_A = transpose(_tr) * spre(A).data diff --git a/src/spin_lattice.jl b/src/spin_lattice.jl index 9ce0094a3..99f356df3 100644 --- a/src/spin_lattice.jl +++ b/src/spin_lattice.jl @@ -51,7 +51,7 @@ function MultiSiteOperator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Inte sites, ops = _get_unique_sites_ops(_sites, _ops) - collect(dims)[sites] == [op.dims[1] for op in ops] || throw(ArgumentError("The dimensions of the operators do not match the dimensions of the lattice.")) + _dims[sites] == [get_dimensions_to(op)[1].size for op in ops] || throw(ArgumentError("The dimensions of the operators do not match the dimensions of the lattice.")) data = kron(I(prod(_dims[1:sites[1]-1])), ops[1].data) for i in 2:length(sites) diff --git a/src/steadystate.jl b/src/steadystate.jl index 606b0907e..43b23a2c3 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -96,7 +96,7 @@ function _steadystate( kwargs..., ) where {T} L_tmp = L.data - N = prod(L.dims) + N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) v0 = _get_dense_similar(L_tmp, N^2) @@ -126,7 +126,7 @@ function _steadystate( ρss = reshape(ρss_vec, N, N) ρss = (ρss + ρss') / 2 # Hermitianize - return QuantumObject(ρss, Operator, L.dims) + return QuantumObject(ρss, Operator, L.dimensions) end function _steadystate( @@ -134,7 +134,7 @@ function _steadystate( solver::SteadyStateEigenSolver; kwargs..., ) where {T} - N = prod(L.dims) + N = prod(L.dimensions) kwargs = merge((sigma = 1e-8, k = 1), (; kwargs...)) @@ -142,7 +142,7 @@ function _steadystate( ρss = reshape(ρss_vec, N, N) ρss /= tr(ρss) ρss = (ρss + ρss') / 2 # Hermitianize - return QuantumObject(ρss, Operator, L.dims) + return QuantumObject(ρss, Operator, L.dimensions) end function _steadystate( @@ -150,7 +150,7 @@ function _steadystate( solver::SteadyStateDirectSolver, ) where {T} L_tmp = L.data - N = prod(L.dims) + N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) v0 = _get_dense_similar(L_tmp, N^2) @@ -170,7 +170,7 @@ function _steadystate( ρss_vec = L_tmp \ v0 # This is still not supported on GPU, yet ρss = reshape(ρss_vec, N, N) ρss = (ρss + ρss') / 2 # Hermitianize - return QuantumObject(ρss, Operator, L.dims) + return QuantumObject(ρss, Operator, L.dimensions) end _steadystate( @@ -411,13 +411,13 @@ function _steadystate_floquet( ρ0 = reshape(ρtot[offset1+1:offset2], Ns, Ns) ρ0_tr = tr(ρ0) ρ0 = ρ0 / ρ0_tr - ρ0 = QuantumObject((ρ0 + ρ0') / 2, type = Operator, dims = L_0.dims) + ρ0 = QuantumObject((ρ0 + ρ0') / 2, type = Operator, dims = L_0.dimensions) ρtot = ρtot / ρ0_tr ρ_list = [ρ0] for i in 0:n_max-1 ρi_m = reshape(ρtot[offset1-(i+1)*N+1:offset1-i*N], Ns, Ns) - ρi_m = QuantumObject(ρi_m, type = Operator, dims = L_0.dims) + ρi_m = QuantumObject(ρi_m, type = Operator, dims = L_0.dimensions) push!(ρ_list, ρi_m) end @@ -434,8 +434,7 @@ function _steadystate_floquet( tol::R = 1e-8, kwargs..., ) where {R<:Real} - ((L_0.dims == L_p.dims) && (L_0.dims == L_m.dims)) || - throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) + check_dimensions(L_0, L_p, L_m) L_eff = liouvillian_floquet(L_0, L_p, L_m, ωd; n_max = n_max, tol = tol) diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index 8b71e94d9..f8a62cb8f 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -401,7 +401,7 @@ function lr_mesolveProblem( opt::NamedTuple = lr_mesolve_options_default, kwargs..., ) where {DT1,T2} - Hdims = H.dims + Hdims = H.dimensions # Formulation of problem H -= 0.5im * mapreduce(op -> op' * op, +, c_ops) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index db8ab0319..99b5d3fad 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -299,7 +299,7 @@ function mcsolveEnsembleProblem( ensemble_prob = TimeEvolutionProblem( EnsembleProblem(prob_mc.prob, prob_func = _prob_func, output_func = _output_func[1], safetycopy = false), prob_mc.times, - prob_mc.dims, + prob_mc.dimensions, (progr = _output_func[2], channel = _output_func[3]), ) @@ -472,7 +472,7 @@ function mcsolve( ) sol = _mcsolve_solve_ens(ens_prob_mc, alg, ensemble_method, ntraj) - dims = ens_prob_mc.dims + dims = ens_prob_mc.dimensions _sol_1 = sol[:, 1] _expvals_sol_1 = _mcsolve_get_expvals(_sol_1) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 2d9f42efc..d119785b3 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -73,7 +73,7 @@ function mesolveProblem( tlist = convert(Vector{_FType(ψ0)}, tlist) # Convert it to support GPUs and avoid type instabilities for OrdinaryDiffEq.jl L_evo = _mesolve_make_L_QobjEvo(H, c_ops) - check_dims(L_evo, ψ0) + check_dimensions(L_evo, ψ0) T = Base.promote_eltype(L_evo, ψ0) ρ0 = sparse_to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type @@ -89,7 +89,7 @@ function mesolveProblem( tspan = (tlist[1], tlist[end]) prob = ODEProblem{getVal(inplace),FullSpecialize}(L, ρ0, tspan, params; kwargs3...) - return TimeEvolutionProblem(prob, tlist, L_evo.dims) + return TimeEvolutionProblem(prob, tlist, L_evo.dimensions) end @doc raw""" @@ -179,7 +179,7 @@ end function mesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) sol = solve(prob.prob, alg) - ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = prob.dims), sol.u) + ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = prob.dimensions), sol.u) return TimeEvolutionSol( prob.times, diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 28562e280..341a2cc55 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -1,8 +1,8 @@ export sesolveProblem, sesolve _sesolve_make_U_QobjEvo(H::QuantumObjectEvolution{<:MatrixOperator}) = - QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dims, type = Operator) -_sesolve_make_U_QobjEvo(H::QuantumObject) = QobjEvo(MatrixOperator(-1im * H.data), dims = H.dims, type = Operator) + QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dimensions, type = Operator) +_sesolve_make_U_QobjEvo(H::QuantumObject) = QobjEvo(MatrixOperator(-1im * H.data), dims = H.dimensions, type = Operator) _sesolve_make_U_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}) = QobjEvo(H, -1im) @doc raw""" @@ -62,7 +62,7 @@ function sesolveProblem( H_evo = _sesolve_make_U_QobjEvo(H) # Multiply by -i isoper(H_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) - check_dims(H_evo, ψ0) + check_dimensions(H_evo, ψ0) T = Base.promote_eltype(H_evo, ψ0) ψ0 = sparse_to_dense(_CType(T), get_data(ψ0)) # Convert it to dense vector with complex element type @@ -78,7 +78,7 @@ function sesolveProblem( tspan = (tlist[1], tlist[end]) prob = ODEProblem{getVal(inplace),FullSpecialize}(U, ψ0, tspan, params; kwargs3...) - return TimeEvolutionProblem(prob, tlist, H_evo.dims) + return TimeEvolutionProblem(prob, tlist, H_evo.dimensions) end @doc raw""" @@ -152,7 +152,7 @@ end function sesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) sol = solve(prob.prob, alg) - ψt = map(ϕ -> QuantumObject(ϕ, type = Ket, dims = prob.dims), sol.u) + ψt = map(ϕ -> QuantumObject(ϕ, type = Ket, dims = prob.dimensions), sol.u) return TimeEvolutionSol( prob.times, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 2dc7fac16..cdd2de9bb 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -166,8 +166,8 @@ function ssesolveProblem( H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, sc_ops) isoper(H_eff_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) - check_dims(H_eff_evo, ψ0) - dims = H_eff_evo.dims + check_dimensions(H_eff_evo, ψ0) + dims = H_eff_evo.dimensions ψ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index b0f701f3f..ec429b1d1 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -15,16 +15,28 @@ A Julia constructor for handling the `ODEProblem` of the time evolution of quant - `prob::AbstractSciMLProblem`: The `ODEProblem` of the time evolution. - `times::Abstractvector`: The time list of the evolution. -- `dims::Abstractvector`: The dimensions of the Hilbert space. +- `dimensions::AbstractDimensions`: The dimensions of the Hilbert space. - `kwargs::KWT`: Generic keyword arguments. + +!!! note "`dims` property" + For a given `prob::TimeEvolutionProblem`, `prob.dims` or `getproperty(prob, :dims)` returns its `dimensions` in the type of integer-vector. """ -struct TimeEvolutionProblem{PT<:AbstractSciMLProblem,TT<:AbstractVector,DT<:AbstractVector,KWT} +struct TimeEvolutionProblem{PT<:AbstractSciMLProblem,TT<:AbstractVector,DT<:AbstractDimensions,KWT} prob::PT times::TT - dims::DT + dimensions::DT kwargs::KWT end +function Base.getproperty(prob::TimeEvolutionProblem, key::Symbol) + # a comment here to avoid bad render by JuliaFormatter + if key === :dims + return dimensions_to_dims(getfield(prob, :dimensions)) + else + return getfield(prob, key) + end +end + TimeEvolutionProblem(prob, times, dims) = TimeEvolutionProblem(prob, times, dims, nothing) @doc raw""" @@ -210,9 +222,7 @@ function liouvillian_floquet( n_max::Int = 3, tol::Real = 1e-15, ) where {T1,T2,T3} - ((L₀.dims == Lₚ.dims) && (L₀.dims == Lₘ.dims)) || - throw(DimensionMismatch("The quantum objects are not of the same Hilbert dimension.")) - + check_dimensions(L₀, Lₚ, Lₘ) return _liouvillian_floquet(L₀, Lₚ, Lₘ, ω, n_max, tol) end @@ -253,11 +263,11 @@ function liouvillian_generalized( ) where {MT<:AbstractMatrix} (length(fields) == length(T_list)) || throw(DimensionMismatch("The number of fields, ωs and Ts must be the same.")) - dims = (N_trunc isa Nothing) ? H.dims : SVector(N_trunc) + dims = (N_trunc isa Nothing) ? H.dimensions : SVector(N_trunc) final_size = prod(dims) result = eigen(H) E = real.(result.values[1:final_size]) - U = QuantumObject(result.vectors, result.type, result.dims) + U = QuantumObject(result.vectors, result.type, result.dimensions) H_d = QuantumObject(Diagonal(complex(E)), type = Operator, dims = dims) @@ -328,6 +338,6 @@ function _liouvillian_floquet( T = -(L_0 + 1im * n_i * ω * I + L_p * T) \ L_m_dense end - tol == 0 && return QuantumObject(L_0 + L_m * S + L_p * T, SuperOperator, L₀.dims) - return QuantumObject(dense_to_sparse(L_0 + L_m * S + L_p * T, tol), SuperOperator, L₀.dims) + tol == 0 && return QuantumObject(L_0 + L_m * S + L_p * T, SuperOperator, L₀.dimensions) + return QuantumObject(dense_to_sparse(L_0 + L_m * S + L_p * T, tol), SuperOperator, L₀.dimensions) end diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 650f406e0..81cf3260d 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -149,7 +149,7 @@ function dfd_mesolveProblem( tol_list::Vector{<:Number} = fill(1e-8, length(maxdims)), kwargs..., ) where {DT1,T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} - length(ψ0.dims) != length(maxdims) && + length(ψ0.dimensions) != length(maxdims) && throw(DimensionMismatch("'dim_list' and 'maxdims' do not have the same dimension.")) dim_list = MVector(ψ0.dims) @@ -362,7 +362,7 @@ function dsf_mesolveProblem( op_l_vec = map(op -> mat2vec(get_data(op)'), op_list) # Create the Krylov subspace with kron(H₀.data, H₀.data) just for initialize expv_cache = arnoldi(kron(H₀.data, H₀.data), mat2vec(ket2dm(ψ0).data), krylov_dim) - dsf_identity = I(prod(H₀.dims)) + dsf_identity = I(prod(H₀.dimensions)) dsf_displace_cache_left = sum(op -> ScalarOperator(one(T)) * MatrixOperator(kron(op.data, dsf_identity)), op_list) dsf_displace_cache_left_dag = sum(op -> ScalarOperator(one(T)) * MatrixOperator(kron(sparse(op.data'), dsf_identity)), op_list) diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index ef767f304..fc1d0769c 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -9,7 +9,7 @@ M = latt.N + 1 # Number of states in the LR basis # Define initial state - ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject,M - 1}}(undef, M) + ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject,Dimensions{M - 1,NTuple{M - 1,Space}}}}(undef, M) ϕ[1] = kron(fill(basis(2, 1), N_modes)...) i = 1 diff --git a/test/core-test/negativity_and_partial_transpose.jl b/test/core-test/negativity_and_partial_transpose.jl index ba10db898..ea1c1580e 100644 --- a/test/core-test/negativity_and_partial_transpose.jl +++ b/test/core-test/negativity_and_partial_transpose.jl @@ -35,6 +35,7 @@ end end @test_throws ArgumentError partial_transpose(A_dense, [true]) + @test_throws ArgumentError partial_transpose(Qobj(zeros(ComplexF64, 3, 2)), [true]) # invalid GeneralDimensions @testset "Type Inference (partial_transpose)" begin @inferred partial_transpose(A_dense, [true, false, true]) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 9ad54a760..91738bdc9 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -13,16 +13,27 @@ # DomainError: incompatible between size of array and type @testset "DomainError" begin a = rand(ComplexF64, 3, 2) - for t in [nothing, Operator, SuperOperator, Bra, OperatorBra] + for t in [SuperOperator, Bra, OperatorBra] @test_throws DomainError Qobj(a, type = t) end + a = rand(ComplexF64, 2, 2, 2) for t in [nothing, Ket, Bra, Operator, SuperOperator, OperatorBra, OperatorKet] @test_throws DomainError Qobj(a, type = t) end + a = rand(ComplexF64, 1, 2) @test_throws DomainError Qobj(a, type = Operator) @test_throws DomainError Qobj(a, type = SuperOperator) + + @test_throws DomainError Qobj(rand(ComplexF64, 2, 1), type = Operator) # should be type = Bra + + # check that Ket, Bra, SuperOperator, OperatorKet, and OperatorBra don't support GeneralDimensions + @test_throws DomainError Qobj(rand(ComplexF64, 2), type = Ket, dims = ((2,), (1,))) + @test_throws DomainError Qobj(rand(ComplexF64, 1, 2), type = Bra, dims = ((1,), (2,))) + @test_throws DomainError Qobj(rand(ComplexF64, 4, 4), type = SuperOperator, dims = ((2,), (2,))) + @test_throws DomainError Qobj(rand(ComplexF64, 4), type = OperatorKet, dims = ((2,), (1,))) + @test_throws DomainError Qobj(rand(ComplexF64, 1, 4), type = OperatorBra, dims = ((1,), (2,))) end # unsupported type of dims @@ -74,6 +85,7 @@ a = sprand(ComplexF64, 100, 100, 0.1) a2 = Qobj(a) a3 = Qobj(a, type = SuperOperator) + a4 = Qobj(sprand(ComplexF64, 100, 10, 0.1)) # GeneralDimensions @test isket(a2) == false @test isbra(a2) == false @test isoper(a2) == true @@ -83,6 +95,7 @@ @test iscached(a2) == true @test isconstant(a2) == true @test isunitary(a2) == false + @test a2.dims == [100] @test isket(a3) == false @test isbra(a3) == false @test isoper(a3) == false @@ -92,7 +105,20 @@ @test iscached(a3) == true @test isconstant(a3) == true @test isunitary(a3) == false + @test a3.dims == [10] + @test isket(a4) == false + @test isbra(a4) == false + @test isoper(a4) == true + @test issuper(a4) == false + @test isoperket(a4) == false + @test isoperbra(a4) == false + @test iscached(a4) == true + @test isconstant(a4) == true + @test isunitary(a4) == false + @test a4.dims == [[100], [10]] @test_throws DimensionMismatch Qobj(a, dims = 2) + @test_throws DimensionMismatch Qobj(a4.data, dims = 2) + @test_throws DimensionMismatch Qobj(a4.data, dims = ((100,), (2,))) end @testset "OperatorKet and OperatorBra" begin @@ -235,6 +261,16 @@ @test opstring == "\nQuantum Object: type=Operator dims=$a_dims size=$a_size ishermitian=$a_isherm\n$datastring" + # GeneralDimensions + Gop = tensor(a, ψ) + opstring = sprint((t, s) -> show(t, "text/plain", s), Gop) + datastring = sprint((t, s) -> show(t, "text/plain", s), Gop.data) + Gop_dims = [[N, N], [N, 1]] + Gop_size = size(Gop) + Gop_isherm = isherm(Gop) + @test opstring == + "\nQuantum Object: type=Operator dims=$Gop_dims size=$Gop_size ishermitian=$Gop_isherm\n$datastring" + a = spre(a) opstring = sprint((t, s) -> show(t, "text/plain", s), a) datastring = sprint((t, s) -> show(t, "text/plain", s), a.data) @@ -310,24 +346,29 @@ for T in [ComplexF32, ComplexF64] N = 4 a = rand(T, N) - @inferred QuantumObject{typeof(a),KetQuantumObject} Qobj(a) + @inferred QuantumObject{typeof(a),KetQuantumObject,Dimensions{1}} Qobj(a) for type in [Ket, OperatorKet] @inferred Qobj(a, type = type) end - UnionType = - Union{QuantumObject{Matrix{T},BraQuantumObject,1},QuantumObject{Matrix{T},OperatorQuantumObject,1}} + UnionType = Union{ + QuantumObject{Matrix{T},BraQuantumObject,Dimensions{1,Tuple{Space}}}, + QuantumObject{Matrix{T},OperatorQuantumObject,Dimensions{1,Tuple{Space}}}, + } a = rand(T, 1, N) @inferred UnionType Qobj(a) for type in [Bra, OperatorBra] @inferred Qobj(a, type = type) end + UnionType2 = Union{ + QuantumObject{Matrix{T},OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}}}, + QuantumObject{Matrix{T},OperatorQuantumObject,Dimensions{1,Tuple{Space}}}, + } a = rand(T, N, N) @inferred UnionType Qobj(a) - for type in [Operator, SuperOperator] - @inferred Qobj(a, type = type) - end + @inferred UnionType2 Qobj(a, type = Operator) + @inferred Qobj(a, type = SuperOperator) end @testset "Math Operation" begin @@ -629,8 +670,26 @@ ρ = kron(ρ1, ρ2) ρ1_ptr = ptrace(ρ, 1) ρ2_ptr = ptrace(ρ, 2) - @test ρ1.data ≈ ρ1_ptr.data atol = 1e-10 - @test ρ2.data ≈ ρ2_ptr.data atol = 1e-10 + + # use GeneralDimensions to do partial trace + ρ1_compound = Qobj(zeros(ComplexF64, 2, 2), dims = ((2, 1), (2, 1))) + II = qeye(2) + basis_list = [basis(2, i) for i in 0:1] + for b in basis_list + ρ1_compound += tensor(II, b') * ρ * tensor(II, b) + end + ρ2_compound = Qobj(zeros(ComplexF64, 2, 2), dims = ((1, 2), (1, 2))) + for b in basis_list + ρ2_compound += tensor(b', II) * ρ * tensor(b, II) + end + @test ρ1.data ≈ ρ1_ptr.data ≈ ρ1_compound.data + @test ρ2.data ≈ ρ2_ptr.data ≈ ρ2_compound.data + @test ρ1.dims != ρ1_compound.dims + @test ρ2.dims != ρ2_compound.dims + ρ1_compound = ptrace(ρ1_compound, 1) + ρ2_compound = ptrace(ρ2_compound, 2) + @test ρ1.dims == ρ1_compound.dims + @test ρ2.dims == ρ2_compound.dims ψlist = [rand_ket(2), rand_ket(3), rand_ket(4), rand_ket(5)] ρlist = [rand_dm(2), rand_dm(3), rand_dm(4), rand_dm(5)] @@ -683,6 +742,7 @@ @test_throws ArgumentError ptrace(ρtotal, (0, 2)) @test_throws ArgumentError ptrace(ρtotal, (2, 5)) @test_throws ArgumentError ptrace(ρtotal, (2, 2, 3)) + @test_throws ArgumentError ptrace(Qobj(zeros(ComplexF64, 3, 2)), 1) # invalid GeneralDimensions @testset "Type Inference (ptrace)" begin @inferred ptrace(ρ, 1) @@ -695,6 +755,7 @@ end @testset "permute" begin + # standard Dimensions ket_a = Qobj(rand(ComplexF64, 2)) ket_b = Qobj(rand(ComplexF64, 3)) ket_c = Qobj(rand(ComplexF64, 4)) @@ -729,10 +790,22 @@ @test_throws ArgumentError permute(op_bdca, wrong_order1) @test_throws ArgumentError permute(op_bdca, wrong_order2) + # GeneralDimensions + Gop_d = Qobj(rand(ComplexF64, 5, 6)) + compound_bdca = permute(tensor(ket_a, op_b, bra_c, Gop_d), (2, 4, 3, 1)) + compound_dacb = permute(tensor(ket_a, op_b, bra_c, Gop_d), (4, 1, 3, 2)) + @test compound_bdca ≈ tensor(op_b, Gop_d, bra_c, ket_a) + @test compound_dacb ≈ tensor(Gop_d, ket_a, bra_c, op_b) + @test compound_bdca.dims == [[3, 5, 1, 2], [3, 6, 4, 1]] + @test compound_dacb.dims == [[5, 2, 1, 3], [6, 1, 4, 3]] + @test isoper(compound_bdca) + @test isoper(compound_dacb) + @testset "Type Inference (permute)" begin @inferred permute(ket_bdca, (2, 4, 3, 1)) @inferred permute(bra_bdca, (2, 4, 3, 1)) @inferred permute(op_bdca, (2, 4, 3, 1)) + @inferred permute(compound_bdca, (2, 4, 3, 1)) end end end diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index f5b39c557..c64004e5b 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -2,9 +2,10 @@ # DomainError: incompatible between size of array and type @testset "Thrown Errors" begin a = MatrixOperator(rand(ComplexF64, 3, 2)) - for t in [Operator, SuperOperator] - @test_throws DomainError QobjEvo(a, type = t) - end + @test_throws DomainError QobjEvo(a, type = SuperOperator) + + a = MatrixOperator(rand(ComplexF64, 4, 4)) + @test_throws DomainError QobjEvo(a, type = SuperOperator, dims = ((2,), (2,))) a = MatrixOperator(rand(ComplexF64, 3, 2)) for t in (Ket, Bra, OperatorKet, OperatorBra) @@ -131,10 +132,13 @@ N = 4 for T in [ComplexF32, ComplexF64] a = MatrixOperator(rand(T, N, N)) - @inferred QobjEvo(a) - for type in [Operator, SuperOperator] - @inferred QobjEvo(a, type = type) - end + UnionType = Union{ + QuantumObjectEvolution{typeof(a),OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}}}, + QuantumObjectEvolution{typeof(a),OperatorQuantumObject,Dimensions{1,Tuple{Space}}}, + } + @inferred UnionType QobjEvo(a) + @inferred UnionType QobjEvo(a, type = Operator) + @inferred QobjEvo(a, type = SuperOperator) end a = destroy(N) diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index 507f1227a..630529559 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -46,10 +46,6 @@ @inferred steadystate(H, c_ops, solver = solver) @inferred steadystate(L, solver = solver) - solver = SteadyStateLinearSolver() - @inferred steadystate(H, c_ops, solver = solver) - @inferred steadystate(L, solver = solver) - solver = SteadyStateEigenSolver() @inferred steadystate(H, c_ops, solver = solver) @inferred steadystate(L, solver = solver) From 2db0455da2960a78b2d5a2d791cff8ada4e58cf5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:40:53 +0100 Subject: [PATCH 163/329] CompatHelper: bump compat for CairoMakie in [weakdeps] to 0.13, (keep existing compat) (#369) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e591de619..264ef05c6 100644 --- a/Project.toml +++ b/Project.toml @@ -41,7 +41,7 @@ QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] Aqua = "0.8" ArrayInterface = "6, 7" CUDA = "5" -CairoMakie = "0.12" +CairoMakie = "0.12, 0.13" DiffEqBase = "6" DiffEqCallbacks = "4.2.1 - 4" DiffEqNoiseProcess = "5" From 84015576ffa38f6a081f2438c50349267fec5b8b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 16 Jan 2025 14:53:57 +0900 Subject: [PATCH 164/329] improve lazy tensor warning --- src/qobj/functions.jl | 3 +++ src/qobj/superoperators.jl | 7 ++++--- src/utilities.jl | 17 +++++++++++++++-- test/core-test/quantum_objects_evo.jl | 26 ++++++++++---------------- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 2b738a800..fbc7f3031 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -184,6 +184,7 @@ function LinearAlgebra.kron( B::AbstractQuantumObject{DT2,OpType,<:Dimensions}, ) where {DT1,DT2,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} QType = promote_op_type(A, B) + _lazy_tensor_warning(A.data, B.data) return QType(kron(A.data, B.data), A.type, Dimensions((A.dimensions.to..., B.dimensions.to...))) end @@ -197,6 +198,7 @@ for ADimType in (:Dimensions, :GeneralDimensions) B::AbstractQuantumObject{DT2,OperatorQuantumObject,<:$BDimType}, ) where {DT1,DT2} QType = promote_op_type(A, B) + _lazy_tensor_warning(A.data, B.data) return QType( kron(A.data, B.data), Operator, @@ -221,6 +223,7 @@ for AOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) B::AbstractQuantumObject{DT2,$BOpType}, ) where {DT1,DT2} QType = promote_op_type(A, B) + _lazy_tensor_warning(A.data, B.data) return QType( kron(A.data, B.data), Operator, diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 90eb44ab8..85c942bd9 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -26,7 +26,7 @@ _spre(A::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spre(A.A, Id)) _spre(A::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(A.λ, _spre(A.L, Id)) _spre(A::AddedOperator, Id::AbstractMatrix) = AddedOperator(map(op -> _spre(op, Id), A.ops)) function _spre(A::AbstractSciMLOperator, Id::AbstractMatrix) - _lazy_tensor_warning("spre", A) + _lazy_tensor_warning(Id, A) return kron(Id, A) end @@ -34,8 +34,9 @@ _spost(B::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spost(B.A, Id)) _spost(B::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(B.λ, _spost(B.L, Id)) _spost(B::AddedOperator, Id::AbstractMatrix) = AddedOperator(map(op -> _spost(op, Id), B.ops)) function _spost(B::AbstractSciMLOperator, Id::AbstractMatrix) - _lazy_tensor_warning("spost", B) - return kron(transpose(B), Id) + B_T = transpose(B) + _lazy_tensor_warning(B_T, Id) + return kron(B_T, Id) end ## intrinsic liouvillian diff --git a/src/utilities.jl b/src/utilities.jl index 3e967c2d7..47b73f525 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -153,8 +153,21 @@ _non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = join(arg, ", ") * ")` instead of `$argname = $arg`." maxlog = 1 -_lazy_tensor_warning(func_name::String, data::AbstractSciMLOperator) = - @warn "The function `$func_name` uses lazy tensor (which can hurt performance) for data type: $(get_typename_wrapper(data))" +# lazy tensor warning +for AType in (:AbstractArray, :AbstractSciMLOperator) + for BType in (:AbstractArray, :AbstractSciMLOperator) + if AType == BType == :AbstractArray + @eval begin + _lazy_tensor_warning(::$AType, ::$BType) = nothing + end + else + @eval begin + _lazy_tensor_warning(A::$AType, B::$BType) = + @warn "using lazy tensor (which can hurt performance) between data types: $(get_typename_wrapper(A)) and $(get_typename_wrapper(B))" + end + end + end +end # functions for getting Float or Complex element type _FType(::AbstractArray{T}) where {T<:Number} = _FType(T) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index c64004e5b..130bfb32a 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -160,25 +160,19 @@ @inferred a * a @inferred a * a' - # TODO: kron is currently not supported - # @inferred kron(a) - # @inferred kron(a, σx) - # @inferred kron(a, eye(2)) + @inferred kron(a) + @test_logs (:warn,) @inferred kron(a, σx) + @test_logs (:warn,) @inferred kron(a, eye(2)) + @test_logs (:warn,) (:warn,) @inferred kron(a, eye(2), eye(2)) end end - # TODO: tensor is currently not supported - # @testset "tensor" begin - # σx = sigmax() - # X3 = kron(σx, σx, σx) - # @test tensor(σx) == kron(σx) - # @test tensor(fill(σx, 3)...) == X3 - # X_warn = @test_logs ( - # :warn, - # "`tensor(A)` or `kron(A)` with `A` is a `Vector` can hurt performance. Try to use `tensor(A...)` or `kron(A...)` instead.", - # ) tensor(fill(σx, 3)) - # @test X_warn == X3 - # end + @testset "tensor" begin + σx = QobjEvo(sigmax()) + X3 = @test_logs (:warn,) (:warn,) tensor(σx, σx, σx) + X_warn = @test_logs (:warn,) (:warn,) (:warn,) tensor(fill(σx, 3)) + @test X_warn(0) == X3(0) == tensor(sigmax(), sigmax(), sigmax()) + end @testset "Time Dependent Operators and SuperOperators" begin N = 10 From 5c2e4e40192bbdc10275878598f8a02400f3d3cc Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Thu, 16 Jan 2025 15:00:23 +0900 Subject: [PATCH 165/329] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 221fac574..8f6929e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change the structure of block diagonalization functions, using `BlockDiagonalForm` struct and changing the function name from `bdf` to `block_diagonal_form`. ([#349]) - Add **GPUArrays** compatibility for `ptrace` function, by using **KernelAbstractions.jl**. ([#350]) - Introduce `Space`, `Dimensions`, `GeneralDimensions` structures to support wider definitions and operations of `Qobj/QobjEvo`, and potential functionalities in the future. ([#271], [#353], [#360]) +- Improve lazy tensor warning for `SciMLOperators`. ([#370]) ## [v0.24.0] Release date: 2024-12-13 @@ -75,3 +76,4 @@ Release date: 2024-11-13 [#350]: https://github.com/qutip/QuantumToolbox.jl/issues/350 [#353]: https://github.com/qutip/QuantumToolbox.jl/issues/353 [#360]: https://github.com/qutip/QuantumToolbox.jl/issues/360 +[#370]: https://github.com/qutip/QuantumToolbox.jl/issues/370 From c939b2a7570fa37f6b8d3d1aaf78b659b63523f2 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sun, 19 Jan 2025 22:21:09 +0100 Subject: [PATCH 166/329] Main Changes --- ext/QuantumToolboxCUDAExt.jl | 34 +-- ext/QuantumToolboxCairoMakieExt.jl | 12 +- src/correlations.jl | 54 ++-- src/deprecated.jl | 54 ++-- src/metrics.jl | 37 +-- src/negativity.jl | 9 +- src/qobj/arithmetic_and_attributes.jl | 234 ++++++++---------- src/qobj/block_diagonal_form.jl | 4 +- src/qobj/boolean_functions.jl | 15 +- src/qobj/eigsolve.jl | 30 +-- src/qobj/functions.jl | 77 +++--- src/qobj/operators.jl | 12 +- src/qobj/quantum_object.jl | 66 ++--- src/qobj/quantum_object_base.jl | 29 ++- src/qobj/quantum_object_evo.jl | 18 +- src/qobj/superoperators.jl | 21 +- src/qobj/synonyms.jl | 18 +- src/spectrum.jl | 28 +-- src/steadystate.jl | 69 ++---- src/time_evolution/lr_mesolve.jl | 24 +- src/time_evolution/mcsolve.jl | 30 +-- src/time_evolution/mesolve.jl | 20 +- src/time_evolution/sesolve.jl | 20 +- src/time_evolution/ssesolve.jl | 30 +-- src/time_evolution/time_evolution.jl | 29 +-- .../time_evolution_dynamical.jl | 36 +-- src/visualization.jl | 14 +- src/wigner.jl | 6 +- test/core-test/low_rank_dynamics.jl | 2 +- test/core-test/quantum_objects.jl | 10 +- 30 files changed, 451 insertions(+), 591 deletions(-) diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index 459356b05..62629fbec 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -10,60 +10,56 @@ import SparseArrays: SparseVector, SparseMatrixCSC If `A.data` is a dense array, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CuArray` for gpu calculations. """ -CuArray(A::QuantumObject{Tq}) where {Tq<:Union{Vector,Matrix}} = QuantumObject(CuArray(A.data), A.type, A.dimensions) +CuArray(A::QuantumObject) = QuantumObject(CuArray(A.data), A.type, A.dimensions) @doc raw""" CuArray{T}(A::QuantumObject) If `A.data` is a dense array, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CuArray` with element type `T` for gpu calculations. """ -CuArray{T}(A::QuantumObject{Tq}) where {T,Tq<:Union{Vector,Matrix}} = - QuantumObject(CuArray{T}(A.data), A.type, A.dimensions) +CuArray{T}(A::QuantumObject) where {T} = QuantumObject(CuArray{T}(A.data), A.type, A.dimensions) @doc raw""" CuSparseVector(A::QuantumObject) If `A.data` is a sparse vector, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseVector` for gpu calculations. """ -CuSparseVector(A::QuantumObject{<:SparseVector}) = QuantumObject(CuSparseVector(A.data), A.type, A.dimensions) +CuSparseVector(A::QuantumObject) = QuantumObject(CuSparseVector(A.data), A.type, A.dimensions) @doc raw""" CuSparseVector{T}(A::QuantumObject) If `A.data` is a sparse vector, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseVector` with element type `T` for gpu calculations. """ -CuSparseVector{T}(A::QuantumObject{<:SparseVector}) where {T} = - QuantumObject(CuSparseVector{T}(A.data), A.type, A.dimensions) +CuSparseVector{T}(A::QuantumObject) where {T} = QuantumObject(CuSparseVector{T}(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSC(A::QuantumObject) If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSC` for gpu calculations. """ -CuSparseMatrixCSC(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseMatrixCSC(A.data), A.type, A.dimensions) +CuSparseMatrixCSC(A::QuantumObject) = QuantumObject(CuSparseMatrixCSC(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSC{T}(A::QuantumObject) If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSC` with element type `T` for gpu calculations. """ -CuSparseMatrixCSC{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T} = - QuantumObject(CuSparseMatrixCSC{T}(A.data), A.type, A.dimensions) +CuSparseMatrixCSC{T}(A::QuantumObject) where {T} = QuantumObject(CuSparseMatrixCSC{T}(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSR(A::QuantumObject) If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSR` for gpu calculations. """ -CuSparseMatrixCSR(A::QuantumObject{<:SparseMatrixCSC}) = QuantumObject(CuSparseMatrixCSR(A.data), A.type, A.dimensions) +CuSparseMatrixCSR(A::QuantumObject) = QuantumObject(CuSparseMatrixCSR(A.data), A.type, A.dimensions) @doc raw""" CuSparseMatrixCSR(A::QuantumObject) If `A.data` is in the type of `SparseMatrixCSC`, return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA.CUSPARSE.CuSparseMatrixCSR` with element type `T` for gpu calculations. """ -CuSparseMatrixCSR{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T} = - QuantumObject(CuSparseMatrixCSR{T}(A.data), A.type, A.dimensions) +CuSparseMatrixCSR{T}(A::QuantumObject) where {T} = QuantumObject(CuSparseMatrixCSR{T}(A.data), A.type, A.dimensions) @doc raw""" cu(A::QuantumObject; word_size::Int=64) @@ -77,12 +73,16 @@ Return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA` arr cu(A::QuantumObject; word_size::Int = 64) = ((word_size == 64) || (word_size == 32)) ? cu(A, Val(word_size)) : throw(DomainError(word_size, "The word size should be 32 or 64.")) -cu(A::QuantumObject{T}, word_size::TW) where {T<:Union{Vector,Matrix},TW<:Union{Val{32},Val{64}}} = +cu(A::QuantumObject, word_size::TW) where {TW<:Union{Val{32},Val{64}}} = CuArray{_change_eltype(eltype(A), word_size)}(A) -cu(A::QuantumObject{<:SparseVector}, word_size::TW) where {TW<:Union{Val{32},Val{64}}} = - CuSparseVector{_change_eltype(eltype(A), word_size)}(A) -cu(A::QuantumObject{<:SparseMatrixCSC}, word_size::TW) where {TW<:Union{Val{32},Val{64}}} = - CuSparseMatrixCSC{_change_eltype(eltype(A), word_size)}(A) +cu( + A::QuantumObject{ObjType,DimsType,<:SparseVector}, + word_size::TW, +) where {ObjType,DimsType,TW<:Union{Val{32},Val{64}}} = CuSparseVector{_change_eltype(eltype(A), word_size)}(A) +cu( + A::QuantumObject{ObjType,DimsType,<:SparseMatrixCSC}, + word_size::TW, +) where {ObjType,DimsType,TW<:Union{Val{32},Val{64}}} = CuSparseMatrixCSC{_change_eltype(eltype(A), word_size)}(A) _change_eltype(::Type{T}, ::Val{64}) where {T<:Int} = Int64 _change_eltype(::Type{T}, ::Val{32}) where {T<:Int} = Int32 diff --git a/ext/QuantumToolboxCairoMakieExt.jl b/ext/QuantumToolboxCairoMakieExt.jl index f5a7a2272..c09ee32fc 100644 --- a/ext/QuantumToolboxCairoMakieExt.jl +++ b/ext/QuantumToolboxCairoMakieExt.jl @@ -6,7 +6,7 @@ using CairoMakie: Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, @doc raw""" plot_wigner( library::Val{:CairoMakie}, - state::QuantumObject{DT,OpType}; + state::QuantumObject{OpType}; xvec::Union{Nothing,AbstractVector} = nothing, yvec::Union{Nothing,AbstractVector} = nothing, g::Real = √2, @@ -15,7 +15,7 @@ using CairoMakie: Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, location::Union{GridPosition,Nothing} = nothing, colorbar::Bool = false, kwargs... - ) where {DT,OpType} + ) where {OpType} Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) plotting library. @@ -44,7 +44,7 @@ Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wi """ function QuantumToolbox.plot_wigner( library::Val{:CairoMakie}, - state::QuantumObject{DT,OpType}; + state::QuantumObject{OpType}; xvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200), yvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200), g::Real = √2, @@ -53,7 +53,7 @@ function QuantumToolbox.plot_wigner( location::Union{GridPosition,Nothing} = nothing, colorbar::Bool = false, kwargs..., -) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} QuantumToolbox.getVal(projection) == :two_dim || QuantumToolbox.getVal(projection) == :three_dim || throw(ArgumentError("Unsupported projection: $projection")) @@ -74,7 +74,7 @@ end function _plot_wigner( ::Val{:CairoMakie}, - state::QuantumObject{DT,OpType}, + state::QuantumObject{OpType}, xvec::AbstractVector, yvec::AbstractVector, projection::Val{:two_dim}, @@ -107,7 +107,7 @@ end function _plot_wigner( ::Val{:CairoMakie}, - state::QuantumObject{DT,OpType}, + state::QuantumObject{OpType}, xvec::AbstractVector, yvec::AbstractVector, projection::Val{:three_dim}, diff --git a/src/correlations.jl b/src/correlations.jl index ada51d3da..ae9598f98 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -24,21 +24,16 @@ Returns the two-times correlation function of three operators ``\hat{A}``, ``\ha If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). """ function correlation_3op_2t( - H::AbstractQuantumObject{DataType,HOpType}, - ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + H::AbstractQuantumObject{HOpType}, + ψ0::Union{Nothing,QuantumObject{StateOpType}}, tlist::AbstractVector, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - C::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}; + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, + C::QuantumObject{OperatorQuantumObject}; kwargs..., ) where { - DataType, - T1, - T2, - T3, - T4, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } @@ -79,20 +74,15 @@ Returns the one-time correlation function of three operators ``\hat{A}``, ``\hat If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). """ function correlation_3op_1t( - H::AbstractQuantumObject{DataType,HOpType}, - ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + H::AbstractQuantumObject{HOpType}, + ψ0::Union{Nothing,QuantumObject{StateOpType}}, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - C::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}; + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, + C::QuantumObject{OperatorQuantumObject}; kwargs..., ) where { - DataType, - T1, - T2, - T3, - T4, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } @@ -119,20 +109,16 @@ If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \right\rangle``. """ function correlation_2op_2t( - H::AbstractQuantumObject{DataType,HOpType}, - ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + H::AbstractQuantumObject{HOpType}, + ψ0::Union{Nothing,QuantumObject{StateOpType}}, tlist::AbstractVector, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}; reverse::Bool = false, kwargs..., ) where { - DataType, - T1, - T2, - T3, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } @@ -163,19 +149,15 @@ If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) When `reverse=true`, the correlation function is calculated as ``\left\langle \hat{A}(0) \hat{B}(\tau) \right\rangle``. """ function correlation_2op_1t( - H::AbstractQuantumObject{DataType,HOpType}, - ψ0::Union{Nothing,QuantumObject{<:AbstractArray{T1},StateOpType}}, + H::AbstractQuantumObject{HOpType}, + ψ0::Union{Nothing,QuantumObject{StateOpType}}, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}; reverse::Bool = false, kwargs..., ) where { - DataType, - T1, - T2, - T3, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } diff --git a/src/deprecated.jl b/src/deprecated.jl index 98d92193d..b39a392bf 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -21,21 +21,16 @@ FFTCorrelation() = error( ) correlation_3op_2t( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + H::QuantumObject{HOpType}, + ψ0::QuantumObject{StateOpType}, t_l::AbstractVector, τ_l::AbstractVector, - A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, - C::QuantumObject{<:AbstractArray{T5},OperatorQuantumObject}, + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, + C::QuantumObject{OperatorQuantumObject}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; kwargs..., ) where { - T1, - T2, - T3, - T4, - T5, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } = error( @@ -43,20 +38,15 @@ correlation_3op_2t( ) correlation_3op_1t( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + H::QuantumObject{HOpType}, + ψ0::QuantumObject{StateOpType}, τ_l::AbstractVector, - A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, - C::QuantumObject{<:AbstractArray{T5},OperatorQuantumObject}, + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, + C::QuantumObject{OperatorQuantumObject}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; kwargs..., ) where { - T1, - T2, - T3, - T4, - T5, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } = error( @@ -64,20 +54,16 @@ correlation_3op_1t( ) correlation_2op_2t( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + H::QuantumObject{HOpType}, + ψ0::QuantumObject{StateOpType}, t_l::AbstractVector, τ_l::AbstractVector, - A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; reverse::Bool = false, kwargs..., ) where { - T1, - T2, - T3, - T4, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } = error( @@ -85,19 +71,15 @@ correlation_2op_2t( ) correlation_2op_1t( - H::QuantumObject{<:AbstractArray{T1},HOpType}, - ψ0::QuantumObject{<:AbstractArray{T2},StateOpType}, + H::QuantumObject{HOpType}, + ψ0::QuantumObject{StateOpType}, τ_l::AbstractVector, - A::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T4},OperatorQuantumObject}, + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; reverse::Bool = false, kwargs..., ) where { - T1, - T2, - T3, - T4, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } = error( diff --git a/src/metrics.jl b/src/metrics.jl index 6bbcc7cc6..e178e579b 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -51,11 +51,8 @@ julia> entropy_vn(ρ, base=2) 1.0 ``` """ -function entropy_vn( - ρ::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}; - base::Int = 0, - tol::Real = 1e-15, -) where {T} +function entropy_vn(ρ::QuantumObject{OperatorQuantumObject}; base::Int = 0, tol::Real = 1e-15) + T = eltype(ρ) vals = eigenenergies(ρ) indexes = findall(x -> abs(x) > tol, vals) length(indexes) == 0 && return zero(real(T)) @@ -71,9 +68,9 @@ Calculates the entanglement by doing the partial trace of `QO`, selecting only t with the indices contained in the `sel` vector, and then using the Von Neumann entropy [`entropy_vn`](@ref). """ function entanglement( - QO::QuantumObject{<:AbstractArray{T},OpType}, + QO::QuantumObject{OpType}, sel::Union{AbstractVector{Int},Tuple}, -) where {T,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} ψ = normalize(QO) ρ_tr = ptrace(ψ, sel) entropy = entropy_vn(ρ_tr) @@ -90,11 +87,9 @@ Calculates the [trace distance](https://en.wikipedia.org/wiki/Trace_distance) be Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). """ tracedist( - ρ::QuantumObject{<:AbstractArray{T1},ObjType1}, - σ::QuantumObject{<:AbstractArray{T2},ObjType2}, + ρ::QuantumObject{ObjType1}, + σ::QuantumObject{ObjType2}, ) where { - T1, - T2, ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, } = norm(ket2dm(ρ) - ket2dm(σ), 1) / 2 @@ -109,23 +104,11 @@ Here, the definition is from Nielsen & Chuang, "Quantum Computation and Quantum Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). """ -function fidelity( - ρ::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - σ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, -) where {T1,T2} +function fidelity(ρ::QuantumObject{OperatorQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) sqrt_ρ = sqrt(ρ) eigval = abs.(eigvals(sqrt_ρ * σ * sqrt_ρ)) return sum(sqrt, eigval) end -fidelity( - ρ::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ψ::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, -) where {T1,T2} = sqrt(abs(expect(ρ, ψ))) -fidelity( - ψ::QuantumObject{<:AbstractArray{T1},KetQuantumObject}, - σ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, -) where {T1,T2} = fidelity(σ, ψ) -fidelity( - ψ::QuantumObject{<:AbstractArray{T1},KetQuantumObject}, - ϕ::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, -) where {T1,T2} = abs(dot(ψ, ϕ)) +fidelity(ρ::QuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) = sqrt(abs(expect(ρ, ψ))) +fidelity(ψ::QuantumObject{KetQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) = fidelity(σ, ψ) +fidelity(ψ::QuantumObject{KetQuantumObject}, ϕ::QuantumObject{KetQuantumObject}) = abs(dot(ψ, ϕ)) diff --git a/src/negativity.jl b/src/negativity.jl index f9fa2a215..79be89720 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -70,7 +70,7 @@ Return the partial transpose of a density matrix ``\rho``, where `mask` is an ar # Returns - `ρ_pt::QuantumObject`: The density matrix with the selected subsystems transposed. """ -function partial_transpose(ρ::QuantumObject{DT,OperatorQuantumObject}, mask::Vector{Bool}) where {DT} +function partial_transpose(ρ::QuantumObject{OperatorQuantumObject}, mask::Vector{Bool}) if length(mask) != length(ρ.dimensions) throw(ArgumentError("The length of \`mask\` should be equal to the length of \`ρ.dims\`.")) end @@ -78,7 +78,7 @@ function partial_transpose(ρ::QuantumObject{DT,OperatorQuantumObject}, mask::Ve end # for dense matrices -function _partial_transpose(ρ::QuantumObject{DT,OperatorQuantumObject}, mask::Vector{Bool}) where {DT<:AbstractArray} +function _partial_transpose(ρ::QuantumObject{OperatorQuantumObject}, mask::Vector{Bool}) isa(ρ.dimensions, GeneralDimensions) && (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) @@ -103,7 +103,10 @@ function _partial_transpose(ρ::QuantumObject{DT,OperatorQuantumObject}, mask::V end # for sparse matrices -function _partial_transpose(ρ::QuantumObject{<:AbstractSparseArray,OperatorQuantumObject}, mask::Vector{Bool}) +function _partial_transpose( + ρ::QuantumObject{OperatorQuantumObject,DimsType,<:AbstractSparseArray}, + mask::Vector{Bool}, +) where {DimsType} isa(ρ.dimensions, GeneralDimensions) && (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 0ae908b6f..09694bb45 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -64,9 +64,9 @@ for ADimType in (:Dimensions, :GeneralDimensions) if ADimType == BDimType == :Dimensions @eval begin function LinearAlgebra.:(*)( - A::AbstractQuantumObject{DT1,OperatorQuantumObject,<:$ADimType}, - B::AbstractQuantumObject{DT2,OperatorQuantumObject,<:$BDimType}, - ) where {DT1,DT2} + A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, + B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, + ) check_dimensions(A, B) QType = promote_op_type(A, B) return QType(A.data * B.data, Operator, A.dimensions) @@ -75,9 +75,9 @@ for ADimType in (:Dimensions, :GeneralDimensions) else @eval begin function LinearAlgebra.:(*)( - A::AbstractQuantumObject{DT1,OperatorQuantumObject,<:$ADimType}, - B::AbstractQuantumObject{DT2,OperatorQuantumObject,<:$BDimType}, - ) where {DT1,DT2} + A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, + B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, + ) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) QType = promote_op_type(A, B) return QType( @@ -92,64 +92,55 @@ for ADimType in (:Dimensions, :GeneralDimensions) end function LinearAlgebra.:(*)( - A::AbstractQuantumObject{DT1,OperatorQuantumObject}, - B::QuantumObject{DT2,KetQuantumObject,<:Dimensions}, -) where {DT1,DT2} + A::AbstractQuantumObject{OperatorQuantumObject}, + B::QuantumObject{KetQuantumObject,<:Dimensions}, +) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) return QuantumObject(A.data * B.data, Ket, Dimensions(get_dimensions_to(A))) end function LinearAlgebra.:(*)( - A::QuantumObject{DT1,BraQuantumObject,<:Dimensions}, - B::AbstractQuantumObject{DT2,OperatorQuantumObject}, -) where {DT1,DT2} + A::QuantumObject{BraQuantumObject,<:Dimensions}, + B::AbstractQuantumObject{OperatorQuantumObject}, +) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) return QuantumObject(A.data * B.data, Bra, Dimensions(get_dimensions_from(B))) end -function LinearAlgebra.:(*)( - A::QuantumObject{DT1,KetQuantumObject}, - B::QuantumObject{DT2,BraQuantumObject}, -) where {DT1,DT2} +function LinearAlgebra.:(*)(A::QuantumObject{KetQuantumObject}, B::QuantumObject{BraQuantumObject}) check_dimensions(A, B) return QuantumObject(A.data * B.data, Operator, A.dimensions) # to align with QuTiP, don't use kron(A, B) to do it. end -function LinearAlgebra.:(*)( - A::QuantumObject{DT1,BraQuantumObject}, - B::QuantumObject{DT2,KetQuantumObject}, -) where {DT1,DT2} +function LinearAlgebra.:(*)(A::QuantumObject{BraQuantumObject}, B::QuantumObject{KetQuantumObject}) check_dimensions(A, B) return A.data * B.data end function LinearAlgebra.:(*)( - A::AbstractQuantumObject{DT1,SuperOperatorQuantumObject}, - B::QuantumObject{DT2,OperatorQuantumObject}, -) where {DT1,DT2} + A::AbstractQuantumObject{SuperOperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, +) check_dimensions(A, B) return QuantumObject(vec2mat(A.data * mat2vec(B.data)), Operator, A.dimensions) end -function LinearAlgebra.:(*)( - A::QuantumObject{DT1,OperatorBraQuantumObject}, - B::QuantumObject{DT2,OperatorKetQuantumObject}, -) where {DT1,DT2} +function LinearAlgebra.:(*)(A::QuantumObject{OperatorBraQuantumObject}, B::QuantumObject{OperatorKetQuantumObject}) check_dimensions(A, B) return A.data * B.data end function LinearAlgebra.:(*)( - A::AbstractQuantumObject{DT1,SuperOperatorQuantumObject}, - B::QuantumObject{DT2,OperatorKetQuantumObject}, -) where {DT1,DT2} + A::AbstractQuantumObject{SuperOperatorQuantumObject}, + B::QuantumObject{OperatorKetQuantumObject}, +) check_dimensions(A, B) return QuantumObject(A.data * B.data, OperatorKet, A.dimensions) end function LinearAlgebra.:(*)( - A::QuantumObject{<:AbstractArray{T1},OperatorBraQuantumObject}, - B::AbstractQuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, -) where {T1,T2} + A::QuantumObject{OperatorBraQuantumObject}, + B::AbstractQuantumObject{SuperOperatorQuantumObject}, +) check_dimensions(A, B) return QuantumObject(A.data * B.data, OperatorBra, A.dimensions) end -LinearAlgebra.:(^)(A::QuantumObject{DT}, n::T) where {DT,T<:Number} = QuantumObject(^(A.data, n), A.type, A.dimensions) -LinearAlgebra.:(/)(A::AbstractQuantumObject{DT}, n::T) where {DT,T<:Number} = +LinearAlgebra.:(^)(A::QuantumObject, n::T) where {T<:Number} = QuantumObject(^(A.data, n), A.type, A.dimensions) +LinearAlgebra.:(/)(A::AbstractQuantumObject, n::T) where {T<:Number} = get_typename_wrapper(A)(A.data / n, A.type, A.dimensions) @doc raw""" @@ -164,9 +155,9 @@ Note that `A` and `B` should be [`Ket`](@ref) or [`OperatorKet`](@ref) `A ⋅ B` (where `⋅` can be typed by tab-completing `\cdot` in the REPL) is a synonym of `dot(A, B)`. """ function LinearAlgebra.dot( - A::QuantumObject{DT1,OpType}, - B::QuantumObject{DT2,OpType}, -) where {DT1,DT2,OpType<:Union{KetQuantumObject,OperatorKetQuantumObject}} + A::QuantumObject{OpType}, + B::QuantumObject{OpType}, +) where {OpType<:Union{KetQuantumObject,OperatorKetQuantumObject}} check_dimensions(A, B) return LinearAlgebra.dot(A.data, B.data) end @@ -185,18 +176,18 @@ Supports the following inputs: `matrix_element(i, A, j)` is a synonym of `dot(i, A, j)`. """ function LinearAlgebra.dot( - i::QuantumObject{DT1,KetQuantumObject}, - A::AbstractQuantumObject{DT2,OperatorQuantumObject}, - j::QuantumObject{DT3,KetQuantumObject}, -) where {DT1,DT2,DT3} + i::QuantumObject{KetQuantumObject}, + A::AbstractQuantumObject{OperatorQuantumObject}, + j::QuantumObject{KetQuantumObject}, +) check_dimensions(i, A, j) return LinearAlgebra.dot(i.data, A.data, j.data) end function LinearAlgebra.dot( - i::QuantumObject{DT1,OperatorKetQuantumObject}, - A::AbstractQuantumObject{DT2,SuperOperatorQuantumObject}, - j::QuantumObject{DT3,OperatorKetQuantumObject}, -) where {DT1,DT2,DT3} + i::QuantumObject{OperatorKetQuantumObject}, + A::AbstractQuantumObject{SuperOperatorQuantumObject}, + j::QuantumObject{OperatorKetQuantumObject}, +) check_dimensions(i, A, j) return LinearAlgebra.dot(i.data, A.data, j.data) end @@ -215,9 +206,7 @@ Return a similar [`AbstractQuantumObject`](@ref) with `dims` and `type` are same Note that `A` must be [`Operator`](@ref) or [`SuperOperator`](@ref). """ -Base.one( - A::AbstractQuantumObject{DT,OpType}, -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.one(A::AbstractQuantumObject{OpType}) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = get_typename_wrapper(A)(one(A.data), A.type, A.dimensions) @doc raw""" @@ -233,8 +222,8 @@ Base.conj(A::AbstractQuantumObject) = get_typename_wrapper(A)(conj(A.data), A.ty Lazy matrix transpose of the [`AbstractQuantumObject`](@ref). """ LinearAlgebra.transpose( - A::AbstractQuantumObject{DT,OpType}, -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + A::AbstractQuantumObject{OpType}, +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = get_typename_wrapper(A)(transpose(A.data), A.type, transpose(A.dimensions)) @doc raw""" @@ -248,16 +237,14 @@ Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) `A'` and `dag(A)` are synonyms of `adjoint(A)`. """ LinearAlgebra.adjoint( - A::AbstractQuantumObject{DT,OpType}, -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + A::AbstractQuantumObject{OpType}, +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = get_typename_wrapper(A)(adjoint(A.data), A.type, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{DT,KetQuantumObject}) where {DT} = - QuantumObject(adjoint(A.data), Bra, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{DT,BraQuantumObject}) where {DT} = - QuantumObject(adjoint(A.data), Ket, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{DT,OperatorKetQuantumObject}) where {DT} = +LinearAlgebra.adjoint(A::QuantumObject{KetQuantumObject}) = QuantumObject(adjoint(A.data), Bra, adjoint(A.dimensions)) +LinearAlgebra.adjoint(A::QuantumObject{BraQuantumObject}) = QuantumObject(adjoint(A.data), Ket, adjoint(A.dimensions)) +LinearAlgebra.adjoint(A::QuantumObject{OperatorKetQuantumObject}) = QuantumObject(adjoint(A.data), OperatorBra, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{DT,OperatorBraQuantumObject}) where {DT} = +LinearAlgebra.adjoint(A::QuantumObject{OperatorBraQuantumObject}) = QuantumObject(adjoint(A.data), OperatorKet, adjoint(A.dimensions)) @doc raw""" @@ -266,14 +253,14 @@ LinearAlgebra.adjoint(A::QuantumObject{DT,OperatorBraQuantumObject}) where {DT} Matrix inverse of the [`AbstractQuantumObject`](@ref). If `A` is a [`QuantumObjectEvolution`](@ref), the inverse is computed at the last computed time. """ LinearAlgebra.inv( - A::AbstractQuantumObject{DT,OpType}, -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + A::AbstractQuantumObject{OpType}, +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(sparse(inv(Matrix(A.data))), A.type, A.dimensions) LinearAlgebra.Hermitian( - A::QuantumObject{DT,OpType}, + A::QuantumObject{OpType}, uplo::Symbol = :U, -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(Hermitian(A.data, uplo), A.type, A.dimensions) @doc raw""" @@ -300,21 +287,21 @@ julia> tr(a' * a) 190.0 + 0.0im ``` """ +LinearAlgebra.tr(A::QuantumObject{OpType}) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + tr(A.data) LinearAlgebra.tr( - A::QuantumObject{<:AbstractArray{T},OpType}, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = tr(A.data) -LinearAlgebra.tr( - A::QuantumObject{<:Union{<:Hermitian{TF},Symmetric{TR}},OpType}, -) where {TF<:BlasFloat,TR<:Real,OpType<:OperatorQuantumObject} = real(tr(A.data)) + A::QuantumObject{OpType,DimsType,<:Union{<:Hermitian{TF},Symmetric{TR}}}, +) where {OpType<:OperatorQuantumObject,DimsType,TF<:BlasFloat,TR<:Real} = real(tr(A.data)) @doc raw""" svdvals(A::QuantumObject) Return the singular values of a [`QuantumObject`](@ref) in descending order """ -LinearAlgebra.svdvals(A::QuantumObject{<:AbstractVector}) = svdvals(A.data) -LinearAlgebra.svdvals(A::QuantumObject{<:AbstractMatrix}) = svdvals(A.data) -LinearAlgebra.svdvals(A::QuantumObject{<:AbstractSparseMatrix}) = svdvals(sparse_to_dense(A.data)) +LinearAlgebra.svdvals(A::QuantumObject{OpType,DimsType,<:AbstractVector}) where {OpType,DimsType} = svdvals(A.data) +LinearAlgebra.svdvals(A::QuantumObject{OpType,DimsType,<:AbstractMatrix}) where {OpType,DimsType} = svdvals(A.data) +LinearAlgebra.svdvals(A::QuantumObject{OpType,DimsType,<:AbstractSparseMatrix}) where {OpType,DimsType} = + svdvals(sparse_to_dense(A.data)) @doc raw""" norm(A::QuantumObject, p::Real) @@ -347,14 +334,14 @@ julia> norm(ψ) ``` """ LinearAlgebra.norm( - A::QuantumObject{<:AbstractArray{T},OpType}, + A::QuantumObject{OpType}, p::Real = 2, -) where {T,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorKetQuantumObject,OperatorBraQuantumObject}} = +) where {OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorKetQuantumObject,OperatorBraQuantumObject}} = norm(A.data, p) function LinearAlgebra.norm( - A::QuantumObject{<:AbstractArray{T},OpType}, + A::QuantumObject{OpType}, p::Real = 1, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} p == 2.0 && return norm(A.data, 2) return norm(svdvals(A), p) end @@ -375,10 +362,10 @@ Support for the following types of [`QuantumObject`](@ref): Also, see [`norm`](@ref) about its definition for different types of [`QuantumObject`](@ref). """ LinearAlgebra.normalize( - A::QuantumObject{<:AbstractArray{T},ObjType}, + A::QuantumObject{ObjType}, p::Real = 2, -) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject}} = QuantumObject(A.data / norm(A, p), A.type, A.dimensions) -LinearAlgebra.normalize(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, p::Real = 1) where {T} = +) where {ObjType<:Union{KetQuantumObject,BraQuantumObject}} = QuantumObject(A.data / norm(A, p), A.type, A.dimensions) +LinearAlgebra.normalize(A::QuantumObject{OperatorQuantumObject}, p::Real = 1) = QuantumObject(A.data / norm(A, p), A.type, A.dimensions) @doc raw""" @@ -393,36 +380,34 @@ Support for the following types of [`QuantumObject`](@ref): Also, see [`norm`](@ref) about its definition for different types of [`QuantumObject`](@ref). """ LinearAlgebra.normalize!( - A::QuantumObject{<:AbstractArray{T},ObjType}, + A::QuantumObject{ObjType}, p::Real = 2, -) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject}} = (rmul!(A.data, 1 / norm(A, p)); A) -LinearAlgebra.normalize!(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, p::Real = 1) where {T} = - (rmul!(A.data, 1 / norm(A, p)); A) +) where {ObjType<:Union{KetQuantumObject,BraQuantumObject}} = (rmul!(A.data, 1 / norm(A, p)); A) +LinearAlgebra.normalize!(A::QuantumObject{OperatorQuantumObject}, p::Real = 1) = (rmul!(A.data, 1 / norm(A, p)); A) LinearAlgebra.triu!( - A::QuantumObject{<:AbstractArray{T},OpType}, + A::QuantumObject{OpType}, k::Integer = 0, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (triu!(A.data, k); A) +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (triu!(A.data, k); A) LinearAlgebra.tril!( - A::QuantumObject{<:AbstractArray{T},OpType}, + A::QuantumObject{OpType}, k::Integer = 0, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (tril!(A.data, k); A) +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (tril!(A.data, k); A) LinearAlgebra.triu( - A::QuantumObject{<:AbstractArray{T},OpType}, + A::QuantumObject{OpType}, k::Integer = 0, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(triu(A.data, k), A.type, A.dimensions) LinearAlgebra.tril( - A::QuantumObject{<:AbstractArray{T},OpType}, + A::QuantumObject{OpType}, k::Integer = 0, -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(tril(A.data, k), A.type, A.dimensions) -LinearAlgebra.lmul!(a::Number, B::QuantumObject{<:AbstractArray}) = (lmul!(a, B.data); B) -LinearAlgebra.rmul!(B::QuantumObject{<:AbstractArray}, a::Number) = (rmul!(B.data, a); B) +LinearAlgebra.lmul!(a::Number, B::QuantumObject) = (lmul!(a, B.data); B) +LinearAlgebra.rmul!(B::QuantumObject, a::Number) = (rmul!(B.data, a); B) -@inline LinearAlgebra.mul!(y::AbstractVector{Ty}, A::QuantumObject{<:AbstractMatrix{Ta}}, x, α, β) where {Ty,Ta} = - mul!(y, A.data, x, α, β) +@inline LinearAlgebra.mul!(y::AbstractVector{T}, A::QuantumObject, x, α, β) where {T} = mul!(y, A.data, x, α, β) @doc raw""" √(A) @@ -433,8 +418,7 @@ Matrix square root of [`QuantumObject`](@ref) !!! note `√(A)` (where `√` can be typed by tab-completing `\sqrt` in the REPL) is a synonym of `sqrt(A)`. """ -LinearAlgebra.sqrt(A::QuantumObject{<:AbstractArray{T}}) where {T} = - QuantumObject(sqrt(sparse_to_dense(A.data)), A.type, A.dimensions) +LinearAlgebra.sqrt(A::QuantumObject) = QuantumObject(sqrt(sparse_to_dense(A.data)), A.type, A.dimensions) @doc raw""" log(A::QuantumObject) @@ -443,9 +427,7 @@ Matrix logarithm of [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.log( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +LinearAlgebra.log(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(log(sparse_to_dense(A.data)), A.type, A.dimensions) @doc raw""" @@ -456,12 +438,12 @@ Matrix exponential of [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ LinearAlgebra.exp( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + A::QuantumObject{ObjType,DimsType,<:AbstractMatrix}, +) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = QuantumObject(dense_to_sparse(exp(A.data)), A.type, A.dimensions) LinearAlgebra.exp( - A::QuantumObject{<:AbstractSparseMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + A::QuantumObject{ObjType,DimsType,<:AbstractSparseMatrix}, +) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = QuantumObject(_spexp(A.data), A.type, A.dimensions) function _spexp(A::SparseMatrixCSC{T,M}; threshold = 1e-14, nonzero_tol = 1e-20) where {T,M} @@ -504,9 +486,8 @@ Matrix sine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.sin( - A::QuantumObject{DT,ObjType}, -) where {DT,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) - exp(-1im * A)) / 2im +LinearAlgebra.sin(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + (exp(1im * A) - exp(-1im * A)) / 2im @doc raw""" cos(A::QuantumObject) @@ -517,9 +498,8 @@ Matrix cosine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.cos( - A::QuantumObject{DT,ObjType}, -) where {DT,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) + exp(-1im * A)) / 2 +LinearAlgebra.cos(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = + (exp(1im * A) + exp(-1im * A)) / 2 @doc raw""" diag(A::QuantumObject, k::Int=0) @@ -529,17 +509,17 @@ Return the `k`-th diagonal elements of a matrix-type [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ LinearAlgebra.diag( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, + A::QuantumObject{ObjType}, k::Int = 0, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = diag(A.data, k) +) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = diag(A.data, k) @doc raw""" proj(ψ::QuantumObject) Return the projector for a [`Ket`](@ref) or [`Bra`](@ref) type of [`QuantumObject`](@ref) """ -proj(ψ::QuantumObject{<:AbstractArray{T},KetQuantumObject}) where {T} = ψ * ψ' -proj(ψ::QuantumObject{<:AbstractArray{T},BraQuantumObject}) where {T} = ψ' * ψ +proj(ψ::QuantumObject{KetQuantumObject}) = ψ * ψ' +proj(ψ::QuantumObject{BraQuantumObject}) = ψ' * ψ @doc raw""" ptrace(QO::QuantumObject, sel) @@ -588,7 +568,7 @@ Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true 0.0+0.0im 0.5+0.0im ``` """ -function ptrace(QO::QuantumObject{<:AbstractArray,KetQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) +function ptrace(QO::QuantumObject{KetQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) _non_static_array_warning("sel", sel) n_s = length(sel) @@ -609,9 +589,9 @@ function ptrace(QO::QuantumObject{<:AbstractArray,KetQuantumObject}, sel::Union{ return QuantumObject(ρtr, type = Operator, dims = Dimensions(dkeep)) end -ptrace(QO::QuantumObject{<:AbstractArray,BraQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel) +ptrace(QO::QuantumObject{BraQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel) -function ptrace(QO::QuantumObject{<:AbstractArray,OperatorQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) +function ptrace(QO::QuantumObject{OperatorQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) # TODO: support for special cases when some of the subsystems have same `to` and `from` space isa(QO.dimensions, GeneralDimensions) && (get_dimensions_to(QO) != get_dimensions_from(QO)) && @@ -711,18 +691,16 @@ Calculate the purity of a [`QuantumObject`](@ref): ``\textrm{Tr}(\rho^2)`` Note that this function only supports for [`Ket`](@ref), [`Bra`](@ref), and [`Operator`](@ref) """ -purity(ρ::QuantumObject{<:AbstractArray{T},ObjType}) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject}} = - sum(abs2, ρ.data) -purity(ρ::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = real(tr(ρ.data^2)) +purity(ρ::QuantumObject{ObjType}) where {ObjType<:Union{KetQuantumObject,BraQuantumObject}} = sum(abs2, ρ.data) +purity(ρ::QuantumObject{OperatorQuantumObject}) = real(tr(ρ.data^2)) @doc raw""" tidyup(A::QuantumObject, tol::Real=1e-14) Given a [`QuantumObject`](@ref) `A`, check the real and imaginary parts of each element separately. Remove the real or imaginary value if its absolute value is less than `tol`. """ -tidyup(A::QuantumObject{<:AbstractArray{T}}, tol::T2 = 1e-14) where {T,T2<:Real} = - QuantumObject(tidyup(A.data, tol), A.type, A.dimensions) -tidyup(A::AbstractArray{T}, tol::T2 = 1e-14) where {T,T2<:Real} = tidyup!(copy(A), tol) +tidyup(A::QuantumObject, tol::T = 1e-14) where {T<:Real} = QuantumObject(tidyup(A.data, tol), A.type, A.dimensions) +tidyup(A::AbstractArray, tol::T2 = 1e-14) where {T2<:Real} = tidyup!(copy(A), tol) @doc raw""" tidyup!(A::QuantumObject, tol::Real=1e-14) @@ -731,8 +709,8 @@ Given a [`QuantumObject`](@ref) `A`, check the real and imaginary parts of each Note that this function is an in-place version of [`tidyup`](@ref). """ -tidyup!(A::QuantumObject{<:AbstractArray{T}}, tol::T2 = 1e-14) where {T,T2<:Real} = (tidyup!(A.data, tol); A) -function tidyup!(A::AbstractSparseArray{T}, tol::T2 = 1e-14) where {T,T2<:Real} +tidyup!(A::QuantumObject, tol::T = 1e-14) where {T<:Real} = (tidyup!(A.data, tol); A) +function tidyup!(A::AbstractSparseArray, tol::T2 = 1e-14) where {T2<:Real} tidyup!(nonzeros(A), tol) # tidyup A.nzval in-place (also support for CUDA sparse arrays) return dropzeros!(A) end @@ -754,7 +732,7 @@ Get the coherence value ``\alpha`` by measuring the expectation value of the des It returns both ``\alpha`` and the corresponding state with the coherence removed: ``\ket{\delta_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \ket{\psi}`` for a pure state, and ``\hat{\rho_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \hat{\rho} \exp ( -\bar{\alpha} \hat{a} + \alpha \hat{a}^\dagger )`` for a density matrix. These states correspond to the quantum fluctuations around the coherent state ``\ket{\alpha}`` or ``|\alpha\rangle\langle\alpha|``. """ -function get_coherence(ψ::QuantumObject{<:AbstractArray,KetQuantumObject}) +function get_coherence(ψ::QuantumObject{KetQuantumObject}) a = destroy(prod(ψ.dimensions)) α = expect(a, ψ) D = exp(α * a' - conj(α) * a) @@ -762,7 +740,7 @@ function get_coherence(ψ::QuantumObject{<:AbstractArray,KetQuantumObject}) return α, D' * ψ end -function get_coherence(ρ::QuantumObject{<:AbstractArray,OperatorQuantumObject}) +function get_coherence(ρ::QuantumObject{OperatorQuantumObject}) a = destroy(prod(ρ.dimensions)) α = expect(a, ρ) D = exp(α * a' - conj(α) * a) @@ -798,9 +776,9 @@ true It is highly recommended to use `permute(A, order)` with `order` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ function SparseArrays.permute( - A::QuantumObject{<:AbstractArray{T},ObjType}, + A::QuantumObject{ObjType}, order::Union{AbstractVector{Int},Tuple}, -) where {T,ObjType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} +) where {ObjType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} (length(order) != length(A.dimensions)) && throw(ArgumentError("The order list must have the same length as the number of subsystems (A.dims)")) diff --git a/src/qobj/block_diagonal_form.jl b/src/qobj/block_diagonal_form.jl index ca3e89a68..118189492 100644 --- a/src/qobj/block_diagonal_form.jl +++ b/src/qobj/block_diagonal_form.jl @@ -51,8 +51,8 @@ Return the block-diagonal form of a [`QuantumObject`](@ref). This is very useful The [`BlockDiagonalForm`](@ref) of `A`. """ function block_diagonal_form( - A::QuantumObject{DT,OpType}, -) where {DT<:AbstractSparseMatrix,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + A::QuantumObject{OpType}, +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} bdf = block_diagonal_form(A.data) B = QuantumObject(bdf.B, type = A.type, dims = A.dimensions) P = QuantumObject(bdf.P, type = A.type, dims = A.dimensions) diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index c67f8fe43..9723fe980 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -11,7 +11,7 @@ export isunitary Checks if the [`QuantumObject`](@ref) `A` is a [`BraQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isbra(A::QuantumObject) = isbra(typeof(A)) -isbra(::Type{QuantumObject{DT,BraQuantumObject,N}}) where {DT,N} = true +isbra(::Type{QuantumObject{BraQuantumObject,N}}) where {N} = true isbra(A) = false # default case @doc raw""" @@ -20,7 +20,7 @@ isbra(A) = false # default case Checks if the [`QuantumObject`](@ref) `A` is a [`KetQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isket(A::QuantumObject) = isket(typeof(A)) -isket(::Type{QuantumObject{DT,KetQuantumObject,N}}) where {DT,N} = true +isket(::Type{QuantumObject{KetQuantumObject,N}}) where {N} = true isket(A) = false # default case @doc raw""" @@ -29,7 +29,7 @@ isket(A) = false # default case Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`OperatorQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isoper(A::AbstractQuantumObject) = isoper(typeof(A)) -isoper(::Type{<:AbstractQuantumObject{DT,OperatorQuantumObject,N}}) where {DT,N} = true +isoper(::Type{<:AbstractQuantumObject{OperatorQuantumObject,N}}) where {N} = true isoper(A) = false # default case @doc raw""" @@ -38,7 +38,7 @@ isoper(A) = false # default case Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBraQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isoperbra(A::QuantumObject) = isoperbra(typeof(A)) -isoperbra(::Type{QuantumObject{DT,OperatorBraQuantumObject,N}}) where {DT,N} = true +isoperbra(::Type{QuantumObject{OperatorBraQuantumObject,N}}) where {N} = true isoperbra(A) = false # default case @doc raw""" @@ -47,7 +47,7 @@ isoperbra(A) = false # default case Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKetQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isoperket(A::QuantumObject) = isoperket(typeof(A)) -isoperket(::Type{QuantumObject{DT,OperatorKetQuantumObject,N}}) where {DT,N} = true +isoperket(::Type{QuantumObject{OperatorKetQuantumObject,N}}) where {N} = true isoperket(A) = false # default case @doc raw""" @@ -56,7 +56,7 @@ isoperket(A) = false # default case Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`SuperOperatorQuantumObject`](@ref). Default case returns `false` for any other inputs. """ issuper(A::AbstractQuantumObject) = issuper(typeof(A)) -issuper(::Type{<:AbstractQuantumObject{DT,SuperOperatorQuantumObject,N}}) where {DT,N} = true +issuper(::Type{<:AbstractQuantumObject{SuperOperatorQuantumObject,N}}) where {N} = true issuper(A) = false # default case @doc raw""" @@ -91,8 +91,7 @@ Test whether the [`QuantumObject`](@ref) ``U`` is unitary operator. This functio Note that all the keyword arguments will be passed to `Base.isapprox`. """ -isunitary(U::QuantumObject{<:AbstractArray{T}}; kwargs...) where {T} = - isoper(U) ? isapprox(U.data * U.data', I(size(U, 1)); kwargs...) : false +isunitary(U::QuantumObject; kwargs...) = isoper(U) ? isapprox(U.data * U.data', I(size(U, 1)); kwargs...) : false @doc raw""" SciMLOperators.iscached(A::AbstractQuantumObject) diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index cbccbed2d..85823190c 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -45,7 +45,7 @@ julia> λ 1.0 + 0.0im julia> ψ -2-element Vector{QuantumObject{Vector{ComplexF64}, KetQuantumObject, Dimensions{1, Tuple{Space}}}}: +2-element Vector{QuantumObject{KetQuantumObject, Dimensions{1, Tuple{Space}},Vector{ComplexF64}}}: Quantum Object: type=Ket dims=[2] size=(2,) 2-element Vector{ComplexF64}: @@ -273,7 +273,7 @@ Solve for the eigenvalues and eigenvectors of a matrix `A` using the Arnoldi met - `EigsolveResult`: A struct containing the eigenvalues, the eigenvectors, and some information about the eigsolver """ function eigsolve( - A::QuantumObject{<:AbstractMatrix}; + A::QuantumObject; v0::Union{Nothing,AbstractVector} = nothing, sigma::Union{Nothing,Real} = nothing, k::Int = 1, @@ -343,7 +343,7 @@ end @doc raw""" eigsolve_al( - H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, + H::Union{AbstractQuantumObject{HOpType},Tuple}, T::Real, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -382,7 +382,7 @@ Solve the eigenvalue problem for a Liouvillian superoperator `L` using the Arnol - [1] Minganti, F., & Huybrechts, D. (2022). Arnoldi-Lindblad time evolution: Faster-than-the-clock algorithm for the spectrum of time-independent and Floquet open quantum systems. Quantum, 6, 649. """ function eigsolve_al( - H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, + H::Union{AbstractQuantumObject{HOpType},Tuple}, T::Real, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -393,7 +393,7 @@ function eigsolve_al( maxiter::Int = 200, eigstol::Real = 1e-6, kwargs..., -) where {DT1,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L_evo = _mesolve_make_L_QobjEvo(H, c_ops) prob = mesolveProblem( @@ -408,7 +408,7 @@ function eigsolve_al( # prog = ProgressUnknown(desc="Applications:", showspeed = true, enabled=progress) - Lmap = ArnoldiLindbladIntegratorMap(eltype(DT1), size(L_evo), integrator) + Lmap = ArnoldiLindbladIntegratorMap(eltype(H), size(L_evo), integrator) res = _eigsolve(Lmap, mat2vec(ρ0), L_evo.type, L_evo.dimensions, k, krylovdim, maxiter = maxiter, tol = eigstol) # finish!(prog) @@ -458,9 +458,10 @@ true ``` """ function LinearAlgebra.eigen( - A::QuantumObject{MT,OpType}; + A::QuantumObject{OpType}; kwargs..., -) where {MT<:AbstractMatrix,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + MT = typeof(A.data) F = eigen(sparse_to_dense(A.data); kwargs...) # This fixes a type inference issue. But doesn't work for GPU arrays E::mat2vec(sparse_to_dense(MT)) = F.values @@ -475,10 +476,9 @@ end Same as [`eigen(A::QuantumObject; kwargs...)`](@ref) but for only the eigenvalues. """ LinearAlgebra.eigvals( - A::QuantumObject{<:AbstractArray{T},OpType}; + A::QuantumObject{OpType}; kwargs..., -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - eigvals(sparse_to_dense(A.data); kwargs...) +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = eigvals(sparse_to_dense(A.data); kwargs...) @doc raw""" eigenenergies(A::QuantumObject; sparse::Bool=false, kwargs...) @@ -494,10 +494,10 @@ Calculate the eigenenergies - `::Vector{<:Number}`: a list of eigenvalues """ function eigenenergies( - A::QuantumObject{<:AbstractArray{T},OpType}; + A::QuantumObject{OpType}; sparse::Bool = false, kwargs..., -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} if !sparse return eigvals(A; kwargs...) else @@ -519,10 +519,10 @@ Calculate the eigenvalues and corresponding eigenvectors - `::EigsolveResult`: containing the eigenvalues, the eigenvectors, and some information from the solver. see also [`EigsolveResult`](@ref) """ function eigenstates( - A::QuantumObject{<:AbstractArray{T},OpType}; + A::QuantumObject{OpType}; sparse::Bool = false, kwargs..., -) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} if !sparse return eigen(A; kwargs...) else diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index fbc7f3031..8069f6b49 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -12,9 +12,9 @@ export vec2mat, mat2vec Transform the ket state ``\ket{\psi}`` into a pure density matrix ``\hat{\rho} = |\psi\rangle\langle\psi|``. """ -ket2dm(ψ::QuantumObject{<:AbstractArray{T},KetQuantumObject}) where {T} = ψ * ψ' +ket2dm(ψ::QuantumObject{KetQuantumObject}) = ψ * ψ' -ket2dm(ρ::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = ρ +ket2dm(ρ::QuantumObject{OperatorQuantumObject}) = ρ @doc raw""" expect(O::AbstractQuantumObject, ψ::Union{QuantumObject,Vector{QuantumObject}}) @@ -43,43 +43,30 @@ julia> expect(Hermitian(a' * a), ψ) |> round 3.0 ``` """ -function expect( - O::AbstractQuantumObject{DT1,OperatorQuantumObject}, - ψ::QuantumObject{DT2,KetQuantumObject}, -) where {DT1,DT2} +function expect(O::AbstractQuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) return dot(ψ.data, O.data, ψ.data) end +expect(O::AbstractQuantumObject{OperatorQuantumObject}, ψ::QuantumObject{BraQuantumObject}) = expect(O, ψ') +expect(O::QuantumObject{OperatorQuantumObject}, ρ::QuantumObject{OperatorQuantumObject}) = tr(O * ρ) function expect( - O::AbstractQuantumObject{DT1,OperatorQuantumObject}, - ψ::QuantumObject{DT2,BraQuantumObject}, -) where {DT1,DT2} - return expect(O, ψ') -end -function expect( - O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - ρ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, -) where {T1,T2} - return tr(O * ρ) -end -function expect( - O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject}, - ψ::QuantumObject{<:AbstractArray{T2},KetQuantumObject}, -) where {TF<:Number,TR<:Real,T2} + O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, + ψ::QuantumObject{KetQuantumObject}, +) where {DimsType,TF<:Number,TR<:Real} return real(dot(ψ.data, O.data, ψ.data)) end function expect( - O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject}, - ψ::QuantumObject{<:AbstractArray{T2},BraQuantumObject}, -) where {TF<:Number,TR<:Real,T2} + O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, + ψ::QuantumObject{BraQuantumObject}, +) where {DimsType,TF<:Number,TR<:Real} return real(expect(O, ψ')) end function expect( - O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject}, - ρ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, -) where {TF<:Number,TR<:Real,T2} + O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, + ρ::QuantumObject{OperatorQuantumObject}, +) where {DimsType,TF<:Number,TR<:Real} return real(tr(O * ρ)) end -function expect(O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, ρ::Vector{<:QuantumObject}) where {T1} +function expect(O::QuantumObject{OperatorQuantumObject}, ρ::Vector{<:QuantumObject}) _expect = _ρ -> expect(O, _ρ) return _expect.(ρ) end @@ -95,17 +82,15 @@ The function returns a real number if `O` is hermitian, and returns a complex nu Note that `ψ` can also be given as a list of [`QuantumObject`](@ref), it returns a list of expectation values. """ -variance(O::QuantumObject{DT1,OperatorQuantumObject}, ψ::QuantumObject{DT2}) where {DT1,DT2} = - expect(O^2, ψ) - expect(O, ψ)^2 -variance(O::QuantumObject{DT1,OperatorQuantumObject}, ψ::Vector{<:QuantumObject}) where {DT1} = - expect(O^2, ψ) .- expect(O, ψ) .^ 2 +variance(O::QuantumObject{OperatorQuantumObject}, ψ::QuantumObject) = expect(O^2, ψ) - expect(O, ψ)^2 +variance(O::QuantumObject{OperatorQuantumObject}, ψ::Vector{<:QuantumObject}) = expect(O^2, ψ) .- expect(O, ψ) .^ 2 @doc raw""" sparse_to_dense(A::QuantumObject) Converts a sparse QuantumObject to a dense QuantumObject. """ -sparse_to_dense(A::QuantumObject{<:AbstractVecOrMat}) = QuantumObject(sparse_to_dense(A.data), A.type, A.dimensions) +sparse_to_dense(A::QuantumObject) = QuantumObject(sparse_to_dense(A.data), A.type, A.dimensions) sparse_to_dense(A::MT) where {MT<:AbstractSparseArray} = Array(A) for op in (:Transpose, :Adjoint) @eval sparse_to_dense(A::$op{T,<:AbstractSparseMatrix}) where {T<:BlasFloat} = Array(A) @@ -131,8 +116,7 @@ sparse_to_dense(::Type{M}) where {M<:AbstractMatrix} = M Converts a dense QuantumObject to a sparse QuantumObject. """ -dense_to_sparse(A::QuantumObject{<:AbstractVecOrMat}, tol::Real = 1e-10) = - QuantumObject(dense_to_sparse(A.data, tol), A.type, A.dimensions) +dense_to_sparse(A::QuantumObject, tol::Real = 1e-10) = QuantumObject(dense_to_sparse(A.data, tol), A.type, A.dimensions) function dense_to_sparse(A::MT, tol::Real = 1e-10) where {MT<:AbstractMatrix} idxs = findall(@. abs(A) > tol) row_indices = getindex.(idxs, 1) @@ -180,9 +164,9 @@ julia> a.dims, O.dims ``` """ function LinearAlgebra.kron( - A::AbstractQuantumObject{DT1,OpType,<:Dimensions}, - B::AbstractQuantumObject{DT2,OpType,<:Dimensions}, -) where {DT1,DT2,OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} + A::AbstractQuantumObject{OpType,<:Dimensions}, + B::AbstractQuantumObject{OpType,<:Dimensions}, +) where {OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} QType = promote_op_type(A, B) _lazy_tensor_warning(A.data, B.data) return QType(kron(A.data, B.data), A.type, Dimensions((A.dimensions.to..., B.dimensions.to...))) @@ -194,9 +178,9 @@ for ADimType in (:Dimensions, :GeneralDimensions) if !(ADimType == BDimType == :Dimensions) # not for this case because it's already implemented @eval begin function LinearAlgebra.kron( - A::AbstractQuantumObject{DT1,OperatorQuantumObject,<:$ADimType}, - B::AbstractQuantumObject{DT2,OperatorQuantumObject,<:$BDimType}, - ) where {DT1,DT2} + A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, + B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, + ) QType = promote_op_type(A, B) _lazy_tensor_warning(A.data, B.data) return QType( @@ -218,10 +202,7 @@ for AOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) for BOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) if (AOpType != BOpType) @eval begin - function LinearAlgebra.kron( - A::AbstractQuantumObject{DT1,$AOpType}, - B::AbstractQuantumObject{DT2,$BOpType}, - ) where {DT1,DT2} + function LinearAlgebra.kron(A::AbstractQuantumObject{$AOpType}, B::AbstractQuantumObject{$BOpType}) QType = promote_op_type(A, B) _lazy_tensor_warning(A.data, B.data) return QType( @@ -259,16 +240,14 @@ end Convert a quantum object from vector ([`OperatorKetQuantumObject`](@ref)-type) to matrix ([`OperatorQuantumObject`](@ref)-type) """ -vec2mat(A::QuantumObject{<:AbstractArray{T},OperatorKetQuantumObject}) where {T} = - QuantumObject(vec2mat(A.data), Operator, A.dimensions) +vec2mat(A::QuantumObject{OperatorKetQuantumObject}) = QuantumObject(vec2mat(A.data), Operator, A.dimensions) @doc raw""" mat2vec(A::QuantumObject) Convert a quantum object from matrix ([`OperatorQuantumObject`](@ref)-type) to vector ([`OperatorKetQuantumObject`](@ref)-type) """ -mat2vec(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = - QuantumObject(mat2vec(A.data), OperatorKet, A.dimensions) +mat2vec(A::QuantumObject{OperatorQuantumObject}) = QuantumObject(mat2vec(A.data), OperatorKet, A.dimensions) @doc raw""" mat2vec(A::AbstractMatrix) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 9b0e1de5f..2c3e05d9f 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -73,11 +73,8 @@ Return the commutator (or `anti`-commutator) of the two [`QuantumObject`](@ref): Note that `A` and `B` must be [`Operator`](@ref) """ -commutator( - A::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}; - anti::Bool = false, -) where {T1,T2} = A * B - (-1)^anti * B * A +commutator(A::QuantumObject{OperatorQuantumObject}, B::QuantumObject{OperatorQuantumObject}; anti::Bool = false) = + A * B - (-1)^anti * B * A @doc raw""" destroy(N::Int) @@ -481,10 +478,9 @@ Note that we put ``\hat{\sigma}_{-} = \begin{pmatrix} 0 & 0 \\ 1 & 0 \end{pmatri """ fcreate(N::Union{Int,Val}, j::Int) = _Jordan_Wigner(N, j, sigmam()) -_Jordan_Wigner(N::Int, j::Int, op::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = - _Jordan_Wigner(Val(N), j, op) +_Jordan_Wigner(N::Int, j::Int, op::QuantumObject{OperatorQuantumObject}) = _Jordan_Wigner(Val(N), j, op) -function _Jordan_Wigner(::Val{N}, j::Int, op::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {N,T} +function _Jordan_Wigner(::Val{N}, j::Int, op::QuantumObject{OperatorQuantumObject}) where {N} (N < 1) && throw(ArgumentError("The total number of sites (N) cannot be less than 1")) ((j > N) || (j < 1)) && throw(ArgumentError("The site index (j) should satisfy: 1 ≤ j ≤ N")) diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index a2ba3ffdf..7198f49fc 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -9,7 +9,7 @@ It also implements the fundamental functions in Julia standard library: export QuantumObject @doc raw""" - struct QuantumObject{DataType<:AbstractArray,ObjType<:QuantumObjectType,DimType<:AbstractDimensions} <: AbstractQuantumObject{DataType,ObjType,DimType} + struct QuantumObject{ObjType<:QuantumObjectType,DimType<:AbstractDimensions,DataType<:AbstractArray} <: AbstractQuantumObject{ObjType,DimType,DataType} data::DataType type::ObjType dimensions::DimType @@ -44,19 +44,19 @@ julia> a.dimensions Dimensions{1, Tuple{Space}}((Space(20),)) ``` """ -struct QuantumObject{DataType<:AbstractArray,ObjType<:QuantumObjectType,DimType<:AbstractDimensions} <: - AbstractQuantumObject{DataType,ObjType,DimType} +struct QuantumObject{ObjType<:QuantumObjectType,DimType<:AbstractDimensions,DataType<:AbstractArray} <: + AbstractQuantumObject{ObjType,DimType,DataType} data::DataType type::ObjType dimensions::DimType - function QuantumObject(data::MT, type::ObjType, dims) where {MT<:AbstractArray,ObjType<:QuantumObjectType} + function QuantumObject(data::DT, type::ObjType, dims) where {DT<:AbstractArray,ObjType<:QuantumObjectType} dimensions = _gen_dimensions(dims) _size = _get_size(data) _check_QuantumObject(type, dimensions, _size[1], _size[2]) - return new{MT,ObjType,typeof(dimensions)}(data, type, dimensions) + return new{ObjType,typeof(dimensions),DT}(data, type, dimensions) end end @@ -132,12 +132,8 @@ function QuantumObject( throw(DomainError(size(A), "The size of the array is not compatible with vector or matrix.")) end -function QuantumObject( - A::QuantumObject{<:AbstractArray{T,N}}; - type::ObjType = A.type, - dims = A.dimensions, -) where {T,N,ObjType<:QuantumObjectType} - _size = N == 1 ? (length(A), 1) : size(A) +function QuantumObject(A::QuantumObject; type::ObjType = A.type, dims = A.dimensions) where {ObjType<:QuantumObjectType} + _size = isa(A.data, AbstractVector) ? (length(A), 1) : size(A) dimensions = _gen_dimensions(dims) _check_QuantumObject(type, dimensions, _size[1], _size[2]) return QuantumObject(copy(A.data), type, dimensions) @@ -145,9 +141,8 @@ end function Base.show( io::IO, - QO::QuantumObject{<:AbstractArray{T},OpType}, + QO::QuantumObject{OpType}, ) where { - T, OpType<:Union{ BraQuantumObject, KetQuantumObject, @@ -188,14 +183,13 @@ end Base.real(x::QuantumObject) = QuantumObject(real(x.data), x.type, x.dimensions) Base.imag(x::QuantumObject) = QuantumObject(imag(x.data), x.type, x.dimensions) -SparseArrays.sparse(A::QuantumObject{<:AbstractArray{T}}) where {T} = - QuantumObject(sparse(A.data), A.type, A.dimensions) -SparseArrays.nnz(A::QuantumObject{<:AbstractSparseArray}) = nnz(A.data) -SparseArrays.nonzeros(A::QuantumObject{<:AbstractSparseArray}) = nonzeros(A.data) -SparseArrays.rowvals(A::QuantumObject{<:AbstractSparseArray}) = rowvals(A.data) -SparseArrays.droptol!(A::QuantumObject{<:AbstractSparseArray}, tol::Real) = (droptol!(A.data, tol); return A) -SparseArrays.dropzeros(A::QuantumObject{<:AbstractSparseArray}) = QuantumObject(dropzeros(A.data), A.type, A.dimensions) -SparseArrays.dropzeros!(A::QuantumObject{<:AbstractSparseArray}) = (dropzeros!(A.data); return A) +SparseArrays.sparse(A::QuantumObject) = QuantumObject(sparse(A.data), A.type, A.dimensions) +SparseArrays.nnz(A::QuantumObject) = nnz(A.data) +SparseArrays.nonzeros(A::QuantumObject) = nonzeros(A.data) +SparseArrays.rowvals(A::QuantumObject) = rowvals(A.data) +SparseArrays.droptol!(A::QuantumObject, tol::Real) = (droptol!(A.data, tol); return A) +SparseArrays.dropzeros(A::QuantumObject) = QuantumObject(dropzeros(A.data), A.type, A.dimensions) +SparseArrays.dropzeros!(A::QuantumObject) = (dropzeros!(A.data); return A) @doc raw""" SciMLOperators.cached_operator(L::AbstractQuantumObject, u) @@ -208,17 +202,15 @@ Here, `u` can be in either the following types: - [`OperatorKet`](@ref)-type [`QuantumObject`](@ref) (if `L` is a [`SuperOperator`](@ref)) """ SciMLOperators.cache_operator( - L::AbstractQuantumObject{DT,OpType}, + L::AbstractQuantumObject{OpType}, u::AbstractVector, -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = get_typename_wrapper(L)(cache_operator(L.data, sparse_to_dense(similar(u))), L.type, L.dimensions) function SciMLOperators.cache_operator( - L::AbstractQuantumObject{DT1,OpType}, - u::QuantumObject{DT2,SType}, + L::AbstractQuantumObject{OpType}, + u::QuantumObject{SType}, ) where { - DT1, - DT2, OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, SType<:Union{KetQuantumObject,OperatorKetQuantumObject}, } @@ -233,17 +225,13 @@ function SciMLOperators.cache_operator( end # data type conversions -Base.Vector(A::QuantumObject{<:AbstractVector}) = QuantumObject(Vector(A.data), A.type, A.dimensions) -Base.Vector{T}(A::QuantumObject{<:AbstractVector}) where {T<:Number} = - QuantumObject(Vector{T}(A.data), A.type, A.dimensions) -Base.Matrix(A::QuantumObject{<:AbstractMatrix}) = QuantumObject(Matrix(A.data), A.type, A.dimensions) -Base.Matrix{T}(A::QuantumObject{<:AbstractMatrix}) where {T<:Number} = - QuantumObject(Matrix{T}(A.data), A.type, A.dimensions) -SparseArrays.SparseVector(A::QuantumObject{<:AbstractVector}) = - QuantumObject(SparseVector(A.data), A.type, A.dimensions) -SparseArrays.SparseVector{T}(A::QuantumObject{<:SparseVector}) where {T<:Number} = +Base.Vector(A::QuantumObject) = QuantumObject(Vector(A.data), A.type, A.dimensions) +Base.Vector{T}(A::QuantumObject) where {T<:Number} = QuantumObject(Vector{T}(A.data), A.type, A.dimensions) +Base.Matrix(A::QuantumObject) = QuantumObject(Matrix(A.data), A.type, A.dimensions) +Base.Matrix{T}(A::QuantumObject) where {T<:Number} = QuantumObject(Matrix{T}(A.data), A.type, A.dimensions) +SparseArrays.SparseVector(A::QuantumObject) = QuantumObject(SparseVector(A.data), A.type, A.dimensions) +SparseArrays.SparseVector{T}(A::QuantumObject) where {T<:Number} = QuantumObject(SparseVector{T}(A.data), A.type, A.dimensions) -SparseArrays.SparseMatrixCSC(A::QuantumObject{<:AbstractMatrix}) = - QuantumObject(SparseMatrixCSC(A.data), A.type, A.dimensions) -SparseArrays.SparseMatrixCSC{T}(A::QuantumObject{<:SparseMatrixCSC}) where {T<:Number} = +SparseArrays.SparseMatrixCSC(A::QuantumObject) = QuantumObject(SparseMatrixCSC(A.data), A.type, A.dimensions) +SparseArrays.SparseMatrixCSC{T}(A::QuantumObject) where {T<:Number} = QuantumObject(SparseMatrixCSC{T}(A.data), A.type, A.dimensions) diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index cec0992b3..da9deeb86 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -14,7 +14,7 @@ export QuantumObjectType, export Bra, Ket, Operator, OperatorBra, OperatorKet, SuperOperator @doc raw""" - abstract type AbstractQuantumObject{DataType,ObjType,DimType} + abstract type AbstractQuantumObject{ObjType,DimType,DataType} Abstract type for all quantum objects like [`QuantumObject`](@ref) and [`QuantumObjectEvolution`](@ref). @@ -24,7 +24,7 @@ julia> sigmax() isa AbstractQuantumObject true ``` """ -abstract type AbstractQuantumObject{DataType,ObjType,DimType} end +abstract type AbstractQuantumObject{ObjType,DimType,DataType} end abstract type QuantumObjectType end @@ -263,25 +263,24 @@ function Base.getproperty(A::AbstractQuantumObject, key::Symbol) end # this returns `to` in GeneralDimensions representation -get_dimensions_to(A::AbstractQuantumObject{DT,KetQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to -get_dimensions_to(A::AbstractQuantumObject{DT,BraQuantumObject,<:Dimensions{N}}) where {DT,N} = space_one_list(N) -get_dimensions_to(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to -get_dimensions_to(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:GeneralDimensions{N}}) where {DT,N} = - A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{KetQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{BraQuantumObject,<:Dimensions{N}}) where {N} = space_one_list(N) +get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions{N}}) where {N} = A.dimensions.to get_dimensions_to( - A::AbstractQuantumObject{DT,ObjType,<:Dimensions{N}}, -) where {DT,ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = + A::AbstractQuantumObject{ObjType,<:Dimensions{N}}, +) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = A.dimensions.to # this returns `from` in GeneralDimensions representation -get_dimensions_from(A::AbstractQuantumObject{DT,KetQuantumObject,<:Dimensions{N}}) where {DT,N} = space_one_list(N) -get_dimensions_from(A::AbstractQuantumObject{DT,BraQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to -get_dimensions_from(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:Dimensions{N}}) where {DT,N} = A.dimensions.to -get_dimensions_from(A::AbstractQuantumObject{DT,OperatorQuantumObject,<:GeneralDimensions{N}}) where {DT,N} = +get_dimensions_from(A::AbstractQuantumObject{KetQuantumObject,<:Dimensions{N}}) where {N} = space_one_list(N) +get_dimensions_from(A::AbstractQuantumObject{BraQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions{N}}) where {N} = A.dimensions.from get_dimensions_from( - A::AbstractQuantumObject{DT,ObjType,<:Dimensions{N}}, -) where {DT,ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = + A::AbstractQuantumObject{ObjType,<:Dimensions{N}}, +) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = A.dimensions.to # functions for getting Float or Complex element type diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index f055c61ae..5cd4d828b 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -5,7 +5,7 @@ This file defines the QuantumObjectEvolution (QobjEvo) structure. export QuantumObjectEvolution @doc raw""" - struct QuantumObjectEvolution{DataType<:AbstractSciMLOperator,ObjType<:QuantumObjectType,DimType<:AbstractDimensions} <: AbstractQuantumObject{DataType,ObjType,DimType} + struct QuantumObjectEvolution{ObjType<:QuantumObjectType,DimType<:AbstractDimensions,DataType<:AbstractSciMLOperator} <: AbstractQuantumObject{ObjType,DimType,DataType} data::DataType type::ObjType dimensions::DimType @@ -109,10 +109,10 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ``` """ struct QuantumObjectEvolution{ - DataType<:AbstractSciMLOperator, ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, DimType<:AbstractDimensions, -} <: AbstractQuantumObject{DataType,ObjType,DimType} + DataType<:AbstractSciMLOperator, +} <: AbstractQuantumObject{ObjType,DimType,DataType} data::DataType type::ObjType dimensions::DimType @@ -130,7 +130,7 @@ struct QuantumObjectEvolution{ _size = _get_size(data) _check_QuantumObject(type, dimensions, _size[1], _size[2]) - return new{DT,ObjType,typeof(dimensions)}(data, type, dimensions) + return new{ObjType,typeof(dimensions),DT}(data, type, dimensions) end end @@ -503,11 +503,11 @@ true ``` """ function (A::QuantumObjectEvolution)( - ψout::QuantumObject{DT1,QobjType}, - ψin::QuantumObject{DT2,QobjType}, + ψout::QuantumObject{QobjType}, + ψin::QuantumObject{QobjType}, p, t, -) where {DT1,DT2,QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} +) where {QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} check_dimensions(A, ψout, ψin) if isoper(A) && isoperket(ψin) @@ -531,10 +531,10 @@ end Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the input state `ψ` at time `t` with parameters `p`. Out-of-place version of [`(A::QuantumObjectEvolution)(ψout, ψin, p, t)`](@ref). The output state is stored in a new [`QuantumObject`](@ref) object. This function mimics the behavior of a `AbstractSciMLOperator` object. """ function (A::QuantumObjectEvolution)( - ψ::QuantumObject{DT,QobjType}, + ψ::QuantumObject{QobjType}, p, t, -) where {DT,QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} +) where {QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} ψout = QuantumObject(similar(ψ.data), ψ.type, ψ.dimensions) return A(ψout, ψ, p, t) end diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 85c942bd9..7fe381478 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -77,7 +77,7 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spost`](@ref) and [`sprepost`](@ref). """ -spre(A::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {DT} = +spre(A::AbstractQuantumObject{OperatorQuantumObject}, Id_cache = I(size(A, 1))) = get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator, A.dimensions) @doc raw""" @@ -96,7 +96,7 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref) and [`sprepost`](@ref). """ -spost(B::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {DT} = +spost(B::AbstractQuantumObject{OperatorQuantumObject}, Id_cache = I(size(B, 1))) = get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator, B.dimensions) @doc raw""" @@ -113,10 +113,7 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r See also [`spre`](@ref) and [`spost`](@ref). """ -function sprepost( - A::AbstractQuantumObject{DT1,OperatorQuantumObject}, - B::AbstractQuantumObject{DT2,OperatorQuantumObject}, -) where {DT1,DT2} +function sprepost(A::AbstractQuantumObject{OperatorQuantumObject}, B::AbstractQuantumObject{OperatorQuantumObject}) check_dimensions(A, B) return promote_op_type(A, B)(_sprepost(A.data, B.data), SuperOperator, A.dimensions) end @@ -135,11 +132,11 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref), [`spost`](@ref), and [`sprepost`](@ref). """ -lindblad_dissipator(O::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(O, 1))) where {DT} = +lindblad_dissipator(O::AbstractQuantumObject{OperatorQuantumObject}, Id_cache = I(size(O, 1))) = get_typename_wrapper(O)(_lindblad_dissipator(O.data, Id_cache), SuperOperator, O.dimensions) # It is already a SuperOperator -lindblad_dissipator(O::AbstractQuantumObject{DT,SuperOperatorQuantumObject}, Id_cache = nothing) where {DT} = O +lindblad_dissipator(O::AbstractQuantumObject{SuperOperatorQuantumObject}, Id_cache = nothing) = O @doc raw""" liouvillian(H::AbstractQuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dimensions))) @@ -161,10 +158,10 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref). """ function liouvillian( - H::AbstractQuantumObject{DT,OpType}, + H::AbstractQuantumObject{OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, Id_cache = I(prod(H.dimensions)), -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, Id_cache) if !(c_ops isa Nothing) # sum all the (time-independent) c_ops first @@ -182,7 +179,7 @@ function liouvillian( return L end -liouvillian(H::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dimensions))) where {DT} = +liouvillian(H::AbstractQuantumObject{OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dimensions))) = get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator, H.dimensions) -liouvillian(H::AbstractQuantumObject{DT,SuperOperatorQuantumObject}, Id_cache::Diagonal) where {DT} = H +liouvillian(H::AbstractQuantumObject{SuperOperatorQuantumObject}, Id_cache::Diagonal) = H diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 7f00dbc13..aa5f72f26 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -59,7 +59,7 @@ Matrix square root of [`Operator`](@ref) type of [`QuantumObject`](@ref) Note that for other types of [`QuantumObject`](@ref) use `sprt(A)` instead. """ -sqrtm(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}) where {T} = sqrt(A) +sqrtm(A::QuantumObject{OperatorQuantumObject}) = sqrt(A) @doc raw""" logm(A::QuantumObject) @@ -68,9 +68,7 @@ Matrix logarithm of [`QuantumObject`](@ref) Note that this function is same as `log(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -logm( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = log(A) +logm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = log(A) @doc raw""" expm(A::QuantumObject) @@ -79,9 +77,7 @@ Matrix exponential of [`QuantumObject`](@ref) Note that this function is same as `exp(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -expm( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = exp(A) +expm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = exp(A) @doc raw""" sinm(A::QuantumObject) @@ -92,9 +88,7 @@ Matrix sine of [`QuantumObject`](@ref), defined as Note that this function is same as `sin(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -sinm( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = sin(A) +sinm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = sin(A) @doc raw""" cosm(A::QuantumObject) @@ -105,6 +99,4 @@ Matrix cosine of [`QuantumObject`](@ref), defined as Note that this function is same as `cos(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -cosm( - A::QuantumObject{<:AbstractMatrix{T},ObjType}, -) where {T,ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = cos(A) +cosm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = cos(A) diff --git a/src/spectrum.jl b/src/spectrum.jl index af4d08b1a..77b690976 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -30,8 +30,8 @@ PseudoInverse(; alg::SciMLLinearSolveAlgorithm = KrylovJL_GMRES()) = PseudoInver spectrum(H::QuantumObject, ωlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}; solver::SpectrumSolver=ExponentialSeries(), kwargs...) @@ -46,14 +46,14 @@ See also the following list for `SpectrumSolver` docstrings: - [`PseudoInverse`](@ref) """ function spectrum( - H::QuantumObject{MT1,HOpType}, + H::QuantumObject{HOpType}, ωlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}; + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}; solver::SpectrumSolver = ExponentialSeries(), kwargs..., -) where {MT1<:AbstractMatrix,T2,T3,HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} return _spectrum(liouvillian(H, c_ops), ωlist, A, B, solver; kwargs...) end @@ -77,13 +77,13 @@ function _spectrum_get_rates_vecs_ss(L, solver::ExponentialSeries{T,false}) wher end function _spectrum( - L::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, + L::QuantumObject{SuperOperatorQuantumObject}, ωlist::AbstractVector, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, solver::ExponentialSeries; kwargs..., -) where {T1,T2,T3} +) check_dimensions(L, A, B) rates, vecs, ρss = _spectrum_get_rates_vecs_ss(L, solver) @@ -103,13 +103,13 @@ function _spectrum( end function _spectrum( - L::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, + L::QuantumObject{SuperOperatorQuantumObject}, ωlist::AbstractVector, - A::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject}, - B::QuantumObject{<:AbstractArray{T3},OperatorQuantumObject}, + A::QuantumObject{OperatorQuantumObject}, + B::QuantumObject{OperatorQuantumObject}, solver::PseudoInverse; kwargs..., -) where {T1,T2,T3} +) check_dimensions(L, A, B) ωList = convert(Vector{_FType(L)}, ωlist) # Convert it to support GPUs and avoid type instabilities diff --git a/src/steadystate.jl b/src/steadystate.jl index 43b23a2c3..a88ef8bf5 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -65,7 +65,7 @@ end @doc raw""" steadystate( - H::QuantumObject{DT,OpType}, + H::QuantumObject{OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateSolver = SteadyStateDirectSolver(), kwargs..., @@ -80,21 +80,17 @@ Solve the stationary state based on different solvers. - `kwargs`: The keyword arguments for the solver. """ function steadystate( - H::QuantumObject{DT,OpType}, + H::QuantumObject{OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateSolver = SteadyStateDirectSolver(), kwargs..., -) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, c_ops) return _steadystate(L, solver; kwargs...) end -function _steadystate( - L::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, - solver::SteadyStateLinearSolver; - kwargs..., -) where {T} +function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateLinearSolver; kwargs...) L_tmp = L.data N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) @@ -129,11 +125,7 @@ function _steadystate( return QuantumObject(ρss, Operator, L.dimensions) end -function _steadystate( - L::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, - solver::SteadyStateEigenSolver; - kwargs..., -) where {T} +function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateEigenSolver; kwargs...) N = prod(L.dimensions) kwargs = merge((sigma = 1e-8, k = 1), (; kwargs...)) @@ -145,10 +137,7 @@ function _steadystate( return QuantumObject(ρss, Operator, L.dimensions) end -function _steadystate( - L::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, - solver::SteadyStateDirectSolver, -) where {T} +function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateDirectSolver) L_tmp = L.data N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) @@ -173,11 +162,7 @@ function _steadystate( return QuantumObject(ρss, Operator, L.dimensions) end -_steadystate( - L::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, - solver::SteadyStateODESolver; - kwargs..., -) where {T} = throw( +_steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateODESolver; kwargs...) = throw( ArgumentError( "The initial state ψ0 is required for SteadyStateODESolver, use the following call instead: `steadystate(H, ψ0, tmax, c_ops)`.", ), @@ -185,8 +170,8 @@ _steadystate( @doc raw""" steadystate( - H::QuantumObject{DT1,HOpType}, - ψ0::QuantumObject{DT2,StateOpType}, + H::QuantumObject{HOpType}, + ψ0::QuantumObject{StateOpType}, tmax::Real = Inf, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateODESolver = SteadyStateODESolver(), @@ -220,8 +205,8 @@ or - `kwargs`: The keyword arguments for the ODEProblem. """ function steadystate( - H::QuantumObject{DT1,HOpType}, - ψ0::QuantumObject{DT2,StateOpType}, + H::QuantumObject{HOpType}, + ψ0::QuantumObject{StateOpType}, tmax::Real = Inf, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateODESolver = SteadyStateODESolver(), @@ -229,8 +214,6 @@ function steadystate( abstol::Real = 1.0e-10, kwargs..., ) where { - DT1, - DT2, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } @@ -265,9 +248,9 @@ end @doc raw""" steadystate_floquet( - H_0::QuantumObject{MT,OpType1}, - H_p::QuantumObject{<:AbstractArray,OpType2}, - H_m::QuantumObject{<:AbstractArray,OpType3}, + H_0::QuantumObject{OpType1}, + H_p::QuantumObject{OpType2}, + H_m::QuantumObject{OpType3}, ωd::Number, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Integer = 2, @@ -343,9 +326,9 @@ In the case of `SSFloquetEffectiveLiouvillian`, instead, the effective Liouvilli - `kwargs...`: Additional keyword arguments to be passed to the solver. """ function steadystate_floquet( - H_0::QuantumObject{MT,OpType1}, - H_p::QuantumObject{<:AbstractArray,OpType2}, - H_m::QuantumObject{<:AbstractArray,OpType3}, + H_0::QuantumObject{OpType1}, + H_p::QuantumObject{OpType2}, + H_m::QuantumObject{OpType3}, ωd::Number, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Integer = 2, @@ -353,7 +336,6 @@ function steadystate_floquet( solver::FSolver = SSFloquetLinearSystem(), kwargs..., ) where { - MT<:AbstractArray, OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, OpType2<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, OpType3<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, @@ -367,15 +349,18 @@ function steadystate_floquet( end function _steadystate_floquet( - L_0::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, - L_p::QuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, - L_m::QuantumObject{<:AbstractArray{T3},SuperOperatorQuantumObject}, + L_0::QuantumObject{SuperOperatorQuantumObject}, + L_p::QuantumObject{SuperOperatorQuantumObject}, + L_m::QuantumObject{SuperOperatorQuantumObject}, ωd::Number, solver::SSFloquetLinearSystem; n_max::Integer = 1, tol::R = 1e-8, kwargs..., -) where {T1,T2,T3,R<:Real} +) where {R<:Real} + T1 = eltype(L_0) + T2 = eltype(L_p) + T3 = eltype(L_m) T = promote_type(T1, T2, T3) L_0_mat = get_data(L_0) @@ -425,9 +410,9 @@ function _steadystate_floquet( end function _steadystate_floquet( - L_0::QuantumObject{<:AbstractArray,SuperOperatorQuantumObject}, - L_p::QuantumObject{<:AbstractArray,SuperOperatorQuantumObject}, - L_m::QuantumObject{<:AbstractArray,SuperOperatorQuantumObject}, + L_0::QuantumObject{SuperOperatorQuantumObject}, + L_p::QuantumObject{SuperOperatorQuantumObject}, + L_m::QuantumObject{SuperOperatorQuantumObject}, ωd::Number, solver::SSFloquetEffectiveLiouvillian; n_max::Integer = 1, diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index f8a62cb8f..bddcc40bf 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -366,9 +366,9 @@ get_B(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, ( @doc raw""" lr_mesolveProblem( - H::QuantumObject{DT1,OperatorQuantumObject}, - z::AbstractArray{T2,2}, - B::AbstractArray{T2,2}, + H::QuantumObject{OperatorQuantumObject}, + z::AbstractArray{T,2}, + B::AbstractArray{T,2}, tlist::AbstractVector, c_ops::Union{AbstractVector,Tuple}=(); e_ops::Union{AbstractVector,Tuple}=(), @@ -391,16 +391,16 @@ Formulates the ODEproblem for the low-rank time evolution of the system. The fun - `kwargs`: Additional keyword arguments. """ function lr_mesolveProblem( - H::QuantumObject{DT1,OperatorQuantumObject}, - z::AbstractArray{T2,2}, - B::AbstractArray{T2,2}, + H::QuantumObject{OperatorQuantumObject}, + z::AbstractArray{T,2}, + B::AbstractArray{T,2}, tlist::AbstractVector, c_ops::Union{AbstractVector,Tuple} = (); e_ops::Union{AbstractVector,Tuple} = (), f_ops::Union{AbstractVector,Tuple} = (), opt::NamedTuple = lr_mesolve_options_default, kwargs..., -) where {DT1,T2} +) where {T} Hdims = H.dimensions # Formulation of problem @@ -497,9 +497,9 @@ end @doc raw""" lr_mesolve( - H::QuantumObject{DT1,OperatorQuantumObject}, - z::AbstractArray{T2,2}, - B::AbstractArray{T2,2}, + H::QuantumObject{OperatorQuantumObject}, + z::AbstractArray{T,2}, + B::AbstractArray{T,2}, tlist::AbstractVector, c_ops::Union{AbstractVector,Tuple}=(); e_ops::Union{AbstractVector,Tuple}=(), @@ -522,7 +522,7 @@ Time evolution of an open quantum system using the low-rank master equation. For - `kwargs`: Additional keyword arguments. """ function lr_mesolve( - H::QuantumObject{DT1,OperatorQuantumObject}, + H::QuantumObject{OperatorQuantumObject}, z::AbstractArray{T2,2}, B::AbstractArray{T2,2}, tlist::AbstractVector, @@ -531,7 +531,7 @@ function lr_mesolve( f_ops::Union{AbstractVector,Tuple} = (), opt::NamedTuple = lr_mesolve_options_default, kwargs..., -) where {DT1,T2} +) where {T2} prob = lr_mesolveProblem(H, z, B, tlist, c_ops; e_ops = e_ops, f_ops = f_ops, opt = opt, kwargs...) return lr_mesolve(prob; kwargs...) end diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 99b5d3fad..de37de3d1 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -82,8 +82,8 @@ end @doc raw""" mcsolveProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -151,8 +151,8 @@ If the environmental measurements register a quantum jump, the wave function und - `prob`: The [`TimeEvolutionProblem`](@ref) containing the `ODEProblem` for the Monte Carlo wave function time evolution. """ function mcsolveProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -160,7 +160,7 @@ function mcsolveProblem( rng::AbstractRNG = default_rng(), jump_callback::TJC = ContinuousLindbladJumpCallback(), kwargs..., -) where {DT1,DT2,TJC<:LindbladJumpCallbackType} +) where {TJC<:LindbladJumpCallbackType} haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) @@ -186,8 +186,8 @@ end @doc raw""" mcsolveEnsembleProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -265,8 +265,8 @@ If the environmental measurements register a quantum jump, the wave function und - `prob`: The [`TimeEvolutionProblem`](@ref) containing the Ensemble `ODEProblem` for the Monte Carlo wave function time evolution. """ function mcsolveEnsembleProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -279,7 +279,7 @@ function mcsolveEnsembleProblem( prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, kwargs..., -) where {DT1,DT2,TJC<:LindbladJumpCallbackType} +) where {TJC<:LindbladJumpCallbackType} _prob_func = prob_func isa Nothing ? _mcsolve_dispatch_prob_func(rng, ntraj, tlist) : prob_func _output_func = output_func isa Nothing ? _mcsolve_dispatch_output_func(ensemble_method, progress_bar, ntraj) : output_func @@ -308,8 +308,8 @@ end @doc raw""" mcsolve( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -393,8 +393,8 @@ If the environmental measurements register a quantum jump, the wave function und - `sol::TimeEvolutionMCSol`: The solution of the time evolution. See also [`TimeEvolutionMCSol`](@ref). """ function mcsolve( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -409,7 +409,7 @@ function mcsolve( output_func::Union{Tuple,Nothing} = nothing, normalize_states::Union{Val,Bool} = Val(true), kwargs..., -) where {DT1,DT2,TJC<:LindbladJumpCallbackType} +) where {TJC<:LindbladJumpCallbackType} ens_prob_mc = mcsolveEnsembleProblem( H, ψ0, diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index d119785b3..b7eb62886 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -5,8 +5,8 @@ _mesolve_make_L_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}, c_ops) = liouvil @doc raw""" mesolveProblem( - H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, - ψ0::QuantumObject{DT2,StateOpType}, + H::Union{AbstractQuantumObject{HOpType},Tuple}, + ψ0::QuantumObject{StateOpType}, tlist, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -52,8 +52,8 @@ where - `prob::ODEProblem`: The ODEProblem for the master equation time evolution. """ function mesolveProblem( - H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, - ψ0::QuantumObject{DT2,StateOpType}, + H::Union{AbstractQuantumObject{HOpType},Tuple}, + ψ0::QuantumObject{StateOpType}, tlist, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -62,8 +62,6 @@ function mesolveProblem( inplace::Union{Val,Bool} = Val(true), kwargs..., ) where { - DT1, - DT2, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } @@ -94,8 +92,8 @@ end @doc raw""" mesolve( - H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, - ψ0::QuantumObject{DT2,StateOpType}, + H::Union{AbstractQuantumObject{HOpType},Tuple}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -144,8 +142,8 @@ where - `sol::TimeEvolutionSol`: The solution of the time evolution. See also [`TimeEvolutionSol`](@ref) """ function mesolve( - H::Union{AbstractQuantumObject{DT1,HOpType},Tuple}, - ψ0::QuantumObject{DT2,StateOpType}, + H::Union{AbstractQuantumObject{HOpType},Tuple}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -155,8 +153,6 @@ function mesolve( inplace::Union{Val,Bool} = Val(true), kwargs..., ) where { - DT1, - DT2, HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, } diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 341a2cc55..0a25d4114 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -7,8 +7,8 @@ _sesolve_make_U_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}) = QobjEvo(H, -1i @doc raw""" sesolveProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), @@ -46,15 +46,15 @@ Generate the ODEProblem for the Schrödinger time evolution of a quantum system: - `prob`: The [`TimeEvolutionProblem`](@ref) containing the `ODEProblem` for the Schrödinger time evolution of the system. """ function sesolveProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), progress_bar::Union{Val,Bool} = Val(true), inplace::Union{Val,Bool} = Val(true), kwargs..., -) where {DT1,DT2} +) haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) @@ -83,8 +83,8 @@ end @doc raw""" sesolve( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -125,8 +125,8 @@ Time evolution of a closed quantum system using the Schrödinger equation: - `sol::TimeEvolutionSol`: The solution of the time evolution. See also [`TimeEvolutionSol`](@ref) """ function sesolve( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -134,7 +134,7 @@ function sesolve( progress_bar::Union{Val,Bool} = Val(true), inplace::Union{Val,Bool} = Val(true), kwargs..., -) where {DT1,DT2} +) prob = sesolveProblem( H, ψ0, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index cdd2de9bb..d0cee3721 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -90,8 +90,8 @@ _ScalarOperator_e2_2(op, f = +) = @doc raw""" ssesolveProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -146,8 +146,8 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener in - `prob`: The `SDEProblem` for the Stochastic Schrödinger time evolution of the system. """ function ssesolveProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -155,7 +155,7 @@ function ssesolveProblem( rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {DT1,DT2} +) haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) @@ -204,8 +204,8 @@ end @doc raw""" ssesolveEnsembleProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -269,8 +269,8 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener i - `prob::EnsembleProblem with SDEProblem`: The Ensemble SDEProblem for the Stochastic Shrödinger time evolution. """ function ssesolveEnsembleProblem( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -282,7 +282,7 @@ function ssesolveEnsembleProblem( output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {DT1,DT2} +) progr = ProgressBar(ntraj, enable = getVal(progress_bar)) if ensemble_method isa EnsembleDistributed progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) @@ -324,8 +324,8 @@ end @doc raw""" ssesolve( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), @@ -394,8 +394,8 @@ Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener in - `sol::TimeEvolutionSSESol`: The solution of the time evolution. See also [`TimeEvolutionSSESol`](@ref). """ function ssesolve( - H::Union{AbstractQuantumObject{DT1,OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{DT2,KetQuantumObject}, + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), @@ -408,7 +408,7 @@ function ssesolve( output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) where {DT1,DT2} +) ens_prob = ssesolveEnsembleProblem( H, ψ0, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index ec429b1d1..75f385e34 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -215,29 +215,26 @@ ContinuousLindbladJumpCallback(; interp_points::Int = 10) = ContinuousLindbladJu ####################################### function liouvillian_floquet( - L₀::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, - Lₚ::QuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, - Lₘ::QuantumObject{<:AbstractArray{T3},SuperOperatorQuantumObject}, + L₀::QuantumObject{SuperOperatorQuantumObject}, + Lₚ::QuantumObject{SuperOperatorQuantumObject}, + Lₘ::QuantumObject{SuperOperatorQuantumObject}, ω::Real; n_max::Int = 3, tol::Real = 1e-15, -) where {T1,T2,T3} +) check_dimensions(L₀, Lₚ, Lₘ) return _liouvillian_floquet(L₀, Lₚ, Lₘ, ω, n_max, tol) end function liouvillian_floquet( - H::QuantumObject{<:AbstractArray{T1},OpType1}, - Hₚ::QuantumObject{<:AbstractArray{T2},OpType2}, - Hₘ::QuantumObject{<:AbstractArray{T3},OpType3}, + H::QuantumObject{OpType1}, + Hₚ::QuantumObject{OpType2}, + Hₘ::QuantumObject{OpType3}, ω::Real, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Int = 3, tol::Real = 1e-15, ) where { - T1, - T2, - T3, OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, OpType2<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, OpType3<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, @@ -254,13 +251,13 @@ Constructs the generalized Liouvillian for a system coupled to a bath of harmoni See, e.g., Settineri, Alessio, et al. "Dissipation and thermal noise in hybrid quantum systems in the ultrastrong-coupling regime." Physical Review A 98.5 (2018): 053834. """ function liouvillian_generalized( - H::QuantumObject{MT,OperatorQuantumObject}, + H::QuantumObject{OperatorQuantumObject}, fields::Vector, T_list::Vector{<:Real}; N_trunc::Union{Int,Nothing} = nothing, tol::Real = 1e-12, σ_filter::Union{Nothing,Real} = nothing, -) where {MT<:AbstractMatrix} +) (length(fields) == length(T_list)) || throw(DimensionMismatch("The number of fields, ωs and Ts must be the same.")) dims = (N_trunc isa Nothing) ? H.dimensions : SVector(N_trunc) @@ -317,13 +314,13 @@ function liouvillian_generalized( end function _liouvillian_floquet( - L₀::QuantumObject{<:AbstractArray{T1},SuperOperatorQuantumObject}, - Lₚ::QuantumObject{<:AbstractArray{T2},SuperOperatorQuantumObject}, - Lₘ::QuantumObject{<:AbstractArray{T3},SuperOperatorQuantumObject}, + L₀::QuantumObject{SuperOperatorQuantumObject}, + Lₚ::QuantumObject{SuperOperatorQuantumObject}, + Lₘ::QuantumObject{SuperOperatorQuantumObject}, ω::Real, n_max::Int, tol::Real, -) where {T1,T2,T3} +) L_0 = L₀.data L_p = Lₚ.data L_m = Lₘ.data diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 81cf3260d..a548a1f4d 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -139,16 +139,16 @@ end function dfd_mesolveProblem( H::Function, - ψ0::QuantumObject{DT1,StateOpType}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Function, maxdims::Vector{T2}, dfd_params::NamedTuple = NamedTuple(); - e_ops::Function = (dim_list) -> Vector{Vector{DT1}}([]), + e_ops::Function = (dim_list) -> Vector{Vector{eltype(ψ0)}}([]), params::NamedTuple = NamedTuple(), tol_list::Vector{<:Number} = fill(1e-8, length(maxdims)), kwargs..., -) where {DT1,T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} length(ψ0.dimensions) != length(maxdims) && throw(DimensionMismatch("'dim_list' and 'maxdims' do not have the same dimension.")) @@ -209,17 +209,17 @@ Time evolution of an open quantum system using master equation, dynamically chan """ function dfd_mesolve( H::Function, - ψ0::QuantumObject{<:AbstractArray{T1},StateOpType}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Function, maxdims::Vector{T2}, dfd_params::NamedTuple = NamedTuple(); alg::OrdinaryDiffEqAlgorithm = Tsit5(), - e_ops::Function = (dim_list) -> Vector{Vector{T1}}([]), + e_ops::Function = (dim_list) -> Vector{Vector{eltype(ψ0)}}([]), params::NamedTuple = NamedTuple(), tol_list::Vector{<:Number} = fill(1e-8, length(maxdims)), kwargs..., -) where {T1,T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} dfd_prob = dfd_mesolveProblem( H, ψ0, @@ -341,7 +341,7 @@ end function dsf_mesolveProblem( H::Function, - ψ0::QuantumObject{<:AbstractVector{T},StateOpType}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Function, op_list::Union{AbstractVector,Tuple}, @@ -352,12 +352,14 @@ function dsf_mesolveProblem( δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} op_list = deepcopy(op_list) H₀ = H(op_list .+ α0_l, dsf_params) c_ops₀ = c_ops(op_list .+ α0_l, dsf_params) e_ops₀ = e_ops(op_list .+ α0_l, dsf_params) + T = eltype(ψ0) + αt_list = convert(Vector{T}, α0_l) op_l_vec = map(op -> mat2vec(get_data(op)'), op_list) # Create the Krylov subspace with kron(H₀.data, H₀.data) just for initialize @@ -421,7 +423,7 @@ Time evolution of an open quantum system using master equation and the Dynamical """ function dsf_mesolve( H::Function, - ψ0::QuantumObject{<:AbstractVector{T},StateOpType}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Function, op_list::Union{AbstractVector,Tuple}, @@ -433,7 +435,7 @@ function dsf_mesolve( δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} dsf_prob = dsf_mesolveProblem( H, ψ0, @@ -454,7 +456,7 @@ end function dsf_mesolve( H::Function, - ψ0::QuantumObject{<:AbstractVector{T},StateOpType}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, op_list::Union{AbstractVector,Tuple}, α0_l::Vector{<:Number} = zeros(length(op_list)), @@ -465,7 +467,7 @@ function dsf_mesolve( δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {T,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} c_ops = op_list -> () return dsf_mesolve( H, @@ -599,7 +601,7 @@ end function dsf_mcsolveEnsembleProblem( H::Function, - ψ0::QuantumObject{<:AbstractVector{T},KetQuantumObject}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Function, op_list::Union{AbstractVector,Tuple}, @@ -614,12 +616,14 @@ function dsf_mcsolveEnsembleProblem( krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), progress_bar::Union{Bool,Val} = Val(true), kwargs..., -) where {T,TJC<:LindbladJumpCallbackType} +) where {TJC<:LindbladJumpCallbackType} op_list = deepcopy(op_list) H₀ = H(op_list .+ α0_l, dsf_params) c_ops₀ = c_ops(op_list .+ α0_l, dsf_params) e_ops₀ = e_ops(op_list .+ α0_l, dsf_params) + T = eltype(ψ0) + αt_list = convert(Vector{T}, α0_l) expv_cache = arnoldi(H₀.data, ψ0.data, krylov_dim) @@ -692,7 +696,7 @@ Time evolution of a quantum system using the Monte Carlo wave function method an """ function dsf_mcsolve( H::Function, - ψ0::QuantumObject{<:AbstractVector{T},KetQuantumObject}, + ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, c_ops::Function, op_list::Union{AbstractVector,Tuple}, @@ -708,7 +712,7 @@ function dsf_mcsolve( krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), progress_bar::Union{Bool,Val} = Val(true), kwargs..., -) where {T,TJC<:LindbladJumpCallbackType} +) where {TJC<:LindbladJumpCallbackType} ens_prob_mc = dsf_mcsolveEnsembleProblem( H, ψ0, diff --git a/src/visualization.jl b/src/visualization.jl index 92ca2f67e..bd21582a0 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -2,17 +2,17 @@ export plot_wigner @doc raw""" plot_wigner( - state::QuantumObject{DT,OpType}; + state::QuantumObject{OpType}; library::Union{Val,Symbol}=Val(:CairoMakie), kwargs... - ) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject} + ) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject} Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`wigner`](@ref) function. The `library` keyword argument specifies the plotting library to use, defaulting to [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie). # Arguments -- `state::QuantumObject{DT,OpType}`: The quantum state for which to plot the Wigner distribution. +- `state::QuantumObject`: The quantum state for which to plot the Wigner distribution. - `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:CairoMakie)`. - `kwargs...`: Additional keyword arguments to pass to the plotting function. See the documentation for the specific plotting library for more information. @@ -23,15 +23,15 @@ The `library` keyword argument specifies the plotting library to use, defaulting If you want to keep type stability, it is recommended to use `Val(:CairoMakie)` instead of `:CairoMakie` as the plotting library. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ plot_wigner( - state::QuantumObject{DT,OpType}; + state::QuantumObject{OpType}; library::Union{Val,Symbol} = Val(:CairoMakie), kwargs..., -) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = +) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = plot_wigner(makeVal(library), state; kwargs...) plot_wigner( ::Val{T}, - state::QuantumObject{DT,OpType}; + state::QuantumObject{OpType}; kwargs..., -) where {T,DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = +) where {T,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) diff --git a/src/wigner.jl b/src/wigner.jl index 9e3bdbe03..d85385c63 100644 --- a/src/wigner.jl +++ b/src/wigner.jl @@ -13,7 +13,7 @@ WignerLaguerre(; parallel = false, tol = 1e-14) = WignerLaguerre(parallel, tol) @doc raw""" wigner( - state::QuantumObject{DT,OpType}, + state::QuantumObject{OpType}, xvec::AbstractVector, yvec::AbstractVector; g::Real = √2, @@ -67,12 +67,12 @@ julia> wig = wigner(ρ, xvec, xvec, method=WignerLaguerre(parallel=true)); ``` """ function wigner( - state::QuantumObject{DT,OpType}, + state::QuantumObject{OpType}, xvec::AbstractVector, yvec::AbstractVector; g::Real = √2, method::WignerSolver = WignerClenshaw(), -) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} ρ = ket2dm(state).data return _wigner(ρ, xvec, yvec, g, method) diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index fc1d0769c..c7ce03c06 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -9,7 +9,7 @@ M = latt.N + 1 # Number of states in the LR basis # Define initial state - ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject,Dimensions{M - 1,NTuple{M - 1,Space}}}}(undef, M) + ϕ = Vector{QuantumObject{KetQuantumObject,Dimensions{M - 1,NTuple{M - 1,Space}},Vector{ComplexF64}}}(undef, M) ϕ[1] = kron(fill(basis(2, 1), N_modes)...) i = 1 diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 91738bdc9..8761a57af 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -346,14 +346,14 @@ for T in [ComplexF32, ComplexF64] N = 4 a = rand(T, N) - @inferred QuantumObject{typeof(a),KetQuantumObject,Dimensions{1}} Qobj(a) + @inferred QuantumObject{KetQuantumObject,Dimensions{1},typeof(a)} Qobj(a) for type in [Ket, OperatorKet] @inferred Qobj(a, type = type) end UnionType = Union{ - QuantumObject{Matrix{T},BraQuantumObject,Dimensions{1,Tuple{Space}}}, - QuantumObject{Matrix{T},OperatorQuantumObject,Dimensions{1,Tuple{Space}}}, + QuantumObject{BraQuantumObject,Dimensions{1,Tuple{Space}},Matrix{T}}, + QuantumObject{OperatorQuantumObject,Dimensions{1,Tuple{Space}},Matrix{T}}, } a = rand(T, 1, N) @inferred UnionType Qobj(a) @@ -362,8 +362,8 @@ end UnionType2 = Union{ - QuantumObject{Matrix{T},OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}}}, - QuantumObject{Matrix{T},OperatorQuantumObject,Dimensions{1,Tuple{Space}}}, + QuantumObject{OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}},Matrix{T}}, + QuantumObject{OperatorQuantumObject,Dimensions{1,Tuple{Space}},Matrix{T}}, } a = rand(T, N, N) @inferred UnionType Qobj(a) From 473e9fe6451d76762a3d2a272e4877c1ae2b16e9 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sun, 19 Jan 2025 22:58:36 +0100 Subject: [PATCH 167/329] Fix all errors --- src/negativity.jl | 2 +- src/qobj/boolean_functions.jl | 8 ++++---- src/qobj/functions.jl | 6 +++--- src/time_evolution/sesolve.jl | 2 +- test/core-test/quantum_objects_evo.jl | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/negativity.jl b/src/negativity.jl index 79be89720..eefaab3d3 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -106,7 +106,7 @@ end function _partial_transpose( ρ::QuantumObject{OperatorQuantumObject,DimsType,<:AbstractSparseArray}, mask::Vector{Bool}, -) where {DimsType} +) where {DimsType<:AbstractDimensions} isa(ρ.dimensions, GeneralDimensions) && (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index 9723fe980..935d1b3d9 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -11,7 +11,7 @@ export isunitary Checks if the [`QuantumObject`](@ref) `A` is a [`BraQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isbra(A::QuantumObject) = isbra(typeof(A)) -isbra(::Type{QuantumObject{BraQuantumObject,N}}) where {N} = true +isbra(::Type{<:QuantumObject{BraQuantumObject,N}}) where {N} = true isbra(A) = false # default case @doc raw""" @@ -20,7 +20,7 @@ isbra(A) = false # default case Checks if the [`QuantumObject`](@ref) `A` is a [`KetQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isket(A::QuantumObject) = isket(typeof(A)) -isket(::Type{QuantumObject{KetQuantumObject,N}}) where {N} = true +isket(::Type{<:QuantumObject{KetQuantumObject,N}}) where {N} = true isket(A) = false # default case @doc raw""" @@ -38,7 +38,7 @@ isoper(A) = false # default case Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBraQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isoperbra(A::QuantumObject) = isoperbra(typeof(A)) -isoperbra(::Type{QuantumObject{OperatorBraQuantumObject,N}}) where {N} = true +isoperbra(::Type{<:QuantumObject{OperatorBraQuantumObject,N}}) where {N} = true isoperbra(A) = false # default case @doc raw""" @@ -47,7 +47,7 @@ isoperbra(A) = false # default case Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKetQuantumObject`](@ref). Default case returns `false` for any other inputs. """ isoperket(A::QuantumObject) = isoperket(typeof(A)) -isoperket(::Type{QuantumObject{OperatorKetQuantumObject,N}}) where {N} = true +isoperket(::Type{<:QuantumObject{OperatorKetQuantumObject,N}}) where {N} = true isoperket(A) = false # default case @doc raw""" diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 8069f6b49..00791f344 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -51,19 +51,19 @@ expect(O::QuantumObject{OperatorQuantumObject}, ρ::QuantumObject{OperatorQuantu function expect( O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, ψ::QuantumObject{KetQuantumObject}, -) where {DimsType,TF<:Number,TR<:Real} +) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} return real(dot(ψ.data, O.data, ψ.data)) end function expect( O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, ψ::QuantumObject{BraQuantumObject}, -) where {DimsType,TF<:Number,TR<:Real} +) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} return real(expect(O, ψ')) end function expect( O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, ρ::QuantumObject{OperatorQuantumObject}, -) where {DimsType,TF<:Number,TR<:Real} +) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} return real(tr(O * ρ)) end function expect(O::QuantumObject{OperatorQuantumObject}, ρ::Vector{<:QuantumObject}) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 0a25d4114..c1c0de763 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -1,6 +1,6 @@ export sesolveProblem, sesolve -_sesolve_make_U_QobjEvo(H::QuantumObjectEvolution{<:MatrixOperator}) = +_sesolve_make_U_QobjEvo(H::QuantumObjectEvolution{OperatorQuantumObject,DimsType,<:MatrixOperator}) where {DimsType<:AbstractDimensions} = QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dimensions, type = Operator) _sesolve_make_U_QobjEvo(H::QuantumObject) = QobjEvo(MatrixOperator(-1im * H.data), dims = H.dimensions, type = Operator) _sesolve_make_U_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}) = QobjEvo(H, -1im) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 130bfb32a..fec8235c0 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -133,8 +133,8 @@ for T in [ComplexF32, ComplexF64] a = MatrixOperator(rand(T, N, N)) UnionType = Union{ - QuantumObjectEvolution{typeof(a),OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}}}, - QuantumObjectEvolution{typeof(a),OperatorQuantumObject,Dimensions{1,Tuple{Space}}}, + QuantumObjectEvolution{OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}},typeof(a)}, + QuantumObjectEvolution{OperatorQuantumObject,Dimensions{1,Tuple{Space}},typeof(a)}, } @inferred UnionType QobjEvo(a) @inferred UnionType QobjEvo(a, type = Operator) From ab5aa32acc95816283c10e5a37896c1a03c29095 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sun, 19 Jan 2025 23:16:57 +0100 Subject: [PATCH 168/329] Format Code --- src/time_evolution/sesolve.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index c1c0de763..b76613f88 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -1,7 +1,8 @@ export sesolveProblem, sesolve -_sesolve_make_U_QobjEvo(H::QuantumObjectEvolution{OperatorQuantumObject,DimsType,<:MatrixOperator}) where {DimsType<:AbstractDimensions} = - QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dimensions, type = Operator) +_sesolve_make_U_QobjEvo( + H::QuantumObjectEvolution{OperatorQuantumObject,DimsType,<:MatrixOperator}, +) where {DimsType<:AbstractDimensions} = QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dimensions, type = Operator) _sesolve_make_U_QobjEvo(H::QuantumObject) = QobjEvo(MatrixOperator(-1im * H.data), dims = H.dimensions, type = Operator) _sesolve_make_U_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}) = QobjEvo(H, -1im) From 510c70908b6a3cbc1c1fdc75262ec50219de78f0 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sun, 19 Jan 2025 23:22:16 +0100 Subject: [PATCH 169/329] Make changelogs --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6929e57..21ec5bb78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add **GPUArrays** compatibility for `ptrace` function, by using **KernelAbstractions.jl**. ([#350]) - Introduce `Space`, `Dimensions`, `GeneralDimensions` structures to support wider definitions and operations of `Qobj/QobjEvo`, and potential functionalities in the future. ([#271], [#353], [#360]) - Improve lazy tensor warning for `SciMLOperators`. ([#370]) +- Change order of `AbstractQuantumObject` data type. For example, from `QuantumObject{DataType,ObjType,DimsType}` to `QuantumObject{ObjType,DimsType,DataType}`. ([#371]) ## [v0.24.0] Release date: 2024-12-13 @@ -77,3 +78,4 @@ Release date: 2024-11-13 [#353]: https://github.com/qutip/QuantumToolbox.jl/issues/353 [#360]: https://github.com/qutip/QuantumToolbox.jl/issues/360 [#370]: https://github.com/qutip/QuantumToolbox.jl/issues/370 +[#371]: https://github.com/qutip/QuantumToolbox.jl/issues/371 From eb31194a9690a9a33b6fb44b710e97efe0c35f75 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 20 Jan 2025 00:32:37 +0100 Subject: [PATCH 170/329] FIx errors on docs and CUDAExt --- ext/QuantumToolboxCUDAExt.jl | 30 +++++++++++++++++++----------- ext/QuantumToolboxCairoMakieExt.jl | 4 ++-- src/qobj/eigsolve.jl | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index 62629fbec..c8258886b 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -1,6 +1,7 @@ module QuantumToolboxCUDAExt using QuantumToolbox +using QuantumToolbox: makeVal, getVal import CUDA: cu, CuArray import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR import SparseArrays: SparseVector, SparseMatrixCSC @@ -70,19 +71,26 @@ Return a new [`QuantumObject`](@ref) where `A.data` is in the type of `CUDA` arr - `A::QuantumObject`: The [`QuantumObject`](@ref) - `word_size::Int`: The word size of the element type of `A`, can be either `32` or `64`. Default to `64`. """ -cu(A::QuantumObject; word_size::Int = 64) = - ((word_size == 64) || (word_size == 32)) ? cu(A, Val(word_size)) : - throw(DomainError(word_size, "The word size should be 32 or 64.")) -cu(A::QuantumObject, word_size::TW) where {TW<:Union{Val{32},Val{64}}} = - CuArray{_change_eltype(eltype(A), word_size)}(A) -cu( +function cu(A::QuantumObject; word_size::Union{Val,Int} = Val(64)) + _word_size = getVal(makeVal(word_size)) + + ((_word_size == 64) || (_word_size == 32)) || throw(DomainError(_word_size, "The word size should be 32 or 64.")) + + return cu(A, makeVal(word_size)) +end +cu(A::QuantumObject, word_size::Union{Val{32},Val{64}}) = CuArray{_change_eltype(eltype(A), word_size)}(A) +function cu( A::QuantumObject{ObjType,DimsType,<:SparseVector}, - word_size::TW, -) where {ObjType,DimsType,TW<:Union{Val{32},Val{64}}} = CuSparseVector{_change_eltype(eltype(A), word_size)}(A) -cu( + word_size::Union{Val{32},Val{64}}, +) where {ObjType<:QuantumObjectType,DimsType<:AbstractDimensions} + return CuSparseVector{_change_eltype(eltype(A), word_size)}(A) +end +function cu( A::QuantumObject{ObjType,DimsType,<:SparseMatrixCSC}, - word_size::TW, -) where {ObjType,DimsType,TW<:Union{Val{32},Val{64}}} = CuSparseMatrixCSC{_change_eltype(eltype(A), word_size)}(A) + word_size::Union{Val{32},Val{64}}, +) where {ObjType<:QuantumObjectType,DimsType<:AbstractDimensions} + return CuSparseMatrixCSC{_change_eltype(eltype(A), word_size)}(A) +end _change_eltype(::Type{T}, ::Val{64}) where {T<:Int} = Int64 _change_eltype(::Type{T}, ::Val{32}) where {T<:Int} = Int32 diff --git a/ext/QuantumToolboxCairoMakieExt.jl b/ext/QuantumToolboxCairoMakieExt.jl index c09ee32fc..23be6452d 100644 --- a/ext/QuantumToolboxCairoMakieExt.jl +++ b/ext/QuantumToolboxCairoMakieExt.jl @@ -83,7 +83,7 @@ function _plot_wigner( location::Union{GridPosition,Nothing}, colorbar::Bool; kwargs..., -) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} fig, location = _getFigAndLocation(location) lyt = GridLayout(location) @@ -116,7 +116,7 @@ function _plot_wigner( location::Union{GridPosition,Nothing}, colorbar::Bool; kwargs..., -) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} fig, location = _getFigAndLocation(location) lyt = GridLayout(location) diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 85823190c..f53cec4ee 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -45,7 +45,7 @@ julia> λ 1.0 + 0.0im julia> ψ -2-element Vector{QuantumObject{KetQuantumObject, Dimensions{1, Tuple{Space}},Vector{ComplexF64}}}: +2-element Vector{QuantumObject{KetQuantumObject, Dimensions{1, Tuple{Space}}, Vector{ComplexF64}}}: Quantum Object: type=Ket dims=[2] size=(2,) 2-element Vector{ComplexF64}: From 679e1b6027c63c843dea6420166db85245d4d523 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:27:42 +0900 Subject: [PATCH 171/329] minor change --- src/qobj/quantum_object.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 7198f49fc..f2c6f5569 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -133,7 +133,7 @@ function QuantumObject( end function QuantumObject(A::QuantumObject; type::ObjType = A.type, dims = A.dimensions) where {ObjType<:QuantumObjectType} - _size = isa(A.data, AbstractVector) ? (length(A), 1) : size(A) + _size = _get_size(A.data) dimensions = _gen_dimensions(dims) _check_QuantumObject(type, dimensions, _size[1], _size[2]) return QuantumObject(copy(A.data), type, dimensions) From 5c7445f3477289aaba3f3a85a437363d99b5f20c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:30:02 +0900 Subject: [PATCH 172/329] simplify definition of `svdvals` --- src/qobj/arithmetic_and_attributes.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 09694bb45..15a0f430d 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -298,10 +298,7 @@ LinearAlgebra.tr( Return the singular values of a [`QuantumObject`](@ref) in descending order """ -LinearAlgebra.svdvals(A::QuantumObject{OpType,DimsType,<:AbstractVector}) where {OpType,DimsType} = svdvals(A.data) -LinearAlgebra.svdvals(A::QuantumObject{OpType,DimsType,<:AbstractMatrix}) where {OpType,DimsType} = svdvals(A.data) -LinearAlgebra.svdvals(A::QuantumObject{OpType,DimsType,<:AbstractSparseMatrix}) where {OpType,DimsType} = - svdvals(sparse_to_dense(A.data)) +LinearAlgebra.svdvals(A::QuantumObject) = svdvals(sparse_to_dense(A.data)) @doc raw""" norm(A::QuantumObject, p::Real) From 540bcaf41a664296688b8ab1a37f8b34528abb23 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 20 Jan 2025 11:50:12 +0900 Subject: [PATCH 173/329] [no ci] bump version to `v0.25.0` --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ec5bb78..53ce48c7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.25.0] +Release date: 2025-01-20 + - Change the structure of block diagonalization functions, using `BlockDiagonalForm` struct and changing the function name from `bdf` to `block_diagonal_form`. ([#349]) - Add **GPUArrays** compatibility for `ptrace` function, by using **KernelAbstractions.jl**. ([#350]) - Introduce `Space`, `Dimensions`, `GeneralDimensions` structures to support wider definitions and operations of `Qobj/QobjEvo`, and potential functionalities in the future. ([#271], [#353], [#360]) @@ -56,6 +59,7 @@ Release date: 2024-11-13 [v0.23.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.0 [v0.23.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.1 [v0.24.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.24.0 +[v0.25.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 264ef05c6..92a7498f8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.24.0" +version = "0.25.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From a2bd2ed974f316f597263b0fbbb6cab6593be686 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 05:12:15 +0000 Subject: [PATCH 174/329] Bump crate-ci/typos from 1.28.3 to 1.29.4 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.3 to 1.29.4. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/v1.28.3...v1.29.4) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index d51382625..e54d866c9 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.28.3 \ No newline at end of file + uses: crate-ci/typos@v1.29.4 \ No newline at end of file From 9cc02248eb7fefb8442d4db9bc545090fbd88842 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 26 Jan 2025 09:21:13 +0100 Subject: [PATCH 175/329] Fix dynamical fock dimension saving (#375) --- CHANGELOG.md | 3 + .../time_evolution_dynamical.jl | 62 +++++++++---------- .../dynamical_fock_dimension_mesolve.jl | 16 ++++- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ce48c7f..0c38a4b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Fix Dynamical Fock Dimension states saving due to wrong saving of dimensions. ([#375]) + ## [v0.25.0] Release date: 2025-01-20 @@ -83,3 +85,4 @@ Release date: 2024-11-13 [#360]: https://github.com/qutip/QuantumToolbox.jl/issues/360 [#370]: https://github.com/qutip/QuantumToolbox.jl/issues/370 [#371]: https://github.com/qutip/QuantumToolbox.jl/issues/371 +[#375]: https://github.com/qutip/QuantumToolbox.jl/issues/375 diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index a548a1f4d..b4ec427a5 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -59,14 +59,14 @@ end _dfd_set_pillow(dim)::Int = min(max(round(Int, 0.02 * dim), 1), 20) function _DFDIncreaseReduceCondition(u, t, integrator) - internal_params = integrator.p - dim_list = internal_params.dim_list - maxdims = internal_params.maxdims - tol_list = internal_params.tol_list - increase_list = internal_params.increase_list - reduce_list = internal_params.reduce_list - pillow_list = internal_params.pillow_list - dfd_ρt_cache = internal_params.dfd_ρt_cache + params = integrator.p + dim_list = params.dim_list + maxdims = params.maxdims + tol_list = params.tol_list + increase_list = params.increase_list + reduce_list = params.reduce_list + pillow_list = params.pillow_list + dfd_ρt_cache = params.dfd_ρt_cache # I need this cache because I can't reshape directly the integrator.u copyto!(dfd_ρt_cache, u) @@ -89,18 +89,18 @@ function _DFDIncreaseReduceCondition(u, t, integrator) end function _DFDIncreaseReduceAffect!(integrator) - internal_params = integrator.p - H = internal_params.H_fun - c_ops = internal_params.c_ops_fun - e_ops = internal_params.e_ops_fun - dim_list = internal_params.dim_list - increase_list = internal_params.increase_list - reduce_list = internal_params.reduce_list - pillow_list = internal_params.pillow_list - dim_list_evo_times = internal_params.dim_list_evo_times - dim_list_evo = internal_params.dim_list_evo - dfd_ρt_cache = internal_params.dfd_ρt_cache - dfd_params = internal_params.dfd_params + params = integrator.p + H = params.H_fun + c_ops = params.c_ops_fun + e_ops = params.e_ops_fun + dim_list = params.dim_list + increase_list = params.increase_list + reduce_list = params.reduce_list + pillow_list = params.pillow_list + dim_list_evo_times = params.dim_list_evo_times + dim_list_evo = params.dim_list_evo + dfd_ρt_cache = params.dfd_ρt_cache + dfd_params = params.dfd_params ρt = vec2mat(dfd_ρt_cache) @@ -122,7 +122,7 @@ function _DFDIncreaseReduceAffect!(integrator) fill!(increase_list, false) fill!(reduce_list, false) push!(dim_list_evo_times, integrator.t) - push!(dim_list_evo, dim_list) + push!(dim_list_evo, copy(dim_list)) e_ops2 = map(op -> mat2vec(get_data(op)'), e_ops(dim_list, dfd_params)) L = liouvillian(H(dim_list, dfd_params), c_ops(dim_list, dfd_params)).data @@ -131,7 +131,7 @@ function _DFDIncreaseReduceAffect!(integrator) copyto!(integrator.u, mat2vec(ρt)) # By doing this, we are assuming that the system is time-independent and f is a MatrixOperator integrator.f = ODEFunction{true,FullSpecialize}(MatrixOperator(L)) - integrator.p = merge(internal_params, (dfd_ρt_cache = similar(integrator.u),)) + integrator.p = merge(params, (dfd_ρt_cache = similar(integrator.u),)) _mesolve_callbacks_new_e_ops!(integrator, e_ops2) return nothing @@ -150,7 +150,7 @@ function dfd_mesolveProblem( kwargs..., ) where {T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} length(ψ0.dimensions) != length(maxdims) && - throw(DimensionMismatch("'dim_list' and 'maxdims' do not have the same dimension.")) + throw(DimensionMismatch("`dim_list` and `maxdims` do not have the same dimension.")) dim_list = MVector(ψ0.dims) H₀ = H(dim_list, dfd_params) @@ -158,7 +158,7 @@ function dfd_mesolveProblem( e_ops₀ = e_ops(dim_list, dfd_params) dim_list_evo_times = [0.0] - dim_list_evo = [dim_list] + dim_list_evo = [copy(dim_list)] reduce_list = MVector(ntuple(i -> false, Val(length(dim_list)))) increase_list = MVector(ntuple(i -> false, Val(length(dim_list)))) pillow_list = _dfd_set_pillow.(dim_list) @@ -235,14 +235,12 @@ function dfd_mesolve( sol = solve(dfd_prob.prob, alg) - ρt = map( - i -> QuantumObject( - vec2mat(sol.u[i]), - type = Operator, - dims = sol.prob.p.dim_list_evo[searchsortedlast(sol.prob.p.dim_list_evo_times, sol.t[i])], - ), - eachindex(sol.t), - ) + ρt = map(eachindex(sol.t)) do i + idx = findfirst(>=(sol.t[i]), sol.prob.p.dim_list_evo_times) + idx2 = isnothing(idx) ? length(sol.prob.p.dim_list_evo) : (idx == 1 ? 1 : idx - 1) + + return QuantumObject(vec2mat(sol.u[i]), type = Operator, dims = sol.prob.p.dim_list_evo[idx2]) + end return TimeEvolutionSol( dfd_prob.times, diff --git a/test/core-test/dynamical_fock_dimension_mesolve.jl b/test/core-test/dynamical_fock_dimension_mesolve.jl index 9c6c8a02a..4c75eef0b 100644 --- a/test/core-test/dynamical_fock_dimension_mesolve.jl +++ b/test/core-test/dynamical_fock_dimension_mesolve.jl @@ -29,9 +29,23 @@ maxdims = [150] ψ0 = fock(3, 0) dfd_params = (Δ = Δ, F = F, κ = κ) - sol = dfd_mesolve(H_dfd0, ψ0, t_l, c_ops_dfd0, maxdims, dfd_params, e_ops = e_ops_dfd0, progress_bar = Val(false)) + sol = dfd_mesolve( + H_dfd0, + ψ0, + t_l, + c_ops_dfd0, + maxdims, + dfd_params, + e_ops = e_ops_dfd0, + progress_bar = Val(false), + saveat = t_l, + ) @test sum(abs.((sol.expect[1, :] .- sol0.expect[1, :]) ./ (sol0.expect[1, :] .+ 1e-16))) < 0.01 + @test length(sol.states) == length(t_l) + @test all( + diff(getindex.(QuantumToolbox.dimensions_to_dims.(QuantumToolbox.get_dimensions_to.(sol.states)), 1)) .>= 0, + ) ###################### From 2f0ce8efd16d2e091a121993af8a1d1964f50d27 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:45:41 +0900 Subject: [PATCH 176/329] Support a list of observables for `expect` (#376) --- CHANGELOG.md | 3 ++ src/qobj/functions.jl | 69 +++++++++++++++++++++---------- test/core-test/quantum_objects.jl | 18 ++++++++ 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c38a4b1c..24aa649e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Fix Dynamical Fock Dimension states saving due to wrong saving of dimensions. ([#375]) +- Support a list of observables for `expect`. ([#374], [#376]) ## [v0.25.0] Release date: 2025-01-20 @@ -85,4 +86,6 @@ Release date: 2024-11-13 [#360]: https://github.com/qutip/QuantumToolbox.jl/issues/360 [#370]: https://github.com/qutip/QuantumToolbox.jl/issues/370 [#371]: https://github.com/qutip/QuantumToolbox.jl/issues/371 +[#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 diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 00791f344..3ed38394e 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -17,7 +17,7 @@ ket2dm(ψ::QuantumObject{KetQuantumObject}) = ψ * ψ' ket2dm(ρ::QuantumObject{OperatorQuantumObject}) = ρ @doc raw""" - expect(O::AbstractQuantumObject, ψ::Union{QuantumObject,Vector{QuantumObject}}) + expect(O::Union{AbstractQuantumObject,Vector{AbstractQuantumObject}}, ψ::Union{QuantumObject,Vector{QuantumObject}}) Expectation value of the [`Operator`](@ref) `O` with the state `ψ`. The state can be a [`Ket`](@ref), [`Bra`](@ref) or [`Operator`](@ref). @@ -27,48 +27,75 @@ If `ψ` is a density matrix ([`Operator`](@ref)), the function calculates ``\tex The function returns a real number if `O` is of `Hermitian` type or `Symmetric` type, and returns a complex number otherwise. You can make an operator `O` hermitian by using `Hermitian(O)`. -Note that `ψ` can also be given as a list of [`QuantumObject`](@ref), it returns a list of expectation values. +!!! note "List of observables and states" + The observable `O` and state `ψ` can be given as a list of [`QuantumObject`](@ref), it returns a list of expectation values. If both of them are given as a list, it returns a `Matrix` of expectation values. # Examples ```jldoctest -julia> ψ = 1 / √2 * (fock(10,2) + fock(10,4)); +julia> ψ1 = 1 / √2 * (fock(10,2) + fock(10,4)); + +julia> ψ2 = coherent(10, 0.6 + 0.8im); julia> a = destroy(10); -julia> expect(a' * a, ψ) |> round +julia> expect(a' * a, ψ1) |> round 3.0 + 0.0im -julia> expect(Hermitian(a' * a), ψ) |> round +julia> expect(Hermitian(a' * a), ψ1) |> round 3.0 + +julia> round.(expect([a' * a, a' + a, a], [ψ1, ψ2]), digits = 1) +3×2 Matrix{ComplexF64}: + 3.0+0.0im 1.0+0.0im + 0.0+0.0im 1.2-0.0im + 0.0+0.0im 0.6+0.8im ``` """ -function expect(O::AbstractQuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) - return dot(ψ.data, O.data, ψ.data) -end +expect(O::AbstractQuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) = + dot(ψ.data, O.data, ψ.data) expect(O::AbstractQuantumObject{OperatorQuantumObject}, ψ::QuantumObject{BraQuantumObject}) = expect(O, ψ') expect(O::QuantumObject{OperatorQuantumObject}, ρ::QuantumObject{OperatorQuantumObject}) = tr(O * ρ) -function expect( +expect( O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, ψ::QuantumObject{KetQuantumObject}, -) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} - return real(dot(ψ.data, O.data, ψ.data)) -end -function expect( +) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = real(dot(ψ.data, O.data, ψ.data)) +expect( O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, ψ::QuantumObject{BraQuantumObject}, -) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} - return real(expect(O, ψ')) -end -function expect( +) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = real(expect(O, ψ')) +expect( O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, ρ::QuantumObject{OperatorQuantumObject}, +) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = real(tr(O * ρ)) +expect( + O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}}, + ρ::QuantumObject, +) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = expect.(O, Ref(ρ)) +function expect(O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject}}, ρ::QuantumObject) + result = Vector{ComplexF64}(undef, length(O)) + result .= expect.(O, Ref(ρ)) + return result +end +expect(O::AbstractQuantumObject{OperatorQuantumObject}, ρ::AbstractVector{<:QuantumObject}) = expect.(Ref(O), ρ) +function expect( + O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}}, + ρ::AbstractVector{<:QuantumObject}, ) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} - return real(tr(O * ρ)) + N_ops = length(O) + result = Matrix{Float64}(undef, N_ops, length(ρ)) + for i in 1:N_ops + result[i, :] .= expect.(Ref(O[i]), ρ) + end + return result end -function expect(O::QuantumObject{OperatorQuantumObject}, ρ::Vector{<:QuantumObject}) - _expect = _ρ -> expect(O, _ρ) - return _expect.(ρ) +function expect(O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject}}, ρ::AbstractVector{<:QuantumObject}) + N_ops = length(O) + result = Matrix{ComplexF64}(undef, N_ops, length(ρ)) + for i in 1:N_ops + result[i, :] .= expect.(Ref(O[i]), ρ) + end + return result end @doc raw""" diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 8761a57af..ad09f848e 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -479,6 +479,18 @@ ψlist = [normalize!(basis(N, 4) + x * basis(N, 3)) for x in xlist] @test all(expect(a', ψlist) .≈ xlist) + # when input is a vector of observables + ρlist = Hermitian.(ket2dm.(ψlist)) # an alternative way to calculate expectation values for a list of density matrices + Olist1 = [a' * a, a' + a, a] + Olist2 = [Hermitian(a' * a), Hermitian(a' + a)] + exp_val_1 = expect(Olist1, ψlist) + exp_val_2 = expect(Olist2, ψlist) + @test size(exp_val_1) == (3, 4) + @test size(exp_val_2) == (2, 4) + @test all(exp_val_1[1, :] .≈ exp_val_2[1, :] .≈ expect(ρlist, a' * a)) + @test all(exp_val_1[2, :] .≈ exp_val_2[2, :] .≈ expect(ρlist, a' + a)) + @test all(exp_val_1[3, :] .≈ expect(a, ρlist)) + @testset "Type Inference (expect)" begin @inferred expect(a, ψ) @inferred expect(a, ψ') @@ -488,6 +500,12 @@ @inferred variance(a, ρ) @inferred expect(a, ψlist) @inferred variance(a, ψlist) + @inferred expect(ρlist, a) + @inferred expect(Olist1, ψ) + @inferred expect(Olist1, ψ') + @inferred expect(Olist1, ρ) + @inferred expect(Olist1, ψlist) + @inferred expect(Olist2, ψlist) end end From de31edb0d738fa39a49f368792ce7f80bd99a933 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:10:37 +0100 Subject: [PATCH 177/329] Check `tlist` properties (#378) --- CHANGELOG.md | 2 ++ src/time_evolution/lr_mesolve.jl | 2 +- src/time_evolution/mcsolve.jl | 2 +- src/time_evolution/mesolve.jl | 2 +- src/time_evolution/sesolve.jl | 2 +- src/time_evolution/ssesolve.jl | 2 +- src/time_evolution/time_evolution.jl | 13 +++++++++++++ test/core-test/time_evolution.jl | 20 ++++++++++++++++++++ 8 files changed, 40 insertions(+), 5 deletions(-) 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. From 9ca0815ad0fd8b913d3c0b479fbffe45b6b114a4 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:17:00 +0100 Subject: [PATCH 178/329] Bump version to v0.25.1 (#379) --- CHANGELOG.md | 3 +++ Project.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7f8fed3..79e04cc96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.25.1] +Release date: 2025-01-29 + - 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]) diff --git a/Project.toml b/Project.toml index 92a7498f8..4604138c7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.25.0" +version = "0.25.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 8e6f1e5e1c28001a06836e320f89656cfdc825ed Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Wed, 29 Jan 2025 13:14:50 +0100 Subject: [PATCH 179/329] [no ci] Move code quality dependencies to separate environment --- Project.toml | 6 +----- test/core-test/code-quality/Project.toml | 8 ++++++++ .../{ => code-quality}/code_quality.jl | 0 test/runtests.jl | 18 +++++++++++------- 4 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 test/core-test/code-quality/Project.toml rename test/core-test/{ => code-quality}/code_quality.jl (100%) diff --git a/Project.toml b/Project.toml index 4604138c7..ddf221a87 100644 --- a/Project.toml +++ b/Project.toml @@ -38,7 +38,6 @@ QuantumToolboxCairoMakieExt = "CairoMakie" QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] [compat] -Aqua = "0.8" ArrayInterface = "6, 7" CUDA = "5" CairoMakie = "0.12, 0.13" @@ -50,7 +49,6 @@ FFTW = "1.5" GPUArrays = "10, 11" Graphs = "1.7" IncompleteLU = "0.2" -JET = "0.9" KernelAbstractions = "0.9.2" LinearAlgebra = "1" LinearSolve = "2" @@ -69,9 +67,7 @@ Test = "1" julia = "1.10" [extras] -Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "JET", "Test"] +test = ["Test"] diff --git a/test/core-test/code-quality/Project.toml b/test/core-test/code-quality/Project.toml new file mode 100644 index 000000000..b1ac4d754 --- /dev/null +++ b/test/core-test/code-quality/Project.toml @@ -0,0 +1,8 @@ +[deps] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" + +[compat] +Aqua = "0.8" +JET = "0.9" diff --git a/test/core-test/code_quality.jl b/test/core-test/code-quality/code_quality.jl similarity index 100% rename from test/core-test/code_quality.jl rename to test/core-test/code-quality/code_quality.jl diff --git a/test/runtests.jl b/test/runtests.jl index ca5c41670..97d4b9736 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,13 +26,6 @@ core_tests = [ "wigner.jl", ] -if (GROUP == "All") || (GROUP == "Code-Quality") - using QuantumToolbox - using Aqua, JET - - include(joinpath(testdir, "core-test", "code_quality.jl")) -end - if (GROUP == "All") || (GROUP == "Core") using QuantumToolbox import QuantumToolbox: position, momentum @@ -46,6 +39,17 @@ if (GROUP == "All") || (GROUP == "Core") end end +if (GROUP == "All") || (GROUP == "Code-Quality") + Pkg.activate("core-test/code-quality") + Pkg.develop(PackageSpec(path = dirname(@__DIR__))) + Pkg.instantiate() + + using QuantumToolbox + using Aqua, JET + + include(joinpath(testdir, "core-test", "code-quality", "code_quality.jl")) +end + if (GROUP == "CairoMakie_Ext")# || (GROUP == "All") Pkg.activate("ext-test/cairomakie") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) From b63c19b956946dc80000f2b914e2d67b74695d40 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Wed, 29 Jan 2025 13:20:04 +0100 Subject: [PATCH 180/329] Make Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e04cc96..a885d0460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Move code quality dependencies to separate environment. ([#380]) + ## [v0.25.1] Release date: 2025-01-29 @@ -67,6 +69,7 @@ Release date: 2024-11-13 [v0.23.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.1 [v0.24.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.24.0 [v0.25.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.0 +[v0.25.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.1 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 @@ -94,3 +97,4 @@ Release date: 2024-11-13 [#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 +[#380]: https://github.com/qutip/QuantumToolbox.jl/issues/380 From 52cf7bb35ee89446aec3f55b0d13855c7dd07013 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:22:54 +0900 Subject: [PATCH 181/329] [Docs] Minor changes to contribute page (#381) --- .github/pull_request_template.md | 2 +- README.md | 2 +- docs/src/resources/contributing.md | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c1c968ca3..db12434e5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ ## Checklist Thank you for contributing to `QuantumToolbox.jl`! Please make sure you have finished the following tasks before opening the PR. -- [ ] Please read [Contributing to QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing). +- [ ] Please read [Contributing to Quantum Toolbox in Julia](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing). - [ ] Any code changes were done in a way that does not break public API. - [ ] Appropriate tests were added and tested locally by running: `make test`. - [ ] Any code changes should be `julia` formatted by running: `make format`. diff --git a/README.md b/README.md index 93ab77261..c1f12cf43 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Here we provide a brief performance comparison between `QuantumToolbox.jl` and o You are most welcome to contribute to `QuantumToolbox.jl` development by forking this repository and sending pull requests (PRs), or filing bug reports at the issues page. You can also help out with users' questions, or discuss proposed changes in the [QuTiP discussion group](https://groups.google.com/g/qutip). -For more information about contribution, including technical advice, please see the [Contributing to QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing) section of the documentation. +For more information about contribution, including technical advice, please see the [Contributing to Quantum Toolbox in Julia](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing). ## Acknowledgements diff --git a/docs/src/resources/contributing.md b/docs/src/resources/contributing.md index 517191c07..0ff15b278 100644 --- a/docs/src/resources/contributing.md +++ b/docs/src/resources/contributing.md @@ -1,4 +1,4 @@ -# [Contributing to QuantumToolbox.jl](@id doc-Contribute) +# [Contributing to Quantum Toolbox in Julia](@id doc-Contribute) ## [Quick Start](@id doc-Contribute:Quick-Start) @@ -32,6 +32,12 @@ make test This command will automatically rebuild `Julia` and run the script located in `test/runtests.jl` (should cover both the original tests and the new test(s) you add). +The tests are divided into several test groups, where the group names are defined in the file `test/runtests.jl` with a variable `GROUP`. One can also run the test scripts just for a certain test group by adding an argument `GROUP=` to the `make test` command. For example, to run the tests for group `Core`, one can use the following command: + +```shell +make GROUP=Core test +``` + ## [Julia Code Format](@id doc-Contribute:Julia-Code-Format) We use [`JuliaFormatter.jl`](https://github.com/domluna/JuliaFormatter.jl) to format all the source codes. The code style and extra formatting options is defined in the file `.JuliaFormatter.toml` in the repository. From 8cbd5499f0473b638f8c58eb15c5532789473a3d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 2 Feb 2025 13:35:26 +0900 Subject: [PATCH 182/329] Add state normalization during evolution in ssesolve (#383) --- CHANGELOG.md | 2 + docs/src/resources/bibliography.bib | 11 ++++++ src/QuantumToolbox.jl | 1 + .../callback_helpers/callback_helpers.jl | 25 ++++++++++-- .../ssesolve_callback_helpers.jl | 25 ++++++++++++ src/time_evolution/ssesolve.jl | 38 ++++++++++--------- test/core-test/time_evolution.jl | 33 +++------------- 7 files changed, 86 insertions(+), 49 deletions(-) create mode 100644 src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index a885d0460..2599d927a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Move code quality dependencies to separate environment. ([#380]) +- Add additional normalization of the state during time evolution of `ssesolve`. This improves the numerical stability of the solver. ([#383]) ## [v0.25.1] Release date: 2025-01-29 @@ -98,3 +99,4 @@ Release date: 2024-11-13 [#376]: https://github.com/qutip/QuantumToolbox.jl/issues/376 [#378]: https://github.com/qutip/QuantumToolbox.jl/issues/378 [#380]: https://github.com/qutip/QuantumToolbox.jl/issues/380 +[#383]: https://github.com/qutip/QuantumToolbox.jl/issues/383 diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index 7a64ec593..e8386a703 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -70,3 +70,14 @@ @article{Huang2023 title = {An efficient {J}ulia framework for hierarchical equations of motion in open quantum systems}, journal = {Communications Physics} } + +@book{Wiseman2009Quantum, + title={Quantum Measurement and Control}, + ISBN={9781107424159}, + url={http://dx.doi.org/10.1017/CBO9780511813948}, + DOI={10.1017/cbo9780511813948}, + publisher={Cambridge University Press}, + author={Wiseman, Howard M. and Milburn, Gerard J.}, + year={2009}, + month=nov +} diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 27055e097..3677bb178 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -99,6 +99,7 @@ include("time_evolution/time_evolution.jl") include("time_evolution/callback_helpers/sesolve_callback_helpers.jl") include("time_evolution/callback_helpers/mesolve_callback_helpers.jl") include("time_evolution/callback_helpers/mcsolve_callback_helpers.jl") +include("time_evolution/callback_helpers/ssesolve_callback_helpers.jl") include("time_evolution/callback_helpers/callback_helpers.jl") include("time_evolution/mesolve.jl") include("time_evolution/lr_mesolve.jl") diff --git a/src/time_evolution/callback_helpers/callback_helpers.jl b/src/time_evolution/callback_helpers/callback_helpers.jl index d1180afe3..6f4103069 100644 --- a/src/time_evolution/callback_helpers/callback_helpers.jl +++ b/src/time_evolution/callback_helpers/callback_helpers.jl @@ -32,9 +32,26 @@ end _get_e_ops_data(e_ops, ::Type{SaveFuncSESolve}) = get_data.(e_ops) _get_e_ops_data(e_ops, ::Type{SaveFuncMESolve}) = [_generate_mesolve_e_op(op) for op in e_ops] # Broadcasting generates type instabilities on Julia v1.10 +_get_e_ops_data(e_ops, ::Type{SaveFuncSSESolve}) = get_data.(e_ops) _generate_mesolve_e_op(op) = mat2vec(adjoint(get_data(op))) +#= +This function add the normalization callback to the kwargs. It is needed to stabilize the integration when using the ssesolve method. +=# +function _ssesolve_add_normalize_cb(kwargs) + _condition = (u, t, integrator) -> true + _affect! = (integrator) -> normalize!(integrator.u) + cb = DiscreteCallback(_condition, _affect!; save_positions = (false, false)) + # return merge(kwargs, (callback = CallbackSet(kwargs[:callback], cb),)) + + cb_set = haskey(kwargs, :callback) ? CallbackSet(kwargs[:callback], cb) : cb + + kwargs2 = merge(kwargs, (callback = cb_set,)) + + return kwargs2 +end + ## # When e_ops is Nothing. Common for both mesolve and sesolve @@ -80,10 +97,10 @@ function _se_me_sse_get_save_callback(cb::CallbackSet) return nothing end end -_se_me_sse_get_save_callback(cb::DiscreteCallback) = - if (cb.affect! isa SaveFuncSESolve) || (cb.affect! isa SaveFuncMESolve) +function _se_me_sse_get_save_callback(cb::DiscreteCallback) + if typeof(cb.affect!) <: Union{SaveFuncSESolve,SaveFuncMESolve,SaveFuncSSESolve} return cb - else - return nothing end + return nothing +end _se_me_sse_get_save_callback(cb::ContinuousCallback) = nothing diff --git a/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl new file mode 100644 index 000000000..69fca4b11 --- /dev/null +++ b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl @@ -0,0 +1,25 @@ +#= +Helper functions for the ssesolve callbacks. Equal to the sesolve case, but with an additional normalization before saving the expectation values. +=# + +struct SaveFuncSSESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} + e_ops::TE + progr::PT + iter::IT + expvals::TEXPV +end + +(f::SaveFuncSSESolve)(integrator) = _save_func_ssesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) +(f::SaveFuncSSESolve{Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both mesolve and sesolve + +## + +# When e_ops is a list of operators +function _save_func_ssesolve(integrator, e_ops, progr, iter, expvals) + ψ = normalize!(integrator.u) + _expect = op -> dot(ψ, op, ψ) + @. expvals[:, iter[]] = _expect(e_ops) + iter[] += 1 + + return _save_func(integrator, progr) +end diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 5e4a15e12..e827db017 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -35,13 +35,14 @@ end Base.@nexprs $N i -> begin mul!(@view(v[:, i]), L.ops[i], u) end - v + return v end end # TODO: Implement the three-argument dot function for SciMLOperators.jl # Currently, we are assuming a time-independent MatrixOperator function _ssesolve_update_coeff(u, p, t, op) + normalize!(u) return real(dot(u, op.A, u)) #this is en/2: /2 = Re end @@ -104,23 +105,23 @@ _ScalarOperator_e2_2(op, f = +) = Generate the SDEProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: ```math -d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) +d|\psi(t)\rangle = -i \hat{K} |\psi(t)\rangle dt + \sum_n \hat{M}_n |\psi(t)\rangle dW_n(t) ``` where ```math -K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), +\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{C}_n - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n - \frac{e_n^2}{8}\right), ``` ```math -M_n = C_n - \frac{e_n}{2}, +\hat{M}_n = \hat{C}_n - \frac{e_n}{2}, ``` and ```math -e_n = \langle C_n + C_n^\dagger \rangle. +e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. ``` -Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. +Above, `\hat{C}_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `\hat{C}_n`. See [Wiseman2009Quantum](@cite) for more details. # Arguments @@ -193,13 +194,14 @@ function ssesolveProblem( saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSESolve) + kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSSESolve) + kwargs4 = _ssesolve_add_normalize_cb(kwargs3) tspan = (tlist[1], tlist[end]) noise = RealWienerProcess!(tlist[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) noise_rate_prototype = similar(ψ0, length(ψ0), length(sc_ops)) - return SDEProblem{true}(K, D, ψ0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, kwargs3...) + return SDEProblem{true}(K, D, ψ0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, kwargs4...) end @doc raw""" @@ -222,23 +224,23 @@ end Generate the SDE EnsembleProblem for the Stochastic Schrödinger time evolution of a quantum system. This is defined by the following stochastic differential equation: ```math -d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) +d|\psi(t)\rangle = -i \hat{K} |\psi(t)\rangle dt + \sum_n \hat{M}_n |\psi(t)\rangle dW_n(t) ``` where ```math -K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), +\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{C}_n - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n - \frac{e_n^2}{8}\right), ``` ```math -M_n = C_n - \frac{e_n}{2}, +\hat{M}_n = \hat{C}_n - \frac{e_n}{2}, ``` and ```math -e_n = \langle C_n + C_n^\dagger \rangle. +e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. ``` -Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. +Above, `\hat{C}_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `\hat{C}_n`. See [Wiseman2009Quantum](@cite) for more details. # Arguments @@ -345,23 +347,23 @@ Stochastic Schrödinger equation evolution of a quantum system given the system The stochastic evolution of the state ``|\psi(t)\rangle`` is defined by: ```math -d|\psi(t)\rangle = -i K |\psi(t)\rangle dt + \sum_n M_n |\psi(t)\rangle dW_n(t) +d|\psi(t)\rangle = -i \hat{K} |\psi(t)\rangle dt + \sum_n \hat{M}_n |\psi(t)\rangle dW_n(t) ``` where ```math -K = \hat{H} + i \sum_n \left(\frac{e_j} C_n - \frac{1}{2} \sum_{j} C_n^\dagger C_n - \frac{e_j^2}{8}\right), +\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{C}_n - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n - \frac{e_n^2}{8}\right), ``` ```math -M_n = C_n - \frac{e_n}{2}, +\hat{M}_n = \hat{C}_n - \frac{e_n}{2}, ``` and ```math -e_n = \langle C_n + C_n^\dagger \rangle. +e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. ``` -Above, `C_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `C_n`. +Above, `\hat{C}_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `\hat{C}_n`. See [Wiseman2009Quantum](@cite) for more details. # Arguments diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 5f62a2479..a1b852e6a 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -18,6 +18,8 @@ e_ops = [a' * a, σz] c_ops = [sqrt(γ * (1 + nth)) * a, sqrt(γ * nth) * a', sqrt(γ * (1 + nth)) * σm, sqrt(γ * nth) * σm'] + ψ0_int = Qobj(round.(Int, real.(ψ0.data)), dims = ψ0.dims) # Used for testing the type inference + @testset "sesolve" begin tlist = range(0, 20 * 2π / g, 1000) @@ -83,7 +85,7 @@ @testset "Type Inference sesolve" begin @inferred sesolveProblem(H, ψ0, tlist, progress_bar = Val(false)) @inferred sesolveProblem(H, ψ0, [0, 10], progress_bar = Val(false)) - @inferred sesolveProblem(H, Qobj(zeros(Int64, N * 2); dims = (N, 2)), tlist, progress_bar = Val(false)) + @inferred sesolveProblem(H, ψ0_int, tlist, progress_bar = Val(false)) @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) @inferred sesolve(H, ψ0, tlist, progress_bar = Val(false)) @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) @@ -367,14 +369,7 @@ ad_t = QobjEvo(a', coef) @inferred mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolveProblem(H, ψ0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolveProblem( - H, - tensor(Qobj(zeros(Int64, N)), Qobj([0, 1])), - tlist, - c_ops, - e_ops = e_ops, - progress_bar = Val(false), - ) + @inferred mesolveProblem(H, ψ0_int, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) @@ -398,15 +393,7 @@ @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, e_ops = e_ops, progress_bar = Val(false), rng = rng) @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, progress_bar = Val(true), rng = rng) @inferred mcsolve(H, ψ0, [0, 10], c_ops, ntraj = 5, progress_bar = Val(false), rng = rng) - @inferred mcsolve( - H, - tensor(Qobj(zeros(Int64, N)), Qobj([0, 1])), - tlist, - c_ops, - ntraj = 5, - progress_bar = Val(false), - rng = rng, - ) + @inferred mcsolve(H, ψ0_int, tlist, c_ops, ntraj = 5, progress_bar = Val(false), rng = rng) @inferred mcsolve( H, ψ0, @@ -454,15 +441,7 @@ ) @inferred ssesolve(H, ψ0, tlist, c_ops_tuple, ntraj = 5, progress_bar = Val(true), rng = rng) @inferred ssesolve(H, ψ0, [0, 10], c_ops_tuple, ntraj = 5, progress_bar = Val(false), rng = rng) - @inferred ssesolve( - H, - tensor(Qobj(zeros(Int64, N)), Qobj([0, 1])), - tlist, - c_ops_tuple, - ntraj = 5, - progress_bar = Val(false), - rng = rng, - ) + @inferred ssesolve(H, ψ0_int, tlist, c_ops_tuple, ntraj = 5, progress_bar = Val(false), rng = rng) @inferred ssesolve( H, ψ0, From 504ac18fc145e355a5237244b098d71f30e07c06 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 2 Feb 2025 13:51:08 +0900 Subject: [PATCH 183/329] Bump version to v0.25.2 (#384) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2599d927a..c2d91a090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.25.2] +Release date: 2025-02-02 + - Move code quality dependencies to separate environment. ([#380]) - Add additional normalization of the state during time evolution of `ssesolve`. This improves the numerical stability of the solver. ([#383]) @@ -71,6 +74,7 @@ Release date: 2024-11-13 [v0.24.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.24.0 [v0.25.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.0 [v0.25.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.1 +[v0.25.2]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.2 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index ddf221a87..77aefefbf 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.25.1" +version = "0.25.2" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From f1176bdbb48e816dc1f2ae645a4fc56941bd6a90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:01:13 +0900 Subject: [PATCH 184/329] Bump crate-ci/typos from 1.29.4 to 1.29.5 (#385) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index e54d866c9..67aa62cf6 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.29.4 \ No newline at end of file + uses: crate-ci/typos@v1.29.5 \ No newline at end of file From 486e89649801d9ef66ebf3a8783391c9dc4b4b09 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 4 Feb 2025 18:38:33 +0100 Subject: [PATCH 185/329] Fix CUDA sparse_to_dense --- ext/QuantumToolboxCUDAExt.jl | 15 ++++++++------- src/qobj/functions.jl | 3 --- test/ext-test/gpu/cuda_ext.jl | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index c8258886b..7a90b27ba 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -2,10 +2,12 @@ module QuantumToolboxCUDAExt using QuantumToolbox using QuantumToolbox: makeVal, getVal -import CUDA: cu, CuArray -import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR +import CUDA: cu, CuArray, allowscalar +import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR, AbstractCuSparseArray import SparseArrays: SparseVector, SparseMatrixCSC +allowscalar(false) + @doc raw""" CuArray(A::QuantumObject) @@ -99,10 +101,9 @@ _change_eltype(::Type{T}, ::Val{32}) where {T<:AbstractFloat} = Float32 _change_eltype(::Type{Complex{T}}, ::Val{64}) where {T<:Union{Int,AbstractFloat}} = ComplexF64 _change_eltype(::Type{Complex{T}}, ::Val{32}) where {T<:Union{Int,AbstractFloat}} = ComplexF32 -sparse_to_dense(::Type{T}, A::CuArray{T}) where {T<:Number} = A -sparse_to_dense(::Type{T1}, A::CuArray{T2}) where {T1<:Number,T2<:Number} = CuArray{T1}(A) -sparse_to_dense(::Type{T}, A::CuSparseVector) where {T<:Number} = CuArray{T}(A) -sparse_to_dense(::Type{T}, A::CuSparseMatrixCSC) where {T<:Number} = CuArray{T}(A) -sparse_to_dense(::Type{T}, A::CuSparseMatrixCSR) where {T<:Number} = CuArray{T}(A) +QuantumToolbox.sparse_to_dense(A::MT) where {MT<:AbstractCuSparseArray} = CuArray(A) + +QuantumToolbox.sparse_to_dense(::Type{T1}, A::CuArray{T2}) where {T1<:Number,T2<:Number} = CuArray{T1}(A) +QuantumToolbox.sparse_to_dense(::Type{T}, A::AbstractCuSparseArray) where {T<:Number} = CuArray{T}(A) end diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 3ed38394e..8fd805b70 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -119,9 +119,6 @@ Converts a sparse QuantumObject to a dense QuantumObject. """ sparse_to_dense(A::QuantumObject) = QuantumObject(sparse_to_dense(A.data), A.type, A.dimensions) sparse_to_dense(A::MT) where {MT<:AbstractSparseArray} = Array(A) -for op in (:Transpose, :Adjoint) - @eval sparse_to_dense(A::$op{T,<:AbstractSparseMatrix}) where {T<:BlasFloat} = Array(A) -end sparse_to_dense(A::MT) where {MT<:AbstractArray} = A sparse_to_dense(::Type{T}, A::AbstractSparseArray) where {T<:Number} = Array{T}(A) diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index 5ed3ce18c..b0e982cd1 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -1,4 +1,7 @@ @testset "CUDA Extension" verbose = true begin + # Test that scalar indexing is disallowed + @test_throws ErrorException CUDA.rand(1)[1] + ψdi = Qobj(Int64[1, 0]) ψdf = Qobj(Float64[1, 0]) ψdc = Qobj(ComplexF64[1, 0]) @@ -63,6 +66,22 @@ @test typeof(CuSparseMatrixCSR(Xsc).data) == CuSparseMatrixCSR{ComplexF64,Int32} @test typeof(CuSparseMatrixCSR{ComplexF32}(Xsc).data) == CuSparseMatrixCSR{ComplexF32,Int32} + # Sparse To Dense + # @test sparse_to_dense(cu(ψsi; word_size = 64)).data isa CuVector{Int64} # TODO: Fix this in CUDA.jl + @test sparse_to_dense(cu(ψsf; word_size = 64)).data isa CuVector{Float64} + @test sparse_to_dense(cu(ψsc; word_size = 64)).data isa CuVector{ComplexF64} + # @test sparse_to_dense(cu(Xsi; word_size = 64)).data isa CuMatrix{Int64} # TODO: Fix this in CUDA.jl + @test sparse_to_dense(cu(Xsf; word_size = 64)).data isa CuMatrix{Float64} + @test sparse_to_dense(cu(Xsc; word_size = 64)).data isa CuMatrix{ComplexF64} + + # @test sparse_to_dense(Int32, cu(ψsf; word_size = 64)).data isa CuVector{Int32} # TODO: Fix this in CUDA.jl + # @test sparse_to_dense(Float32, cu(ψsf; word_size = 64)).data isa CuVector{Float32} # TODO: Fix this in CUDA.jl + # @test sparse_to_dense(ComplexF32, cu(ψsf; word_size = 64)).data isa CuVector{ComplexF32} # TODO: Fix this in CUDA.jl + # @test sparse_to_dense(Int64, cu(Xsf; word_size = 32)).data isa CuMatrix{Int64} # TODO: Fix this in CUDA.jl + # @test sparse_to_dense(Float64, cu(Xsf; word_size = 32)).data isa CuMatrix{Float64} # TODO: Fix this in CUDA.jl + # @test sparse_to_dense(ComplexF64, cu(Xsf; word_size = 32)).data isa CuMatrix{ComplexF64} # TODO: Fix this in CUDA.jl + + # brief example in README and documentation N = 20 ω64 = 1.0 # Float64 From 5bc12dd147cafff08d3f990cda5aa19d2beecc1d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 4 Feb 2025 18:39:48 +0100 Subject: [PATCH 186/329] [no ci] empty From 658bc372d6eaefb99ee7b728e33fbbf3a1c98093 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 4 Feb 2025 18:41:09 +0100 Subject: [PATCH 187/329] Format and add changelog --- CHANGELOG.md | 3 +++ test/ext-test/gpu/cuda_ext.jl | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d91a090..bac863ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Fix CUDA `sparse_to_dense`. ([#386]) + ## [v0.25.2] Release date: 2025-02-02 @@ -104,3 +106,4 @@ Release date: 2024-11-13 [#378]: https://github.com/qutip/QuantumToolbox.jl/issues/378 [#380]: https://github.com/qutip/QuantumToolbox.jl/issues/380 [#383]: https://github.com/qutip/QuantumToolbox.jl/issues/383 +[#386]: https://github.com/qutip/QuantumToolbox.jl/issues/386 diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index b0e982cd1..262ae88ed 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -81,7 +81,6 @@ # @test sparse_to_dense(Float64, cu(Xsf; word_size = 32)).data isa CuMatrix{Float64} # TODO: Fix this in CUDA.jl # @test sparse_to_dense(ComplexF64, cu(Xsf; word_size = 32)).data isa CuMatrix{ComplexF64} # TODO: Fix this in CUDA.jl - # brief example in README and documentation N = 20 ω64 = 1.0 # Float64 From 4832d37bff9d6189a034fecd74d2466f71d64147 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 5 Feb 2025 03:28:40 +0900 Subject: [PATCH 188/329] Update Benchmarks url in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1f12cf43..9131a3b3b 100644 --- a/README.md +++ b/README.md @@ -167,9 +167,9 @@ sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) ## Performance comparison with other packages -Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://albertomercurio.github.io/Lectures/QuantumToolbox.jl/package_comparison.html). +Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://github.com/albertomercurio/QuantumToolbox.jl-Paper-Figures/blob/main/src/benchmarks.jl). -![](https://albertomercurio.github.io/Lectures/QuantumToolbox.jl/package_comparison_files/figure-html/cell-12-output-1.svg) +![](https://raw.githubusercontent.com/albertomercurio/QuantumToolbox.jl-Paper-Figures/refs/heads/main/figures/benchmarks.svg) ## Contributing to QuantumToolbox.jl From 74a3c3e4d9e5b8aef8393972a07698e034351451 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Sat, 8 Feb 2025 05:32:50 +0100 Subject: [PATCH 189/329] Remove manual alloscalar set --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 97d4b9736..21d133dbe 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -70,7 +70,7 @@ if (GROUP == "CUDA_Ext")# || (GROUP == "All") using QuantumToolbox using CUDA using CUDA.CUSPARSE - CUDA.allowscalar(false) # Avoid unexpected scalar indexing + # CUDA.allowscalar(false) # This is already set in the extension script QuantumToolbox.about() CUDA.versioninfo() From c5ee41780da5d775071274305f40212c33328c87 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:23:45 +0900 Subject: [PATCH 190/329] Improve pseudo inverse spectrum (#388) Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> --- CHANGELOG.md | 2 ++ src/spectrum.jl | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bac863ac9..25e8f3888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Fix CUDA `sparse_to_dense`. ([#386]) +- Improve pseudo inverse spectrum solver. ([#388]) ## [v0.25.2] Release date: 2025-02-02 @@ -107,3 +108,4 @@ Release date: 2024-11-13 [#380]: https://github.com/qutip/QuantumToolbox.jl/issues/380 [#383]: https://github.com/qutip/QuantumToolbox.jl/issues/383 [#386]: https://github.com/qutip/QuantumToolbox.jl/issues/386 +[#388]: https://github.com/qutip/QuantumToolbox.jl/issues/388 diff --git a/src/spectrum.jl b/src/spectrum.jl index 77b690976..14bc88893 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -125,19 +125,20 @@ function _spectrum( _tr = SparseVector(D^2, [1 + n * (D + 1) for n in 0:(D-1)], ones(_CType(L), D)) # same as vec(system_identity_matrix) _tr_A = transpose(_tr) * spre(A).data - cache = nothing - I_cache = I(D^2) + Id = I(D^2) + + # DO the idx = 1 case + ω = ωList[1] + cache = init(LinearProblem(L.data - 1im * ω * Id, b), solver.alg, kwargs...) + sol = solve!(cache) + spec[1] = -2 * real(dot(_tr_A, sol.u)) + popfirst!(ωList) for (idx, ω) in enumerate(ωList) - if idx == 1 - cache = init(LinearProblem(L.data - 1im * ω * I_cache, b), solver.alg, kwargs...) - sol = solve!(cache) - else - cache.A = L.data - 1im * ω * I_cache - sol = solve!(cache) - end + cache.A = L.data - 1im * ω * Id + sol = solve!(cache) # trace over the Hilbert space of system (expectation value) - spec[idx] = -2 * real(dot(_tr_A, sol.u)) + spec[idx+1] = -2 * real(dot(_tr_A, sol.u)) end return spec From 1886ccaa8bd08d18c8d3149620a1ab59784bcfb3 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 9 Feb 2025 12:57:10 +0900 Subject: [PATCH 191/329] Add smesolve solver (#389) Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> --- .typos.toml | 3 +- CHANGELOG.md | 2 + docs/src/resources/api.md | 5 +- docs/src/users_guide/time_evolution/intro.md | 3 +- src/QuantumToolbox.jl | 1 + src/qobj/superoperators.jl | 34 +- src/time_evolution/mcsolve.jl | 81 +--- src/time_evolution/mesolve.jl | 8 +- src/time_evolution/smesolve.jl | 375 +++++++++++++++++++ src/time_evolution/ssesolve.jl | 57 ++- src/time_evolution/time_evolution.jl | 127 ++++++- test/core-test/time_evolution.jl | 199 +++++++++- 12 files changed, 757 insertions(+), 138 deletions(-) create mode 100644 src/time_evolution/smesolve.jl diff --git a/.typos.toml b/.typos.toml index 73648865c..e3264a3d3 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,2 +1,3 @@ [default.extend-words] -ket = "ket" \ No newline at end of file +ket = "ket" +sme = "sme" diff --git a/CHANGELOG.md b/CHANGELOG.md index 25e8f3888..08a281c88 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 CUDA `sparse_to_dense`. ([#386]) - Improve pseudo inverse spectrum solver. ([#388]) +- Add `smesolve` function for stochastic master equation. ([#389]) ## [v0.25.2] Release date: 2025-02-02 @@ -109,3 +110,4 @@ Release date: 2024-11-13 [#383]: https://github.com/qutip/QuantumToolbox.jl/issues/383 [#386]: https://github.com/qutip/QuantumToolbox.jl/issues/386 [#388]: https://github.com/qutip/QuantumToolbox.jl/issues/388 +[#389]: https://github.com/qutip/QuantumToolbox.jl/issues/389 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 1063f21f3..d3d09c3c1 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -189,17 +189,20 @@ cosm TimeEvolutionProblem TimeEvolutionSol TimeEvolutionMCSol -TimeEvolutionSSESol +TimeEvolutionStochasticSol sesolveProblem mesolveProblem mcsolveProblem mcsolveEnsembleProblem ssesolveProblem ssesolveEnsembleProblem +smesolveProblem +smesolveEnsembleProblem sesolve mesolve mcsolve ssesolve +smesolve dfd_mesolve liouvillian liouvillian_generalized diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index 3b9ccf985..aabbb25a0 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -36,7 +36,8 @@ The following table lists the solvers provided by `QuantumToolbox` for dynamic q | Unitary evolution, Schrödinger equation | [`sesolve`](@ref) | [`sesolveProblem`](@ref) | [`TimeEvolutionSol`](@ref) | | Lindblad master eqn. or Von Neuman eqn.| [`mesolve`](@ref) | [`mesolveProblem`](@ref) | [`TimeEvolutionSol`](@ref) | | Monte Carlo evolution | [`mcsolve`](@ref) | [`mcsolveProblem`](@ref) [`mcsolveEnsembleProblem`](@ref) | [`TimeEvolutionMCSol`](@ref) | -| Stochastic Schrödinger equation | [`ssesolve`](@ref) | [`ssesolveProblem`](@ref) [`ssesolveEnsembleProblem`](@ref) | [`TimeEvolutionSSESol`](@ref) | +| Stochastic Schrödinger equation | [`ssesolve`](@ref) | [`ssesolveProblem`](@ref) [`ssesolveEnsembleProblem`](@ref) | [`TimeEvolutionStochasticSol`](@ref) | +| Stochastic master equation | [`smesolve`](@ref) | [`smesolveProblem`](@ref) [`smesolveEnsembleProblem`](@ref) | [`TimeEvolutionStochasticSol`](@ref) | !!! note "Solving dynamics with pre-defined problems" `QuantumToolbox` provides two different methods to solve the dynamics. One can use the function calls listed above by either taking all the operators (like Hamiltonian and collapse operators, etc.) as inputs directly, or generating the `prob`lems by yourself and take it as an input of the function call, e.g., `sesolve(prob)`. diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 3677bb178..4776c0d43 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -106,6 +106,7 @@ include("time_evolution/lr_mesolve.jl") include("time_evolution/sesolve.jl") include("time_evolution/mcsolve.jl") include("time_evolution/ssesolve.jl") +include("time_evolution/smesolve.jl") include("time_evolution/time_evolution_dynamical.jl") # Others diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index 7fe381478..affc0a980 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -164,22 +164,34 @@ function liouvillian( ) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L = liouvillian(H, Id_cache) if !(c_ops isa Nothing) - # sum all the (time-independent) c_ops first - c_ops_ti = filter(op -> isa(op, QuantumObject), c_ops) - if !isempty(c_ops_ti) - L += mapreduce(op -> lindblad_dissipator(op, Id_cache), +, c_ops_ti) - end - - # sum rest of the QobjEvo together - c_ops_td = filter(op -> isa(op, QuantumObjectEvolution), c_ops) - if !isempty(c_ops_td) - L += mapreduce(op -> lindblad_dissipator(op, Id_cache), +, c_ops_td) - end + L += _sum_lindblad_dissipators(c_ops, Id_cache) end return L end +liouvillian(H::Nothing, c_ops::Union{AbstractVector,Tuple}, Id_cache::Diagonal = I(prod(c_ops[1].dims))) = + _sum_lindblad_dissipators(c_ops, Id_cache) + +liouvillian(H::Nothing, c_ops::Nothing) = 0 + liouvillian(H::AbstractQuantumObject{OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dimensions))) = get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator, H.dimensions) liouvillian(H::AbstractQuantumObject{SuperOperatorQuantumObject}, Id_cache::Diagonal) = H + +function _sum_lindblad_dissipators(c_ops, Id_cache::Diagonal) + D = 0 + # sum all the (time-independent) c_ops first + c_ops_ti = filter(op -> isa(op, QuantumObject), c_ops) + if !isempty(c_ops_ti) + D += mapreduce(op -> lindblad_dissipator(op, Id_cache), +, c_ops_ti) + end + + # sum rest of the QobjEvo together + c_ops_td = filter(op -> isa(op, QuantumObjectEvolution), c_ops) + if !isempty(c_ops_td) + D += mapreduce(op -> lindblad_dissipator(op, Id_cache), +, c_ops_td) + end + + return D +end diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 94c0372f8..7c006f8e8 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -12,11 +12,6 @@ function _mcsolve_prob_func(prob, i, repeat, global_rng, seeds, tlist) return remake(prob, f = f, callback = cb) end -function _mcsolve_dispatch_prob_func(rng, ntraj, tlist) - seeds = map(i -> rand(rng, UInt64), 1:ntraj) - return (prob, i, repeat) -> _mcsolve_prob_func(prob, i, repeat, rng, seeds, tlist) -end - # Standard output function function _mcsolve_output_func(sol, i) idx = _mc_get_jump_callback(sol).affect!.jump_times_which_idx[] @@ -25,43 +20,6 @@ function _mcsolve_output_func(sol, i) return (sol, false) end -# Output function with progress bar update -function _mcsolve_output_func_progress(sol, i, progr) - next!(progr) - return _mcsolve_output_func(sol, i) -end - -# Output function with distributed channel update for progress bar -function _mcsolve_output_func_distributed(sol, i, channel) - put!(channel, true) - return _mcsolve_output_func(sol, i) -end - -function _mcsolve_dispatch_output_func(::ET, progress_bar, ntraj) where {ET<:Union{EnsembleSerial,EnsembleThreads}} - if getVal(progress_bar) - progr = ProgressBar(ntraj, enable = getVal(progress_bar)) - f = (sol, i) -> _mcsolve_output_func_progress(sol, i, progr) - return (f, progr, nothing) - else - return (_mcsolve_output_func, nothing, nothing) - end -end -function _mcsolve_dispatch_output_func( - ::ET, - progress_bar, - ntraj, -) where {ET<:Union{EnsembleSplitThreads,EnsembleDistributed}} - if getVal(progress_bar) - progr = ProgressBar(ntraj, enable = getVal(progress_bar)) - progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) - - f = (sol, i) -> _mcsolve_output_func_distributed(sol, i, progr_channel) - return (f, progr, progr_channel) - else - return (_mcsolve_output_func, nothing, nothing) - end -end - function _normalize_state!(u, dims, normalize_states) getVal(normalize_states) && normalize!(u) return QuantumObject(u, type = Ket, dims = dims) @@ -280,9 +238,10 @@ function mcsolveEnsembleProblem( output_func::Union{Tuple,Nothing} = nothing, kwargs..., ) where {TJC<:LindbladJumpCallbackType} - _prob_func = prob_func isa Nothing ? _mcsolve_dispatch_prob_func(rng, ntraj, tlist) : prob_func + _prob_func = isnothing(prob_func) ? _ensemble_dispatch_prob_func(rng, ntraj, tlist, _mcsolve_prob_func) : prob_func _output_func = - output_func isa Nothing ? _mcsolve_dispatch_output_func(ensemble_method, progress_bar, ntraj) : output_func + output_func isa Nothing ? + _ensemble_dispatch_output_func(ensemble_method, progress_bar, ntraj, _mcsolve_output_func) : output_func prob_mc = mcsolveProblem( H, @@ -431,38 +390,6 @@ function mcsolve( return mcsolve(ens_prob_mc, alg, ntraj, ensemble_method, normalize_states) end -function _mcsolve_solve_ens( - ens_prob_mc::TimeEvolutionProblem, - alg::OrdinaryDiffEqAlgorithm, - ensemble_method::ET, - ntraj::Int, -) where {ET<:Union{EnsembleSplitThreads,EnsembleDistributed}} - sol = nothing - - @sync begin - @async while take!(ens_prob_mc.kwargs.channel) - next!(ens_prob_mc.kwargs.progr) - end - - @async begin - sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) - put!(ens_prob_mc.kwargs.channel, false) - end - end - - return sol -end - -function _mcsolve_solve_ens( - ens_prob_mc::TimeEvolutionProblem, - alg::OrdinaryDiffEqAlgorithm, - ensemble_method, - ntraj::Int, -) - sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) - return sol -end - function mcsolve( ens_prob_mc::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -470,7 +397,7 @@ function mcsolve( ensemble_method = EnsembleThreads(), normalize_states = Val(true), ) - sol = _mcsolve_solve_ens(ens_prob_mc, alg, ensemble_method, ntraj) + sol = _ensemble_dispatch_solve(ens_prob_mc, alg, ensemble_method, ntraj) dims = ens_prob_mc.dimensions _sol_1 = sol[:, 1] diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index ae1a40667..a87bc3864 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -1,7 +1,9 @@ export mesolveProblem, mesolve -_mesolve_make_L_QobjEvo(H::QuantumObject, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator) +_mesolve_make_L_QobjEvo(H::Union{QuantumObject,Nothing}, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator) _mesolve_make_L_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}, c_ops) = liouvillian(QobjEvo(H), c_ops) +_mesolve_make_L_QobjEvo(H::Nothing, c_ops::Nothing) = throw(ArgumentError("Both H and +c_ops are Nothing. You are probably running the wrong function.")) @doc raw""" mesolveProblem( @@ -31,7 +33,7 @@ where # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. -- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. @@ -119,7 +121,7 @@ where # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. -- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm for the ODE solver. The default value is `Tsit5()`. diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl new file mode 100644 index 000000000..904951d42 --- /dev/null +++ b/src/time_evolution/smesolve.jl @@ -0,0 +1,375 @@ +export smesolveProblem, smesolveEnsembleProblem, smesolve + +_smesolve_generate_state(u, dims) = QuantumObject(vec2mat(u), type = Operator, dims = dims) + +function _smesolve_update_coeff(u, p, t, op_vec) + return real(dot(u, op_vec)) / 2 #this is Tr[Sn * ρ + ρ * Sn'] +end + +_smesolve_ScalarOperator(op_vec) = + ScalarOperator(one(eltype(op_vec)), (a, u, p, t) -> -_smesolve_update_coeff(u, p, t, op_vec)) + +@doc raw""" + smesolveProblem( + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, + tlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) + +Generate the SDEProblem for the Stochastic Master Equation time evolution of an open quantum system. This is defined by the following stochastic differential equation: + +```math +d| \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_n \mathcal{D}[\hat{C}_n] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), +``` + +where + +```math +\mathcal{D}[\hat{O}] \rho = \hat{O} \rho \hat{O}^\dagger - \frac{1}{2} \{\hat{O}^\dagger \hat{O}, \rho\}, +``` + +is the Lindblad superoperator, and + +```math +\mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, + +Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. + +# Arguments + +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of measurement collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. + +# Notes + +- The states will be saved depend on the keyword argument `saveat` in `kwargs`. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. +- The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. +- For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) + +# Returns + +- `prob`: The [`TimeEvolutionProblem`](@ref) containing the `SDEProblem` for the Stochastic Master Equation time evolution. +""" +function smesolveProblem( + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, + tlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., +) + haskey(kwargs, :save_idxs) && + throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) + + isnothing(sc_ops) && + throw(ArgumentError("The list of measurement collapse operators must be provided. Use mesolveProblem instead.")) + + tlist = _check_tlist(tlist, _FType(ψ0)) + + L_evo = _mesolve_make_L_QobjEvo(H, c_ops) + _mesolve_make_L_QobjEvo(nothing, sc_ops) + check_dimensions(L_evo, ψ0) + dims = L_evo.dimensions + + T = Base.promote_eltype(L_evo, ψ0) + ρ0 = sparse_to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + + progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) + + sc_ops_evo_data = Tuple(map(get_data ∘ QobjEvo, sc_ops)) + + K = get_data(L_evo) + + Id = I(prod(dims)) + D_l = map(sc_ops_evo_data) do op + # TODO: Implement the three-argument dot function for SciMLOperators.jl + # Currently, we are assuming a time-independent MatrixOperator + op_vec = mat2vec(adjoint(op.A)) + return _spre(op, Id) + _spost(op', Id) + _smesolve_ScalarOperator(op_vec) * IdentityOperator(prod(dims)^2) + end + D = DiffusionOperator(D_l) + + p = (progr = progr, times = tlist, Hdims = dims, n_sc_ops = length(sc_ops), params...) + + is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) + + saveat = is_empty_e_ops ? tlist : [tlist[end]] + default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) + kwargs2 = merge(default_values, kwargs) + kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncMESolve) + + tspan = (tlist[1], tlist[end]) + noise = + RealWienerProcess!(tlist[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) + noise_rate_prototype = similar(ρ0, length(ρ0), length(sc_ops)) + prob = SDEProblem{true}(K, D, ρ0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, kwargs3...) + + return TimeEvolutionProblem(prob, tlist, dims) +end + +@doc raw""" + smesolveEnsembleProblem( + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, + tlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + prob_func::Union{Function, Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) + +Generate the SDEProblem for the Stochastic Master Equation time evolution of an open quantum system. This is defined by the following stochastic differential equation: + +```math +d| \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_n \mathcal{D}[\hat{C}_n] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), +``` + +where + +```math +\mathcal{D}[\hat{O}] \rho = \hat{O} \rho \hat{O}^\dagger - \frac{1}{2} \{\hat{O}^\dagger \hat{O}, \rho\}, +``` + +is the Lindblad superoperator, and + +```math +\mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, + +Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. + +# Arguments + +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of measurement collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `ntraj`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. + +# Notes + +- The states will be saved depend on the keyword argument `saveat` in `kwargs`. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. +- The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. +- For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) + +# Returns + +- `prob`: The [`TimeEvolutionProblem`](@ref) containing the Ensemble `SDEProblem` for the Stochastic Master Equation time evolution. +""" +function smesolveEnsembleProblem( + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, + tlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + prob_func::Union{Function,Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., +) + _prob_func = + isnothing(prob_func) ? _ensemble_dispatch_prob_func(rng, ntraj, tlist, _stochastic_prob_func) : prob_func + _output_func = + output_func isa Nothing ? + _ensemble_dispatch_output_func(ensemble_method, progress_bar, ntraj, _stochastic_output_func) : output_func + + prob_sme = smesolveProblem( + H, + ψ0, + tlist, + c_ops, + sc_ops; + e_ops = e_ops, + params = params, + rng = rng, + progress_bar = Val(false), + kwargs..., + ) + + ensemble_prob = TimeEvolutionProblem( + EnsembleProblem(prob_sme, prob_func = _prob_func, output_func = _output_func[1], safetycopy = true), + prob_sme.times, + prob_sme.dimensions, + (progr = _output_func[2], channel = _output_func[3]), + ) + + return ensemble_prob +end + +@doc raw""" + smesolve( + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, + tlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + alg::StochasticDiffEqAlgorithm = SRA1(), + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + prob_func::Union{Function, Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., + ) + +Stochastic Master Equation time evolution of an open quantum system. This is defined by the following stochastic differential equation: + +```math +d| \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_n \mathcal{D}[\hat{C}_n] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), +``` + +where + +```math +\mathcal{D}[\hat{O}] \rho = \hat{O} \rho \hat{O}^\dagger - \frac{1}{2} \{\hat{O}^\dagger \hat{O}, \rho\}, +``` + +is the Lindblad superoperator, and + +```math +\mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, + +Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. + +# Arguments + +- `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of measurement collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `params`: `NamedTuple` of parameters to pass to the solver. +- `rng`: Random number generator for reproducibility. +- `ntraj`: Number of trajectories to use. +- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `prob_func`: Function to use for generating the ODEProblem. +- `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. +- `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `kwargs`: The keyword arguments for the ODEProblem. + +# Notes + +- The states will be saved depend on the keyword argument `saveat` in `kwargs`. +- If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. +- The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. +- For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) + +# Returns + +- `sol::TimeEvolutionStochasticSol`: The solution of the time evolution. See [`TimeEvolutionStochasticSol`](@ref). +""" +function smesolve( + H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + ψ0::QuantumObject{KetQuantumObject}, + tlist::AbstractVector, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + alg::StochasticDiffEqAlgorithm = SRA1(), + e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, + params::NamedTuple = NamedTuple(), + rng::AbstractRNG = default_rng(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), + prob_func::Union{Function,Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, + progress_bar::Union{Val,Bool} = Val(true), + kwargs..., +) + ensemble_prob = smesolveEnsembleProblem( + H, + ψ0, + tlist, + c_ops, + sc_ops; + e_ops = e_ops, + params = params, + rng = rng, + ntraj = ntraj, + ensemble_method = ensemble_method, + prob_func = prob_func, + output_func = output_func, + progress_bar = progress_bar, + kwargs..., + ) + + return smesolve(ensemble_prob, alg, ntraj, ensemble_method) +end + +function smesolve( + ens_prob::TimeEvolutionProblem, + alg::StochasticDiffEqAlgorithm = SRA1(), + ntraj::Int = 1, + ensemble_method = EnsembleThreads(), +) + sol = _ensemble_dispatch_solve(ens_prob, alg, ensemble_method, ntraj) + + _sol_1 = sol[:, 1] + _expvals_sol_1 = _se_me_sse_get_expvals(_sol_1) + + normalize_states = Val(false) + dims = ens_prob.dimensions + _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) + expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) + states = map(i -> _smesolve_generate_state.(sol[:, i].u, Ref(dims)), eachindex(sol)) + + expvals = + _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : + dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) + + return TimeEvolutionStochasticSol( + ntraj, + ens_prob.times, + states, + expvals, + expvals_all, + sol.converged, + _sol_1.alg, + _sol_1.prob.kwargs[:abstol], + _sol_1.prob.kwargs[:reltol], + ) +end diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index e827db017..0b8ec2146 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -1,5 +1,25 @@ export ssesolveProblem, ssesolveEnsembleProblem, ssesolve +# TODO: Merge this with _stochastic_prob_func +function _ssesolve_prob_func(prob, i, repeat) + internal_params = prob.p + + global_rng = internal_params.global_rng + seed = internal_params.seeds[i] + traj_rng = typeof(global_rng)() + seed!(traj_rng, seed) + + noise = RealWienerProcess!( + prob.tspan[1], + zeros(internal_params.n_sc_ops), + zeros(internal_params.n_sc_ops), + save_everystep = false, + rng = traj_rng, + ) + + return remake(prob, noise = noise, seed = seed) +end + #= struct DiffusionOperator @@ -46,41 +66,18 @@ function _ssesolve_update_coeff(u, p, t, op) return real(dot(u, op.A, u)) #this is en/2: /2 = Re end -function _ssesolve_prob_func(prob, i, repeat) - internal_params = prob.p - - global_rng = internal_params.global_rng - seed = internal_params.seeds[i] - traj_rng = typeof(global_rng)() - seed!(traj_rng, seed) - - noise = RealWienerProcess!( - prob.tspan[1], - zeros(internal_params.n_sc_ops), - zeros(internal_params.n_sc_ops), - save_everystep = false, - rng = traj_rng, - ) - - return remake(prob, noise = noise, seed = seed) -end - -# Standard output function -_ssesolve_output_func(sol, i) = (sol, false) - # Output function with progress bar update function _ssesolve_output_func_progress(sol, i) next!(sol.prob.p.progr) - return _ssesolve_output_func(sol, i) + return _stochastic_output_func(sol, i) end # Output function with distributed channel update for progress bar function _ssesolve_output_func_distributed(sol, i) put!(sol.prob.p.progr_channel, true) - return _ssesolve_output_func(sol, i) + return _stochastic_output_func(sol, i) end -_ssesolve_dispatch_output_func() = _ssesolve_output_func _ssesolve_dispatch_output_func(::ET) where {ET<:Union{EnsembleSerial,EnsembleThreads}} = _ssesolve_output_func_progress _ssesolve_dispatch_output_func(::EnsembleDistributed) = _ssesolve_output_func_distributed @@ -121,7 +118,7 @@ and e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. ``` -Above, `\hat{C}_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `\hat{C}_n`. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{C}_n`` is the `n`-th collapse operator and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{C}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments @@ -240,7 +237,7 @@ and e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. ``` -Above, `\hat{C}_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `\hat{C}_n`. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{C}_n`` is the `n`-th collapse operator and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{C}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments @@ -363,7 +360,7 @@ and e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. ``` -Above, `\hat{C}_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wiener increment associated to `\hat{C}_n`. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{C}_n`` is the `n`-th collapse operator and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{C}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments @@ -393,7 +390,7 @@ Above, `\hat{C}_n` is the `n`-th collapse operator and `dW_j(t)` is the real Wie # Returns -- `sol::TimeEvolutionSSESol`: The solution of the time evolution. See also [`TimeEvolutionSSESol`](@ref). +- `sol::TimeEvolutionStochasticSol`: The solution of the time evolution. See [`TimeEvolutionStochasticSol`](@ref). """ function ssesolve( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, @@ -458,7 +455,7 @@ function ssesolve( _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) - return TimeEvolutionSSESol( + return TimeEvolutionStochasticSol( ntraj, _sol_1.prob.p.times, states, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 39d5d1641..722463101 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -1,4 +1,4 @@ -export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionSSESol +export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionStochasticSol export liouvillian_floquet, liouvillian_generalized @@ -149,9 +149,9 @@ function Base.show(io::IO, sol::TimeEvolutionMCSol) end @doc raw""" - struct TimeEvolutionSSESol + struct TimeEvolutionStochasticSol -A structure storing the results and some information from solving trajectories of the Stochastic Shrodinger equation time evolution. +A structure storing the results and some information from solving trajectories of the Stochastic time evolution. # Fields (Attributes) @@ -165,7 +165,7 @@ A structure storing the results and some information from solving trajectories o - `abstol::Real`: The absolute tolerance which is used during the solving process. - `reltol::Real`: The relative tolerance which is used during the solving process. """ -struct TimeEvolutionSSESol{ +struct TimeEvolutionStochasticSol{ TT<:AbstractVector{<:Real}, TS<:AbstractVector, TE<:Union{AbstractMatrix,Nothing}, @@ -185,8 +185,8 @@ struct TimeEvolutionSSESol{ reltol::RT end -function Base.show(io::IO, sol::TimeEvolutionSSESol) - print(io, "Solution of quantum trajectories\n") +function Base.show(io::IO, sol::TimeEvolutionStochasticSol) + print(io, "Solution of stochastic quantum trajectories\n") print(io, "(converged: $(sol.converged))\n") print(io, "--------------------------------\n") print(io, "num_trajectories = $(sol.ntraj)\n") @@ -202,6 +202,11 @@ function Base.show(io::IO, sol::TimeEvolutionSSESol) return nothing end +####################################### +#= + Callbacks for Monte Carlo quantum trajectories +=# + abstract type LindbladJumpCallbackType end struct ContinuousLindbladJumpCallback <: LindbladJumpCallbackType @@ -225,6 +230,116 @@ function _check_tlist(tlist, T::Type) return tlist2 end +####################################### +#= +Helpers for handling output of ensemble problems. +This is very useful especially for dispatching which method to use to update the progress bar. +=# + +# Output function with progress bar update +function _ensemble_output_func_progress(sol, i, progr, output_func) + next!(progr) + return output_func(sol, i) +end + +# Output function with distributed channel update for progress bar +function _ensemble_output_func_distributed(sol, i, channel, output_func) + put!(channel, true) + return output_func(sol, i) +end + +function _ensemble_dispatch_output_func( + ::ET, + progress_bar, + ntraj, + output_func, +) where {ET<:Union{EnsembleSerial,EnsembleThreads}} + if getVal(progress_bar) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + f = (sol, i) -> _ensemble_output_func_progress(sol, i, progr, output_func) + return (f, progr, nothing) + else + return (output_func, nothing, nothing) + end +end +function _ensemble_dispatch_output_func( + ::ET, + progress_bar, + ntraj, + output_func, +) where {ET<:Union{EnsembleSplitThreads,EnsembleDistributed}} + if getVal(progress_bar) + progr = ProgressBar(ntraj, enable = getVal(progress_bar)) + progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) + + f = (sol, i) -> _ensemble_output_func_distributed(sol, i, progr_channel, output_func) + return (f, progr, progr_channel) + else + return (output_func, nothing, nothing) + end +end + +function _ensemble_dispatch_prob_func(rng, ntraj, tlist, prob_func) + seeds = map(i -> rand(rng, UInt64), 1:ntraj) + return (prob, i, repeat) -> prob_func(prob, i, repeat, rng, seeds, tlist) +end + +function _ensemble_dispatch_solve( + ens_prob_mc::TimeEvolutionProblem, + alg::Union{<:OrdinaryDiffEqAlgorithm,<:StochasticDiffEqAlgorithm}, + ensemble_method::ET, + ntraj::Int, +) where {ET<:Union{EnsembleSplitThreads,EnsembleDistributed}} + sol = nothing + + @sync begin + @async while take!(ens_prob_mc.kwargs.channel) + next!(ens_prob_mc.kwargs.progr) + end + + @async begin + sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) + put!(ens_prob_mc.kwargs.channel, false) + end + end + + return sol +end +function _ensemble_dispatch_solve( + ens_prob_mc::TimeEvolutionProblem, + alg::Union{<:OrdinaryDiffEqAlgorithm,<:StochasticDiffEqAlgorithm}, + ensemble_method, + ntraj::Int, +) + sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) + return sol +end + +####################################### +#= + Stochastic funcs +=# +function _stochastic_prob_func(prob, i, repeat, rng, seeds, tlist) + params = prob.prob.p + + seed = seeds[i] + traj_rng = typeof(rng)() + seed!(traj_rng, seed) + + noise = RealWienerProcess!( + prob.prob.tspan[1], + zeros(params.n_sc_ops), + zeros(params.n_sc_ops), + save_everystep = false, + rng = traj_rng, + ) + + return remake(prob.prob, noise = noise, seed = seed) +end + +# Standard output function +_stochastic_output_func(sol, i) = (sol, false) + ####################################### function liouvillian_floquet( diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index a1b852e6a..e09690365 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -18,6 +18,10 @@ e_ops = [a' * a, σz] c_ops = [sqrt(γ * (1 + nth)) * a, sqrt(γ * nth) * a', sqrt(γ * (1 + nth)) * σm, sqrt(γ * nth) * σm'] + sme_η = 0.7 # Efficiency of the homodyne detector for smesolve + c_ops_sme = [sqrt(1 - sme_η) * op for op in c_ops] + sc_ops_sme = [sqrt(sme_η) * op for op in c_ops] + ψ0_int = Qobj(round.(Int, real.(ψ0.data)), dims = ψ0.dims) # Used for testing the type inference @testset "sesolve" begin @@ -93,7 +97,7 @@ end end - @testset "mesolve, mcsolve, and ssesolve" begin + @testset "mesolve, mcsolve, ssesolve and smesolve" begin tlist = range(0, 10 / γ, 100) prob_me = mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) @@ -124,6 +128,7 @@ jump_callback = DiscreteLindbladJumpCallback(), ) sol_sse = ssesolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_sme = smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) @@ -137,13 +142,15 @@ sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) sol_mc_string_states = sprint((t, s) -> show(t, "text/plain", s), sol_mc_states) sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) + sol_sme_string = sprint((t, s) -> show(t, "text/plain", s), sol_sme) @test prob_me.prob.f.f isa MatrixOperator @test prob_mc.prob.f.f isa MatrixOperator - @test sum(abs.(sol_mc.expect .- sol_me.expect)) / length(tlist) < 0.1 - @test sum(abs.(sol_mc2.expect .- sol_me.expect)) / length(tlist) < 0.1 - @test sum(abs.(vec(expect_mc_states_mean) .- vec(sol_me.expect[1, :]))) / length(tlist) < 0.1 - @test sum(abs.(vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, :]))) / length(tlist) < 0.1 - @test sum(abs.(sol_sse.expect .- sol_me.expect)) / length(tlist) < 0.1 + @test sum(abs, sol_mc.expect .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, sol_mc2.expect .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, :])) / length(tlist) < 0.1 + @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, :])) / length(tlist) < 0.1 + @test sum(abs, sol_sse.expect .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, sol_sme.expect .- sol_me.expect) / length(tlist) < 0.1 @test length(sol_me.times) == length(tlist) @test length(sol_me.states) == 1 @test size(sol_me.expect) == (length(e_ops), length(tlist)) @@ -159,6 +166,8 @@ @test sol_mc_states.expect === nothing @test length(sol_sse.times) == length(tlist) @test size(sol_sse.expect) == (length(e_ops), length(tlist)) + @test length(sol_sme.times) == length(tlist) + @test size(sol_sme.expect) == (length(e_ops), length(tlist)) @test sol_me_string == "Solution of time evolution\n" * "(return code: $(sol_me.retcode))\n" * @@ -189,7 +198,7 @@ "abstol = $(sol_mc_states.abstol)\n" * "reltol = $(sol_mc_states.reltol)\n" @test sol_sse_string == - "Solution of quantum trajectories\n" * + "Solution of stochastic quantum trajectories\n" * "(converged: $(sol_sse.converged))\n" * "--------------------------------\n" * "num_trajectories = $(sol_sse.ntraj)\n" * @@ -198,6 +207,16 @@ "SDE alg.: $(sol_sse.alg)\n" * "abstol = $(sol_sse.abstol)\n" * "reltol = $(sol_sse.reltol)\n" + @test sol_sme_string == + "Solution of stochastic quantum trajectories\n" * + "(converged: $(sol_sme.converged))\n" * + "--------------------------------\n" * + "num_trajectories = $(sol_sme.ntraj)\n" * + "num_states = $(length(sol_sme.states[1]))\n" * + "num_expect = $(size(sol_sme.expect, 1))\n" * + "SDE alg.: $(sol_sme.alg)\n" * + "abstol = $(sol_sme.abstol)\n" * + "reltol = $(sol_sme.reltol)\n" tlist1 = Float64[] tlist2 = [0, 0.2, 0.1] @@ -211,6 +230,9 @@ @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)) + @test_throws ArgumentError smesolve(H, ψ0, tlist1, c_ops_sme, sc_ops_sme, progress_bar = Val(false)) + @test_throws ArgumentError smesolve(H, ψ0, tlist2, c_ops_sme, sc_ops_sme, progress_bar = Val(false)) + @test_throws ArgumentError smesolve(H, ψ0, tlist3, c_ops_sme, sc_ops_sme, 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. @@ -364,6 +386,52 @@ @test allocs_tot < 570000 # TODO: Fix this high number of allocations end + @testset "Memory Allocations (smesolve)" begin + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + e_ops = e_ops, + ntraj = 100, + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + e_ops = e_ops, + ntraj = 100, + progress_bar = Val(false), + ) + @test allocs_tot < 2710000 # TODO: Fix this high number of allocations + + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 100, + saveat = [tlist[end]], + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 100, + saveat = [tlist[end]], + progress_bar = Val(false), + ) + @test allocs_tot < 570000 # TODO: Fix this high number of allocations + end + @testset "Type Inference mesolve" begin coef(p, t) = exp(-t) ad_t = QobjEvo(a', coef) @@ -465,17 +533,125 @@ ) end - @testset "mcsolve and ssesolve reproducibility" begin + @testset "Type Inference smesolve" begin + c_ops_sme_tuple = Tuple(c_ops_sme) # To avoid type instability, we must have a Tuple instead of a Vector + sc_ops_sme_tuple = Tuple(sc_ops_sme) # To avoid type instability, we must have a Tuple instead of a Vector + @inferred smesolveEnsembleProblem( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + progress_bar = Val(true), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + [0, 10], + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0_int, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + e_ops = (a' * a, a'), + progress_bar = Val(false), + rng = rng, + ) # We test the type inference for Tuple of different types + end + + @testset "mcsolve, ssesolve and smesolve reproducibility" begin rng = MersenneTwister(1234) sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) sol_sse1 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sme1 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 50, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) rng = MersenneTwister(1234) sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) sol_sse2 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sme2 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 50, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) rng = MersenneTwister(1234) sol_mc3 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sse3 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 60, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sme3 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 60, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 @test sol_mc1.expect_all ≈ sol_mc2.expect_all atol = 1e-10 @@ -486,6 +662,13 @@ @test sol_sse1.expect ≈ sol_sse2.expect atol = 1e-10 @test sol_sse1.expect_all ≈ sol_sse2.expect_all atol = 1e-10 + + @test sol_sse1.expect_all ≈ sol_sse3.expect_all[:, :, 1:50] atol = 1e-10 + + @test sol_sme1.expect ≈ sol_sme2.expect atol = 1e-10 + @test sol_sme1.expect_all ≈ sol_sme2.expect_all atol = 1e-10 + + @test sol_sme1.expect_all ≈ sol_sme3.expect_all[:, :, 1:50] atol = 1e-10 end end From 8c728f08107c8ef5dcbe006b8d044eed907fdd70 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 9 Feb 2025 13:04:52 +0900 Subject: [PATCH 192/329] Bump to v0.26.0 (#390) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08a281c88..8553614a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.26.0] +Release date: 2025-02-09 + - Fix CUDA `sparse_to_dense`. ([#386]) - Improve pseudo inverse spectrum solver. ([#388]) - Add `smesolve` function for stochastic master equation. ([#389]) @@ -79,6 +82,7 @@ Release date: 2024-11-13 [v0.25.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.0 [v0.25.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.1 [v0.25.2]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.2 +[v0.26.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.26.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 77aefefbf..54b5d80ad 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.25.2" +version = "0.26.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 162c76be2c6f9db79d653a077aa19727b78e4c63 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:41:36 +0900 Subject: [PATCH 193/329] Rename function `sparse_to_dense` as `to_dense` and `dense_to_sparse` as `to_sparse` (#392) --- CHANGELOG.md | 3 ++ docs/src/resources/api.md | 4 +- .../QuantumObject/QuantumObject.md | 6 +-- ext/QuantumToolboxCUDAExt.jl | 6 +-- src/deprecated.jl | 8 ++++ src/qobj/arithmetic_and_attributes.jl | 8 ++-- src/qobj/eigsolve.jl | 8 ++-- src/qobj/functions.jl | 28 +++++++------- src/qobj/operators.jl | 4 +- src/qobj/quantum_object.jl | 2 +- src/qobj/quantum_object_evo.jl | 2 +- src/time_evolution/mesolve.jl | 2 +- src/time_evolution/sesolve.jl | 2 +- src/time_evolution/smesolve.jl | 2 +- src/time_evolution/ssesolve.jl | 2 +- src/time_evolution/time_evolution.jl | 14 +++---- src/wigner.jl | 2 +- test/core-test/eigenvalues_and_operators.jl | 2 +- test/core-test/generalized_master_equation.jl | 20 +++++----- .../negativity_and_partial_transpose.jl | 2 +- test/core-test/quantum_objects.jl | 33 +++++++++------- test/core-test/states_and_operators.jl | 2 +- test/core-test/wigner.jl | 2 +- test/ext-test/gpu/cuda_ext.jl | 38 +++++++++---------- 24 files changed, 109 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8553614a0..e11ac2e8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Rename `sparse_to_dense` as `to_dense` and `dense_to_sparse` as `to_sparse`. ([#392]) + ## [v0.26.0] Release date: 2025-02-09 @@ -115,3 +117,4 @@ Release date: 2024-11-13 [#386]: https://github.com/qutip/QuantumToolbox.jl/issues/386 [#388]: https://github.com/qutip/QuantumToolbox.jl/issues/388 [#389]: https://github.com/qutip/QuantumToolbox.jl/issues/389 +[#392]: https://github.com/qutip/QuantumToolbox.jl/issues/392 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index d3d09c3c1..1a5af52a8 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -103,8 +103,8 @@ ket2dm expect variance LinearAlgebra.kron -sparse_to_dense -dense_to_sparse +to_dense +to_sparse vec2mat mat2vec ``` diff --git a/docs/src/users_guide/QuantumObject/QuantumObject.md b/docs/src/users_guide/QuantumObject/QuantumObject.md index 58377dcd7..1a1aead59 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject.md @@ -214,14 +214,14 @@ SparseMatrixCSC{Int64}(x_s) Matrix{Float64}(x_s) ``` -To convert between dense and sparse arrays, one can also use [`dense_to_sparse`](@ref) and [`sparse_to_dense`](@ref): +To convert between dense and sparse arrays, one can also use [`to_sparse`](@ref) and [`to_dense`](@ref): ```@example Qobj -x_d = sparse_to_dense(x_s) +x_d = to_dense(x_s) ``` ```@example Qobj -dense_to_sparse(x_d) +to_sparse(x_d) ``` !!! note "Convert to GPU arrays" diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index 7a90b27ba..70168b2b1 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -101,9 +101,9 @@ _change_eltype(::Type{T}, ::Val{32}) where {T<:AbstractFloat} = Float32 _change_eltype(::Type{Complex{T}}, ::Val{64}) where {T<:Union{Int,AbstractFloat}} = ComplexF64 _change_eltype(::Type{Complex{T}}, ::Val{32}) where {T<:Union{Int,AbstractFloat}} = ComplexF32 -QuantumToolbox.sparse_to_dense(A::MT) where {MT<:AbstractCuSparseArray} = CuArray(A) +QuantumToolbox.to_dense(A::MT) where {MT<:AbstractCuSparseArray} = CuArray(A) -QuantumToolbox.sparse_to_dense(::Type{T1}, A::CuArray{T2}) where {T1<:Number,T2<:Number} = CuArray{T1}(A) -QuantumToolbox.sparse_to_dense(::Type{T}, A::AbstractCuSparseArray) where {T<:Number} = CuArray{T}(A) +QuantumToolbox.to_dense(::Type{T1}, A::CuArray{T2}) where {T1<:Number,T2<:Number} = CuArray{T1}(A) +QuantumToolbox.to_dense(::Type{T}, A::AbstractCuSparseArray) where {T<:Number} = CuArray{T}(A) end diff --git a/src/deprecated.jl b/src/deprecated.jl index b39a392bf..a5d729711 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -15,11 +15,19 @@ end =# export FFTCorrelation +export sparse_to_dense, dense_to_sparse FFTCorrelation() = error( "`FFTCorrelation` has been deprecated and will be removed in next major release, please use `spectrum_correlation_fft` to calculate the spectrum with FFT method instead.", ) +sparse_to_dense(args...) = error( + "`sparse_to_dense` has been deprecated and will be removed in next major release, please use `to_dense` instead.", +) +dense_to_sparse(args...) = error( + "`dense_to_sparse` has been deprecated and will be removed in next major release, please use `to_sparse` instead.", +) + correlation_3op_2t( H::QuantumObject{HOpType}, ψ0::QuantumObject{StateOpType}, diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 15a0f430d..488be7956 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -298,7 +298,7 @@ LinearAlgebra.tr( Return the singular values of a [`QuantumObject`](@ref) in descending order """ -LinearAlgebra.svdvals(A::QuantumObject) = svdvals(sparse_to_dense(A.data)) +LinearAlgebra.svdvals(A::QuantumObject) = svdvals(to_dense(A.data)) @doc raw""" norm(A::QuantumObject, p::Real) @@ -415,7 +415,7 @@ Matrix square root of [`QuantumObject`](@ref) !!! note `√(A)` (where `√` can be typed by tab-completing `\sqrt` in the REPL) is a synonym of `sqrt(A)`. """ -LinearAlgebra.sqrt(A::QuantumObject) = QuantumObject(sqrt(sparse_to_dense(A.data)), A.type, A.dimensions) +LinearAlgebra.sqrt(A::QuantumObject) = QuantumObject(sqrt(to_dense(A.data)), A.type, A.dimensions) @doc raw""" log(A::QuantumObject) @@ -425,7 +425,7 @@ Matrix logarithm of [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ LinearAlgebra.log(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - QuantumObject(log(sparse_to_dense(A.data)), A.type, A.dimensions) + QuantumObject(log(to_dense(A.data)), A.type, A.dimensions) @doc raw""" exp(A::QuantumObject) @@ -437,7 +437,7 @@ Note that this function only supports for [`Operator`](@ref) and [`SuperOperator LinearAlgebra.exp( A::QuantumObject{ObjType,DimsType,<:AbstractMatrix}, ) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = - QuantumObject(dense_to_sparse(exp(A.data)), A.type, A.dimensions) + QuantumObject(to_sparse(exp(A.data)), A.type, A.dimensions) LinearAlgebra.exp( A::QuantumObject{ObjType,DimsType,<:AbstractSparseMatrix}, ) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index f53cec4ee..73db30c7b 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -462,10 +462,10 @@ function LinearAlgebra.eigen( kwargs..., ) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} MT = typeof(A.data) - F = eigen(sparse_to_dense(A.data); kwargs...) + F = eigen(to_dense(A.data); kwargs...) # This fixes a type inference issue. But doesn't work for GPU arrays - E::mat2vec(sparse_to_dense(MT)) = F.values - U::sparse_to_dense(MT) = F.vectors + E::mat2vec(to_dense(MT)) = F.values + U::to_dense(MT) = F.vectors return EigsolveResult(E, U, A.type, A.dimensions, 0, 0, true) end @@ -478,7 +478,7 @@ Same as [`eigen(A::QuantumObject; kwargs...)`](@ref) but for only the eigenvalue LinearAlgebra.eigvals( A::QuantumObject{OpType}; kwargs..., -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = eigvals(sparse_to_dense(A.data); kwargs...) +) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = eigvals(to_dense(A.data); kwargs...) @doc raw""" eigenenergies(A::QuantumObject; sparse::Bool=false, kwargs...) diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 8fd805b70..e2c225762 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -4,7 +4,7 @@ Functions which manipulates QuantumObject export ket2dm export expect, variance -export sparse_to_dense, dense_to_sparse +export to_dense, to_sparse export vec2mat, mat2vec @doc raw""" @@ -113,19 +113,19 @@ variance(O::QuantumObject{OperatorQuantumObject}, ψ::QuantumObject) = expect(O^ variance(O::QuantumObject{OperatorQuantumObject}, ψ::Vector{<:QuantumObject}) = expect(O^2, ψ) .- expect(O, ψ) .^ 2 @doc raw""" - sparse_to_dense(A::QuantumObject) + to_dense(A::QuantumObject) Converts a sparse QuantumObject to a dense QuantumObject. """ -sparse_to_dense(A::QuantumObject) = QuantumObject(sparse_to_dense(A.data), A.type, A.dimensions) -sparse_to_dense(A::MT) where {MT<:AbstractSparseArray} = Array(A) -sparse_to_dense(A::MT) where {MT<:AbstractArray} = A +to_dense(A::QuantumObject) = QuantumObject(to_dense(A.data), A.type, A.dimensions) +to_dense(A::MT) where {MT<:AbstractSparseArray} = Array(A) +to_dense(A::MT) where {MT<:AbstractArray} = A -sparse_to_dense(::Type{T}, A::AbstractSparseArray) where {T<:Number} = Array{T}(A) -sparse_to_dense(::Type{T1}, A::AbstractArray{T2}) where {T1<:Number,T2<:Number} = Array{T1}(A) -sparse_to_dense(::Type{T}, A::AbstractArray{T}) where {T<:Number} = A +to_dense(::Type{T}, A::AbstractSparseArray) where {T<:Number} = Array{T}(A) +to_dense(::Type{T1}, A::AbstractArray{T2}) where {T1<:Number,T2<:Number} = Array{T1}(A) +to_dense(::Type{T}, A::AbstractArray{T}) where {T<:Number} = A -function sparse_to_dense(::Type{M}) where {M<:SparseMatrixCSC} +function to_dense(::Type{M}) where {M<:SparseMatrixCSC} T = M par = T.parameters npar = length(par) @@ -133,22 +133,22 @@ function sparse_to_dense(::Type{M}) where {M<:SparseMatrixCSC} return Matrix{par[1]} end -sparse_to_dense(::Type{M}) where {M<:AbstractMatrix} = M +to_dense(::Type{M}) where {M<:AbstractMatrix} = M @doc raw""" - dense_to_sparse(A::QuantumObject) + to_sparse(A::QuantumObject) Converts a dense QuantumObject to a sparse QuantumObject. """ -dense_to_sparse(A::QuantumObject, tol::Real = 1e-10) = QuantumObject(dense_to_sparse(A.data, tol), A.type, A.dimensions) -function dense_to_sparse(A::MT, tol::Real = 1e-10) where {MT<:AbstractMatrix} +to_sparse(A::QuantumObject, tol::Real = 1e-10) = QuantumObject(to_sparse(A.data, tol), A.type, A.dimensions) +function to_sparse(A::MT, tol::Real = 1e-10) where {MT<:AbstractMatrix} idxs = findall(@. abs(A) > tol) row_indices = getindex.(idxs, 1) col_indices = getindex.(idxs, 2) vals = getindex(A, idxs) return sparse(row_indices, col_indices, vals, size(A)...) end -function dense_to_sparse(A::VT, tol::Real = 1e-10) where {VT<:AbstractVector} +function to_sparse(A::VT, tol::Real = 1e-10) where {VT<:AbstractVector} idxs = findall(@. abs(A) > tol) vals = getindex(A, idxs) return sparsevec(idxs, vals, length(A)) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 2c3e05d9f..5445818ef 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -48,7 +48,7 @@ function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, : # Because inv(Λ) ⋅ R has real and strictly positive elements, Q · Λ is therefore Haar distributed. Λ = diag(R) # take the diagonal elements of R Λ ./= abs.(Λ) # rescaling the elements - return QuantumObject(sparse_to_dense(Q * Diagonal(Λ)); type = Operator, dims = dimensions) + return QuantumObject(to_dense(Q * Diagonal(Λ)); type = Operator, dims = dimensions) end function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, ::Val{:exp}) N = prod(dimensions) @@ -59,7 +59,7 @@ function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, : # generate Hermitian matrix H = QuantumObject((Z + Z') / 2; type = Operator, dims = dimensions) - return sparse_to_dense(exp(-1.0im * H)) + return to_dense(exp(-1.0im * H)) end rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, ::Val{T}) where {T} = throw(ArgumentError("Invalid distribution: $(T)")) diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index f2c6f5569..9f760c876 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -205,7 +205,7 @@ SciMLOperators.cache_operator( L::AbstractQuantumObject{OpType}, u::AbstractVector, ) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - get_typename_wrapper(L)(cache_operator(L.data, sparse_to_dense(similar(u))), L.type, L.dimensions) + get_typename_wrapper(L)(cache_operator(L.data, to_dense(similar(u))), L.type, L.dimensions) function SciMLOperators.cache_operator( L::AbstractQuantumObject{OpType}, diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 5cd4d828b..4d97d90d4 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -281,7 +281,7 @@ function QuantumObjectEvolution( end # Preallocate the SciMLOperator cache using a dense vector as a reference - v0 = sparse_to_dense(similar(op.data, size(op, 1))) + v0 = to_dense(similar(op.data, size(op, 1))) data = cache_operator(data, v0) return QuantumObjectEvolution(data, type, dims) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index a87bc3864..5b640bc25 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -76,7 +76,7 @@ function mesolveProblem( check_dimensions(L_evo, ψ0) T = Base.promote_eltype(L_evo, ψ0) - ρ0 = sparse_to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + ρ0 = to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type L = L_evo.data is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 999218b9d..a9996a633 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -66,7 +66,7 @@ function sesolveProblem( check_dimensions(H_evo, ψ0) T = Base.promote_eltype(H_evo, ψ0) - ψ0 = sparse_to_dense(_CType(T), get_data(ψ0)) # Convert it to dense vector with complex element type + ψ0 = to_dense(_CType(T), get_data(ψ0)) # Convert it to dense vector with complex element type U = H_evo.data is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 904951d42..6dab420af 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -91,7 +91,7 @@ function smesolveProblem( dims = L_evo.dimensions T = Base.promote_eltype(L_evo, ψ0) - ρ0 = sparse_to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + ρ0 = to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 0b8ec2146..22227e939 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -167,7 +167,7 @@ function ssesolveProblem( check_dimensions(H_eff_evo, ψ0) dims = H_eff_evo.dimensions - ψ0 = sparse_to_dense(_CType(ψ0), get_data(ψ0)) + ψ0 = to_dense(_CType(ψ0), get_data(ψ0)) progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 722463101..a21221e26 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -397,12 +397,12 @@ function liouvillian_generalized( H_d = QuantumObject(Diagonal(complex(E)), type = Operator, dims = dims) Ω = E' .- E - Ωp = triu(dense_to_sparse(Ω, tol), 1) + Ωp = triu(to_sparse(Ω, tol), 1) # Filter in the Hilbert space σ = isnothing(σ_filter) ? 500 * maximum([norm(field) / length(field) for field in fields]) : σ_filter F1 = QuantumObject(gaussian.(Ω, 0, σ), type = Operator, dims = dims) - F1 = dense_to_sparse(F1, tol) + F1 = to_sparse(F1, tol) # Filter in the Liouville space # M1 = ones(final_size, final_size) @@ -412,13 +412,13 @@ function liouvillian_generalized( Ω2 = kron(M1, Ω) Ωdiff = Ω1 .- Ω2 F2 = QuantumObject(gaussian.(Ωdiff, 0, σ), SuperOperator, dims) - F2 = dense_to_sparse(F2, tol) + F2 = to_sparse(F2, tol) L = liouvillian(H_d) for i in eachindex(fields) # The operator that couples the system to the bath in the eigenbasis - X_op = dense_to_sparse((U'*fields[i]*U).data[1:final_size, 1:final_size], tol) + X_op = to_sparse((U'*fields[i]*U).data[1:final_size, 1:final_size], tol) if ishermitian(fields[i]) X_op = (X_op + X_op') / 2 # Make sure it's hermitian end @@ -452,8 +452,8 @@ function _liouvillian_floquet( L_0 = L₀.data L_p = Lₚ.data L_m = Lₘ.data - L_p_dense = sparse_to_dense(Lₚ.data) - L_m_dense = sparse_to_dense(Lₘ.data) + L_p_dense = to_dense(Lₚ.data) + L_m_dense = to_dense(Lₘ.data) S = -(L_0 - 1im * n_max * ω * I) \ L_p_dense T = -(L_0 + 1im * n_max * ω * I) \ L_m_dense @@ -464,5 +464,5 @@ function _liouvillian_floquet( end tol == 0 && return QuantumObject(L_0 + L_m * S + L_p * T, SuperOperator, L₀.dimensions) - return QuantumObject(dense_to_sparse(L_0 + L_m * S + L_p * T, tol), SuperOperator, L₀.dimensions) + return QuantumObject(to_sparse(L_0 + L_m * S + L_p * T, tol), SuperOperator, L₀.dimensions) end diff --git a/src/wigner.jl b/src/wigner.jl index d85385c63..dec636642 100644 --- a/src/wigner.jl +++ b/src/wigner.jl @@ -60,7 +60,7 @@ julia> wig = wigner(ψ, xvec, xvec); or taking advantage of the parallel computation of the `WignerLaguerre` method ```jldoctest wigner -julia> ρ = ket2dm(ψ) |> dense_to_sparse; +julia> ρ = ket2dm(ψ) |> to_sparse; julia> wig = wigner(ρ, xvec, xvec, method=WignerLaguerre(parallel=true)); diff --git a/test/core-test/eigenvalues_and_operators.jl b/test/core-test/eigenvalues_and_operators.jl index 31d5997af..481aedce0 100644 --- a/test/core-test/eigenvalues_and_operators.jl +++ b/test/core-test/eigenvalues_and_operators.jl @@ -58,7 +58,7 @@ # eigen solve for general matrices vals, _, vecs = eigsolve(L.data, sigma = 0.01, k = 10, krylovdim = 50) - vals2, vecs2 = eigen(sparse_to_dense(L.data)) + vals2, vecs2 = eigen(to_dense(L.data)) vals3, state3, vecs3 = eigsolve_al(L, 1 \ (40 * κ), k = 10, krylovdim = 50) idxs = sortperm(vals2, by = abs) vals2 = vals2[idxs][1:10] diff --git a/test/core-test/generalized_master_equation.jl b/test/core-test/generalized_master_equation.jl index a35114e86..54a9ac8e9 100644 --- a/test/core-test/generalized_master_equation.jl +++ b/test/core-test/generalized_master_equation.jl @@ -15,12 +15,12 @@ Tlist = [0, 0.0] E, U, L1 = liouvillian_generalized(H, fields, Tlist, N_trunc = N_trunc, tol = tol) - Ω = dense_to_sparse((E'.-E)[1:N_trunc, 1:N_trunc], tol) + Ω = to_sparse((E'.-E)[1:N_trunc, 1:N_trunc], tol) - H_d = Qobj(dense_to_sparse((U'*H*U)[1:N_trunc, 1:N_trunc], tol)) - Xp = Qobj(Ω .* dense_to_sparse(triu((U'*(a+a')*U).data[1:N_trunc, 1:N_trunc], 1), tol)) - a2 = Qobj(dense_to_sparse((U'*a*U).data[1:N_trunc, 1:N_trunc], tol)) - sm2 = Qobj(dense_to_sparse((U'*sm*U).data[1:N_trunc, 1:N_trunc], tol)) + H_d = Qobj(to_sparse((U'*H*U)[1:N_trunc, 1:N_trunc], tol)) + Xp = Qobj(Ω .* to_sparse(triu((U'*(a+a')*U).data[1:N_trunc, 1:N_trunc], 1), tol)) + a2 = Qobj(to_sparse((U'*a*U).data[1:N_trunc, 1:N_trunc], tol)) + sm2 = Qobj(to_sparse((U'*sm*U).data[1:N_trunc, 1:N_trunc], tol)) # Standard liouvillian case c_ops = [sqrt(0.01) * a2, sqrt(0.01) * sm2] @@ -33,12 +33,12 @@ Tlist = [0.2, 0.0] E, U, L1 = liouvillian_generalized(H, fields, Tlist, N_trunc = N_trunc, tol = tol) - Ω = dense_to_sparse((E'.-E)[1:N_trunc, 1:N_trunc], tol) + Ω = to_sparse((E'.-E)[1:N_trunc, 1:N_trunc], tol) - H_d = Qobj(dense_to_sparse((U'*H*U)[1:N_trunc, 1:N_trunc], tol)) - Xp = Qobj(Ω .* dense_to_sparse(triu((U'*(a+a')*U).data[1:N_trunc, 1:N_trunc], 1), tol)) - a2 = Qobj(dense_to_sparse((U'*a*U).data[1:N_trunc, 1:N_trunc], tol)) - sm2 = Qobj(dense_to_sparse((U'*sm*U).data[1:N_trunc, 1:N_trunc], tol)) + H_d = Qobj(to_sparse((U'*H*U)[1:N_trunc, 1:N_trunc], tol)) + Xp = Qobj(Ω .* to_sparse(triu((U'*(a+a')*U).data[1:N_trunc, 1:N_trunc], 1), tol)) + a2 = Qobj(to_sparse((U'*a*U).data[1:N_trunc, 1:N_trunc], tol)) + sm2 = Qobj(to_sparse((U'*sm*U).data[1:N_trunc, 1:N_trunc], tol)) @test abs(expect(Xp' * Xp, steadystate(L1)) - n_thermal(1, Tlist[1])) / n_thermal(1, Tlist[1]) < 1e-4 diff --git a/test/core-test/negativity_and_partial_transpose.jl b/test/core-test/negativity_and_partial_transpose.jl index ea1c1580e..475cd343e 100644 --- a/test/core-test/negativity_and_partial_transpose.jl +++ b/test/core-test/negativity_and_partial_transpose.jl @@ -24,7 +24,7 @@ @testset "partial_transpose" begin # A (24 * 24)-matrix which contains number 1 ~ 576 A_dense = Qobj(reshape(1:(24^2), (24, 24)), dims = (2, 3, 4)) - A_sparse = dense_to_sparse(A_dense) + A_sparse = to_sparse(A_dense) PT = (true, false) for s1 in PT for s2 in PT diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index ad09f848e..a03b96de3 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -323,9 +323,9 @@ @testset "element type conversion" begin vd = Qobj(Int64[0, 0]) - vs = Qobj(dense_to_sparse(vd)) + vs = Qobj(to_sparse(vd)) Md = Qobj(Int64[0 0; 0 0]) - Ms = Qobj(dense_to_sparse(Md)) + Ms = Qobj(to_sparse(Md)) @test typeof(Vector(vd).data) == Vector{Int64} @test typeof(Vector(vs).data) == Vector{Int64} @test typeof(Vector{ComplexF64}(vd).data) == Vector{ComplexF64} @@ -340,6 +340,11 @@ @test typeof(SparseMatrixCSC(Md).data) == SparseMatrixCSC{Int64,Int64} @test typeof(SparseMatrixCSC(Ms).data) == SparseMatrixCSC{Int64,Int64} @test typeof(SparseMatrixCSC{ComplexF64}(Ms).data) == SparseMatrixCSC{ComplexF64,Int64} + + @testset "Deprecated Errors" begin + @test_throws ErrorException sparse_to_dense(vs) + @test_throws ErrorException dense_to_sparse(vd) + end end @testset "Type Inference (QuantumObject)" begin @@ -564,8 +569,8 @@ @testset "trace distance" begin ψz0 = basis(2, 0) ψz1 = basis(2, 1) - ρz0 = dense_to_sparse(ket2dm(ψz0)) - ρz1 = dense_to_sparse(ket2dm(ψz1)) + ρz0 = to_sparse(ket2dm(ψz0)) + ρz1 = to_sparse(ket2dm(ψz1)) ψx0 = sqrt(0.5) * (basis(2, 0) + basis(2, 1)) @test tracedist(ψz0, ψx0) ≈ sqrt(0.5) @test tracedist(ρz0, ψz1) ≈ 1.0 @@ -586,7 +591,7 @@ ψ1 = Qobj(rand(ComplexF64, 5)) ψ2 = Qobj(rand(ComplexF64, 5)) M1 = ψ1 * ψ1' - @test sqrtm(M0) ≈ sqrtm(sparse_to_dense(M0)) + @test sqrtm(M0) ≈ sqrtm(to_dense(M0)) @test isapprox(fidelity(M0, M1), fidelity(ψ1, M0); atol = 1e-6) @test isapprox(fidelity(ψ1, ψ2), fidelity(ket2dm(ψ1), ket2dm(ψ2)); atol = 1e-6) @@ -601,7 +606,7 @@ @testset "log, exp, sinm, cosm" begin M0 = rand(ComplexF64, 4, 4) Md = Qobj(M0 * M0') - Ms = dense_to_sparse(Md) + Ms = to_sparse(Md) e_p = expm(1im * Md) e_m = expm(-1im * Md) @test logm(expm(Ms)) ≈ expm(logm(Md)) @@ -625,28 +630,28 @@ tol = 0.5 ## Vector{Float64} with in-place tidyup ψ1 = Qobj(rand(Float64, N)) - ψ2 = dense_to_sparse(ψ1) + ψ2 = to_sparse(ψ1) @test tidyup!(ψ2, tol) == ψ2 != ψ1 - @test dense_to_sparse(tidyup!(ψ1, tol)) == ψ2 + @test to_sparse(tidyup!(ψ1, tol)) == ψ2 ## Vector{Float64} with normal tidyup ψ1 = Qobj(rand(Float64, N)) - ψ2 = dense_to_sparse(ψ1) + ψ2 = to_sparse(ψ1) @test tidyup(ψ2, tol) != ψ2 - @test dense_to_sparse(tidyup(ψ1, tol)) == tidyup(ψ2, tol) + @test to_sparse(tidyup(ψ1, tol)) == tidyup(ψ2, tol) ## Matrix{ComplexF64} with in-place tidyup tol = 0.1 ρ1 = rand_dm(N) - ρ2 = dense_to_sparse(ρ1) + ρ2 = to_sparse(ρ1) @test tidyup!(ρ2, tol) == ρ2 != ρ1 - @test dense_to_sparse(tidyup!(ρ1, tol)) == ρ2 + @test to_sparse(tidyup!(ρ1, tol)) == ρ2 ## Matrix{ComplexF64} with normal tidyup ρ1 = rand_dm(N) - ρ2 = dense_to_sparse(ρ1) + ρ2 = to_sparse(ρ1) @test tidyup(ρ2, tol) != ρ2 - @test dense_to_sparse(tidyup(ρ1, tol)) == tidyup(ρ2, tol) + @test to_sparse(tidyup(ρ1, tol)) == tidyup(ρ2, tol) @testset "Type Inference (tidyup)" begin @inferred tidyup(ψ1, tol) diff --git a/test/core-test/states_and_operators.jl b/test/core-test/states_and_operators.jl index 573dedd04..f43b3e2a3 100644 --- a/test/core-test/states_and_operators.jl +++ b/test/core-test/states_and_operators.jl @@ -341,7 +341,7 @@ @testset "superoperators" begin # spre, spost, and sprepost Xs = sigmax() - Xd = sparse_to_dense(Xs) + Xd = to_dense(Xs) A_wrong1 = Qobj(rand(4, 4), dims = 4) A_wrong2 = Qobj(rand(4, 4), dims = (2, 2)) A_wrong3 = Qobj(rand(3, 3)) diff --git a/test/core-test/wigner.jl b/test/core-test/wigner.jl index e12a12c2e..e48b53497 100644 --- a/test/core-test/wigner.jl +++ b/test/core-test/wigner.jl @@ -1,7 +1,7 @@ @testset "Wigner" begin α = 0.5 + 0.8im ψ = coherent(30, α) - ρ = dense_to_sparse(ket2dm(ψ), 1e-6) + ρ = to_sparse(ket2dm(ψ), 1e-6) xvec = LinRange(-3, 3, 300) yvec = LinRange(-3, 3, 300) diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index 262ae88ed..42cd278cb 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -5,16 +5,16 @@ ψdi = Qobj(Int64[1, 0]) ψdf = Qobj(Float64[1, 0]) ψdc = Qobj(ComplexF64[1, 0]) - ψsi = dense_to_sparse(ψdi) - ψsf = dense_to_sparse(ψdf) - ψsc = dense_to_sparse(ψdc) + ψsi = to_sparse(ψdi) + ψsf = to_sparse(ψdf) + ψsc = to_sparse(ψdc) Xdi = Qobj(Int64[0 1; 1 0]) Xdf = Qobj(Float64[0 1; 1 0]) Xdc = Qobj(ComplexF64[0 1; 1 0]) - Xsi = dense_to_sparse(Xdi) - Xsf = dense_to_sparse(Xdf) - Xsc = dense_to_sparse(Xdc) + Xsi = to_sparse(Xdi) + Xsf = to_sparse(Xdf) + Xsc = to_sparse(Xdc) @test_throws DomainError cu(ψdi; word_size = 16) @@ -67,19 +67,19 @@ @test typeof(CuSparseMatrixCSR{ComplexF32}(Xsc).data) == CuSparseMatrixCSR{ComplexF32,Int32} # Sparse To Dense - # @test sparse_to_dense(cu(ψsi; word_size = 64)).data isa CuVector{Int64} # TODO: Fix this in CUDA.jl - @test sparse_to_dense(cu(ψsf; word_size = 64)).data isa CuVector{Float64} - @test sparse_to_dense(cu(ψsc; word_size = 64)).data isa CuVector{ComplexF64} - # @test sparse_to_dense(cu(Xsi; word_size = 64)).data isa CuMatrix{Int64} # TODO: Fix this in CUDA.jl - @test sparse_to_dense(cu(Xsf; word_size = 64)).data isa CuMatrix{Float64} - @test sparse_to_dense(cu(Xsc; word_size = 64)).data isa CuMatrix{ComplexF64} - - # @test sparse_to_dense(Int32, cu(ψsf; word_size = 64)).data isa CuVector{Int32} # TODO: Fix this in CUDA.jl - # @test sparse_to_dense(Float32, cu(ψsf; word_size = 64)).data isa CuVector{Float32} # TODO: Fix this in CUDA.jl - # @test sparse_to_dense(ComplexF32, cu(ψsf; word_size = 64)).data isa CuVector{ComplexF32} # TODO: Fix this in CUDA.jl - # @test sparse_to_dense(Int64, cu(Xsf; word_size = 32)).data isa CuMatrix{Int64} # TODO: Fix this in CUDA.jl - # @test sparse_to_dense(Float64, cu(Xsf; word_size = 32)).data isa CuMatrix{Float64} # TODO: Fix this in CUDA.jl - # @test sparse_to_dense(ComplexF64, cu(Xsf; word_size = 32)).data isa CuMatrix{ComplexF64} # TODO: Fix this in CUDA.jl + # @test to_dense(cu(ψsi; word_size = 64)).data isa CuVector{Int64} # TODO: Fix this in CUDA.jl + @test to_dense(cu(ψsf; word_size = 64)).data isa CuVector{Float64} + @test to_dense(cu(ψsc; word_size = 64)).data isa CuVector{ComplexF64} + # @test to_dense(cu(Xsi; word_size = 64)).data isa CuMatrix{Int64} # TODO: Fix this in CUDA.jl + @test to_dense(cu(Xsf; word_size = 64)).data isa CuMatrix{Float64} + @test to_dense(cu(Xsc; word_size = 64)).data isa CuMatrix{ComplexF64} + + # @test to_dense(Int32, cu(ψsf; word_size = 64)).data isa CuVector{Int32} # TODO: Fix this in CUDA.jl + # @test to_dense(Float32, cu(ψsf; word_size = 64)).data isa CuVector{Float32} # TODO: Fix this in CUDA.jl + # @test to_dense(ComplexF32, cu(ψsf; word_size = 64)).data isa CuVector{ComplexF32} # TODO: Fix this in CUDA.jl + # @test to_dense(Int64, cu(Xsf; word_size = 32)).data isa CuMatrix{Int64} # TODO: Fix this in CUDA.jl + # @test to_dense(Float64, cu(Xsf; word_size = 32)).data isa CuMatrix{Float64} # TODO: Fix this in CUDA.jl + # @test to_dense(ComplexF64, cu(Xsf; word_size = 32)).data isa CuMatrix{ComplexF64} # TODO: Fix this in CUDA.jl # brief example in README and documentation N = 20 From 1a2479e5eb4dc966f0bff1ecbfc3a540549f6e69 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:43:02 +0900 Subject: [PATCH 194/329] Minor changes in documentation and docstrings (#391) --- docs/src/getting_started/logo.md | 12 ++++++------ src/correlations.jl | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/getting_started/logo.md b/docs/src/getting_started/logo.md index 587e732fa..154ad0c26 100644 --- a/docs/src/getting_started/logo.md +++ b/docs/src/getting_started/logo.md @@ -14,23 +14,23 @@ A cat state, often referred to as a Schrödinger cat state, is a quantum state t where ``| \alpha \rangle`` is a coherent state with amplitude ``\alpha``. -The triangular cat state is a generalization of the standard cat state. It is a superposition of three coherent states with phases ``\theta_0, \theta_1, \theta_2`` separated by ``120^\circ``(or ``2\pi/3``radians): +The triangular cat state is a generalization of the standard cat state. It is a superposition of three coherent states with phases ``\theta_0, \theta_1, \theta_2`` separated by ``120^\circ`` (or ``2\pi/3``radians): ```math | \psi_{\text{tri-cat}} \rangle = \frac{1}{\sqrt{3}} \left( | \alpha_0 \rangle + | \alpha_1 \rangle + | \alpha_2 \rangle \right) ``` -where ``\alpha_j = \rho e^{i\theta_j}``with ``\theta_j = \frac{\pi}{2} + \frac{2\pi j}{3}``and ``j = 0, 1, 2``. +where ``\alpha_j = \rho e^{i\theta_j}`` with ``\theta_j = \frac{\pi}{2} + \frac{2\pi j}{3}`` and ``j = 0, 1, 2``. ### Wigner Function -The Wigner function ``W(x, p)``is a quasi-probability distribution used in quantum mechanics to represent quantum states in phase space. It is defined as: +The Wigner function ``W(x, p)`` is a quasi-probability distribution used in quantum mechanics to represent quantum states in phase space. It is defined as: ```math W(x, p) = \frac{1}{\pi \hbar} \int_{-\infty}^{\infty} \psi^*(x + y) \psi(x - y) e^{2ipy / \hbar} \, dy ``` -where ``\psi(x)``is the wave function of the quantum state, ``x``is the position, ``p``is the momentum, and ``\hbar``is the reduced Planck constant. Unlike classical probability distributions, the Wigner function can take negative values, which indicates non-classical behavior. +where ``\psi(x)`` is the wave function of the quantum state, ``x`` is the position, ``p`` is the momentum, and ``\hbar`` is the reduced Planck constant. Unlike classical probability distributions, the Wigner function can take negative values, which indicates non-classical behavior. ## Generating the Logo @@ -91,7 +91,7 @@ The figure obtained above coulb be already a potential logo for the package. How \frac{d \hat{\rho}}{dt} = -i [\hat{H}, \hat{\rho}] + \gamma \left( 2 \hat{a} \hat{\rho} \hat{a}^\dagger - \hat{a}^\dagger \hat{a} \hat{\rho} - \hat{\rho} \hat{a}^\dagger \hat{a} \right) ``` -where ``\hat{\rho}`` is the density matrix, ``\hat{H} = \omega \hat{a}^\dagger \hat{a}``is the Hamiltonian of the harmonic oscillator (``\hbar = 1``), ``\hat{a}``and ``\hat{a}^\dagger``are the annihilation and creation operators, and ``\gamma``is the damping rate. Thus, we initialize the system in the triangular cat state and evolve it under the Lindblad master equation, using the [`mesolve`](@ref) function. +where ``\hat{\rho}`` is the density matrix, ``\hat{H} = \omega \hat{a}^\dagger \hat{a}`` is the Hamiltonian of the harmonic oscillator (``\hbar = 1``), ``\hat{a}`` and ``\hat{a}^\dagger`` are the annihilation and creation operators, and ``\gamma`` is the damping rate. Thus, we initialize the system in the triangular cat state and evolve it under the [Lindblad master equation](@ref doc-TE:Lindblad-Master-Equation-Solver), using the [`mesolve`](@ref) function. ```@example logo γ = 0.012 @@ -166,7 +166,7 @@ cmap3 = cgrad(vcat(fill(julia_blue, n_repeats), fill(julia_purple, n_repeats))) ### Normalizing the Wigner function and applying the custom colormap -The colormaps require the input to be in the range ``[0, 1]``. We normalize the Wigner function such that the maximum value is ``1``and the zeros are set to ``0.5``. +The colormaps require the input to be in the range ``[0, 1]``. We normalize the Wigner function such that the maximum value is ``1`` and the zeros are set to ``0.5``. ```@example logo vmax = maximum(wig) diff --git a/src/correlations.jl b/src/correlations.jl index ae9598f98..0df4375f2 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -69,7 +69,7 @@ end C::QuantumObject; kwargs...) -Returns the one-time correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(0) \hat{B}(\tau) \hat{C}(0) \right\rangle`` for a given initial state ``|\psi_0\rangle``. +Returns the two-time correlation function (with only one time coordinate ``\tau``) of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(0) \hat{B}(\tau) \hat{C}(0) \right\rangle`` for a given initial state ``|\psi_0\rangle``. If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). """ @@ -142,7 +142,7 @@ end reverse::Bool=false, kwargs...) -Returns the one-time correlation function of two operators ``\hat{A}`` and ``\hat{B}`` : ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle`` for a given initial state ``|\psi_0\rangle``. +Returns the two-time correlation function (with only one time coordinate ``\tau``) of two operators ``\hat{A}`` and ``\hat{B}`` : ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle`` for a given initial state ``|\psi_0\rangle``. If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). From 1f281608dc827873745b8b1081c2b4f2884bb04d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 10 Feb 2025 05:39:56 +0100 Subject: [PATCH 195/329] Fix instabilities of smesolve --- src/time_evolution/smesolve.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 6dab420af..fa7cfed33 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -3,7 +3,7 @@ export smesolveProblem, smesolveEnsembleProblem, smesolve _smesolve_generate_state(u, dims) = QuantumObject(vec2mat(u), type = Operator, dims = dims) function _smesolve_update_coeff(u, p, t, op_vec) - return real(dot(u, op_vec)) / 2 #this is Tr[Sn * ρ + ρ * Sn'] + return dot(op_vec, u) #this is Tr[Sn * ρ] end _smesolve_ScalarOperator(op_vec) = @@ -100,11 +100,13 @@ function smesolveProblem( K = get_data(L_evo) Id = I(prod(dims)) + Id_op = IdentityOperator(prod(dims)^2) D_l = map(sc_ops_evo_data) do op - # TODO: Implement the three-argument dot function for SciMLOperators.jl - # Currently, we are assuming a time-independent MatrixOperator + # TODO: # Currently, we are assuming a time-independent MatrixOperator + # Also, the u state may become non-hermitian, so Tr[Sn * ρ + ρ * Sn'] != real(Tr[Sn * ρ]) / 2 op_vec = mat2vec(adjoint(op.A)) - return _spre(op, Id) + _spost(op', Id) + _smesolve_ScalarOperator(op_vec) * IdentityOperator(prod(dims)^2) + op_vec_dag = mat2vec(op.A) + return _spre(op, Id) + _spost(op', Id) + _smesolve_ScalarOperator(op_vec) * Id_op + _smesolve_ScalarOperator(op_vec_dag) * Id_op end D = DiffusionOperator(D_l) From b04ddeb451c31d40a78a38ba9ca6400cbf496bc6 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 10 Feb 2025 10:50:29 +0100 Subject: [PATCH 196/329] Fix instabilities of smesolve --- src/time_evolution/smesolve.jl | 3 +++ test/core-test/time_evolution.jl | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index fa7cfed33..ad8abc243 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -39,6 +39,7 @@ is the Lindblad superoperator, and ```math \mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, +``` Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. @@ -162,6 +163,7 @@ is the Lindblad superoperator, and ```math \mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, +``` Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. @@ -273,6 +275,7 @@ is the Lindblad superoperator, and ```math \mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, +``` Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index e09690365..f2c2b6447 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -407,7 +407,7 @@ ntraj = 100, progress_bar = Val(false), ) - @test allocs_tot < 2710000 # TODO: Fix this high number of allocations + @test allocs_tot < 2750000 # TODO: Fix this high number of allocations allocs_tot = @allocations smesolve( H, From e7671188dbb30881547312fe8270b9d5fa765c7e Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 10 Feb 2025 10:52:01 +0100 Subject: [PATCH 197/329] [no ci] Format code --- src/time_evolution/smesolve.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index ad8abc243..972a486ca 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -107,7 +107,10 @@ function smesolveProblem( # Also, the u state may become non-hermitian, so Tr[Sn * ρ + ρ * Sn'] != real(Tr[Sn * ρ]) / 2 op_vec = mat2vec(adjoint(op.A)) op_vec_dag = mat2vec(op.A) - return _spre(op, Id) + _spost(op', Id) + _smesolve_ScalarOperator(op_vec) * Id_op + _smesolve_ScalarOperator(op_vec_dag) * Id_op + return _spre(op, Id) + + _spost(op', Id) + + _smesolve_ScalarOperator(op_vec) * Id_op + + _smesolve_ScalarOperator(op_vec_dag) * Id_op end D = DiffusionOperator(D_l) From 6924d9ce20fdd71878f90721493f678bd513c8f1 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 10 Feb 2025 11:06:00 +0100 Subject: [PATCH 198/329] Fix the definition of the stochastic term --- src/time_evolution/smesolve.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 972a486ca..ca1dbc698 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -3,7 +3,7 @@ export smesolveProblem, smesolveEnsembleProblem, smesolve _smesolve_generate_state(u, dims) = QuantumObject(vec2mat(u), type = Operator, dims = dims) function _smesolve_update_coeff(u, p, t, op_vec) - return dot(op_vec, u) #this is Tr[Sn * ρ] + return 2 * real(dot(op_vec, u)) #this is Tr[Sn * ρ + ρ * Sn'] end _smesolve_ScalarOperator(op_vec) = @@ -106,11 +106,9 @@ function smesolveProblem( # TODO: # Currently, we are assuming a time-independent MatrixOperator # Also, the u state may become non-hermitian, so Tr[Sn * ρ + ρ * Sn'] != real(Tr[Sn * ρ]) / 2 op_vec = mat2vec(adjoint(op.A)) - op_vec_dag = mat2vec(op.A) return _spre(op, Id) + _spost(op', Id) + - _smesolve_ScalarOperator(op_vec) * Id_op + - _smesolve_ScalarOperator(op_vec_dag) * Id_op + _smesolve_ScalarOperator(op_vec) * Id_op end D = DiffusionOperator(D_l) From fdc7aef1dc2745482529214e0124065dcbb863de Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 10 Feb 2025 11:08:08 +0100 Subject: [PATCH 199/329] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e11ac2e8a..0b426e88b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Rename `sparse_to_dense` as `to_dense` and `dense_to_sparse` as `to_sparse`. ([#392]) +- Fix erroneous definition of the stochastic term in `smesolve`. ([#393]) ## [v0.26.0] Release date: 2025-02-09 @@ -118,3 +119,4 @@ Release date: 2024-11-13 [#388]: https://github.com/qutip/QuantumToolbox.jl/issues/388 [#389]: https://github.com/qutip/QuantumToolbox.jl/issues/389 [#392]: https://github.com/qutip/QuantumToolbox.jl/issues/392 +[#393]: https://github.com/qutip/QuantumToolbox.jl/issues/393 From 210a78e20da22675c3122ba0e43bdcc25d713bd5 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 10 Feb 2025 11:22:23 +0100 Subject: [PATCH 200/329] Format code --- src/time_evolution/smesolve.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index ca1dbc698..f571ac4d3 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -106,9 +106,7 @@ function smesolveProblem( # TODO: # Currently, we are assuming a time-independent MatrixOperator # Also, the u state may become non-hermitian, so Tr[Sn * ρ + ρ * Sn'] != real(Tr[Sn * ρ]) / 2 op_vec = mat2vec(adjoint(op.A)) - return _spre(op, Id) + - _spost(op', Id) + - _smesolve_ScalarOperator(op_vec) * Id_op + return _spre(op, Id) + _spost(op', Id) + _smesolve_ScalarOperator(op_vec) * Id_op end D = DiffusionOperator(D_l) From c984f9536814e4bd61f419bca4f0283164ffe716 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 11 Feb 2025 14:45:29 +0900 Subject: [PATCH 201/329] Change MultiSiteOperator function name to multisite_operator (#394) Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> --- CHANGELOG.md | 2 ++ docs/src/resources/api.md | 2 +- docs/src/users_guide/cluster.md | 6 +++--- src/deprecated.jl | 4 ++++ src/spin_lattice.jl | 24 ++++++++++++------------ test/core-test/low_rank_dynamics.jl | 12 ++++++------ 6 files changed, 28 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b426e88b..3ce866819 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 - Rename `sparse_to_dense` as `to_dense` and `dense_to_sparse` as `to_sparse`. ([#392]) - Fix erroneous definition of the stochastic term in `smesolve`. ([#393]) +- Change name of `MultiSiteOperator` to `multisite_operator`. ([#394]) ## [v0.26.0] Release date: 2025-02-09 @@ -120,3 +121,4 @@ Release date: 2024-11-13 [#389]: https://github.com/qutip/QuantumToolbox.jl/issues/389 [#392]: https://github.com/qutip/QuantumToolbox.jl/issues/392 [#393]: https://github.com/qutip/QuantumToolbox.jl/issues/393 +[#394]: https://github.com/qutip/QuantumToolbox.jl/issues/394 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 1a5af52a8..6e95ec396 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -260,7 +260,7 @@ fidelity ```@docs Lattice -MultiSiteOperator +multisite_operator DissipativeIsing ``` diff --git a/docs/src/users_guide/cluster.md b/docs/src/users_guide/cluster.md index 3e1726ffa..f290ae1b4 100644 --- a/docs/src/users_guide/cluster.md +++ b/docs/src/users_guide/cluster.md @@ -99,9 +99,9 @@ hy = 0.0 hz = 0.0 γ = 1 -Sx = mapreduce(i -> MultiSiteOperator(latt, i=>sigmax()), +, 1:latt.N) -Sy = mapreduce(i -> MultiSiteOperator(latt, i=>sigmay()), +, 1:latt.N) -Sz = mapreduce(i -> MultiSiteOperator(latt, i=>sigmaz()), +, 1:latt.N) +Sx = mapreduce(i -> multisite_operator(latt, i=>sigmax()), +, 1:latt.N) +Sy = mapreduce(i -> multisite_operator(latt, i=>sigmay()), +, 1:latt.N) +Sz = mapreduce(i -> multisite_operator(latt, i=>sigmaz()), +, 1:latt.N) H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) e_ops = [Sx, Sy, Sz] diff --git a/src/deprecated.jl b/src/deprecated.jl index a5d729711..56e5d04e7 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -93,3 +93,7 @@ correlation_2op_1t( } = error( "The parameter order of `correlation_2op_1t` has been changed, please use `?correlation_2op_1t` to check the updated docstring.", ) + +MultiSiteOperator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Integer,<:QuantumObject}...) = error( + "`MultiSiteOperator` has been deprecated and will be removed in next major release, please use `multisite_operator` instead.", +) diff --git a/src/spin_lattice.jl b/src/spin_lattice.jl index 99f356df3..17275aba1 100644 --- a/src/spin_lattice.jl +++ b/src/spin_lattice.jl @@ -1,4 +1,4 @@ -export Lattice, MultiSiteOperator, DissipativeIsing +export Lattice, multisite_operator, DissipativeIsing @doc raw""" Lattice @@ -15,7 +15,7 @@ end #Definition of many-body operators @doc raw""" - MultiSiteOperator(dims::Union{AbstractVector, Tuple}, pairs::Pair{<:Integer,<:QuantumObject}...) + multisite_operator(dims::Union{AbstractVector, Tuple}, pairs::Pair{<:Integer,<:QuantumObject}...) A Julia function for generating a multi-site operator ``\\hat{O} = \\hat{O}_i \\hat{O}_j \\cdots \\hat{O}_k``. The function takes a vector of dimensions `dims` and a list of pairs `pairs` where the first element of the pair is the site index and the second element is the operator acting on that site. @@ -28,7 +28,7 @@ A Julia function for generating a multi-site operator ``\\hat{O} = \\hat{O}_i \\ # Example ```jldoctest -julia> op = MultiSiteOperator(Val(8), 5=>sigmax(), 7=>sigmaz()); +julia> op = multisite_operator(Val(8), 5=>sigmax(), 7=>sigmaz()); julia> op.dims 8-element SVector{8, Int64} with indices SOneTo(8): @@ -42,7 +42,7 @@ julia> op.dims 2 ``` """ -function MultiSiteOperator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Integer,<:QuantumObject}...) +function multisite_operator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Integer,<:QuantumObject}...) sites_unsorted = collect(first.(pairs)) idxs = sortperm(sites_unsorted) _sites = sites_unsorted[idxs] @@ -61,13 +61,13 @@ function MultiSiteOperator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Inte return QuantumObject(data; type = Operator, dims = dims) end -function MultiSiteOperator(N::Union{Integer,Val}, pairs::Pair{<:Integer,<:QuantumObject}...) +function multisite_operator(N::Union{Integer,Val}, pairs::Pair{<:Integer,<:QuantumObject}...) dims = ntuple(j -> 2, makeVal(N)) - return MultiSiteOperator(dims, pairs...) + return multisite_operator(dims, pairs...) end -function MultiSiteOperator(latt::Lattice, pairs::Pair{<:Integer,<:QuantumObject}...) - return MultiSiteOperator(makeVal(latt.N), pairs...) +function multisite_operator(latt::Lattice, pairs::Pair{<:Integer,<:QuantumObject}...) + return multisite_operator(makeVal(latt.N), pairs...) end #Definition of nearest-neighbour sites on lattice @@ -127,7 +127,7 @@ function DissipativeIsing( boundary_condition::Union{Symbol,Val} = Val(:periodic_bc), order::Integer = 1, ) - S = [MultiSiteOperator(latt, i => sigmam()) for i in 1:latt.N] + S = [multisite_operator(latt, i => sigmam()) for i in 1:latt.N] c_ops = sqrt(γ) .* S op_sum(S, i::CartesianIndex) = @@ -135,17 +135,17 @@ function DissipativeIsing( H = 0 if (Jx != 0 || hx != 0) - S = [MultiSiteOperator(latt, i => sigmax()) for i in 1:latt.N] + S = [multisite_operator(latt, i => sigmax()) for i in 1:latt.N] H += Jx / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) #/2 because we are double counting H += hx * sum(S) end if (Jy != 0 || hy != 0) - S = [MultiSiteOperator(latt, i => sigmay()) for i in 1:latt.N] + S = [multisite_operator(latt, i => sigmay()) for i in 1:latt.N] H += Jy / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) H += hy * sum(S) end if (Jz != 0 || hz != 0) - S = [MultiSiteOperator(latt, i => sigmaz()) for i in 1:latt.N] + S = [multisite_operator(latt, i => sigmaz()) for i in 1:latt.N] H += Jz / 2 * mapreduce(i -> op_sum(S, i), +, latt.car_idx) H += hz * sum(S) end diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index c7ce03c06..7b718c734 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -15,12 +15,12 @@ i = 1 for j in 1:N_modes i += 1 - i <= M && (ϕ[i] = MultiSiteOperator(latt, j => sigmap()) * ϕ[1]) + i <= M && (ϕ[i] = multisite_operator(latt, j => sigmap()) * ϕ[1]) end for k in 1:N_modes-1 for l in k+1:N_modes i += 1 - i <= M && (ϕ[i] = MultiSiteOperator(latt, k => sigmap(), l => sigmap()) * ϕ[1]) + i <= M && (ϕ[i] = multisite_operator(latt, k => sigmap(), l => sigmap()) * ϕ[1]) end end for i in i+1:M @@ -43,11 +43,11 @@ hz = 0.0 γ = 1 - Sx = mapreduce(i -> MultiSiteOperator(latt, i => sigmax()), +, 1:latt.N) - Sy = mapreduce(i -> MultiSiteOperator(latt, i => sigmay()), +, 1:latt.N) - Sz = mapreduce(i -> MultiSiteOperator(latt, i => sigmaz()), +, 1:latt.N) + Sx = mapreduce(i -> multisite_operator(latt, i => sigmax()), +, 1:latt.N) + Sy = mapreduce(i -> multisite_operator(latt, i => sigmay()), +, 1:latt.N) + Sz = mapreduce(i -> multisite_operator(latt, i => sigmaz()), +, 1:latt.N) SFxx = mapreduce( - x -> MultiSiteOperator(latt, x[1] => sigmax(), x[2] => sigmax()), + x -> multisite_operator(latt, x[1] => sigmax(), x[2] => sigmax()), +, Iterators.product(1:latt.N, 1:latt.N), ) From 2fbace49430ad480386e3b979e39f199ba1ae2b0 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 11 Feb 2025 19:09:38 +0900 Subject: [PATCH 202/329] Fix stochastic solvers and add documentation page (#395) --- CHANGELOG.md | 2 + docs/src/users_guide/time_evolution/intro.md | 7 +- .../src/users_guide/time_evolution/mcsolve.md | 2 +- .../users_guide/time_evolution/solution.md | 5 +- .../users_guide/time_evolution/stochastic.md | 147 +++++++++++++++++- src/time_evolution/mesolve.jl | 8 +- src/time_evolution/smesolve.jl | 44 +++--- src/time_evolution/ssesolve.jl | 34 ++-- 8 files changed, 200 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce866819..77d2e96de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `sparse_to_dense` as `to_dense` and `dense_to_sparse` as `to_sparse`. ([#392]) - Fix erroneous definition of the stochastic term in `smesolve`. ([#393]) - Change name of `MultiSiteOperator` to `multisite_operator`. ([#394]) +- Fix `smesolve` for specifying initial state as density matrix. ([#395]) ## [v0.26.0] Release date: 2025-02-09 @@ -122,3 +123,4 @@ Release date: 2024-11-13 [#392]: https://github.com/qutip/QuantumToolbox.jl/issues/392 [#393]: https://github.com/qutip/QuantumToolbox.jl/issues/393 [#394]: https://github.com/qutip/QuantumToolbox.jl/issues/394 +[#395]: https://github.com/qutip/QuantumToolbox.jl/issues/395 diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index aabbb25a0..b8ad2ca0f 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -5,8 +5,8 @@ - [Introduction](@ref doc-TE:Introduction) - [Time Evolution Solutions](@ref doc-TE:Time-Evolution-Solutions) - [Solution](@ref doc-TE:Solution) - - [Multiple trajectories solution](@ref doc-TE:Multiple-trajectories-solution) - [Accessing data in solutions](@ref doc-TE:Accessing-data-in-solutions) + - [Multiple trajectories solution](@ref doc-TE:Multiple-trajectories-solution) - [Schrödinger Equation Solver](@ref doc-TE:Schrödinger-Equation-Solver) - [Unitary evolution](@ref doc-TE:Unitary-evolution) - [Example: Spin dynamics](@ref doc-TE:Example:Spin-dynamics) @@ -16,8 +16,11 @@ - [Example: Dissipative Spin dynamics](@ref doc-TE:Example:Dissipative-Spin-dynamics) - [Example: Harmonic oscillator in thermal bath](@ref doc-TE:Example:Harmonic-oscillator-in-thermal-bath) - [Example: Two-level atom coupled to dissipative single-mode cavity](@ref doc-TE:Example:Two-level-atom-coupled-to-dissipative-single-mode-cavity) -- [Monte-Carlo Solver](@ref doc-TE:Monte-Carlo-Solver) +- [Monte Carlo Solver](@ref doc-TE:Monte-Carlo-Solver) - [Stochastic Solver](@ref doc-TE:Stochastic-Solver) + - [Stochastic Schrödinger equation](@ref doc-TE:Stochastic-Schrödinger-equation) + - [Stochastic master equation](@ref doc-TE:Stochastic-master-equation) + - [Example: Homodyne detection](@ref doc-TE:Example:Homodyne-detection) - [Solving Problems with Time-dependent Hamiltonians](@ref doc-TE:Solving-Problems-with-Time-dependent-Hamiltonians) - [Generate QobjEvo](@ref doc-TE:Generate-QobjEvo) - [QobjEvo fields (attributes)](@ref doc-TE:QobjEvo-fields-(attributes)) diff --git a/docs/src/users_guide/time_evolution/mcsolve.md b/docs/src/users_guide/time_evolution/mcsolve.md index b0cfa5591..d62540830 100644 --- a/docs/src/users_guide/time_evolution/mcsolve.md +++ b/docs/src/users_guide/time_evolution/mcsolve.md @@ -1,3 +1,3 @@ -# [Monte-Carlo Solver](@id doc-TE:Monte-Carlo-Solver) +# [Monte Carlo Solver](@id doc-TE:Monte-Carlo-Solver) This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index 5294ef214..b6a694837 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -89,4 +89,7 @@ Some other solvers can have other output. ## [Multiple trajectories solution](@id doc-TE:Multiple-trajectories-solution) -This part is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file +This part is still under construction, please read the docstrings for the following functions first: + +- [`TimeEvolutionMCSol`](@ref) +- [`TimeEvolutionStochasticSol`](@ref) \ No newline at end of file diff --git a/docs/src/users_guide/time_evolution/stochastic.md b/docs/src/users_guide/time_evolution/stochastic.md index 2006195ab..3687001ce 100644 --- a/docs/src/users_guide/time_evolution/stochastic.md +++ b/docs/src/users_guide/time_evolution/stochastic.md @@ -1,5 +1,148 @@ # [Stochastic Solver](@id doc-TE:Stochastic-Solver) -This page is still under construction, please visit [API](@ref doc-API) first. +When a quantum system is subjected to continuous measurement, through homodyne detection for example, it is possible to simulate the conditional quantum state using stochastic Schrödinger and master equations. The solution of these stochastic equations are quantum trajectories, which represent the conditioned evolution of the system given a specific measurement record. -## Stochastic Schrodinger equation \ No newline at end of file +## [Stochastic Schrödinger equation](@id doc-TE:Stochastic-Schrödinger-equation) + +The stochastic Schrödinger time evolution of a quantum system is defined by the following stochastic differential equation [Wiseman2009Quantum; section 4.4](@cite): + +```math +d|\psi(t)\rangle = -i \hat{K} |\psi(t)\rangle dt + \sum_n \hat{M}_n |\psi(t)\rangle dW_n(t) +``` + +where + +```math +\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{S}_n - \frac{1}{2} \hat{S}_n^\dagger \hat{S}_n - \frac{e_n^2}{8}\right), +``` +```math +\hat{M}_n = \hat{S}_n - \frac{e_n}{2}, +``` +and +```math +e_n = \langle \psi(t) | \hat{S}_n + \hat{S}_n^\dagger | \psi(t) \rangle. +``` + +Above, ``\hat{H}`` is the Hamiltonian, ``\hat{S}_n`` are the stochastic collapse operators, and ``dW_n(t)`` is the real Wiener increment (associated to ``\hat{S}_n``) which has the expectation values of ``E[dW_n]=0`` and ``E[dW_n^2]=dt``. + +The solver [`ssesolve`](@ref) will construct the operators ``\hat{K}`` and ``\hat{M}_n``. Once the user passes the Hamiltonian (``\hat{H}``) and the stochastic collapse operators list (`sc_ops`; ``\{\hat{S}_n\}_n``). As with the [`mcsolve`](@ref), the number of trajectories and the random number generator for the noise realization can be fixed using the arguments: `ntraj` and `rng`, respectively. + +## [Stochastic master equation](@id doc-TE:Stochastic-master-equation) + +When the initial state of the system is a density matrix ``\rho(0)``, or when additional loss channels are included, the stochastic master equation solver [`smesolve`](@ref) must be used. The stochastic master equation is given by [Wiseman2009Quantum; section 4.4](@cite): + +```math +d \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_i \mathcal{D}[\hat{C}_i] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), +``` + +where + +```math +\mathcal{D}[\hat{O}] \rho = \hat{O} \rho \hat{O}^\dagger - \frac{1}{2} \{\hat{O}^\dagger \hat{O}, \rho\}, +``` + +is the Lindblad superoperator, and + +```math +\mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, +``` + +The above implementation takes into account 2 types of collapse operators. ``\hat{C}_i`` (`c_ops`) represent the collapse operators related to pure dissipation, while ``\hat{S}_n`` (`sc_ops`) are the stochastic collapse operators. The first three terms on the right-hand side of the above equation is the deterministic part of the evolution which takes into account all operators ``\hat{C}_i`` and ``\hat{S}_n``. The last term (``\mathcal{H}[\hat{S}_n] \rho(t)``) is the stochastic part given solely by the operators ``\hat{S}_n``. + + +## [Example: Homodyne detection](@id doc-TE:Example:Homodyne-detection) + +First, we solve the dynamics for an optical cavity at absolute zero (``0K``) whose output is monitored using homodyne detection. The cavity decay rate is given by ``\kappa`` and the ``\Delta`` is the cavity detuning with respect to the driving field. The homodyne current ``J_x`` is calculated using + +```math +J_x = \langle \hat{x} \rangle + dW/dt, +``` + +where ``\hat{x}`` is the operator build from the `sc_ops` as + +```math +\hat{x} = \hat{S} + \hat{S}^\dagger +``` + +```@setup stochastic-solve +using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) +``` + +```@example stochastic-solve +# parameters +N = 20 # Fock space dimension +Δ = 5 * 2 * π # cavity detuning +κ = 2 # cavity decay rate +α = 4 # intensity of initial state +ntraj = 500 # number of trajectories + +tlist = 0:0.0025:1 + +# operators +a = destroy(N) +x = a + a' +H = Δ * a' * a + +# initial state +ψ0 = coherent(N, √α) + +# temperature with average of 0 excitations (absolute zero) +n_th = 0 +# c_ops = [√(κ * n_th) * a'] -> nothing +sc_ops = [√(κ * (n_th + 1)) * a] +``` + +In this case, there is no additional dissipation (`c_ops = nothing`), and thus, we can use the [`ssesolve`](@ref): + +```@example stochastic-solve +sse_sol = ssesolve( + H, + ψ0, + tlist, + sc_ops, + e_ops = [x], + ntraj = ntraj, +) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = "Time") +#lines!(ax, tlist, real(sse_sol.xxxxxx), label = L"J_x", color = :red, linestyle = :solid) TODO: add this in the future +lines!(ax, tlist, real(sse_sol.expect[1,:]), label = L"\langle x \rangle", color = :black, linestyle = :solid) + +axislegend(ax, position = :rt) + +fig +``` + +Next, we consider the same model but at a finite temperature to demonstrate [`smesolve`](@ref): + +```@example stochastic-solve +# temperature with average of 1 excitations +n_th = 1 +c_ops = [√(κ * n_th) * a'] +sc_ops = [√(κ * (n_th + 1)) * a] + +sme_sol = smesolve( + H, + ψ0, + tlist, + c_ops, + sc_ops, + e_ops = [x], + ntraj = ntraj, +) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], xlabel = "Time") +#lines!(ax, tlist, real(sme_sol.xxxxxx), label = L"J_x", color = :red, linestyle = :solid) TODO: add this in the future +lines!(ax, tlist, real(sme_sol.expect[1,:]), label = L"\langle x \rangle", color = :black, linestyle = :solid) + +axislegend(ax, position = :rt) + +fig +``` diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 5b640bc25..de4b7c335 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -7,8 +7,8 @@ c_ops are Nothing. You are probably running the wrong function.")) @doc raw""" mesolveProblem( - H::Union{AbstractQuantumObject{HOpType},Tuple}, - ψ0::QuantumObject{StateOpType}, + H::Union{AbstractQuantumObject,Tuple}, + ψ0::QuantumObject, tlist, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -94,8 +94,8 @@ end @doc raw""" mesolve( - H::Union{AbstractQuantumObject{HOpType},Tuple}, - ψ0::QuantumObject{StateOpType}, + H::Union{AbstractQuantumObject,Tuple}, + ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index f571ac4d3..1a6aa8879 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -12,7 +12,7 @@ _smesolve_ScalarOperator(op_vec) = @doc raw""" smesolveProblem( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; @@ -26,7 +26,7 @@ _smesolve_ScalarOperator(op_vec) = Generate the SDEProblem for the Stochastic Master Equation time evolution of an open quantum system. This is defined by the following stochastic differential equation: ```math -d| \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_n \mathcal{D}[\hat{C}_n] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), +d \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_i \mathcal{D}[\hat{C}_i] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), ``` where @@ -41,15 +41,15 @@ is the Lindblad superoperator, and \mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, ``` -Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipation, while ``\hat{S}_n`` are the stochastic collapse operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. -- `sc_ops`: List of measurement collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NamedTuple` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -69,7 +69,7 @@ Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while """ function smesolveProblem( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; @@ -78,12 +78,12 @@ function smesolveProblem( rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) isnothing(sc_ops) && - throw(ArgumentError("The list of measurement collapse operators must be provided. Use mesolveProblem instead.")) + throw(ArgumentError("The list of stochastic collapse operators must be provided. Use mesolveProblem instead.")) tlist = _check_tlist(tlist, _FType(ψ0)) @@ -131,7 +131,7 @@ end @doc raw""" smesolveEnsembleProblem( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; @@ -149,7 +149,7 @@ end Generate the SDEProblem for the Stochastic Master Equation time evolution of an open quantum system. This is defined by the following stochastic differential equation: ```math -d| \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_n \mathcal{D}[\hat{C}_n] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), +d \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_i \mathcal{D}[\hat{C}_i] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), ``` where @@ -164,15 +164,15 @@ is the Lindblad superoperator, and \mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, ``` -Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipation, while ``\hat{S}_n`` are the stochastic collapse operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. -- `sc_ops`: List of measurement collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NamedTuple` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -196,7 +196,7 @@ Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while """ function smesolveEnsembleProblem( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; @@ -209,7 +209,7 @@ function smesolveEnsembleProblem( output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} _prob_func = isnothing(prob_func) ? _ensemble_dispatch_prob_func(rng, ntraj, tlist, _stochastic_prob_func) : prob_func _output_func = @@ -242,7 +242,7 @@ end @doc raw""" smesolve( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; @@ -261,7 +261,7 @@ end Stochastic Master Equation time evolution of an open quantum system. This is defined by the following stochastic differential equation: ```math -d| \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_n \mathcal{D}[\hat{C}_n] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), +d \rho (t) = -i [\hat{H}, \rho(t)] dt + \sum_i \mathcal{D}[\hat{C}_i] \rho(t) dt + \sum_n \mathcal{D}[\hat{S}_n] \rho(t) dt + \sum_n \mathcal{H}[\hat{S}_n] \rho(t) dW_n(t), ``` where @@ -276,15 +276,15 @@ is the Lindblad superoperator, and \mathcal{H}[\hat{O}] \rho = \hat{O} \rho + \rho \hat{O}^\dagger - \mathrm{Tr}[\hat{O} \rho + \rho \hat{O}^\dagger] \rho, ``` -Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while ``\hat{S}_n`` are the measurement operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipation, while ``\hat{S}_n`` are the stochastic co operators. The ``dW_n(t)`` term is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. -- `sc_ops`: List of measurement collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NamedTuple` of parameters to pass to the solver. @@ -309,7 +309,7 @@ Above, ``\hat{C}_n`` represent the operators related to pure dissipation, while """ function smesolve( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; @@ -323,7 +323,7 @@ function smesolve( output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), kwargs..., -) +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} ensemble_prob = smesolveEnsembleProblem( H, ψ0, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 22227e939..e2c5accc2 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -108,24 +108,24 @@ d|\psi(t)\rangle = -i \hat{K} |\psi(t)\rangle dt + \sum_n \hat{M}_n |\psi(t)\ran where ```math -\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{C}_n - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n - \frac{e_n^2}{8}\right), +\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{S}_n - \frac{1}{2} \hat{S}_n^\dagger \hat{S}_n - \frac{e_n^2}{8}\right), ``` ```math -\hat{M}_n = \hat{C}_n - \frac{e_n}{2}, +\hat{M}_n = \hat{S}_n - \frac{e_n}{2}, ``` and ```math -e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. +e_n = \langle \hat{S}_n + \hat{S}_n^\dagger \rangle. ``` -Above, ``\hat{C}_n`` is the `n`-th collapse operator and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{C}_n``. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `sc_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NamedTuple` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -158,7 +158,7 @@ function ssesolveProblem( throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) sc_ops isa Nothing && - throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) + throw(ArgumentError("The list of stochastic collapse operators must be provided. Use sesolveProblem instead.")) tlist = _check_tlist(tlist, _FType(ψ0)) @@ -227,24 +227,24 @@ d|\psi(t)\rangle = -i \hat{K} |\psi(t)\rangle dt + \sum_n \hat{M}_n |\psi(t)\ran where ```math -\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{C}_n - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n - \frac{e_n^2}{8}\right), +\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{S}_n - \frac{1}{2} \hat{S}_n^\dagger \hat{S}_n - \frac{e_n^2}{8}\right), ``` ```math -\hat{M}_n = \hat{C}_n - \frac{e_n}{2}, +\hat{M}_n = \hat{S}_n - \frac{e_n}{2}, ``` and ```math -e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. +e_n = \langle \hat{S}_n + \hat{S}_n^\dagger \rangle. ``` -Above, ``\hat{C}_n`` is the `n`-th collapse operator and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{C}_n``. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `sc_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NamedTuple` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -340,7 +340,7 @@ end ) -Stochastic Schrödinger equation evolution of a quantum system given the system Hamiltonian ``\hat{H}`` and a list of stochadtic collapse (jump) operators ``\{\hat{C}_n\}_n``. +Stochastic Schrödinger equation evolution of a quantum system given the system Hamiltonian ``\hat{H}`` and a list of stochastic collapse (jump) operators ``\{\hat{S}_n\}_n``. The stochastic evolution of the state ``|\psi(t)\rangle`` is defined by: ```math @@ -350,17 +350,17 @@ d|\psi(t)\rangle = -i \hat{K} |\psi(t)\rangle dt + \sum_n \hat{M}_n |\psi(t)\ran where ```math -\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{C}_n - \frac{1}{2} \hat{C}_n^\dagger \hat{C}_n - \frac{e_n^2}{8}\right), +\hat{K} = \hat{H} + i \sum_n \left(\frac{e_n}{2} \hat{S}_n - \frac{1}{2} \hat{S}_n^\dagger \hat{S}_n - \frac{e_n^2}{8}\right), ``` ```math -\hat{M}_n = \hat{C}_n - \frac{e_n}{2}, +\hat{M}_n = \hat{S}_n - \frac{e_n}{2}, ``` and ```math -e_n = \langle \hat{C}_n + \hat{C}_n^\dagger \rangle. +e_n = \langle \hat{S}_n + \hat{S}_n^\dagger \rangle. ``` -Above, ``\hat{C}_n`` is the `n`-th collapse operator and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{C}_n``. See [Wiseman2009Quantum](@cite) for more details. +Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is the real Wiener increment associated to ``\hat{S}_n``. See [Wiseman2009Quantum](@cite) for more details. # Arguments @@ -368,7 +368,7 @@ Above, ``\hat{C}_n`` is the `n`-th collapse operator and ``dW_n(t)`` is the real - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `sc_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NamedTuple` of parameters to pass to the solver. From b377e5155ecfb41e6be83669b331b7840c30a91f Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 11 Feb 2025 17:40:03 +0100 Subject: [PATCH 203/329] {commit_message} From 0c0adcf2742063df89390a57fb8cea7e89270c77 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 11 Feb 2025 17:43:50 +0100 Subject: [PATCH 204/329] Trigger tests From af1fbf2afd86b6cba42cc48980ba53750721dbb3 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Mon, 10 Feb 2025 17:59:53 +0100 Subject: [PATCH 205/329] [no ci] change MultiSiteOperator function name to multisite_operator --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d2e96de..6b39f7c8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,7 +56,7 @@ Release date: 2024-12-06 ## [v0.23.0] Release date: 2024-12-04 -- Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324]) +- Change `SingleSiteOperator` with the more general `multisite_operator`. ([#324]) - Make `spectrum` and `correlation` functions align with `Python QuTiP`, introduce spectrum solver `PseudoInverse`, remove spectrum solver `FFTCorrelation`, and introduce `spectrum_correlation_fft`. ([#330]) ## [v0.22.0] From 7b72a8e8740f8818437e215f66ea74aa1ade8787 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Tue, 11 Feb 2025 05:05:05 +0100 Subject: [PATCH 206/329] Fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b39f7c8f..77d2e96de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,7 +56,7 @@ Release date: 2024-12-06 ## [v0.23.0] Release date: 2024-12-04 -- Change `SingleSiteOperator` with the more general `multisite_operator`. ([#324]) +- Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324]) - Make `spectrum` and `correlation` functions align with `Python QuTiP`, introduce spectrum solver `PseudoInverse`, remove spectrum solver `FFTCorrelation`, and introduce `spectrum_correlation_fft`. ([#330]) ## [v0.22.0] From 458bc63c2b299d685925215cd9bbc336eeba1b00 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Wed, 12 Feb 2025 17:12:45 +0100 Subject: [PATCH 207/329] [no ci] Fix time evolution output when using saveat --- src/time_evolution/mcsolve.jl | 7 ++----- src/time_evolution/mesolve.jl | 6 +----- src/time_evolution/sesolve.jl | 6 +----- src/time_evolution/smesolve.jl | 6 +----- src/time_evolution/ssesolve.jl | 6 +----- src/time_evolution/time_evolution.jl | 15 +++++++++++++-- test/core-test/time_evolution.jl | 24 +++++++++++++++--------- 7 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 7c006f8e8..e61179832 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -131,12 +131,9 @@ function mcsolveProblem( T = Base.promote_eltype(H_eff_evo, ψ0) - is_empty_e_ops = e_ops isa Nothing ? true : isempty(e_ops) - - saveat = is_empty_e_ops ? tlist : [tlist[end]] # We disable the progress bar of the sesolveProblem because we use a global progress bar for all the trajectories - default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat, progress_bar = Val(false)) - kwargs2 = merge(default_values, kwargs) + default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., progress_bar = Val(false)) + kwargs2 = _merge_saveat(tlist, e_ops, default_values; kwargs...) kwargs3 = _generate_mcsolve_kwargs(ψ0, T, e_ops, tlist, c_ops, jump_callback, rng, kwargs2) return sesolveProblem(H_eff_evo, ψ0, tlist; params = params, kwargs3...) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index de4b7c335..7db098bdd 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -79,11 +79,7 @@ function mesolveProblem( ρ0 = to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type L = L_evo.data - is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) - - saveat = is_empty_e_ops ? tlist : [tlist[end]] - default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) - kwargs2 = merge(default_values, kwargs) + kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_ODE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncMESolve) tspan = (tlist[1], tlist[end]) diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index a9996a633..368d46241 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -69,11 +69,7 @@ function sesolveProblem( ψ0 = to_dense(_CType(T), get_data(ψ0)) # Convert it to dense vector with complex element type U = H_evo.data - is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) - - saveat = is_empty_e_ops ? tlist : [tlist[end]] - default_values = (DEFAULT_ODE_SOLVER_OPTIONS..., saveat = saveat) - kwargs2 = merge(default_values, kwargs) + kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_ODE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSESolve) tspan = (tlist[1], tlist[end]) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 1a6aa8879..793b53b9e 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -112,11 +112,7 @@ function smesolveProblem( p = (progr = progr, times = tlist, Hdims = dims, n_sc_ops = length(sc_ops), params...) - is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) - - saveat = is_empty_e_ops ? tlist : [tlist[end]] - default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) - kwargs2 = merge(default_values, kwargs) + kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncMESolve) tspan = (tlist[1], tlist[end]) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index e2c5accc2..e6c5515d8 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -186,11 +186,7 @@ function ssesolveProblem( p = (progr = progr, times = tlist, Hdims = dims, n_sc_ops = length(sc_ops), params...) - is_empty_e_ops = (e_ops isa Nothing) ? true : isempty(e_ops) - - saveat = is_empty_e_ops ? tlist : [tlist[end]] - default_values = (DEFAULT_SDE_SOLVER_OPTIONS..., saveat = saveat) - kwargs2 = merge(default_values, kwargs) + kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSSESolve) kwargs4 = _ssesolve_add_normalize_cb(kwargs3) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index a21221e26..1b19695b4 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -2,8 +2,8 @@ export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionStochasticSol export liouvillian_floquet, liouvillian_generalized -const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true) -const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true) +const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false) +const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false) const JUMP_TIMES_WHICH_INIT_SIZE = 200 @doc raw""" @@ -230,6 +230,17 @@ function _check_tlist(tlist, T::Type) return tlist2 end +####################################### + +function _merge_saveat(tlist, e_ops, default_options; kwargs...) + is_empty_e_ops = isnothing(e_ops) ? true : isempty(e_ops) + saveat = is_empty_e_ops ? tlist : [tlist[end]] + default_values = (default_options..., saveat = saveat) + kwargs2 = merge(default_values, kwargs) + save_end = tlist[end] in kwargs2.saveat # DifferentialEquations.jl has this weird setting + return merge(kwargs2, (save_end = save_end,)) +end + ####################################### #= Helpers for handling output of ensemble problems. diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index f2c2b6447..cf480fa6f 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -26,11 +26,13 @@ @testset "sesolve" begin tlist = range(0, 20 * 2π / g, 1000) + saveat_idxs = 500:900 + saveat = tlist[saveat_idxs] prob = sesolveProblem(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) sol = sesolve(prob) sol2 = sesolve(H, ψ0, tlist, progress_bar = Val(false)) - sol3 = sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) + sol3 = sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) sol_string = sprint((t, s) -> show(t, "text/plain", s), sol) sol_string2 = sprint((t, s) -> show(t, "text/plain", s), sol2) @@ -48,8 +50,9 @@ @test length(sol2.states) == length(tlist) @test sol2.expect === nothing @test length(sol3.times) == length(tlist) - @test length(sol3.states) == length(tlist) + @test length(sol3.states) == length(saveat) @test size(sol3.expect) == (length(e_ops), length(tlist)) + @test sol.expect[1, saveat_idxs] ≈ expect(e_ops[1], sol3.states) atol = 1e-6 @test sol_string == "Solution of time evolution\n" * "(return code: $(sol.retcode))\n" * @@ -92,18 +95,20 @@ @inferred sesolveProblem(H, ψ0_int, tlist, progress_bar = Val(false)) @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) @inferred sesolve(H, ψ0, tlist, progress_bar = Val(false)) - @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) @inferred sesolve(H, ψ0, tlist, e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types end end @testset "mesolve, mcsolve, ssesolve and smesolve" begin tlist = range(0, 10 / γ, 100) + saveat_idxs = 50:90 + saveat = tlist[saveat_idxs] prob_me = mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) sol_me = mesolve(prob_me) sol_me2 = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) - sol_me3 = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) + sol_me3 = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) prob_mc = mcsolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) sol_mc = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) sol_mc2 = mcsolve( @@ -116,14 +121,14 @@ progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), ) - sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, saveat = tlist, progress_bar = Val(false)) + sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, saveat = saveat, progress_bar = Val(false)) sol_mc_states2 = mcsolve( H, ψ0, tlist, c_ops, ntraj = 500, - saveat = tlist, + saveat = saveat, progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), ) @@ -147,8 +152,8 @@ @test prob_mc.prob.f.f isa MatrixOperator @test sum(abs, sol_mc.expect .- sol_me.expect) / length(tlist) < 0.1 @test sum(abs, sol_mc2.expect .- sol_me.expect) / length(tlist) < 0.1 - @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, :])) / length(tlist) < 0.1 - @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, :])) / length(tlist) < 0.1 + @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 + @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 @test sum(abs, sol_sse.expect .- sol_me.expect) / length(tlist) < 0.1 @test sum(abs, sol_sme.expect .- sol_me.expect) / length(tlist) < 0.1 @test length(sol_me.times) == length(tlist) @@ -158,8 +163,9 @@ @test length(sol_me2.states) == length(tlist) @test sol_me2.expect === nothing @test length(sol_me3.times) == length(tlist) - @test length(sol_me3.states) == length(tlist) + @test length(sol_me3.states) == length(saveat) @test size(sol_me3.expect) == (length(e_ops), length(tlist)) + @test sol_me3.expect[1, saveat_idxs] ≈ expect(e_ops[1], sol_me3.states) atol = 1e-6 @test length(sol_mc.times) == length(tlist) @test size(sol_mc.expect) == (length(e_ops), length(tlist)) @test length(sol_mc_states.times) == length(tlist) From b1db66e388ab8315ff2a66f8cf974ec03a37e168 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Wed, 12 Feb 2025 17:17:10 +0100 Subject: [PATCH 208/329] Make changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d2e96de..ab1f53bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix erroneous definition of the stochastic term in `smesolve`. ([#393]) - Change name of `MultiSiteOperator` to `multisite_operator`. ([#394]) - Fix `smesolve` for specifying initial state as density matrix. ([#395]) +- Fix time evolution output when using `saveat` keyword argument. ([#398]) ## [v0.26.0] Release date: 2025-02-09 @@ -124,3 +125,4 @@ Release date: 2024-11-13 [#393]: https://github.com/qutip/QuantumToolbox.jl/issues/393 [#394]: https://github.com/qutip/QuantumToolbox.jl/issues/394 [#395]: https://github.com/qutip/QuantumToolbox.jl/issues/395 +[#398]: https://github.com/qutip/QuantumToolbox.jl/issues/398 From 72e7b79d141009ab6e4ae2b841686580caa2943e Mon Sep 17 00:00:00 2001 From: Alberto Mercurio Date: Wed, 12 Feb 2025 17:56:15 +0100 Subject: [PATCH 209/329] Fix steadystate --- src/time_evolution/time_evolution.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 1b19695b4..20b7f9257 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -2,8 +2,8 @@ export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionStochasticSol export liouvillian_floquet, liouvillian_generalized -const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false) -const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false) +const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true) +const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true) const JUMP_TIMES_WHICH_INIT_SIZE = 200 @doc raw""" @@ -237,7 +237,13 @@ function _merge_saveat(tlist, e_ops, default_options; kwargs...) saveat = is_empty_e_ops ? tlist : [tlist[end]] default_values = (default_options..., saveat = saveat) kwargs2 = merge(default_values, kwargs) - save_end = tlist[end] in kwargs2.saveat # DifferentialEquations.jl has this weird setting + + # DifferentialEquations.jl has this weird save_end setting + # So we need to do this to make sure it's consistent + haskey(kwargs, :save_end) && return kwargs2 + isempty(kwargs2.saveat) && return kwargs2 + + save_end = tlist[end] in kwargs2.saveat return merge(kwargs2, (save_end = save_end,)) end From 28fe4a902cb986209f6d2bb1c0f7816406bc271e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:53:18 +0900 Subject: [PATCH 210/329] CompatHelper: bump compat for LinearSolve to 3, (keep existing compat) (#387) --- Project.toml | 2 +- src/qobj/arithmetic_and_attributes.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 54b5d80ad..1552ca026 100644 --- a/Project.toml +++ b/Project.toml @@ -51,7 +51,7 @@ Graphs = "1.7" IncompleteLU = "0.2" KernelAbstractions = "0.9.2" LinearAlgebra = "1" -LinearSolve = "2" +LinearSolve = "2, 3" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 488be7956..add9248e8 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -443,7 +443,7 @@ LinearAlgebra.exp( ) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = QuantumObject(_spexp(A.data), A.type, A.dimensions) -function _spexp(A::SparseMatrixCSC{T,M}; threshold = 1e-14, nonzero_tol = 1e-20) where {T,M} +function _spexp(A::SparseMatrixCSC{T,M}; threshold = 1e-14, nonzero_tol = 1e-20) where {T<:Number,M<:Int} m = checksquare(A) # Throws exception if not square mat_norm = norm(A, Inf) From 0ce0b452128c569dc01291b4f94e6bba9bc01b07 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:23:55 +0900 Subject: [PATCH 211/329] Add more generic solver for steadystate_floquet (#396) --- CHANGELOG.md | 2 + docs/src/resources/api.md | 3 +- docs/src/users_guide/steadystate.md | 2 +- src/steadystate.jl | 76 ++++++++++++++++++++--------- test/core-test/steady_state.jl | 17 +++++-- 5 files changed, 69 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1f53bc1..c03b26d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix erroneous definition of the stochastic term in `smesolve`. ([#393]) - Change name of `MultiSiteOperator` to `multisite_operator`. ([#394]) - Fix `smesolve` for specifying initial state as density matrix. ([#395]) +- Add more generic solver for `steadystate_floquet` to allow more linear solvers. ([#396]) - Fix time evolution output when using `saveat` keyword argument. ([#398]) ## [v0.26.0] @@ -125,4 +126,5 @@ Release date: 2024-11-13 [#393]: https://github.com/qutip/QuantumToolbox.jl/issues/393 [#394]: https://github.com/qutip/QuantumToolbox.jl/issues/394 [#395]: https://github.com/qutip/QuantumToolbox.jl/issues/395 +[#396]: https://github.com/qutip/QuantumToolbox.jl/issues/396 [#398]: https://github.com/qutip/QuantumToolbox.jl/issues/398 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 6e95ec396..a6f5389a9 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -212,11 +212,12 @@ liouvillian_generalized ```@docs steadystate -steadystate_floquet +steadystate_fourier SteadyStateDirectSolver SteadyStateEigenSolver SteadyStateLinearSolver SteadyStateODESolver +SSFloquetEffectiveLiouvillian ``` ### [Dynamical Shifted Fock method](@id doc-API:Dynamical-Shifted-Fock-method) diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index 52e64419c..bd68d5a58 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -121,4 +121,4 @@ fig ## Calculate steady state for periodically driven systems -See the docstring of [`steadystate_floquet`](@ref) for more details. +See the docstring of [`steadystate_fourier`](@ref) for more details. diff --git a/src/steadystate.jl b/src/steadystate.jl index a88ef8bf5..94cbd3222 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -1,15 +1,12 @@ -export steadystate, steadystate_floquet +export steadystate, steadystate_fourier, steadystate_floquet export SteadyStateSolver, SteadyStateDirectSolver, SteadyStateEigenSolver, SteadyStateLinearSolver, SteadyStateODESolver, - SteadyStateFloquetSolver, - SSFloquetLinearSystem, SSFloquetEffectiveLiouvillian abstract type SteadyStateSolver end -abstract type SteadyStateFloquetSolver end @doc raw""" SteadyStateDirectSolver() @@ -58,8 +55,18 @@ Base.@kwdef struct SteadyStateODESolver{MT<:OrdinaryDiffEqAlgorithm} <: SteadySt alg::MT = Tsit5() end -struct SSFloquetLinearSystem <: SteadyStateFloquetSolver end -Base.@kwdef struct SSFloquetEffectiveLiouvillian{SSST<:SteadyStateSolver} <: SteadyStateFloquetSolver +@doc raw""" + SSFloquetEffectiveLiouvillian(steadystate_solver = SteadyStateDirectSolver()) + +A solver which solves [`steadystate_fourier`](@ref) by first extracting an effective time-independent Liouvillian and then using the `steadystate_solver` to extract the steadystate.. + +# Parameters +- `steadystate_solver::SteadyStateSolver=SteadyStateDirectSolver()`: The solver to use for the effective Liouvillian. + +!!! note + This solver is only available for [`steadystate_fourier`](@ref). +""" +Base.@kwdef struct SSFloquetEffectiveLiouvillian{SSST<:SteadyStateSolver} <: SteadyStateSolver steadystate_solver::SSST = SteadyStateDirectSolver() end @@ -85,6 +92,12 @@ function steadystate( solver::SteadyStateSolver = SteadyStateDirectSolver(), kwargs..., ) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} + solver isa SSFloquetEffectiveLiouvillian && throw( + ArgumentError( + "The solver `SSFloquetEffectiveLiouvillian` is only available for the `steadystate_fourier` function.", + ), + ) + L = liouvillian(H, c_ops) return _steadystate(L, solver; kwargs...) @@ -247,15 +260,15 @@ function _steadystate_ode_condition(integrator, abstol, reltol, min_t) end @doc raw""" - steadystate_floquet( - H_0::QuantumObject{OpType1}, - H_p::QuantumObject{OpType2}, - H_m::QuantumObject{OpType3}, + steadystate_fourier( + H_0::QuantumObject, + H_p::QuantumObject, + H_m::QuantumObject, ωd::Number, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Integer = 2, tol::R = 1e-8, - solver::FSolver = SSFloquetLinearSystem, + solver::FSolver = SteadyStateLinearSolver(), kwargs..., ) @@ -265,11 +278,11 @@ Considering a monochromatic drive at frequency ``\omega_d``, we divide it into t `H_p` and `H_m`, where `H_p` oscillates as ``e^{i \omega t}`` and `H_m` oscillates as ``e^{-i \omega t}``. There are two solvers available for this function: -- `SSFloquetLinearSystem`: Solves the linear system of equations. +- `SteadyStateLinearSolver`: Solves the linear system of equations. - `SSFloquetEffectiveLiouvillian`: Solves the effective Liouvillian. For both cases, `n_max` is the number of Fourier components to consider, and `tol` is the tolerance for the solver. -In the case of `SSFloquetLinearSystem`, the full linear system is solved at once: +In the case of `SteadyStateLinearSolver`, the full linear system is solved at once: ```math ( \mathcal{L}_0 - i n \omega_d ) \hat{\rho}_n + \mathcal{L}_1 \hat{\rho}_{n-1} + \mathcal{L}_{-1} \hat{\rho}_{n+1} = 0 @@ -312,7 +325,10 @@ This will allow to simultaneously obtain all the ``\hat{\rho}_n``. In the case of `SSFloquetEffectiveLiouvillian`, instead, the effective Liouvillian is calculated using the matrix continued fraction method. !!! note "different return" - The two solvers returns different objects. The `SSFloquetLinearSystem` returns a list of [`QuantumObject`](@ref), containing the density matrices for each Fourier component (``\hat{\rho}_{-n}``, with ``n`` from ``0`` to ``n_\textrm{max}``), while the `SSFloquetEffectiveLiouvillian` returns only ``\hat{\rho}_0``. + The two solvers returns different objects. The `SteadyStateLinearSolver` returns a list of [`QuantumObject`](@ref), containing the density matrices for each Fourier component (``\hat{\rho}_{-n}``, with ``n`` from ``0`` to ``n_\textrm{max}``), while the `SSFloquetEffectiveLiouvillian` returns only ``\hat{\rho}_0``. + +!!! note + `steadystate_floquet` is a synonym of `steadystate_fourier`. ## Arguments - `H_0::QuantumObject`: The Hamiltonian or the Liouvillian of the undriven system. @@ -322,10 +338,10 @@ In the case of `SSFloquetEffectiveLiouvillian`, instead, the effective Liouvilli - `c_ops::Union{Nothing,AbstractVector} = nothing`: The optional collapse operators. - `n_max::Integer = 2`: The number of Fourier components to consider. - `tol::R = 1e-8`: The tolerance for the solver. -- `solver::FSolver = SSFloquetLinearSystem`: The solver to use. +- `solver::FSolver = SteadyStateLinearSolver`: The solver to use. - `kwargs...`: Additional keyword arguments to be passed to the solver. """ -function steadystate_floquet( +function steadystate_fourier( H_0::QuantumObject{OpType1}, H_p::QuantumObject{OpType2}, H_m::QuantumObject{OpType3}, @@ -333,27 +349,27 @@ function steadystate_floquet( c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; n_max::Integer = 2, tol::R = 1e-8, - solver::FSolver = SSFloquetLinearSystem(), + solver::FSolver = SteadyStateLinearSolver(), kwargs..., ) where { OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, OpType2<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, OpType3<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, R<:Real, - FSolver<:SteadyStateFloquetSolver, + FSolver<:SteadyStateSolver, } L_0 = liouvillian(H_0, c_ops) L_p = liouvillian(H_p) L_m = liouvillian(H_m) - return _steadystate_floquet(L_0, L_p, L_m, ωd, solver; n_max = n_max, tol = tol, kwargs...) + return _steadystate_fourier(L_0, L_p, L_m, ωd, solver; n_max = n_max, tol = tol, kwargs...) end -function _steadystate_floquet( +function _steadystate_fourier( L_0::QuantumObject{SuperOperatorQuantumObject}, L_p::QuantumObject{SuperOperatorQuantumObject}, L_m::QuantumObject{SuperOperatorQuantumObject}, ωd::Number, - solver::SSFloquetLinearSystem; + solver::SteadyStateLinearSolver; n_max::Integer = 1, tol::R = 1e-8, kwargs..., @@ -387,9 +403,18 @@ function _steadystate_floquet( v0 = zeros(T, n_fourier * N) v0[n_max*N+1] = weight - Pl = ilu(M, τ = 0.01) + (haskey(kwargs, :Pl) || haskey(kwargs, :Pr)) && error("The use of preconditioners must be defined in the solver.") + if !isnothing(solver.Pl) + kwargs = merge((; kwargs...), (Pl = solver.Pl(M),)) + elseif isa(M, SparseMatrixCSC) + kwargs = merge((; kwargs...), (Pl = ilu(M, τ = 0.01),)) + end + !isnothing(solver.Pr) && (kwargs = merge((; kwargs...), (Pr = solver.Pr(M),))) + !haskey(kwargs, :abstol) && (kwargs = merge((; kwargs...), (abstol = tol,))) + !haskey(kwargs, :reltol) && (kwargs = merge((; kwargs...), (reltol = tol,))) + prob = LinearProblem(M, v0) - ρtot = solve(prob, KrylovJL_GMRES(), Pl = Pl, abstol = tol, reltol = tol).u + ρtot = solve(prob, solver.alg; kwargs...).u offset1 = n_max * N offset2 = (n_max + 1) * N @@ -409,7 +434,7 @@ function _steadystate_floquet( return ρ_list end -function _steadystate_floquet( +function _steadystate_fourier( L_0::QuantumObject{SuperOperatorQuantumObject}, L_p::QuantumObject{SuperOperatorQuantumObject}, L_m::QuantumObject{SuperOperatorQuantumObject}, @@ -425,3 +450,6 @@ function _steadystate_floquet( return steadystate(L_eff; solver = solver.steadystate_solver, kwargs...) end + +# TODO: Synonym to align with QuTiP. Track https://github.com/qutip/qutip/issues/2632 when this can be removed. +const steadystate_floquet = steadystate_fourier diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index 630529559..de33b2ad1 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -65,16 +65,23 @@ H_td = (H, (H_t, coeff)) sol_me = mesolve(H_td, psi0, t_l, c_ops, e_ops = e_ops, progress_bar = Val(false)) - ρ_ss1 = steadystate_floquet(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetLinearSystem())[1] + ρ_ss1 = steadystate_fourier(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SteadyStateLinearSolver())[1] ρ_ss2 = - steadystate_floquet(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetEffectiveLiouvillian()) + steadystate_fourier(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetEffectiveLiouvillian()) @test abs(sum(sol_me.expect[1, end-100:end]) / 101 - expect(e_ops[1], ρ_ss1)) < 1e-3 @test abs(sum(sol_me.expect[1, end-100:end]) / 101 - expect(e_ops[1], ρ_ss2)) < 1e-3 - @testset "Type Inference (steadystate_floquet)" begin - @inferred steadystate_floquet(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetLinearSystem()) - @inferred steadystate_floquet( + @testset "Type Inference (steadystate_fourier)" begin + @inferred steadystate_fourier( + H, + -1im * 0.5 * H_t, + 1im * 0.5 * H_t, + 1, + c_ops, + solver = SteadyStateLinearSolver(), + ) + @inferred steadystate_fourier( H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, From bfcb2b050129f2822f7fb3465ccf6072b045c31c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:30:17 +0900 Subject: [PATCH 212/329] fix CleanPreviewDoc CI --- .github/workflows/CleanPreviewDoc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CleanPreviewDoc.yml b/.github/workflows/CleanPreviewDoc.yml index d3e4de25e..8301dd1ac 100644 --- a/.github/workflows/CleanPreviewDoc.yml +++ b/.github/workflows/CleanPreviewDoc.yml @@ -23,4 +23,5 @@ jobs: git push --force origin gh-pages-new:gh-pages fi env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PRNUM: ${{ github.event.number }} From 350904e1a8e94f331ea8ff52378ff15f3e75addf Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:32:03 +0900 Subject: [PATCH 213/329] fix CleanPreviewDoc CI --- .github/workflows/CleanPreviewDoc.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/CleanPreviewDoc.yml b/.github/workflows/CleanPreviewDoc.yml index 8301dd1ac..bf115de14 100644 --- a/.github/workflows/CleanPreviewDoc.yml +++ b/.github/workflows/CleanPreviewDoc.yml @@ -4,6 +4,10 @@ on: pull_request: types: [closed] +permissions: + contents: write + deployments: write + jobs: cleanup-preview-doc: runs-on: ubuntu-latest From dff6aa17cb6a5e9effd228d086dcbdf67001dcde Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:27:56 +0900 Subject: [PATCH 214/329] Improve ensemble generation of ssesolve and change parameters on stochastic processes (#403) --- CHANGELOG.md | 2 + src/time_evolution/smesolve.jl | 39 ++-- src/time_evolution/ssesolve.jl | 264 +++++++++------------------ src/time_evolution/time_evolution.jl | 51 +++++- 4 files changed, 157 insertions(+), 199 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c03b26d02..d31c9ea89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix `smesolve` for specifying initial state as density matrix. ([#395]) - Add more generic solver for `steadystate_floquet` to allow more linear solvers. ([#396]) - Fix time evolution output when using `saveat` keyword argument. ([#398]) +- Improve ensemble generation of `ssesolve` and change parameters handling on stochastic processes. ([#403]) ## [v0.26.0] Release date: 2025-02-09 @@ -128,3 +129,4 @@ Release date: 2024-11-13 [#395]: https://github.com/qutip/QuantumToolbox.jl/issues/395 [#396]: https://github.com/qutip/QuantumToolbox.jl/issues/396 [#398]: https://github.com/qutip/QuantumToolbox.jl/issues/398 +[#403]: https://github.com/qutip/QuantumToolbox.jl/issues/403 diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 793b53b9e..5d50851d1 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -17,7 +17,7 @@ _smesolve_ScalarOperator(op_vec) = c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., @@ -51,7 +51,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs`: The keyword arguments for the ODEProblem. @@ -74,7 +74,7 @@ function smesolveProblem( c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., @@ -110,8 +110,6 @@ function smesolveProblem( end D = DiffusionOperator(D_l) - p = (progr = progr, times = tlist, Hdims = dims, n_sc_ops = length(sc_ops), params...) - kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncMESolve) @@ -119,7 +117,16 @@ function smesolveProblem( noise = RealWienerProcess!(tlist[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) noise_rate_prototype = similar(ρ0, length(ρ0), length(sc_ops)) - prob = SDEProblem{true}(K, D, ρ0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, kwargs3...) + prob = SDEProblem{true}( + K, + D, + ρ0, + tspan, + params; + noise_rate_prototype = noise_rate_prototype, + noise = noise, + kwargs3..., + ) return TimeEvolutionProblem(prob, tlist, dims) end @@ -132,7 +139,7 @@ end c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), @@ -170,11 +177,11 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. -- `prob_func`: Function to use for generating the ODEProblem. +- `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs`: The keyword arguments for the ODEProblem. @@ -197,7 +204,7 @@ function smesolveEnsembleProblem( c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), @@ -207,7 +214,8 @@ function smesolveEnsembleProblem( kwargs..., ) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} _prob_func = - isnothing(prob_func) ? _ensemble_dispatch_prob_func(rng, ntraj, tlist, _stochastic_prob_func) : prob_func + isnothing(prob_func) ? + _ensemble_dispatch_prob_func(rng, ntraj, tlist, _stochastic_prob_func; n_sc_ops = length(sc_ops)) : prob_func _output_func = output_func isa Nothing ? _ensemble_dispatch_output_func(ensemble_method, progress_bar, ntraj, _stochastic_output_func) : output_func @@ -244,7 +252,7 @@ end sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), @@ -283,11 +291,11 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. -- `prob_func`: Function to use for generating the ODEProblem. +- `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs`: The keyword arguments for the ODEProblem. @@ -311,7 +319,7 @@ function smesolve( sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), @@ -351,7 +359,6 @@ function smesolve( _sol_1 = sol[:, 1] _expvals_sol_1 = _se_me_sse_get_expvals(_sol_1) - normalize_states = Val(false) dims = ens_prob.dimensions _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index e6c5515d8..f3173dd04 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -1,64 +1,5 @@ export ssesolveProblem, ssesolveEnsembleProblem, ssesolve -# TODO: Merge this with _stochastic_prob_func -function _ssesolve_prob_func(prob, i, repeat) - internal_params = prob.p - - global_rng = internal_params.global_rng - seed = internal_params.seeds[i] - traj_rng = typeof(global_rng)() - seed!(traj_rng, seed) - - noise = RealWienerProcess!( - prob.tspan[1], - zeros(internal_params.n_sc_ops), - zeros(internal_params.n_sc_ops), - save_everystep = false, - rng = traj_rng, - ) - - return remake(prob, noise = noise, seed = seed) -end - -#= - struct DiffusionOperator - -A struct to represent the diffusion operator. This is used to perform the diffusion process on N different Wiener processes. -=# -struct DiffusionOperator{T,OpType<:Tuple{Vararg{AbstractSciMLOperator}}} <: AbstractSciMLOperator{T} - ops::OpType - function DiffusionOperator(ops::OpType) where {OpType} - T = mapreduce(eltype, promote_type, ops) - return new{T,OpType}(ops) - end -end - -@generated function update_coefficients!(L::DiffusionOperator, u, p, t) - ops_types = L.parameters[2].parameters - N = length(ops_types) - quote - Base.@nexprs $N i -> begin - update_coefficients!(L.ops[i], u, p, t) - end - - nothing - end -end - -@generated function LinearAlgebra.mul!(v::AbstractVecOrMat, L::DiffusionOperator, u::AbstractVecOrMat) - ops_types = L.parameters[2].parameters - N = length(ops_types) - quote - M = length(u) - S = size(v) - (S[1] == M && S[2] == $N) || throw(DimensionMismatch("The size of the output vector is incorrect.")) - Base.@nexprs $N i -> begin - mul!(@view(v[:, i]), L.ops[i], u) - end - return v - end -end - # TODO: Implement the three-argument dot function for SciMLOperators.jl # Currently, we are assuming a time-independent MatrixOperator function _ssesolve_update_coeff(u, p, t, op) @@ -66,21 +7,6 @@ function _ssesolve_update_coeff(u, p, t, op) return real(dot(u, op.A, u)) #this is en/2: /2 = Re end -# Output function with progress bar update -function _ssesolve_output_func_progress(sol, i) - next!(sol.prob.p.progr) - return _stochastic_output_func(sol, i) -end - -# Output function with distributed channel update for progress bar -function _ssesolve_output_func_distributed(sol, i) - put!(sol.prob.p.progr_channel, true) - return _stochastic_output_func(sol, i) -end - -_ssesolve_dispatch_output_func(::ET) where {ET<:Union{EnsembleSerial,EnsembleThreads}} = _ssesolve_output_func_progress -_ssesolve_dispatch_output_func(::EnsembleDistributed) = _ssesolve_output_func_distributed - _ScalarOperator_e(op, f = +) = ScalarOperator(one(eltype(op)), (a, u, p, t) -> f(_ssesolve_update_coeff(u, p, t, op))) _ScalarOperator_e2_2(op, f = +) = @@ -93,7 +19,7 @@ _ScalarOperator_e2_2(op, f = +) = tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., @@ -127,7 +53,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `tlist`: List of times at which to save either the state or the expectation values of the system. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs`: The keyword arguments for the ODEProblem. @@ -149,7 +75,7 @@ function ssesolveProblem( tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), kwargs..., @@ -184,8 +110,6 @@ function ssesolveProblem( D_l = map(op -> op + _ScalarOperator_e(op, -) * IdentityOperator(prod(dims)), sc_ops_evo_data) D = DiffusionOperator(D_l) - p = (progr = progr, times = tlist, Hdims = dims, n_sc_ops = length(sc_ops), params...) - kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSSESolve) kwargs4 = _ssesolve_add_normalize_cb(kwargs3) @@ -194,7 +118,18 @@ function ssesolveProblem( noise = RealWienerProcess!(tlist[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) noise_rate_prototype = similar(ψ0, length(ψ0), length(sc_ops)) - return SDEProblem{true}(K, D, ψ0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, kwargs4...) + prob = SDEProblem{true}( + K, + D, + ψ0, + tspan, + params; + noise_rate_prototype = noise_rate_prototype, + noise = noise, + kwargs4..., + ) + + return TimeEvolutionProblem(prob, tlist, dims) end @doc raw""" @@ -204,12 +139,12 @@ end tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), - prob_func::Function = _ssesolve_prob_func, - output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), + prob_func::Union{Function, Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) @@ -242,13 +177,13 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is t - `tlist`: List of times at which to save either the state or the expectation values of the system. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. - `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. -- `prob_func`: Function to use for generating the ODEProblem. -- `output_func`: Function to use for generating the output of a single trajectory. +- `prob_func`: Function to use for generating the SDEProblem. +- `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs`: The keyword arguments for the ODEProblem. @@ -269,52 +204,42 @@ function ssesolveEnsembleProblem( tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), - prob_func::Function = _ssesolve_prob_func, - output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), + prob_func::Union{Function,Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) - progr = ProgressBar(ntraj, enable = getVal(progress_bar)) - if ensemble_method isa EnsembleDistributed - progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) - @async while take!(progr_channel) - next!(progr) - end - params = merge(params, (progr_channel = progr_channel,)) - else - params = merge(params, (progr_trajectories = progr,)) - end - - # Stop the async task if an error occurs - try - seeds = map(i -> rand(rng, UInt64), 1:ntraj) - prob_sse = ssesolveProblem( - H, - ψ0, - tlist, - sc_ops; - e_ops = e_ops, - params = merge(params, (global_rng = rng, seeds = seeds)), - rng = rng, - progress_bar = Val(false), - kwargs..., - ) - - # safetycopy is set to true because the K and D functions cannot be currently deepcopied. - # the memory overhead shouldn't be too large, compared to the safetycopy=false case. - ensemble_prob = EnsembleProblem(prob_sse, prob_func = prob_func, output_func = output_func, safetycopy = true) - - return ensemble_prob - catch e - if ensemble_method isa EnsembleDistributed - put!(progr_channel, false) - end - rethrow(e) - end + _prob_func = + isnothing(prob_func) ? + _ensemble_dispatch_prob_func(rng, ntraj, tlist, _stochastic_prob_func; n_sc_ops = length(sc_ops)) : prob_func + _output_func = + output_func isa Nothing ? + _ensemble_dispatch_output_func(ensemble_method, progress_bar, ntraj, _stochastic_output_func) : output_func + + prob_sme = ssesolveProblem( + H, + ψ0, + tlist, + sc_ops; + e_ops = e_ops, + params = params, + rng = rng, + progress_bar = Val(false), + kwargs..., + ) + + ensemble_prob = TimeEvolutionProblem( + EnsembleProblem(prob_sme, prob_func = _prob_func, output_func = _output_func[1], safetycopy = true), + prob_sme.times, + prob_sme.dimensions, + (progr = _output_func[2], channel = _output_func[3]), + ) + + return ensemble_prob end @doc raw""" @@ -325,12 +250,12 @@ end sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), - prob_func::Function = _ssesolve_prob_func, - output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), + prob_func::Union{Function, Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) @@ -367,12 +292,12 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. -- `params`: `NamedTuple` of parameters to pass to the solver. +- `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. - `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. -- `prob_func`: Function to use for generating the ODEProblem. -- `output_func`: Function to use for generating the output of a single trajectory. +- `prob_func`: Function to use for generating the SDEProblem. +- `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `kwargs`: The keyword arguments for the ODEProblem. @@ -395,12 +320,12 @@ function ssesolve( sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::StochasticDiffEqAlgorithm = SRA1(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - params::NamedTuple = NamedTuple(), + params = NullParameters(), rng::AbstractRNG = default_rng(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), - prob_func::Function = _ssesolve_prob_func, - output_func::Function = _ssesolve_dispatch_output_func(ensemble_method), + prob_func::Union{Function,Nothing} = nothing, + output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), kwargs..., ) @@ -420,52 +345,39 @@ function ssesolve( kwargs..., ) - return ssesolve(ens_prob; alg = alg, ntraj = ntraj, ensemble_method = ensemble_method) + return ssesolve(ens_prob, alg, ntraj, ensemble_method) end function ssesolve( - ens_prob::EnsembleProblem; + ens_prob::TimeEvolutionProblem, alg::StochasticDiffEqAlgorithm = SRA1(), ntraj::Int = 1, ensemble_method = EnsembleThreads(), ) - # Stop the async task if an error occurs - try - sol = solve(ens_prob, alg, ensemble_method, trajectories = ntraj) - - if ensemble_method isa EnsembleDistributed - put!(sol[:, 1].prob.p.progr_channel, false) - end - - _sol_1 = sol[:, 1] - _expvals_sol_1 = _se_me_sse_get_expvals(_sol_1) - - normalize_states = Val(false) - dims = _sol_1.prob.p.Hdims - _expvals_all = - _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) - expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) - states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) - - expvals = - _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : - dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) - - return TimeEvolutionStochasticSol( - ntraj, - _sol_1.prob.p.times, - states, - expvals, - expvals_all, - sol.converged, - _sol_1.alg, - _sol_1.prob.kwargs[:abstol], - _sol_1.prob.kwargs[:reltol], - ) - catch e - if ensemble_method isa EnsembleDistributed - put!(ens_prob.prob.p.progr_channel, false) - end - rethrow(e) - end + sol = _ensemble_dispatch_solve(ens_prob, alg, ensemble_method, ntraj) + + _sol_1 = sol[:, 1] + _expvals_sol_1 = _se_me_sse_get_expvals(_sol_1) + + normalize_states = Val(false) + dims = ens_prob.dimensions + _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) + expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) + states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) + + expvals = + _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : + dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) + + return TimeEvolutionStochasticSol( + ntraj, + ens_prob.times, + states, + expvals, + expvals_all, + sol.converged, + _sol_1.alg, + _sol_1.prob.kwargs[:abstol], + _sol_1.prob.kwargs[:reltol], + ) end diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 20b7f9257..95f9b1089 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -296,9 +296,9 @@ function _ensemble_dispatch_output_func( end end -function _ensemble_dispatch_prob_func(rng, ntraj, tlist, prob_func) +function _ensemble_dispatch_prob_func(rng, ntraj, tlist, prob_func; kwargs...) seeds = map(i -> rand(rng, UInt64), 1:ntraj) - return (prob, i, repeat) -> prob_func(prob, i, repeat, rng, seeds, tlist) + return (prob, i, repeat) -> prob_func(prob, i, repeat, rng, seeds, tlist; kwargs...) end function _ensemble_dispatch_solve( @@ -336,17 +336,15 @@ end #= Stochastic funcs =# -function _stochastic_prob_func(prob, i, repeat, rng, seeds, tlist) - params = prob.prob.p - +function _stochastic_prob_func(prob, i, repeat, rng, seeds, tlist; kwargs...) seed = seeds[i] traj_rng = typeof(rng)() seed!(traj_rng, seed) noise = RealWienerProcess!( prob.prob.tspan[1], - zeros(params.n_sc_ops), - zeros(params.n_sc_ops), + zeros(kwargs[:n_sc_ops]), + zeros(kwargs[:n_sc_ops]), save_everystep = false, rng = traj_rng, ) @@ -357,6 +355,45 @@ end # Standard output function _stochastic_output_func(sol, i) = (sol, false) +#= + struct DiffusionOperator + +A struct to represent the diffusion operator. This is used to perform the diffusion process on N different Wiener processes. +=# +struct DiffusionOperator{T,OpType<:Tuple{Vararg{AbstractSciMLOperator}}} <: AbstractSciMLOperator{T} + ops::OpType + function DiffusionOperator(ops::OpType) where {OpType} + T = mapreduce(eltype, promote_type, ops) + return new{T,OpType}(ops) + end +end + +@generated function update_coefficients!(L::DiffusionOperator, u, p, t) + ops_types = L.parameters[2].parameters + N = length(ops_types) + quote + Base.@nexprs $N i -> begin + update_coefficients!(L.ops[i], u, p, t) + end + + nothing + end +end + +@generated function LinearAlgebra.mul!(v::AbstractVecOrMat, L::DiffusionOperator, u::AbstractVecOrMat) + ops_types = L.parameters[2].parameters + N = length(ops_types) + quote + M = length(u) + S = size(v) + (S[1] == M && S[2] == $N) || throw(DimensionMismatch("The size of the output vector is incorrect.")) + Base.@nexprs $N i -> begin + mul!(@view(v[:, i]), L.ops[i], u) + end + return v + end +end + ####################################### function liouvillian_floquet( From ed5b0da12f273282e11d939196e3aba386aeb51d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:12:31 +0900 Subject: [PATCH 215/329] Align some attributes of `mcsolve`, `ssesolve` and `smesolve` results with QuTiP (#402) --- CHANGELOG.md | 2 + .../mcsolve_callback_helpers.jl | 56 +++++++++---------- src/time_evolution/mcsolve.jl | 19 ++++--- src/time_evolution/smesolve.jl | 5 +- src/time_evolution/ssesolve.jl | 5 +- src/time_evolution/time_evolution.jl | 26 +++++---- test/core-test/time_evolution.jl | 16 +++--- 7 files changed, 69 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d31c9ea89..9c2d31a8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Release date: 2025-01-29 - 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]) +- Change the definition of jump_times and jump_which into col_times and col_which, respectively. ([#402]) ## [v0.25.0] Release date: 2025-01-20 @@ -129,4 +130,5 @@ Release date: 2024-11-13 [#395]: https://github.com/qutip/QuantumToolbox.jl/issues/395 [#396]: https://github.com/qutip/QuantumToolbox.jl/issues/396 [#398]: https://github.com/qutip/QuantumToolbox.jl/issues/398 +[#402]: https://github.com/qutip/QuantumToolbox.jl/issues/402 [#403]: https://github.com/qutip/QuantumToolbox.jl/issues/403 diff --git a/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl index c27b8cbcb..cc3e3598d 100644 --- a/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl @@ -28,9 +28,9 @@ struct LindbladJump{ cache_mc::CT weights_mc::WT cumsum_weights_mc::WT - jump_times::JTT - jump_which::JWT - jump_times_which_idx::JTWIT + col_times::JTT + col_which::JWT + col_times_which_idx::JTWIT end (f::LindbladJump)(integrator) = _lindblad_jump_affect!( @@ -42,9 +42,9 @@ end f.cache_mc, f.weights_mc, f.cumsum_weights_mc, - f.jump_times, - f.jump_which, - f.jump_times_which_idx, + f.col_times, + f.col_which, + f.col_times_which_idx, ) ## @@ -71,9 +71,9 @@ function _generate_mcsolve_kwargs(ψ0, T, e_ops, tlist, c_ops, jump_callback, rn weights_mc = Vector{Float64}(undef, length(c_ops)) cumsum_weights_mc = similar(weights_mc) - jump_times = Vector{Float64}(undef, JUMP_TIMES_WHICH_INIT_SIZE) - jump_which = Vector{Int}(undef, JUMP_TIMES_WHICH_INIT_SIZE) - jump_times_which_idx = Ref(1) + col_times = Vector{Float64}(undef, COL_TIMES_WHICH_INIT_SIZE) + col_which = Vector{Int}(undef, COL_TIMES_WHICH_INIT_SIZE) + col_times_which_idx = Ref(1) random_n = Ref(rand(rng)) @@ -85,9 +85,9 @@ function _generate_mcsolve_kwargs(ψ0, T, e_ops, tlist, c_ops, jump_callback, rn cache_mc, weights_mc, cumsum_weights_mc, - jump_times, - jump_which, - jump_times_which_idx, + col_times, + col_which, + col_times_which_idx, ) if jump_callback isa DiscreteLindbladJumpCallback @@ -129,9 +129,9 @@ function _lindblad_jump_affect!( cache_mc, weights_mc, cumsum_weights_mc, - jump_times, - jump_which, - jump_times_which_idx, + col_times, + col_which, + col_times_which_idx, ) ψ = integrator.u @@ -147,13 +147,13 @@ function _lindblad_jump_affect!( random_n[] = rand(traj_rng) - idx = jump_times_which_idx[] - @inbounds jump_times[idx] = integrator.t - @inbounds jump_which[idx] = collapse_idx - jump_times_which_idx[] += 1 - if jump_times_which_idx[] > length(jump_times) - resize!(jump_times, length(jump_times) + JUMP_TIMES_WHICH_INIT_SIZE) - resize!(jump_which, length(jump_which) + JUMP_TIMES_WHICH_INIT_SIZE) + idx = col_times_which_idx[] + @inbounds col_times[idx] = integrator.t + @inbounds col_which[idx] = collapse_idx + col_times_which_idx[] += 1 + if col_times_which_idx[] > length(col_times) + resize!(col_times, length(col_times) + COL_TIMES_WHICH_INIT_SIZE) + resize!(col_which, length(col_which) + COL_TIMES_WHICH_INIT_SIZE) end u_modified!(integrator, true) return nothing @@ -309,9 +309,9 @@ function _similar_affect!(affect::LindbladJump, traj_rng) cache_mc = similar(affect.cache_mc) weights_mc = similar(affect.weights_mc) cumsum_weights_mc = similar(affect.cumsum_weights_mc) - jump_times = similar(affect.jump_times) - jump_which = similar(affect.jump_which) - jump_times_which_idx = Ref(1) + col_times = similar(affect.col_times) + col_which = similar(affect.col_which) + col_times_which_idx = Ref(1) return LindbladJump( affect.c_ops, @@ -321,9 +321,9 @@ function _similar_affect!(affect::LindbladJump, traj_rng) cache_mc, weights_mc, cumsum_weights_mc, - jump_times, - jump_which, - jump_times_which_idx, + col_times, + col_which, + col_times_which_idx, ) end diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index e61179832..066fa68c0 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -14,9 +14,9 @@ end # Standard output function function _mcsolve_output_func(sol, i) - idx = _mc_get_jump_callback(sol).affect!.jump_times_which_idx[] - resize!(_mc_get_jump_callback(sol).affect!.jump_times, idx - 1) - resize!(_mc_get_jump_callback(sol).affect!.jump_which, idx - 1) + idx = _mc_get_jump_callback(sol).affect!.col_times_which_idx[] + resize!(_mc_get_jump_callback(sol).affect!.col_times, idx - 1) + resize!(_mc_get_jump_callback(sol).affect!.col_which, idx - 1) return (sol, false) end @@ -401,21 +401,22 @@ function mcsolve( _expvals_sol_1 = _mcsolve_get_expvals(_sol_1) _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _mcsolve_get_expvals(sol[:, i]), eachindex(sol)) - expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) + expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) - jump_times = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.jump_times, eachindex(sol)) - jump_which = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.jump_which, eachindex(sol)) + col_times = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.col_times, eachindex(sol)) + col_which = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.col_which, eachindex(sol)) - expvals = _expvals_sol_1 isa Nothing ? nothing : dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) + expvals = _expvals_sol_1 isa Nothing ? nothing : dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) return TimeEvolutionMCSol( ntraj, ens_prob_mc.times, states, expvals, + expvals, # This is average_expect expvals_all, - jump_times, - jump_which, + col_times, + col_which, sol.converged, _sol_1.alg, NamedTuple(_sol_1.prob.kwargs).abstol, diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 5d50851d1..741c0d045 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -361,18 +361,19 @@ function smesolve( dims = ens_prob.dimensions _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) - expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) + expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP states = map(i -> _smesolve_generate_state.(sol[:, i].u, Ref(dims)), eachindex(sol)) expvals = _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : - dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) + dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) return TimeEvolutionStochasticSol( ntraj, ens_prob.times, states, expvals, + expvals, # This is average_expect expvals_all, sol.converged, _sol_1.alg, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index f3173dd04..d137d5352 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -362,18 +362,19 @@ function ssesolve( normalize_states = Val(false) dims = ens_prob.dimensions _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) - expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all) + expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) expvals = _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : - dropdims(sum(expvals_all, dims = 3), dims = 3) ./ length(sol) + dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) return TimeEvolutionStochasticSol( ntraj, ens_prob.times, states, expvals, + expvals, # This is average_expect expvals_all, sol.converged, _sol_1.alg, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 95f9b1089..672414928 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -4,7 +4,7 @@ export liouvillian_floquet, liouvillian_generalized const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true) const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true) -const JUMP_TIMES_WHICH_INIT_SIZE = 200 +const COL_TIMES_WHICH_INIT_SIZE = 200 @doc raw""" struct TimeEvolutionProblem @@ -99,9 +99,10 @@ A structure storing the results and some information from solving quantum trajec - `times::AbstractVector`: The time list of the evolution. - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. - `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `expect_all::Union{AbstractMatrix,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` -- `jump_times::Vector{Vector{Real}}`: The time records of every quantum jump occurred in each trajectory. -- `jump_which::Vector{Vector{Int}}`: The indices of the jump operators in `c_ops` that describe the corresponding quantum jumps occurred in each trajectory. +- `average_expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. +- `runs_expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` +- `col_times::Vector{Vector{Real}}`: The time records of every quantum jump occurred in each trajectory. +- `col_which::Vector{Vector{Int}}`: The indices of which collapse operator was responsible for each quantum jump in `col_times`. - `converged::Bool`: Whether the solution is converged or not. - `alg`: The algorithm which is used during the solving process. - `abstol::Real`: The absolute tolerance which is used during the solving process. @@ -122,9 +123,10 @@ struct TimeEvolutionMCSol{ times::TT states::TS expect::TE - expect_all::TEA - jump_times::TJT - jump_which::TJW + average_expect::TE # Currently just a synonym for `expect` + runs_expect::TEA + col_times::TJT + col_which::TJW converged::Bool alg::AlgT abstol::AT @@ -140,7 +142,7 @@ function Base.show(io::IO, sol::TimeEvolutionMCSol) if sol.expect isa Nothing print(io, "num_expect = 0\n") else - print(io, "num_expect = $(size(sol.expect, 1))\n") + print(io, "num_expect = $(size(sol.average_expect, 1))\n") end print(io, "ODE alg.: $(sol.alg)\n") print(io, "abstol = $(sol.abstol)\n") @@ -159,7 +161,8 @@ A structure storing the results and some information from solving trajectories o - `times::AbstractVector`: The time list of the evolution. - `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. - `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `expect_all::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` +- `average_expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. +- `runs_expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` - `converged::Bool`: Whether the solution is converged or not. - `alg`: The algorithm which is used during the solving process. - `abstol::Real`: The absolute tolerance which is used during the solving process. @@ -178,7 +181,8 @@ struct TimeEvolutionStochasticSol{ times::TT states::TS expect::TE - expect_all::TEA + average_expect::TE # Currently just a synonym for `expect` + runs_expect::TEA converged::Bool alg::AlgT abstol::AT @@ -194,7 +198,7 @@ function Base.show(io::IO, sol::TimeEvolutionStochasticSol) if sol.expect isa Nothing print(io, "num_expect = 0\n") else - print(io, "num_expect = $(size(sol.expect, 1))\n") + print(io, "num_expect = $(size(sol.average_expect, 1))\n") end print(io, "SDE alg.: $(sol.alg)\n") print(io, "abstol = $(sol.abstol)\n") diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index cf480fa6f..163a02bf6 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -660,21 +660,21 @@ ) @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 - @test sol_mc1.expect_all ≈ sol_mc2.expect_all atol = 1e-10 - @test sol_mc1.jump_times ≈ sol_mc2.jump_times atol = 1e-10 - @test sol_mc1.jump_which ≈ sol_mc2.jump_which atol = 1e-10 + @test sol_mc1.runs_expect ≈ sol_mc2.runs_expect atol = 1e-10 + @test sol_mc1.col_times ≈ sol_mc2.col_times atol = 1e-10 + @test sol_mc1.col_which ≈ sol_mc2.col_which atol = 1e-10 - @test sol_mc1.expect_all ≈ sol_mc3.expect_all[:, :, 1:500] atol = 1e-10 + @test sol_mc1.runs_expect ≈ sol_mc3.runs_expect[:, 1:500, :] atol = 1e-10 @test sol_sse1.expect ≈ sol_sse2.expect atol = 1e-10 - @test sol_sse1.expect_all ≈ sol_sse2.expect_all atol = 1e-10 + @test sol_sse1.runs_expect ≈ sol_sse2.runs_expect atol = 1e-10 - @test sol_sse1.expect_all ≈ sol_sse3.expect_all[:, :, 1:50] atol = 1e-10 + @test sol_sse1.runs_expect ≈ sol_sse3.runs_expect[:, 1:50, :] atol = 1e-10 @test sol_sme1.expect ≈ sol_sme2.expect atol = 1e-10 - @test sol_sme1.expect_all ≈ sol_sme2.expect_all atol = 1e-10 + @test sol_sme1.runs_expect ≈ sol_sme2.runs_expect atol = 1e-10 - @test sol_sme1.expect_all ≈ sol_sme3.expect_all[:, :, 1:50] atol = 1e-10 + @test sol_sme1.runs_expect ≈ sol_sme3.runs_expect[:, 1:50, :] atol = 1e-10 end end From 084c8a0c59a9358e406f8ab0407ec158b6aaae5a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:47:06 +0900 Subject: [PATCH 216/329] fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2d31a8c..9273811f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix `smesolve` for specifying initial state as density matrix. ([#395]) - Add more generic solver for `steadystate_floquet` to allow more linear solvers. ([#396]) - Fix time evolution output when using `saveat` keyword argument. ([#398]) +- Align some attributes of `mcsolve`, `ssesolve` and `smesolve` results with `QuTiP`. ([#402]) - Improve ensemble generation of `ssesolve` and change parameters handling on stochastic processes. ([#403]) ## [v0.26.0] @@ -34,7 +35,6 @@ Release date: 2025-01-29 - 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]) -- Change the definition of jump_times and jump_which into col_times and col_which, respectively. ([#402]) ## [v0.25.0] Release date: 2025-01-20 From 81724addb8acf774911452066ebddbc4107d3e42 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:37:16 +0900 Subject: [PATCH 217/329] Set default trajectories to 500 and rename the keyword argument `ensemble_method` to `ensemblealg` (#405) --- CHANGELOG.md | 2 + benchmarks/timeevolution.jl | 4 +- docs/make.jl | 4 +- docs/src/users_guide/cluster.md | 4 +- docs/src/users_guide/time_evolution/intro.md | 3 + .../src/users_guide/time_evolution/mcsolve.md | 133 +++++++++++++++++- src/QuantumToolbox.jl | 1 + src/time_evolution/mcsolve.jl | 34 ++--- src/time_evolution/time_evolution.jl | 8 +- .../time_evolution_dynamical.jl | 18 +-- test/core-test/dynamical-shifted-fock.jl | 2 - test/core-test/time_evolution.jl | 38 ++--- 12 files changed, 182 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9273811f9..8eb59f8d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix time evolution output when using `saveat` keyword argument. ([#398]) - Align some attributes of `mcsolve`, `ssesolve` and `smesolve` results with `QuTiP`. ([#402]) - Improve ensemble generation of `ssesolve` and change parameters handling on stochastic processes. ([#403]) +- Set default trajectories to 500 and rename the keyword argument `ensemble_method` to `ensemblealg`. ([#405]) ## [v0.26.0] Release date: 2025-02-09 @@ -132,3 +133,4 @@ Release date: 2024-11-13 [#398]: https://github.com/qutip/QuantumToolbox.jl/issues/398 [#402]: https://github.com/qutip/QuantumToolbox.jl/issues/402 [#403]: https://github.com/qutip/QuantumToolbox.jl/issues/403 +[#405]: https://github.com/qutip/QuantumToolbox.jl/issues/405 diff --git a/benchmarks/timeevolution.jl b/benchmarks/timeevolution.jl index b76816ed4..6c1d18f25 100644 --- a/benchmarks/timeevolution.jl +++ b/benchmarks/timeevolution.jl @@ -50,7 +50,7 @@ function benchmark_timeevolution!(SUITE) ntraj = 100, e_ops = $e_ops, progress_bar = Val(false), - ensemble_method = EnsembleSerial(), + ensemblealg = EnsembleSerial(), ) SUITE["Time Evolution"]["time-independent"]["mcsolve"]["Multithreaded"] = @benchmarkable mcsolve( $H, @@ -60,7 +60,7 @@ function benchmark_timeevolution!(SUITE) ntraj = 100, e_ops = $e_ops, progress_bar = Val(false), - ensemble_method = EnsembleThreads(), + ensemblealg = EnsembleThreads(), ) return nothing diff --git a/docs/make.jl b/docs/make.jl index 0aec9e286..e624cbee3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -34,7 +34,7 @@ const PAGES = [ "Home" => "index.md", "Getting Started" => [ "Brief Example" => "getting_started/brief_example.md", - "Key differences from QuTiP" => "getting_started/qutip_differences.md", + # "Key differences from QuTiP" => "getting_started/qutip_differences.md", "The Importance of Type-Stability" => "getting_started/type_stability.md", "Example: Create QuantumToolbox.jl Logo" => "getting_started/logo.md", # "Cite QuantumToolbox.jl" => "getting_started/cite.md", @@ -51,7 +51,7 @@ const PAGES = [ "Time Evolution Solutions" => "users_guide/time_evolution/solution.md", "Schrödinger Equation Solver" => "users_guide/time_evolution/sesolve.md", "Lindblad Master Equation Solver" => "users_guide/time_evolution/mesolve.md", - "Monte-Carlo Solver" => "users_guide/time_evolution/mcsolve.md", + "Monte Carlo Solver" => "users_guide/time_evolution/mcsolve.md", "Stochastic Solver" => "users_guide/time_evolution/stochastic.md", "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", ], diff --git a/docs/src/users_guide/cluster.md b/docs/src/users_guide/cluster.md index f290ae1b4..d36259f03 100644 --- a/docs/src/users_guide/cluster.md +++ b/docs/src/users_guide/cluster.md @@ -112,7 +112,7 @@ e_ops = [Sx, Sy, Sz] tlist = range(0, 10, 100) -sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj=5000, ensemble_method=EnsembleSplitThreads()) +sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj=5000, ensemblealg=EnsembleSplitThreads()) ## @@ -131,7 +131,7 @@ println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.n command, we test that the distributed network is correctly initialized. The `remotecall_fetch(Threads.nthreads, 2)` command returns the number of threads of the worker with ID `2`. -We then write the main part of the script, where we define the lattice through the [`Lattice`](@ref) function. We set the parameters and define the Hamiltonian and collapse operators with the [`DissipativeIsing`](@ref) function. We also define the expectation operators `e_ops` and the initial state `ψ0`. Finally, we perform the Monte Carlo quantum trajectories with the [`mcsolve`](@ref) function. The `ensemble_method=EnsembleSplitThreads()` argument is used to parallelize the Monte Carlo quantum trajectories, by splitting the ensemble of trajectories among the workers. For a more detailed explanation of the different ensemble methods, you can check the [official documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/) of the [**DifferentialEquations.jl**](https://github.com/SciML/DifferentialEquations.jl/) package. Finally, the `rmprocs(workers())` command is used to remove the workers after the computation is finished. +We then write the main part of the script, where we define the lattice through the [`Lattice`](@ref) function. We set the parameters and define the Hamiltonian and collapse operators with the [`DissipativeIsing`](@ref) function. We also define the expectation operators `e_ops` and the initial state `ψ0`. Finally, we perform the Monte Carlo quantum trajectories with the [`mcsolve`](@ref) function. The `ensemblealg=EnsembleSplitThreads()` argument is used to parallelize the Monte Carlo quantum trajectories, by splitting the ensemble of trajectories among the workers. For a more detailed explanation of the different ensemble methods, you can check the [official documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/) of the [**DifferentialEquations.jl**](https://github.com/SciML/DifferentialEquations.jl/) package. Finally, the `rmprocs(workers())` command is used to remove the workers after the computation is finished. The output of the script will be printed in the `output.out` file, which contains an output similar to the following: diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index b8ad2ca0f..e1755183d 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -17,6 +17,9 @@ - [Example: Harmonic oscillator in thermal bath](@ref doc-TE:Example:Harmonic-oscillator-in-thermal-bath) - [Example: Two-level atom coupled to dissipative single-mode cavity](@ref doc-TE:Example:Two-level-atom-coupled-to-dissipative-single-mode-cavity) - [Monte Carlo Solver](@ref doc-TE:Monte-Carlo-Solver) + - [Monte Carlo wave-function](@ref doc-TE:Monte-Carlo-wave-function) + - [Example: Two-level atom coupled to dissipative single-mode cavity (MC)](@ref doc-TE:Example:Two-level-atom-coupled-to-dissipative-single-mode-cavity-(MC)) + - [Running trajectories in parallel](@ref doc-TE:Running-trajectories-in-parallel) - [Stochastic Solver](@ref doc-TE:Stochastic-Solver) - [Stochastic Schrödinger equation](@ref doc-TE:Stochastic-Schrödinger-equation) - [Stochastic master equation](@ref doc-TE:Stochastic-master-equation) diff --git a/docs/src/users_guide/time_evolution/mcsolve.md b/docs/src/users_guide/time_evolution/mcsolve.md index d62540830..e5a3e0309 100644 --- a/docs/src/users_guide/time_evolution/mcsolve.md +++ b/docs/src/users_guide/time_evolution/mcsolve.md @@ -1,3 +1,134 @@ # [Monte Carlo Solver](@id doc-TE:Monte-Carlo-Solver) -This page is still under construction, please visit [API](@ref doc-API) first. \ No newline at end of file +## [Monte Carlo wave-function](@id doc-TE:Monte-Carlo-wave-function) + +Where as the density matrix formalism describes the ensemble average over many identical realizations of a quantum system, the Monte Carlo (MC), or quantum-jump approach to wave function evolution, allows for simulating an individual realization of the system dynamics. Here, the environment is continuously monitored, resulting in a series of quantum jumps in the system wave function, conditioned on the increase in information gained about the state of the system via the environmental measurements. In general, this evolution is governed by the Schrödinger equation with a non-Hermitian effective Hamiltonian + +```math +\hat{H}_{\textrm{eff}} = \hat{H} - \frac{i}{2} \sum_{n=1}^N \hat{C}_n^\dagger \hat{C}_n. +``` + +where ``\hat{H}`` is the system Hamiltonian and ``\hat{C}_n`` are collapse (jump) operators (assume ``N`` is the total number of collapse operators). Each collapse operator corresponds to a separate irreversible process with rate ``\gamma_n``. Here, the strictly negative non-Hermitian portion of the above equation gives rise to a reduction in the norm of the wave function, that to first-order in a small time ``\delta t``, is given by + +```math +\langle \psi(t + \delta t) | \psi(t + \delta t) \rangle = 1 - \delta p, +``` + +where + +```math +\delta p = \delta t \sum_{n=1}^N \langle \psi(t) | \hat{C}_n^\dagger \hat{C}_n | \psi(t) \rangle, +``` + +and ``\delta t`` is such that ``\delta p \ll 1``. With a probability of remaining in the state ``| \psi(t + \delta t) \rangle`` given by ``1 - \delta p``, the corresponding quantum jump probability is thus ``\delta p``. If the environmental measurements register a quantum jump, say via the emission of a photon into the environment, or a change in the spin of a quantum dot, the wave function undergoes a jump into a state defined by projecting ``| \psi(t) \rangle`` using the collapse operator ``\hat{C}_n`` corresponding to the measurement + +```math +| \psi(t+\delta t) \rangle = \frac{\hat{C}_n |\psi(t)\rangle}{ \sqrt{\langle \psi(t) | \hat{C}_n^\dagger \hat{C}_n | \psi(t) \rangle} }. +``` + +If more than a single collapse operator is present in ``\hat{H}_{\textrm{eff}}``, the probability of collapse due to the ``n``-th operator ``\hat{C}_n`` is given by + +```math +P_n(t) = \frac{1}{\delta p}\langle \psi(t) | \hat{C}_n^\dagger \hat{C}_n | \psi(t) \rangle. +``` + +Note that the probability of all collapses should be normalized to unity for all time ``t``, namely + +```math +\sum_{n=1}^N P_n(t) = 1 ~~~\forall~~t. +``` + +Evaluating the MC evolution to first-order in time is quite tedious. Instead, `QuantumToolbox.jl` provides the function [`mcsolve`](@ref) which uses the following algorithm to simulate a single realization of a quantum system. Starting from a pure state ``| \psi(0) \rangle``: + +1. Choose two random numbers (``r_1`` and ``r_2``) between 0 and 1, where ``r_1`` represents the probability that a quantum jump occurs and ``r_2`` is used to select which collapse operator was responsible for the jump. +1. Integrate the Schrödinger equation with respect to the effective Hamiltonian ``\hat{H}_{\textrm{eff}}`` until a time ``\tau`` such that the norm of the wave function satisfies ``\langle \psi(\tau) | \psi(\tau) \rangle = r_1``, at which point a jump occurs +1. The resultant jump projects the system at time ``\tau`` into one of the renormalized states ``| \psi(\tau + \delta t) \rangle``. The corresponding collapse operator ``\hat{C}_n`` is chosen such that ``\tilde{n} \leq N`` is the smallest integer satisfying ``\sum_{n=1}^{\tilde{n}} P_n(\tau) \geq r_2``. +1. Using the renormalized state from previous step as the new initial condition at time ``\tau`` and repeat the above procedure until the final simulation time is reached. + +## [Example: Two-level atom coupled to dissipative single-mode cavity (MC)](@id doc-TE:Example:Two-level-atom-coupled-to-dissipative-single-mode-cavity-(MC)) + +In `QuantumToolbox.jl`, Monte Carlo evolution is implemented with the [`mcsolve`](@ref) function. It takes nearly the same arguments as the [`mesolve`](@ref) function for [Lindblad master equation evolution](@ref doc-TE:Lindblad-Master-Equation-Solver), except that the initial state must be a [`Ket`](@ref) vector, as oppose to a density matrix, and there is an optional keyword argument `ntraj` that defines the number of stochastic trajectories to be simulated. By default, `ntraj=500` indicating that `500` Monte Carlo trajectories will be performed. + +To illustrate the use of the Monte Carlo evolution of quantum systems in `QuantumToolbox.jl`, let’s again consider the case of a two-level atom coupled to a leaky cavity. The only differences to the master equation treatment is that in this case we invoke the [`mcsolve`](@ref) function instead of [`mesolve`](@ref) + +```@setup mcsolve +using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) +``` + +```@example mcsolve +times = LinRange(0.0, 10.0, 200) + +ψ0 = tensor(fock(2, 0), fock(10, 8)) +a = tensor(qeye(2), destroy(10)) +σm = tensor(destroy(2), qeye(10)) +H = 2 * π * a' * a + 2 * π * σm' * σm + 2 * π * 0.25 * (σm * a' + σm' * a) + +c_ops = [sqrt(0.1) * a] +e_ops = [a' * a, σm' * σm] + +sol_500 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], + xlabel = "Time", + ylabel = "Expectation values", + title = "Monte Carlo time evolution (500 trajectories)", +) +lines!(ax, times, real(sol_500.expect[1,:]), label = "cavity photon number", linestyle = :solid) +lines!(ax, times, real(sol_500.expect[2,:]), label = "atom excitation probability", linestyle = :dash) + +axislegend(ax, position = :rt) + +fig +``` + +The advantage of the Monte Carlo method over the master equation approach is that only the state vector ([`Ket`](@ref)) is required to be kept in the computers memory, as opposed to the entire density matrix. For large quantum system this becomes a significant advantage, and the Monte Carlo solver is therefore generally recommended for such systems. However, for small systems, the added overhead of averaging a large number of stochastic trajectories to obtain the open system dynamics, as well as starting the multiprocessing functionality, outweighs the benefit of the minor (in this case) memory saving. Master equation methods are therefore generally more efficient when Hilbert space sizes are on the order of a couple of hundred states or smaller. + +We can also change the number of trajectories (`ntraj`). This can be used to explore the convergence of the Monte Carlo solver. For example, the following code plots the expectation values for `1`, `10` and `100` trajectories: + +```@example mcsolve +e_ops = [a' * a] + +sol_1 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ntraj = 1) +sol_10 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ntraj = 10) +sol_100 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ntraj = 100) + +# plot by CairoMakie.jl +fig = Figure(size = (500, 350)) +ax = Axis(fig[1, 1], + xlabel = "Time", + ylabel = "Expectation values", + title = "Monte Carlo time evolution", +) +lines!(ax, times, real(sol_1.expect[1,:]), label = "1 trajectory", linestyle = :dashdot) +lines!(ax, times, real(sol_10.expect[1,:]), label = "10 trajectories", linestyle = :dash) +lines!(ax, times, real(sol_100.expect[1,:]), label = "100 trajectories", linestyle = :solid) + +axislegend(ax, position = :rt) + +fig +``` + +## [Running trajectories in parallel](@id doc-TE:Running-trajectories-in-parallel) + +Monte Carlo evolutions often need hundreds of trajectories to obtain sufficient statistics. Since all trajectories are independent of each other, they can be computed in parallel. The keyword argument `ensemblealg` can specify how the multiple trajectories are handled. The common ensemble methods are: + +- `EnsembleSerial()`: No parallelism +- `EnsembleThreads()`: **The default.** This uses multithreading. +- `EnsembleDistributed()`: This uses as many processors as you have Julia processes. +- `EnsembleSplitThreads()`: This uses multithreading on each process. + +!!! note "Other Ensemble Algorithms" + See the [documentation of `DifferentialEquations.jl`](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/) for more details. Also, see Julia's documentation for more details about multithreading and adding more processes. + +```julia +sol_serial = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ensemblealg=EnsembleSerial()) +sol_parallel = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ensemblealg=EnsembleThreads()); +``` + +!!! tip "Parallelization on a Cluster" + See the section [Intensive parallelization on a Cluster](@ref doc:Intensive-parallelization-on-a-Cluster) for more details. diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 4776c0d43..7f512d685 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -28,6 +28,7 @@ import SciMLBase: ODEProblem, SDEProblem, EnsembleProblem, + EnsembleAlgorithm, EnsembleSerial, EnsembleThreads, EnsembleSplitThreads, diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 066fa68c0..172e8e099 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -148,8 +148,8 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), progress_bar::Union{Val,Bool} = Val(true), prob_func::Union{Function, Nothing} = nothing, @@ -201,7 +201,7 @@ If the environmental measurements register a quantum jump, the wave function und - `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `ensemblealg`: Ensemble algorithm to use. Default to `EnsembleThreads()`. - `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `prob_func`: Function to use for generating the ODEProblem. @@ -227,8 +227,8 @@ function mcsolveEnsembleProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), progress_bar::Union{Val,Bool} = Val(true), prob_func::Union{Function,Nothing} = nothing, @@ -238,7 +238,7 @@ function mcsolveEnsembleProblem( _prob_func = isnothing(prob_func) ? _ensemble_dispatch_prob_func(rng, ntraj, tlist, _mcsolve_prob_func) : prob_func _output_func = output_func isa Nothing ? - _ensemble_dispatch_output_func(ensemble_method, progress_bar, ntraj, _mcsolve_output_func) : output_func + _ensemble_dispatch_output_func(ensemblealg, progress_bar, ntraj, _mcsolve_output_func) : output_func prob_mc = mcsolveProblem( H, @@ -272,8 +272,8 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), progress_bar::Union{Val,Bool} = Val(true), prob_func::Union{Function, Nothing} = nothing, @@ -327,7 +327,7 @@ If the environmental measurements register a quantum jump, the wave function und - `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `rng`: Random number generator for reproducibility. - `ntraj`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `ensemblealg`: Ensemble algorithm to use. Default to `EnsembleThreads()`. - `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `prob_func`: Function to use for generating the ODEProblem. @@ -337,7 +337,7 @@ If the environmental measurements register a quantum jump, the wave function und # Notes -- `ensemble_method` can be one of `EnsembleThreads()`, `EnsembleSerial()`, `EnsembleDistributed()` +- `ensemblealg` can be one of `EnsembleThreads()`, `EnsembleSerial()`, `EnsembleDistributed()` - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. @@ -357,8 +357,8 @@ function mcsolve( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), progress_bar::Union{Val,Bool} = Val(true), prob_func::Union{Function,Nothing} = nothing, @@ -376,7 +376,7 @@ function mcsolve( params = params, rng = rng, ntraj = ntraj, - ensemble_method = ensemble_method, + ensemblealg = ensemblealg, jump_callback = jump_callback, progress_bar = progress_bar, prob_func = prob_func, @@ -384,17 +384,17 @@ function mcsolve( kwargs..., ) - return mcsolve(ens_prob_mc, alg, ntraj, ensemble_method, normalize_states) + return mcsolve(ens_prob_mc, alg, ntraj, ensemblealg, normalize_states) end function mcsolve( ens_prob_mc::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), normalize_states = Val(true), ) - sol = _ensemble_dispatch_solve(ens_prob_mc, alg, ensemble_method, ntraj) + sol = _ensemble_dispatch_solve(ens_prob_mc, alg, ensemblealg, ntraj) dims = ens_prob_mc.dimensions _sol_1 = sol[:, 1] diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 672414928..c32805524 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -308,7 +308,7 @@ end function _ensemble_dispatch_solve( ens_prob_mc::TimeEvolutionProblem, alg::Union{<:OrdinaryDiffEqAlgorithm,<:StochasticDiffEqAlgorithm}, - ensemble_method::ET, + ensemblealg::ET, ntraj::Int, ) where {ET<:Union{EnsembleSplitThreads,EnsembleDistributed}} sol = nothing @@ -319,7 +319,7 @@ function _ensemble_dispatch_solve( end @async begin - sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) + sol = solve(ens_prob_mc.prob, alg, ensemblealg, trajectories = ntraj) put!(ens_prob_mc.kwargs.channel, false) end end @@ -329,10 +329,10 @@ end function _ensemble_dispatch_solve( ens_prob_mc::TimeEvolutionProblem, alg::Union{<:OrdinaryDiffEqAlgorithm,<:StochasticDiffEqAlgorithm}, - ensemble_method, + ensemblealg, ntraj::Int, ) - sol = solve(ens_prob_mc.prob, alg, ensemble_method, trajectories = ntraj) + sol = solve(ens_prob_mc.prob, alg, ensemblealg, trajectories = ntraj) return sol end diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index b4ec427a5..77969751e 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -607,8 +607,8 @@ function dsf_mcsolveEnsembleProblem( dsf_params::NamedTuple = NamedTuple(); e_ops::Function = (op_list, p) -> (), params::NamedTuple = NamedTuple(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), jump_callback::TJC = ContinuousLindbladJumpCallback(), krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), @@ -660,7 +660,7 @@ function dsf_mcsolveEnsembleProblem( e_ops = e_ops₀, params = params2, ntraj = ntraj, - ensemble_method = ensemble_method, + ensemblealg = ensemblealg, jump_callback = jump_callback, prob_func = _dsf_mcsolve_prob_func, progress_bar = progress_bar, @@ -679,8 +679,8 @@ end e_ops::Function=(op_list,p) -> Vector{TOl}([]), params::NamedTuple=NamedTuple(), δα_list::Vector{<:Real}=fill(0.2, length(op_list)), - ntraj::Int=1, - ensemble_method=EnsembleThreads(), + ntraj::Int=500, + ensemblealg::EnsembleAlgorithm=EnsembleThreads(), jump_callback::LindbladJumpCallbackType=ContinuousLindbladJumpCallback(), krylov_dim::Int=max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), progress_bar::Union{Bool,Val} = Val(true) @@ -704,8 +704,8 @@ function dsf_mcsolve( e_ops::Function = (op_list, p) -> (), params::NamedTuple = NamedTuple(), δα_list::Vector{<:Real} = fill(0.2, length(op_list)), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), jump_callback::TJC = ContinuousLindbladJumpCallback(), krylov_dim::Int = min(5, cld(length(ψ0.data), 3)), progress_bar::Union{Bool,Val} = Val(true), @@ -723,7 +723,7 @@ function dsf_mcsolve( e_ops = e_ops, params = params, ntraj = ntraj, - ensemble_method = ensemble_method, + ensemblealg = ensemblealg, δα_list = δα_list, jump_callback = jump_callback, krylov_dim = krylov_dim, @@ -731,5 +731,5 @@ function dsf_mcsolve( kwargs..., ) - return mcsolve(ens_prob_mc, alg, ntraj, ensemble_method) + return mcsolve(ens_prob_mc, alg, ntraj, ensemblealg) end diff --git a/test/core-test/dynamical-shifted-fock.jl b/test/core-test/dynamical-shifted-fock.jl index bb1bebc16..663ac6494 100644 --- a/test/core-test/dynamical-shifted-fock.jl +++ b/test/core-test/dynamical-shifted-fock.jl @@ -60,7 +60,6 @@ dsf_params, e_ops = e_ops_dsf, progress_bar = Val(false), - ntraj = 500, ) val_ss = abs2(sol0.expect[1, end]) @test sum(abs2.(sol0.expect[1, :] .- sol_dsf_me.expect[1, :])) / (val_ss * length(tlist)) < 0.1 @@ -140,7 +139,6 @@ dsf_params, e_ops = e_ops_dsf2, progress_bar = Val(false), - ntraj = 500, ) val_ss = abs2(sol0.expect[1, end]) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 163a02bf6..2b9d0afe7 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -110,24 +110,22 @@ sol_me2 = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) sol_me3 = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) prob_mc = mcsolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_mc = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) sol_mc2 = mcsolve( H, ψ0, tlist, c_ops, - ntraj = 500, e_ops = e_ops, progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), ) - sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, saveat = saveat, progress_bar = Val(false)) + sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, saveat = saveat, progress_bar = Val(false)) sol_mc_states2 = mcsolve( H, ψ0, tlist, c_ops, - ntraj = 500, saveat = saveat, progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), @@ -256,7 +254,7 @@ sol_se = sesolve(H_dr_fr, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) sol_me = mesolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_mc = mcsolve(H_dr_fr, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc = mcsolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) # sol_sse = ssesolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) # Time Evolution in the lab frame @@ -269,17 +267,7 @@ sol_se_td = sesolve(H_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) sol_me_td = mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) - sol_mc_td = mcsolve( - H_td, - ψ0, - tlist, - c_ops, - ntraj = 500, - e_ops = e_ops, - progress_bar = Val(false), - params = p, - rng = rng, - ) + sol_mc_td = mcsolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) # sol_sse_td = ssesolve(H_td, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) @test sol_se.expect ≈ sol_se_td.expect atol = 1e-6 * length(tlist) @@ -292,17 +280,7 @@ sol_se_td2 = sesolve(H_td2, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) sol_me_td2 = mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) - sol_mc_td2 = mcsolve( - H_td2, - ψ0, - tlist, - c_ops, - ntraj = 500, - e_ops = e_ops, - progress_bar = Val(false), - params = p, - rng = rng, - ) + sol_mc_td2 = mcsolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) # sol_sse_td2 = # ssesolve(H_td2, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) @@ -609,7 +587,7 @@ @testset "mcsolve, ssesolve and smesolve reproducibility" begin rng = MersenneTwister(1234) - sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) sol_sse1 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) @@ -626,7 +604,7 @@ ) rng = MersenneTwister(1234) - sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) sol_sse2 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) rng = MersenneTwister(1234) @@ -714,7 +692,7 @@ psi0 = kron(psi0_1, psi0_2) t_l = LinRange(0, 20 / γ1, 1000) sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = false) # Here we don't put Val(false) because we want to test the support for Bool type - sol_mc = mcsolve(H, psi0, t_l, c_ops, ntraj = 500, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = Val(false)) + sol_mc = mcsolve(H, psi0, t_l, c_ops, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = Val(false)) @test sum(abs.(sol_mc.expect[1:2, :] .- sol_me.expect[1:2, :])) / length(t_l) < 0.1 @test expect(sp1 * sm1, sol_me.states[end]) ≈ expect(sigmap() * sigmam(), ptrace(sol_me.states[end], 1)) end From 49e9ffc109c4f3ec86dcaf58efaceeb3f75cdee9 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:47:12 +0900 Subject: [PATCH 218/329] Introduce measurement on `ssesolve` and `smesolve` (#404) --- CHANGELOG.md | 2 + .../users_guide/time_evolution/stochastic.md | 12 +- src/QuantumToolbox.jl | 3 +- .../callback_helpers/callback_helpers.jl | 124 +++++++++++++----- .../mcsolve_callback_helpers.jl | 62 +-------- .../mesolve_callback_helpers.jl | 8 +- .../sesolve_callback_helpers.jl | 4 +- .../smesolve_callback_helpers.jl | 55 ++++++++ .../ssesolve_callback_helpers.jl | 54 +++++++- src/time_evolution/mcsolve.jl | 5 +- src/time_evolution/mesolve.jl | 2 +- src/time_evolution/sesolve.jl | 2 +- src/time_evolution/smesolve.jl | 88 +++++++++---- src/time_evolution/ssesolve.jl | 91 +++++++++---- src/time_evolution/time_evolution.jl | 4 +- .../time_evolution_dynamical.jl | 4 +- test/core-test/time_evolution.jl | 40 +++++- 17 files changed, 395 insertions(+), 165 deletions(-) create mode 100644 src/time_evolution/callback_helpers/smesolve_callback_helpers.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eb59f8d1..253fba8fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Align some attributes of `mcsolve`, `ssesolve` and `smesolve` results with `QuTiP`. ([#402]) - Improve ensemble generation of `ssesolve` and change parameters handling on stochastic processes. ([#403]) - Set default trajectories to 500 and rename the keyword argument `ensemble_method` to `ensemblealg`. ([#405]) +- Introduce measurement on `ssesolve` and `smesolve`. ([#404]) ## [v0.26.0] Release date: 2025-02-09 @@ -133,4 +134,5 @@ Release date: 2024-11-13 [#398]: https://github.com/qutip/QuantumToolbox.jl/issues/398 [#402]: https://github.com/qutip/QuantumToolbox.jl/issues/402 [#403]: https://github.com/qutip/QuantumToolbox.jl/issues/403 +[#404]: https://github.com/qutip/QuantumToolbox.jl/issues/404 [#405]: https://github.com/qutip/QuantumToolbox.jl/issues/405 diff --git a/docs/src/users_guide/time_evolution/stochastic.md b/docs/src/users_guide/time_evolution/stochastic.md index 3687001ce..852ae089d 100644 --- a/docs/src/users_guide/time_evolution/stochastic.md +++ b/docs/src/users_guide/time_evolution/stochastic.md @@ -105,12 +105,16 @@ sse_sol = ssesolve( sc_ops, e_ops = [x], ntraj = ntraj, + store_measurement = Val(true), ) +measurement_avg = sum(sse_sol.measurement, dims=2) / size(sse_sol.measurement, 2) +measurement_avg = dropdims(measurement_avg, dims=2) + # plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time") -#lines!(ax, tlist, real(sse_sol.xxxxxx), label = L"J_x", color = :red, linestyle = :solid) TODO: add this in the future +lines!(ax, tlist[1:end-1], real(measurement_avg[1,:]), label = L"J_x", color = :red, linestyle = :solid) lines!(ax, tlist, real(sse_sol.expect[1,:]), label = L"\langle x \rangle", color = :black, linestyle = :solid) axislegend(ax, position = :rt) @@ -134,12 +138,16 @@ sme_sol = smesolve( sc_ops, e_ops = [x], ntraj = ntraj, + store_measurement = Val(true), ) +measurement_avg = sum(sme_sol.measurement, dims=2) / size(sme_sol.measurement, 2) +measurement_avg = dropdims(measurement_avg, dims=2) + # plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time") -#lines!(ax, tlist, real(sme_sol.xxxxxx), label = L"J_x", color = :red, linestyle = :solid) TODO: add this in the future +lines!(ax, tlist[1:end-1], real(measurement_avg[1,:]), label = L"J_x", color = :red, linestyle = :solid) lines!(ax, tlist, real(sme_sol.expect[1,:]), label = L"\langle x \rangle", color = :black, linestyle = :solid) axislegend(ax, position = :rt) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 7f512d685..f1bb81b95 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -97,11 +97,12 @@ include("qobj/block_diagonal_form.jl") # time evolution include("time_evolution/time_evolution.jl") +include("time_evolution/callback_helpers/callback_helpers.jl") include("time_evolution/callback_helpers/sesolve_callback_helpers.jl") include("time_evolution/callback_helpers/mesolve_callback_helpers.jl") include("time_evolution/callback_helpers/mcsolve_callback_helpers.jl") include("time_evolution/callback_helpers/ssesolve_callback_helpers.jl") -include("time_evolution/callback_helpers/callback_helpers.jl") +include("time_evolution/callback_helpers/smesolve_callback_helpers.jl") include("time_evolution/mesolve.jl") include("time_evolution/lr_mesolve.jl") include("time_evolution/sesolve.jl") diff --git a/src/time_evolution/callback_helpers/callback_helpers.jl b/src/time_evolution/callback_helpers/callback_helpers.jl index 6f4103069..4170635a8 100644 --- a/src/time_evolution/callback_helpers/callback_helpers.jl +++ b/src/time_evolution/callback_helpers/callback_helpers.jl @@ -4,6 +4,8 @@ This file contains helper functions for callbacks. The affect! function are defi ## +abstract type AbstractSaveFunc end + # Multiple dispatch depending on the progress_bar and e_ops types function _generate_se_me_kwargs(e_ops, progress_bar, tlist, kwargs, method) cb = _generate_save_callback(e_ops, tlist, progress_bar, method) @@ -11,6 +13,34 @@ function _generate_se_me_kwargs(e_ops, progress_bar, tlist, kwargs, method) end _generate_se_me_kwargs(e_ops::Nothing, progress_bar::Val{false}, tlist, kwargs, method) = kwargs +function _generate_stochastic_kwargs( + e_ops, + sc_ops, + progress_bar, + tlist, + store_measurement, + kwargs, + method::Type{SF}, +) where {SF<:AbstractSaveFunc} + cb_save = _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurement, progress_bar, method) + + if SF === SaveFuncSSESolve + cb_normalize = _ssesolve_generate_normalize_cb() + return _merge_kwargs_with_callback(kwargs, CallbackSet(cb_normalize, cb_save)) + end + + return _merge_kwargs_with_callback(kwargs, cb_save) +end +_generate_stochastic_kwargs( + e_ops::Nothing, + sc_ops, + progress_bar::Val{false}, + tlist, + store_measurement::Val{false}, + kwargs, + method::Type{SF}, +) where {SF<:AbstractSaveFunc} = kwargs + function _merge_kwargs_with_callback(kwargs, cb) kwargs2 = haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(cb, kwargs.callback),)) : @@ -30,38 +60,29 @@ function _generate_save_callback(e_ops, tlist, progress_bar, method) return PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)) end -_get_e_ops_data(e_ops, ::Type{SaveFuncSESolve}) = get_data.(e_ops) -_get_e_ops_data(e_ops, ::Type{SaveFuncMESolve}) = [_generate_mesolve_e_op(op) for op in e_ops] # Broadcasting generates type instabilities on Julia v1.10 -_get_e_ops_data(e_ops, ::Type{SaveFuncSSESolve}) = get_data.(e_ops) - -_generate_mesolve_e_op(op) = mat2vec(adjoint(get_data(op))) - -#= -This function add the normalization callback to the kwargs. It is needed to stabilize the integration when using the ssesolve method. -=# -function _ssesolve_add_normalize_cb(kwargs) - _condition = (u, t, integrator) -> true - _affect! = (integrator) -> normalize!(integrator.u) - cb = DiscreteCallback(_condition, _affect!; save_positions = (false, false)) - # return merge(kwargs, (callback = CallbackSet(kwargs[:callback], cb),)) +function _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurement, progress_bar, method) + e_ops_data = e_ops isa Nothing ? nothing : _get_e_ops_data(e_ops, method) + m_ops_data = _get_m_ops_data(sc_ops, method) - cb_set = haskey(kwargs, :callback) ? CallbackSet(kwargs[:callback], cb) : cb + progr = getVal(progress_bar) ? ProgressBar(length(tlist), enable = getVal(progress_bar)) : nothing - kwargs2 = merge(kwargs, (callback = cb_set,)) + expvals = e_ops isa Nothing ? nothing : Array{ComplexF64}(undef, length(e_ops), length(tlist)) + m_expvals = getVal(store_measurement) ? Array{Float64}(undef, length(sc_ops), length(tlist) - 1) : nothing - return kwargs2 + _save_affect! = method(store_measurement, e_ops_data, m_ops_data, progr, Ref(1), expvals, m_expvals) + return PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)) end ## -# When e_ops is Nothing. Common for both mesolve and sesolve +# When e_ops is Nothing. Common for all solvers function _save_func(integrator, progr) next!(progr) u_modified!(integrator, false) return nothing end -# When progr is Nothing. Common for both mesolve and sesolve +# When progr is Nothing. Common for all solvers function _save_func(integrator, progr::Nothing) u_modified!(integrator, false) return nothing @@ -69,9 +90,34 @@ end ## +#= + To extract the measurement outcomes of a stochastic solver +=# +function _get_m_expvals(integrator::AbstractODESolution, method::Type{SF}) where {SF<:AbstractSaveFunc} + cb = _get_save_callback(integrator, method) + if cb isa Nothing + return nothing + else + return cb.affect!.m_expvals + end +end + +#= + With this function we extract the e_ops from the SaveFuncMCSolve `affect!` function of the callback of the integrator. + This callback can only be a PresetTimeCallback (DiscreteCallback). +=# +function _get_e_ops(integrator::AbstractODEIntegrator, method::Type{SF}) where {SF<:AbstractSaveFunc} + cb = _get_save_callback(integrator, method) + if cb isa Nothing + return nothing + else + return cb.affect!.e_ops + end +end + # Get the e_ops from a given AbstractODESolution. Valid for `sesolve`, `mesolve` and `ssesolve`. -function _se_me_sse_get_expvals(sol::AbstractODESolution) - cb = _se_me_sse_get_save_callback(sol) +function _get_expvals(sol::AbstractODESolution, method::Type{SF}) where {SF<:AbstractSaveFunc} + cb = _get_save_callback(sol, method) if cb isa Nothing return nothing else @@ -79,28 +125,46 @@ function _se_me_sse_get_expvals(sol::AbstractODESolution) end end -function _se_me_sse_get_save_callback(sol::AbstractODESolution) +#= + _get_save_callback + +Return the Callback that is responsible for saving the expectation values of the system. +=# +function _get_save_callback(sol::AbstractODESolution, method::Type{SF}) where {SF<:AbstractSaveFunc} kwargs = NamedTuple(sol.prob.kwargs) # Convert to NamedTuple to support Zygote.jl if hasproperty(kwargs, :callback) - return _se_me_sse_get_save_callback(kwargs.callback) + return _get_save_callback(kwargs.callback, method) else return nothing end end -_se_me_sse_get_save_callback(integrator::AbstractODEIntegrator) = _se_me_sse_get_save_callback(integrator.opts.callback) -function _se_me_sse_get_save_callback(cb::CallbackSet) +_get_save_callback(integrator::AbstractODEIntegrator, method::Type{SF}) where {SF<:AbstractSaveFunc} = + _get_save_callback(integrator.opts.callback, method) +function _get_save_callback(cb::CallbackSet, method::Type{SF}) where {SF<:AbstractSaveFunc} cbs_discrete = cb.discrete_callbacks if length(cbs_discrete) > 0 - _cb = cb.discrete_callbacks[1] - return _se_me_sse_get_save_callback(_cb) + idx = _get_save_callback_idx(cb, method) + _cb = cb.discrete_callbacks[idx] + return _get_save_callback(_cb, method) else return nothing end end -function _se_me_sse_get_save_callback(cb::DiscreteCallback) - if typeof(cb.affect!) <: Union{SaveFuncSESolve,SaveFuncMESolve,SaveFuncSSESolve} +function _get_save_callback(cb::DiscreteCallback, ::Type{SF}) where {SF<:AbstractSaveFunc} + if typeof(cb.affect!) <: AbstractSaveFunc return cb end return nothing end -_se_me_sse_get_save_callback(cb::ContinuousCallback) = nothing +_get_save_callback(cb::ContinuousCallback, ::Type{SF}) where {SF<:AbstractSaveFunc} = nothing + +_get_save_callback_idx(cb, method) = 1 + +# %% ------------ Noise Measurement Helpers ------------ %% + +# TODO: Add some cache mechanism to avoid memory allocations +function _homodyne_dWdt(integrator) + @inbounds _dWdt = (integrator.W.u[end] .- integrator.W.u[end-1]) ./ (integrator.W.t[end] - integrator.W.t[end-1]) + + return _dWdt +end diff --git a/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl index cc3e3598d..b0095e705 100644 --- a/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl @@ -2,7 +2,7 @@ Helper functions for the mcsolve callbacks. =# -struct SaveFuncMCSolve{TE,IT,TEXPV} +struct SaveFuncMCSolve{TE,IT,TEXPV} <: AbstractSaveFunc e_ops::TE iter::IT expvals::TEXPV @@ -10,6 +10,9 @@ end (f::SaveFuncMCSolve)(integrator) = _save_func_mcsolve(integrator, f.e_ops, f.iter, f.expvals) +_get_save_callback_idx(cb, ::Type{SaveFuncMCSolve}) = _mcsolve_has_continuous_jump(cb) ? 1 : 2 + +## struct LindbladJump{ T1, T2, @@ -167,37 +170,6 @@ _mcsolve_discrete_condition(u, t, integrator) = ## -#= - _mc_get_save_callback - -Return the Callback that is responsible for saving the expectation values of the system. -=# -function _mc_get_save_callback(sol::AbstractODESolution) - kwargs = NamedTuple(sol.prob.kwargs) # Convert to NamedTuple to support Zygote.jl - return _mc_get_save_callback(kwargs.callback) # There is always the Jump callback -end -_mc_get_save_callback(integrator::AbstractODEIntegrator) = _mc_get_save_callback(integrator.opts.callback) -function _mc_get_save_callback(cb::CallbackSet) - cbs_discrete = cb.discrete_callbacks - - if length(cbs_discrete) > 0 - idx = _mcsolve_has_continuous_jump(cb) ? 1 : 2 - _cb = cb.discrete_callbacks[idx] - return _mc_get_save_callback(_cb) - else - return nothing - end -end -_mc_get_save_callback(cb::DiscreteCallback) = - if cb.affect! isa SaveFuncMCSolve - return cb - else - return nothing - end -_mc_get_save_callback(cb::ContinuousCallback) = nothing - -## - function _mc_get_jump_callback(sol::AbstractODESolution) kwargs = NamedTuple(sol.prob.kwargs) # Convert to NamedTuple to support Zygote.jl return _mc_get_jump_callback(kwargs.callback) # There is always the Jump callback @@ -215,8 +187,8 @@ _mc_get_jump_callback(cb::DiscreteCallback) = cb ## #= -With this function we extract the c_ops and c_ops_herm from the LindbladJump `affect!` function of the callback of the integrator. -This callback can be a DiscreteLindbladJumpCallback or a ContinuousLindbladJumpCallback. + With this function we extract the c_ops and c_ops_herm from the LindbladJump `affect!` function of the callback of the integrator. + This callback can be a DiscreteLindbladJumpCallback or a ContinuousLindbladJumpCallback. =# function _mcsolve_get_c_ops(integrator::AbstractODEIntegrator) cb = _mc_get_jump_callback(integrator) @@ -227,28 +199,6 @@ function _mcsolve_get_c_ops(integrator::AbstractODEIntegrator) end end -#= -With this function we extract the e_ops from the SaveFuncMCSolve `affect!` function of the callback of the integrator. -This callback can only be a PresetTimeCallback (DiscreteCallback). -=# -function _mcsolve_get_e_ops(integrator::AbstractODEIntegrator) - cb = _mc_get_save_callback(integrator) - if cb isa Nothing - return nothing - else - return cb.affect!.e_ops - end -end - -function _mcsolve_get_expvals(sol::AbstractODESolution) - cb = _mc_get_save_callback(sol) - if cb isa Nothing - return nothing - else - return cb.affect!.expvals - end -end - #= _mcsolve_initialize_callbacks(prob, tlist) diff --git a/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl index 449e2645a..ee253d765 100644 --- a/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl @@ -2,7 +2,7 @@ Helper functions for the mesolve callbacks. =# -struct SaveFuncMESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} +struct SaveFuncMESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} <: AbstractSaveFunc e_ops::TE progr::PT iter::IT @@ -12,6 +12,8 @@ end (f::SaveFuncMESolve)(integrator) = _save_func_mesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) (f::SaveFuncMESolve{Nothing})(integrator) = _save_func(integrator, f.progr) +_get_e_ops_data(e_ops, ::Type{SaveFuncMESolve}) = [_generate_mesolve_e_op(op) for op in e_ops] # Broadcasting generates type instabilities on Julia v1.10 + ## # When e_ops is a list of operators @@ -29,7 +31,7 @@ function _save_func_mesolve(integrator, e_ops, progr, iter, expvals) end function _mesolve_callbacks_new_e_ops!(integrator::AbstractODEIntegrator, e_ops) - cb = _se_me_sse_get_save_callback(integrator) + cb = _get_save_callback(integrator, SaveFuncMESolve) if cb isa Nothing return nothing else @@ -37,3 +39,5 @@ function _mesolve_callbacks_new_e_ops!(integrator::AbstractODEIntegrator, e_ops) return nothing end end + +_generate_mesolve_e_op(op) = mat2vec(adjoint(get_data(op))) diff --git a/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl index 54f0945f9..e5205ffc6 100644 --- a/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl @@ -2,7 +2,7 @@ Helper functions for the sesolve callbacks. =# -struct SaveFuncSESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} +struct SaveFuncSESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} <: AbstractSaveFunc e_ops::TE progr::PT iter::IT @@ -12,6 +12,8 @@ end (f::SaveFuncSESolve)(integrator) = _save_func_sesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) (f::SaveFuncSESolve{Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both mesolve and sesolve +_get_e_ops_data(e_ops, ::Type{SaveFuncSESolve}) = get_data.(e_ops) + ## # When e_ops is a list of operators diff --git a/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl new file mode 100644 index 000000000..ab14bf81b --- /dev/null +++ b/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl @@ -0,0 +1,55 @@ +#= +Helper functions for the smesolve callbacks. Almost equal to the mesolve case, but with an additional possibility to store the measurement operators expectation values. +=# + +struct SaveFuncSMESolve{ + SM, + TE, + TE2, + PT<:Union{Nothing,ProgressBar}, + IT, + TEXPV<:Union{Nothing,AbstractMatrix}, + TMEXPV<:Union{Nothing,AbstractMatrix}, +} <: AbstractSaveFunc + store_measurement::Val{SM} + e_ops::TE + m_ops::TE2 + progr::PT + iter::IT + expvals::TEXPV + m_expvals::TMEXPV +end + +(f::SaveFuncSMESolve)(integrator) = + _save_func_smesolve(integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) +(f::SaveFuncSMESolve{false,Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both all solvers + +_get_e_ops_data(e_ops, ::Type{SaveFuncSMESolve}) = _get_e_ops_data(e_ops, SaveFuncMESolve) +_get_m_ops_data(sc_ops, ::Type{SaveFuncSMESolve}) = + map(op -> _generate_mesolve_e_op(op) + _generate_mesolve_e_op(op'), sc_ops) + +## + +# When e_ops is a list of operators +function _save_func_smesolve(integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) + # This is equivalent to tr(op * ρ), when both are matrices. + # The advantage of using this convention is that We don't need + # to reshape u to make it a matrix, but we reshape the e_ops once. + + ρ = integrator.u + + _expect = op -> dot(op, ρ) + + if !isnothing(e_ops) + @. expvals[:, iter[]] = _expect(e_ops) + end + + if !isnothing(m_expvals) && iter[] > 1 + _dWdt = _homodyne_dWdt(integrator) + @. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + _dWdt + end + + iter[] += 1 + + return _save_func(integrator, progr) +end diff --git a/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl index 69fca4b11..bd20d2d2c 100644 --- a/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl @@ -1,25 +1,65 @@ #= -Helper functions for the ssesolve callbacks. Equal to the sesolve case, but with an additional normalization before saving the expectation values. +Helper functions for the ssesolve callbacks. Almost equal to the sesolve case, but with an additional possibility to store the measurement operators expectation values. Also, this callback is not the first one, but the second one, due to the presence of the normalization callback. =# -struct SaveFuncSSESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing,AbstractMatrix}} +struct SaveFuncSSESolve{ + SM, + TE, + TE2, + PT<:Union{Nothing,ProgressBar}, + IT, + TEXPV<:Union{Nothing,AbstractMatrix}, + TMEXPV<:Union{Nothing,AbstractMatrix}, +} <: AbstractSaveFunc + store_measurement::Val{SM} e_ops::TE + m_ops::TE2 progr::PT iter::IT expvals::TEXPV + m_expvals::TMEXPV end -(f::SaveFuncSSESolve)(integrator) = _save_func_ssesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) -(f::SaveFuncSSESolve{Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both mesolve and sesolve +(f::SaveFuncSSESolve)(integrator) = + _save_func_ssesolve(integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) +(f::SaveFuncSSESolve{false,Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both all solvers + +_get_e_ops_data(e_ops, ::Type{SaveFuncSSESolve}) = get_data.(e_ops) +_get_m_ops_data(sc_ops, ::Type{SaveFuncSSESolve}) = map(op -> Hermitian(get_data(op) + get_data(op)'), sc_ops) + +_get_save_callback_idx(cb, ::Type{SaveFuncSSESolve}) = 2 # The first one is the normalization callback ## # When e_ops is a list of operators -function _save_func_ssesolve(integrator, e_ops, progr, iter, expvals) - ψ = normalize!(integrator.u) +function _save_func_ssesolve(integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) + ψ = integrator.u + _expect = op -> dot(ψ, op, ψ) - @. expvals[:, iter[]] = _expect(e_ops) + + if !isnothing(e_ops) + @. expvals[:, iter[]] = _expect(e_ops) + end + + if !isnothing(m_expvals) && iter[] > 1 + _dWdt = _homodyne_dWdt(integrator) + @. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + _dWdt + end + iter[] += 1 return _save_func(integrator, progr) end + +## + +#= + This function generates the normalization callback. It is needed to stabilize the integration when using the ssesolve method. +=# +function _ssesolve_generate_normalize_cb() + _condition = (u, t, integrator) -> true + _affect! = (integrator) -> normalize!(integrator.u) + cb = DiscreteCallback(_condition, _affect!; save_positions = (false, false)) + + return cb +end diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 172e8e099..210cf37a7 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -398,9 +398,10 @@ function mcsolve( dims = ens_prob_mc.dimensions _sol_1 = sol[:, 1] - _expvals_sol_1 = _mcsolve_get_expvals(_sol_1) + _expvals_sol_1 = _get_expvals(_sol_1, SaveFuncMCSolve) - _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _mcsolve_get_expvals(sol[:, i]), eachindex(sol)) + _expvals_all = + _expvals_sol_1 isa Nothing ? nothing : map(i -> _get_expvals(sol[:, i], SaveFuncMCSolve), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) col_times = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.col_times, eachindex(sol)) diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 7db098bdd..ee78e7eac 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -178,7 +178,7 @@ function mesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit return TimeEvolutionSol( prob.times, ρt, - _se_me_sse_get_expvals(sol), + _get_expvals(sol, SaveFuncMESolve), sol.retcode, sol.alg, NamedTuple(sol.prob.kwargs).abstol, diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 368d46241..571b1a396 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -154,7 +154,7 @@ function sesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit return TimeEvolutionSol( prob.times, ψt, - _se_me_sse_get_expvals(sol), + _get_expvals(sol, SaveFuncSESolve), sol.retcode, sol.alg, NamedTuple(sol.prob.kwargs).abstol, diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 741c0d045..32f02be70 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -20,6 +20,7 @@ _smesolve_ScalarOperator(op_vec) = params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val, Bool} = Val(false), kwargs..., ) @@ -54,6 +55,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `store_measurement`: Whether to store the measurement expectation values. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -77,6 +79,7 @@ function smesolveProblem( params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} haskey(kwargs, :save_idxs) && @@ -111,11 +114,24 @@ function smesolveProblem( D = DiffusionOperator(D_l) kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) - kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncMESolve) + kwargs3 = _generate_stochastic_kwargs( + e_ops, + sc_ops, + makeVal(progress_bar), + tlist, + makeVal(store_measurement), + kwargs2, + SaveFuncSMESolve, + ) tspan = (tlist[1], tlist[end]) - noise = - RealWienerProcess!(tlist[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) + noise = RealWienerProcess!( + tlist[1], + zeros(length(sc_ops)), + zeros(length(sc_ops)), + save_everystep = getVal(store_measurement), + rng = rng, + ) noise_rate_prototype = similar(ρ0, length(ρ0), length(sc_ops)) prob = SDEProblem{true}( K, @@ -141,11 +157,12 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function, Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) @@ -179,11 +196,12 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. -- `ntraj`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `ntraj`: Number of trajectories to use. Default is `500`. +- `ensemblealg`: Ensemble method to use. Default to `EnsembleThreads()`. - `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `store_measurement`: Whether to store the measurement expectation values. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -206,19 +224,27 @@ function smesolveEnsembleProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} _prob_func = isnothing(prob_func) ? - _ensemble_dispatch_prob_func(rng, ntraj, tlist, _stochastic_prob_func; n_sc_ops = length(sc_ops)) : prob_func + _ensemble_dispatch_prob_func( + rng, + ntraj, + tlist, + _stochastic_prob_func; + n_sc_ops = length(sc_ops), + store_measurement = makeVal(store_measurement), + ) : prob_func _output_func = output_func isa Nothing ? - _ensemble_dispatch_output_func(ensemble_method, progress_bar, ntraj, _stochastic_output_func) : output_func + _ensemble_dispatch_output_func(ensemblealg, progress_bar, ntraj, _stochastic_output_func) : output_func prob_sme = smesolveProblem( H, @@ -230,6 +256,7 @@ function smesolveEnsembleProblem( params = params, rng = rng, progress_bar = Val(false), + store_measurement = makeVal(store_measurement), kwargs..., ) @@ -254,11 +281,12 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function, Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) @@ -293,11 +321,12 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. -- `ntraj`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `ntraj`: Number of trajectories to use. Default is `500`. +- `ensemblealg`: Ensemble method to use. Default to `EnsembleThreads()`. - `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `store_measurement`: Whether to store the measurement expectation values. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -321,11 +350,12 @@ function smesolve( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} ensemble_prob = smesolveEnsembleProblem( @@ -338,34 +368,41 @@ function smesolve( params = params, rng = rng, ntraj = ntraj, - ensemble_method = ensemble_method, + ensemblealg = ensemblealg, prob_func = prob_func, output_func = output_func, progress_bar = progress_bar, + store_measurement = makeVal(store_measurement), kwargs..., ) - return smesolve(ensemble_prob, alg, ntraj, ensemble_method) + return smesolve(ensemble_prob, alg, ntraj, ensemblealg) end function smesolve( ens_prob::TimeEvolutionProblem, alg::StochasticDiffEqAlgorithm = SRA1(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), ) - sol = _ensemble_dispatch_solve(ens_prob, alg, ensemble_method, ntraj) + sol = _ensemble_dispatch_solve(ens_prob, alg, ensemblealg, ntraj) _sol_1 = sol[:, 1] - _expvals_sol_1 = _se_me_sse_get_expvals(_sol_1) + _expvals_sol_1 = _get_expvals(_sol_1, SaveFuncMESolve) + _m_expvals_sol_1 = _get_m_expvals(_sol_1, SaveFuncSMESolve) dims = ens_prob.dimensions - _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) + _expvals_all = + _expvals_sol_1 isa Nothing ? nothing : map(i -> _get_expvals(sol[:, i], SaveFuncMESolve), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP states = map(i -> _smesolve_generate_state.(sol[:, i].u, Ref(dims)), eachindex(sol)) + _m_expvals = + _m_expvals_sol_1 isa Nothing ? nothing : map(i -> _get_m_expvals(sol[:, i], SaveFuncSMESolve), eachindex(sol)) + m_expvals = _m_expvals isa Nothing ? nothing : stack(_m_expvals, dims = 2) + expvals = - _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : + _get_expvals(_sol_1, SaveFuncMESolve) isa Nothing ? nothing : dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) return TimeEvolutionStochasticSol( @@ -375,6 +412,7 @@ function smesolve( expvals, expvals, # This is average_expect expvals_all, + m_expvals, # Measurement expectation values sol.converged, _sol_1.alg, _sol_1.prob.kwargs[:abstol], diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index d137d5352..c428b1547 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -22,6 +22,7 @@ _ScalarOperator_e2_2(op, f = +) = params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val, Bool} = Val(false), kwargs..., ) @@ -56,6 +57,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `store_measurement`: Whether to store the measurement results. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -78,6 +80,7 @@ function ssesolveProblem( params = NullParameters(), rng::AbstractRNG = default_rng(), progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) haskey(kwargs, :save_idxs) && @@ -111,12 +114,24 @@ function ssesolveProblem( D = DiffusionOperator(D_l) kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) - kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSSESolve) - kwargs4 = _ssesolve_add_normalize_cb(kwargs3) + kwargs3 = _generate_stochastic_kwargs( + e_ops, + sc_ops, + makeVal(progress_bar), + tlist, + makeVal(store_measurement), + kwargs2, + SaveFuncSSESolve, + ) tspan = (tlist[1], tlist[end]) - noise = - RealWienerProcess!(tlist[1], zeros(length(sc_ops)), zeros(length(sc_ops)), save_everystep = false, rng = rng) + noise = RealWienerProcess!( + tlist[1], + zeros(length(sc_ops)), + zeros(length(sc_ops)), + save_everystep = getVal(store_measurement), + rng = rng, + ) noise_rate_prototype = similar(ψ0, length(ψ0), length(sc_ops)) prob = SDEProblem{true}( K, @@ -126,7 +141,7 @@ function ssesolveProblem( params; noise_rate_prototype = noise_rate_prototype, noise = noise, - kwargs4..., + kwargs3..., ) return TimeEvolutionProblem(prob, tlist, dims) @@ -141,11 +156,12 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function, Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) @@ -179,12 +195,13 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is t - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. -- `ntraj`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `ntraj`: Number of trajectories to use. Default is `500`. +- `ensemblealg`: Ensemble method to use. Default to `EnsembleThreads()`. - `jump_callback`: The Jump Callback type: Discrete or Continuous. The default is `ContinuousLindbladJumpCallback()`, which is more precise. - `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `store_measurement`: Whether to store the measurement results. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -206,19 +223,27 @@ function ssesolveEnsembleProblem( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) _prob_func = isnothing(prob_func) ? - _ensemble_dispatch_prob_func(rng, ntraj, tlist, _stochastic_prob_func; n_sc_ops = length(sc_ops)) : prob_func + _ensemble_dispatch_prob_func( + rng, + ntraj, + tlist, + _stochastic_prob_func; + n_sc_ops = length(sc_ops), + store_measurement = makeVal(store_measurement), + ) : prob_func _output_func = output_func isa Nothing ? - _ensemble_dispatch_output_func(ensemble_method, progress_bar, ntraj, _stochastic_output_func) : output_func + _ensemble_dispatch_output_func(ensemblealg, progress_bar, ntraj, _stochastic_output_func) : output_func prob_sme = ssesolveProblem( H, @@ -229,6 +254,7 @@ function ssesolveEnsembleProblem( params = params, rng = rng, progress_bar = Val(false), + store_measurement = makeVal(store_measurement), kwargs..., ) @@ -252,11 +278,12 @@ end e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function, Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) @@ -294,11 +321,12 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. -- `ntraj`: Number of trajectories to use. -- `ensemble_method`: Ensemble method to use. Default to `EnsembleThreads()`. +- `ntraj`: Number of trajectories to use. Default is `500`. +- `ensemblealg`: Ensemble method to use. Default to `EnsembleThreads()`. - `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `store_measurement`: Whether to store the measurement results. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. # Notes @@ -322,11 +350,12 @@ function ssesolve( e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) ens_prob = ssesolveEnsembleProblem( @@ -338,35 +367,42 @@ function ssesolve( params = params, rng = rng, ntraj = ntraj, - ensemble_method = ensemble_method, + ensemblealg = ensemblealg, prob_func = prob_func, output_func = output_func, progress_bar = progress_bar, + store_measurement = makeVal(store_measurement), kwargs..., ) - return ssesolve(ens_prob, alg, ntraj, ensemble_method) + return ssesolve(ens_prob, alg, ntraj, ensemblealg) end function ssesolve( ens_prob::TimeEvolutionProblem, alg::StochasticDiffEqAlgorithm = SRA1(), - ntraj::Int = 1, - ensemble_method = EnsembleThreads(), + ntraj::Int = 500, + ensemblealg::EnsembleAlgorithm = EnsembleThreads(), ) - sol = _ensemble_dispatch_solve(ens_prob, alg, ensemble_method, ntraj) + sol = _ensemble_dispatch_solve(ens_prob, alg, ensemblealg, ntraj) _sol_1 = sol[:, 1] - _expvals_sol_1 = _se_me_sse_get_expvals(_sol_1) + _expvals_sol_1 = _get_expvals(_sol_1, SaveFuncSSESolve) + _m_expvals_sol_1 = _get_m_expvals(_sol_1, SaveFuncSSESolve) normalize_states = Val(false) dims = ens_prob.dimensions - _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _se_me_sse_get_expvals(sol[:, i]), eachindex(sol)) + _expvals_all = + _expvals_sol_1 isa Nothing ? nothing : map(i -> _get_expvals(sol[:, i], SaveFuncSSESolve), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) + _m_expvals = + _m_expvals_sol_1 isa Nothing ? nothing : map(i -> _get_m_expvals(sol[:, i], SaveFuncSSESolve), eachindex(sol)) + m_expvals = _m_expvals isa Nothing ? nothing : stack(_m_expvals, dims = 2) + expvals = - _se_me_sse_get_expvals(_sol_1) isa Nothing ? nothing : + _get_expvals(_sol_1, SaveFuncSSESolve) isa Nothing ? nothing : dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) return TimeEvolutionStochasticSol( @@ -376,6 +412,7 @@ function ssesolve( expvals, expvals, # This is average_expect expvals_all, + m_expvals, # Measurement expectation values sol.converged, _sol_1.alg, _sol_1.prob.kwargs[:abstol], diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index c32805524..737837358 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -173,6 +173,7 @@ struct TimeEvolutionStochasticSol{ TS<:AbstractVector, TE<:Union{AbstractMatrix,Nothing}, TEA<:Union{AbstractArray,Nothing}, + TEM<:Union{AbstractArray,Nothing}, AlgT<:StochasticDiffEqAlgorithm, AT<:Real, RT<:Real, @@ -183,6 +184,7 @@ struct TimeEvolutionStochasticSol{ expect::TE average_expect::TE # Currently just a synonym for `expect` runs_expect::TEA + measurement::TEM converged::Bool alg::AlgT abstol::AT @@ -349,7 +351,7 @@ function _stochastic_prob_func(prob, i, repeat, rng, seeds, tlist; kwargs...) prob.prob.tspan[1], zeros(kwargs[:n_sc_ops]), zeros(kwargs[:n_sc_ops]), - save_everystep = false, + save_everystep = getVal(kwargs[:store_measurement]), rng = traj_rng, ) diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 77969751e..c320946e8 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -245,7 +245,7 @@ function dfd_mesolve( return TimeEvolutionSol( dfd_prob.times, ρt, - _se_me_sse_get_expvals(sol), + _get_expvals(sol, SaveFuncMESolve), sol.retcode, sol.alg, NamedTuple(sol.prob.kwargs).abstol, @@ -522,7 +522,7 @@ function _DSF_mcsolve_Affect!(integrator) # e_ops0 = params.e_ops # c_ops0 = params.c_ops - e_ops0 = _mcsolve_get_e_ops(integrator) + e_ops0 = _get_e_ops(integrator, SaveFuncMCSolve) c_ops0, c_ops0_herm = _mcsolve_get_c_ops(integrator) copyto!(ψt, integrator.u) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 2b9d0afe7..419c17677 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -24,7 +24,7 @@ ψ0_int = Qobj(round.(Int, real.(ψ0.data)), dims = ψ0.dims) # Used for testing the type inference - @testset "sesolve" begin + @testset "sesolve" verbose = true begin tlist = range(0, 20 * 2π / g, 1000) saveat_idxs = 500:900 saveat = tlist[saveat_idxs] @@ -100,7 +100,7 @@ end end - @testset "mesolve, mcsolve, ssesolve and smesolve" begin + @testset "mesolve, mcsolve, ssesolve and smesolve" verbose = true begin tlist = range(0, 10 / γ, 100) saveat_idxs = 50:90 saveat = tlist[saveat_idxs] @@ -130,8 +130,29 @@ progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), ) - sol_sse = ssesolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) - sol_sme = smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 500, e_ops = e_ops, progress_bar = Val(false)) + sol_sse = ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_sse2 = ssesolve( + H, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + ntraj = 20, + progress_bar = Val(false), + store_measurement = Val(true), + ) + sol_sme = smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, e_ops = e_ops, progress_bar = Val(false)) + sol_sme2 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + e_ops = e_ops, + ntraj = 20, + progress_bar = Val(false), + store_measurement = Val(true), + ) ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) @@ -172,6 +193,11 @@ @test size(sol_sse.expect) == (length(e_ops), length(tlist)) @test length(sol_sme.times) == length(tlist) @test size(sol_sme.expect) == (length(e_ops), length(tlist)) + @test isnothing(sol_sse.measurement) + @test isnothing(sol_sme.measurement) + @test size(sol_sse2.measurement) == (length(c_ops), 20, length(tlist) - 1) + @test size(sol_sme2.measurement) == (length(sc_ops_sme), 20, length(tlist) - 1) + @test sol_me_string == "Solution of time evolution\n" * "(return code: $(sol_me.retcode))\n" * @@ -255,7 +281,7 @@ sol_se = sesolve(H_dr_fr, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) sol_me = mesolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) sol_mc = mcsolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) - # sol_sse = ssesolve(H, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), rng = rng) + # sol_sse = ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) # Time Evolution in the lab frame @@ -268,7 +294,7 @@ sol_se_td = sesolve(H_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) sol_me_td = mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) sol_mc_td = mcsolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) - # sol_sse_td = ssesolve(H_td, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + # sol_sse_td = ssesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) @test sol_se.expect ≈ sol_se_td.expect atol = 1e-6 * length(tlist) @test sol_me.expect ≈ sol_me_td.expect atol = 1e-6 * length(tlist) @@ -282,7 +308,7 @@ sol_me_td2 = mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) sol_mc_td2 = mcsolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) # sol_sse_td2 = - # ssesolve(H_td2, ψ0, tlist, c_ops, ntraj = 500, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + # ssesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) @test sol_se.expect ≈ sol_se_td2.expect atol = 1e-6 * length(tlist) @test sol_me.expect ≈ sol_me_td2.expect atol = 1e-6 * length(tlist) From 3029a54a22313b48c692c67d5a55fb5bfce47d80 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:08:32 +0900 Subject: [PATCH 219/329] Bump version to v0.27.0 (#406) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 253fba8fd..b59a7bf4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.27.0] +Release date: 2025-02-14 + - Rename `sparse_to_dense` as `to_dense` and `dense_to_sparse` as `to_sparse`. ([#392]) - Fix erroneous definition of the stochastic term in `smesolve`. ([#393]) - Change name of `MultiSiteOperator` to `multisite_operator`. ([#394]) @@ -94,6 +97,7 @@ Release date: 2024-11-13 [v0.25.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.1 [v0.25.2]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.2 [v0.26.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.26.0 +[v0.27.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.27.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 1552ca026..f8f680204 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.26.0" +version = "0.27.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From ce73793f5267530d7aa53817358ce2ffc4d68a90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:50:33 +0900 Subject: [PATCH 220/329] Bump crate-ci/typos from 1.29.5 to 1.29.7 (#409) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 67aa62cf6..074d50e27 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.29.5 \ No newline at end of file + uses: crate-ci/typos@v1.29.7 \ No newline at end of file From 9e3ddf36fe8060415e4395baf93e455286dcea16 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Mon, 17 Feb 2025 18:07:55 +0900 Subject: [PATCH 221/329] Support for single `sc_ops` for faster specific method in `ssesolve` and `smesolve` (#408) --- CHANGELOG.md | 3 ++ src/QuantumToolbox.jl | 4 +- src/time_evolution/smesolve.jl | 61 ++++++++++++++++------------ src/time_evolution/ssesolve.jl | 61 ++++++++++++++++------------ src/time_evolution/time_evolution.jl | 36 ++++++++++++---- test/core-test/time_evolution.jl | 23 ++++++++++- 6 files changed, 126 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b59a7bf4f..ce0837a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Support for single `AbstractQuantumObject` in `sc_ops` for faster specific method in `ssesolve` and `smesolve`. ([#408]) + ## [v0.27.0] Release date: 2025-02-14 @@ -140,3 +142,4 @@ Release date: 2024-11-13 [#403]: https://github.com/qutip/QuantumToolbox.jl/issues/403 [#404]: https://github.com/qutip/QuantumToolbox.jl/issues/404 [#405]: https://github.com/qutip/QuantumToolbox.jl/issues/405 +[#408]: https://github.com/qutip/QuantumToolbox.jl/issues/408 diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index f1bb81b95..e4835221b 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -40,7 +40,7 @@ import SciMLBase: AbstractSciMLProblem, AbstractODEIntegrator, AbstractODESolution -import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA1 +import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA2, SRIW1 import SciMLOperators: SciMLOperators, AbstractSciMLOperator, @@ -56,7 +56,7 @@ import DiffEqBase: get_tstops import DiffEqCallbacks: PeriodicCallback, PresetTimeCallback, TerminateSteadyState import OrdinaryDiffEqCore: OrdinaryDiffEqAlgorithm import OrdinaryDiffEqTsit5: Tsit5 -import DiffEqNoiseProcess: RealWienerProcess! +import DiffEqNoiseProcess: RealWienerProcess!, RealWienerProcess # other dependencies (in alphabetical order) import ArrayInterface: allowed_getindex, allowed_setindex! diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 32f02be70..4890030d2 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -15,7 +15,7 @@ _smesolve_ScalarOperator(op_vec) = ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -50,7 +50,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. -- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -65,6 +65,9 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) +!!! tip "Performance Tip" + When `sc_ops` contains only a single operator, it is recommended to pass only that operator as the argument. This ensures that the stochastic noise is diagonal, making the simulation faster. + # Returns - `prob`: The [`TimeEvolutionProblem`](@ref) containing the `SDEProblem` for the Stochastic Master Equation time evolution. @@ -74,7 +77,7 @@ function smesolveProblem( ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -87,10 +90,12 @@ function smesolveProblem( isnothing(sc_ops) && throw(ArgumentError("The list of stochastic collapse operators must be provided. Use mesolveProblem instead.")) + sc_ops_list = _make_c_ops_list(sc_ops) # If it is an AbstractQuantumObject but we need to iterate + sc_ops_isa_Qobj = sc_ops isa AbstractQuantumObject # We can avoid using non-diagonal noise if sc_ops is just an AbstractQuantumObject tlist = _check_tlist(tlist, _FType(ψ0)) - L_evo = _mesolve_make_L_QobjEvo(H, c_ops) + _mesolve_make_L_QobjEvo(nothing, sc_ops) + L_evo = _mesolve_make_L_QobjEvo(H, c_ops) + _mesolve_make_L_QobjEvo(nothing, sc_ops_list) check_dimensions(L_evo, ψ0) dims = L_evo.dimensions @@ -99,7 +104,7 @@ function smesolveProblem( progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) - sc_ops_evo_data = Tuple(map(get_data ∘ QobjEvo, sc_ops)) + sc_ops_evo_data = Tuple(map(get_data ∘ QobjEvo, sc_ops_list)) K = get_data(L_evo) @@ -116,7 +121,7 @@ function smesolveProblem( kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_stochastic_kwargs( e_ops, - sc_ops, + sc_ops_list, makeVal(progress_bar), tlist, makeVal(store_measurement), @@ -125,14 +130,8 @@ function smesolveProblem( ) tspan = (tlist[1], tlist[end]) - noise = RealWienerProcess!( - tlist[1], - zeros(length(sc_ops)), - zeros(length(sc_ops)), - save_everystep = getVal(store_measurement), - rng = rng, - ) - noise_rate_prototype = similar(ρ0, length(ρ0), length(sc_ops)) + noise = _make_noise(tspan[1], sc_ops, makeVal(store_measurement), rng) + noise_rate_prototype = sc_ops_isa_Qobj ? nothing : similar(ρ0, length(ρ0), length(sc_ops_list)) prob = SDEProblem{true}( K, D, @@ -153,7 +152,7 @@ end ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -192,7 +191,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. -- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -211,6 +210,9 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) +!!! tip "Performance Tip" + When `sc_ops` contains only a single operator, it is recommended to pass only that operator as the argument. This ensures that the stochastic noise is diagonal, making the simulation faster. + # Returns - `prob`: The [`TimeEvolutionProblem`](@ref) containing the Ensemble `SDEProblem` for the Stochastic Master Equation time evolution. @@ -220,7 +222,7 @@ function smesolveEnsembleProblem( ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -239,7 +241,7 @@ function smesolveEnsembleProblem( ntraj, tlist, _stochastic_prob_func; - n_sc_ops = length(sc_ops), + sc_ops = sc_ops, store_measurement = makeVal(store_measurement), ) : prob_func _output_func = @@ -276,8 +278,8 @@ end ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::StochasticDiffEqAlgorithm = SRA1(), + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; + alg::Union{Nothing,StochasticDiffEqAlgorithm} = nothing, e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -316,8 +318,8 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. -- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. -- `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. +- `alg`: The algorithm to use for the stochastic differential equation. Default is `SRIW1()` if `sc_ops` is an [`AbstractQuantumObject`](@ref) (diagonal noise), and `SRA2()` otherwise (non-diagonal noise). - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -336,6 +338,9 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) +!!! tip "Performance Tip" + When `sc_ops` contains only a single operator, it is recommended to pass only that operator as the argument. This ensures that the stochastic noise is diagonal, making the simulation faster. + # Returns - `sol::TimeEvolutionStochasticSol`: The solution of the time evolution. See [`TimeEvolutionStochasticSol`](@ref). @@ -345,8 +350,8 @@ function smesolve( ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::StochasticDiffEqAlgorithm = SRA1(), + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; + alg::Union{Nothing,StochasticDiffEqAlgorithm} = nothing, e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -376,12 +381,18 @@ function smesolve( kwargs..., ) + sc_ops_isa_Qobj = sc_ops isa AbstractQuantumObject # We can avoid using non-diagonal noise if sc_ops is just an AbstractQuantumObject + + if isnothing(alg) + alg = sc_ops_isa_Qobj ? SRIW1() : SRA2() + end + return smesolve(ensemble_prob, alg, ntraj, ensemblealg) end function smesolve( ens_prob::TimeEvolutionProblem, - alg::StochasticDiffEqAlgorithm = SRA1(), + alg::StochasticDiffEqAlgorithm = SRA2(), ntraj::Int = 500, ensemblealg::EnsembleAlgorithm = EnsembleThreads(), ) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index c428b1547..5d7e89d0b 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -17,7 +17,7 @@ _ScalarOperator_e2_2(op, f = +) = H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -52,7 +52,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -67,6 +67,9 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) +!!! tip "Performance Tip" + When `sc_ops` contains only a single operator, it is recommended to pass only that operator as the argument. This ensures that the stochastic noise is diagonal, making the simulation faster. + # Returns - `prob`: The `SDEProblem` for the Stochastic Schrödinger time evolution of the system. @@ -75,7 +78,7 @@ function ssesolveProblem( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -88,10 +91,12 @@ function ssesolveProblem( sc_ops isa Nothing && throw(ArgumentError("The list of stochastic collapse operators must be provided. Use sesolveProblem instead.")) + sc_ops_list = _make_c_ops_list(sc_ops) # If it is an AbstractQuantumObject but we need to iterate + sc_ops_isa_Qobj = sc_ops isa AbstractQuantumObject # We can avoid using non-diagonal noise if sc_ops is just an AbstractQuantumObject tlist = _check_tlist(tlist, _FType(ψ0)) - H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, sc_ops) + H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, sc_ops_list) isoper(H_eff_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) check_dimensions(H_eff_evo, ψ0) dims = H_eff_evo.dimensions @@ -100,7 +105,7 @@ function ssesolveProblem( progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) - sc_ops_evo_data = Tuple(map(get_data ∘ QobjEvo, sc_ops)) + sc_ops_evo_data = Tuple(map(get_data ∘ QobjEvo, sc_ops_list)) # Here the coefficients depend on the state, so this is a non-linear operator, which should be implemented with FunctionOperator instead. However, the nonlinearity is only on the coefficients, and it should be safe. K_l = sum( @@ -116,7 +121,7 @@ function ssesolveProblem( kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_SDE_SOLVER_OPTIONS; kwargs...) kwargs3 = _generate_stochastic_kwargs( e_ops, - sc_ops, + sc_ops_list, makeVal(progress_bar), tlist, makeVal(store_measurement), @@ -125,14 +130,8 @@ function ssesolveProblem( ) tspan = (tlist[1], tlist[end]) - noise = RealWienerProcess!( - tlist[1], - zeros(length(sc_ops)), - zeros(length(sc_ops)), - save_everystep = getVal(store_measurement), - rng = rng, - ) - noise_rate_prototype = similar(ψ0, length(ψ0), length(sc_ops)) + noise = _make_noise(tspan[1], sc_ops, makeVal(store_measurement), rng) + noise_rate_prototype = sc_ops_isa_Qobj ? nothing : similar(ψ0, length(ψ0), length(sc_ops_list)) prob = SDEProblem{true}( K, D, @@ -152,7 +151,7 @@ end H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -191,7 +190,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is t - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -211,6 +210,9 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is t - The default tolerances in `kwargs` are given as `reltol=1e-2` and `abstol=1e-2`. - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) +!!! tip "Performance Tip" + When `sc_ops` contains only a single operator, it is recommended to pass only that operator as the argument. This ensures that the stochastic noise is diagonal, making the simulation faster. + # Returns - `prob::EnsembleProblem with SDEProblem`: The Ensemble SDEProblem for the Stochastic Shrödinger time evolution. @@ -219,7 +221,7 @@ function ssesolveEnsembleProblem( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -238,7 +240,7 @@ function ssesolveEnsembleProblem( ntraj, tlist, _stochastic_prob_func; - n_sc_ops = length(sc_ops), + sc_ops = sc_ops, store_measurement = makeVal(store_measurement), ) : prob_func _output_func = @@ -273,8 +275,8 @@ end H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::StochasticDiffEqAlgorithm = SRA1(), + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; + alg::Union{Nothing,StochasticDiffEqAlgorithm} = nothing, e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -316,8 +318,8 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. - `tlist`: List of times at which to save either the state or the expectation values of the system. -- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector` or a `Tuple`. -- `alg`: The algorithm to use for the stochastic differential equation. Default is `SRA1()`. +- `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. +- `alg`: The algorithm to use for the stochastic differential equation. Default is `SRIW1()` if `sc_ops` is an [`AbstractQuantumObject`](@ref) (diagonal noise), and `SRA2()` otherwise (non-diagonal noise). - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. - `rng`: Random number generator for reproducibility. @@ -337,6 +339,9 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - For more details about `alg` please refer to [`DifferentialEquations.jl` (SDE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) +!!! tip "Performance Tip" + When `sc_ops` contains only a single operator, it is recommended to pass only that operator as the argument. This ensures that the stochastic noise is diagonal, making the simulation faster. + # Returns - `sol::TimeEvolutionStochasticSol`: The solution of the time evolution. See [`TimeEvolutionStochasticSol`](@ref). @@ -345,8 +350,8 @@ function ssesolve( H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, ψ0::QuantumObject{KetQuantumObject}, tlist::AbstractVector, - sc_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - alg::StochasticDiffEqAlgorithm = SRA1(), + sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; + alg::Union{Nothing,StochasticDiffEqAlgorithm} = nothing, e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), rng::AbstractRNG = default_rng(), @@ -375,12 +380,18 @@ function ssesolve( kwargs..., ) + sc_ops_isa_Qobj = sc_ops isa AbstractQuantumObject # We can avoid using non-diagonal noise if sc_ops is just an AbstractQuantumObject + + if isnothing(alg) + alg = sc_ops_isa_Qobj ? SRIW1() : SRA2() + end + return ssesolve(ens_prob, alg, ntraj, ensemblealg) end function ssesolve( ens_prob::TimeEvolutionProblem, - alg::StochasticDiffEqAlgorithm = SRA1(), + alg::StochasticDiffEqAlgorithm = SRA2(), ntraj::Int = 500, ensemblealg::EnsembleAlgorithm = EnsembleThreads(), ) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 737837358..b487d6511 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -238,6 +238,9 @@ end ####################################### +_make_c_ops_list(c_ops) = c_ops +_make_c_ops_list(c_ops::AbstractQuantumObject) = (c_ops,) + function _merge_saveat(tlist, e_ops, default_options; kwargs...) is_empty_e_ops = isnothing(e_ops) ? true : isempty(e_ops) saveat = is_empty_e_ops ? tlist : [tlist[end]] @@ -347,13 +350,9 @@ function _stochastic_prob_func(prob, i, repeat, rng, seeds, tlist; kwargs...) traj_rng = typeof(rng)() seed!(traj_rng, seed) - noise = RealWienerProcess!( - prob.prob.tspan[1], - zeros(kwargs[:n_sc_ops]), - zeros(kwargs[:n_sc_ops]), - save_everystep = getVal(kwargs[:store_measurement]), - rng = traj_rng, - ) + sc_ops = kwargs[:sc_ops] + store_measurement = kwargs[:store_measurement] + noise = _make_noise(prob.prob.tspan[1], sc_ops, store_measurement, traj_rng) return remake(prob.prob, noise = noise, seed = seed) end @@ -361,6 +360,27 @@ end # Standard output function _stochastic_output_func(sol, i) = (sol, false) +#= + Define diagonal or non-diagonal noise depending on the type of `sc_ops`. + If `sc_ops` is a `AbstractQuantumObject`, we avoid using the non-diagonal noise. +=# +function _make_noise(t0, sc_ops, store_measurement::Val, rng) + noise = RealWienerProcess!( + t0, + zeros(length(sc_ops)), + zeros(length(sc_ops)), + save_everystep = getVal(store_measurement), + rng = rng, + ) + + return noise +end +function _make_noise(t0, sc_ops::AbstractQuantumObject, store_measurement::Val, rng) + noise = RealWienerProcess(t0, 0.0, 0.0, save_everystep = getVal(store_measurement), rng = rng) + + return noise +end + #= struct DiffusionOperator @@ -391,7 +411,7 @@ end N = length(ops_types) quote M = length(u) - S = size(v) + S = (size(v, 1), size(v, 2)) # This supports also `v` as a `Vector` (S[1] == M && S[2] == $N) || throw(DimensionMismatch("The size of the output vector is incorrect.")) Base.@nexprs $N i -> begin mul!(@view(v[:, i]), L.ops[i], u) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 419c17677..55be066a3 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -21,6 +21,9 @@ sme_η = 0.7 # Efficiency of the homodyne detector for smesolve c_ops_sme = [sqrt(1 - sme_η) * op for op in c_ops] sc_ops_sme = [sqrt(sme_η) * op for op in c_ops] + # The following definition is to test the case of `sc_ops` as an `AbstractQuantumObject` + c_ops_sme2 = c_ops[2:end] + sc_ops_sme2 = c_ops[1] ψ0_int = Qobj(round.(Int, real.(ψ0.data)), dims = ψ0.dims) # Used for testing the type inference @@ -153,6 +156,7 @@ progress_bar = Val(false), store_measurement = Val(true), ) + sol_sme3 = smesolve(H, ψ0, tlist, c_ops_sme2, sc_ops_sme2, e_ops = e_ops, progress_bar = Val(false)) ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) @@ -175,6 +179,7 @@ @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 @test sum(abs, sol_sse.expect .- sol_me.expect) / length(tlist) < 0.1 @test sum(abs, sol_sme.expect .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, sol_sme3.expect .- sol_me.expect) / length(tlist) < 0.1 @test length(sol_me.times) == length(tlist) @test length(sol_me.states) == 1 @test size(sol_me.expect) == (length(e_ops), length(tlist)) @@ -544,8 +549,11 @@ end @testset "Type Inference smesolve" begin - c_ops_sme_tuple = Tuple(c_ops_sme) # To avoid type instability, we must have a Tuple instead of a Vector - sc_ops_sme_tuple = Tuple(sc_ops_sme) # To avoid type instability, we must have a Tuple instead of a Vector + # To avoid type instability, we must have a Tuple instead of a Vector + c_ops_sme_tuple = Tuple(c_ops_sme) + sc_ops_sme_tuple = Tuple(sc_ops_sme) + c_ops_sme2_tuple = Tuple(c_ops_sme2) + sc_ops_sme2_tuple = sc_ops_sme2 # This is an `AbstractQuantumObject` @inferred smesolveEnsembleProblem( H, ψ0, @@ -568,6 +576,17 @@ progress_bar = Val(false), rng = rng, ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme2_tuple, + sc_ops_sme2_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) @inferred smesolve( H, ψ0, From d48371cc48d2138ca3d65bee396950a9e53f43bf Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 18 Feb 2025 00:56:03 +0100 Subject: [PATCH 222/329] Align `eigenstates` and `eigenenergies` to QuTiP (#411) --- CHANGELOG.md | 2 + benchmarks/eigenvalues.jl | 3 +- src/qobj/eigsolve.jl | 43 +++++++++++++-------- src/steadystate.jl | 2 +- test/core-test/eigenvalues_and_operators.jl | 16 ++++---- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0837a02..253449b48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Support for single `AbstractQuantumObject` in `sc_ops` for faster specific method in `ssesolve` and `smesolve`. ([#408]) +- Align `eigenstates` and `eigenenergies` to QuTiP. ([#411]) ## [v0.27.0] Release date: 2025-02-14 @@ -143,3 +144,4 @@ Release date: 2024-11-13 [#404]: https://github.com/qutip/QuantumToolbox.jl/issues/404 [#405]: https://github.com/qutip/QuantumToolbox.jl/issues/405 [#408]: https://github.com/qutip/QuantumToolbox.jl/issues/408 +[#411]: https://github.com/qutip/QuantumToolbox.jl/issues/411 diff --git a/benchmarks/eigenvalues.jl b/benchmarks/eigenvalues.jl index 3fc4287a0..3fab5302d 100644 --- a/benchmarks/eigenvalues.jl +++ b/benchmarks/eigenvalues.jl @@ -16,7 +16,8 @@ function benchmark_eigenvalues!(SUITE) L = liouvillian(H, c_ops) SUITE["Eigenvalues"]["eigenstates"]["dense"] = @benchmarkable eigenstates($L) - SUITE["Eigenvalues"]["eigenstates"]["sparse"] = @benchmarkable eigenstates($L, sparse = true, sigma = 0.01, k = 5) + SUITE["Eigenvalues"]["eigenstates"]["sparse"] = + @benchmarkable eigenstates($L, sparse = true, sigma = 0.01, eigvals = 5) return nothing end diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 73db30c7b..f180c0272 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -257,7 +257,7 @@ end eigsolve(A::QuantumObject; v0::Union{Nothing,AbstractVector}=nothing, sigma::Union{Nothing, Real}=nothing, - k::Int = 1, + eigvals::Int = 1, krylovdim::Int = max(20, 2*k+1), tol::Real = 1e-8, maxiter::Int = 200, @@ -266,6 +266,17 @@ end Solve for the eigenvalues and eigenvectors of a matrix `A` using the Arnoldi method. +# Arguments +- `A::QuantumObject`: the [`QuantumObject`](@ref) to solve eigenvalues and eigenvectors. +- `v0::Union{Nothing,AbstractVector}`: the initial vector for the Arnoldi method. Default is a random vector. +- `sigma::Union{Nothing, Real}`: the shift for the eigenvalue problem. Default is `nothing`. +- `eigvals::Int`: the number of eigenvalues to compute. Default is `1`. +- `krylovdim::Int`: the dimension of the Krylov subspace. Default is `max(20, 2*k+1)`. +- `tol::Real`: the tolerance for the Arnoldi method. Default is `1e-8`. +- `maxiter::Int`: the maximum number of iterations for the Arnoldi method. Default is `200`. +- `solver::Union{Nothing, SciMLLinearSolveAlgorithm}`: the linear solver algorithm. Default is `nothing`. +- `kwargs`: Additional keyword arguments passed to the solver. + # Notes - For more details about `solver` and extra `kwargs`, please refer to [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/) @@ -276,8 +287,8 @@ function eigsolve( A::QuantumObject; v0::Union{Nothing,AbstractVector} = nothing, sigma::Union{Nothing,Real} = nothing, - k::Int = 1, - krylovdim::Int = max(20, 2 * k + 1), + eigvals::Int = 1, + krylovdim::Int = max(20, 2 * eigvals + 1), tol::Real = 1e-8, maxiter::Int = 200, solver::Union{Nothing,SciMLLinearSolveAlgorithm} = nothing, @@ -289,7 +300,7 @@ function eigsolve( type = A.type, dimensions = A.dimensions, sigma = sigma, - k = k, + eigvals = eigvals, krylovdim = krylovdim, tol = tol, maxiter = maxiter, @@ -304,8 +315,8 @@ function eigsolve( type::Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject} = nothing, dimensions = nothing, sigma::Union{Nothing,Real} = nothing, - k::Int = 1, - krylovdim::Int = max(20, 2 * k + 1), + eigvals::Int = 1, + krylovdim::Int = max(20, 2 * eigvals + 1), tol::Real = 1e-8, maxiter::Int = 200, solver::Union{Nothing,SciMLLinearSolveAlgorithm} = nothing, @@ -316,7 +327,7 @@ function eigsolve( v0 === nothing && (v0 = normalize!(rand(T, size(A, 1)))) if sigma === nothing - res = _eigsolve(A, v0, type, dimensions, k, krylovdim, tol = tol, maxiter = maxiter) + res = _eigsolve(A, v0, type, dimensions, eigvals, krylovdim, tol = tol, maxiter = maxiter) vals = res.values else Aₛ = A - sigma * I @@ -334,7 +345,7 @@ function eigsolve( Amap = EigsolveInverseMap(T, size(A), linsolve) - res = _eigsolve(Amap, v0, type, dimensions, k, krylovdim, tol = tol, maxiter = maxiter) + res = _eigsolve(Amap, v0, type, dimensions, eigvals, krylovdim, tol = tol, maxiter = maxiter) vals = @. (1 + sigma * res.values) / res.values end @@ -349,7 +360,7 @@ end alg::OrdinaryDiffEqAlgorithm = Tsit5(), params::NamedTuple = NamedTuple(), ρ0::AbstractMatrix = rand_dm(prod(H.dimensions)).data, - k::Int = 1, + eigvals::Int = 1, krylovdim::Int = min(10, size(H, 1)), maxiter::Int = 200, eigstol::Real = 1e-6, @@ -365,7 +376,7 @@ Solve the eigenvalue problem for a Liouvillian superoperator `L` using the Arnol - `alg`: The differential equation solver algorithm. Default is `Tsit5()`. - `params`: A `NamedTuple` containing the parameters of the system. - `ρ0`: The initial density matrix. If not specified, a random density matrix is used. -- `k`: The number of eigenvalues to compute. +- `eigvals`: The number of eigenvalues to compute. - `krylovdim`: The dimension of the Krylov subspace. - `maxiter`: The maximum number of iterations for the eigsolver. - `eigstol`: The tolerance for the eigsolver. @@ -388,7 +399,7 @@ function eigsolve_al( alg::OrdinaryDiffEqAlgorithm = Tsit5(), params::NamedTuple = NamedTuple(), ρ0::AbstractMatrix = rand_dm(prod(H.dimensions)).data, - k::Int = 1, + eigvals::Int = 1, krylovdim::Int = min(10, size(H, 1)), maxiter::Int = 200, eigstol::Real = 1e-6, @@ -406,12 +417,10 @@ function eigsolve_al( ).prob integrator = init(prob, alg) - # prog = ProgressUnknown(desc="Applications:", showspeed = true, enabled=progress) - Lmap = ArnoldiLindbladIntegratorMap(eltype(H), size(L_evo), integrator) - res = _eigsolve(Lmap, mat2vec(ρ0), L_evo.type, L_evo.dimensions, k, krylovdim, maxiter = maxiter, tol = eigstol) - # finish!(prog) + res = + _eigsolve(Lmap, mat2vec(ρ0), L_evo.type, L_evo.dimensions, eigvals, krylovdim, maxiter = maxiter, tol = eigstol) vals = similar(res.values) vecs = similar(res.vectors) @@ -488,7 +497,7 @@ Calculate the eigenenergies # Arguments - `A::QuantumObject`: the [`QuantumObject`](@ref) to solve eigenvalues - `sparse::Bool`: if `false` call [`eigvals(A::QuantumObject; kwargs...)`](@ref), otherwise call [`eigsolve`](@ref). Default to `false`. -- `kwargs`: Additional keyword arguments passed to the solver +- `kwargs`: Additional keyword arguments passed to the solver. If `sparse=true`, the keyword arguments are passed to [`eigsolve`](@ref), otherwise to [`eigen`](@ref). # Returns - `::Vector{<:Number}`: a list of eigenvalues @@ -513,7 +522,7 @@ Calculate the eigenvalues and corresponding eigenvectors # Arguments - `A::QuantumObject`: the [`QuantumObject`](@ref) to solve eigenvalues and eigenvectors - `sparse::Bool`: if `false` call [`eigen(A::QuantumObject; kwargs...)`](@ref), otherwise call [`eigsolve`](@ref). Default to `false`. -- `kwargs`: Additional keyword arguments passed to the solver +- `kwargs`: Additional keyword arguments passed to the solver. If `sparse=true`, the keyword arguments are passed to [`eigsolve`](@ref), otherwise to [`eigen`](@ref). # Returns - `::EigsolveResult`: containing the eigenvalues, the eigenvectors, and some information from the solver. see also [`EigsolveResult`](@ref) diff --git a/src/steadystate.jl b/src/steadystate.jl index 94cbd3222..e0296954a 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -141,7 +141,7 @@ end function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateEigenSolver; kwargs...) N = prod(L.dimensions) - kwargs = merge((sigma = 1e-8, k = 1), (; kwargs...)) + kwargs = merge((sigma = 1e-8, eigvals = 1), (; kwargs...)) ρss_vec = eigsolve(L; kwargs...).vectors[:, 1] ρss = reshape(ρss_vec, N, N) diff --git a/test/core-test/eigenvalues_and_operators.jl b/test/core-test/eigenvalues_and_operators.jl index 481aedce0..3faebf0dc 100644 --- a/test/core-test/eigenvalues_and_operators.jl +++ b/test/core-test/eigenvalues_and_operators.jl @@ -5,15 +5,15 @@ resstring = sprint((t, s) -> show(t, "text/plain", s), result) valstring = sprint((t, s) -> show(t, "text/plain", s), result.values) vecsstring = sprint((t, s) -> show(t, "text/plain", s), result.vectors) - λs, ψs, Ts = eigenstates(σx, sparse = true, k = 2) - λs1, ψs1, Ts1 = eigenstates(σx, sparse = true, k = 1) + λs, ψs, Ts = eigenstates(σx, sparse = true, eigvals = 2) + λs1, ψs1, Ts1 = eigenstates(σx, sparse = true, eigvals = 1) @test all([ψ.type isa KetQuantumObject for ψ in ψd]) @test typeof(Td) <: AbstractMatrix @test typeof(Ts) <: AbstractMatrix @test typeof(Ts1) <: AbstractMatrix @test all(abs.(eigenenergies(σx, sparse = false)) .≈ abs.(λd)) - @test all(abs.(eigenenergies(σx, sparse = true, k = 2)) .≈ abs.(λs)) + @test all(abs.(eigenenergies(σx, sparse = true, eigvals = 2)) .≈ abs.(λs)) @test resstring == "EigsolveResult: type=$(Operator) dims=$(result.dims)\nvalues:\n$(valstring)\nvectors:\n$vecsstring" @@ -33,7 +33,7 @@ vals_d, vecs_d, mat_d = eigenstates(H_d) vals_c, vecs_c, mat_c = eigenstates(H_c) - vals2, vecs2, mat2 = eigenstates(H_d, sparse = true, sigma = -0.9, k = 10, krylovdim = 30) + vals2, vecs2, mat2 = eigenstates(H_d, sparse = true, sigma = -0.9, eigvals = 10, krylovdim = 30) sort!(vals_c, by = real) sort!(vals2, by = real) @@ -57,9 +57,9 @@ L = liouvillian(H, c_ops) # eigen solve for general matrices - vals, _, vecs = eigsolve(L.data, sigma = 0.01, k = 10, krylovdim = 50) + vals, _, vecs = eigsolve(L.data, sigma = 0.01, eigvals = 10, krylovdim = 50) vals2, vecs2 = eigen(to_dense(L.data)) - vals3, state3, vecs3 = eigsolve_al(L, 1 \ (40 * κ), k = 10, krylovdim = 50) + vals3, state3, vecs3 = eigsolve_al(L, 1 \ (40 * κ), eigvals = 10, krylovdim = 50) idxs = sortperm(vals2, by = abs) vals2 = vals2[idxs][1:10] vecs2 = vecs2[:, idxs][:, 1:10] @@ -70,7 +70,7 @@ @test isapprox(vec2mat(vecs[:, 1]) * exp(-1im * angle(vecs[1, 1])), vec2mat(vecs3[:, 1]), atol = 1e-5) # eigen solve for QuantumObject - result = eigenstates(L, sparse = true, sigma = 0.01, k = 10, krylovdim = 50) + result = eigenstates(L, sparse = true, sigma = 0.01, eigvals = 10, krylovdim = 50) vals, vecs = result resstring = sprint((t, s) -> show(t, "text/plain", s), result) valstring = sprint((t, s) -> show(t, "text/plain", s), result.values) @@ -112,6 +112,6 @@ @inferred eigenstates(H, sparse = false) @inferred eigenstates(H, sparse = true) @inferred eigenstates(L, sparse = true) - @inferred eigsolve_al(L, 1 \ (40 * κ), k = 10) + @inferred eigsolve_al(L, 1 \ (40 * κ), eigvals = 10) end end From 300fd5de584c86eaca47e89a685e693e9a75c55f Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 18 Feb 2025 03:50:24 +0100 Subject: [PATCH 223/329] Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback` (#410) Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> --- CHANGELOG.md | 2 + src/QuantumToolbox.jl | 2 +- .../callback_helpers/callback_helpers.jl | 18 ++-- .../mcsolve_callback_helpers.jl | 30 +++---- .../mesolve_callback_helpers.jl | 10 +-- .../sesolve_callback_helpers.jl | 8 +- .../smesolve_callback_helpers.jl | 10 +-- .../ssesolve_callback_helpers.jl | 10 +-- test/core-test/time_evolution.jl | 87 ++++++++++++++----- 9 files changed, 113 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 253449b48..b2dcd5593 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Support for single `AbstractQuantumObject` in `sc_ops` for faster specific method in `ssesolve` and `smesolve`. ([#408]) +- Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback`. ([#410]) - Align `eigenstates` and `eigenenergies` to QuTiP. ([#411]) ## [v0.27.0] @@ -144,4 +145,5 @@ Release date: 2024-11-13 [#404]: https://github.com/qutip/QuantumToolbox.jl/issues/404 [#405]: https://github.com/qutip/QuantumToolbox.jl/issues/405 [#408]: https://github.com/qutip/QuantumToolbox.jl/issues/408 +[#410]: https://github.com/qutip/QuantumToolbox.jl/issues/410 [#411]: https://github.com/qutip/QuantumToolbox.jl/issues/411 diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index e4835221b..d06979dda 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -53,7 +53,7 @@ import SciMLOperators: concretize import LinearSolve: LinearProblem, SciMLLinearSolveAlgorithm, KrylovJL_MINRES, KrylovJL_GMRES import DiffEqBase: get_tstops -import DiffEqCallbacks: PeriodicCallback, PresetTimeCallback, TerminateSteadyState +import DiffEqCallbacks: PeriodicCallback, FunctionCallingCallback, FunctionCallingAffect, TerminateSteadyState import OrdinaryDiffEqCore: OrdinaryDiffEqAlgorithm import OrdinaryDiffEqTsit5: Tsit5 import DiffEqNoiseProcess: RealWienerProcess!, RealWienerProcess diff --git a/src/time_evolution/callback_helpers/callback_helpers.jl b/src/time_evolution/callback_helpers/callback_helpers.jl index 4170635a8..e84085b47 100644 --- a/src/time_evolution/callback_helpers/callback_helpers.jl +++ b/src/time_evolution/callback_helpers/callback_helpers.jl @@ -56,8 +56,8 @@ function _generate_save_callback(e_ops, tlist, progress_bar, method) expvals = e_ops isa Nothing ? nothing : Array{ComplexF64}(undef, length(e_ops), length(tlist)) - _save_affect! = method(e_ops_data, progr, Ref(1), expvals) - return PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)) + _save_func = method(e_ops_data, progr, Ref(1), expvals) + return FunctionCallingCallback(_save_func, funcat = tlist) end function _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurement, progress_bar, method) @@ -69,8 +69,8 @@ function _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurem expvals = e_ops isa Nothing ? nothing : Array{ComplexF64}(undef, length(e_ops), length(tlist)) m_expvals = getVal(store_measurement) ? Array{Float64}(undef, length(sc_ops), length(tlist) - 1) : nothing - _save_affect! = method(store_measurement, e_ops_data, m_ops_data, progr, Ref(1), expvals, m_expvals) - return PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)) + _save_func = method(store_measurement, e_ops_data, m_ops_data, progr, Ref(1), expvals, m_expvals) + return FunctionCallingCallback(_save_func, funcat = tlist) end ## @@ -98,20 +98,20 @@ function _get_m_expvals(integrator::AbstractODESolution, method::Type{SF}) where if cb isa Nothing return nothing else - return cb.affect!.m_expvals + return cb.affect!.func.m_expvals end end #= With this function we extract the e_ops from the SaveFuncMCSolve `affect!` function of the callback of the integrator. - This callback can only be a PresetTimeCallback (DiscreteCallback). + This callback can only be a FunctionCallingCallback (DiscreteCallback). =# function _get_e_ops(integrator::AbstractODEIntegrator, method::Type{SF}) where {SF<:AbstractSaveFunc} cb = _get_save_callback(integrator, method) if cb isa Nothing return nothing else - return cb.affect!.e_ops + return cb.affect!.func.e_ops end end @@ -121,7 +121,7 @@ function _get_expvals(sol::AbstractODESolution, method::Type{SF}) where {SF<:Abs if cb isa Nothing return nothing else - return cb.affect!.expvals + return cb.affect!.func.expvals end end @@ -151,7 +151,7 @@ function _get_save_callback(cb::CallbackSet, method::Type{SF}) where {SF<:Abstra end end function _get_save_callback(cb::DiscreteCallback, ::Type{SF}) where {SF<:AbstractSaveFunc} - if typeof(cb.affect!) <: AbstractSaveFunc + if typeof(cb.affect!) <: FunctionCallingAffect && typeof(cb.affect!.func) <: AbstractSaveFunc return cb end return nothing diff --git a/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl index b0095e705..d96b50202 100644 --- a/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/mcsolve_callback_helpers.jl @@ -8,7 +8,7 @@ struct SaveFuncMCSolve{TE,IT,TEXPV} <: AbstractSaveFunc expvals::TEXPV end -(f::SaveFuncMCSolve)(integrator) = _save_func_mcsolve(integrator, f.e_ops, f.iter, f.expvals) +(f::SaveFuncMCSolve)(u, t, integrator) = _save_func_mcsolve(u, integrator, f.e_ops, f.iter, f.expvals) _get_save_callback_idx(cb, ::Type{SaveFuncMCSolve}) = _mcsolve_has_continuous_jump(cb) ? 1 : 2 @@ -52,10 +52,10 @@ end ## -function _save_func_mcsolve(integrator, e_ops, iter, expvals) +function _save_func_mcsolve(u, integrator, e_ops, iter, expvals) cache_mc = _mc_get_jump_callback(integrator).affect!.cache_mc - copyto!(cache_mc, integrator.u) + copyto!(cache_mc, u) normalize!(cache_mc) ψ = cache_mc _expect = op -> dot(ψ, op, ψ) @@ -114,8 +114,8 @@ function _generate_mcsolve_kwargs(ψ0, T, e_ops, tlist, c_ops, jump_callback, rn else expvals = Array{ComplexF64}(undef, length(e_ops), length(tlist)) - _save_affect! = SaveFuncMCSolve(get_data.(e_ops), Ref(1), expvals) - cb2 = PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)) + _save_func = SaveFuncMCSolve(get_data.(e_ops), Ref(1), expvals) + cb2 = FunctionCallingCallback(_save_func, funcat = tlist) kwargs2 = haskey(kwargs, :callback) ? merge(kwargs, (callback = CallbackSet(cb1, cb2, kwargs.callback),)) : merge(kwargs, (callback = CallbackSet(cb1, cb2),)) @@ -214,11 +214,11 @@ function _mcsolve_initialize_callbacks(cb::CallbackSet, tlist, traj_rng) if _mcsolve_has_continuous_jump(cb) idx = 1 - if cb_discrete[idx].affect! isa SaveFuncMCSolve - e_ops = cb_discrete[idx].affect!.e_ops - expvals = similar(cb_discrete[idx].affect!.expvals) - _save_affect! = SaveFuncMCSolve(e_ops, Ref(1), expvals) - cb_save = (PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)),) + if cb_discrete[idx].affect!.func isa SaveFuncMCSolve + e_ops = cb_discrete[idx].affect!.func.e_ops + expvals = similar(cb_discrete[idx].affect!.func.expvals) + _save_func = SaveFuncMCSolve(e_ops, Ref(1), expvals) + cb_save = (FunctionCallingCallback(_save_func, funcat = tlist),) else cb_save = () end @@ -229,11 +229,11 @@ function _mcsolve_initialize_callbacks(cb::CallbackSet, tlist, traj_rng) return CallbackSet((cb_jump, cb_continuous[2:end]...), (cb_save..., cb_discrete[2:end]...)) else idx = 2 - if cb_discrete[idx].affect! isa SaveFuncMCSolve - e_ops = cb_discrete[idx].affect!.e_ops - expvals = similar(cb_discrete[idx].affect!.expvals) - _save_affect! = SaveFuncMCSolve(e_ops, Ref(1), expvals) - cb_save = (PresetTimeCallback(tlist, _save_affect!, save_positions = (false, false)),) + if cb_discrete[idx].affect!.func isa SaveFuncMCSolve + e_ops = cb_discrete[idx].affect!.func.e_ops + expvals = similar(cb_discrete[idx].affect!.func.expvals) + _save_func = SaveFuncMCSolve(e_ops, Ref(1), expvals) + cb_save = (FunctionCallingCallback(_save_func, funcat = tlist),) else cb_save = () end diff --git a/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl index ee253d765..a24229d29 100644 --- a/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/mesolve_callback_helpers.jl @@ -9,20 +9,20 @@ struct SaveFuncMESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing expvals::TEXPV end -(f::SaveFuncMESolve)(integrator) = _save_func_mesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) -(f::SaveFuncMESolve{Nothing})(integrator) = _save_func(integrator, f.progr) +(f::SaveFuncMESolve)(u, t, integrator) = _save_func_mesolve(u, integrator, f.e_ops, f.progr, f.iter, f.expvals) +(f::SaveFuncMESolve{Nothing})(u, t, integrator) = _save_func(integrator, f.progr) _get_e_ops_data(e_ops, ::Type{SaveFuncMESolve}) = [_generate_mesolve_e_op(op) for op in e_ops] # Broadcasting generates type instabilities on Julia v1.10 ## # When e_ops is a list of operators -function _save_func_mesolve(integrator, e_ops, progr, iter, expvals) +function _save_func_mesolve(u, integrator, e_ops, progr, iter, expvals) # This is equivalent to tr(op * ρ), when both are matrices. # The advantage of using this convention is that We don't need # to reshape u to make it a matrix, but we reshape the e_ops once. - ρ = integrator.u + ρ = u _expect = op -> dot(op, ρ) @. expvals[:, iter[]] = _expect(e_ops) iter[] += 1 @@ -35,7 +35,7 @@ function _mesolve_callbacks_new_e_ops!(integrator::AbstractODEIntegrator, e_ops) if cb isa Nothing return nothing else - cb.affect!.e_ops .= e_ops # Only works if e_ops is a Vector of operators + cb.affect!.func.e_ops .= e_ops # Only works if e_ops is a Vector of operators return nothing end end diff --git a/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl index e5205ffc6..2bbff8bf0 100644 --- a/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/sesolve_callback_helpers.jl @@ -9,16 +9,16 @@ struct SaveFuncSESolve{TE,PT<:Union{Nothing,ProgressBar},IT,TEXPV<:Union{Nothing expvals::TEXPV end -(f::SaveFuncSESolve)(integrator) = _save_func_sesolve(integrator, f.e_ops, f.progr, f.iter, f.expvals) -(f::SaveFuncSESolve{Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both mesolve and sesolve +(f::SaveFuncSESolve)(u, t, integrator) = _save_func_sesolve(u, integrator, f.e_ops, f.progr, f.iter, f.expvals) +(f::SaveFuncSESolve{Nothing})(u, t, integrator) = _save_func(integrator, f.progr) # Common for both mesolve and sesolve _get_e_ops_data(e_ops, ::Type{SaveFuncSESolve}) = get_data.(e_ops) ## # When e_ops is a list of operators -function _save_func_sesolve(integrator, e_ops, progr, iter, expvals) - ψ = integrator.u +function _save_func_sesolve(u, integrator, e_ops, progr, iter, expvals) + ψ = u _expect = op -> dot(ψ, op, ψ) @. expvals[:, iter[]] = _expect(e_ops) iter[] += 1 diff --git a/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl index ab14bf81b..6fb39c14b 100644 --- a/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl @@ -20,9 +20,9 @@ struct SaveFuncSMESolve{ m_expvals::TMEXPV end -(f::SaveFuncSMESolve)(integrator) = - _save_func_smesolve(integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) -(f::SaveFuncSMESolve{false,Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both all solvers +(f::SaveFuncSMESolve)(u, t, integrator) = + _save_func_smesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) +(f::SaveFuncSMESolve{false,Nothing})(u, t, integrator) = _save_func(integrator, f.progr) # Common for both all solvers _get_e_ops_data(e_ops, ::Type{SaveFuncSMESolve}) = _get_e_ops_data(e_ops, SaveFuncMESolve) _get_m_ops_data(sc_ops, ::Type{SaveFuncSMESolve}) = @@ -31,12 +31,12 @@ _get_m_ops_data(sc_ops, ::Type{SaveFuncSMESolve}) = ## # When e_ops is a list of operators -function _save_func_smesolve(integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) +function _save_func_smesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) # This is equivalent to tr(op * ρ), when both are matrices. # The advantage of using this convention is that We don't need # to reshape u to make it a matrix, but we reshape the e_ops once. - ρ = integrator.u + ρ = u _expect = op -> dot(op, ρ) diff --git a/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl index bd20d2d2c..c84cd7e5e 100644 --- a/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl @@ -20,9 +20,9 @@ struct SaveFuncSSESolve{ m_expvals::TMEXPV end -(f::SaveFuncSSESolve)(integrator) = - _save_func_ssesolve(integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) -(f::SaveFuncSSESolve{false,Nothing})(integrator) = _save_func(integrator, f.progr) # Common for both all solvers +(f::SaveFuncSSESolve)(u, t, integrator) = + _save_func_ssesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) +(f::SaveFuncSSESolve{false,Nothing})(u, t, integrator) = _save_func(integrator, f.progr) # Common for both all solvers _get_e_ops_data(e_ops, ::Type{SaveFuncSSESolve}) = get_data.(e_ops) _get_m_ops_data(sc_ops, ::Type{SaveFuncSSESolve}) = map(op -> Hermitian(get_data(op) + get_data(op)'), sc_ops) @@ -32,8 +32,8 @@ _get_save_callback_idx(cb, ::Type{SaveFuncSSESolve}) = 2 # The first one is the ## # When e_ops is a list of operators -function _save_func_ssesolve(integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) - ψ = integrator.u +function _save_func_ssesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) + ψ = u _expect = op -> dot(ψ, op, ψ) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 55be066a3..bd5be390a 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -85,11 +85,11 @@ @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)) - @test allocs_tot < 150 + @test allocs_tot < 110 allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) - @test allocs_tot < 100 + @test allocs_tot < 90 end @testset "Type Inference sesolve" begin @@ -327,21 +327,21 @@ allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) # Warm-up allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) - @test allocs_tot < 210 + @test allocs_tot < 180 allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) - @test allocs_tot < 120 + @test allocs_tot < 110 allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) # Warm-up allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) - @test allocs_tot < 210 + @test allocs_tot < 180 allocs_tot = @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) # Warm-up allocs_tot = @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) - @test allocs_tot < 120 + @test allocs_tot < 110 end @testset "Memory Allocations (mcsolve)" begin @@ -350,7 +350,7 @@ @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up allocs_tot = @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) - @test allocs_tot < 160 * ntraj + 500 # 150 allocations per trajectory + 500 for initialization + @test allocs_tot < 120 * ntraj + 400 # 150 allocations per trajectory + 500 for initialization allocs_tot = @allocations mcsolve( H, @@ -370,22 +370,23 @@ saveat = [tlist[end]], progress_bar = Val(false), ) - @test allocs_tot < 160 * ntraj + 300 # 100 allocations per trajectory + 300 for initialization + @test allocs_tot < 110 * ntraj + 300 # 100 allocations per trajectory + 300 for initialization end @testset "Memory Allocations (ssesolve)" begin + ntraj = 100 allocs_tot = - @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = 100, progress_bar = Val(false)) # Warm-up + @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up allocs_tot = - @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = 100, progress_bar = Val(false)) - @test allocs_tot < 1950000 # TODO: Fix this high number of allocations + @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) + @test allocs_tot < 1100 * ntraj + 400 # TODO: Fix this high number of allocations allocs_tot = @allocations ssesolve( H, ψ0, tlist, c_ops, - ntraj = 100, + ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false), ) # Warm-up @@ -394,14 +395,15 @@ ψ0, tlist, c_ops, - ntraj = 100, + ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false), ) - @test allocs_tot < 570000 # TODO: Fix this high number of allocations + @test allocs_tot < 1000 * ntraj + 300 # TODO: Fix this high number of allocations end @testset "Memory Allocations (smesolve)" begin + ntraj = 100 allocs_tot = @allocations smesolve( H, ψ0, @@ -409,7 +411,7 @@ c_ops_sme, sc_ops_sme, e_ops = e_ops, - ntraj = 100, + ntraj = ntraj, progress_bar = Val(false), ) # Warm-up allocs_tot = @allocations smesolve( @@ -419,10 +421,10 @@ c_ops_sme, sc_ops_sme, e_ops = e_ops, - ntraj = 100, + ntraj = ntraj, progress_bar = Val(false), ) - @test allocs_tot < 2750000 # TODO: Fix this high number of allocations + @test allocs_tot < 1100 * ntraj + 1800 # TODO: Fix this high number of allocations allocs_tot = @allocations smesolve( H, @@ -430,7 +432,7 @@ tlist, c_ops_sme, sc_ops_sme, - ntraj = 100, + ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false), ) # Warm-up @@ -440,11 +442,56 @@ tlist, c_ops_sme, sc_ops_sme, - ntraj = 100, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + ) + @test allocs_tot < 1000 * ntraj + 1500 # TODO: Fix this high number of allocations + + # Diagonal Noise Case + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + e_ops = e_ops, + ntraj = 1, + progress_bar = Val(false), + ) + @test allocs_tot < 600 * ntraj + 1400 # TODO: Fix this high number of allocations + + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + ntraj = 1, saveat = [tlist[end]], progress_bar = Val(false), ) - @test allocs_tot < 570000 # TODO: Fix this high number of allocations + @test allocs_tot < 550 * ntraj + 1000 # TODO: Fix this high number of allocations end @testset "Type Inference mesolve" begin From 7d58e4690efbe53580553e9c4659ebc82d0d1eed Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 18 Feb 2025 16:30:42 +0900 Subject: [PATCH 224/329] Introduce `operator_to_vector` and `vector_to_operator` (#413) --- CHANGELOG.md | 2 ++ docs/src/resources/api.md | 2 ++ docs/src/users_guide/states_and_operators.md | 2 +- src/qobj/functions.jl | 8 ++++++++ src/qobj/synonyms.jl | 4 ++++ test/core-test/quantum_objects.jl | 6 +++--- 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2dcd5593..2439ab0a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for single `AbstractQuantumObject` in `sc_ops` for faster specific method in `ssesolve` and `smesolve`. ([#408]) - Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback`. ([#410]) - Align `eigenstates` and `eigenenergies` to QuTiP. ([#411]) +- Introduce `vector_to_operator` and `operator_to_vector`. ([#413]) ## [v0.27.0] Release date: 2025-02-14 @@ -147,3 +148,4 @@ Release date: 2024-11-13 [#408]: https://github.com/qutip/QuantumToolbox.jl/issues/408 [#410]: https://github.com/qutip/QuantumToolbox.jl/issues/410 [#411]: https://github.com/qutip/QuantumToolbox.jl/issues/411 +[#413]: https://github.com/qutip/QuantumToolbox.jl/issues/413 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index a6f5389a9..6ac78d455 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -176,6 +176,8 @@ unit tensor ⊗ qeye +vector_to_operator +operator_to_vector sqrtm logm expm diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index 140779240..913f640af 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -314,7 +314,7 @@ Therefore, a given density matrix ``\hat{\rho}`` can then be vectorized, denoted |\hat{\rho}\rangle\rangle = \textrm{vec}(\hat{\rho}). ``` -`QuantumToolbox` uses the column-stacking convention for the isomorphism between ``\mathcal{L}(\mathcal{H})`` and ``\mathcal{H}\otimes\mathcal{H}``. This isomorphism is implemented by the functions [`mat2vec`](@ref) and [`vec2mat`](@ref): +`QuantumToolbox` uses the column-stacking convention for the isomorphism between ``\mathcal{L}(\mathcal{H})`` and ``\mathcal{H}\otimes\mathcal{H}``. This isomorphism is implemented by the functions [`mat2vec`](@ref) (or [`operator_to_vector`](@ref)) and [`vec2mat`](@ref) (or [`vector_to_operator`](@ref)): ```@example states_and_operators rho = Qobj([1 2; 3 4]) diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index e2c225762..1d602bd92 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -261,15 +261,23 @@ end @doc raw""" vec2mat(A::QuantumObject) + vector_to_operator(A::QuantumObject) Convert a quantum object from vector ([`OperatorKetQuantumObject`](@ref)-type) to matrix ([`OperatorQuantumObject`](@ref)-type) + +!!! note + `vector_to_operator` is a synonym of `vec2mat`. """ vec2mat(A::QuantumObject{OperatorKetQuantumObject}) = QuantumObject(vec2mat(A.data), Operator, A.dimensions) @doc raw""" mat2vec(A::QuantumObject) + operator_to_vector(A::QuantumObject) Convert a quantum object from matrix ([`OperatorQuantumObject`](@ref)-type) to vector ([`OperatorKetQuantumObject`](@ref)-type) + +!!! note + `operator_to_vector` is a synonym of `mat2vec`. """ mat2vec(A::QuantumObject{OperatorQuantumObject}) = QuantumObject(mat2vec(A.data), OperatorKet, A.dimensions) diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index aa5f72f26..6171e4211 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -6,6 +6,7 @@ export Qobj, QobjEvo, shape, isherm export trans, dag, matrix_element, unit export tensor, ⊗ export qeye +export vector_to_operator, operator_to_vector export sqrtm, logm, expm, sinm, cosm @doc raw""" @@ -52,6 +53,9 @@ const ⊗ = kron const qeye = eye +const vector_to_operator = vec2mat +const operator_to_vector = mat2vec + @doc raw""" sqrtm(A::QuantumObject) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index a03b96de3..2bc8648f3 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -125,10 +125,10 @@ H = 0.3 * sigmax() + 0.7 * sigmaz() L = liouvillian(H) ρ = Qobj(rand(ComplexF64, 2, 2)) - ρ_ket = mat2vec(ρ) + ρ_ket = operator_to_vector(ρ) ρ_bra = ρ_ket' - @test ρ_bra == Qobj(mat2vec(ρ.data)', type = OperatorBra) - @test ρ == vec2mat(ρ_ket) + @test ρ_bra == Qobj(operator_to_vector(ρ.data)', type = OperatorBra) + @test ρ == vector_to_operator(ρ_ket) @test isket(ρ_ket) == false @test isbra(ρ_ket) == false @test isoper(ρ_ket) == false From 46b98de36105478d87b81acd5de02e9d04837c5e Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:41:54 +0900 Subject: [PATCH 225/329] Introduce some entropy related functions (#416) --- CHANGELOG.md | 6 + docs/src/resources/api.md | 6 +- src/QuantumToolbox.jl | 1 + src/entropy.jl | 211 ++++++++++++++++++++++++++ src/metrics.jl | 76 +--------- src/qobj/arithmetic_and_attributes.jl | 10 +- src/qobj/functions.jl | 2 +- test/core-test/entanglement.jl | 13 -- test/core-test/entropy_and_metric.jl | 101 ++++++++++++ test/core-test/quantum_objects.jl | 42 +---- test/runtests.jl | 2 +- 11 files changed, 335 insertions(+), 135 deletions(-) create mode 100644 src/entropy.jl delete mode 100644 test/core-test/entanglement.jl create mode 100644 test/core-test/entropy_and_metric.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 2439ab0a6..d96870bfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback`. ([#410]) - Align `eigenstates` and `eigenenergies` to QuTiP. ([#411]) - Introduce `vector_to_operator` and `operator_to_vector`. ([#413]) +- Introduce some entropy related functions. ([#416]) + - `entropy_linear` + - `entropy_mutual` + - `entropy_conditional` + - `entropy_relative` ## [v0.27.0] Release date: 2025-02-14 @@ -149,3 +154,4 @@ Release date: 2024-11-13 [#410]: https://github.com/qutip/QuantumToolbox.jl/issues/410 [#411]: https://github.com/qutip/QuantumToolbox.jl/issues/411 [#413]: https://github.com/qutip/QuantumToolbox.jl/issues/413 +[#416]: https://github.com/qutip/QuantumToolbox.jl/issues/416 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 6ac78d455..4a5ead010 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -250,10 +250,14 @@ ExponentialSeries PseudoInverse ``` -## [Metrics](@id doc-API:Metrics) +## [Entropy and Metrics](@id doc-API:Entropy-and-Metrics) ```@docs entropy_vn +entropy_relative +entropy_linear +entropy_mutual +entropy_conditional entanglement tracedist fidelity diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index d06979dda..44b1aa84e 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -117,6 +117,7 @@ include("spectrum.jl") include("wigner.jl") include("spin_lattice.jl") include("arnoldi.jl") +include("entropy.jl") include("metrics.jl") include("negativity.jl") include("steadystate.jl") diff --git a/src/entropy.jl b/src/entropy.jl new file mode 100644 index 000000000..c9f2555ab --- /dev/null +++ b/src/entropy.jl @@ -0,0 +1,211 @@ +#= +Entropy related functions. +=# + +export entropy_vn, entropy_relative, entropy_linear, entropy_mutual, entropy_conditional +export entanglement + +@doc raw""" + entropy_vn(ρ::QuantumObject; base::Int=0, tol::Real=1e-15) + +Calculates the [Von Neumann entropy](https://en.wikipedia.org/wiki/Von_Neumann_entropy) ``S = - \textrm{Tr} \left[ \hat{\rho} \log \left( \hat{\rho} \right) \right]``, where ``\hat{\rho}`` is the density matrix of the system. + +# Notes + +- `ρ` is the quantum state, can be either a [`Ket`](@ref) or an [`Operator`](@ref). +- `base` specifies the base of the logarithm to use, and when using the default value `0`, the natural logarithm is used. +- `tol` describes the absolute tolerance for detecting the zero-valued eigenvalues of the density matrix ``\hat{\rho}``. + +# Examples + +Pure state: +```jldoctest +julia> ψ = fock(2,0) + +Quantum Object: type=Ket dims=[2] size=(2,) +2-element Vector{ComplexF64}: + 1.0 + 0.0im + 0.0 + 0.0im + +julia> entropy_vn(ψ, base=2) +-0.0 +``` + +Mixed state: +```jldoctest +julia> ρ = maximally_mixed_dm(2) + +Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +2×2 Diagonal{ComplexF64, Vector{ComplexF64}}: + 0.5-0.0im ⋅ + ⋅ 0.5-0.0im + +julia> entropy_vn(ρ, base=2) +1.0 +``` +""" +function entropy_vn( + ρ::QuantumObject{ObjType}; + base::Int = 0, + tol::Real = 1e-15, +) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject}} + T = eltype(ρ) + vals = eigenenergies(ket2dm(ρ)) + indexes = findall(x -> abs(x) > tol, vals) + length(indexes) == 0 && return zero(real(T)) + nzvals = vals[indexes] + logvals = base != 0 ? log.(base, Complex.(nzvals)) : log.(Complex.(nzvals)) + return -real(mapreduce(*, +, nzvals, logvals)) +end + +@doc raw""" + entropy_relative(ρ::QuantumObject, σ::QuantumObject; base::Int=0, tol::Real=1e-15) + +Calculates the [quantum relative entropy](https://en.wikipedia.org/wiki/Quantum_relative_entropy) of ``\hat{\rho}`` with respect to ``\hat{\sigma}``: ``D(\hat{\rho}||\hat{\sigma}) = \textrm{Tr} \left[ \hat{\rho} \log \left( \hat{\rho} \right) \right] - \textrm{Tr} \left[ \hat{\rho} \log \left( \hat{\sigma} \right) \right]``. + +# Notes + +- `ρ` is a quantum state, can be either a [`Ket`](@ref) or an [`Operator`](@ref). +- `σ` is a quantum state, can be either a [`Ket`](@ref) or an [`Operator`](@ref). +- `base` specifies the base of the logarithm to use, and when using the default value `0`, the natural logarithm is used. +- `tol` describes the absolute tolerance for detecting the zero-valued eigenvalues of the density matrix ``\hat{\rho}``. + +# References +- [Nielsen-Chuang2011; section 11.3.1, page 511](@citet) +""" +function entropy_relative( + ρ::QuantumObject{ObjType1}, + σ::QuantumObject{ObjType2}; + base::Int = 0, + tol::Real = 1e-15, +) where { + ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, + ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, +} + check_dimensions(ρ, σ) + + # the logic of this code follows the detail given in the reference of the docstring + # consider the eigen decompositions: + # ρ = Σ_i p_i |i⟩⟨i| + # σ = Σ_j q_j |j⟩⟨j| + ρ_result = eigenstates(ket2dm(ρ)) + σ_result = eigenstates(ket2dm(σ)) + + # make sure all p_i and q_j are real + any(p_i -> imag(p_i) >= tol, ρ_result.values) && error("Input `ρ` has non-real eigenvalues.") + any(q_j -> imag(q_j) >= tol, σ_result.values) && error("Input `σ` has non-real eigenvalues.") + p = real(ρ_result.values) + q = real(σ_result.values) + Uρ = ρ_result.vectors + Uσ = σ_result.vectors + + # create P_ij matrix (all elements should be real) + P = abs2.(Uρ' * Uσ) # this equals to ⟨i|j⟩⟨j|i⟩ + + # return +∞ if kernel of σ overlaps with support of ρ, i.e., supp(p) ⊆ supp(q) + # That is, if σ is not full rank, S(ρ||σ) = +∞ + # note that, one special case is that S(ρ||σ) = 0 (if ρ == σ) + ((transpose(p .>= tol) * (P .>= tol) * (q .< tol)) == 0) || return Inf + + # Avoid -∞ from log(0), these terms will be multiplied by zero later anyway + replace!(q_j -> abs(q_j) < tol ? 1 : q_j, q) + p_vals = filter(p_i -> abs(p_i) >= tol, p) + + if base == 0 + log_p = log.(p_vals) + log_q = log.(q) + else + log_p = log.(base, p_vals) + log_q = log.(base, q) + end + + # the relative entropy is guaranteed to be ≥ 0 + # so we calculate the value to 0 to avoid small violations of the lower bound. + return max(0.0, dot(p_vals, log_p) - dot(p, P, log_q)) +end + +@doc raw""" + entropy_linear(ρ::QuantumObject) + +Calculates the quantum linear entropy ``S_L = 1 - \textrm{Tr} \left[ \hat{\rho}^2 \right]``, where ``\hat{\rho}`` is the density matrix of the system. + +Note that `ρ` can be either a [`Ket`](@ref) or an [`Operator`](@ref). +""" +entropy_linear(ρ::QuantumObject{ObjType}) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject}} = + 1.0 - purity(ρ) # use 1.0 to make sure it always return value in Float-type + +@doc raw""" + entropy_mutual(ρAB::QuantumObject, selA, selB; kwargs...) + +Calculates the [quantum mutual information](https://en.wikipedia.org/wiki/Quantum_mutual_information) ``I(A:B) = S(\hat{\rho}_A) + S(\hat{\rho}_B) - S(\hat{\rho}_{AB})`` between subsystems ``A`` and ``B``. + +Here, ``S`` is the [Von Neumann entropy](https://en.wikipedia.org/wiki/Von_Neumann_entropy), ``\hat{\rho}_{AB}`` is the density matrix of the entire system, ``\hat{\rho}_A = \textrm{Tr}_B \left[ \hat{\rho}_{AB} \right]``, ``\hat{\rho}_B = \textrm{Tr}_A \left[ \hat{\rho}_{AB} \right]``. + +# Notes + +- `ρAB` can be either a [`Ket`](@ref) or an [`Operator`](@ref). +- `selA` specifies the indices of the sub-system `A` in `ρAB.dimensions`. See also [`ptrace`](@ref). +- `selB` specifies the indices of the sub-system `B` in `ρAB.dimensions`. See also [`ptrace`](@ref). +- `kwargs` are the keyword arguments for calculating Von Neumann entropy. See also [`entropy_vn`](@ref). +""" +function entropy_mutual( + ρAB::QuantumObject{ObjType,<:AbstractDimensions{N}}, + selA::Union{Int,AbstractVector{Int},Tuple}, + selB::Union{Int,AbstractVector{Int},Tuple}; + kwargs..., +) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject},N} + # check if selA and selB matches the dimensions of ρAB + sel_A_B = (selA..., selB...) + (length(sel_A_B) != N) && throw( + ArgumentError( + "The indices in `selA = $(selA)` and `selB = $(selB)` does not match the given QuantumObject which has $N sub-systems", + ), + ) + allunique(sel_A_B) || throw(ArgumentError("Duplicate selection indices in `selA = $(selA)` and `selB = $(selB)`")) + + ρA = ptrace(ρAB, selA) + ρB = ptrace(ρAB, selB) + return entropy_vn(ρA; kwargs...) + entropy_vn(ρB; kwargs...) - entropy_vn(ρAB; kwargs...) +end + +@doc raw""" + entropy_conditional(ρAB::QuantumObject, selB; kwargs...) + +Calculates the [conditional quantum entropy](https://en.wikipedia.org/wiki/Conditional_quantum_entropy) with respect to sub-system ``B``: ``S(A|B) = S(\hat{\rho}_{AB}) - S(\hat{\rho}_{B})``. + +Here, ``S`` is the [Von Neumann entropy](https://en.wikipedia.org/wiki/Von_Neumann_entropy), ``\hat{\rho}_{AB}`` is the density matrix of the entire system, and ``\hat{\rho}_B = \textrm{Tr}_A \left[ \hat{\rho}_{AB} \right]``. + +# Notes + +- `ρAB` can be either a [`Ket`](@ref) or an [`Operator`](@ref). +- `selB` specifies the indices of the sub-system `B` in `ρAB.dimensions`. See also [`ptrace`](@ref). +- `kwargs` are the keyword arguments for calculating Von Neumann entropy. See also [`entropy_vn`](@ref). +""" +entropy_conditional( + ρAB::QuantumObject{ObjType,<:AbstractDimensions{N}}, + selB::Union{Int,AbstractVector{Int},Tuple}; + kwargs..., +) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject},N} = + entropy_vn(ρAB; kwargs...) - entropy_vn(ptrace(ρAB, selB); kwargs...) + +@doc raw""" + entanglement(ρ::QuantumObject, sel; kwargs...) + +Calculates the [entanglement entropy](https://en.wikipedia.org/wiki/Entropy_of_entanglement) by doing the partial trace of `ρ`, selecting only the dimensions with the indices contained in the `sel` vector, and then use the Von Neumann entropy [`entropy_vn`](@ref). + +# Notes + +- `ρ` can be either a [`Ket`](@ref) or an [`Operator`](@ref). +- `sel` specifies the indices of the remaining sub-system. See also [`ptrace`](@ref). +- `kwargs` are the keyword arguments for calculating Von Neumann entropy. See also [`entropy_vn`](@ref). +""" +function entanglement( + ρ::QuantumObject{OpType}, + sel::Union{Int,AbstractVector{Int},Tuple}, + kwargs..., +) where {OpType<:Union{KetQuantumObject,OperatorQuantumObject}} + _ρ = normalize(ρ) + ρ_tr = ptrace(_ρ, sel) + val = entropy_vn(ρ_tr; kwargs...) + return (val > 0) * val +end diff --git a/src/metrics.jl b/src/metrics.jl index e178e579b..39ff1e3ad 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -2,81 +2,7 @@ Functions for calculating metrics (distance measures) between states and operators. =# -export entropy_vn, entanglement, tracedist, fidelity - -@doc raw""" - entropy_vn(ρ::QuantumObject; base::Int=0, tol::Real=1e-15) - -Calculates the [Von Neumann entropy](https://en.wikipedia.org/wiki/Von_Neumann_entropy) -``S = - \textrm{Tr} \left[ \hat{\rho} \log \left( \hat{\rho} \right) \right]`` where ``\hat{\rho}`` -is the density matrix of the system. - -The `base` parameter specifies the base of the logarithm to use, and when using the default value 0, -the natural logarithm is used. The `tol` parameter -describes the absolute tolerance for detecting the zero-valued eigenvalues of the density -matrix ``\hat{\rho}``. - -# Examples - -Pure state: -```jldoctest -julia> ψ = fock(2,0) - -Quantum Object: type=Ket dims=[2] size=(2,) -2-element Vector{ComplexF64}: - 1.0 + 0.0im - 0.0 + 0.0im - -julia> ρ = ket2dm(ψ) - -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true -2×2 Matrix{ComplexF64}: - 1.0+0.0im 0.0+0.0im - 0.0+0.0im 0.0+0.0im - -julia> entropy_vn(ρ, base=2) --0.0 -``` - -Mixed state: -```jldoctest -julia> ρ = maximally_mixed_dm(2) - -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true -2×2 Diagonal{ComplexF64, Vector{ComplexF64}}: - 0.5-0.0im ⋅ - ⋅ 0.5-0.0im - -julia> entropy_vn(ρ, base=2) -1.0 -``` -""" -function entropy_vn(ρ::QuantumObject{OperatorQuantumObject}; base::Int = 0, tol::Real = 1e-15) - T = eltype(ρ) - vals = eigenenergies(ρ) - indexes = findall(x -> abs(x) > tol, vals) - length(indexes) == 0 && return zero(real(T)) - nzvals = vals[indexes] - logvals = base != 0 ? log.(base, Complex.(nzvals)) : log.(Complex.(nzvals)) - return -real(mapreduce(*, +, nzvals, logvals)) -end - -""" - entanglement(QO::QuantumObject, sel::Union{Int,AbstractVector{Int},Tuple}) - -Calculates the entanglement by doing the partial trace of `QO`, selecting only the dimensions -with the indices contained in the `sel` vector, and then using the Von Neumann entropy [`entropy_vn`](@ref). -""" -function entanglement( - QO::QuantumObject{OpType}, - sel::Union{AbstractVector{Int},Tuple}, -) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} - ψ = normalize(QO) - ρ_tr = ptrace(ψ, sel) - entropy = entropy_vn(ρ_tr) - return (entropy > 0) * entropy -end -entanglement(QO::QuantumObject, sel::Int) = entanglement(QO, (sel,)) +export tracedist, fidelity @doc raw""" tracedist(ρ::QuantumObject, σ::QuantumObject) diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index add9248e8..2cf9503cc 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -568,8 +568,7 @@ Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true function ptrace(QO::QuantumObject{KetQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) _non_static_array_warning("sel", sel) - n_s = length(sel) - if n_s == 0 # return full trace for empty sel + if length(sel) == 0 # return full trace for empty sel return tr(ket2dm(QO)) else n_d = length(QO.dimensions) @@ -577,7 +576,7 @@ function ptrace(QO::QuantumObject{KetQuantumObject}, sel::Union{AbstractVector{I (any(>(n_d), sel) || any(<(1), sel)) && throw( ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(n_d) sub-systems"), ) - (n_s != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) + allunique(sel) || throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) (n_d == 1) && return ket2dm(QO) # ptrace should always return Operator end @@ -596,8 +595,7 @@ function ptrace(QO::QuantumObject{OperatorQuantumObject}, sel::Union{AbstractVec _non_static_array_warning("sel", sel) - n_s = length(sel) - if n_s == 0 # return full trace for empty sel + if length(sel) == 0 # return full trace for empty sel return tr(QO) else n_d = length(QO.dimensions) @@ -605,7 +603,7 @@ function ptrace(QO::QuantumObject{OperatorQuantumObject}, sel::Union{AbstractVec (any(>(n_d), sel) || any(<(1), sel)) && throw( ArgumentError("Invalid indices in `sel`: $(sel), the given QuantumObject only have $(n_d) sub-systems"), ) - (n_s != length(unique(sel))) && throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) + allunique(sel) || throw(ArgumentError("Duplicate selection indices in `sel`: $(sel)")) (n_d == 1) && return QO end diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 1d602bd92..4cb8d6c38 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -125,7 +125,7 @@ to_dense(::Type{T}, A::AbstractSparseArray) where {T<:Number} = Array{T}(A) to_dense(::Type{T1}, A::AbstractArray{T2}) where {T1<:Number,T2<:Number} = Array{T1}(A) to_dense(::Type{T}, A::AbstractArray{T}) where {T<:Number} = A -function to_dense(::Type{M}) where {M<:SparseMatrixCSC} +function to_dense(::Type{M}) where {M<:Union{Diagonal,SparseMatrixCSC}} T = M par = T.parameters npar = length(par) diff --git a/test/core-test/entanglement.jl b/test/core-test/entanglement.jl deleted file mode 100644 index c9df3e204..000000000 --- a/test/core-test/entanglement.jl +++ /dev/null @@ -1,13 +0,0 @@ -@testset "Entanglement" begin - g = fock(2, 1) - e = fock(2, 0) - state = normalize(kron(g, e) + kron(e, g)) - rho = state * state' - @test entanglement(state, 1) / log(2) ≈ 1 - @test entanglement(rho, 1) / log(2) ≈ 1 - - @testset "Type Stability (entanglement)" begin - @inferred entanglement(state, 1) - @inferred entanglement(rho, 1) - end -end diff --git a/test/core-test/entropy_and_metric.jl b/test/core-test/entropy_and_metric.jl new file mode 100644 index 000000000..5225c5ea2 --- /dev/null +++ b/test/core-test/entropy_and_metric.jl @@ -0,0 +1,101 @@ +@testset "entropy" begin + base = 2 + λ = rand() + ψ = rand_ket(10) + ρ1 = rand_dm(10) + ρ2 = rand_dm(10) + σ1 = rand_dm(10) + σ2 = rand_dm(10) + + dims = (2, 3) + ρAB = rand_dm((dims..., dims...)) + selA = (1, 2) + selB = (3, 4) + ρA = ptrace(ρAB, selA) + ρB = ptrace(ρAB, selB) + nA = nB = prod(dims) + IA = qeye(nA, dims = dims) + IB = qeye(nB, dims = dims) + + # quantum relative entropy + @test entropy_relative(ρ1, ψ) == Inf + @test entropy_relative(ρ1, rand_dm(10, rank = 9)) == Inf + @test entropy_relative(ψ, ψ) + 1 ≈ 1 + @test entropy_relative(λ * ρ1 + (1 - λ) * ρ2, λ * σ1 + (1 - λ) * σ2) <= + λ * entropy_relative(ρ1, σ1) + (1 - λ) * entropy_relative(ρ2, σ2) # joint convexity + + # relations between different entropies + @test entropy_relative(ρA, IA / nA) ≈ log(nA) - entropy_vn(ρA) + @test entropy_relative(ρB, IB / nB; base = base) ≈ log(base, nB) - entropy_vn(ρB; base = base) + @test entropy_relative(ρAB, tensor(ρA, ρB)) ≈ entropy_mutual(ρAB, selA, selB) + @test entropy_relative(ρAB, tensor(ρA, ρB)) ≈ entropy_mutual(ρAB, selA, selB) + @test entropy_relative(ρAB, tensor(ρA, IB / nB)) ≈ log(nB) - entropy_conditional(ρAB, selA) + @test entropy_linear(ρ1) == 1 - purity(ρ1) + + ρ_wrong = Qobj(rand(ComplexF64, 10, 10)) + @test_throws ErrorException entropy_relative(ρ1, ρ_wrong) + @test_throws ErrorException entropy_relative(ρ_wrong, ρ1) + @test_throws ArgumentError entropy_mutual(ρAB, 1, 3) + @test_throws ArgumentError entropy_mutual(ρAB, 1, (3, 4)) + @test_throws ArgumentError entropy_mutual(ρAB, (1, 2), 3) + @test_throws ArgumentError entropy_mutual(ρAB, (1, 2), (1, 3)) + + @testset "Type Stability (entropy)" begin + @inferred entropy_vn(ρ1) + @inferred entropy_vn(ρ1, base = base) + @inferred entropy_relative(ρ1, ψ) + @inferred entropy_relative(ρ1, σ1, base = base) + @inferred entropy_linear(ρ1) + @inferred entropy_mutual(ρAB, selA, selB) + @inferred entropy_conditional(ρAB, selA) + end +end + +@testset "Entanglement" begin + g = fock(2, 1) + e = fock(2, 0) + state = normalize(kron(g, e) + kron(e, g)) + rho = state * state' + @test entanglement(state, 1) / log(2) ≈ 1 + @test entanglement(rho, 1) / log(2) ≈ 1 + + @testset "Type Stability (entanglement)" begin + @inferred entanglement(state, 1) + @inferred entanglement(rho, 1) + end +end + +@testset "trace distance" begin + ψz0 = basis(2, 0) + ψz1 = basis(2, 1) + ρz0 = to_sparse(ket2dm(ψz0)) + ρz1 = to_sparse(ket2dm(ψz1)) + ψx0 = sqrt(0.5) * (basis(2, 0) + basis(2, 1)) + @test tracedist(ψz0, ψx0) ≈ sqrt(0.5) + @test tracedist(ρz0, ψz1) ≈ 1.0 + @test tracedist(ψz1, ρz0) ≈ 1.0 + @test tracedist(ρz0, ρz1) ≈ 1.0 + + @testset "Type Inference (trace distance)" begin + @inferred tracedist(ψz0, ψx0) + @inferred tracedist(ρz0, ψz1) + @inferred tracedist(ψz1, ρz0) + @inferred tracedist(ρz0, ρz1) + end +end + +@testset "fidelity" begin + M = sprand(ComplexF64, 5, 5, 0.5) + M0 = Qobj(M * M') + ψ1 = Qobj(rand(ComplexF64, 5)) + ψ2 = Qobj(rand(ComplexF64, 5)) + M1 = ψ1 * ψ1' + @test isapprox(fidelity(M0, M1), fidelity(ψ1, M0); atol = 1e-6) + @test isapprox(fidelity(ψ1, ψ2), fidelity(ket2dm(ψ1), ket2dm(ψ2)); atol = 1e-6) + + @testset "Type Inference (fidelity)" begin + @inferred fidelity(M0, M1) + @inferred fidelity(ψ1, M0) + @inferred fidelity(ψ1, ψ2) + end +end diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 2bc8648f3..8aebcc244 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -566,54 +566,20 @@ end end - @testset "trace distance" begin - ψz0 = basis(2, 0) - ψz1 = basis(2, 1) - ρz0 = to_sparse(ket2dm(ψz0)) - ρz1 = to_sparse(ket2dm(ψz1)) - ψx0 = sqrt(0.5) * (basis(2, 0) + basis(2, 1)) - @test tracedist(ψz0, ψx0) ≈ sqrt(0.5) - @test tracedist(ρz0, ψz1) ≈ 1.0 - @test tracedist(ψz1, ρz0) ≈ 1.0 - @test tracedist(ρz0, ρz1) ≈ 1.0 - - @testset "Type Inference (trace distance)" begin - @inferred tracedist(ψz0, ψx0) - @inferred tracedist(ρz0, ψz1) - @inferred tracedist(ψz1, ρz0) - @inferred tracedist(ρz0, ρz1) - end - end - - @testset "sqrt and fidelity" begin - M = sprand(ComplexF64, 5, 5, 0.5) - M0 = Qobj(M * M') - ψ1 = Qobj(rand(ComplexF64, 5)) - ψ2 = Qobj(rand(ComplexF64, 5)) - M1 = ψ1 * ψ1' - @test sqrtm(M0) ≈ sqrtm(to_dense(M0)) - @test isapprox(fidelity(M0, M1), fidelity(ψ1, M0); atol = 1e-6) - @test isapprox(fidelity(ψ1, ψ2), fidelity(ket2dm(ψ1), ket2dm(ψ2)); atol = 1e-6) - - @testset "Type Inference (sqrt and fidelity)" begin - @inferred sqrtm(M0) - @inferred fidelity(M0, M1) - @inferred fidelity(ψ1, M0) - @inferred fidelity(ψ1, ψ2) - end - end - - @testset "log, exp, sinm, cosm" begin + @testset "sqrt, log, exp, sinm, cosm" begin M0 = rand(ComplexF64, 4, 4) Md = Qobj(M0 * M0') Ms = to_sparse(Md) e_p = expm(1im * Md) e_m = expm(-1im * Md) + @test sqrtm(Md) ≈ sqrtm(Ms) @test logm(expm(Ms)) ≈ expm(logm(Md)) @test cosm(Ms) ≈ (e_p + e_m) / 2 @test sinm(Ms) ≈ (e_p - e_m) / 2im @testset "Type Inference" begin + @inferred sqrtm(Md) + @inferred sqrtm(Ms) @inferred expm(Md) @inferred expm(Ms) @inferred logm(Md) diff --git a/test/runtests.jl b/test/runtests.jl index 21d133dbe..aa768d718 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,7 +12,7 @@ core_tests = [ "dynamical_fock_dimension_mesolve.jl", "dynamical-shifted-fock.jl", "eigenvalues_and_operators.jl", - "entanglement.jl", + "entropy_and_metric.jl", "generalized_master_equation.jl", "low_rank_dynamics.jl", "negativity_and_partial_transpose.jl", From 71b8d8cbaaa190e2a0c2abf97fd663d51bade668 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 21 Feb 2025 05:58:34 +0900 Subject: [PATCH 226/329] Fix `entanglement` and introduce `concurrence` (#419) --- CHANGELOG.md | 6 +++- docs/src/resources/api.md | 3 +- src/entropy.jl | 49 ++++++++++++++++++++++++---- test/core-test/entropy_and_metric.jl | 40 ++++++++++++++++++----- 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d96870bfa..7226dbdc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback`. ([#410]) - Align `eigenstates` and `eigenenergies` to QuTiP. ([#411]) - Introduce `vector_to_operator` and `operator_to_vector`. ([#413]) -- Introduce some entropy related functions. ([#416]) +- Introduce some entropy related functions. ([#414], [#416]) - `entropy_linear` - `entropy_mutual` - `entropy_conditional` - `entropy_relative` +- Fix `entanglement` and introduce `concurrence`. ([#414], [#418], [#419]) ## [v0.27.0] Release date: 2025-02-14 @@ -154,4 +155,7 @@ Release date: 2024-11-13 [#410]: https://github.com/qutip/QuantumToolbox.jl/issues/410 [#411]: https://github.com/qutip/QuantumToolbox.jl/issues/411 [#413]: https://github.com/qutip/QuantumToolbox.jl/issues/413 +[#414]: https://github.com/qutip/QuantumToolbox.jl/issues/414 [#416]: https://github.com/qutip/QuantumToolbox.jl/issues/416 +[#418]: https://github.com/qutip/QuantumToolbox.jl/issues/418 +[#419]: https://github.com/qutip/QuantumToolbox.jl/issues/419 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 4a5ead010..f965ff24f 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -259,6 +259,8 @@ entropy_linear entropy_mutual entropy_conditional entanglement +concurrence +negativity tracedist fidelity ``` @@ -282,7 +284,6 @@ BlockDiagonalForm ```@docs wigner -negativity ``` ## [Linear Maps](@id doc-API:Linear-Maps) diff --git a/src/entropy.jl b/src/entropy.jl index c9f2555ab..59a992185 100644 --- a/src/entropy.jl +++ b/src/entropy.jl @@ -1,9 +1,9 @@ #= -Entropy related functions. +Entropy related functions and some entanglement measures. =# export entropy_vn, entropy_relative, entropy_linear, entropy_mutual, entropy_conditional -export entanglement +export entanglement, concurrence @doc raw""" entropy_vn(ρ::QuantumObject; base::Int=0, tol::Real=1e-15) @@ -121,7 +121,7 @@ function entropy_relative( # the relative entropy is guaranteed to be ≥ 0 # so we calculate the value to 0 to avoid small violations of the lower bound. - return max(0.0, dot(p_vals, log_p) - dot(p, P, log_q)) + return max(0.0, dot(p_vals, log_p) - dot(p, P, log_q)) # use 0.0 to make sure it always return value in Float-type end @doc raw""" @@ -195,7 +195,7 @@ Calculates the [entanglement entropy](https://en.wikipedia.org/wiki/Entropy_of_e # Notes -- `ρ` can be either a [`Ket`](@ref) or an [`Operator`](@ref). +- `ρ` can be either a [`Ket`](@ref) or an [`Operator`](@ref). But should be a pure state. - `sel` specifies the indices of the remaining sub-system. See also [`ptrace`](@ref). - `kwargs` are the keyword arguments for calculating Von Neumann entropy. See also [`entropy_vn`](@ref). """ @@ -204,8 +204,43 @@ function entanglement( sel::Union{Int,AbstractVector{Int},Tuple}, kwargs..., ) where {OpType<:Union{KetQuantumObject,OperatorQuantumObject}} - _ρ = normalize(ρ) - ρ_tr = ptrace(_ρ, sel) + p = purity(ρ) + isapprox(p, 1; atol = 1e-2) || throw( + ArgumentError( + "The entanglement entropy only works for normalized pure state, the purity of the given state: $(p) ≉ 1", + ), + ) + + ρ_tr = ptrace(ρ, sel) val = entropy_vn(ρ_tr; kwargs...) - return (val > 0) * val + return max(0.0, val) # use 0.0 to make sure it always return value in Float-type +end + +@doc raw""" + concurrence(ρ::QuantumObject) + +Calculate the [concurrence](https://en.wikipedia.org/wiki/Concurrence_(quantum_computing)) for a two-qubit state. + +# Notes + +- `ρ` can be either a [`Ket`](@ref) or an [`Operator`](@ref). +""" +function concurrence(ρ::QuantumObject{OpType}) where {OpType<:Union{KetQuantumObject,OperatorQuantumObject}} + (ρ.dimensions == Dimensions((Space(2), Space(2)))) || throw( + ArgumentError( + "The `concurrence` only works for a two-qubit state, invalid dims = $(_get_dims_string(ρ.dimensions)).", + ), + ) + + _ρ = ket2dm(ρ).data + σy = sigmay() + σyσy = kron(σy, σy).data + ρ_tilde = σyσy * conj(_ρ) * σyσy + + # we use the alternative way to calculate concurrence (more efficient) + # calculate the square root of each eigenvalues (in decreasing order) of the non-Hermitian matrix: ρ * ρ_tilde + # note that we add abs here to avoid problems with sqrt for very small negative numbers + λ = sqrt.(abs.(real(eigvals(_ρ * ρ_tilde; sortby = x -> -real(x))))) + + return max(0.0, λ[1] - λ[2] - λ[3] - λ[4]) # use 0.0 to make sure it always return value in Float-type end diff --git a/test/core-test/entropy_and_metric.jl b/test/core-test/entropy_and_metric.jl index 5225c5ea2..746a491e7 100644 --- a/test/core-test/entropy_and_metric.jl +++ b/test/core-test/entropy_and_metric.jl @@ -51,17 +51,39 @@ end end -@testset "Entanglement" begin - g = fock(2, 1) - e = fock(2, 0) - state = normalize(kron(g, e) + kron(e, g)) - rho = state * state' - @test entanglement(state, 1) / log(2) ≈ 1 - @test entanglement(rho, 1) / log(2) ≈ 1 +@testset "entanglement and concurrence" begin + # bell state + ψb = bell_state(Val(1), Val(0)) + ρb = ket2dm(ψb) + @test entanglement(ψb, 1) / log(2) ≈ 1 + @test entanglement(ρb, 1) / log(2) ≈ 1 + @test concurrence(ψb) ≈ 1 + @test concurrence(ρb) ≈ 1 + + # separable pure state + ψs = kron(rand_ket(2), rand_ket(2)) + @test entanglement(ψs, 1) + 1 ≈ 1 + @test entanglement(ψs, 2) + 1 ≈ 1 + @test concurrence(ψs) + 1 ≈ 1 + + # this only works for "pure" two-qubit states + ψr = rand_ket((2, 2)) # might be an entangled two-qubit state + val = concurrence(ψr) + @test isapprox(val, sqrt(2 * entropy_linear(ptrace(ψr, 1))); atol = 1e-5) # √(2 * (1 - Tr(ρA^2))) + @test isapprox(val, sqrt(2 * entropy_linear(ptrace(ψr, 2))); atol = 1e-5) # √(2 * (1 - Tr(ρB^2))) + + @test_throws ArgumentError entanglement(rand_dm((2, 2)), 1) + @test_throws ArgumentError concurrence(rand_dm((2, 3))) + @test_throws ArgumentError concurrence(rand_dm(4)) @testset "Type Stability (entanglement)" begin - @inferred entanglement(state, 1) - @inferred entanglement(rho, 1) + @inferred entanglement(ψb, 1) + @inferred entanglement(ρb, 1) + end + + @testset "Type Stability (concurrence)" begin + @inferred concurrence(ψb) + @inferred concurrence(ρb) end end From 6e14238233b8db30a3d080faad920739aeadf74b Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:04:25 +0900 Subject: [PATCH 227/329] Introduce some metric functions (#420) --- CHANGELOG.md | 6 ++ docs/src/resources/api.md | 6 +- docs/src/resources/bibliography.bib | 23 +++++ docs/src/users_guide/states_and_operators.md | 2 +- src/metrics.jl | 101 ++++++++++++++++--- test/core-test/entropy_and_metric.jl | 69 +++++++++++-- 6 files changed, 186 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7226dbdc2..e47df2f05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `entropy_conditional` - `entropy_relative` - Fix `entanglement` and introduce `concurrence`. ([#414], [#418], [#419]) +- Introduce some metric functions. ([#414], [#420]) + - `hilbert_dist` + - `hellinger_dist` + - `bures_dist` + - `bures_angle` ## [v0.27.0] Release date: 2025-02-14 @@ -159,3 +164,4 @@ Release date: 2024-11-13 [#416]: https://github.com/qutip/QuantumToolbox.jl/issues/416 [#418]: https://github.com/qutip/QuantumToolbox.jl/issues/418 [#419]: https://github.com/qutip/QuantumToolbox.jl/issues/419 +[#420]: https://github.com/qutip/QuantumToolbox.jl/issues/420 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index f965ff24f..bb3e0b361 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -261,8 +261,12 @@ entropy_conditional entanglement concurrence negativity -tracedist fidelity +tracedist +hilbert_dist +hellinger_dist +bures_dist +bures_angle ``` ## [Spin Lattice](@id doc-API:Spin-Lattice) diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index e8386a703..2f55bbf57 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -81,3 +81,26 @@ @book{Wiseman2009Quantum year={2009}, month=nov } + +@article{Vedral-Plenio1998, + title = {Entanglement measures and purification procedures}, + author = {Vedral, V. and Plenio, M. B.}, + journal = {Phys. Rev. A}, + volume = {57}, + issue = {3}, + pages = {1619--1633}, + numpages = {0}, + year = {1998}, + month = {Mar}, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevA.57.1619}, + url = {https://link.aps.org/doi/10.1103/PhysRevA.57.1619} +} + +@article{Spehner2017, + title={Geometric measures of quantum correlations with Bures and Hellinger distances}, + author={D. Spehner and F. Illuminati and M. Orszag and W. Roga}, + year={2017}, + journal={arXiv:1611.03449}, + url={https://arxiv.org/abs/1611.03449}, +} diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index 913f640af..f52c1e978 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -161,7 +161,7 @@ coherent_dm(5, 1.25) thermal_dm(5, 1.25) ``` -`QuantumToolbox` also provides a set of distance metrics for determining how close two density matrix distributions are to each other. Included are the [`fidelity`](@ref), and trace distance ([`tracedist`](@ref)). +`QuantumToolbox` also provides a set of distance metrics for determining how close two density matrix distributions are to each other. For example, [`fidelity`](@ref), and trace distance ([`tracedist`](@ref)) are included. For more metric functions, see section [Entropy and Metrics](@ref doc-API:Entropy-and-Metrics) in the API page. ```@example states_and_operators x = coherent_dm(5, 1.25) diff --git a/src/metrics.jl b/src/metrics.jl index 39ff1e3ad..d9462682f 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -2,7 +2,28 @@ Functions for calculating metrics (distance measures) between states and operators. =# -export tracedist, fidelity +export fidelity +export tracedist, hilbert_dist, hellinger_dist +export bures_dist, bures_angle + +@doc raw""" + fidelity(ρ::QuantumObject, σ::QuantumObject) + +Calculate the fidelity of two [`QuantumObject`](@ref): +``F(\hat{\rho}, \hat{\sigma}) = \textrm{Tr} \sqrt{\sqrt{\hat{\rho}} \hat{\sigma} \sqrt{\hat{\rho}}}`` + +Here, the definition is from [Nielsen-Chuang2011](@citet). It is the square root of the fidelity defined in [Jozsa1994](@citet). + +Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). +""" +function fidelity(ρ::QuantumObject{OperatorQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) + sqrt_ρ = sqrt(ρ) + eigval = abs.(eigvals(sqrt_ρ * σ * sqrt_ρ)) + return sum(sqrt, eigval) +end +fidelity(ρ::QuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) = sqrt(abs(expect(ρ, ψ))) +fidelity(ψ::QuantumObject{KetQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) = fidelity(σ, ψ) +fidelity(ψ::QuantumObject{KetQuantumObject}, ϕ::QuantumObject{KetQuantumObject}) = abs(dot(ψ, ϕ)) @doc raw""" tracedist(ρ::QuantumObject, σ::QuantumObject) @@ -21,20 +42,76 @@ tracedist( } = norm(ket2dm(ρ) - ket2dm(σ), 1) / 2 @doc raw""" - fidelity(ρ::QuantumObject, σ::QuantumObject) + hilbert_dist(ρ::QuantumObject, σ::QuantumObject) -Calculate the fidelity of two [`QuantumObject`](@ref): -``F(\hat{\rho}, \hat{\sigma}) = \textrm{Tr} \sqrt{\sqrt{\hat{\rho}} \hat{\sigma} \sqrt{\hat{\rho}}}`` +Calculates the Hilbert-Schmidt distance between two [`QuantumObject`](@ref): +``D_{HS}(\hat{\rho}, \hat{\sigma}) = \textrm{Tr}\left[\hat{A}^\dagger \hat{A}\right]``, where ``\hat{A} = \hat{\rho} - \hat{\sigma}``. + +Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). + +# References +- [Vedral-Plenio1998](@citet) +""" +function hilbert_dist( + ρ::QuantumObject{ObjType1}, + σ::QuantumObject{ObjType2}, +) where { + ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, + ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, +} + check_dimensions(ρ, σ) + + A = ket2dm(ρ) - ket2dm(σ) + return tr(A' * A) +end + +@doc raw""" + hellinger_dist(ρ::QuantumObject, σ::QuantumObject) -Here, the definition is from Nielsen & Chuang, "Quantum Computation and Quantum Information". It is the square root of the fidelity defined in R. Jozsa, Journal of Modern Optics, 41:12, 2315 (1994). +Calculates the [Hellinger distance](https://en.wikipedia.org/wiki/Hellinger_distance) between two [`QuantumObject`](@ref): +``D_H(\hat{\rho}, \hat{\sigma}) = \sqrt{2 - 2 \textrm{Tr}\left(\sqrt{\hat{\rho}}\sqrt{\hat{\sigma}}\right)}`` Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). + +# References +- [Spehner2017](@citet) """ -function fidelity(ρ::QuantumObject{OperatorQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) - sqrt_ρ = sqrt(ρ) - eigval = abs.(eigvals(sqrt_ρ * σ * sqrt_ρ)) - return sum(sqrt, eigval) +function hellinger_dist( + ρ::QuantumObject{ObjType1}, + σ::QuantumObject{ObjType2}, +) where { + ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, + ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, +} + # Ket (pure state) doesn't need to do square root + sqrt_ρ = isket(ρ) ? ket2dm(ρ) : sqrt(ρ) + sqrt_σ = isket(σ) ? ket2dm(σ) : sqrt(σ) + + # `max` is to avoid numerical instabilities + # it happens when ρ = σ, sum(eigvals) might be slightly larger than 1 + return sqrt(2.0 * max(0.0, 1.0 - sum(real, eigvals(sqrt_ρ * sqrt_σ)))) end -fidelity(ρ::QuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) = sqrt(abs(expect(ρ, ψ))) -fidelity(ψ::QuantumObject{KetQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) = fidelity(σ, ψ) -fidelity(ψ::QuantumObject{KetQuantumObject}, ϕ::QuantumObject{KetQuantumObject}) = abs(dot(ψ, ϕ)) + +@doc raw""" + bures_dist(ρ::QuantumObject, σ::QuantumObject) + +Calculate the [Bures distance](https://en.wikipedia.org/wiki/Bures_metric) between two [`QuantumObject`](@ref): +``D_B(\hat{\rho}, \hat{\sigma}) = \sqrt{2 \left(1 - F(\hat{\rho}, \hat{\sigma}) \right)}`` + +Here, the definition of [`fidelity`](@ref) ``F`` is from [Nielsen-Chuang2011](@citet). It is the square root of the fidelity defined in [Jozsa1994](@citet). + +Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). +""" +bures_dist(ρ::QuantumObject, σ::QuantumObject) = sqrt(2 * (1 - fidelity(ρ, σ))) + +@doc raw""" + bures_angle(ρ::QuantumObject, σ::QuantumObject) + +Calculate the [Bures angle](https://en.wikipedia.org/wiki/Bures_metric) between two [`QuantumObject`](@ref): +``D_A(\hat{\rho}, \hat{\sigma}) = \arccos\left(F(\hat{\rho}, \hat{\sigma})\right)`` + +Here, the definition of [`fidelity`](@ref) ``F`` is from [Nielsen-Chuang2011](@citet). It is the square root of the fidelity defined in [Jozsa1994](@citet). + +Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). +""" +bures_angle(ρ::QuantumObject, σ::QuantumObject) = acos(fidelity(ρ, σ)) diff --git a/test/core-test/entropy_and_metric.jl b/test/core-test/entropy_and_metric.jl index 746a491e7..b90a059f7 100644 --- a/test/core-test/entropy_and_metric.jl +++ b/test/core-test/entropy_and_metric.jl @@ -87,7 +87,7 @@ end end end -@testset "trace distance" begin +@testset "trace and Hilbert-Schmidt distance" begin ψz0 = basis(2, 0) ψz1 = basis(2, 1) ρz0 = to_sparse(ket2dm(ψz0)) @@ -98,26 +98,81 @@ end @test tracedist(ψz1, ρz0) ≈ 1.0 @test tracedist(ρz0, ρz1) ≈ 1.0 + ψ = rand_ket(10) + ϕ = rand_ket(10) + @test isapprox(tracedist(ψ, ϕ)^2, hilbert_dist(ψ, ϕ) / 2; atol = 1e-6) + @testset "Type Inference (trace distance)" begin @inferred tracedist(ψz0, ψx0) @inferred tracedist(ρz0, ψz1) @inferred tracedist(ψz1, ρz0) @inferred tracedist(ρz0, ρz1) end + + @testset "Type Inference (Hilbert-Schmidt distance)" begin + @inferred hilbert_dist(ψz0, ψx0) + @inferred hilbert_dist(ρz0, ψz1) + @inferred hilbert_dist(ψz1, ρz0) + @inferred hilbert_dist(ρz0, ρz1) + end end -@testset "fidelity" begin - M = sprand(ComplexF64, 5, 5, 0.5) - M0 = Qobj(M * M') - ψ1 = Qobj(rand(ComplexF64, 5)) - ψ2 = Qobj(rand(ComplexF64, 5)) - M1 = ψ1 * ψ1' +@testset "fidelity, Bures metric, and Hellinger distance" begin + M0 = rand_dm(5) + ψ1 = rand_ket(5) + ψ2 = rand_ket(5) + M1 = ket2dm(ψ1) + b00 = bell_state(Val(0), Val(0)) + b01 = bell_state(Val(0), Val(1)) @test isapprox(fidelity(M0, M1), fidelity(ψ1, M0); atol = 1e-6) @test isapprox(fidelity(ψ1, ψ2), fidelity(ket2dm(ψ1), ket2dm(ψ2)); atol = 1e-6) + @test isapprox(fidelity(b00, b00), 1; atol = 1e-6) + @test isapprox(bures_dist(b00, b00) + 1, 1; atol = 1e-6) + @test isapprox(bures_angle(b00, b00) + 1, 1; atol = 1e-6) + @test isapprox(hellinger_dist(b00, b00) + 1, 1; atol = 1e-6) + @test isapprox(fidelity(b00, b01) + 1, 1; atol = 1e-6) + @test isapprox(bures_dist(b00, b01), √2; atol = 1e-6) + @test isapprox(bures_angle(b00, b01), π / 2; atol = 1e-6) + @test isapprox(hellinger_dist(b00, b01), √2; atol = 1e-6) + + # some relations between Bures and Hellinger dintances + # together with some monotonicity under tensor products + # [see arXiv:1611.03449 (2017); section 4.2] + ρA = rand_dm(5) + ρB = rand_dm(6) + ρAB = tensor(ρA, ρB) + σA = rand_dm(5) + σB = rand_dm(6) + σAB = tensor(σA, σB) + d_Bu_A = bures_dist(ρA, σA) + d_Bu_AB = bures_dist(ρAB, σAB) + d_He_A = hellinger_dist(ρA, σA) + d_He_AB = hellinger_dist(ρAB, σAB) + @test isapprox(fidelity(ρAB, σAB), fidelity(ρA, σA) * fidelity(ρB, σB); atol = 1e-6) + @test d_He_AB >= d_Bu_AB + @test d_Bu_AB >= d_Bu_A + @test isapprox(bures_dist(ρAB, tensor(σA, ρB)), d_Bu_A; atol = 1e-6) + @test d_He_AB >= d_He_A + @test isapprox(hellinger_dist(ρAB, tensor(σA, ρB)), d_He_A; atol = 1e-6) @testset "Type Inference (fidelity)" begin @inferred fidelity(M0, M1) @inferred fidelity(ψ1, M0) @inferred fidelity(ψ1, ψ2) end + + @testset "Type Inference (Hellinger distance)" begin + @inferred hellinger_dist(M0, M1) + @inferred hellinger_dist(ψ1, M0) + @inferred hellinger_dist(ψ1, ψ2) + end + + @testset "Type Inference (Bures metric)" begin + @inferred bures_dist(M0, M1) + @inferred bures_dist(ψ1, M0) + @inferred bures_dist(ψ1, ψ2) + @inferred bures_angle(M0, M1) + @inferred bures_angle(ψ1, M0) + @inferred bures_angle(ψ1, ψ2) + end end From 2d352f9012c94bb96f82fa3804f3ef3b39b4a266 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 22 Feb 2025 02:59:50 +0100 Subject: [PATCH 228/329] Align `steadystate` ODE solver and improve GPU support (#421) --- CHANGELOG.md | 2 + docs/src/users_guide/steadystate.md | 4 - ext/QuantumToolboxCUDAExt.jl | 4 + src/steadystate.jl | 129 ++++++++++++---------------- src/utilities.jl | 6 +- test/core-test/steady_state.jl | 9 +- test/ext-test/gpu/cuda_ext.jl | 25 ++++++ 7 files changed, 94 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e47df2f05..fb2d91e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `hellinger_dist` - `bures_dist` - `bures_angle` +- Align `steadystate` ODE solver to other methods and improve GPU support. ([#421]) ## [v0.27.0] Release date: 2025-02-14 @@ -165,3 +166,4 @@ Release date: 2024-11-13 [#418]: https://github.com/qutip/QuantumToolbox.jl/issues/418 [#419]: https://github.com/qutip/QuantumToolbox.jl/issues/419 [#420]: https://github.com/qutip/QuantumToolbox.jl/issues/420 +[#421]: https://github.com/qutip/QuantumToolbox.jl/issues/421 diff --git a/docs/src/users_guide/steadystate.md b/docs/src/users_guide/steadystate.md index bd68d5a58..a74353b90 100644 --- a/docs/src/users_guide/steadystate.md +++ b/docs/src/users_guide/steadystate.md @@ -37,10 +37,6 @@ To change a solver, use the keyword argument `solver`, for example: ρ_ss = steadystate(H, c_ops; solver = SteadyStateLinearSolver()) ``` -!!! note "Initial state for `SteadyStateODESolver()`" - It is necessary to provide the initial state `ψ0` for ODE solving method, namely - `steadystate(H, ψ0, tspan, c_ops, solver = SteadyStateODESolver())`, where `tspan::Real` represents the final time step, defaults to `Inf` (infinity). - Although it is not obvious, the [`SteadyStateDirectSolver()`](@ref SteadyStateDirectSolver) and [`SteadyStateEigenSolver()`](@ref SteadyStateEigenSolver) methods all use an LU decomposition internally and thus can have a large memory overhead. In contrast, for [`SteadyStateLinearSolver()`](@ref SteadyStateLinearSolver), iterative algorithms provided by [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/solvers/solvers/), such as `KrylovJL_GMRES()` and `KrylovJL_BICGSTAB()`, do not factor the matrix and thus take less memory than the LU methods and allow, in principle, for extremely large system sizes. The downside is that these methods can take much longer than the direct method as the condition number of the Liouvillian matrix is large, indicating that these iterative methods require a large number of iterations for convergence. To overcome this, one can provide preconditioner that solves for an approximate inverse for the (modified) Liouvillian, thus better conditioning the problem, leading to faster convergence. The left and right preconditioner can be specified as the keyword argument `Pl` and `Pr`, respectively: diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index 70168b2b1..b4fcf0fc2 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -2,6 +2,7 @@ module QuantumToolboxCUDAExt using QuantumToolbox using QuantumToolbox: makeVal, getVal +import QuantumToolbox: _sparse_similar import CUDA: cu, CuArray, allowscalar import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR, AbstractCuSparseArray import SparseArrays: SparseVector, SparseMatrixCSC @@ -106,4 +107,7 @@ QuantumToolbox.to_dense(A::MT) where {MT<:AbstractCuSparseArray} = CuArray(A) QuantumToolbox.to_dense(::Type{T1}, A::CuArray{T2}) where {T1<:Number,T2<:Number} = CuArray{T1}(A) QuantumToolbox.to_dense(::Type{T}, A::AbstractCuSparseArray) where {T<:Number} = CuArray{T}(A) +QuantumToolbox._sparse_similar(A::CuSparseMatrixCSC, args...) = sparse(args..., fmt = :csc) +QuantumToolbox._sparse_similar(A::CuSparseMatrixCSR, args...) = sparse(args..., fmt = :csr) + end diff --git a/src/steadystate.jl b/src/steadystate.jl index e0296954a..921c7b398 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -27,7 +27,7 @@ struct SteadyStateEigenSolver <: SteadyStateSolver end A solver which solves [`steadystate`](@ref) by finding the inverse of Liouvillian [`SuperOperator`](@ref) using the `alg`orithms given in [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/). -# Parameters +# Arguments - `alg::SciMLLinearSolveAlgorithm=KrylovJL_GMRES()`: algorithms given in [`LinearSolve.jl`](https://docs.sciml.ai/LinearSolve/stable/) - `Pl::Union{Function,Nothing}=nothing`: left preconditioner, see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for more details. - `Pr::Union{Function,Nothing}=nothing`: right preconditioner, see documentation [Solving for Steady-State Solutions](@ref doc:Solving-for-Steady-State-Solutions) for more details. @@ -43,16 +43,40 @@ Base.@kwdef struct SteadyStateLinearSolver{ end @doc raw""" - SteadyStateODESolver(alg = Tsit5()) + SteadyStateODESolver( + alg = Tsit5(), + ψ0 = nothing, + tmax = Inf, + ) An ordinary differential equation (ODE) solver for solving [`steadystate`](@ref). -It includes a field (attribute) `SteadyStateODESolver.alg` that specifies the solving algorithm. Default to `Tsit5()`. +Solve the stationary state based on time evolution (ordinary differential equations; `OrdinaryDiffEq.jl`) with a given initial state. + +The termination condition of the stationary state ``|\rho\rangle\rangle`` is that either the following condition is `true`: + +```math +\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{reltol} \times\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}+|\hat{\rho}\rangle\rangle\rVert +``` + +or -For more details about the solvers, please refer to [`OrdinaryDiffEq.jl`](https://docs.sciml.ai/OrdinaryDiffEq/stable/) +```math +\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{abstol} +``` + +# Arguments +- `alg::OrdinaryDiffEqAlgorithm=Tsit5()`: The algorithm to solve the ODE. +- `ψ0::Union{Nothing,QuantumObject}=nothing`: The initial state of the system. If not specified, a random pure state will be generated. +- `tmax::Real=Inf`: The final time step for the steady state problem. + +For more details about the solvers, please refer to [`OrdinaryDiffEq.jl`](https://docs.sciml.ai/OrdinaryDiffEq/stable/). """ -Base.@kwdef struct SteadyStateODESolver{MT<:OrdinaryDiffEqAlgorithm} <: SteadyStateSolver +Base.@kwdef struct SteadyStateODESolver{MT<:OrdinaryDiffEqAlgorithm,ST<:Union{Nothing,QuantumObject},T<:Real} <: + SteadyStateSolver alg::MT = Tsit5() + ψ0::ST = nothing + tmax::T = Inf end @doc raw""" @@ -108,18 +132,18 @@ function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::Stea N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) - v0 = _get_dense_similar(L_tmp, N^2) + v0 = _dense_similar(L_tmp, N^2) fill!(v0, 0) allowed_setindex!(v0, weight, 1) # Because scalar indexing is not allowed on GPU arrays idx_range = collect(1:N) - rows = _get_dense_similar(L_tmp, N) - cols = _get_dense_similar(L_tmp, N) - vals = _get_dense_similar(L_tmp, N) + rows = _dense_similar(L_tmp, N) + cols = _dense_similar(L_tmp, N) + vals = _dense_similar(L_tmp, N) fill!(rows, 1) copyto!(cols, N .* (idx_range .- 1) .+ idx_range) fill!(vals, weight) - Tn = sparse(rows, cols, vals, N^2, N^2) + Tn = _sparse_similar(L_tmp, rows, cols, vals, N^2, N^2) L_tmp = L_tmp + Tn (haskey(kwargs, :Pl) || haskey(kwargs, :Pr)) && error("The use of preconditioners must be defined in the solver.") @@ -155,14 +179,14 @@ function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::Stea N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) - v0 = _get_dense_similar(L_tmp, N^2) + v0 = _dense_similar(L_tmp, N^2) fill!(v0, 0) allowed_setindex!(v0, weight, 1) # Because scalar indexing is not allowed on GPU arrays idx_range = collect(1:N) - rows = _get_dense_similar(L_tmp, N) - cols = _get_dense_similar(L_tmp, N) - vals = _get_dense_similar(L_tmp, N) + rows = _dense_similar(L_tmp, N) + cols = _dense_similar(L_tmp, N) + vals = _dense_similar(L_tmp, N) fill!(rows, 1) copyto!(cols, N .* (idx_range .- 1) .+ idx_range) fill!(vals, weight) @@ -175,68 +199,20 @@ function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::Stea return QuantumObject(ρss, Operator, L.dimensions) end -_steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateODESolver; kwargs...) = throw( - ArgumentError( - "The initial state ψ0 is required for SteadyStateODESolver, use the following call instead: `steadystate(H, ψ0, tmax, c_ops)`.", - ), -) +function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateODESolver; kwargs...) + tmax = solver.tmax -@doc raw""" - steadystate( - H::QuantumObject{HOpType}, - ψ0::QuantumObject{StateOpType}, - tmax::Real = Inf, - c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - solver::SteadyStateODESolver = SteadyStateODESolver(), - reltol::Real = 1.0e-8, - abstol::Real = 1.0e-10, - kwargs..., - ) - -Solve the stationary state based on time evolution (ordinary differential equations; `OrdinaryDiffEq.jl`) with a given initial state. + ψ0 = isnothing(solver.ψ0) ? rand_ket(L.dimensions) : solver.ψ0 + abstol = haskey(kwargs, :abstol) ? kwargs[:abstol] : DEFAULT_ODE_SOLVER_OPTIONS.abstol + reltol = haskey(kwargs, :reltol) ? kwargs[:reltol] : DEFAULT_ODE_SOLVER_OPTIONS.reltol -The termination condition of the stationary state ``|\rho\rangle\rangle`` is that either the following condition is `true`: - -```math -\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{reltol} \times\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}+|\hat{\rho}\rangle\rangle\rVert -``` - -or - -```math -\lVert\frac{\partial |\hat{\rho}\rangle\rangle}{\partial t}\rVert \leq \textrm{abstol} -``` - -# Parameters -- `H`: The Hamiltonian or the Liouvillian of the system. -- `ψ0`: The initial state of the system. -- `tmax=Inf`: The final time step for the steady state problem. -- `c_ops=nothing`: The list of the collapse operators. -- `solver`: see [`SteadyStateODESolver`](@ref) for more details. -- `reltol=1.0e-8`: Relative tolerance in steady state terminate condition and solver adaptive timestepping. -- `abstol=1.0e-10`: Absolute tolerance in steady state terminate condition and solver adaptive timestepping. -- `kwargs`: The keyword arguments for the ODEProblem. -""" -function steadystate( - H::QuantumObject{HOpType}, - ψ0::QuantumObject{StateOpType}, - tmax::Real = Inf, - c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; - solver::SteadyStateODESolver = SteadyStateODESolver(), - reltol::Real = 1.0e-8, - abstol::Real = 1.0e-10, - kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} ftype = _FType(ψ0) - cb = TerminateSteadyState(abstol, reltol, _steadystate_ode_condition) + _terminate_func = SteadyStateODECondition(similar(mat2vec(ket2dm(ψ0)).data)) + cb = TerminateSteadyState(abstol, reltol, _terminate_func) sol = mesolve( - H, + L, ψ0, [ftype(0), ftype(tmax)], - c_ops, progress_bar = Val(false), save_everystep = false, saveat = ftype[], @@ -247,12 +223,17 @@ function steadystate( return ρss end -function _steadystate_ode_condition(integrator, abstol, reltol, min_t) +struct SteadyStateODECondition{CT<:AbstractArray} + cache::CT +end + +function (f::SteadyStateODECondition)(integrator, abstol, reltol, min_t) # this condition is same as DiffEqBase.NormTerminationMode - du_dt = (integrator.u - integrator.uprev) / integrator.dt - norm_du_dt = norm(du_dt) - if (norm_du_dt <= reltol * norm(du_dt + integrator.u)) || (norm_du_dt <= abstol) + f.cache .= (integrator.u .- integrator.uprev) ./ integrator.dt + norm_du_dt = norm(f.cache) + f.cache .+= integrator.u + if norm_du_dt <= reltol * norm(f.cache) || norm_du_dt <= abstol return true else return false diff --git a/src/utilities.jl b/src/utilities.jl index 47b73f525..0a660995f 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -130,8 +130,10 @@ end get_typename_wrapper(A) = Base.typename(typeof(A)).wrapper -_get_dense_similar(A::AbstractArray, args...) = similar(A, args...) -_get_dense_similar(A::AbstractSparseMatrix, args...) = similar(nonzeros(A), args...) +_dense_similar(A::AbstractArray, args...) = similar(A, args...) +_dense_similar(A::AbstractSparseMatrix, args...) = similar(nonzeros(A), args...) + +_sparse_similar(A::AbstractArray, args...) = sparse(args...) _Ginibre_ensemble(n::Int, rank::Int = n) = randn(ComplexF64, n, rank) / sqrt(n) diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index de33b2ad1..795ef7d5a 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -11,9 +11,8 @@ rho_me = sol_me.states[end] solver = SteadyStateODESolver() - ρ_ss = steadystate(H, psi0, t_l[end], c_ops, solver = solver) + ρ_ss = steadystate(H, c_ops, solver = solver) @test tracedist(rho_me, ρ_ss) < 1e-4 - @test_throws ArgumentError steadystate(H, c_ops, solver = solver) solver = SteadyStateDirectSolver() ρ_ss = steadystate(H, c_ops, solver = solver) @@ -34,9 +33,9 @@ @testset "Type Inference (steadystate)" begin L = liouvillian(H, c_ops) - solver = SteadyStateODESolver() - @inferred steadystate(H, psi0, t_l[end], c_ops, solver = solver) - @inferred steadystate(L, psi0, t_l[end], solver = solver) + solver = SteadyStateODESolver(tmax = t_l[end]) + @inferred steadystate(H, c_ops, solver = solver) + @inferred steadystate(L, solver = solver) solver = SteadyStateDirectSolver() @inferred steadystate(H, c_ops, solver = solver) diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index 42cd278cb..78fe6a417 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -125,6 +125,31 @@ @test all([isapprox(sol_cpu.expect[i], sol_gpu32.expect[i]; atol = 1e-6) for i in 1:length(tlist)]) end +@testset "CUDA steadystate" begin + N = 50 + Δ = 0.01 + F = 0.1 + γ = 0.1 + nth = 2 + + a = destroy(N) + H = Δ * a' * a + F * (a + a') + c_ops = [sqrt(γ * (nth + 1)) * a, sqrt(γ * nth) * a'] + + ρ_ss_cpu = steadystate(H, c_ops) + + H_gpu_csc = cu(H) + c_ops_gpu_csc = [cu(c_op) for c_op in c_ops] + ρ_ss_gpu_csc = steadystate(H_gpu_csc, c_ops_gpu_csc, solver = SteadyStateLinearSolver()) + + H_gpu_csr = CuSparseMatrixCSR(H_gpu_csc) + c_ops_gpu_csr = [CuSparseMatrixCSR(c_op) for c_op in c_ops_gpu_csc] + ρ_ss_gpu_csr = steadystate(H_gpu_csr, c_ops_gpu_csr, solver = SteadyStateLinearSolver()) + + @test ρ_ss_cpu.data ≈ Array(ρ_ss_gpu_csc.data) atol = 1e-8 * length(ρ_ss_cpu) + @test ρ_ss_cpu.data ≈ Array(ρ_ss_gpu_csr.data) atol = 1e-8 * length(ρ_ss_cpu) +end + @testset "CUDA ptrace" begin g = fock(2, 1) e = fock(2, 0) From 35e9c6693151724c31bd9ae8a71fa7044b46dc18 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sat, 22 Feb 2025 11:08:42 +0900 Subject: [PATCH 229/329] bump version to `v0.28.0` (#422) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2d91e9c..301d6c673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.28.0] +Release date: 2025-02-22 + - Support for single `AbstractQuantumObject` in `sc_ops` for faster specific method in `ssesolve` and `smesolve`. ([#408]) - Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback`. ([#410]) - Align `eigenstates` and `eigenenergies` to QuTiP. ([#411]) @@ -115,6 +118,7 @@ Release date: 2024-11-13 [v0.25.2]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.2 [v0.26.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.26.0 [v0.27.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.27.0 +[v0.28.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.28.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index f8f680204..7ebae69ce 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.27.0" +version = "0.28.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 5b65086439eed6dfc283df1168227dba333b3166 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 23 Feb 2025 06:17:58 +0100 Subject: [PATCH 230/329] Add support for `OperatorKet` state input for `mesolve` and `smesolve` (#423) --- CHANGELOG.md | 3 +++ src/time_evolution/mesolve.jl | 23 ++++++++++++++++------- src/time_evolution/smesolve.jl | 28 +++++++++++++++++----------- test/core-test/time_evolution.jl | 29 +++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 301d6c673..51f3946c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Add support for `OperatorKet` state input for `mesolve` and `smesolve`. ([#423]) + ## [v0.28.0] Release date: 2025-02-22 @@ -171,3 +173,4 @@ Release date: 2024-11-13 [#419]: https://github.com/qutip/QuantumToolbox.jl/issues/419 [#420]: https://github.com/qutip/QuantumToolbox.jl/issues/420 [#421]: https://github.com/qutip/QuantumToolbox.jl/issues/421 +[#423]: https://github.com/qutip/QuantumToolbox.jl/issues/423 diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index ee78e7eac..f0796f0c4 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -33,7 +33,7 @@ where # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. -- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. @@ -65,7 +65,7 @@ function mesolveProblem( kwargs..., ) where { HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}, } haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) @@ -76,7 +76,11 @@ function mesolveProblem( check_dimensions(L_evo, ψ0) T = Base.promote_eltype(L_evo, ψ0) - ρ0 = to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + ρ0 = if isoperket(ψ0) # Convert it to dense vector with complex element type + to_dense(_CType(T), copy(ψ0.data)) + else + to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) + end L = L_evo.data kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_ODE_SOLVER_OPTIONS; kwargs...) @@ -85,7 +89,7 @@ function mesolveProblem( tspan = (tlist[1], tlist[end]) prob = ODEProblem{getVal(inplace),FullSpecialize}(L, ρ0, tspan, params; kwargs3...) - return TimeEvolutionProblem(prob, tlist, L_evo.dimensions) + return TimeEvolutionProblem(prob, tlist, L_evo.dimensions, (isoperket = Val(isoperket(ψ0)),)) end @doc raw""" @@ -117,7 +121,7 @@ where # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. -- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm for the ODE solver. The default value is `Tsit5()`. @@ -152,7 +156,7 @@ function mesolve( kwargs..., ) where { HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, + StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}, } prob = mesolveProblem( H, @@ -173,7 +177,12 @@ end function mesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) sol = solve(prob.prob, alg) - ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = prob.dimensions), sol.u) + # No type instabilities since `isoperket` is a Val, and so it is known at compile time + if getVal(prob.kwargs.isoperket) + ρt = map(ϕ -> QuantumObject(ϕ, type = OperatorKet, dims = prob.dimensions), sol.u) + else + ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = prob.dimensions), sol.u) + end return TimeEvolutionSol( prob.times, diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 4890030d2..ae28e4716 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -1,6 +1,7 @@ export smesolveProblem, smesolveEnsembleProblem, smesolve -_smesolve_generate_state(u, dims) = QuantumObject(vec2mat(u), type = Operator, dims = dims) +_smesolve_generate_state(u, dims, isoperket::Val{false}) = QuantumObject(vec2mat(u), type = Operator, dims = dims) +_smesolve_generate_state(u, dims, isoperket::Val{true}) = QuantumObject(u, type = OperatorKet, dims = dims) function _smesolve_update_coeff(u, p, t, op_vec) return 2 * real(dot(op_vec, u)) #this is Tr[Sn * ρ + ρ * Sn'] @@ -47,7 +48,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. -- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. @@ -84,7 +85,7 @@ function smesolveProblem( progress_bar::Union{Val,Bool} = Val(true), store_measurement::Union{Val,Bool} = Val(false), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}} haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) @@ -100,7 +101,11 @@ function smesolveProblem( dims = L_evo.dimensions T = Base.promote_eltype(L_evo, ψ0) - ρ0 = to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) # Convert it to dense vector with complex element type + ρ0 = if isoperket(ψ0) # Convert it to dense vector with complex element type + to_dense(_CType(T), copy(ψ0.data)) + else + to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) + end progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) @@ -143,7 +148,7 @@ function smesolveProblem( kwargs3..., ) - return TimeEvolutionProblem(prob, tlist, dims) + return TimeEvolutionProblem(prob, tlist, dims, (isoperket = Val(isoperket(ψ0)),)) end @doc raw""" @@ -188,7 +193,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. -- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. @@ -233,7 +238,7 @@ function smesolveEnsembleProblem( progress_bar::Union{Val,Bool} = Val(true), store_measurement::Union{Val,Bool} = Val(false), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}} _prob_func = isnothing(prob_func) ? _ensemble_dispatch_prob_func( @@ -266,7 +271,7 @@ function smesolveEnsembleProblem( EnsembleProblem(prob_sme, prob_func = _prob_func, output_func = _output_func[1], safetycopy = true), prob_sme.times, prob_sme.dimensions, - (progr = _output_func[2], channel = _output_func[3]), + merge(prob_sme.kwargs, (progr = _output_func[2], channel = _output_func[3])), ) return ensemble_prob @@ -315,7 +320,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio # Arguments - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. -- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref) or a [`Operator`](@ref). +- `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). - `tlist`: List of times at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. @@ -362,7 +367,7 @@ function smesolve( progress_bar::Union{Val,Bool} = Val(true), store_measurement::Union{Val,Bool} = Val(false), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}} ensemble_prob = smesolveEnsembleProblem( H, ψ0, @@ -406,7 +411,8 @@ function smesolve( _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _get_expvals(sol[:, i], SaveFuncMESolve), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP - states = map(i -> _smesolve_generate_state.(sol[:, i].u, Ref(dims)), eachindex(sol)) + + states = map(i -> _smesolve_generate_state.(sol[:, i].u, Ref(dims), ens_prob.kwargs.isoperket), eachindex(sol)) _m_expvals = _m_expvals_sol_1 isa Nothing ? nothing : map(i -> _get_m_expvals(sol[:, i], SaveFuncSMESolve), eachindex(sol)) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index bd5be390a..6d5e401fa 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -158,6 +158,31 @@ ) sol_sme3 = smesolve(H, ψ0, tlist, c_ops_sme2, sc_ops_sme2, e_ops = e_ops, progress_bar = Val(false)) + # For testing the `OperatorKet` input + sol_me4 = mesolve(H, operator_to_vector(ket2dm(ψ0)), tlist, c_ops, saveat = saveat, progress_bar = Val(false)) + sol_sme4 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + saveat = saveat, + ntraj = 10, + progress_bar = Val(false), + rng = MersenneTwister(12), + ) + sol_sme5 = smesolve( + H, + operator_to_vector(ket2dm(ψ0)), + tlist, + c_ops_sme, + sc_ops_sme, + saveat = saveat, + ntraj = 10, + progress_bar = Val(false), + rng = MersenneTwister(12), + ) + ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) expect_mc_states_mean = sum(expect_mc_states, dims = 2) / size(expect_mc_states, 2) @@ -190,6 +215,7 @@ @test length(sol_me3.states) == length(saveat) @test size(sol_me3.expect) == (length(e_ops), length(tlist)) @test sol_me3.expect[1, saveat_idxs] ≈ expect(e_ops[1], sol_me3.states) atol = 1e-6 + @test all([sol_me3.states[i] ≈ vector_to_operator(sol_me4.states[i]) for i in eachindex(saveat)]) @test length(sol_mc.times) == length(tlist) @test size(sol_mc.expect) == (length(e_ops), length(tlist)) @test length(sol_mc_states.times) == length(tlist) @@ -202,6 +228,9 @@ @test isnothing(sol_sme.measurement) @test size(sol_sse2.measurement) == (length(c_ops), 20, length(tlist) - 1) @test size(sol_sme2.measurement) == (length(sc_ops_sme), 20, length(tlist) - 1) + @test all([ + sol_sme4.states[j][i] ≈ vector_to_operator(sol_sme5.states[j][i]) for i in eachindex(saveat), j in 1:10 + ]) @test sol_me_string == "Solution of time evolution\n" * From 20809fd805eed0a0d4d7cb5b0624fa7f1c34fcf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 00:37:39 +0900 Subject: [PATCH 231/329] Bump crate-ci/typos from 1.29.7 to 1.29.9 (#424) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 074d50e27..a98296f01 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.29.7 \ No newline at end of file + uses: crate-ci/typos@v1.29.9 \ No newline at end of file From ba32a265b28e5bcb7389b0b4f20375c6470b1ce9 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 25 Feb 2025 00:41:43 +0900 Subject: [PATCH 232/329] Update dependabot.yml --- .github/dependabot.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ff6499d68..e0db79d1c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,4 +4,8 @@ updates: - package-ecosystem: "github-actions" directory: "/" # Location of package manifests schedule: - interval: "weekly" \ No newline at end of file + interval: "weekly" + labels: + - "dependencies" + - "Skip ChangeLog" + From 20bf62bc928ac8a8470b8dcc4d2913e3e50809de Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:09:48 +0900 Subject: [PATCH 233/329] Update buildkite CI pipeline settings (#425) --- .buildkite/CUDA_Ext.yml | 9 --------- .buildkite/pipeline.yml | 13 +++++++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.buildkite/CUDA_Ext.yml b/.buildkite/CUDA_Ext.yml index 589a310d3..40a80d6e5 100644 --- a/.buildkite/CUDA_Ext.yml +++ b/.buildkite/CUDA_Ext.yml @@ -22,12 +22,3 @@ steps: GROUP: "CUDA_Ext" SECRET_CODECOV_TOKEN: "ZfhQu/IcRLqNyZ//ZNs5sjBPaV76IHfU5gui52Qn+Rp8tOurukqgScuyDt+3HQ4R0hJYBw1/Nqg6jmBsvWSc9NEUx8kGsUJFHfN3no0+b+PFxA8oJkWc9EpyIsjht5ZIjlsFWR3f0DpPqMEle/QyWOPcal63CChXR8oAoR+Fz1Bh8GkokLlnC8F9Ugp9xBlu401GCbyZhvLTZnNIgK5yy9q8HBJnBg1cPOhI81J6JvYpEmcIofEzFV/qkfpTUPclu43WNoFX2DZPzbxilf3fsAd5/+nRkRfkNML8KiN4mnmjHxPPbuY8F5zC/PS5ybXtDpfvaMQc01WApXCkZk0ZAQ==;U2FsdGVkX1+eDT7dqCME5+Ox5i8GvWRTQbwiP/VYjapThDbxXFDeSSIC6Opmon+M8go22Bun3bat6Fzie65ang==" timeout_in_minutes: 60 - if: | - // Don't run Buildkite if the commit message includes the text [skip ci], [ci skip], or [no ci] - // Don't run Buildkite for PR draft - // Only run Buildkite when new commits and PR are made to main branch - build.message !~ /\[skip ci\]/ && - build.message !~ /\[ci skip\]/ && - build.message !~ /\[no ci\]/ && - !build.pull_request.draft && - (build.branch =~ /main/ || build.pull_request.base_branch =~ /main/) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 58abe6921..36199fd77 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,6 +1,17 @@ # see: https://github.com/staticfloat/forerunner-buildkite-plugin steps: - label: ":runner: Dynamically launch pipelines" + if: | + // Don't run Buildkite if the commit message includes the text [skip ci], [ci skip], or [no ci] + // Don't run Buildkite for PR draft + // Only run Buildkite when new commits and PR are made to main branch + build.message !~ /\[skip ci\]/ && + build.message !~ /\[ci skip\]/ && + build.message !~ /\[no ci\]/ && + !build.pull_request.draft && + (build.branch =~ /main/ || build.pull_request.base_branch =~ /main/) + agents: + queue: "juliagpu" plugins: - staticfloat/forerunner: # CUDA.jl tests watch: @@ -12,5 +23,3 @@ steps: - "test/ext-test/gpu/**" - "Project.toml" target: ".buildkite/CUDA_Ext.yml" - agents: - queue: "juliagpu" From 14c9e13c733f2b488420f5cb93830b545d3fdc68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 21:24:49 +0800 Subject: [PATCH 234/329] Bump crate-ci/typos from 1.29.9 to 1.30.0 (#427) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index a98296f01..69cadd707 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.29.9 \ No newline at end of file + uses: crate-ci/typos@v1.30.0 \ No newline at end of file From b5b770f3368dab13beff4cdf4cc6cb577a6c488f Mon Sep 17 00:00:00 2001 From: Li-Xun Cai <157601901+TendonFFF@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:17:17 +0800 Subject: [PATCH 235/329] Introduce `plot_fock_distribution` (#428) --- CHANGELOG.md | 2 + docs/src/resources/api.md | 1 + docs/src/users_guide/extensions/cairomakie.md | 3 +- ext/QuantumToolboxCairoMakieExt.jl | 81 ++++++++++++++++++- src/visualization.jl | 38 ++++++++- test/ext-test/cairomakie/cairomakie_ext.jl | 12 +++ 6 files changed, 134 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f3946c5..a09a9ee93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Add support for `OperatorKet` state input for `mesolve` and `smesolve`. ([#423]) +- Introduce `plot_fock_distribution` to plot the population of a state (ket, bra, or density matrix) in its basis (assumed to be Fock basis). ([#428]) ## [v0.28.0] Release date: 2025-02-22 @@ -174,3 +175,4 @@ Release date: 2024-11-13 [#420]: https://github.com/qutip/QuantumToolbox.jl/issues/420 [#421]: https://github.com/qutip/QuantumToolbox.jl/issues/421 [#423]: https://github.com/qutip/QuantumToolbox.jl/issues/423 +[#428]: https://github.com/qutip/QuantumToolbox.jl/issues/428 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index bb3e0b361..eb768caca 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -313,4 +313,5 @@ meshgrid ```@docs plot_wigner +plot_fock_distribution ``` diff --git a/docs/src/users_guide/extensions/cairomakie.md b/docs/src/users_guide/extensions/cairomakie.md index e847dd36c..b96c92d64 100644 --- a/docs/src/users_guide/extensions/cairomakie.md +++ b/docs/src/users_guide/extensions/cairomakie.md @@ -18,4 +18,5 @@ The supported plotting functions are listed as follows: | **Plotting Function** | **Description** | |:----------------------|:----------------| -| [`plot_wigner`](@ref) | [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) | \ No newline at end of file +| [`plot_wigner`](@ref) | [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) | +| [`plot_fock_distribution`](@ref) | [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution | \ No newline at end of file diff --git a/ext/QuantumToolboxCairoMakieExt.jl b/ext/QuantumToolboxCairoMakieExt.jl index 23be6452d..950859e3f 100644 --- a/ext/QuantumToolboxCairoMakieExt.jl +++ b/ext/QuantumToolboxCairoMakieExt.jl @@ -1,7 +1,8 @@ module QuantumToolboxCairoMakieExt using QuantumToolbox -using CairoMakie: Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, GridPosition, @L_str, Reverse +using CairoMakie: + Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, barplot!, GridPosition, @L_str, Reverse, ylims! @doc raw""" plot_wigner( @@ -139,6 +140,84 @@ function _plot_wigner( return fig, ax, surf end +@doc raw""" + plot_fock_distribution( + library::Val{:CairoMakie}, + ρ::QuantumObject{SType}; + fock_numbers::Union{Nothing, AbstractVector} = nothing, + unit_y_range::Bool = true, + location::Union{GridPosition,Nothing} = nothing, + kwargs... + ) where {SType<:Union{KetQuantumObject,OperatorQuantumObject}} + +Plot the [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution of `ρ`. + +# Arguments +- `library::Val{:CairoMakie}`: The plotting library to use. +- `ρ::QuantumObject`: The quantum state for which the Fock state distribution is to be plotted. It can be either a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). +- `location::Union{GridPosition,Nothing}`: The location of the plot in the layout. If `nothing`, the plot is created in a new figure. Default is `nothing`. +- `fock_numbers::Union{Nothing, AbstractVector}`: list of x ticklabels to represent fock numbers, default is `nothing`. +- `unit_y_range::Bool`: Set y-axis limits [0, 1] or not, default is `true`. +- `kwargs...`: Additional keyword arguments to pass to the plotting function. + +# Returns +- `fig`: The figure object. +- `ax`: The axis object. +- `bp`: The barplot object. + +!!! note "Import library first" + [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) must first be imported before using this function. + +!!! warning "Beware of type-stability!" + If you want to keep type stability, it is recommended to use `Val(:two_dim)` and `Val(:three_dim)` instead of `:two_dim` and `:three_dim`, respectively. Also, specify the library as `Val(:CairoMakie)` See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +function QuantumToolbox.plot_fock_distribution( + library::Val{:CairoMakie}, + ρ::QuantumObject{SType}; + fock_numbers::Union{Nothing,AbstractVector} = nothing, + unit_y_range::Bool = true, + location::Union{GridPosition,Nothing} = nothing, + kwargs..., +) where {SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} + return _plot_fock_distribution( + library, + ρ; + fock_numbers = fock_numbers, + unit_y_range = unit_y_range, + location = location, + kwargs..., + ) +end + +function _plot_fock_distribution( + ::Val{:CairoMakie}, + ρ::QuantumObject{SType}; + fock_numbers::Union{Nothing,AbstractVector} = nothing, + unit_y_range::Bool = true, + location::Union{GridPosition,Nothing} = nothing, + kwargs..., +) where {SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} + ρ = ket2dm(ρ) + D = prod(ρ.dims) + isapprox(tr(ρ), 1, atol = 1e-4) || (@warn "The input ρ should be normalized.") + + xvec = 0:(D-1) + isnothing(fock_numbers) && (fock_numbers = string.(collect(xvec))) + + fig, location = _getFigAndLocation(location) + lyt = GridLayout(location) + ax = Axis(lyt[1, 1]) + + bp = barplot!(ax, xvec, real(diag(ρ)); kwargs...) + + ax.xticks = (xvec, fock_numbers) + ax.xlabel = "Fock number" + ax.ylabel = "Occupation probability" + unit_y_range && ylims!(ax, 0, 1) + + return fig, ax, bp +end + raw""" _getFigAndLocation(location::Nothing) diff --git a/src/visualization.jl b/src/visualization.jl index bd21582a0..0e1fbd3fb 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -1,4 +1,4 @@ -export plot_wigner +export plot_wigner, plot_fock_distribution @doc raw""" plot_wigner( @@ -35,3 +35,39 @@ plot_wigner( kwargs..., ) where {T,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) + +@doc raw""" + plot_fock_distribution( + ρ::QuantumObject{SType}; + library::Union{Val, Symbol} = Val(:CairoMakie), + kwargs... + ) where {SType<:Union{KetQuantumObject,OperatorQuantumObject}} + +Plot the [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution of `ρ`. + +The `library` keyword argument specifies the plotting library to use, defaulting to [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie). + +# Arguments +- `ρ::QuantumObject`: The quantum state for which to plot the Fock state distribution. +- `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:CairoMakie)`. +- `kwargs...`: Additional keyword arguments to pass to the plotting function. See the documentation for the specific plotting library for more information. + +!!! note "Import library first" + The plotting libraries must first be imported before using them with this function. + +!!! warning "Beware of type-stability!" + If you want to keep type stability, it is recommended to use `Val(:CairoMakie)` instead of `:CairoMakie` as the plotting library. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +plot_fock_distribution( + ρ::QuantumObject{SType}; + library::Union{Val,Symbol} = Val(:CairoMakie), + kwargs..., +) where {SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = + plot_fock_distribution(makeVal(library), ρ; kwargs...) + +plot_fock_distribution( + ::Val{T}, + ρ::QuantumObject{SType}; + kwargs..., +) where {T,SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = + throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) diff --git a/test/ext-test/cairomakie/cairomakie_ext.jl b/test/ext-test/cairomakie/cairomakie_ext.jl index 2571e02d1..0c73dc363 100644 --- a/test/ext-test/cairomakie/cairomakie_ext.jl +++ b/test/ext-test/cairomakie/cairomakie_ext.jl @@ -5,6 +5,8 @@ @test_throws ArgumentError plot_wigner(ψ; library = :CairoMakie, xvec = xvec, yvec = yvec) + @test_throws ArgumentError plot_fock_distribution(ψ; library = :CairoMakie) + using CairoMakie fig, ax, hm = plot_wigner( @@ -60,4 +62,14 @@ ) @test fig1 === fig @test fig[2, 3].layout.content[1].content[1, 1].layout.content[1].content === ax + + fig = Figure() + pos = fig[2, 3] + fig1, ax = plot_fock_distribution(ψ; library = Val(:CairoMakie), location = pos) + @test fig1 === fig + @test fig[2, 3].layout.content[1].content[1, 1].layout.content[1].content === ax + + fig = Figure() + pos = fig[2, 3] + fig1, ax = @test_logs (:warn,) plot_fock_distribution(ψ * 2; library = Val(:CairoMakie), location = pos) end From e76fcbeeebc8143f5128e9be18af769721cb973f Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:22:51 +0800 Subject: [PATCH 236/329] Bump version to `v0.29.0` (#429) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a09a9ee93..1c47ce07e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.29.0] +Release date: 2025-03-07 + - Add support for `OperatorKet` state input for `mesolve` and `smesolve`. ([#423]) - Introduce `plot_fock_distribution` to plot the population of a state (ket, bra, or density matrix) in its basis (assumed to be Fock basis). ([#428]) @@ -122,6 +125,7 @@ Release date: 2024-11-13 [v0.26.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.26.0 [v0.27.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.27.0 [v0.28.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.28.0 +[v0.29.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 7ebae69ce..442dc33f9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.28.0" +version = "0.29.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From e22908f29ecc3e601e371e5eabc714e1dbadeb0b Mon Sep 17 00:00:00 2001 From: Li-Xun Cai <157601901+TendonFFF@users.noreply.github.com> Date: Fri, 7 Mar 2025 16:05:48 +0800 Subject: [PATCH 237/329] move and rename eltype conversion (#430) --- ext/QuantumToolboxCUDAExt.jl | 15 ++++----------- src/utilities.jl | 7 +++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index b4fcf0fc2..11a764244 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -2,7 +2,7 @@ module QuantumToolboxCUDAExt using QuantumToolbox using QuantumToolbox: makeVal, getVal -import QuantumToolbox: _sparse_similar +import QuantumToolbox: _sparse_similar, _convert_eltype_wordsize import CUDA: cu, CuArray, allowscalar import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR, AbstractCuSparseArray import SparseArrays: SparseVector, SparseMatrixCSC @@ -81,27 +81,20 @@ function cu(A::QuantumObject; word_size::Union{Val,Int} = Val(64)) return cu(A, makeVal(word_size)) end -cu(A::QuantumObject, word_size::Union{Val{32},Val{64}}) = CuArray{_change_eltype(eltype(A), word_size)}(A) +cu(A::QuantumObject, word_size::Union{Val{32},Val{64}}) = CuArray{_convert_eltype_wordsize(eltype(A), word_size)}(A) function cu( A::QuantumObject{ObjType,DimsType,<:SparseVector}, word_size::Union{Val{32},Val{64}}, ) where {ObjType<:QuantumObjectType,DimsType<:AbstractDimensions} - return CuSparseVector{_change_eltype(eltype(A), word_size)}(A) + return CuSparseVector{_convert_eltype_wordsize(eltype(A), word_size)}(A) end function cu( A::QuantumObject{ObjType,DimsType,<:SparseMatrixCSC}, word_size::Union{Val{32},Val{64}}, ) where {ObjType<:QuantumObjectType,DimsType<:AbstractDimensions} - return CuSparseMatrixCSC{_change_eltype(eltype(A), word_size)}(A) + return CuSparseMatrixCSC{_convert_eltype_wordsize(eltype(A), word_size)}(A) end -_change_eltype(::Type{T}, ::Val{64}) where {T<:Int} = Int64 -_change_eltype(::Type{T}, ::Val{32}) where {T<:Int} = Int32 -_change_eltype(::Type{T}, ::Val{64}) where {T<:AbstractFloat} = Float64 -_change_eltype(::Type{T}, ::Val{32}) where {T<:AbstractFloat} = Float32 -_change_eltype(::Type{Complex{T}}, ::Val{64}) where {T<:Union{Int,AbstractFloat}} = ComplexF64 -_change_eltype(::Type{Complex{T}}, ::Val{32}) where {T<:Union{Int,AbstractFloat}} = ComplexF32 - QuantumToolbox.to_dense(A::MT) where {MT<:AbstractCuSparseArray} = CuArray(A) QuantumToolbox.to_dense(::Type{T1}, A::CuArray{T2}) where {T1<:Number,T2<:Number} = CuArray{T1}(A) diff --git a/src/utilities.jl b/src/utilities.jl index 0a660995f..a3547cf61 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -190,3 +190,10 @@ _CType(::Type{Complex{Int32}}) = ComplexF32 _CType(::Type{Complex{Int64}}) = ComplexF64 _CType(::Type{Complex{Float32}}) = ComplexF32 _CType(::Type{Complex{Float64}}) = ComplexF64 + +_convert_eltype_wordsize(::Type{T}, ::Val{64}) where {T<:Int} = Int64 +_convert_eltype_wordsize(::Type{T}, ::Val{32}) where {T<:Int} = Int32 +_convert_eltype_wordsize(::Type{T}, ::Val{64}) where {T<:AbstractFloat} = Float64 +_convert_eltype_wordsize(::Type{T}, ::Val{32}) where {T<:AbstractFloat} = Float32 +_convert_eltype_wordsize(::Type{Complex{T}}, ::Val{64}) where {T<:Union{Int,AbstractFloat}} = ComplexF64 +_convert_eltype_wordsize(::Type{Complex{T}}, ::Val{32}) where {T<:Union{Int,AbstractFloat}} = ComplexF32 From 1c2688850740cef0fc7bf445de5d5b2a0f305924 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 7 Mar 2025 16:11:17 +0800 Subject: [PATCH 238/329] Bump version to `v0.29.1` (#431) --- CHANGELOG.md | 7 +++++++ Project.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c47ce07e..500083a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.29.1] +Release date: 2025-03-07 + +- Minor changes for GPU matrices element type and word size handling. ([#430]) + ## [v0.29.0] Release date: 2025-03-07 @@ -126,6 +131,7 @@ Release date: 2024-11-13 [v0.27.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.27.0 [v0.28.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.28.0 [v0.29.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.0 +[v0.29.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.1 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 @@ -180,3 +186,4 @@ Release date: 2024-11-13 [#421]: https://github.com/qutip/QuantumToolbox.jl/issues/421 [#423]: https://github.com/qutip/QuantumToolbox.jl/issues/423 [#428]: https://github.com/qutip/QuantumToolbox.jl/issues/428 +[#430]: https://github.com/qutip/QuantumToolbox.jl/issues/430 diff --git a/Project.toml b/Project.toml index 442dc33f9..fc5d42037 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.29.0" +version = "0.29.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From bd4df90d604f89c3d4de795b3d36ac93b0e0712f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 20:40:21 +0900 Subject: [PATCH 239/329] Bump crate-ci/typos from 1.30.0 to 1.30.1 (#432) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 69cadd707..dc25adaa6 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.30.0 \ No newline at end of file + uses: crate-ci/typos@v1.30.1 \ No newline at end of file From 6d4e7d26f3e1d93b406cc45a8ebfcfdc8dedb9cf Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:15:45 +0800 Subject: [PATCH 240/329] Add reference for `concurrence` (#433) --- docs/src/resources/bibliography.bib | 15 +++++++++++++++ src/entropy.jl | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index 2f55bbf57..9cb6b77e1 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -104,3 +104,18 @@ @article{Spehner2017 journal={arXiv:1611.03449}, url={https://arxiv.org/abs/1611.03449}, } + +@article{Hill-Wootters1997, + title = {Entanglement of a Pair of Quantum Bits}, + author = {Hill, Sam A. and Wootters, William K.}, + journal = {Phys. Rev. Lett.}, + volume = {78}, + issue = {26}, + pages = {5022--5025}, + numpages = {0}, + year = {1997}, + month = {Jun}, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevLett.78.5022}, + url = {https://link.aps.org/doi/10.1103/PhysRevLett.78.5022} +} diff --git a/src/entropy.jl b/src/entropy.jl index 59a992185..43560fd6d 100644 --- a/src/entropy.jl +++ b/src/entropy.jl @@ -224,6 +224,10 @@ Calculate the [concurrence](https://en.wikipedia.org/wiki/Concurrence_(quantum_c # Notes - `ρ` can be either a [`Ket`](@ref) or an [`Operator`](@ref). + +# References + +- [Hill-Wootters1997](@citet) """ function concurrence(ρ::QuantumObject{OpType}) where {OpType<:Union{KetQuantumObject,OperatorQuantumObject}} (ρ.dimensions == Dimensions((Space(2), Space(2)))) || throw( From 5f5e21b94c22f3009d61e78ae9e64e10b63f87eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:00:39 +0800 Subject: [PATCH 241/329] Bump crate-ci/typos from 1.30.1 to 1.30.2 (#434) --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index dc25adaa6..e5c6ed335 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.30.1 \ No newline at end of file + uses: crate-ci/typos@v1.30.2 \ No newline at end of file From a87e6981590a0c22cb60587bcfc1cdd011a7ad62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 08:17:20 +0200 Subject: [PATCH 242/329] Bump crate-ci/typos from 1.30.2 to 1.31.0 (#435) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index e5c6ed335..2d3a6928f 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.30.2 \ No newline at end of file + uses: crate-ci/typos@v1.31.0 \ No newline at end of file From acc1ddd93c8f2aac00439a1efcb8b6d9e1ccc7d6 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 4 Apr 2025 01:02:19 +0200 Subject: [PATCH 243/329] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9131a3b3b..cd8c30383 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) ## Performance comparison with other packages -Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://github.com/albertomercurio/QuantumToolbox.jl-Paper-Figures/blob/main/src/benchmarks.jl). +Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://github.com/albertomercurio/QuantumToolbox.jl-Paper-Figures/blob/main/src/benchmarks/benchmarks.jl). ![](https://raw.githubusercontent.com/albertomercurio/QuantumToolbox.jl-Paper-Figures/refs/heads/main/figures/benchmarks.svg) From d8f83ccb7cfd7b2084eeec644e3e057a9b6ad0ef Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 5 Apr 2025 01:37:55 +0200 Subject: [PATCH 244/329] Make CUDA conversion more general (#437) --- CHANGELOG.md | 3 +++ ext/QuantumToolboxCUDAExt.jl | 4 +++- test/ext-test/gpu/cuda_ext.jl | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 500083a9c..3d4d56ddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Make CUDA conversion more general using Adapt.jl. ([#437]) + ## [v0.29.1] Release date: 2025-03-07 @@ -187,3 +189,4 @@ Release date: 2024-11-13 [#423]: https://github.com/qutip/QuantumToolbox.jl/issues/423 [#428]: https://github.com/qutip/QuantumToolbox.jl/issues/428 [#430]: https://github.com/qutip/QuantumToolbox.jl/issues/430 +[#437]: https://github.com/qutip/QuantumToolbox.jl/issues/437 diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index 11a764244..d05a65648 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -6,6 +6,7 @@ import QuantumToolbox: _sparse_similar, _convert_eltype_wordsize import CUDA: cu, CuArray, allowscalar import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR, AbstractCuSparseArray import SparseArrays: SparseVector, SparseMatrixCSC +import CUDA.Adapt: adapt allowscalar(false) @@ -81,7 +82,8 @@ function cu(A::QuantumObject; word_size::Union{Val,Int} = Val(64)) return cu(A, makeVal(word_size)) end -cu(A::QuantumObject, word_size::Union{Val{32},Val{64}}) = CuArray{_convert_eltype_wordsize(eltype(A), word_size)}(A) +cu(A::QuantumObject, word_size::Union{Val{32},Val{64}}) = + QuantumObject(adapt(CuArray{_convert_eltype_wordsize(eltype(A), word_size)}, A.data), A.type, A.dimensions) function cu( A::QuantumObject{ObjType,DimsType,<:SparseVector}, word_size::Union{Val{32},Val{64}}, diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index 78fe6a417..e6399e42a 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -66,6 +66,10 @@ @test typeof(CuSparseMatrixCSR(Xsc).data) == CuSparseMatrixCSR{ComplexF64,Int32} @test typeof(CuSparseMatrixCSR{ComplexF32}(Xsc).data) == CuSparseMatrixCSR{ComplexF32,Int32} + # type conversion of CUDA Diagonal arrays + @test cu(qeye(10), word_size = Val(32)).data isa Diagonal{ComplexF32,<:CuVector{ComplexF32}} + @test cu(qeye(10), word_size = Val(64)).data isa Diagonal{ComplexF64,<:CuVector{ComplexF64}} + # Sparse To Dense # @test to_dense(cu(ψsi; word_size = 64)).data isa CuVector{Int64} # TODO: Fix this in CUDA.jl @test to_dense(cu(ψsf; word_size = 64)).data isa CuVector{Float64} From 5fbce6d6e1143e547d623f93e79d9b22bd63c00d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:05:55 +0200 Subject: [PATCH 245/329] Make `fock` non-mutating (#438) --- CHANGELOG.md | 5 ++++- src/qobj/states.jl | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d4d56ddc..e06ec247b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) -- Make CUDA conversion more general using Adapt.jl. ([#437]) +- Make CUDA conversion more general using Adapt.jl. ([#436], [#437]) +- Make the generation of `fock` states non-mutating to support Zygote.jl. ([#438]) ## [v0.29.1] Release date: 2025-03-07 @@ -189,4 +190,6 @@ Release date: 2024-11-13 [#423]: https://github.com/qutip/QuantumToolbox.jl/issues/423 [#428]: https://github.com/qutip/QuantumToolbox.jl/issues/428 [#430]: https://github.com/qutip/QuantumToolbox.jl/issues/430 +[#436]: https://github.com/qutip/QuantumToolbox.jl/issues/436 [#437]: https://github.com/qutip/QuantumToolbox.jl/issues/437 +[#438]: https://github.com/qutip/QuantumToolbox.jl/issues/438 diff --git a/src/qobj/states.jl b/src/qobj/states.jl index 886982f7c..784c172aa 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -37,8 +37,7 @@ function fock(N::Int, j::Int = 0; dims::Union{Int,AbstractVector{Int},Tuple} = N if getVal(sparse) array = sparsevec([j + 1], [1.0 + 0im], N) else - array = zeros(ComplexF64, N) - array[j+1] = 1 + array = [i == (j + 1) ? 1.0 + 0im : 0.0 + 0im for i in 1:N] end return QuantumObject(array; type = Ket, dims = dims) end From 7fd9e80a3460f547812e1019b244656ca7d253d6 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 6 Apr 2025 11:13:40 +0200 Subject: [PATCH 246/329] Format the code according to the new version of JuliaFormatter.jl (#439) --- src/entropy.jl | 2 +- src/qobj/eigsolve.jl | 19 +++--- src/qobj/operators.jl | 12 ++-- src/qobj/states.jl | 2 +- src/spin_lattice.jl | 6 +- src/steadystate.jl | 8 +-- src/time_evolution/lr_mesolve.jl | 60 +++++++++---------- src/time_evolution/time_evolution.jl | 4 +- .../time_evolution_dynamical.jl | 10 ++-- src/wigner.jl | 6 +- test/core-test/generalized_master_equation.jl | 4 +- test/core-test/low_rank_dynamics.jl | 6 +- test/core-test/steady_state.jl | 4 +- 13 files changed, 71 insertions(+), 72 deletions(-) diff --git a/src/entropy.jl b/src/entropy.jl index 43560fd6d..d32bc7c9a 100644 --- a/src/entropy.jl +++ b/src/entropy.jl @@ -55,7 +55,7 @@ function entropy_vn( length(indexes) == 0 && return zero(real(T)) nzvals = vals[indexes] logvals = base != 0 ? log.(base, Complex.(nzvals)) : log.(Complex.(nzvals)) - return -real(mapreduce(*, +, nzvals, logvals)) + return -real(mapreduce(*,+,nzvals,logvals)) end @doc raw""" diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index f180c0272..d68a3a163 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -225,7 +225,7 @@ function _eigsolve( # println( A * view(V, :, 1:k) ≈ view(V, :, 1:k) * M(view(H, 1:k, 1:k)) + qₘ * M(transpose(view(transpose(βeₘ) * Uₘ, 1:k))) ) # SHOULD BE TRUE - for j in k+1:m + for j in (k+1):m β = arnoldi_step!(A, V, H, j) if β < tol numops += j - k - 1 @@ -406,15 +406,14 @@ function eigsolve_al( kwargs..., ) where {HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} L_evo = _mesolve_make_L_QobjEvo(H, c_ops) - prob = - mesolveProblem( - L_evo, - QuantumObject(ρ0, type = Operator, dims = H.dimensions), - [zero(T), T]; - params = params, - progress_bar = Val(false), - kwargs..., - ).prob + prob = mesolveProblem( + L_evo, + QuantumObject(ρ0, type = Operator, dims = H.dimensions), + [zero(T), T]; + params = params, + progress_bar = Val(false), + kwargs..., + ).prob integrator = init(prob, alg) Lmap = ArnoldiLindbladIntegratorMap(eltype(H), size(L_evo), integrator) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 5445818ef..90d6d1739 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -100,7 +100,7 @@ julia> fock(20, 3)' * a * fock(20, 4) 2.0 + 0.0im ``` """ -destroy(N::Int) = QuantumObject(spdiagm(1 => Array{ComplexF64}(sqrt.(1:N-1))), Operator, N) +destroy(N::Int) = QuantumObject(spdiagm(1 => Array{ComplexF64}(sqrt.(1:(N-1)))), Operator, N) @doc raw""" create(N::Int) @@ -126,7 +126,7 @@ julia> fock(20, 4)' * a_d * fock(20, 3) 2.0 + 0.0im ``` """ -create(N::Int) = QuantumObject(spdiagm(-1 => Array{ComplexF64}(sqrt.(1:N-1))), Operator, N) +create(N::Int) = QuantumObject(spdiagm(-1 => Array{ComplexF64}(sqrt.(1:(N-1)))), Operator, N) @doc raw""" displace(N::Int, α::Number) @@ -167,7 +167,7 @@ Bosonic number operator with Hilbert space cutoff `N`. This operator is defined as ``\hat{N}=\hat{a}^\dagger \hat{a}``, where ``\hat{a}`` is the bosonic annihilation operator. """ -num(N::Int) = QuantumObject(spdiagm(0 => Array{ComplexF64}(0:N-1)), Operator, N) +num(N::Int) = QuantumObject(spdiagm(0 => Array{ComplexF64}(0:(N-1))), Operator, N) @doc raw""" position(N::Int) @@ -311,11 +311,11 @@ end jmat(j::Real, ::Val{T}) where {T} = throw(ArgumentError("Invalid spin operator: $(T)")) function _jm(j::Real) - m = j:(-1):-j - data = sqrt.(j * (j + 1) .- m .* (m .- 1))[1:end-1] + m = j:(-1):(-j) + data = sqrt.(j * (j + 1) .- m .* (m .- 1))[1:(end-1)] return spdiagm(-1 => Array{ComplexF64}(data)) end -_jz(j::Real) = spdiagm(0 => Array{ComplexF64}(j .- (0:Int(2 * j)))) +_jz(j::Real) = spdiagm(0 => Array{ComplexF64}(j .- (0:Int(2*j)))) @doc raw""" spin_Jx(j::Real) diff --git a/src/qobj/states.jl b/src/qobj/states.jl index 784c172aa..c2759c9a4 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -127,7 +127,7 @@ Density matrix for a thermal state (generating thermal state probabilities) with """ function thermal_dm(N::Int, n::Real; sparse::Union{Bool,Val} = Val(false)) β = log(1.0 / n + 1.0) - N_list = Array{Float64}(0:N-1) + N_list = Array{Float64}(0:(N-1)) data = exp.(-β .* N_list) if getVal(sparse) return QuantumObject(spdiagm(0 => data ./ sum(data)), Operator, N) diff --git a/src/spin_lattice.jl b/src/spin_lattice.jl index 17275aba1..72ee4f38f 100644 --- a/src/spin_lattice.jl +++ b/src/spin_lattice.jl @@ -53,11 +53,11 @@ function multisite_operator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Int _dims[sites] == [get_dimensions_to(op)[1].size for op in ops] || throw(ArgumentError("The dimensions of the operators do not match the dimensions of the lattice.")) - data = kron(I(prod(_dims[1:sites[1]-1])), ops[1].data) + data = kron(I(prod(_dims[1:(sites[1]-1)])), ops[1].data) for i in 2:length(sites) - data = kron(data, I(prod(_dims[sites[i-1]+1:sites[i]-1])), ops[i].data) + data = kron(data, I(prod(_dims[(sites[i-1]+1):(sites[i]-1)])), ops[i].data) end - data = kron(data, I(prod(_dims[sites[end]+1:end]))) + data = kron(data, I(prod(_dims[(sites[end]+1):end]))) return QuantumObject(data; type = Operator, dims = dims) end diff --git a/src/steadystate.jl b/src/steadystate.jl index 921c7b398..1d25eda6b 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -367,7 +367,7 @@ function _steadystate_fourier( N = size(L_0_mat, 1) Ns = isqrt(N) n_fourier = 2 * n_max + 1 - n_list = -n_max:n_max + n_list = (-n_max):n_max weight = 1 Mn = sparse(ones(Ns), [Ns * (j - 1) + j for j in 1:Ns], fill(weight, Ns), N, N) @@ -399,15 +399,15 @@ function _steadystate_fourier( offset1 = n_max * N offset2 = (n_max + 1) * N - ρ0 = reshape(ρtot[offset1+1:offset2], Ns, Ns) + ρ0 = reshape(ρtot[(offset1+1):offset2], Ns, Ns) ρ0_tr = tr(ρ0) ρ0 = ρ0 / ρ0_tr ρ0 = QuantumObject((ρ0 + ρ0') / 2, type = Operator, dims = L_0.dimensions) ρtot = ρtot / ρ0_tr ρ_list = [ρ0] - for i in 0:n_max-1 - ρi_m = reshape(ρtot[offset1-(i+1)*N+1:offset1-i*N], Ns, Ns) + for i in 0:(n_max-1) + ρi_m = reshape(ρtot[(offset1-(i+1)*N+1):(offset1-i*N)], Ns, Ns) ρi_m = QuantumObject(ρi_m, type = Operator, dims = L_0.dimensions) push!(ρ_list, ρi_m) end diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index 873e7e9ed..4a0f204b3 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -55,9 +55,9 @@ lr_mesolve_options_default = ( Δt = 0.0, ) -#=======================================================# -# ADDITIONAL FUNCTIONS -#=======================================================# +#= + ADDITIONAL FUNCTIONS +=# select(x::Real, xarr::AbstractArray, retval = false) = retval ? xarr[argmin(abs.(x .- xarr))] : argmin(abs.(x .- xarr)) @@ -133,9 +133,9 @@ function _calculate_expectation!(p, z, B, idx) end end -#=======================================================# -# SAVING FUNCTIONS -#=======================================================# +#= + SAVING FUNCTIONS +=# function _periodicsave_func(integrator) ip = integrator.p @@ -151,8 +151,8 @@ function _save_affect_lr_mesolve!(integrator) N, M = ip.N, ip.M idx = select(integrator.t, ip.times) - @views z = reshape(integrator.u[1:N*M], N, M) - @views B = reshape(integrator.u[N*M+1:end], M, M) + @views z = reshape(integrator.u[1:(N*M)], N, M) + @views B = reshape(integrator.u[(N*M+1):end], M, M) _calculate_expectation!(ip, z, B, idx) if integrator.p.opt.progress @@ -162,9 +162,9 @@ function _save_affect_lr_mesolve!(integrator) return u_modified!(integrator, false) end -#=======================================================# -# CALLBACK FUNCTIONS -#=======================================================# +#= + CALLBACK FUNCTIONS +=# #= _adjM_condition_ratio(u, t, integrator) @@ -185,8 +185,8 @@ function _adjM_condition_ratio(u, t, integrator) C = ip.A0 σ = ip.temp_MM - @views z = reshape(u[1:N*M], N, M) - @views B = reshape(u[N*M+1:end], M, M) + @views z = reshape(u[1:(N*M)], N, M) + @views B = reshape(u[(N*M+1):end], M, M) mul!(C, z, sqrt(B)) mul!(σ, C', C) p = abs.(eigvals(σ)) @@ -232,8 +232,8 @@ function _adjM_affect!(integrator) N, M = ip.N, ip.M @views begin - z = Δt > 0 ? reshape(ip.u_save[1:N*M], N, M) : reshape(integrator.u[1:N*M], N, M) - B = Δt > 0 ? reshape(ip.u_save[N*M+1:end], M, M) : reshape(integrator.u[N*M+1:end], M, M) + z = Δt > 0 ? reshape(ip.u_save[1:(N*M)], N, M) : reshape(integrator.u[1:(N*M)], N, M) + B = Δt > 0 ? reshape(ip.u_save[(N*M+1):end], M, M) : reshape(integrator.u[(N*M+1):end], M, M) ψ = ip.L_tilde[:, 1] normalize!(ψ) @@ -241,7 +241,7 @@ function _adjM_affect!(integrator) B = cat(B, opt.p0, dims = (1, 2)) resize!(integrator, length(z) + length(B)) integrator.u[1:length(z)] .= z[:] - integrator.u[length(z)+1:end] .= B[:] + integrator.u[(length(z)+1):end] .= B[:] end integrator.p = merge( @@ -277,9 +277,9 @@ function _adjM_affect!(integrator) end end -#=======================================================# -# DYNAMICAL EVOLUTION EQUATIONS -#=======================================================# +#= + DYNAMICAL EVOLUTION EQUATIONS +=# #= dBdz!(du, u, p, t) @@ -305,10 +305,10 @@ function dBdz!(du, u, p, t) N, M = p.N, p.M opt = p.opt - @views z = reshape(u[1:N*M], N, M) - @views dz = reshape(du[1:N*M], N, M) - @views B = reshape(u[N*M+1:end], M, M) - @views dB = reshape(du[N*M+1:end], M, M) + @views z = reshape(u[1:(N*M)], N, M) + @views dz = reshape(du[1:(N*M)], N, M) + @views B = reshape(u[(N*M+1):end], M, M) + @views dB = reshape(du[(N*M+1):end], M, M) #Assign A0 and S mul!(S, z', z) @@ -352,17 +352,17 @@ function dBdz!(du, u, p, t) return dB .-= temp_MM end -#=======================================================# -# OUTPUT GENNERATION -#=======================================================# +#= + OUTPUT GENNERATION +=# -get_z(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, 1:M*N), N, M) +get_z(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, 1:(M*N)), N, M) get_B(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, (M*N+1):length(u)), M, M) -#=======================================================# -# PROBLEM FORMULATION -#=======================================================# +#= + PROBLEM FORMULATION +=# @doc raw""" lr_mesolveProblem( diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index b487d6511..13188cc25 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -397,7 +397,7 @@ end @generated function update_coefficients!(L::DiffusionOperator, u, p, t) ops_types = L.parameters[2].parameters N = length(ops_types) - quote + return quote Base.@nexprs $N i -> begin update_coefficients!(L.ops[i], u, p, t) end @@ -538,7 +538,7 @@ function _liouvillian_floquet( S = -(L_0 - 1im * n_max * ω * I) \ L_p_dense T = -(L_0 + 1im * n_max * ω * I) \ L_m_dense - for n_i in n_max-1:-1:1 + for n_i in (n_max-1):-1:1 S = -(L_0 - 1im * n_i * ω * I + L_m * S) \ L_p_dense T = -(L_0 + 1im * n_i * ω * I + L_p * T) \ L_m_dense end diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index c320946e8..e46633239 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -39,15 +39,15 @@ function _increase_dims( if n_d == 1 ρmat = similar(QO, dims_new[1], dims_new[1]) - fill!(selectdim(ρmat, 1, dims[1]+1:dims_new[1]), 0) - fill!(selectdim(ρmat, 2, dims[1]+1:dims_new[1]), 0) + fill!(selectdim(ρmat, 1, (dims[1]+1):dims_new[1]), 0) + fill!(selectdim(ρmat, 2, (dims[1]+1):dims_new[1]), 0) copyto!(view(ρmat, 1:dims[1], 1:dims[1]), QO) else ρmat2 = similar(QO, reverse(vcat(dims_new, dims_new))...) ρmat = reshape(QO, reverse(vcat(dims, dims))...) for i in eachindex(sel) - fill!(selectdim(ρmat2, n_d - sel[i] + 1, dims[sel[i]]+1:dims_new[sel[i]]), 0) - fill!(selectdim(ρmat2, 2 * n_d - sel[i] + 1, dims[sel[i]]+1:dims_new[sel[i]]), 0) + fill!(selectdim(ρmat2, n_d - sel[i] + 1, (dims[sel[i]]+1):dims_new[sel[i]]), 0) + fill!(selectdim(ρmat2, 2 * n_d - sel[i] + 1, (dims[sel[i]]+1):dims_new[sel[i]]), 0) end copyto!(view(ρmat2, reverse!(repeat([1:n for n in dims], 2))...), ρmat) ρmat = reshape(ρmat2, prod(dims_new), prod(dims_new)) @@ -77,7 +77,7 @@ function _DFDIncreaseReduceCondition(u, t, integrator) pillow_i = pillow_list[i] if dim_i < maxdim_i && dim_i > 2 && maxdim_i != 0 ρi = _ptrace_oper(vec2mat(dfd_ρt_cache), dim_list, SVector(i))[1] - @views res = norm(ρi[diagind(ρi)[end-pillow_i:end]], 1) * sqrt(dim_i) / pillow_i + @views res = norm(ρi[diagind(ρi)[(end-pillow_i):end]], 1) * sqrt(dim_i) / pillow_i if res > tol_list[i] increase_list[i] = true elseif res < tol_list[i] * 1e-2 && dim_i > 3 diff --git a/src/wigner.jl b/src/wigner.jl index dec636642..813457b6e 100644 --- a/src/wigner.jl +++ b/src/wigner.jl @@ -166,10 +166,10 @@ function _wigner_laguerre(ρ::AbstractArray, A::AbstractArray, W::AbstractArray, if method.parallel throw(ArgumentError("Parallel version is not implemented for dense matrices")) else - for m in 0:M-1 + for m in 0:(M-1) ρmn = ρ[m+1, m+1] abs(ρmn) > tol && (@. W += real(ρmn * (-1)^m * _genlaguerre(m, 0, B))) - for n in m+1:M-1 + for n in (m+1):(M-1) ρmn = ρ[m+1, n+1] # Γ_mn = sqrt(gamma(m+1) / gamma(n+1)) Γ_mn = sqrt(exp(loggamma(m + 1) - loggamma(n + 1))) # Is this a good trick? @@ -197,7 +197,7 @@ function _genlaguerre(n::Int, α::Number, x::T) where {T<:BlasFloat} α = convert(T, α) p0, p1 = one(T), -x + (α + 1) n == 0 && return p0 - for k in 1:n-1 + for k in 1:(n-1) p1, p0 = ((2k + α + 1) / (k + 1) - x / (k + 1)) * p1 - (k + α) / (k + 1) * p0, p1 end return p1 diff --git a/test/core-test/generalized_master_equation.jl b/test/core-test/generalized_master_equation.jl index 54a9ac8e9..03d2989e0 100644 --- a/test/core-test/generalized_master_equation.jl +++ b/test/core-test/generalized_master_equation.jl @@ -15,7 +15,7 @@ Tlist = [0, 0.0] E, U, L1 = liouvillian_generalized(H, fields, Tlist, N_trunc = N_trunc, tol = tol) - Ω = to_sparse((E'.-E)[1:N_trunc, 1:N_trunc], tol) + Ω = to_sparse((E' .- E)[1:N_trunc, 1:N_trunc], tol) H_d = Qobj(to_sparse((U'*H*U)[1:N_trunc, 1:N_trunc], tol)) Xp = Qobj(Ω .* to_sparse(triu((U'*(a+a')*U).data[1:N_trunc, 1:N_trunc], 1), tol)) @@ -33,7 +33,7 @@ Tlist = [0.2, 0.0] E, U, L1 = liouvillian_generalized(H, fields, Tlist, N_trunc = N_trunc, tol = tol) - Ω = to_sparse((E'.-E)[1:N_trunc, 1:N_trunc], tol) + Ω = to_sparse((E' .- E)[1:N_trunc, 1:N_trunc], tol) H_d = Qobj(to_sparse((U'*H*U)[1:N_trunc, 1:N_trunc], tol)) Xp = Qobj(Ω .* to_sparse(triu((U'*(a+a')*U).data[1:N_trunc, 1:N_trunc], 1), tol)) diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index 7b718c734..27066cdb1 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -17,13 +17,13 @@ i += 1 i <= M && (ϕ[i] = multisite_operator(latt, j => sigmap()) * ϕ[1]) end - for k in 1:N_modes-1 - for l in k+1:N_modes + for k in 1:(N_modes-1) + for l in (k+1):N_modes i += 1 i <= M && (ϕ[i] = multisite_operator(latt, k => sigmap(), l => sigmap()) * ϕ[1]) end end - for i in i+1:M + for i in (i+1):M ϕ[i] = QuantumObject(rand(ComplexF64, size(ϕ[1])[1]), dims = ϕ[1].dims) normalize!(ϕ[i]) end diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index 795ef7d5a..9981d5abb 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -68,8 +68,8 @@ ρ_ss2 = steadystate_fourier(H, -1im * 0.5 * H_t, 1im * 0.5 * H_t, 1, c_ops, solver = SSFloquetEffectiveLiouvillian()) - @test abs(sum(sol_me.expect[1, end-100:end]) / 101 - expect(e_ops[1], ρ_ss1)) < 1e-3 - @test abs(sum(sol_me.expect[1, end-100:end]) / 101 - expect(e_ops[1], ρ_ss2)) < 1e-3 + @test abs(sum(sol_me.expect[1, (end-100):end]) / 101 - expect(e_ops[1], ρ_ss1)) < 1e-3 + @test abs(sum(sol_me.expect[1, (end-100):end]) / 101 - expect(e_ops[1], ρ_ss2)) < 1e-3 @testset "Type Inference (steadystate_fourier)" begin @inferred steadystate_fourier( From 14b3de581d37cc2489fe233357199d6a46600bec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 08:56:09 +0200 Subject: [PATCH 247/329] Bump crate-ci/typos from 1.31.0 to 1.31.1 (#441) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 2d3a6928f..1f24114b0 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.31.0 \ No newline at end of file + uses: crate-ci/typos@v1.31.1 \ No newline at end of file From 6898156a0a904ae9f373e7eca6f62a908b890f4c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:10:48 +0900 Subject: [PATCH 248/329] Adjust extension test files structure (#442) --- .github/workflows/CI.yml | 4 ++-- test/ext-test/{ => cpu}/cairomakie/Project.toml | 0 test/ext-test/{ => cpu}/cairomakie/cairomakie_ext.jl | 0 test/runtests.jl | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename test/ext-test/{ => cpu}/cairomakie/Project.toml (100%) rename test/ext-test/{ => cpu}/cairomakie/cairomakie_ext.jl (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 57deaf5e8..c32f88395 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,7 +10,7 @@ on: - 'ext/**' - 'test/runtests.jl' - 'test/core-test/**' - - 'test/ext-test/cairomakie/**' + - 'test/ext-test/cpu/**' - 'Project.toml' pull_request: branches: @@ -21,7 +21,7 @@ on: - 'ext/**' - 'test/runtests.jl' - 'test/core-test/**' - - 'test/ext-test/cairomakie/**' + - 'test/ext-test/cpu/**' - 'Project.toml' types: - opened diff --git a/test/ext-test/cairomakie/Project.toml b/test/ext-test/cpu/cairomakie/Project.toml similarity index 100% rename from test/ext-test/cairomakie/Project.toml rename to test/ext-test/cpu/cairomakie/Project.toml diff --git a/test/ext-test/cairomakie/cairomakie_ext.jl b/test/ext-test/cpu/cairomakie/cairomakie_ext.jl similarity index 100% rename from test/ext-test/cairomakie/cairomakie_ext.jl rename to test/ext-test/cpu/cairomakie/cairomakie_ext.jl diff --git a/test/runtests.jl b/test/runtests.jl index aa768d718..c8a8efb4e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -51,7 +51,7 @@ if (GROUP == "All") || (GROUP == "Code-Quality") end if (GROUP == "CairoMakie_Ext")# || (GROUP == "All") - Pkg.activate("ext-test/cairomakie") + Pkg.activate("ext-test/cpu/cairomakie") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) Pkg.instantiate() @@ -59,7 +59,7 @@ if (GROUP == "CairoMakie_Ext")# || (GROUP == "All") QuantumToolbox.about() # CarioMakie is imported in the following script - include(joinpath(testdir, "ext-test", "cairomakie", "cairomakie_ext.jl")) + include(joinpath(testdir, "ext-test", "cpu", "cairomakie", "cairomakie_ext.jl")) end if (GROUP == "CUDA_Ext")# || (GROUP == "All") From 54145bf845f96403d07413958566190bf277e989 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 10 Apr 2025 11:55:57 +0200 Subject: [PATCH 249/329] Remove Re-export (#443) --- CHANGELOG.md | 2 + Project.toml | 2 - benchmarks/runbenchmarks.jl | 2 + docs/make.jl | 7 +- docs/src/getting_started/type_stability.md | 6 +- docs/src/resources/api.md | 6 ++ .../QuantumObject/QuantumObject.md | 4 + .../QuantumObject/QuantumObject_functions.md | 2 - docs/src/users_guide/tensor.md | 2 +- ext/QuantumToolboxCUDAExt.jl | 2 +- src/QuantumToolbox.jl | 35 ++++----- src/qobj/arithmetic_and_attributes.jl | 76 +++++++------------ src/qobj/dimensions.jl | 6 +- src/qobj/eigsolve.jl | 2 + src/qobj/functions.jl | 10 +-- src/qobj/operators.jl | 4 +- src/qobj/quantum_object.jl | 2 +- src/qobj/states.jl | 14 ++-- src/spin_lattice.jl | 2 +- src/utilities.jl | 6 +- test/core-test/eigenvalues_and_operators.jl | 2 +- test/core-test/quantum_objects.jl | 10 +-- test/core-test/quantum_objects_evo.jl | 2 +- test/core-test/states_and_operators.jl | 2 +- test/runtests.jl | 5 ++ 25 files changed, 111 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e06ec247b..98a8398b3 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 - Make CUDA conversion more general using Adapt.jl. ([#436], [#437]) - Make the generation of `fock` states non-mutating to support Zygote.jl. ([#438]) +- Remove Reexport.jl from the dependencies. ([#443]) ## [v0.29.1] Release date: 2025-03-07 @@ -193,3 +194,4 @@ Release date: 2024-11-13 [#436]: https://github.com/qutip/QuantumToolbox.jl/issues/436 [#437]: https://github.com/qutip/QuantumToolbox.jl/issues/437 [#438]: https://github.com/qutip/QuantumToolbox.jl/issues/438 +[#443]: https://github.com/qutip/QuantumToolbox.jl/issues/443 diff --git a/Project.toml b/Project.toml index fc5d42037..dbf8fe0ca 100644 --- a/Project.toml +++ b/Project.toml @@ -18,7 +18,6 @@ OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -Reexport = "189a3867-3050-52da-a836-e630ba90ab69" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -56,7 +55,6 @@ OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" Random = "1" -Reexport = "1" SciMLBase = "2" SciMLOperators = "0.3" SparseArrays = "1" diff --git a/benchmarks/runbenchmarks.jl b/benchmarks/runbenchmarks.jl index e921c81a4..a8419c516 100644 --- a/benchmarks/runbenchmarks.jl +++ b/benchmarks/runbenchmarks.jl @@ -1,4 +1,6 @@ using BenchmarkTools +using LinearAlgebra +using SparseArrays using QuantumToolbox using OrdinaryDiffEq using LinearSolve diff --git a/docs/make.jl b/docs/make.jl index e624cbee3..2a6548979 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,7 +10,12 @@ using Changelog # Load of packages required to compile the extension documentation using CairoMakie -DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) +doctest_setup = quote + using LinearAlgebra + using SparseArrays + using QuantumToolbox +end +DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, doctest_setup; recursive = true) # some options for `makedocs` const DRAFT = false # set `true` to disable cell evaluation diff --git a/docs/src/getting_started/type_stability.md b/docs/src/getting_started/type_stability.md index 1daabb063..2b5153665 100644 --- a/docs/src/getting_started/type_stability.md +++ b/docs/src/getting_started/type_stability.md @@ -199,7 +199,7 @@ Which returns a tensor of size `2x2x2x2x2x2`. Let's check the `@code_warntype`: @code_warntype reshape_operator_data([2, 2, 2]) ``` -We got a `Any` type, because the compiler doesn't know the size of the `dims` vector. We can fix this by using a `Tuple` (or `SVector`): +We got a `Any` type, because the compiler doesn't know the size of the `dims` vector. We can fix this by using a `Tuple` (or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl)): ```@example type-stability typeof(reshape_operator_data((2, 2, 2))) @@ -219,13 +219,15 @@ Finally, let's look at the benchmarks @benchmark reshape_operator_data($((2, 2, 2))) ``` -Which is an innocuous but huge difference in terms of performance. Hence, we highly recommend using `Tuple` or `SVector` when defining the dimensions of a user-defined [`QuantumObject`](@ref). +Which is an innocuous but huge difference in terms of performance. Hence, we highly recommend using `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) when defining the dimensions of a user-defined [`QuantumObject`](@ref). ## The use of `Val` in some `QuantumToolbox.jl` functions In some functions of `QuantumToolbox.jl`, you may find the use of the [`Val`](https://docs.julialang.org/en/v1/base/base/#Base.Val) type in the arguments. This is a trick to pass a value at compile time, and it is very useful to avoid type instabilities. Let's make a very simple example, where we want to create a Fock state ``|j\rangle`` of a given dimension `N`, and we give the possibility to create it as a sparse or dense vector. At first, we can write the function without using `Val`: ```@example type-stability +using SparseArrays + function my_fock(N::Int, j::Int = 0; sparse::Bool = false) if sparse array = sparsevec([j + 1], [1.0 + 0im], N) diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index eb768caca..fc1d9fc7c 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -1,5 +1,11 @@ ```@meta CurrentModule = QuantumToolbox + +DocTestSetup = quote + using LinearAlgebra + using SparseArrays + using QuantumToolbox +end ``` # [API](@id doc-API) diff --git a/docs/src/users_guide/QuantumObject/QuantumObject.md b/docs/src/users_guide/QuantumObject/QuantumObject.md index 1a1aead59..0ac2a6fb7 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject.md @@ -45,6 +45,8 @@ Qobj(rand(4, 4)) M = rand(ComplexF64, 4, 4) Qobj(M, dims = [2, 2]) # dims as Vector Qobj(M, dims = (2, 2)) # dims as Tuple (recommended) + +import QuantumToolbox: SVector # or using StaticArrays Qobj(M, dims = SVector(2, 2)) # dims as StaticArrays.SVector (recommended) ``` @@ -195,6 +197,8 @@ Vector{Int64}(v_d) ``` ```@example Qobj +using SparseArrays + v_s = SparseVector(v_d) ``` diff --git a/docs/src/users_guide/QuantumObject/QuantumObject_functions.md b/docs/src/users_guide/QuantumObject/QuantumObject_functions.md index ac2c62698..be76b0c06 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject_functions.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject_functions.md @@ -43,8 +43,6 @@ Here is a table that summarizes all the supported linear algebra functions and a - [`eigenenergies`](@ref): return eigenenergies (eigenvalues) - [`eigenstates`](@ref): return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) -- [`eigvals`](@ref): return eigenvalues -- [`eigen`](@ref): using dense eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) - [`eigsolve`](@ref): using sparse eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) - [`eigsolve_al`](@ref): using the Arnoldi-Lindblad eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) diff --git a/docs/src/users_guide/tensor.md b/docs/src/users_guide/tensor.md index 61e125358..fc0e6cdd0 100644 --- a/docs/src/users_guide/tensor.md +++ b/docs/src/users_guide/tensor.md @@ -108,7 +108,7 @@ H = 0.5 * ωa * σz + ωc * a' * a + g * (a' * σm + a * σm') The partial trace is an operation that reduces the dimension of a Hilbert space by eliminating some degrees of freedom by averaging (tracing). In this sense it is therefore the converse of the tensor product. It is useful when one is interested in only a part of a coupled quantum system. For open quantum systems, this typically involves tracing over the environment leaving only the system of interest. In `QuantumToolbox` the function [`ptrace`](@ref) is used to take partial traces. [`ptrace`](@ref) takes one [`QuantumObject`](@ref) as an input, and also one argument `sel`, which marks the component systems that should be kept, and all the other components are traced out. -Remember that the index of `Julia` starts from `1`, and all the elements in `sel` should be positive `Integer`. Therefore, the type of `sel` can be either `Integer`, `Tuple`, `SVector`, or `Vector`. +Remember that the index of `Julia` starts from `1`, and all the elements in `sel` should be positive `Integer`. Therefore, the type of `sel` can be either `Integer`, `Tuple`, `SVector` ([StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl)), or `Vector`. !!! warning "Beware of type-stability!" Although it supports also `Vector` type, it is recommended to use `Tuple` or `SVector` from [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) to improve performance. For a brief explanation on the impact of the type of `sel`, see the section [The Importance of Type-Stability](@ref doc:Type-Stability). diff --git a/ext/QuantumToolboxCUDAExt.jl b/ext/QuantumToolboxCUDAExt.jl index d05a65648..e676b4ea4 100644 --- a/ext/QuantumToolboxCUDAExt.jl +++ b/ext/QuantumToolboxCUDAExt.jl @@ -5,7 +5,7 @@ using QuantumToolbox: makeVal, getVal import QuantumToolbox: _sparse_similar, _convert_eltype_wordsize import CUDA: cu, CuArray, allowscalar import CUDA.CUSPARSE: CuSparseVector, CuSparseMatrixCSC, CuSparseMatrixCSR, AbstractCuSparseArray -import SparseArrays: SparseVector, SparseMatrixCSC +import SparseArrays: SparseVector, SparseMatrixCSC, sparse import CUDA.Adapt: adapt allowscalar(false) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 44b1aa84e..409bcfa54 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -1,19 +1,10 @@ module QuantumToolbox -# Re-export: -# 1. StaticArraysCore.SVector for the type of dims -# 2. basic functions in LinearAlgebra and SparseArrays -# 3. some functions in SciMLOperators -import Reexport: @reexport -@reexport import StaticArraysCore: SVector -@reexport using LinearAlgebra -@reexport using SparseArrays -@reexport import SciMLOperators: cache_operator, iscached, isconstant - -# other functions in LinearAlgebra -import LinearAlgebra: BlasReal, BlasInt, BlasFloat, BlasComplex, checksquare -import LinearAlgebra.BLAS: @blasfunc +# Standard Julia libraries +using LinearAlgebra +import LinearAlgebra: BlasInt, BlasFloat, checksquare import LinearAlgebra.LAPACK: hseqr! +using SparseArrays # SciML packages (for QobjEvo, OrdinaryDiffEq, and LinearSolve) import SciMLBase: @@ -42,6 +33,9 @@ import SciMLBase: AbstractODESolution import StochasticDiffEq: StochasticDiffEqAlgorithm, SRA2, SRIW1 import SciMLOperators: + cache_operator, + iscached, + isconstant, SciMLOperators, AbstractSciMLOperator, MatrixOperator, @@ -67,11 +61,18 @@ import IncompleteLU: ilu import Pkg import Random: AbstractRNG, default_rng, seed! import SpecialFunctions: loggamma -import StaticArraysCore: MVector +import StaticArraysCore: SVector, MVector + +# Export functions from the other modules + +# LinearAlgebra +export ishermitian, issymmetric, isposdef, dot, tr, svdvals, norm, normalize, normalize!, diag, Hermitian, Symmetric + +# SparseArrays +export permute -# Setting the number of threads to 1 allows -# to achieve better performances for more massive parallelizations -BLAS.set_num_threads(1) +# SciMLOperators +export cache_operator, iscached, isconstant # Utility include("utilities.jl") diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 2cf9503cc..a18a383c8 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -36,16 +36,16 @@ end for op in (:(+), :(-), :(*)) @eval begin - function LinearAlgebra.$op(A::AbstractQuantumObject, B::AbstractQuantumObject) + function Base.$op(A::AbstractQuantumObject, B::AbstractQuantumObject) check_dimensions(A, B) QType = promote_op_type(A, B) return QType($(op)(A.data, B.data), A.type, A.dimensions) end - LinearAlgebra.$op(A::AbstractQuantumObject) = get_typename_wrapper(A)($(op)(A.data), A.type, A.dimensions) + Base.$op(A::AbstractQuantumObject) = get_typename_wrapper(A)($(op)(A.data), A.type, A.dimensions) - LinearAlgebra.$op(n::T, A::AbstractQuantumObject) where {T<:Number} = + Base.$op(n::T, A::AbstractQuantumObject) where {T<:Number} = get_typename_wrapper(A)($(op)(n * I, A.data), A.type, A.dimensions) - LinearAlgebra.$op(A::AbstractQuantumObject, n::T) where {T<:Number} = + Base.$op(A::AbstractQuantumObject, n::T) where {T<:Number} = get_typename_wrapper(A)($(op)(A.data, n * I), A.type, A.dimensions) end end @@ -63,7 +63,7 @@ for ADimType in (:Dimensions, :GeneralDimensions) for BDimType in (:Dimensions, :GeneralDimensions) if ADimType == BDimType == :Dimensions @eval begin - function LinearAlgebra.:(*)( + function Base.:(*)( A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, ) @@ -74,7 +74,7 @@ for ADimType in (:Dimensions, :GeneralDimensions) end else @eval begin - function LinearAlgebra.:(*)( + function Base.:(*)( A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, ) @@ -91,57 +91,41 @@ for ADimType in (:Dimensions, :GeneralDimensions) end end -function LinearAlgebra.:(*)( - A::AbstractQuantumObject{OperatorQuantumObject}, - B::QuantumObject{KetQuantumObject,<:Dimensions}, -) +function Base.:(*)(A::AbstractQuantumObject{OperatorQuantumObject}, B::QuantumObject{KetQuantumObject,<:Dimensions}) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) return QuantumObject(A.data * B.data, Ket, Dimensions(get_dimensions_to(A))) end -function LinearAlgebra.:(*)( - A::QuantumObject{BraQuantumObject,<:Dimensions}, - B::AbstractQuantumObject{OperatorQuantumObject}, -) +function Base.:(*)(A::QuantumObject{BraQuantumObject,<:Dimensions}, B::AbstractQuantumObject{OperatorQuantumObject}) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) return QuantumObject(A.data * B.data, Bra, Dimensions(get_dimensions_from(B))) end -function LinearAlgebra.:(*)(A::QuantumObject{KetQuantumObject}, B::QuantumObject{BraQuantumObject}) +function Base.:(*)(A::QuantumObject{KetQuantumObject}, B::QuantumObject{BraQuantumObject}) check_dimensions(A, B) return QuantumObject(A.data * B.data, Operator, A.dimensions) # to align with QuTiP, don't use kron(A, B) to do it. end -function LinearAlgebra.:(*)(A::QuantumObject{BraQuantumObject}, B::QuantumObject{KetQuantumObject}) +function Base.:(*)(A::QuantumObject{BraQuantumObject}, B::QuantumObject{KetQuantumObject}) check_dimensions(A, B) return A.data * B.data end -function LinearAlgebra.:(*)( - A::AbstractQuantumObject{SuperOperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, -) +function Base.:(*)(A::AbstractQuantumObject{SuperOperatorQuantumObject}, B::QuantumObject{OperatorQuantumObject}) check_dimensions(A, B) return QuantumObject(vec2mat(A.data * mat2vec(B.data)), Operator, A.dimensions) end -function LinearAlgebra.:(*)(A::QuantumObject{OperatorBraQuantumObject}, B::QuantumObject{OperatorKetQuantumObject}) +function Base.:(*)(A::QuantumObject{OperatorBraQuantumObject}, B::QuantumObject{OperatorKetQuantumObject}) check_dimensions(A, B) return A.data * B.data end -function LinearAlgebra.:(*)( - A::AbstractQuantumObject{SuperOperatorQuantumObject}, - B::QuantumObject{OperatorKetQuantumObject}, -) +function Base.:(*)(A::AbstractQuantumObject{SuperOperatorQuantumObject}, B::QuantumObject{OperatorKetQuantumObject}) check_dimensions(A, B) return QuantumObject(A.data * B.data, OperatorKet, A.dimensions) end -function LinearAlgebra.:(*)( - A::QuantumObject{OperatorBraQuantumObject}, - B::AbstractQuantumObject{SuperOperatorQuantumObject}, -) +function Base.:(*)(A::QuantumObject{OperatorBraQuantumObject}, B::AbstractQuantumObject{SuperOperatorQuantumObject}) check_dimensions(A, B) return QuantumObject(A.data * B.data, OperatorBra, A.dimensions) end -LinearAlgebra.:(^)(A::QuantumObject, n::T) where {T<:Number} = QuantumObject(^(A.data, n), A.type, A.dimensions) -LinearAlgebra.:(/)(A::AbstractQuantumObject, n::T) where {T<:Number} = - get_typename_wrapper(A)(A.data / n, A.type, A.dimensions) +Base.:(^)(A::QuantumObject, n::T) where {T<:Number} = QuantumObject(^(A.data, n), A.type, A.dimensions) +Base.:(/)(A::AbstractQuantumObject, n::T) where {T<:Number} = get_typename_wrapper(A)(A.data / n, A.type, A.dimensions) @doc raw""" A ⋅ B @@ -221,7 +205,7 @@ Base.conj(A::AbstractQuantumObject) = get_typename_wrapper(A)(conj(A.data), A.ty Lazy matrix transpose of the [`AbstractQuantumObject`](@ref). """ -LinearAlgebra.transpose( +Base.transpose( A::AbstractQuantumObject{OpType}, ) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = get_typename_wrapper(A)(transpose(A.data), A.type, transpose(A.dimensions)) @@ -236,15 +220,13 @@ Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) !!! note `A'` and `dag(A)` are synonyms of `adjoint(A)`. """ -LinearAlgebra.adjoint( - A::AbstractQuantumObject{OpType}, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.adjoint(A::AbstractQuantumObject{OpType}) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = get_typename_wrapper(A)(adjoint(A.data), A.type, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{KetQuantumObject}) = QuantumObject(adjoint(A.data), Bra, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{BraQuantumObject}) = QuantumObject(adjoint(A.data), Ket, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{OperatorKetQuantumObject}) = +Base.adjoint(A::QuantumObject{KetQuantumObject}) = QuantumObject(adjoint(A.data), Bra, adjoint(A.dimensions)) +Base.adjoint(A::QuantumObject{BraQuantumObject}) = QuantumObject(adjoint(A.data), Ket, adjoint(A.dimensions)) +Base.adjoint(A::QuantumObject{OperatorKetQuantumObject}) = QuantumObject(adjoint(A.data), OperatorBra, adjoint(A.dimensions)) -LinearAlgebra.adjoint(A::QuantumObject{OperatorBraQuantumObject}) = +Base.adjoint(A::QuantumObject{OperatorBraQuantumObject}) = QuantumObject(adjoint(A.data), OperatorKet, adjoint(A.dimensions)) @doc raw""" @@ -415,7 +397,7 @@ Matrix square root of [`QuantumObject`](@ref) !!! note `√(A)` (where `√` can be typed by tab-completing `\sqrt` in the REPL) is a synonym of `sqrt(A)`. """ -LinearAlgebra.sqrt(A::QuantumObject) = QuantumObject(sqrt(to_dense(A.data)), A.type, A.dimensions) +Base.sqrt(A::QuantumObject) = QuantumObject(sqrt(to_dense(A.data)), A.type, A.dimensions) @doc raw""" log(A::QuantumObject) @@ -424,7 +406,7 @@ Matrix logarithm of [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.log(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.log(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = QuantumObject(log(to_dense(A.data)), A.type, A.dimensions) @doc raw""" @@ -434,11 +416,11 @@ Matrix exponential of [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.exp( +Base.exp( A::QuantumObject{ObjType,DimsType,<:AbstractMatrix}, ) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = QuantumObject(to_sparse(exp(A.data)), A.type, A.dimensions) -LinearAlgebra.exp( +Base.exp( A::QuantumObject{ObjType,DimsType,<:AbstractSparseMatrix}, ) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = QuantumObject(_spexp(A.data), A.type, A.dimensions) @@ -483,7 +465,7 @@ Matrix sine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.sin(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.sin(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) - exp(-1im * A)) / 2im @doc raw""" @@ -495,7 +477,7 @@ Matrix cosine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.cos(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.cos(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (exp(1im * A) + exp(-1im * A)) / 2 @doc raw""" @@ -768,7 +750,7 @@ true ``` !!! warning "Beware of type-stability!" - It is highly recommended to use `permute(A, order)` with `order` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. + It is highly recommended to use `permute(A, order)` with `order` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ function SparseArrays.permute( A::QuantumObject{ObjType}, diff --git a/src/qobj/dimensions.jl b/src/qobj/dimensions.jl index 0fd19f3a9..a10d73c64 100644 --- a/src/qobj/dimensions.jl +++ b/src/qobj/dimensions.jl @@ -91,9 +91,9 @@ _get_space_size(s::AbstractSpace)::Int = s.size Base.prod(dims::Dimensions) = prod(dims.to) Base.prod(spaces::NTuple{N,AbstractSpace}) where {N} = prod(_get_space_size, spaces) -LinearAlgebra.transpose(dimensions::Dimensions) = dimensions -LinearAlgebra.transpose(dimensions::GeneralDimensions) = GeneralDimensions(dimensions.from, dimensions.to) # switch `to` and `from` -LinearAlgebra.adjoint(dimensions::AbstractDimensions) = transpose(dimensions) +Base.transpose(dimensions::Dimensions) = dimensions +Base.transpose(dimensions::GeneralDimensions) = GeneralDimensions(dimensions.from, dimensions.to) # switch `to` and `from` +Base.adjoint(dimensions::AbstractDimensions) = transpose(dimensions) # this is used to show `dims` for Qobj and QobjEvo _get_dims_string(dimensions::Dimensions) = string(dimensions_to_dims(dimensions)) diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index d68a3a163..986a25321 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -444,6 +444,8 @@ julia> a = destroy(5); julia> H = a + a'; +julia> using LinearAlgebra; + julia> E, ψ, U = eigen(H) EigsolveResult: type=Operator dims=[5] values: diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 4cb8d6c38..694e6b05f 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -187,7 +187,7 @@ julia> a.dims, O.dims ([20], [20, 20]) ``` """ -function LinearAlgebra.kron( +function Base.kron( A::AbstractQuantumObject{OpType,<:Dimensions}, B::AbstractQuantumObject{OpType,<:Dimensions}, ) where {OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} @@ -201,7 +201,7 @@ for ADimType in (:Dimensions, :GeneralDimensions) for BDimType in (:Dimensions, :GeneralDimensions) if !(ADimType == BDimType == :Dimensions) # not for this case because it's already implemented @eval begin - function LinearAlgebra.kron( + function Base.kron( A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, ) @@ -226,7 +226,7 @@ for AOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) for BOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) if (AOpType != BOpType) @eval begin - function LinearAlgebra.kron(A::AbstractQuantumObject{$AOpType}, B::AbstractQuantumObject{$BOpType}) + function Base.kron(A::AbstractQuantumObject{$AOpType}, B::AbstractQuantumObject{$BOpType}) QType = promote_op_type(A, B) _lazy_tensor_warning(A.data, B.data) return QType( @@ -243,8 +243,8 @@ for AOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) end end -LinearAlgebra.kron(A::AbstractQuantumObject) = A -function LinearAlgebra.kron(A::Vector{<:AbstractQuantumObject}) +Base.kron(A::AbstractQuantumObject) = A +function Base.kron(A::Vector{<:AbstractQuantumObject}) @warn "`tensor(A)` or `kron(A)` with `A` is a `Vector` can hurt performance. Try to use `tensor(A...)` or `kron(A...)` instead." return kron(A...) end diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 90d6d1739..d7c460fdb 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -29,7 +29,7 @@ The `distribution` specifies which of the method used to obtain the unitary matr 1. [F. Mezzadri, How to generate random matrices from the classical compact groups, arXiv:math-ph/0609050 (2007)](https://arxiv.org/abs/math-ph/0609050) !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `rand_unitary(dimensions, Val(distribution))` instead of `rand_unitary(dimensions, distribution)`. Also, put `dimensions` as `Tuple` or `SVector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `rand_unitary(dimensions, Val(distribution))` instead of `rand_unitary(dimensions, distribution)`. Also, put `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl). See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ rand_unitary(dimensions::Int, distribution::Union{Symbol,Val} = Val(:haar)) = rand_unitary(SVector(dimensions), makeVal(distribution)) @@ -553,7 +553,7 @@ The `dimensions` can be either the following types: where ``\omega = \exp(\frac{2 \pi i}{N})``. !!! warning "Beware of type-stability!" - It is highly recommended to use `qft(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. + It is highly recommended to use `qft(dimensions)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ qft(dimensions::Int) = QuantumObject(_qft_op(dimensions), Operator, dimensions) qft(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) = diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 9f760c876..763113776 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -37,7 +37,7 @@ julia> a isa QuantumObject true julia> a.dims -1-element SVector{1, Int64} with indices SOneTo(1): +1-element StaticArraysCore.SVector{1, Int64} with indices SOneTo(1): 20 julia> a.dimensions diff --git a/src/qobj/states.jl b/src/qobj/states.jl index c2759c9a4..4474ebd16 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -17,7 +17,7 @@ The `dimensions` can be either the following types: - `dimensions::Union{Dimensions,AbstractVector{Int}, Tuple}`: list of dimensions representing the each number of basis in the subsystems. !!! warning "Beware of type-stability!" - It is highly recommended to use `zero_ket(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. + It is highly recommended to use `zero_ket(dimensions)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ zero_ket(dimensions::Int) = QuantumObject(zeros(ComplexF64, dimensions), Ket, dimensions) zero_ket(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) = @@ -31,7 +31,7 @@ Generates a fock state ``\ket{\psi}`` of dimension `N`. It is also possible to specify the list of dimensions `dims` if different subsystems are present. !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `fock(N, j, dims=dims, sparse=Val(sparse))` instead of `fock(N, j, dims=dims, sparse=sparse)`. Consider also to use `dims` as a `Tuple` or `SVector` instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `fock(N, j, dims=dims, sparse=Val(sparse))` instead of `fock(N, j, dims=dims, sparse=sparse)`. Consider also to use `dims` as a `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ function fock(N::Int, j::Int = 0; dims::Union{Int,AbstractVector{Int},Tuple} = N, sparse::Union{Bool,Val} = Val(false)) if getVal(sparse) @@ -50,7 +50,7 @@ Generates a fock state like [`fock`](@ref). It is also possible to specify the list of dimensions `dims` if different subsystems are present. !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `basis(N, j, dims=dims)` with `dims` as a `Tuple` or `SVector` instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `basis(N, j, dims=dims)` with `dims` as a `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ basis(N::Int, j::Int = 0; dims::Union{Int,AbstractVector{Int},Tuple} = N) = fock(N, j, dims = dims) @@ -73,7 +73,7 @@ The `dimensions` can be either the following types: - `dimensions::Union{Dimensions,AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `rand_ket(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `rand_ket(dimensions)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ rand_ket(dimensions::Int) = rand_ket(SVector(dimensions)) function rand_ket(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) @@ -90,7 +90,7 @@ Density matrix representation of a Fock state. Constructed via outer product of [`fock`](@ref). !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `fock_dm(N, j, dims=dims, sparse=Val(sparse))` instead of `fock_dm(N, j, dims=dims, sparse=sparse)`. Consider also to use `dims` as a `Tuple` or `SVector` instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `fock_dm(N, j, dims=dims, sparse=Val(sparse))` instead of `fock_dm(N, j, dims=dims, sparse=sparse)`. Consider also to use `dims` as a `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ function fock_dm( N::Int, @@ -146,7 +146,7 @@ The `dimensions` can be either the following types: - `dimensions::Union{Dimensions,AbstractVector{Int},Tuple}`: list of dimensions representing the each number of basis in the subsystems. !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `maximally_mixed_dm(dimensions)` with `dimensions` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `maximally_mixed_dm(dimensions)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ maximally_mixed_dm(dimensions::Int) = QuantumObject(I(dimensions) / complex(dimensions), Operator, SVector(dimensions)) function maximally_mixed_dm(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) @@ -166,7 +166,7 @@ The `dimensions` can be either the following types: The default keyword argument `rank = prod(dimensions)` (full rank). !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `rand_dm(dimensions; rank=rank)` with `dimensions` as `Tuple` or `SVector` instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `rand_dm(dimensions; rank=rank)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) instead of `Vector`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. # References - [J. Ginibre, Statistical ensembles of complex, quaternion, and real matrices, Journal of Mathematical Physics 6.3 (1965): 440-449](https://doi.org/10.1063/1.1704292) diff --git a/src/spin_lattice.jl b/src/spin_lattice.jl index 72ee4f38f..1abfff235 100644 --- a/src/spin_lattice.jl +++ b/src/spin_lattice.jl @@ -31,7 +31,7 @@ A Julia function for generating a multi-site operator ``\\hat{O} = \\hat{O}_i \\ julia> op = multisite_operator(Val(8), 5=>sigmax(), 7=>sigmaz()); julia> op.dims -8-element SVector{8, Int64} with indices SOneTo(8): +8-element StaticArraysCore.SVector{8, Int64} with indices SOneTo(8): 2 2 2 diff --git a/src/utilities.jl b/src/utilities.jl index a3547cf61..ebe9481f4 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -151,9 +151,11 @@ _non_static_array_warning(argname, arg::Tuple{}) = throw(ArgumentError("The argument $argname must be a Tuple or a StaticVector of non-zero length.")) _non_static_array_warning(argname, arg::Union{SVector{N,T},MVector{N,T},NTuple{N,T}}) where {N,T} = nothing _non_static_array_warning(argname, arg::AbstractVector{T}) where {T} = - @warn "The argument $argname should be a Tuple or a StaticVector for better performance. Try to use `$argname = $(Tuple(arg))` or `$argname = SVector(" * + @warn "The argument $argname should be a Tuple or a StaticVector for better performance. Try to use `$argname = $(Tuple(arg))` instead of `$argname = $arg`. " * + "Alternatively, you can do `import QuantumToolbox: SVector` " * + "and use `$argname = SVector(" * join(arg, ", ") * - ")` instead of `$argname = $arg`." maxlog = 1 + ")`." maxlog = 1 # lazy tensor warning for AType in (:AbstractArray, :AbstractSciMLOperator) diff --git a/test/core-test/eigenvalues_and_operators.jl b/test/core-test/eigenvalues_and_operators.jl index 3faebf0dc..0b4959beb 100644 --- a/test/core-test/eigenvalues_and_operators.jl +++ b/test/core-test/eigenvalues_and_operators.jl @@ -58,7 +58,7 @@ # eigen solve for general matrices vals, _, vecs = eigsolve(L.data, sigma = 0.01, eigvals = 10, krylovdim = 50) - vals2, vecs2 = eigen(to_dense(L.data)) + vals2, _, vecs2 = eigenstates(L) vals3, state3, vecs3 = eigsolve_al(L, 1 \ (40 * κ), eigvals = 10, krylovdim = 50) idxs = sortperm(vals2, by = abs) vals2 = vals2[idxs][1:10] diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 8aebcc244..52adfb7fb 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -44,7 +44,7 @@ @test_throws DomainError Qobj(rand(2, 2), dims = (2, -2)) @test_logs ( :warn, - "The argument dims should be a Tuple or a StaticVector for better performance. Try to use `dims = (2, 2)` or `dims = SVector(2, 2)` instead of `dims = [2, 2]`.", + "The argument dims should be a Tuple or a StaticVector for better performance. Try to use `dims = (2, 2)` instead of `dims = [2, 2]`. Alternatively, you can do `import QuantumToolbox: SVector` and use `dims = SVector(2, 2)`.", ) Qobj(rand(4, 4), dims = [2, 2]) end @@ -178,7 +178,7 @@ a = sprand(ComplexF64, 100, 100, 0.1) a2 = Qobj(a) a3 = Qobj(a, type = SuperOperator) - a4 = sparse(a2) + a4 = to_sparse(a2) @test isequal(a4, a2) == true @test isequal(a4, a3) == false @test a4 ≈ a2 @@ -412,7 +412,7 @@ @testset "projection" begin N = 10 ψ = fock(N, 3) - @test proj(ψ) ≈ proj(ψ') ≈ sparse(ket2dm(ψ)) ≈ projection(N, 3, 3) + @test proj(ψ) ≈ proj(ψ') ≈ to_sparse(ket2dm(ψ)) ≈ projection(N, 3, 3) @test isket(ψ') == false @test isbra(ψ') == true @test shape(ψ) == (N,) @@ -715,11 +715,11 @@ @test ptrace(ρtotal, (1, 3, 4)) ≈ ptrace(ρtotal, (3, 1, 4)) # check sort of sel @test_logs ( :warn, - "The argument sel should be a Tuple or a StaticVector for better performance. Try to use `sel = (1, 2)` or `sel = SVector(1, 2)` instead of `sel = [1, 2]`.", + "The argument sel should be a Tuple or a StaticVector for better performance. Try to use `sel = (1, 2)` instead of `sel = [1, 2]`. Alternatively, you can do `import QuantumToolbox: SVector` and use `sel = SVector(1, 2)`.", ) ptrace(ψtotal, [1, 2]) @test_logs ( :warn, - "The argument sel should be a Tuple or a StaticVector for better performance. Try to use `sel = (1, 2)` or `sel = SVector(1, 2)` instead of `sel = [1, 2]`.", + "The argument sel should be a Tuple or a StaticVector for better performance. Try to use `sel = (1, 2)` instead of `sel = [1, 2]`. Alternatively, you can do `import QuantumToolbox: SVector` and use `sel = SVector(1, 2)`.", ) ptrace(ρtotal, [1, 2]) @test_throws ArgumentError ptrace(ψtotal, 0) @test_throws ArgumentError ptrace(ψtotal, 5) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index fec8235c0..5367599fe 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -31,7 +31,7 @@ @test_throws DomainError QobjEvo(a, dims = (2, -2)) @test_logs ( :warn, - "The argument dims should be a Tuple or a StaticVector for better performance. Try to use `dims = (2, 2)` or `dims = SVector(2, 2)` instead of `dims = [2, 2]`.", + "The argument dims should be a Tuple or a StaticVector for better performance. Try to use `dims = (2, 2)` instead of `dims = [2, 2]`. Alternatively, you can do `import QuantumToolbox: SVector` and use `dims = SVector(2, 2)`.", ) QobjEvo(MatrixOperator(rand(4, 4)), dims = [2, 2]) end diff --git a/test/core-test/states_and_operators.jl b/test/core-test/states_and_operators.jl index f43b3e2a3..0cdfbd9e5 100644 --- a/test/core-test/states_and_operators.jl +++ b/test/core-test/states_and_operators.jl @@ -283,7 +283,7 @@ sites = 4 SIZE = 2^sites dims = ntuple(i -> 2, Val(sites)) - Q_iden = Qobj(sparse((1.0 + 0.0im) * LinearAlgebra.I, SIZE, SIZE); dims = dims) + Q_iden = Qobj(sparse((1.0 + 0.0im) * I, SIZE, SIZE); dims = dims) Q_zero = Qobj(spzeros(ComplexF64, SIZE, SIZE); dims = dims) for i in 1:sites d_i = fdestroy(sites, i) diff --git a/test/runtests.jl b/test/runtests.jl index c8a8efb4e..d8670d9d3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,11 @@ using Test using Pkg +# Importing only the necessary functions to keep track the re-export of the functions +import LinearAlgebra: Diagonal, I, mul!, triu, tril, triu!, tril! +import SparseArrays: sparse, sprand, spzeros, spdiagm, nnz, SparseVector, SparseMatrixCSC, AbstractSparseMatrix +import StaticArraysCore: SVector + const GROUP = get(ENV, "GROUP", "All") const testdir = dirname(@__FILE__) From e4122097d18d824bd99c0628b75dda5cfe8b4b9e Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 10 Apr 2025 20:46:49 +0900 Subject: [PATCH 250/329] [Doc] Add a list of recommended packages in main page (#445) --- docs/src/index.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/src/index.md b/docs/src/index.md index 148d29327..0b32c754a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -74,3 +74,25 @@ using QuantumToolbox QuantumToolbox.versioninfo() QuantumToolbox.about() ``` + +# [Other Useful Packages](@id doc:Other-Useful-Packages) + +In order to get a better experience and take full advantage of `QuantumToolbox`, we recommend the following external packages: + +- Standard `Julia` Libraries: (recommended to also `using` with `QuantumToolbox.jl`) + - [`LinearAlgebra.jl`](https://github.com/JuliaLang/LinearAlgebra.jl) + - [`SparseArrays.jl`](https://github.com/JuliaSparse/SparseArrays.jl) +- Solver `alg`orithms: + - [`DifferentialEquations.jl`](https://github.com/SciML/DifferentialEquations.jl) or [`OrdinaryDiffEq.jl`](https://github.com/SciML/OrdinaryDiffEq.jl) + - [`LinearSolve.jl`](https://github.com/SciML/LinearSolve.jl) +- GPU support: + - [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl) +- Distributed Computing support: + - [`Disributed.jl`](https://github.com/JuliaLang/Distributed.jl) + - [`SlurmClusterManager.jl`](https://github.com/JuliaParallel/SlurmClusterManager.jl) +- Plotting Libraries: + - [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) +- Packages for other advanced usage: + - [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) + - [`SciMLOperators.jl`](https://github.com/SciML/SciMLOperators.jl) + - [`DiffEqCallbacks.jl`](https://github.com/SciML/DiffEqCallbacks.jl) From a07b9918ca0f965510af60552688ba4dce7c7d15 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 12 Apr 2025 07:49:03 +0200 Subject: [PATCH 251/329] Add support to automatic differentiation for `sesolve` and `mesolve` (#440) --- .github/workflows/CI.yml | 5 ++ CHANGELOG.md | 2 + Project.toml | 5 +- ext/QuantumToolboxChainRulesCoreExt.jl | 14 +++++ src/qobj/quantum_object_evo.jl | 31 ++++++--- src/time_evolution/mesolve.jl | 8 ++- src/time_evolution/sesolve.jl | 8 ++- test/core-test/quantum_objects_evo.jl | 2 +- test/ext-test/cpu/autodiff/Project.toml | 5 ++ test/ext-test/cpu/autodiff/zygote.jl | 84 +++++++++++++++++++++++++ test/runtests.jl | 17 ++++- 11 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 ext/QuantumToolboxChainRulesCoreExt.jl create mode 100644 test/ext-test/cpu/autodiff/Project.toml create mode 100644 test/ext-test/cpu/autodiff/zygote.jl diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c32f88395..f34372fb4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -68,6 +68,11 @@ jobs: os: 'ubuntu-latest' arch: 'x64' group: 'CairoMakie_Ext' + - version: '1' + node: + os: 'ubuntu-latest' + arch: 'x64' + group: 'AutoDiff_Ext' steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a8398b3..e4b73f275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make CUDA conversion more general using Adapt.jl. ([#436], [#437]) - Make the generation of `fock` states non-mutating to support Zygote.jl. ([#438]) - Remove Reexport.jl from the dependencies. ([#443]) +- Add support for automatic differentiation for `sesolve` and `mesolve`. ([#440]) ## [v0.29.1] Release date: 2025-03-07 @@ -194,4 +195,5 @@ Release date: 2024-11-13 [#436]: https://github.com/qutip/QuantumToolbox.jl/issues/436 [#437]: https://github.com/qutip/QuantumToolbox.jl/issues/437 [#438]: https://github.com/qutip/QuantumToolbox.jl/issues/438 +[#440]: https://github.com/qutip/QuantumToolbox.jl/issues/440 [#443]: https://github.com/qutip/QuantumToolbox.jl/issues/443 diff --git a/Project.toml b/Project.toml index dbf8fe0ca..87766530a 100644 --- a/Project.toml +++ b/Project.toml @@ -28,18 +28,21 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" [weakdeps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" [extensions] -QuantumToolboxCUDAExt = "CUDA" QuantumToolboxCairoMakieExt = "CairoMakie" +QuantumToolboxChainRulesCoreExt = "ChainRulesCore" +QuantumToolboxCUDAExt = "CUDA" QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] [compat] ArrayInterface = "6, 7" CUDA = "5" CairoMakie = "0.12, 0.13" +ChainRulesCore = "1" DiffEqBase = "6" DiffEqCallbacks = "4.2.1 - 4" DiffEqNoiseProcess = "5" diff --git a/ext/QuantumToolboxChainRulesCoreExt.jl b/ext/QuantumToolboxChainRulesCoreExt.jl new file mode 100644 index 000000000..968d2e674 --- /dev/null +++ b/ext/QuantumToolboxChainRulesCoreExt.jl @@ -0,0 +1,14 @@ +module QuantumToolboxChainRulesCoreExt + +using LinearAlgebra +import QuantumToolbox: QuantumObject +import ChainRulesCore: rrule, NoTangent, Tangent + +function rrule(::Type{QuantumObject}, data, type, dimensions) + obj = QuantumObject(data, type, dimensions) + f_pullback(Δobj) = (NoTangent(), Δobj.data, NoTangent(), NoTangent()) + f_pullback(Δobj_data::AbstractArray) = (NoTangent(), Δobj_data, NoTangent(), NoTangent()) + return obj, f_pullback +end + +end diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 4d97d90d4..9f9364462 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -360,7 +360,7 @@ function QuantumObjectEvolution( if α isa Nothing return QuantumObjectEvolution(op.data, type, op.dimensions) end - return QuantumObjectEvolution(α * op.data, type, op.dimensions) + return QuantumObjectEvolution(_promote_to_scimloperator(α, op.data), type, op.dimensions) end #= @@ -397,7 +397,6 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` ) op = :(op_func_list[$i][1]) - data_type = op_type.parameters[1] dims_expr = (dims_expr..., :($op.dimensions)) func_methods_expr = (func_methods_expr..., :(methods(op_func_list[$i][2], [Any, Real]))) # [Any, Real] means each func must accept 2 arguments if i == 1 @@ -409,7 +408,6 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` (isoper(op_type) || issuper(op_type)) || throw(ArgumentError("The element must be a Operator or SuperOperator.")) - data_type = op_type.parameters[1] dims_expr = (dims_expr..., :(op_func_list[$i].dimensions)) if i == 1 first_op = :(op_func_list[$i]) @@ -445,16 +443,33 @@ function _make_SciMLOperator(op_func::Tuple, α) T = eltype(op_func[1]) update_func = (a, u, p, t) -> op_func[2](p, t) if α isa Nothing - return ScalarOperator(zero(T), update_func) * MatrixOperator(op_func[1].data) + return ScalarOperator(zero(T), update_func) * _promote_to_scimloperator(op_func[1].data) end - return ScalarOperator(zero(T), update_func) * MatrixOperator(α * op_func[1].data) + return ScalarOperator(zero(T), update_func) * _promote_to_scimloperator(α, op_func[1].data) end -function _make_SciMLOperator(op::QuantumObject, α) +function _make_SciMLOperator(op::AbstractQuantumObject, α) if α isa Nothing - return MatrixOperator(op.data) + return _promote_to_scimloperator(op.data) end - return MatrixOperator(α * op.data) + return _promote_to_scimloperator(α, op.data) +end + +_promote_to_scimloperator(data::AbstractMatrix) = MatrixOperator(data) +_promote_to_scimloperator(data::AbstractSciMLOperator) = data +# TODO: The following special cases can be simplified after +# https://github.com/SciML/SciMLOperators.jl/pull/264 is merged +_promote_to_scimloperator(α::Number, data::AbstractMatrix) = MatrixOperator(α * data) +function _promote_to_scimloperator(α::Number, data::MatrixOperator) + isconstant(data) && return MatrixOperator(α * data.A) + return ScaledOperator(α, data) # Going back to the generic case +end +function _promote_to_scimloperator(α::Number, data::ScaledOperator) + isconstant(data.λ) && return ScaledOperator(α * data.λ, data.L) + return ScaledOperator(data.λ, _promote_to_scimloperator(α, data.L)) # Try to propagate the rule +end +function _promote_to_scimloperator(α::Number, data::AbstractSciMLOperator) + return α * data # Going back to the generic case end @doc raw""" diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index f0796f0c4..268535aa8 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -87,7 +87,13 @@ function mesolveProblem( kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncMESolve) tspan = (tlist[1], tlist[end]) - prob = ODEProblem{getVal(inplace),FullSpecialize}(L, ρ0, tspan, params; kwargs3...) + + # TODO: Remove this when https://github.com/SciML/SciMLSensitivity.jl/issues/1181 is fixed + if haskey(kwargs3, :sensealg) + prob = ODEProblem{getVal(inplace)}(L, ρ0, tspan, params; kwargs3...) + else + prob = ODEProblem{getVal(inplace),FullSpecialize}(L, ρ0, tspan, params; kwargs3...) + end return TimeEvolutionProblem(prob, tlist, L_evo.dimensions, (isoperket = Val(isoperket(ψ0)),)) end diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 571b1a396..66f1df343 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -73,7 +73,13 @@ function sesolveProblem( kwargs3 = _generate_se_me_kwargs(e_ops, makeVal(progress_bar), tlist, kwargs2, SaveFuncSESolve) tspan = (tlist[1], tlist[end]) - prob = ODEProblem{getVal(inplace),FullSpecialize}(U, ψ0, tspan, params; kwargs3...) + + # TODO: Remove this when https://github.com/SciML/SciMLSensitivity.jl/issues/1181 is fixed + if haskey(kwargs3, :sensealg) + prob = ODEProblem{getVal(inplace)}(U, ψ0, tspan, params; kwargs3...) + else + prob = ODEProblem{getVal(inplace),FullSpecialize}(U, ψ0, tspan, params; kwargs3...) + end return TimeEvolutionProblem(prob, tlist, H_evo.dimensions) end diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 5367599fe..1e923cf31 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -19,7 +19,7 @@ @test_throws DimensionMismatch QobjEvo(a, type = SuperOperator) ψ = fock(10, 3) - @test_throws TypeError QobjEvo(ψ) + @test_throws MethodError QobjEvo(ψ) end # unsupported type of dims diff --git a/test/ext-test/cpu/autodiff/Project.toml b/test/ext-test/cpu/autodiff/Project.toml new file mode 100644 index 000000000..314afe38b --- /dev/null +++ b/test/ext-test/cpu/autodiff/Project.toml @@ -0,0 +1,5 @@ +[deps] +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" +SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" diff --git a/test/ext-test/cpu/autodiff/zygote.jl b/test/ext-test/cpu/autodiff/zygote.jl new file mode 100644 index 000000000..824da7a12 --- /dev/null +++ b/test/ext-test/cpu/autodiff/zygote.jl @@ -0,0 +1,84 @@ +@testset "Zygote.jl Autodiff" verbose=true begin + @testset "sesolve" begin + coef_Ω(p, t) = p[1] + + H = QobjEvo(sigmax(), coef_Ω) + ψ0 = fock(2, 1) + t_max = 10 + + function my_f_sesolve(p) + tlist = range(0, t_max, 100) + + sol = sesolve( + H, + ψ0, + tlist, + progress_bar = Val(false), + params = p, + sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), + ) + + return real(expect(projection(2, 0, 0), sol.states[end])) + end + + # Analytical solution + my_f_analytic(Ω) = abs2(sin(Ω * t_max)) + my_f_analytic_deriv(Ω) = 2 * t_max * sin(Ω * t_max) * cos(Ω * t_max) + + Ω = 1.0 + params = [Ω] + + my_f_analytic(Ω) + my_f_sesolve(params) + + grad_qt = Zygote.gradient(my_f_sesolve, params)[1] + grad_exact = [my_f_analytic_deriv(params[1])] + + @test grad_qt ≈ grad_exact atol=1e-6 + end + + @testset "mesolve" begin + N = 20 + a = destroy(N) + + coef_Δ(p, t) = p[1] + coef_F(p, t) = p[2] + coef_γ(p, t) = sqrt(p[3]) + + H = QobjEvo(a' * a, coef_Δ) + QobjEvo(a + a', coef_F) + c_ops = [QobjEvo(a, coef_γ)] + L = liouvillian(H, c_ops) + + ψ0 = fock(N, 0) + + function my_f_mesolve(p) + tlist = range(0, 40, 100) + + sol = mesolve( + L, + ψ0, + tlist, + progress_bar = Val(false), + params = p, + sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), + ) + + return real(expect(a' * a, sol.states[end])) + end + + # Analytical solution + n_ss(Δ, F, γ) = abs2(F / (Δ + 1im * γ / 2)) + + Δ = 1.0 + F = 1.0 + γ = 1.0 + params = [Δ, F, γ] + + # The factor 2 is due to a bug + grad_qt = Zygote.gradient(my_f_mesolve, params)[1] + + grad_exact = Zygote.gradient((p) -> n_ss(p[1], p[2], p[3]), params)[1] + + @test grad_qt ≈ grad_exact atol=1e-6 + end +end diff --git a/test/runtests.jl b/test/runtests.jl index d8670d9d3..8b026b207 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -55,7 +55,20 @@ if (GROUP == "All") || (GROUP == "Code-Quality") include(joinpath(testdir, "core-test", "code-quality", "code_quality.jl")) end -if (GROUP == "CairoMakie_Ext")# || (GROUP == "All") +if (GROUP == "AutoDiff_Ext") + Pkg.activate("ext-test/cpu/autodiff") + Pkg.develop(PackageSpec(path = dirname(@__DIR__))) + Pkg.instantiate() + + using QuantumToolbox + using Zygote + using Enzyme + using SciMLSensitivity + + include(joinpath(testdir, "ext-test", "cpu", "autodiff", "zygote.jl")) +end + +if (GROUP == "CairoMakie_Ext") Pkg.activate("ext-test/cpu/cairomakie") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) Pkg.instantiate() @@ -67,7 +80,7 @@ if (GROUP == "CairoMakie_Ext")# || (GROUP == "All") include(joinpath(testdir, "ext-test", "cpu", "cairomakie", "cairomakie_ext.jl")) end -if (GROUP == "CUDA_Ext")# || (GROUP == "All") +if (GROUP == "CUDA_Ext") Pkg.activate("ext-test/gpu") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) Pkg.instantiate() From 6bc6e383e414cd37f567e36533bbe3848e4656ac Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 12 Apr 2025 08:29:20 +0200 Subject: [PATCH 252/329] Bump to v0.30.0 (#446) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4b73f275..d392c1b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.30.0] +Release date: 2025-04-12 + - Make CUDA conversion more general using Adapt.jl. ([#436], [#437]) - Make the generation of `fock` states non-mutating to support Zygote.jl. ([#438]) - Remove Reexport.jl from the dependencies. ([#443]) @@ -137,6 +140,7 @@ Release date: 2024-11-13 [v0.28.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.28.0 [v0.29.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.0 [v0.29.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.1 +[v0.30.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 87766530a..f7386893c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.29.1" +version = "0.30.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From ad8b0b88993a566537f409048907ce93d519e9e0 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 16 Apr 2025 09:43:16 +0200 Subject: [PATCH 253/329] Support different length for `to` and `from` on GeneralDImensions (#448) --- CHANGELOG.md | 3 +++ src/entropy.jl | 4 ++-- src/qobj/dimensions.jl | 26 +++++++++++--------------- src/qobj/quantum_object_base.jl | 23 ++++++++++------------- test/core-test/quantum_objects.jl | 7 +++++-- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d392c1b1c..d3a57dc71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Support different length for `to` and `from` on GeneralDimensions. ([#448]) + ## [v0.30.0] Release date: 2025-04-12 @@ -201,3 +203,4 @@ Release date: 2024-11-13 [#438]: https://github.com/qutip/QuantumToolbox.jl/issues/438 [#440]: https://github.com/qutip/QuantumToolbox.jl/issues/440 [#443]: https://github.com/qutip/QuantumToolbox.jl/issues/443 +[#448]: https://github.com/qutip/QuantumToolbox.jl/issues/448 diff --git a/src/entropy.jl b/src/entropy.jl index d32bc7c9a..bdf057d86 100644 --- a/src/entropy.jl +++ b/src/entropy.jl @@ -149,7 +149,7 @@ Here, ``S`` is the [Von Neumann entropy](https://en.wikipedia.org/wiki/Von_Neuma - `kwargs` are the keyword arguments for calculating Von Neumann entropy. See also [`entropy_vn`](@ref). """ function entropy_mutual( - ρAB::QuantumObject{ObjType,<:AbstractDimensions{N}}, + ρAB::QuantumObject{ObjType,<:AbstractDimensions{N,N}}, selA::Union{Int,AbstractVector{Int},Tuple}, selB::Union{Int,AbstractVector{Int},Tuple}; kwargs..., @@ -182,7 +182,7 @@ Here, ``S`` is the [Von Neumann entropy](https://en.wikipedia.org/wiki/Von_Neuma - `kwargs` are the keyword arguments for calculating Von Neumann entropy. See also [`entropy_vn`](@ref). """ entropy_conditional( - ρAB::QuantumObject{ObjType,<:AbstractDimensions{N}}, + ρAB::QuantumObject{ObjType,<:AbstractDimensions{N,N}}, selB::Union{Int,AbstractVector{Int},Tuple}; kwargs..., ) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject},N} = diff --git a/src/qobj/dimensions.jl b/src/qobj/dimensions.jl index a10d73c64..ba72659e1 100644 --- a/src/qobj/dimensions.jl +++ b/src/qobj/dimensions.jl @@ -4,16 +4,16 @@ This file defines the Dimensions structures, which can describe composite Hilber export AbstractDimensions, Dimensions, GeneralDimensions -abstract type AbstractDimensions{N} end +abstract type AbstractDimensions{M,N} end @doc raw""" - struct Dimensions{N,T<:Tuple} <: AbstractDimensions{N} + struct Dimensions{N,T<:Tuple} <: AbstractDimensions{N, N} to::T end A structure that describes the Hilbert [`Space`](@ref) of each subsystems. """ -struct Dimensions{N,T<:Tuple} <: AbstractDimensions{N} +struct Dimensions{N,T<:Tuple} <: AbstractDimensions{N,N} to::T # make sure the elements in the tuple are all AbstractSpace @@ -24,7 +24,7 @@ function Dimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Intege L = length(dims) (L > 0) || throw(DomainError(dims, "The argument dims must be of non-zero length")) - return Dimensions(NTuple{L,Space}(Space.(dims))) + return Dimensions(Tuple(Space.(dims))) end Dimensions(dims::Int) = Dimensions(Space(dims)) Dimensions(dims::DimType) where {DimType<:AbstractSpace} = Dimensions((dims,)) @@ -42,14 +42,14 @@ Dimensions(dims::Any) = throw( A structure that describes the left-hand side (`to`) and right-hand side (`from`) Hilbert [`Space`](@ref) of an [`Operator`](@ref). """ -struct GeneralDimensions{N,T1<:Tuple,T2<:Tuple} <: AbstractDimensions{N} +struct GeneralDimensions{M,N,T1<:Tuple,T2<:Tuple} <: AbstractDimensions{M,N} # note that the number `N` should be the same for both `to` and `from` to::T1 # space acting on the left from::T2 # space acting on the right # make sure the elements in the tuple are all AbstractSpace - GeneralDimensions(to::NTuple{N,T1}, from::NTuple{N,T2}) where {N,T1<:AbstractSpace,T2<:AbstractSpace} = - new{N,typeof(to),typeof(from)}(to, from) + GeneralDimensions(to::NTuple{M,T1}, from::NTuple{N,T2}) where {M,N,T1<:AbstractSpace,T2<:AbstractSpace} = + new{M,N,typeof(to),typeof(from)}(to, from) end function GeneralDimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Union{AbstractVector,NTuple},N} (length(dims) != 2) && throw(ArgumentError("Invalid dims = $dims")) @@ -59,14 +59,10 @@ function GeneralDimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T< L1 = length(dims[1]) L2 = length(dims[2]) - ((L1 > 0) && (L1 == L2)) || throw( - DomainError( - (L1, L2), - "The length of the arguments `dims[1]` and `dims[2]` must be in the same length and have at least one element.", - ), - ) - - return GeneralDimensions(NTuple{L1,Space}(Space.(dims[1])), NTuple{L1,Space}(Space.(dims[2]))) + (L1 > 0) || throw(DomainError(L1, "The length of `dims[1]` must be larger or equal to 1.")) + (L2 > 0) || throw(DomainError(L2, "The length of `dims[2]` must be larger or equal to 1.")) + + return GeneralDimensions(Tuple(Space.(dims[1])), Tuple(Space.(dims[2]))) end _gen_dimensions(dims::AbstractDimensions) = dims diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index da9deeb86..b43dd06d4 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -263,25 +263,22 @@ function Base.getproperty(A::AbstractQuantumObject, key::Symbol) end # this returns `to` in GeneralDimensions representation -get_dimensions_to(A::AbstractQuantumObject{KetQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{KetQuantumObject,<:Dimensions}) = A.dimensions.to get_dimensions_to(A::AbstractQuantumObject{BraQuantumObject,<:Dimensions{N}}) where {N} = space_one_list(N) -get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to -get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions{N}}) where {N} = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions}) = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions}) = A.dimensions.to get_dimensions_to( - A::AbstractQuantumObject{ObjType,<:Dimensions{N}}, -) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = - A.dimensions.to + A::AbstractQuantumObject{ObjType,<:Dimensions}, +) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject}} = A.dimensions.to # this returns `from` in GeneralDimensions representation get_dimensions_from(A::AbstractQuantumObject{KetQuantumObject,<:Dimensions{N}}) where {N} = space_one_list(N) -get_dimensions_from(A::AbstractQuantumObject{BraQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to -get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions{N}}) where {N} = A.dimensions.to -get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions{N}}) where {N} = - A.dimensions.from +get_dimensions_from(A::AbstractQuantumObject{BraQuantumObject,<:Dimensions}) = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions}) = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions}) = A.dimensions.from get_dimensions_from( - A::AbstractQuantumObject{ObjType,<:Dimensions{N}}, -) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject},N} = - A.dimensions.to + A::AbstractQuantumObject{ObjType,<:Dimensions}, +) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject}} = A.dimensions.to # functions for getting Float or Complex element type _FType(A::AbstractQuantumObject) = _FType(eltype(A)) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 52adfb7fb..0730b069f 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -86,6 +86,7 @@ a2 = Qobj(a) a3 = Qobj(a, type = SuperOperator) a4 = Qobj(sprand(ComplexF64, 100, 10, 0.1)) # GeneralDimensions + a5 = QuantumObject(rand(ComplexF64, 2*3*4, 5), dims = ((2, 3, 4), (5,))) @test isket(a2) == false @test isbra(a2) == false @test isoper(a2) == true @@ -116,6 +117,8 @@ @test isconstant(a4) == true @test isunitary(a4) == false @test a4.dims == [[100], [10]] + @test isoper(a5) == true + @test a5.dims == [[2, 3, 4], [5]] @test_throws DimensionMismatch Qobj(a, dims = 2) @test_throws DimensionMismatch Qobj(a4.data, dims = 2) @test_throws DimensionMismatch Qobj(a4.data, dims = ((100,), (2,))) @@ -351,7 +354,7 @@ for T in [ComplexF32, ComplexF64] N = 4 a = rand(T, N) - @inferred QuantumObject{KetQuantumObject,Dimensions{1},typeof(a)} Qobj(a) + @inferred Qobj(a) for type in [Ket, OperatorKet] @inferred Qobj(a, type = type) end @@ -367,7 +370,7 @@ end UnionType2 = Union{ - QuantumObject{OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}},Matrix{T}}, + QuantumObject{OperatorQuantumObject,GeneralDimensions{1,1,Tuple{Space},Tuple{Space}},Matrix{T}}, QuantumObject{OperatorQuantumObject,Dimensions{1,Tuple{Space}},Matrix{T}}, } a = rand(T, N, N) From 130eafee74fe94ce9ed9ab487db3ef86cb571df8 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 19 Apr 2025 16:22:31 +0200 Subject: [PATCH 254/329] Make Makie extension more general (#450) --- .github/workflows/CI.yml | 2 +- CHANGELOG.md | 2 ++ Project.toml | 6 ++-- docs/make.jl | 2 +- docs/src/getting_started/logo.md | 4 +-- docs/src/users_guide/extensions/cairomakie.md | 10 +++--- ...oMakieExt.jl => QuantumToolboxMakieExt.jl} | 32 ++++++++--------- src/visualization.jl | 20 +++++------ test/ext-test/cpu/cairomakie/Project.toml | 6 ---- test/ext-test/cpu/makie/Project.toml | 3 ++ .../cairomakie_ext.jl => makie/makie_ext.jl} | 36 +++++++------------ test/runtests.jl | 6 ++-- 12 files changed, 58 insertions(+), 71 deletions(-) rename ext/{QuantumToolboxCairoMakieExt.jl => QuantumToolboxMakieExt.jl} (85%) delete mode 100644 test/ext-test/cpu/cairomakie/Project.toml create mode 100644 test/ext-test/cpu/makie/Project.toml rename test/ext-test/cpu/{cairomakie/cairomakie_ext.jl => makie/makie_ext.jl} (64%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f34372fb4..a4307888d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -67,7 +67,7 @@ jobs: node: os: 'ubuntu-latest' arch: 'x64' - group: 'CairoMakie_Ext' + group: 'Makie_Ext' - version: '1' node: os: 'ubuntu-latest' diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a57dc71..b4bf7ed77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Support different length for `to` and `from` on GeneralDimensions. ([#448]) +- Extend the `Makie.jl` extension to all the other available backends. ([#450]) ## [v0.30.0] Release date: 2025-04-12 @@ -204,3 +205,4 @@ Release date: 2024-11-13 [#440]: https://github.com/qutip/QuantumToolbox.jl/issues/440 [#443]: https://github.com/qutip/QuantumToolbox.jl/issues/443 [#448]: https://github.com/qutip/QuantumToolbox.jl/issues/448 +[#450]: https://github.com/qutip/QuantumToolbox.jl/issues/450 diff --git a/Project.toml b/Project.toml index f7386893c..4c5508b1e 100644 --- a/Project.toml +++ b/Project.toml @@ -27,13 +27,13 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" [weakdeps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [extensions] -QuantumToolboxCairoMakieExt = "CairoMakie" +QuantumToolboxMakieExt = "Makie" QuantumToolboxChainRulesCoreExt = "ChainRulesCore" QuantumToolboxCUDAExt = "CUDA" QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] @@ -41,7 +41,6 @@ QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] [compat] ArrayInterface = "6, 7" CUDA = "5" -CairoMakie = "0.12, 0.13" ChainRulesCore = "1" DiffEqBase = "6" DiffEqCallbacks = "4.2.1 - 4" @@ -54,6 +53,7 @@ IncompleteLU = "0.2" KernelAbstractions = "0.9.2" LinearAlgebra = "1" LinearSolve = "2, 3" +Makie = "0.20, 0.21, 0.22" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" diff --git a/docs/make.jl b/docs/make.jl index 2a6548979..01e18c019 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -81,7 +81,7 @@ const PAGES = [ makedocs(; modules = [ QuantumToolbox, - Base.get_extension(QuantumToolbox, :QuantumToolboxCairoMakieExt), + Base.get_extension(QuantumToolbox, :QuantumToolboxMakieExt), ], authors = "Alberto Mercurio and Yi-Te Huang", repo = Remotes.GitHub("qutip", "QuantumToolbox.jl"), diff --git a/docs/src/getting_started/logo.md b/docs/src/getting_started/logo.md index 154ad0c26..ae3093bff 100644 --- a/docs/src/getting_started/logo.md +++ b/docs/src/getting_started/logo.md @@ -76,7 +76,7 @@ xvec = range(-ρ, ρ, 500) .* 1.5 yvec = xvec .+ (abs(imag(α1)) - abs(imag(α2))) / 2 fig = Figure(size = (250, 250), figure_padding = 0) -fig, ax, hm = plot_wigner(ψ, xvec = xvec, yvec = yvec, g = 2, library = Val(:CairoMakie), location = fig[1,1]) +fig, ax, hm = plot_wigner(ψ, xvec = xvec, yvec = yvec, g = 2, library = Val(:Makie), location = fig[1,1]) hidespines!(ax) hidexdecorations!(ax) hideydecorations!(ax) @@ -110,7 +110,7 @@ And the Wigner function becomes more uniform: ```@example logo fig = Figure(size = (250, 250), figure_padding = 0) -fig, ax, hm = plot_wigner(sol.states[end], xvec = xvec, yvec = yvec, g = 2, library = Val(:CairoMakie), location = fig[1,1]) +fig, ax, hm = plot_wigner(sol.states[end], xvec = xvec, yvec = yvec, g = 2, library = Val(:Makie), location = fig[1,1]) hidespines!(ax) hidexdecorations!(ax) hideydecorations!(ax) diff --git a/docs/src/users_guide/extensions/cairomakie.md b/docs/src/users_guide/extensions/cairomakie.md index b96c92d64..af332ba11 100644 --- a/docs/src/users_guide/extensions/cairomakie.md +++ b/docs/src/users_guide/extensions/cairomakie.md @@ -1,18 +1,18 @@ -# [Extension for CairoMakie.jl](@id doc:CairoMakie) +# [Extension for the Makie.jl ecosystem](@id doc:Makie) -This is an extension to support visualization (plotting functions) using [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) library. +This is an extension to support visualization (plotting functions) using [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) library. -This extension will be automatically loaded if user imports both `QuantumToolbox.jl` and [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie): +This extension will be automatically loaded if user imports both `QuantumToolbox.jl` and [`Makie.jl`](https://github.com/MakieOrg/Makie.jl). It is worth noting that the `Makie.jl` package provides only the engine for plotting, and the user has to import the specific backend. Here we demonstrate the usage of [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie), which will automatically import `Makie.jl`. ```julia using QuantumToolbox using CairoMakie ``` -To plot with [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) library, specify the keyword argument `library = Val(:CairoMakie)` for the plotting functions. +To plot with [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) library, specify the keyword argument `library = Val(:Makie)` for the plotting functions. !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `Val(:CairoMakie)` instead of `:CairoMakie`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `Val(:Makie)` instead of `:Makie`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. The supported plotting functions are listed as follows: diff --git a/ext/QuantumToolboxCairoMakieExt.jl b/ext/QuantumToolboxMakieExt.jl similarity index 85% rename from ext/QuantumToolboxCairoMakieExt.jl rename to ext/QuantumToolboxMakieExt.jl index 950859e3f..3beb577da 100644 --- a/ext/QuantumToolboxCairoMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -1,12 +1,12 @@ -module QuantumToolboxCairoMakieExt +module QuantumToolboxMakieExt using QuantumToolbox -using CairoMakie: +using Makie: Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, barplot!, GridPosition, @L_str, Reverse, ylims! @doc raw""" plot_wigner( - library::Val{:CairoMakie}, + library::Val{:Makie}, state::QuantumObject{OpType}; xvec::Union{Nothing,AbstractVector} = nothing, yvec::Union{Nothing,AbstractVector} = nothing, @@ -18,10 +18,10 @@ using CairoMakie: kwargs... ) where {OpType} -Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) plotting library. +Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`Makie`](https://github.com/MakieOrg/Makie.jl) plotting library. # Arguments -- `library::Val{:CairoMakie}`: The plotting library to use. +- `library::Val{:Makie}`: The plotting library to use. - `state::QuantumObject`: The quantum state for which the Wigner function is calculated. It can be either a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). - `xvec::AbstractVector`: The x-coordinates of the phase space grid. Defaults to a linear range from -7.5 to 7.5 with 200 points. - `yvec::AbstractVector`: The y-coordinates of the phase space grid. Defaults to a linear range from -7.5 to 7.5 with 200 points. @@ -38,13 +38,13 @@ Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wi - `hm`: Either the heatmap or surface object, depending on the projection. !!! note "Import library first" - [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) must first be imported before using this function. + [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) must first be imported before using this function. This can be done by importing one of the available backends, such as [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie), [`GLMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/GLMakie), or [`WGLMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/WGLMakie). !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `Val(:two_dim)` and `Val(:three_dim)` instead of `:two_dim` and `:three_dim`, respectively. Also, specify the library as `Val(:CairoMakie)` See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `Val(:two_dim)` and `Val(:three_dim)` instead of `:two_dim` and `:three_dim`, respectively. Also, specify the library as `Val(:Makie)` See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ function QuantumToolbox.plot_wigner( - library::Val{:CairoMakie}, + library::Val{:Makie}, state::QuantumObject{OpType}; xvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200), yvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200), @@ -74,7 +74,7 @@ function QuantumToolbox.plot_wigner( end function _plot_wigner( - ::Val{:CairoMakie}, + ::Val{:Makie}, state::QuantumObject{OpType}, xvec::AbstractVector, yvec::AbstractVector, @@ -107,7 +107,7 @@ function _plot_wigner( end function _plot_wigner( - ::Val{:CairoMakie}, + ::Val{:Makie}, state::QuantumObject{OpType}, xvec::AbstractVector, yvec::AbstractVector, @@ -142,7 +142,7 @@ end @doc raw""" plot_fock_distribution( - library::Val{:CairoMakie}, + library::Val{:Makie}, ρ::QuantumObject{SType}; fock_numbers::Union{Nothing, AbstractVector} = nothing, unit_y_range::Bool = true, @@ -153,7 +153,7 @@ end Plot the [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution of `ρ`. # Arguments -- `library::Val{:CairoMakie}`: The plotting library to use. +- `library::Val{:Makie}`: The plotting library to use. - `ρ::QuantumObject`: The quantum state for which the Fock state distribution is to be plotted. It can be either a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). - `location::Union{GridPosition,Nothing}`: The location of the plot in the layout. If `nothing`, the plot is created in a new figure. Default is `nothing`. - `fock_numbers::Union{Nothing, AbstractVector}`: list of x ticklabels to represent fock numbers, default is `nothing`. @@ -166,13 +166,13 @@ Plot the [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution of - `bp`: The barplot object. !!! note "Import library first" - [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) must first be imported before using this function. + [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) must first be imported before using this function. This can be done by importing one of the available backends, such as [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie), [`GLMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/GLMakie), or [`WGLMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/WGLMakie). !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `Val(:two_dim)` and `Val(:three_dim)` instead of `:two_dim` and `:three_dim`, respectively. Also, specify the library as `Val(:CairoMakie)` See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `Val(:two_dim)` and `Val(:three_dim)` instead of `:two_dim` and `:three_dim`, respectively. Also, specify the library as `Val(:Makie)` See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ function QuantumToolbox.plot_fock_distribution( - library::Val{:CairoMakie}, + library::Val{:Makie}, ρ::QuantumObject{SType}; fock_numbers::Union{Nothing,AbstractVector} = nothing, unit_y_range::Bool = true, @@ -190,7 +190,7 @@ function QuantumToolbox.plot_fock_distribution( end function _plot_fock_distribution( - ::Val{:CairoMakie}, + ::Val{:Makie}, ρ::QuantumObject{SType}; fock_numbers::Union{Nothing,AbstractVector} = nothing, unit_y_range::Bool = true, diff --git a/src/visualization.jl b/src/visualization.jl index 0e1fbd3fb..f24485228 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -3,28 +3,28 @@ export plot_wigner, plot_fock_distribution @doc raw""" plot_wigner( state::QuantumObject{OpType}; - library::Union{Val,Symbol}=Val(:CairoMakie), + library::Union{Val,Symbol}=Val(:Makie), kwargs... ) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject} Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`wigner`](@ref) function. -The `library` keyword argument specifies the plotting library to use, defaulting to [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie). +The `library` keyword argument specifies the plotting library to use, defaulting to [`Makie.jl`](https://github.com/MakieOrg/Makie.jl). # Arguments - `state::QuantumObject`: The quantum state for which to plot the Wigner distribution. -- `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:CairoMakie)`. +- `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:Makie)`. - `kwargs...`: Additional keyword arguments to pass to the plotting function. See the documentation for the specific plotting library for more information. !!! note "Import library first" The plotting libraries must first be imported before using them with this function. !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `Val(:CairoMakie)` instead of `:CairoMakie` as the plotting library. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `Val(:Makie)` instead of `:Makie` as the plotting library. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ plot_wigner( state::QuantumObject{OpType}; - library::Union{Val,Symbol} = Val(:CairoMakie), + library::Union{Val,Symbol} = Val(:Makie), kwargs..., ) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = plot_wigner(makeVal(library), state; kwargs...) @@ -39,28 +39,28 @@ plot_wigner( @doc raw""" plot_fock_distribution( ρ::QuantumObject{SType}; - library::Union{Val, Symbol} = Val(:CairoMakie), + library::Union{Val, Symbol} = Val(:Makie), kwargs... ) where {SType<:Union{KetQuantumObject,OperatorQuantumObject}} Plot the [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution of `ρ`. -The `library` keyword argument specifies the plotting library to use, defaulting to [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie). +The `library` keyword argument specifies the plotting library to use, defaulting to [`Makie`](https://github.com/MakieOrg/Makie.jl). # Arguments - `ρ::QuantumObject`: The quantum state for which to plot the Fock state distribution. -- `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:CairoMakie)`. +- `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:Makie)`. - `kwargs...`: Additional keyword arguments to pass to the plotting function. See the documentation for the specific plotting library for more information. !!! note "Import library first" The plotting libraries must first be imported before using them with this function. !!! warning "Beware of type-stability!" - If you want to keep type stability, it is recommended to use `Val(:CairoMakie)` instead of `:CairoMakie` as the plotting library. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + If you want to keep type stability, it is recommended to use `Val(:Makie)` instead of `:Makie` as the plotting library. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. """ plot_fock_distribution( ρ::QuantumObject{SType}; - library::Union{Val,Symbol} = Val(:CairoMakie), + library::Union{Val,Symbol} = Val(:Makie), kwargs..., ) where {SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = plot_fock_distribution(makeVal(library), ρ; kwargs...) diff --git a/test/ext-test/cpu/cairomakie/Project.toml b/test/ext-test/cpu/cairomakie/Project.toml deleted file mode 100644 index 06b706a2f..000000000 --- a/test/ext-test/cpu/cairomakie/Project.toml +++ /dev/null @@ -1,6 +0,0 @@ -[deps] -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" -QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" - -[compat] -CairoMakie = "0.12" \ No newline at end of file diff --git a/test/ext-test/cpu/makie/Project.toml b/test/ext-test/cpu/makie/Project.toml new file mode 100644 index 000000000..0bcbd97e7 --- /dev/null +++ b/test/ext-test/cpu/makie/Project.toml @@ -0,0 +1,3 @@ +[deps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" diff --git a/test/ext-test/cpu/cairomakie/cairomakie_ext.jl b/test/ext-test/cpu/makie/makie_ext.jl similarity index 64% rename from test/ext-test/cpu/cairomakie/cairomakie_ext.jl rename to test/ext-test/cpu/makie/makie_ext.jl index 0c73dc363..4f68e696f 100644 --- a/test/ext-test/cpu/cairomakie/cairomakie_ext.jl +++ b/test/ext-test/cpu/makie/makie_ext.jl @@ -1,35 +1,23 @@ -@testset "CairoMakie Extension" verbose = true begin +@testset "Makie Extension" verbose = true begin ψ = normalize(coherent(50, 5.0) + coherent(50, -5.0)) xvec = yvec = -15.0:0.1:15.0 wig = transpose(wigner(ψ, xvec, yvec)) - @test_throws ArgumentError plot_wigner(ψ; library = :CairoMakie, xvec = xvec, yvec = yvec) + @test_throws ArgumentError plot_wigner(ψ; library = :Makie, xvec = xvec, yvec = yvec) - @test_throws ArgumentError plot_fock_distribution(ψ; library = :CairoMakie) + @test_throws ArgumentError plot_fock_distribution(ψ; library = :Makie) - using CairoMakie + using Makie - fig, ax, hm = plot_wigner( - ψ; - library = Val(:CairoMakie), - xvec = xvec, - yvec = yvec, - projection = Val(:two_dim), - colorbar = true, - ) + fig, ax, hm = + plot_wigner(ψ; library = Val(:Makie), xvec = xvec, yvec = yvec, projection = Val(:two_dim), colorbar = true) @test fig isa Figure @test ax isa Axis @test hm isa Heatmap @test all(isapprox.(hm[3].val, wig, atol = 1e-6)) - fig, ax, surf = plot_wigner( - ψ; - library = Val(:CairoMakie), - xvec = xvec, - yvec = yvec, - projection = Val(:three_dim), - colorbar = true, - ) + fig, ax, surf = + plot_wigner(ψ; library = Val(:Makie), xvec = xvec, yvec = yvec, projection = Val(:three_dim), colorbar = true) @test fig isa Figure @test ax isa Axis3 @test surf isa Surface @@ -39,7 +27,7 @@ pos = fig[2, 3] fig1, ax, hm = plot_wigner( ψ; - library = Val(:CairoMakie), + library = Val(:Makie), xvec = xvec, yvec = yvec, projection = Val(:two_dim), @@ -53,7 +41,7 @@ pos = fig[2, 3] fig1, ax, surf = plot_wigner( ψ; - library = Val(:CairoMakie), + library = Val(:Makie), xvec = xvec, yvec = yvec, projection = Val(:three_dim), @@ -65,11 +53,11 @@ fig = Figure() pos = fig[2, 3] - fig1, ax = plot_fock_distribution(ψ; library = Val(:CairoMakie), location = pos) + fig1, ax = plot_fock_distribution(ψ; library = Val(:Makie), location = pos) @test fig1 === fig @test fig[2, 3].layout.content[1].content[1, 1].layout.content[1].content === ax fig = Figure() pos = fig[2, 3] - fig1, ax = @test_logs (:warn,) plot_fock_distribution(ψ * 2; library = Val(:CairoMakie), location = pos) + fig1, ax = @test_logs (:warn,) plot_fock_distribution(ψ * 2; library = Val(:Makie), location = pos) end diff --git a/test/runtests.jl b/test/runtests.jl index 8b026b207..7407d82fb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -68,8 +68,8 @@ if (GROUP == "AutoDiff_Ext") include(joinpath(testdir, "ext-test", "cpu", "autodiff", "zygote.jl")) end -if (GROUP == "CairoMakie_Ext") - Pkg.activate("ext-test/cpu/cairomakie") +if (GROUP == "Makie_Ext") + Pkg.activate("ext-test/cpu/makie") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) Pkg.instantiate() @@ -77,7 +77,7 @@ if (GROUP == "CairoMakie_Ext") QuantumToolbox.about() # CarioMakie is imported in the following script - include(joinpath(testdir, "ext-test", "cpu", "cairomakie", "cairomakie_ext.jl")) + include(joinpath(testdir, "ext-test", "cpu", "makie", "makie_ext.jl")) end if (GROUP == "CUDA_Ext") From 9832ecc817e1a404681b0479f2c9b4b742f8c1a2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:48:50 +0900 Subject: [PATCH 255/329] [Doc] fix typo (#451) --- docs/src/users_guide/states_and_operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index f52c1e978..b50814ede 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -378,7 +378,7 @@ println(issuper(S_AB)) With the above definitions, the following equalities hold in `Julia`: ```math -\textrm{vec}(\hat{A} \hat{\rho} \hat{B}) = \textrm{spre}(\hat{A}) * \textrm{spre}(\hat{B}) * \textrm{vec}(\hat{\rho}) = \textrm{sprepost}(\hat{A},\hat{B}) * \textrm{vec}(\hat{\rho}) ~~\forall~~\hat{A}, \hat{B}, \hat{\rho} +\textrm{vec}(\hat{A} \hat{\rho} \hat{B}) = \textrm{spre}(\hat{A}) * \textrm{spost}(\hat{B}) * \textrm{vec}(\hat{\rho}) = \textrm{sprepost}(\hat{A},\hat{B}) * \textrm{vec}(\hat{\rho}) ~~\forall~~\hat{A}, \hat{B}, \hat{\rho} ``` ```@example states_and_operators From 68ed7c3fb810a80e54a4ee0053fa50ad1d975bc9 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:56:01 +0200 Subject: [PATCH 256/329] Fix definition of noise derivative in stochastic solvers (#453) --- CHANGELOG.md | 2 ++ src/qobj/quantum_object_evo.jl | 3 +++ .../callback_helpers/callback_helpers.jl | 24 +++++++++++++------ .../smesolve_callback_helpers.jl | 12 ++++++---- .../ssesolve_callback_helpers.jl | 12 ++++++---- src/time_evolution/ssesolve.jl | 2 +- src/time_evolution/time_evolution.jl | 2 +- 7 files changed, 40 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4bf7ed77..1f406f394 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 - Support different length for `to` and `from` on GeneralDimensions. ([#448]) - Extend the `Makie.jl` extension to all the other available backends. ([#450]) +- Fix definition of noise derivative in stochastic solvers. ([#453]) ## [v0.30.0] Release date: 2025-04-12 @@ -206,3 +207,4 @@ Release date: 2024-11-13 [#443]: https://github.com/qutip/QuantumToolbox.jl/issues/443 [#448]: https://github.com/qutip/QuantumToolbox.jl/issues/448 [#450]: https://github.com/qutip/QuantumToolbox.jl/issues/450 +[#453]: https://github.com/qutip/QuantumToolbox.jl/issues/453 diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 9f9364462..62b15b0b5 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -468,6 +468,9 @@ function _promote_to_scimloperator(α::Number, data::ScaledOperator) isconstant(data.λ) && return ScaledOperator(α * data.λ, data.L) return ScaledOperator(data.λ, _promote_to_scimloperator(α, data.L)) # Try to propagate the rule end +function _promote_to_scimloperator(α::Number, data::AddedOperator) + return AddedOperator(_promote_to_scimloperator.(α, data.ops)) # Try to propagate the rule +end function _promote_to_scimloperator(α::Number, data::AbstractSciMLOperator) return α * data # Going back to the generic case end diff --git a/src/time_evolution/callback_helpers/callback_helpers.jl b/src/time_evolution/callback_helpers/callback_helpers.jl index e84085b47..0d01fd7f6 100644 --- a/src/time_evolution/callback_helpers/callback_helpers.jl +++ b/src/time_evolution/callback_helpers/callback_helpers.jl @@ -24,12 +24,17 @@ function _generate_stochastic_kwargs( ) where {SF<:AbstractSaveFunc} cb_save = _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurement, progress_bar, method) + # Ensure that the noise is stored in tlist. # TODO: Fix this directly in DiffEqNoiseProcess.jl + # See https://github.com/SciML/DiffEqNoiseProcess.jl/issues/214 for example + tstops = haskey(kwargs, :tstops) ? unique!(sort!(vcat(tlist, kwargs.tstops))) : tlist + kwargs2 = merge(kwargs, (tstops = tstops,)) + if SF === SaveFuncSSESolve cb_normalize = _ssesolve_generate_normalize_cb() - return _merge_kwargs_with_callback(kwargs, CallbackSet(cb_normalize, cb_save)) + return _merge_kwargs_with_callback(kwargs2, CallbackSet(cb_normalize, cb_save)) end - return _merge_kwargs_with_callback(kwargs, cb_save) + return _merge_kwargs_with_callback(kwargs2, cb_save) end _generate_stochastic_kwargs( e_ops::Nothing, @@ -69,7 +74,9 @@ function _generate_stochastic_save_callback(e_ops, sc_ops, tlist, store_measurem expvals = e_ops isa Nothing ? nothing : Array{ComplexF64}(undef, length(e_ops), length(tlist)) m_expvals = getVal(store_measurement) ? Array{Float64}(undef, length(sc_ops), length(tlist) - 1) : nothing - _save_func = method(store_measurement, e_ops_data, m_ops_data, progr, Ref(1), expvals, m_expvals) + _save_func_cache = Array{Float64}(undef, length(sc_ops)) + _save_func = + method(store_measurement, e_ops_data, m_ops_data, progr, Ref(1), expvals, m_expvals, tlist, _save_func_cache) return FunctionCallingCallback(_save_func, funcat = tlist) end @@ -162,9 +169,12 @@ _get_save_callback_idx(cb, method) = 1 # %% ------------ Noise Measurement Helpers ------------ %% -# TODO: Add some cache mechanism to avoid memory allocations -function _homodyne_dWdt(integrator) - @inbounds _dWdt = (integrator.W.u[end] .- integrator.W.u[end-1]) ./ (integrator.W.t[end] - integrator.W.t[end-1]) +# TODO: To improve. See https://github.com/SciML/DiffEqNoiseProcess.jl/issues/214 +function _homodyne_dWdt!(dWdt_cache, integrator, tlist, iter) + idx = findfirst(>=(tlist[iter[]-1]), integrator.W.t) + + # We are assuming that the last element is tlist[iter[]] + @inbounds dWdt_cache .= (integrator.W.u[end] .- integrator.W.u[idx]) ./ (integrator.W.t[end] - integrator.W.t[idx]) - return _dWdt + return nothing end diff --git a/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl index 6fb39c14b..6fb75a979 100644 --- a/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/smesolve_callback_helpers.jl @@ -10,6 +10,8 @@ struct SaveFuncSMESolve{ IT, TEXPV<:Union{Nothing,AbstractMatrix}, TMEXPV<:Union{Nothing,AbstractMatrix}, + TLT<:AbstractVector, + CT<:AbstractVector, } <: AbstractSaveFunc store_measurement::Val{SM} e_ops::TE @@ -18,10 +20,12 @@ struct SaveFuncSMESolve{ iter::IT expvals::TEXPV m_expvals::TMEXPV + tlist::TLT + dWdt_cache::CT end (f::SaveFuncSMESolve)(u, t, integrator) = - _save_func_smesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) + _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) (f::SaveFuncSMESolve{false,Nothing})(u, t, integrator) = _save_func(integrator, f.progr) # Common for both all solvers _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}) = ## # When e_ops is a list of operators -function _save_func_smesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) +function _save_func_smesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals, tlist, dWdt_cache) # This is equivalent to tr(op * ρ), when both are matrices. # The advantage of using this convention is that We don't need # 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, end if !isnothing(m_expvals) && iter[] > 1 - _dWdt = _homodyne_dWdt(integrator) - @. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + _dWdt + _homodyne_dWdt!(dWdt_cache, integrator, tlist, iter) + @. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + dWdt_cache end iter[] += 1 diff --git a/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl index c84cd7e5e..e28ccc6ef 100644 --- a/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl +++ b/src/time_evolution/callback_helpers/ssesolve_callback_helpers.jl @@ -10,6 +10,8 @@ struct SaveFuncSSESolve{ IT, TEXPV<:Union{Nothing,AbstractMatrix}, TMEXPV<:Union{Nothing,AbstractMatrix}, + TLT<:AbstractVector, + CT<:AbstractVector, } <: AbstractSaveFunc store_measurement::Val{SM} e_ops::TE @@ -18,10 +20,12 @@ struct SaveFuncSSESolve{ iter::IT expvals::TEXPV m_expvals::TMEXPV + tlist::TLT + dWdt_cache::CT end (f::SaveFuncSSESolve)(u, t, integrator) = - _save_func_ssesolve(u, integrator, f.e_ops, f.m_ops, f.progr, f.iter, f.expvals, f.m_expvals) + _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) (f::SaveFuncSSESolve{false,Nothing})(u, t, integrator) = _save_func(integrator, f.progr) # Common for both all solvers _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 ## # When e_ops is a list of operators -function _save_func_ssesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals) +function _save_func_ssesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, m_expvals, tlist, dWdt_cache) ψ = u _expect = op -> dot(ψ, op, ψ) @@ -42,8 +46,8 @@ function _save_func_ssesolve(u, integrator, e_ops, m_ops, progr, iter, expvals, end if !isnothing(m_expvals) && iter[] > 1 - _dWdt = _homodyne_dWdt(integrator) - @. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + _dWdt + _homodyne_dWdt!(dWdt_cache, integrator, tlist, iter) + @. m_expvals[:, iter[]-1] = real(_expect(m_ops)) + dWdt_cache end iter[] += 1 diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 5d7e89d0b..b29925481 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -113,7 +113,7 @@ function ssesolveProblem( sc_ops_evo_data, ) - K = -1im * get_data(H_eff_evo) + K_l + K = get_data(QobjEvo(H_eff_evo, -1im)) + K_l D_l = map(op -> op + _ScalarOperator_e(op, -) * IdentityOperator(prod(dims)), sc_ops_evo_data) D = DiffusionOperator(D_l) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 13188cc25..2ab50893a 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -3,7 +3,7 @@ export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionStochasticSol export liouvillian_floquet, liouvillian_generalized const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true) -const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true) +const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-3, reltol = 2e-3, save_everystep = false, save_end = true) const COL_TIMES_WHICH_INIT_SIZE = 200 @doc raw""" From 2b5e86efb5ca00a65277a4b02bda07531cd529cf Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:58:33 +0200 Subject: [PATCH 257/329] Bump to v0.30.1 (#454) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f406f394..2ed988293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.30.1] +Release date: 2025-04-24 + - Support different length for `to` and `from` on GeneralDimensions. ([#448]) - Extend the `Makie.jl` extension to all the other available backends. ([#450]) - Fix definition of noise derivative in stochastic solvers. ([#453]) @@ -145,6 +148,7 @@ Release date: 2024-11-13 [v0.29.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.0 [v0.29.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.1 [v0.30.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.0 +[v0.30.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.1 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 4c5508b1e..d51086d35 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.30.0" +version = "0.30.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 85d23efaa18525754b8516ebf9c327f9c8d9b667 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Wed, 30 Apr 2025 04:36:59 +0200 Subject: [PATCH 258/329] Return `sesolve` when `mesolve` allows it (#455) --- CHANGELOG.md | 3 +++ src/time_evolution/mesolve.jl | 25 +++++++++++++++++++++++++ test/core-test/time_evolution.jl | 4 ++++ 3 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed988293..8a9f46f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Return `sesolve` when `mesolve` allows it. ([#455]) + ## [v0.30.1] Release date: 2025-04-24 @@ -212,3 +214,4 @@ Release date: 2024-11-13 [#448]: https://github.com/qutip/QuantumToolbox.jl/issues/448 [#450]: https://github.com/qutip/QuantumToolbox.jl/issues/450 [#453]: https://github.com/qutip/QuantumToolbox.jl/issues/453 +[#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 268535aa8..968cc8aad 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -46,6 +46,7 @@ where - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. +- If `H` is an [`Operator`](@ref), `ψ0` is a [`Ket`](@ref) and `c_ops` is `Nothing`, the function will call [`sesolveProblem`](@ref) instead. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -67,6 +68,17 @@ function mesolveProblem( HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}, } + (isoper(H) && isket(ψ0) && isnothing(c_ops)) && return sesolveProblem( + H, + ψ0, + tlist; + e_ops = e_ops, + params = params, + progress_bar = progress_bar, + inplace = inplace, + kwargs..., + ) + haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) @@ -141,6 +153,7 @@ where - The states will be saved depend on the keyword argument `saveat` in `kwargs`. - If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). You can also specify `e_ops` and `saveat` separately. +- If `H` is an [`Operator`](@ref), `ψ0` is a [`Ket`](@ref) and `c_ops` is `Nothing`, the function will call [`sesolve`](@ref) instead. - The default tolerances in `kwargs` are given as `reltol=1e-6` and `abstol=1e-8`. - For more details about `alg` please refer to [`DifferentialEquations.jl` (ODE Solvers)](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) - For more details about `kwargs` please refer to [`DifferentialEquations.jl` (Keyword Arguments)](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/) @@ -164,6 +177,18 @@ function mesolve( HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}, } + (isoper(H) && isket(ψ0) && isnothing(c_ops)) && return sesolve( + H, + ψ0, + tlist; + alg = alg, + e_ops = e_ops, + params = params, + progress_bar = progress_bar, + inplace = inplace, + kwargs..., + ) + prob = mesolveProblem( H, ψ0, diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 6d5e401fa..5abb7d18d 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -183,6 +183,9 @@ rng = MersenneTwister(12), ) + # Redirect to `sesolve` + sol_me5 = mesolve(H, ψ0, tlist, progress_bar = Val(false)) + ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) expect_mc_states_mean = sum(expect_mc_states, dims = 2) / size(expect_mc_states, 2) @@ -198,6 +201,7 @@ sol_sme_string = sprint((t, s) -> show(t, "text/plain", s), sol_sme) @test prob_me.prob.f.f isa MatrixOperator @test prob_mc.prob.f.f isa MatrixOperator + @test isket(sol_me5.states[1]) @test sum(abs, sol_mc.expect .- sol_me.expect) / length(tlist) < 0.1 @test sum(abs, sol_mc2.expect .- sol_me.expect) / length(tlist) < 0.1 @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 From fe4eaf0d50697a62e3b0cd0d6a8f04c63a25d05e Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 1 May 2025 21:23:16 +0900 Subject: [PATCH 259/329] Add citation bibtex for arXiv preprint (#457) --- CITATION.bib | 11 +++++++++++ README.md | 23 ++++++++++++++++++++--- docs/make.jl | 2 +- docs/src/getting_started/cite.md | 17 +++++++++++++++++ docs/src/index.md | 3 +++ 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 CITATION.bib create mode 100644 docs/src/getting_started/cite.md diff --git a/CITATION.bib b/CITATION.bib new file mode 100644 index 000000000..aea40ca26 --- /dev/null +++ b/CITATION.bib @@ -0,0 +1,11 @@ +@article{QuantumToolbox-jl2025, + title={{QuantumToolbox.jl}: An efficient {Julia} framework for simulating open quantum systems}, + author={Mercurio, Alberto and Huang, Yi-Te and Cai, Li-Xun and Chen, Yueh-Nan and Savona, Vincenzo and Nori, Franco}, + journal={arXiv preprint arXiv:2504.21440}, + year={2025}, + publisher = {arXiv}, + eprint={2504.21440}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + doi = {10.48550/arXiv.2504.21440} +} \ No newline at end of file diff --git a/README.md b/README.md index cd8c30383..29b10f0dc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ and [Y.-T. Huang](https://github.com/ytdHuang). -| **Release** | [![Release][release-img]][release-url] [![License][license-img]][license-url] [![DOI][doi-img]][doi-url] [![Downloads][download-img]][download-url] | +| **Release** | [![Release][release-img]][release-url] [![License][license-img]][license-url] [![Cite][cite-img]][cite-url] [![Downloads][download-img]][download-url] | |:-----------------:|:-------------| | **Runtests** | [![Runtests][runtests-img]][runtests-url] [![Coverage][codecov-img]][codecov-url] | | **Code Quality** | [![Code Quality][code-quality-img]][code-quality-url] [![Aqua QA][aqua-img]][aqua-url] [![JET][jet-img]][jet-url] | @@ -24,8 +24,8 @@ and [Y.-T. Huang](https://github.com/ytdHuang). [license-img]: https://img.shields.io/badge/license-New%20BSD-blue.svg [license-url]: https://opensource.org/licenses/BSD-3-Clause -[doi-img]: https://zenodo.org/badge/DOI/10.5281/zenodo.10822816.svg -[doi-url]: https://doi.org/10.5281/zenodo.10822816 +[cite-img]: https://img.shields.io/badge/cite-arXiv%3A2504.21440_(2023)-blue +[cite-url]: https://doi.org/10.48550/arXiv.2504.21440 [download-img]: https://img.shields.io/badge/dynamic/json?url=http%3A%2F%2Fjuliapkgstats.com%2Fapi%2Fv1%2Ftotal_downloads%2FQuantumToolbox&query=total_requests&label=Downloads [download-url]: https://juliapkgstats.com/pkg/QuantumToolbox @@ -177,6 +177,23 @@ You are most welcome to contribute to `QuantumToolbox.jl` development by forking For more information about contribution, including technical advice, please see the [Contributing to Quantum Toolbox in Julia](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing). +## Cite `QuantumToolbox.jl` +If you like `QuantumToolbox.jl`, we would appreciate it if you starred the repository in order to help us increase its visibility. Furthermore, if you find the framework useful in your research, we would be grateful if you could cite our arXiv preprint [ [arXiv:2504.21440 (2025)](https://doi.org/10.48550/arXiv.2504.21440) ] using the following bibtex entry: + +```bib +@article{QuantumToolbox-jl2025, + title={{QuantumToolbox.jl}: An efficient {Julia} framework for simulating open quantum systems}, + author={Mercurio, Alberto and Huang, Yi-Te and Cai, Li-Xun and Chen, Yueh-Nan and Savona, Vincenzo and Nori, Franco}, + journal={arXiv preprint arXiv:2504.21440}, + year={2025}, + publisher = {arXiv}, + eprint={2504.21440}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + doi = {10.48550/arXiv.2504.21440} +} +``` + ## Acknowledgements ### Fundings diff --git a/docs/make.jl b/docs/make.jl index 01e18c019..21b051f04 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -42,7 +42,7 @@ const PAGES = [ # "Key differences from QuTiP" => "getting_started/qutip_differences.md", "The Importance of Type-Stability" => "getting_started/type_stability.md", "Example: Create QuantumToolbox.jl Logo" => "getting_started/logo.md", - # "Cite QuantumToolbox.jl" => "getting_started/cite.md", + "Cite QuantumToolbox.jl" => "getting_started/cite.md", ], "Users Guide" => [ "Basic Operations on Quantum Objects" => [ diff --git a/docs/src/getting_started/cite.md b/docs/src/getting_started/cite.md new file mode 100644 index 000000000..a3a44e0ad --- /dev/null +++ b/docs/src/getting_started/cite.md @@ -0,0 +1,17 @@ +# [Cite QuantumToolbox.jl](@id doc:Cite) + +If you like `QuantumToolbox.jl`, we would appreciate it if you could cite our arXiv preprint [ [arXiv:2504.21440 (2025)](https://doi.org/10.48550/arXiv.2504.21440) ] using the following bibtex entry: + +```bib +@article{QuantumToolbox-jl2025, + title={{QuantumToolbox.jl}: An efficient {Julia} framework for simulating open quantum systems}, + author={Mercurio, Alberto and Huang, Yi-Te and Cai, Li-Xun and Chen, Yueh-Nan and Savona, Vincenzo and Nori, Franco}, + journal={arXiv preprint arXiv:2504.21440}, + year={2025}, + publisher = {arXiv}, + eprint={2504.21440}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + doi = {10.48550/arXiv.2504.21440} +} +``` diff --git a/docs/src/index.md b/docs/src/index.md index 0b32c754a..6fa526ab8 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -22,6 +22,9 @@ hero: - theme: alt text: API link: /resources/api + - theme: alt + text: Cite us + link: /getting_started/cite - theme: alt text: View on Github link: https://github.com/qutip/QuantumToolbox.jl From 201e066b57116075923b9781e1cad3c9ecc670b3 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 3 May 2025 11:32:12 +0200 Subject: [PATCH 260/329] Unify structure of `QuantumObjectType` (#456) --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- CHANGELOG.md | 2 + docs/src/getting_started/type_stability.md | 9 +- docs/src/resources/api.md | 6 - .../QuantumObject/QuantumObject.md | 2 +- docs/src/users_guide/extensions/cuda.md | 24 +- docs/src/users_guide/states_and_operators.md | 2 +- ext/QuantumToolboxMakieExt.jl | 12 +- src/correlations.jl | 40 ++-- src/deprecated.jl | 40 ++-- src/entropy.jl | 27 +-- src/metrics.jl | 23 +- src/negativity.jl | 18 +- src/qobj/arithmetic_and_attributes.jl | 207 +++++++----------- src/qobj/block_diagonal_form.jl | 4 +- src/qobj/boolean_functions.jl | 24 +- src/qobj/eigsolve.jl | 43 ++-- src/qobj/functions.jl | 61 +++--- src/qobj/operators.jl | 59 +++-- src/qobj/quantum_object.jl | 69 +++--- src/qobj/quantum_object_base.jl | 132 ++++------- src/qobj/quantum_object_evo.jl | 113 ++++------ src/qobj/states.jl | 43 ++-- src/qobj/superoperators.jl | 26 +-- src/qobj/synonyms.jl | 10 +- src/spectrum.jl | 22 +- src/spin_lattice.jl | 2 +- src/steadystate.jl | 38 ++-- src/time_evolution/lr_mesolve.jl | 10 +- src/time_evolution/mcsolve.jl | 26 +-- src/time_evolution/mesolve.jl | 16 +- src/time_evolution/sesolve.jl | 26 ++- src/time_evolution/smesolve.jl | 22 +- src/time_evolution/ssesolve.jl | 24 +- src/time_evolution/time_evolution.jl | 36 +-- .../time_evolution_dynamical.jl | 16 +- src/visualization.jl | 22 +- src/wigner.jl | 6 +- test/core-test/eigenvalues_and_operators.jl | 10 +- test/core-test/low_rank_dynamics.jl | 4 +- test/core-test/quantum_objects.jl | 76 +++---- test/core-test/quantum_objects_evo.jl | 28 +-- test/core-test/states_and_operators.jl | 8 +- 43 files changed, 588 insertions(+), 802 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index cc2245f6e..e2f33b540 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -22,7 +22,7 @@ body: attributes: label: Code Output description: Please paste the relevant output here (automatically formatted) - placeholder: "Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true\n2×2 Diagonal{ComplexF64, Vector{ComplexF64}}:\n 1.0+0.0im ⋅ \n ⋅ 1.0+0.0im" + placeholder: "Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true\n2×2 Diagonal{ComplexF64, Vector{ComplexF64}}:\n 1.0+0.0im ⋅ \n ⋅ 1.0+0.0im" render: shell - type: textarea id: expected-behaviour diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a9f46f76..d949e3651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Return `sesolve` when `mesolve` allows it. ([#455]) +- Simplify structure of `QuantumObjectType`s. ([#456]) ## [v0.30.1] Release date: 2025-04-24 @@ -215,3 +216,4 @@ Release date: 2024-11-13 [#450]: https://github.com/qutip/QuantumToolbox.jl/issues/450 [#453]: https://github.com/qutip/QuantumToolbox.jl/issues/453 [#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 +[#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456 diff --git a/docs/src/getting_started/type_stability.md b/docs/src/getting_started/type_stability.md index 2b5153665..bd22d3481 100644 --- a/docs/src/getting_started/type_stability.md +++ b/docs/src/getting_started/type_stability.md @@ -158,7 +158,7 @@ and its type is obj_type = typeof(σx_2) ``` -This is exactly what the Julia compiler sees: it is a [`QuantumObject`](@ref), composed by a field of type `SparseMatrixCSC{ComplexF64, Int64}` (i.e., the 8x8 matrix containing the Pauli matrix, tensored with the identity matrices of the other two qubits). Then, we can also see that it is a [`OperatorQuantumObject`](@ref), with `3` subsystems in total. Hence, just looking at the type of the object, the compiler has all the information it needs to generate a specialized version of the functions. +This is exactly what the Julia compiler sees: it is a [`QuantumObject`](@ref), composed by a field of type `SparseMatrixCSC{ComplexF64, Int64}` (i.e., the 8x8 matrix containing the Pauli matrix, tensored with the identity matrices of the other two qubits). Then, we can also see that it is a [`Operator`](@ref), with `3` subsystems in total. Hence, just looking at the type of the object, the compiler has all the information it needs to generate a specialized version of the functions. Let's see more in the details all the internal fields of the [`QuantumObject`](@ref) type: @@ -174,7 +174,6 @@ fieldnames(obj_type) σx_2.type ``` -[`Operator`](@ref) is a synonym for [`OperatorQuantumObject`](@ref). ```@example type-stability σx_2.dims @@ -184,7 +183,7 @@ The `dims` field contains the dimensions of the subsystems (in this case, three ```@example type-stability function reshape_operator_data(dims) - op = Qobj(randn(prod(dims), prod(dims)), type=Operator, dims=dims) + op = Qobj(randn(prod(dims), prod(dims)), type=Operator(), dims=dims) op_dims = op.dims op_data = op.data return reshape(op_data, vcat(op_dims, op_dims)...) @@ -235,7 +234,7 @@ function my_fock(N::Int, j::Int = 0; sparse::Bool = false) array = zeros(ComplexF64, N) array[j+1] = 1 end - return QuantumObject(array; type = Ket) + return QuantumObject(array; type = Ket()) end @show my_fock(2, 1) @show my_fock(2, 1; sparse = true) @@ -263,7 +262,7 @@ function my_fock_good(N::Int, j::Int = 0; sparse::Val = Val(false)) else array = sparsevec([j + 1], [1.0 + 0im], N) end - return QuantumObject(array; type = Ket) + return QuantumObject(array; type = Ket()) end @show my_fock_good(2, 1) @show my_fock_good(2, 1; sparse = Val(true)) diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index fc1d9fc7c..b9df5dbd9 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -21,17 +21,11 @@ Space Dimensions GeneralDimensions AbstractQuantumObject -BraQuantumObject Bra -KetQuantumObject Ket -OperatorQuantumObject Operator -OperatorBraQuantumObject OperatorBra -OperatorKetQuantumObject OperatorKet -SuperOperatorQuantumObject SuperOperator QuantumObject QuantumObjectEvolution diff --git a/docs/src/users_guide/QuantumObject/QuantumObject.md b/docs/src/users_guide/QuantumObject/QuantumObject.md index 0ac2a6fb7..8ca8104e0 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject.md @@ -54,7 +54,7 @@ Qobj(M, dims = SVector(2, 2)) # dims as StaticArrays.SVector (recommended) Please note that here we put the `dims` as a tuple `(2, 2)`. Although it supports also `Vector` type (`dims = [2, 2]`), it is recommended to use `Tuple` or `SVector` from [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) to improve performance. For a brief explanation on the impact of the type of `dims`, see the Section [The Importance of Type-Stability](@ref doc:Type-Stability). ```@example Qobj -Qobj(rand(4, 4), type = SuperOperator) +Qobj(rand(4, 4), type = SuperOperator()) ``` !!! note "Difference between `dims` and `size`" diff --git a/docs/src/users_guide/extensions/cuda.md b/docs/src/users_guide/extensions/cuda.md index 228a8acf1..eacef8083 100644 --- a/docs/src/users_guide/extensions/cuda.md +++ b/docs/src/users_guide/extensions/cuda.md @@ -36,7 +36,7 @@ V = fock(2, 0) # CPU dense vector ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element Vector{ComplexF64}: 1.0 + 0.0im 0.0 + 0.0im @@ -47,7 +47,7 @@ cu(V) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuArray{ComplexF64, 1, CUDA.DeviceMemory}: 1.0 + 0.0im 0.0 + 0.0im @@ -58,7 +58,7 @@ cu(V; word_size = 32) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuArray{ComplexF32, 1, CUDA.DeviceMemory}: 1.0 + 0.0im 0.0 + 0.0im @@ -69,7 +69,7 @@ M = Qobj([1 2; 3 4]) # CPU dense matrix ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=false 2×2 Matrix{Int64}: 1 2 3 4 @@ -80,7 +80,7 @@ cu(M) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=false 2×2 CuArray{Int64, 2, CUDA.DeviceMemory}: 1 2 3 4 @@ -91,7 +91,7 @@ cu(M; word_size = 32) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=false 2×2 CuArray{Int32, 2, CUDA.DeviceMemory}: 1 2 3 4 @@ -104,7 +104,7 @@ V = fock(2, 0; sparse=true) # CPU sparse vector ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element SparseVector{ComplexF64, Int64} with 1 stored entry: [1] = 1.0+0.0im ``` @@ -114,7 +114,7 @@ cu(V) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuSparseVector{ComplexF64, Int32} with 1 stored entry: [1] = 1.0+0.0im ``` @@ -124,7 +124,7 @@ cu(V; word_size = 32) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuSparseVector{ComplexF32, Int32} with 1 stored entry: [1] = 1.0+0.0im ``` @@ -134,7 +134,7 @@ M = sigmax() # CPU sparse matrix ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries: ⋅ 1.0+0.0im 1.0+0.0im ⋅ @@ -145,7 +145,7 @@ cu(M) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 CuSparseMatrixCSC{ComplexF64, Int32} with 2 stored entries: ⋅ 1.0+0.0im 1.0+0.0im ⋅ @@ -156,7 +156,7 @@ cu(M; word_size = 32) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 CuSparseMatrixCSC{ComplexF32, Int32} with 2 stored entries: ⋅ 1.0+0.0im 1.0+0.0im ⋅ diff --git a/docs/src/users_guide/states_and_operators.md b/docs/src/users_guide/states_and_operators.md index b50814ede..4795756c3 100644 --- a/docs/src/users_guide/states_and_operators.md +++ b/docs/src/users_guide/states_and_operators.md @@ -389,7 +389,7 @@ B = Qobj(rand(ComplexF64, N, N)) mat2vec(A * ρ * B) ≈ spre(A) * spost(B) * mat2vec(ρ) ≈ sprepost(A, B) * mat2vec(ρ) ``` -In addition, dynamical generators on this extended space, often called Liouvillian superoperators, can be created using the [`liouvillian`](@ref) function. Each of these takes a Hamiltonian along with a list of collapse operators, and returns a [`type=SuperOperator`](@ref SuperOperator) object that can be exponentiated to find the superoperator for that evolution. +In addition, dynamical generators on this extended space, often called Liouvillian superoperators, can be created using the [`liouvillian`](@ref) function. Each of these takes a Hamiltonian along with a list of collapse operators, and returns a [`QuantumObject`](@ref) of type [`SuperOperator`](@ref) that can be exponentiated to find the superoperator for that evolution. ```@example states_and_operators H = 10 * sigmaz() diff --git a/ext/QuantumToolboxMakieExt.jl b/ext/QuantumToolboxMakieExt.jl index 3beb577da..41aefe2b9 100644 --- a/ext/QuantumToolboxMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -54,7 +54,7 @@ function QuantumToolbox.plot_wigner( location::Union{GridPosition,Nothing} = nothing, colorbar::Bool = false, kwargs..., -) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{Bra,Ket,Operator}} QuantumToolbox.getVal(projection) == :two_dim || QuantumToolbox.getVal(projection) == :three_dim || throw(ArgumentError("Unsupported projection: $projection")) @@ -84,7 +84,7 @@ function _plot_wigner( location::Union{GridPosition,Nothing}, colorbar::Bool; kwargs..., -) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{Bra,Ket,Operator}} fig, location = _getFigAndLocation(location) lyt = GridLayout(location) @@ -117,7 +117,7 @@ function _plot_wigner( location::Union{GridPosition,Nothing}, colorbar::Bool; kwargs..., -) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{Bra,Ket,Operator}} fig, location = _getFigAndLocation(location) lyt = GridLayout(location) @@ -148,7 +148,7 @@ end unit_y_range::Bool = true, location::Union{GridPosition,Nothing} = nothing, kwargs... - ) where {SType<:Union{KetQuantumObject,OperatorQuantumObject}} + ) where {SType<:Union{Ket,Operator}} Plot the [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution of `ρ`. @@ -178,7 +178,7 @@ function QuantumToolbox.plot_fock_distribution( unit_y_range::Bool = true, location::Union{GridPosition,Nothing} = nothing, kwargs..., -) where {SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {SType<:Union{Bra,Ket,Operator}} return _plot_fock_distribution( library, ρ; @@ -196,7 +196,7 @@ function _plot_fock_distribution( unit_y_range::Bool = true, location::Union{GridPosition,Nothing} = nothing, kwargs..., -) where {SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {SType<:Union{Bra,Ket,Operator}} ρ = ket2dm(ρ) D = prod(ρ.dims) isapprox(tr(ρ), 1, atol = 1e-4) || (@warn "The input ρ should be normalized.") diff --git a/src/correlations.jl b/src/correlations.jl index 0df4375f2..3eae092b5 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -29,14 +29,11 @@ function correlation_3op_2t( tlist::AbstractVector, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, - C::QuantumObject{OperatorQuantumObject}; + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, + C::QuantumObject{Operator}; kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} # check tlist and τlist _check_correlation_time_list(tlist) _check_correlation_time_list(τlist) @@ -78,14 +75,11 @@ function correlation_3op_1t( ψ0::Union{Nothing,QuantumObject{StateOpType}}, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, - C::QuantumObject{OperatorQuantumObject}; + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, + C::QuantumObject{Operator}; kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} corr = correlation_3op_2t(H, ψ0, [0], τlist, c_ops, A, B, C; kwargs...) return corr[1, :] # 1 means tlist[1] = 0 @@ -114,14 +108,11 @@ function correlation_2op_2t( tlist::AbstractVector, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}; + A::QuantumObject{Operator}, + B::QuantumObject{Operator}; reverse::Bool = false, kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} C = eye(prod(H.dimensions), dims = H.dimensions) if reverse corr = correlation_3op_2t(H, ψ0, tlist, τlist, c_ops, A, B, C; kwargs...) @@ -153,14 +144,11 @@ function correlation_2op_1t( ψ0::Union{Nothing,QuantumObject{StateOpType}}, τlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}; + A::QuantumObject{Operator}, + B::QuantumObject{Operator}; reverse::Bool = false, kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} corr = correlation_2op_2t(H, ψ0, [0], τlist, c_ops, A, B; reverse = reverse, kwargs...) return corr[1, :] # 1 means tlist[1] = 0 diff --git a/src/deprecated.jl b/src/deprecated.jl index 56e5d04e7..f0a095bf0 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -33,15 +33,12 @@ correlation_3op_2t( ψ0::QuantumObject{StateOpType}, t_l::AbstractVector, τ_l::AbstractVector, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, - C::QuantumObject{OperatorQuantumObject}, + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, + C::QuantumObject{Operator}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} = error( +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} = error( "The parameter order of `correlation_3op_2t` has been changed, please use `?correlation_3op_2t` to check the updated docstring.", ) @@ -49,15 +46,12 @@ correlation_3op_1t( H::QuantumObject{HOpType}, ψ0::QuantumObject{StateOpType}, τ_l::AbstractVector, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, - C::QuantumObject{OperatorQuantumObject}, + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, + C::QuantumObject{Operator}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} = error( +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} = error( "The parameter order of `correlation_3op_1t` has been changed, please use `?correlation_3op_1t` to check the updated docstring.", ) @@ -66,15 +60,12 @@ correlation_2op_2t( ψ0::QuantumObject{StateOpType}, t_l::AbstractVector, τ_l::AbstractVector, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; reverse::Bool = false, kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} = error( +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} = error( "The parameter order of `correlation_2op_2t` has been changed, please use `?correlation_2op_2t` to check the updated docstring.", ) @@ -82,15 +73,12 @@ correlation_2op_1t( H::QuantumObject{HOpType}, ψ0::QuantumObject{StateOpType}, τ_l::AbstractVector, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; reverse::Bool = false, kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}, -} = error( +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator}} = error( "The parameter order of `correlation_2op_1t` has been changed, please use `?correlation_2op_1t` to check the updated docstring.", ) diff --git a/src/entropy.jl b/src/entropy.jl index bdf057d86..575ea3209 100644 --- a/src/entropy.jl +++ b/src/entropy.jl @@ -22,7 +22,7 @@ Pure state: ```jldoctest julia> ψ = fock(2,0) -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element Vector{ComplexF64}: 1.0 + 0.0im 0.0 + 0.0im @@ -35,7 +35,7 @@ Mixed state: ```jldoctest julia> ρ = maximally_mixed_dm(2) -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 Diagonal{ComplexF64, Vector{ComplexF64}}: 0.5-0.0im ⋅ ⋅ 0.5-0.0im @@ -44,11 +44,7 @@ julia> entropy_vn(ρ, base=2) 1.0 ``` """ -function entropy_vn( - ρ::QuantumObject{ObjType}; - base::Int = 0, - tol::Real = 1e-15, -) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject}} +function entropy_vn(ρ::QuantumObject{ObjType}; base::Int = 0, tol::Real = 1e-15) where {ObjType<:Union{Ket,Operator}} T = eltype(ρ) vals = eigenenergies(ket2dm(ρ)) indexes = findall(x -> abs(x) > tol, vals) @@ -78,10 +74,7 @@ function entropy_relative( σ::QuantumObject{ObjType2}; base::Int = 0, tol::Real = 1e-15, -) where { - ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, - ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, -} +) where {ObjType1<:Union{Ket,Operator},ObjType2<:Union{Ket,Operator}} check_dimensions(ρ, σ) # the logic of this code follows the detail given in the reference of the docstring @@ -131,8 +124,7 @@ Calculates the quantum linear entropy ``S_L = 1 - \textrm{Tr} \left[ \hat{\rho}^ Note that `ρ` can be either a [`Ket`](@ref) or an [`Operator`](@ref). """ -entropy_linear(ρ::QuantumObject{ObjType}) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject}} = - 1.0 - purity(ρ) # use 1.0 to make sure it always return value in Float-type +entropy_linear(ρ::QuantumObject{ObjType}) where {ObjType<:Union{Ket,Operator}} = 1.0 - purity(ρ) # use 1.0 to make sure it always return value in Float-type @doc raw""" entropy_mutual(ρAB::QuantumObject, selA, selB; kwargs...) @@ -153,7 +145,7 @@ function entropy_mutual( selA::Union{Int,AbstractVector{Int},Tuple}, selB::Union{Int,AbstractVector{Int},Tuple}; kwargs..., -) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject},N} +) where {ObjType<:Union{Ket,Operator},N} # check if selA and selB matches the dimensions of ρAB sel_A_B = (selA..., selB...) (length(sel_A_B) != N) && throw( @@ -185,8 +177,7 @@ entropy_conditional( ρAB::QuantumObject{ObjType,<:AbstractDimensions{N,N}}, selB::Union{Int,AbstractVector{Int},Tuple}; kwargs..., -) where {ObjType<:Union{KetQuantumObject,OperatorQuantumObject},N} = - entropy_vn(ρAB; kwargs...) - entropy_vn(ptrace(ρAB, selB); kwargs...) +) where {ObjType<:Union{Ket,Operator},N} = entropy_vn(ρAB; kwargs...) - entropy_vn(ptrace(ρAB, selB); kwargs...) @doc raw""" entanglement(ρ::QuantumObject, sel; kwargs...) @@ -203,7 +194,7 @@ function entanglement( ρ::QuantumObject{OpType}, sel::Union{Int,AbstractVector{Int},Tuple}, kwargs..., -) where {OpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{Ket,Operator}} p = purity(ρ) isapprox(p, 1; atol = 1e-2) || throw( ArgumentError( @@ -229,7 +220,7 @@ Calculate the [concurrence](https://en.wikipedia.org/wiki/Concurrence_(quantum_c - [Hill-Wootters1997](@citet) """ -function concurrence(ρ::QuantumObject{OpType}) where {OpType<:Union{KetQuantumObject,OperatorQuantumObject}} +function concurrence(ρ::QuantumObject{OpType}) where {OpType<:Union{Ket,Operator}} (ρ.dimensions == Dimensions((Space(2), Space(2)))) || throw( ArgumentError( "The `concurrence` only works for a two-qubit state, invalid dims = $(_get_dims_string(ρ.dimensions)).", diff --git a/src/metrics.jl b/src/metrics.jl index d9462682f..90c22b01a 100644 --- a/src/metrics.jl +++ b/src/metrics.jl @@ -16,14 +16,14 @@ Here, the definition is from [Nielsen-Chuang2011](@citet). It is the square root Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). """ -function fidelity(ρ::QuantumObject{OperatorQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) +function fidelity(ρ::QuantumObject{Operator}, σ::QuantumObject{Operator}) sqrt_ρ = sqrt(ρ) eigval = abs.(eigvals(sqrt_ρ * σ * sqrt_ρ)) return sum(sqrt, eigval) end -fidelity(ρ::QuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) = sqrt(abs(expect(ρ, ψ))) -fidelity(ψ::QuantumObject{KetQuantumObject}, σ::QuantumObject{OperatorQuantumObject}) = fidelity(σ, ψ) -fidelity(ψ::QuantumObject{KetQuantumObject}, ϕ::QuantumObject{KetQuantumObject}) = abs(dot(ψ, ϕ)) +fidelity(ρ::QuantumObject{Operator}, ψ::QuantumObject{Ket}) = sqrt(abs(expect(ρ, ψ))) +fidelity(ψ::QuantumObject{Ket}, σ::QuantumObject{Operator}) = fidelity(σ, ψ) +fidelity(ψ::QuantumObject{Ket}, ϕ::QuantumObject{Ket}) = abs(dot(ψ, ϕ)) @doc raw""" tracedist(ρ::QuantumObject, σ::QuantumObject) @@ -36,10 +36,7 @@ Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). tracedist( ρ::QuantumObject{ObjType1}, σ::QuantumObject{ObjType2}, -) where { - ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, - ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, -} = norm(ket2dm(ρ) - ket2dm(σ), 1) / 2 +) where {ObjType1<:Union{Ket,Operator},ObjType2<:Union{Ket,Operator}} = norm(ket2dm(ρ) - ket2dm(σ), 1) / 2 @doc raw""" hilbert_dist(ρ::QuantumObject, σ::QuantumObject) @@ -55,10 +52,7 @@ Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). function hilbert_dist( ρ::QuantumObject{ObjType1}, σ::QuantumObject{ObjType2}, -) where { - ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, - ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, -} +) where {ObjType1<:Union{Ket,Operator},ObjType2<:Union{Ket,Operator}} check_dimensions(ρ, σ) A = ket2dm(ρ) - ket2dm(σ) @@ -79,10 +73,7 @@ Note that `ρ` and `σ` must be either [`Ket`](@ref) or [`Operator`](@ref). function hellinger_dist( ρ::QuantumObject{ObjType1}, σ::QuantumObject{ObjType2}, -) where { - ObjType1<:Union{KetQuantumObject,OperatorQuantumObject}, - ObjType2<:Union{KetQuantumObject,OperatorQuantumObject}, -} +) where {ObjType1<:Union{Ket,Operator},ObjType2<:Union{Ket,Operator}} # Ket (pure state) doesn't need to do square root sqrt_ρ = isket(ρ) ? ket2dm(ρ) : sqrt(ρ) sqrt_σ = isket(σ) ? ket2dm(σ) : sqrt(σ) diff --git a/src/negativity.jl b/src/negativity.jl index eefaab3d3..e19afa341 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -8,7 +8,7 @@ where ``\hat{\rho}^{\Gamma}`` is the partial transpose of ``\hat{\rho}`` with re and ``\Vert \hat{X} \Vert_1=\textrm{Tr}\sqrt{\hat{X}^\dagger \hat{X}}`` is the trace norm. # Arguments -- `ρ::QuantumObject`: The density matrix (`ρ.type` must be [`OperatorQuantumObject`](@ref)). +- `ρ::QuantumObject`: The density matrix (`ρ.type` must be [`Operator`](@ref)). - `subsys::Int`: an index that indicates which subsystem to compute the negativity for. - `logarithmic::Bool`: choose whether to calculate logarithmic negativity or not. Default as `false` @@ -20,7 +20,7 @@ and ``\Vert \hat{X} \Vert_1=\textrm{Tr}\sqrt{\hat{X}^\dagger \hat{X}}`` is the t ```jldoctest julia> Ψ = bell_state(0, 0) -Quantum Object: type=Ket dims=[2, 2] size=(4,) +Quantum Object: type=Ket() dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im 0.0 + 0.0im @@ -29,7 +29,7 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) julia> ρ = ket2dm(Ψ) -Quantum Object: type=Operator dims=[2, 2] size=(4, 4) ishermitian=true +Quantum Object: type=Operator() dims=[2, 2] size=(4, 4) ishermitian=true 4×4 Matrix{ComplexF64}: 0.5+0.0im 0.0+0.0im 0.0+0.0im 0.5+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im @@ -64,13 +64,13 @@ end Return the partial transpose of a density matrix ``\rho``, where `mask` is an array/vector with length that equals the length of `ρ.dims`. The elements in `mask` are boolean (`true` or `false`) which indicates whether or not the corresponding subsystem should be transposed. # Arguments -- `ρ::QuantumObject`: The density matrix (`ρ.type` must be [`OperatorQuantumObject`](@ref)). +- `ρ::QuantumObject`: The density matrix (`ρ.type` must be [`Operator`](@ref)). - `mask::Vector{Bool}`: A boolean vector selects which subsystems should be transposed. # Returns - `ρ_pt::QuantumObject`: The density matrix with the selected subsystems transposed. """ -function partial_transpose(ρ::QuantumObject{OperatorQuantumObject}, mask::Vector{Bool}) +function partial_transpose(ρ::QuantumObject{Operator}, mask::Vector{Bool}) if length(mask) != length(ρ.dimensions) throw(ArgumentError("The length of \`mask\` should be equal to the length of \`ρ.dims\`.")) end @@ -78,7 +78,7 @@ function partial_transpose(ρ::QuantumObject{OperatorQuantumObject}, mask::Vecto end # for dense matrices -function _partial_transpose(ρ::QuantumObject{OperatorQuantumObject}, mask::Vector{Bool}) +function _partial_transpose(ρ::QuantumObject{Operator}, mask::Vector{Bool}) isa(ρ.dimensions, GeneralDimensions) && (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) @@ -97,14 +97,14 @@ function _partial_transpose(ρ::QuantumObject{OperatorQuantumObject}, mask::Vect ] return QuantumObject( reshape(permutedims(reshape(ρ.data, (dims..., dims...)), pt_idx), size(ρ)), - Operator, + Operator(), Dimensions(ρ.dimensions.to), ) end # for sparse matrices function _partial_transpose( - ρ::QuantumObject{OperatorQuantumObject,DimsType,<:AbstractSparseArray}, + ρ::QuantumObject{Operator,DimsType,<:AbstractSparseArray}, mask::Vector{Bool}, ) where {DimsType<:AbstractDimensions} isa(ρ.dimensions, GeneralDimensions) && @@ -144,5 +144,5 @@ function _partial_transpose( end end - return QuantumObject(sparse(I_pt, J_pt, V_pt, M, N), Operator, ρ.dimensions) + return QuantumObject(sparse(I_pt, J_pt, V_pt, M, N), Operator(), ρ.dimensions) end diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index a18a383c8..8eecc5acd 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -64,25 +64,25 @@ for ADimType in (:Dimensions, :GeneralDimensions) if ADimType == BDimType == :Dimensions @eval begin function Base.:(*)( - A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, - B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, + A::AbstractQuantumObject{Operator,<:$ADimType}, + B::AbstractQuantumObject{Operator,<:$BDimType}, ) check_dimensions(A, B) QType = promote_op_type(A, B) - return QType(A.data * B.data, Operator, A.dimensions) + return QType(A.data * B.data, Operator(), A.dimensions) end end else @eval begin function Base.:(*)( - A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, - B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, + A::AbstractQuantumObject{Operator,<:$ADimType}, + B::AbstractQuantumObject{Operator,<:$BDimType}, ) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) QType = promote_op_type(A, B) return QType( A.data * B.data, - Operator, + Operator(), GeneralDimensions(get_dimensions_to(A), get_dimensions_from(B)), ) end @@ -91,37 +91,37 @@ for ADimType in (:Dimensions, :GeneralDimensions) end end -function Base.:(*)(A::AbstractQuantumObject{OperatorQuantumObject}, B::QuantumObject{KetQuantumObject,<:Dimensions}) +function Base.:(*)(A::AbstractQuantumObject{Operator}, B::QuantumObject{Ket,<:Dimensions}) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) - return QuantumObject(A.data * B.data, Ket, Dimensions(get_dimensions_to(A))) + return QuantumObject(A.data * B.data, Ket(), Dimensions(get_dimensions_to(A))) end -function Base.:(*)(A::QuantumObject{BraQuantumObject,<:Dimensions}, B::AbstractQuantumObject{OperatorQuantumObject}) +function Base.:(*)(A::QuantumObject{Bra,<:Dimensions}, B::AbstractQuantumObject{Operator}) check_mul_dimensions(get_dimensions_from(A), get_dimensions_to(B)) - return QuantumObject(A.data * B.data, Bra, Dimensions(get_dimensions_from(B))) + return QuantumObject(A.data * B.data, Bra(), Dimensions(get_dimensions_from(B))) end -function Base.:(*)(A::QuantumObject{KetQuantumObject}, B::QuantumObject{BraQuantumObject}) +function Base.:(*)(A::QuantumObject{Ket}, B::QuantumObject{Bra}) check_dimensions(A, B) - return QuantumObject(A.data * B.data, Operator, A.dimensions) # to align with QuTiP, don't use kron(A, B) to do it. + return QuantumObject(A.data * B.data, Operator(), A.dimensions) # to align with QuTiP, don't use kron(A, B) to do it. end -function Base.:(*)(A::QuantumObject{BraQuantumObject}, B::QuantumObject{KetQuantumObject}) +function Base.:(*)(A::QuantumObject{Bra}, B::QuantumObject{Ket}) check_dimensions(A, B) return A.data * B.data end -function Base.:(*)(A::AbstractQuantumObject{SuperOperatorQuantumObject}, B::QuantumObject{OperatorQuantumObject}) +function Base.:(*)(A::AbstractQuantumObject{SuperOperator}, B::QuantumObject{Operator}) check_dimensions(A, B) - return QuantumObject(vec2mat(A.data * mat2vec(B.data)), Operator, A.dimensions) + return QuantumObject(vec2mat(A.data * mat2vec(B.data)), Operator(), A.dimensions) end -function Base.:(*)(A::QuantumObject{OperatorBraQuantumObject}, B::QuantumObject{OperatorKetQuantumObject}) +function Base.:(*)(A::QuantumObject{OperatorBra}, B::QuantumObject{OperatorKet}) check_dimensions(A, B) return A.data * B.data end -function Base.:(*)(A::AbstractQuantumObject{SuperOperatorQuantumObject}, B::QuantumObject{OperatorKetQuantumObject}) +function Base.:(*)(A::AbstractQuantumObject{SuperOperator}, B::QuantumObject{OperatorKet}) check_dimensions(A, B) - return QuantumObject(A.data * B.data, OperatorKet, A.dimensions) + return QuantumObject(A.data * B.data, OperatorKet(), A.dimensions) end -function Base.:(*)(A::QuantumObject{OperatorBraQuantumObject}, B::AbstractQuantumObject{SuperOperatorQuantumObject}) +function Base.:(*)(A::QuantumObject{OperatorBra}, B::AbstractQuantumObject{SuperOperator}) check_dimensions(A, B) - return QuantumObject(A.data * B.data, OperatorBra, A.dimensions) + return QuantumObject(A.data * B.data, OperatorBra(), A.dimensions) end Base.:(^)(A::QuantumObject, n::T) where {T<:Number} = QuantumObject(^(A.data, n), A.type, A.dimensions) @@ -138,10 +138,7 @@ Note that `A` and `B` should be [`Ket`](@ref) or [`OperatorKet`](@ref) !!! note `A ⋅ B` (where `⋅` can be typed by tab-completing `\cdot` in the REPL) is a synonym of `dot(A, B)`. """ -function LinearAlgebra.dot( - A::QuantumObject{OpType}, - B::QuantumObject{OpType}, -) where {OpType<:Union{KetQuantumObject,OperatorKetQuantumObject}} +function LinearAlgebra.dot(A::QuantumObject{OpType}, B::QuantumObject{OpType}) where {OpType<:Union{Ket,OperatorKet}} check_dimensions(A, B) return LinearAlgebra.dot(A.data, B.data) end @@ -159,18 +156,14 @@ Supports the following inputs: !!! note `matrix_element(i, A, j)` is a synonym of `dot(i, A, j)`. """ -function LinearAlgebra.dot( - i::QuantumObject{KetQuantumObject}, - A::AbstractQuantumObject{OperatorQuantumObject}, - j::QuantumObject{KetQuantumObject}, -) +function LinearAlgebra.dot(i::QuantumObject{Ket}, A::AbstractQuantumObject{Operator}, j::QuantumObject{Ket}) check_dimensions(i, A, j) return LinearAlgebra.dot(i.data, A.data, j.data) end function LinearAlgebra.dot( - i::QuantumObject{OperatorKetQuantumObject}, - A::AbstractQuantumObject{SuperOperatorQuantumObject}, - j::QuantumObject{OperatorKetQuantumObject}, + i::QuantumObject{OperatorKet}, + A::AbstractQuantumObject{SuperOperator}, + j::QuantumObject{OperatorKet}, ) check_dimensions(i, A, j) return LinearAlgebra.dot(i.data, A.data, j.data) @@ -190,7 +183,7 @@ Return a similar [`AbstractQuantumObject`](@ref) with `dims` and `type` are same Note that `A` must be [`Operator`](@ref) or [`SuperOperator`](@ref). """ -Base.one(A::AbstractQuantumObject{OpType}) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.one(A::AbstractQuantumObject{OpType}) where {OpType<:Union{Operator,SuperOperator}} = get_typename_wrapper(A)(one(A.data), A.type, A.dimensions) @doc raw""" @@ -205,9 +198,7 @@ Base.conj(A::AbstractQuantumObject) = get_typename_wrapper(A)(conj(A.data), A.ty Lazy matrix transpose of the [`AbstractQuantumObject`](@ref). """ -Base.transpose( - A::AbstractQuantumObject{OpType}, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.transpose(A::AbstractQuantumObject{OpType}) where {OpType<:Union{Operator,SuperOperator}} = get_typename_wrapper(A)(transpose(A.data), A.type, transpose(A.dimensions)) @doc raw""" @@ -220,29 +211,22 @@ Lazy adjoint (conjugate transposition) of the [`AbstractQuantumObject`](@ref) !!! note `A'` and `dag(A)` are synonyms of `adjoint(A)`. """ -Base.adjoint(A::AbstractQuantumObject{OpType}) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.adjoint(A::AbstractQuantumObject{OpType}) where {OpType<:Union{Operator,SuperOperator}} = get_typename_wrapper(A)(adjoint(A.data), A.type, adjoint(A.dimensions)) -Base.adjoint(A::QuantumObject{KetQuantumObject}) = QuantumObject(adjoint(A.data), Bra, adjoint(A.dimensions)) -Base.adjoint(A::QuantumObject{BraQuantumObject}) = QuantumObject(adjoint(A.data), Ket, adjoint(A.dimensions)) -Base.adjoint(A::QuantumObject{OperatorKetQuantumObject}) = - QuantumObject(adjoint(A.data), OperatorBra, adjoint(A.dimensions)) -Base.adjoint(A::QuantumObject{OperatorBraQuantumObject}) = - QuantumObject(adjoint(A.data), OperatorKet, adjoint(A.dimensions)) +Base.adjoint(A::QuantumObject{Ket}) = QuantumObject(adjoint(A.data), Bra(), adjoint(A.dimensions)) +Base.adjoint(A::QuantumObject{Bra}) = QuantumObject(adjoint(A.data), Ket(), adjoint(A.dimensions)) +Base.adjoint(A::QuantumObject{OperatorKet}) = QuantumObject(adjoint(A.data), OperatorBra(), adjoint(A.dimensions)) +Base.adjoint(A::QuantumObject{OperatorBra}) = QuantumObject(adjoint(A.data), OperatorKet(), adjoint(A.dimensions)) @doc raw""" inv(A::AbstractQuantumObject) Matrix inverse of the [`AbstractQuantumObject`](@ref). If `A` is a [`QuantumObjectEvolution`](@ref), the inverse is computed at the last computed time. """ -LinearAlgebra.inv( - A::AbstractQuantumObject{OpType}, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +LinearAlgebra.inv(A::AbstractQuantumObject{OpType}) where {OpType<:Union{Operator,SuperOperator}} = QuantumObject(sparse(inv(Matrix(A.data))), A.type, A.dimensions) -LinearAlgebra.Hermitian( - A::QuantumObject{OpType}, - uplo::Symbol = :U, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +LinearAlgebra.Hermitian(A::QuantumObject{OpType}, uplo::Symbol = :U) where {OpType<:Union{Operator,SuperOperator}} = QuantumObject(Hermitian(A.data, uplo), A.type, A.dimensions) @doc raw""" @@ -257,7 +241,7 @@ Note that this function only supports for [`Operator`](@ref) and [`SuperOperator ```jldoctest julia> a = destroy(20) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ @@ -269,11 +253,10 @@ julia> tr(a' * a) 190.0 + 0.0im ``` """ -LinearAlgebra.tr(A::QuantumObject{OpType}) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - tr(A.data) +LinearAlgebra.tr(A::QuantumObject{OpType}) where {OpType<:Union{Operator,SuperOperator}} = tr(A.data) LinearAlgebra.tr( A::QuantumObject{OpType,DimsType,<:Union{<:Hermitian{TF},Symmetric{TR}}}, -) where {OpType<:OperatorQuantumObject,DimsType,TF<:BlasFloat,TR<:Real} = real(tr(A.data)) +) where {OpType<:Operator,DimsType,TF<:BlasFloat,TR<:Real} = real(tr(A.data)) @doc raw""" svdvals(A::QuantumObject) @@ -295,7 +278,7 @@ Return the standard vector `p`-norm or [Schatten](https://en.wikipedia.org/wiki/ ```jldoctest julia> ψ = fock(10, 2) -Quantum Object: type=Ket dims=[10] size=(10,) +Quantum Object: type=Ket() dims=[10] size=(10,) 10-element Vector{ComplexF64}: 0.0 + 0.0im 0.0 + 0.0im @@ -312,15 +295,9 @@ julia> norm(ψ) 1.0 ``` """ -LinearAlgebra.norm( - A::QuantumObject{OpType}, - p::Real = 2, -) where {OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorKetQuantumObject,OperatorBraQuantumObject}} = +LinearAlgebra.norm(A::QuantumObject{OpType}, p::Real = 2) where {OpType<:Union{Ket,Bra,OperatorKet,OperatorBra}} = norm(A.data, p) -function LinearAlgebra.norm( - A::QuantumObject{OpType}, - p::Real = 1, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +function LinearAlgebra.norm(A::QuantumObject{OpType}, p::Real = 1) where {OpType<:Union{Operator,SuperOperator}} p == 2.0 && return norm(A.data, 2) return norm(svdvals(A), p) end @@ -340,11 +317,9 @@ Support for the following types of [`QuantumObject`](@ref): Also, see [`norm`](@ref) about its definition for different types of [`QuantumObject`](@ref). """ -LinearAlgebra.normalize( - A::QuantumObject{ObjType}, - p::Real = 2, -) where {ObjType<:Union{KetQuantumObject,BraQuantumObject}} = QuantumObject(A.data / norm(A, p), A.type, A.dimensions) -LinearAlgebra.normalize(A::QuantumObject{OperatorQuantumObject}, p::Real = 1) = +LinearAlgebra.normalize(A::QuantumObject{ObjType}, p::Real = 2) where {ObjType<:Union{Ket,Bra}} = + QuantumObject(A.data / norm(A, p), A.type, A.dimensions) +LinearAlgebra.normalize(A::QuantumObject{Operator}, p::Real = 1) = QuantumObject(A.data / norm(A, p), A.type, A.dimensions) @doc raw""" @@ -358,29 +333,17 @@ Support for the following types of [`QuantumObject`](@ref): Also, see [`norm`](@ref) about its definition for different types of [`QuantumObject`](@ref). """ -LinearAlgebra.normalize!( - A::QuantumObject{ObjType}, - p::Real = 2, -) where {ObjType<:Union{KetQuantumObject,BraQuantumObject}} = (rmul!(A.data, 1 / norm(A, p)); A) -LinearAlgebra.normalize!(A::QuantumObject{OperatorQuantumObject}, p::Real = 1) = (rmul!(A.data, 1 / norm(A, p)); A) - -LinearAlgebra.triu!( - A::QuantumObject{OpType}, - k::Integer = 0, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (triu!(A.data, k); A) -LinearAlgebra.tril!( - A::QuantumObject{OpType}, - k::Integer = 0, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (tril!(A.data, k); A) -LinearAlgebra.triu( - A::QuantumObject{OpType}, - k::Integer = 0, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +LinearAlgebra.normalize!(A::QuantumObject{ObjType}, p::Real = 2) where {ObjType<:Union{Ket,Bra}} = + (rmul!(A.data, 1 / norm(A, p)); A) +LinearAlgebra.normalize!(A::QuantumObject{Operator}, p::Real = 1) = (rmul!(A.data, 1 / norm(A, p)); A) + +LinearAlgebra.triu!(A::QuantumObject{OpType}, k::Integer = 0) where {OpType<:Union{Operator,SuperOperator}} = + (triu!(A.data, k); A) +LinearAlgebra.tril!(A::QuantumObject{OpType}, k::Integer = 0) where {OpType<:Union{Operator,SuperOperator}} = + (tril!(A.data, k); A) +LinearAlgebra.triu(A::QuantumObject{OpType}, k::Integer = 0) where {OpType<:Union{Operator,SuperOperator}} = QuantumObject(triu(A.data, k), A.type, A.dimensions) -LinearAlgebra.tril( - A::QuantumObject{OpType}, - k::Integer = 0, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +LinearAlgebra.tril(A::QuantumObject{OpType}, k::Integer = 0) where {OpType<:Union{Operator,SuperOperator}} = QuantumObject(tril(A.data, k), A.type, A.dimensions) LinearAlgebra.lmul!(a::Number, B::QuantumObject) = (lmul!(a, B.data); B) @@ -406,7 +369,7 @@ Matrix logarithm of [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -Base.log(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.log(A::QuantumObject{ObjType}) where {ObjType<:Union{Operator,SuperOperator}} = QuantumObject(log(to_dense(A.data)), A.type, A.dimensions) @doc raw""" @@ -416,14 +379,11 @@ Matrix exponential of [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -Base.exp( - A::QuantumObject{ObjType,DimsType,<:AbstractMatrix}, -) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = +Base.exp(A::QuantumObject{ObjType,DimsType,<:AbstractMatrix}) where {ObjType<:Union{Operator,SuperOperator},DimsType} = QuantumObject(to_sparse(exp(A.data)), A.type, A.dimensions) Base.exp( A::QuantumObject{ObjType,DimsType,<:AbstractSparseMatrix}, -) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject},DimsType} = - QuantumObject(_spexp(A.data), A.type, A.dimensions) +) where {ObjType<:Union{Operator,SuperOperator},DimsType} = QuantumObject(_spexp(A.data), A.type, A.dimensions) function _spexp(A::SparseMatrixCSC{T,M}; threshold = 1e-14, nonzero_tol = 1e-20) where {T<:Number,M<:Int} m = checksquare(A) # Throws exception if not square @@ -465,7 +425,7 @@ Matrix sine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -Base.sin(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +Base.sin(A::QuantumObject{ObjType}) where {ObjType<:Union{Operator,SuperOperator}} = (exp(1im * A) - exp(-1im * A)) / 2im @doc raw""" @@ -477,8 +437,7 @@ Matrix cosine of [`QuantumObject`](@ref), defined as Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -Base.cos(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = - (exp(1im * A) + exp(-1im * A)) / 2 +Base.cos(A::QuantumObject{ObjType}) where {ObjType<:Union{Operator,SuperOperator}} = (exp(1im * A) + exp(-1im * A)) / 2 @doc raw""" diag(A::QuantumObject, k::Int=0) @@ -487,18 +446,16 @@ Return the `k`-th diagonal elements of a matrix-type [`QuantumObject`](@ref) Note that this function only supports for [`Operator`](@ref) and [`SuperOperator`](@ref) """ -LinearAlgebra.diag( - A::QuantumObject{ObjType}, - k::Int = 0, -) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = diag(A.data, k) +LinearAlgebra.diag(A::QuantumObject{ObjType}, k::Int = 0) where {ObjType<:Union{Operator,SuperOperator}} = + diag(A.data, k) @doc raw""" proj(ψ::QuantumObject) Return the projector for a [`Ket`](@ref) or [`Bra`](@ref) type of [`QuantumObject`](@ref) """ -proj(ψ::QuantumObject{KetQuantumObject}) = ψ * ψ' -proj(ψ::QuantumObject{BraQuantumObject}) = ψ' * ψ +proj(ψ::QuantumObject{Ket}) = ψ * ψ' +proj(ψ::QuantumObject{Bra}) = ψ' * ψ @doc raw""" ptrace(QO::QuantumObject, sel) @@ -513,7 +470,7 @@ Two qubits in the state ``\ket{\psi} = \ket{e,g}``: ```jldoctest julia> ψ = kron(fock(2,0), fock(2,1)) -Quantum Object: type=Ket dims=[2, 2] size=(4,) +Quantum Object: type=Ket() dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.0 + 0.0im 1.0 + 0.0im @@ -522,7 +479,7 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) julia> ptrace(ψ, 2) -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 Matrix{ComplexF64}: 0.0+0.0im 0.0+0.0im 0.0+0.0im 1.0+0.0im @@ -532,7 +489,7 @@ or in an entangled state ``\ket{\psi} = \frac{1}{\sqrt{2}} \left( \ket{e,e} + \k ```jldoctest julia> ψ = 1 / √2 * (kron(fock(2,0), fock(2,0)) + kron(fock(2,1), fock(2,1))) -Quantum Object: type=Ket dims=[2, 2] size=(4,) +Quantum Object: type=Ket() dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im 0.0 + 0.0im @@ -541,13 +498,13 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) julia> ptrace(ψ, 1) -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 Matrix{ComplexF64}: 0.5+0.0im 0.0+0.0im 0.0+0.0im 0.5+0.0im ``` """ -function ptrace(QO::QuantumObject{KetQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) +function ptrace(QO::QuantumObject{Ket}, sel::Union{AbstractVector{Int},Tuple}) _non_static_array_warning("sel", sel) if length(sel) == 0 # return full trace for empty sel @@ -564,12 +521,12 @@ function ptrace(QO::QuantumObject{KetQuantumObject}, sel::Union{AbstractVector{I _sort_sel = sort(SVector{length(sel),Int}(sel)) ρtr, dkeep = _ptrace_ket(QO.data, QO.dims, _sort_sel) - return QuantumObject(ρtr, type = Operator, dims = Dimensions(dkeep)) + return QuantumObject(ρtr, type = Operator(), dims = Dimensions(dkeep)) end -ptrace(QO::QuantumObject{BraQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel) +ptrace(QO::QuantumObject{Bra}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel) -function ptrace(QO::QuantumObject{OperatorQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) +function ptrace(QO::QuantumObject{Operator}, sel::Union{AbstractVector{Int},Tuple}) # TODO: support for special cases when some of the subsystems have same `to` and `from` space isa(QO.dimensions, GeneralDimensions) && (get_dimensions_to(QO) != get_dimensions_from(QO)) && @@ -592,7 +549,7 @@ function ptrace(QO::QuantumObject{OperatorQuantumObject}, sel::Union{AbstractVec dims = dimensions_to_dims(get_dimensions_to(QO)) _sort_sel = sort(SVector{length(sel),Int}(sel)) ρtr, dkeep = _ptrace_oper(QO.data, dims, _sort_sel) - return QuantumObject(ρtr, type = Operator, dims = Dimensions(dkeep)) + return QuantumObject(ρtr, type = Operator(), dims = Dimensions(dkeep)) end ptrace(QO::QuantumObject, sel::Int) = ptrace(QO, SVector(sel)) @@ -668,8 +625,8 @@ Calculate the purity of a [`QuantumObject`](@ref): ``\textrm{Tr}(\rho^2)`` Note that this function only supports for [`Ket`](@ref), [`Bra`](@ref), and [`Operator`](@ref) """ -purity(ρ::QuantumObject{ObjType}) where {ObjType<:Union{KetQuantumObject,BraQuantumObject}} = sum(abs2, ρ.data) -purity(ρ::QuantumObject{OperatorQuantumObject}) = real(tr(ρ.data^2)) +purity(ρ::QuantumObject{ObjType}) where {ObjType<:Union{Ket,Bra}} = sum(abs2, ρ.data) +purity(ρ::QuantumObject{Operator}) = real(tr(ρ.data^2)) @doc raw""" tidyup(A::QuantumObject, tol::Real=1e-14) @@ -709,7 +666,7 @@ Get the coherence value ``\alpha`` by measuring the expectation value of the des It returns both ``\alpha`` and the corresponding state with the coherence removed: ``\ket{\delta_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \ket{\psi}`` for a pure state, and ``\hat{\rho_\alpha} = \exp ( \alpha^* \hat{a} - \alpha \hat{a}^\dagger ) \hat{\rho} \exp ( -\bar{\alpha} \hat{a} + \alpha \hat{a}^\dagger )`` for a density matrix. These states correspond to the quantum fluctuations around the coherent state ``\ket{\alpha}`` or ``|\alpha\rangle\langle\alpha|``. """ -function get_coherence(ψ::QuantumObject{KetQuantumObject}) +function get_coherence(ψ::QuantumObject{Ket}) a = destroy(prod(ψ.dimensions)) α = expect(a, ψ) D = exp(α * a' - conj(α) * a) @@ -717,7 +674,7 @@ function get_coherence(ψ::QuantumObject{KetQuantumObject}) return α, D' * ψ end -function get_coherence(ρ::QuantumObject{OperatorQuantumObject}) +function get_coherence(ρ::QuantumObject{Operator}) a = destroy(prod(ρ.dimensions)) α = expect(a, ρ) D = exp(α * a' - conj(α) * a) @@ -755,7 +712,7 @@ true function SparseArrays.permute( A::QuantumObject{ObjType}, order::Union{AbstractVector{Int},Tuple}, -) where {ObjType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} +) where {ObjType<:Union{Ket,Bra,Operator}} (length(order) != length(A.dimensions)) && throw(ArgumentError("The order list must have the same length as the number of subsystems (A.dims)")) @@ -773,19 +730,15 @@ function SparseArrays.permute( return QuantumObject(reshape(permutedims(reshape(A.data, dims...), Tuple(perm)), size(A)), A.type, order_dimensions) end -_dims_and_perm( - ::ObjType, - dims::SVector{N,Int}, - order::AbstractVector{Int}, - L::Int, -) where {ObjType<:Union{KetQuantumObject,BraQuantumObject},N} = reverse(dims), reverse((L + 1) .- order) +_dims_and_perm(::ObjType, dims::SVector{N,Int}, order::AbstractVector{Int}, L::Int) where {ObjType<:Union{Ket,Bra},N} = + reverse(dims), reverse((L + 1) .- order) # if dims originates from Dimensions -_dims_and_perm(::OperatorQuantumObject, dims::SVector{N,Int}, order::AbstractVector{Int}, L::Int) where {N} = +_dims_and_perm(::Operator, dims::SVector{N,Int}, order::AbstractVector{Int}, L::Int) where {N} = reverse(vcat(dims, dims)), reverse((2 * L + 1) .- vcat(order, order .+ L)) # if dims originates from GeneralDimensions -_dims_and_perm(::OperatorQuantumObject, dims::SVector{2,SVector{N,Int}}, order::AbstractVector{Int}, L::Int) where {N} = +_dims_and_perm(::Operator, dims::SVector{2,SVector{N,Int}}, order::AbstractVector{Int}, L::Int) where {N} = reverse(vcat(dims[2], dims[1])), reverse((2 * L + 1) .- vcat(order, order .+ L)) _order_dimensions(dimensions::Dimensions, order::AbstractVector{Int}) = Dimensions(dimensions.to[order]) diff --git a/src/qobj/block_diagonal_form.jl b/src/qobj/block_diagonal_form.jl index 118189492..13367477b 100644 --- a/src/qobj/block_diagonal_form.jl +++ b/src/qobj/block_diagonal_form.jl @@ -50,9 +50,7 @@ Return the block-diagonal form of a [`QuantumObject`](@ref). This is very useful # Returns The [`BlockDiagonalForm`](@ref) of `A`. """ -function block_diagonal_form( - A::QuantumObject{OpType}, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +function block_diagonal_form(A::QuantumObject{OpType}) where {OpType<:Union{Operator,SuperOperator}} bdf = block_diagonal_form(A.data) B = QuantumObject(bdf.B, type = A.type, dims = A.dimensions) P = QuantumObject(bdf.P, type = A.type, dims = A.dimensions) diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index 935d1b3d9..9a5cafa20 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -8,55 +8,55 @@ export isunitary @doc raw""" isbra(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`BraQuantumObject`](@ref). Default case returns `false` for any other inputs. +Checks if the [`QuantumObject`](@ref) `A` is a [`Bra`](@ref). Default case returns `false` for any other inputs. """ isbra(A::QuantumObject) = isbra(typeof(A)) -isbra(::Type{<:QuantumObject{BraQuantumObject,N}}) where {N} = true +isbra(::Type{<:QuantumObject{Bra,N}}) where {N} = true isbra(A) = false # default case @doc raw""" isket(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`KetQuantumObject`](@ref). Default case returns `false` for any other inputs. +Checks if the [`QuantumObject`](@ref) `A` is a [`Ket`](@ref). Default case returns `false` for any other inputs. """ isket(A::QuantumObject) = isket(typeof(A)) -isket(::Type{<:QuantumObject{KetQuantumObject,N}}) where {N} = true +isket(::Type{<:QuantumObject{Ket,N}}) where {N} = true isket(A) = false # default case @doc raw""" isoper(A) -Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`OperatorQuantumObject`](@ref). Default case returns `false` for any other inputs. +Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`Operator`](@ref). Default case returns `false` for any other inputs. """ isoper(A::AbstractQuantumObject) = isoper(typeof(A)) -isoper(::Type{<:AbstractQuantumObject{OperatorQuantumObject,N}}) where {N} = true +isoper(::Type{<:AbstractQuantumObject{Operator,N}}) where {N} = true isoper(A) = false # default case @doc raw""" isoperbra(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBraQuantumObject`](@ref). Default case returns `false` for any other inputs. +Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBra`](@ref). Default case returns `false` for any other inputs. """ isoperbra(A::QuantumObject) = isoperbra(typeof(A)) -isoperbra(::Type{<:QuantumObject{OperatorBraQuantumObject,N}}) where {N} = true +isoperbra(::Type{<:QuantumObject{OperatorBra,N}}) where {N} = true isoperbra(A) = false # default case @doc raw""" isoperket(A) -Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKetQuantumObject`](@ref). Default case returns `false` for any other inputs. +Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKet`](@ref). Default case returns `false` for any other inputs. """ isoperket(A::QuantumObject) = isoperket(typeof(A)) -isoperket(::Type{<:QuantumObject{OperatorKetQuantumObject,N}}) where {N} = true +isoperket(::Type{<:QuantumObject{OperatorKet,N}}) where {N} = true isoperket(A) = false # default case @doc raw""" issuper(A) -Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`SuperOperatorQuantumObject`](@ref). Default case returns `false` for any other inputs. +Checks if the [`AbstractQuantumObject`](@ref) `A` is a [`SuperOperator`](@ref). Default case returns `false` for any other inputs. """ issuper(A::AbstractQuantumObject) = issuper(typeof(A)) -issuper(::Type{<:AbstractQuantumObject{SuperOperatorQuantumObject,N}}) where {N} = true +issuper(::Type{<:AbstractQuantumObject{<:SuperOperatorType,N}}) where {N} = true issuper(A) = false # default case @doc raw""" diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 986a25321..1d8f256e3 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -27,7 +27,7 @@ A struct containing the eigenvalues, the eigenvectors, and some information from One can obtain the eigenvalues and the corresponding [`QuantumObject`](@ref)-type eigenvectors by: ```jldoctest julia> result = eigenstates(sigmax()) -EigsolveResult: type=Operator dims=[2] +EigsolveResult: type=Operator() dims=[2] values: 2-element Vector{ComplexF64}: -1.0 + 0.0im @@ -45,14 +45,14 @@ julia> λ 1.0 + 0.0im julia> ψ -2-element Vector{QuantumObject{KetQuantumObject, Dimensions{1, Tuple{Space}}, Vector{ComplexF64}}}: +2-element Vector{QuantumObject{Ket, Dimensions{1, Tuple{Space}}, Vector{ComplexF64}}}: -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element Vector{ComplexF64}: -0.7071067811865475 + 0.0im 0.7071067811865475 + 0.0im -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im 0.7071067811865475 + 0.0im @@ -66,7 +66,7 @@ julia> U struct EigsolveResult{ T1<:Vector{<:Number}, T2<:AbstractMatrix{<:Number}, - ObjType<:Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject}, + ObjType<:Union{Nothing,Operator,SuperOperator}, DimType<:Union{Nothing,AbstractDimensions}, } values::T1 @@ -90,10 +90,10 @@ end Base.iterate(res::EigsolveResult) = (res.values, Val(:vector_list)) Base.iterate(res::EigsolveResult{T1,T2,Nothing}, ::Val{:vector_list}) where {T1,T2} = ([res.vectors[:, k] for k in 1:length(res.values)], Val(:vectors)) -Base.iterate(res::EigsolveResult{T1,T2,OperatorQuantumObject}, ::Val{:vector_list}) where {T1,T2} = - ([QuantumObject(res.vectors[:, k], Ket, res.dimensions) for k in 1:length(res.values)], Val(:vectors)) -Base.iterate(res::EigsolveResult{T1,T2,SuperOperatorQuantumObject}, ::Val{:vector_list}) where {T1,T2} = - ([QuantumObject(res.vectors[:, k], OperatorKet, res.dimensions) for k in 1:length(res.values)], Val(:vectors)) +Base.iterate(res::EigsolveResult{T1,T2,Operator}, ::Val{:vector_list}) where {T1,T2} = + ([QuantumObject(res.vectors[:, k], Ket(), res.dimensions) for k in 1:length(res.values)], Val(:vectors)) +Base.iterate(res::EigsolveResult{T1,T2,SuperOperator}, ::Val{:vector_list}) where {T1,T2} = + ([QuantumObject(res.vectors[:, k], OperatorKet(), res.dimensions) for k in 1:length(res.values)], Val(:vectors)) Base.iterate(res::EigsolveResult, ::Val{:vectors}) = (res.vectors, Val(:done)) Base.iterate(res::EigsolveResult, ::Val{:done}) = nothing @@ -170,7 +170,7 @@ function _eigsolve( m::Int = max(20, 2 * k + 1); tol::Real = 1e-8, maxiter::Int = 200, -) where {T<:BlasFloat,ObjType<:Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {T<:BlasFloat,ObjType<:Union{Nothing,Operator,SuperOperator}} n = size(A, 2) V = similar(b, n, m + 1) H = zeros(T, m + 1, m) @@ -312,7 +312,7 @@ end function eigsolve( A; v0::Union{Nothing,AbstractVector} = nothing, - type::Union{Nothing,OperatorQuantumObject,SuperOperatorQuantumObject} = nothing, + type::Union{Nothing,Operator,SuperOperator} = nothing, dimensions = nothing, sigma::Union{Nothing,Real} = nothing, eigvals::Int = 1, @@ -404,11 +404,11 @@ function eigsolve_al( maxiter::Int = 200, eigstol::Real = 1e-6, kwargs..., -) where {HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {HOpType<:Union{Operator,SuperOperator}} L_evo = _mesolve_make_L_QobjEvo(H, c_ops) prob = mesolveProblem( L_evo, - QuantumObject(ρ0, type = Operator, dims = H.dimensions), + QuantumObject(ρ0, type = Operator(), dims = H.dimensions), [zero(T), T]; params = params, progress_bar = Val(false), @@ -447,7 +447,7 @@ julia> H = a + a'; julia> using LinearAlgebra; julia> E, ψ, U = eigen(H) -EigsolveResult: type=Operator dims=[5] +EigsolveResult: type=Operator() dims=[5] values: 5-element Vector{ComplexF64}: -2.8569700138728 + 0.0im @@ -467,10 +467,7 @@ julia> expect(H, ψ[1]) ≈ E[1] true ``` """ -function LinearAlgebra.eigen( - A::QuantumObject{OpType}; - kwargs..., -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +function LinearAlgebra.eigen(A::QuantumObject{OpType}; kwargs...) where {OpType<:Union{Operator,SuperOperator}} MT = typeof(A.data) F = eigen(to_dense(A.data); kwargs...) # This fixes a type inference issue. But doesn't work for GPU arrays @@ -485,10 +482,8 @@ end Same as [`eigen(A::QuantumObject; kwargs...)`](@ref) but for only the eigenvalues. """ -LinearAlgebra.eigvals( - A::QuantumObject{OpType}; - kwargs..., -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = eigvals(to_dense(A.data); kwargs...) +LinearAlgebra.eigvals(A::QuantumObject{OpType}; kwargs...) where {OpType<:Union{Operator,SuperOperator}} = + eigvals(to_dense(A.data); kwargs...) @doc raw""" eigenenergies(A::QuantumObject; sparse::Bool=false, kwargs...) @@ -507,7 +502,7 @@ function eigenenergies( A::QuantumObject{OpType}; sparse::Bool = false, kwargs..., -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{Operator,SuperOperator}} if !sparse return eigvals(A; kwargs...) else @@ -532,7 +527,7 @@ function eigenstates( A::QuantumObject{OpType}; sparse::Bool = false, kwargs..., -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{Operator,SuperOperator}} if !sparse return eigen(A; kwargs...) else diff --git a/src/qobj/functions.jl b/src/qobj/functions.jl index 694e6b05f..820c20c30 100644 --- a/src/qobj/functions.jl +++ b/src/qobj/functions.jl @@ -12,9 +12,9 @@ export vec2mat, mat2vec Transform the ket state ``\ket{\psi}`` into a pure density matrix ``\hat{\rho} = |\psi\rangle\langle\psi|``. """ -ket2dm(ψ::QuantumObject{KetQuantumObject}) = ψ * ψ' +ket2dm(ψ::QuantumObject{Ket}) = ψ * ψ' -ket2dm(ρ::QuantumObject{OperatorQuantumObject}) = ρ +ket2dm(ρ::QuantumObject{Operator}) = ρ @doc raw""" expect(O::Union{AbstractQuantumObject,Vector{AbstractQuantumObject}}, ψ::Union{QuantumObject,Vector{QuantumObject}}) @@ -52,34 +52,33 @@ julia> round.(expect([a' * a, a' + a, a], [ψ1, ψ2]), digits = 1) 0.0+0.0im 0.6+0.8im ``` """ -expect(O::AbstractQuantumObject{OperatorQuantumObject}, ψ::QuantumObject{KetQuantumObject}) = - dot(ψ.data, O.data, ψ.data) -expect(O::AbstractQuantumObject{OperatorQuantumObject}, ψ::QuantumObject{BraQuantumObject}) = expect(O, ψ') -expect(O::QuantumObject{OperatorQuantumObject}, ρ::QuantumObject{OperatorQuantumObject}) = tr(O * ρ) +expect(O::AbstractQuantumObject{Operator}, ψ::QuantumObject{Ket}) = dot(ψ.data, O.data, ψ.data) +expect(O::AbstractQuantumObject{Operator}, ψ::QuantumObject{Bra}) = expect(O, ψ') +expect(O::QuantumObject{Operator}, ρ::QuantumObject{Operator}) = tr(O * ρ) expect( - O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, - ψ::QuantumObject{KetQuantumObject}, + O::QuantumObject{Operator,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, + ψ::QuantumObject{Ket}, ) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = real(dot(ψ.data, O.data, ψ.data)) expect( - O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, - ψ::QuantumObject{BraQuantumObject}, + O::QuantumObject{Operator,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, + ψ::QuantumObject{Bra}, ) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = real(expect(O, ψ')) expect( - O::QuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, - ρ::QuantumObject{OperatorQuantumObject}, + O::QuantumObject{Operator,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}, + ρ::QuantumObject{Operator}, ) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = real(tr(O * ρ)) expect( - O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}}, + O::AbstractVector{<:AbstractQuantumObject{Operator,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}}, ρ::QuantumObject, ) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} = expect.(O, Ref(ρ)) -function expect(O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject}}, ρ::QuantumObject) +function expect(O::AbstractVector{<:AbstractQuantumObject{Operator}}, ρ::QuantumObject) result = Vector{ComplexF64}(undef, length(O)) result .= expect.(O, Ref(ρ)) return result end -expect(O::AbstractQuantumObject{OperatorQuantumObject}, ρ::AbstractVector{<:QuantumObject}) = expect.(Ref(O), ρ) +expect(O::AbstractQuantumObject{Operator}, ρ::AbstractVector{<:QuantumObject}) = expect.(Ref(O), ρ) function expect( - O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}}, + O::AbstractVector{<:AbstractQuantumObject{Operator,DimsType,<:Union{<:Hermitian{TF},<:Symmetric{TR}}}}, ρ::AbstractVector{<:QuantumObject}, ) where {DimsType<:AbstractDimensions,TF<:Number,TR<:Real} N_ops = length(O) @@ -89,7 +88,7 @@ function expect( end return result end -function expect(O::AbstractVector{<:AbstractQuantumObject{OperatorQuantumObject}}, ρ::AbstractVector{<:QuantumObject}) +function expect(O::AbstractVector{<:AbstractQuantumObject{Operator}}, ρ::AbstractVector{<:QuantumObject}) N_ops = length(O) result = Matrix{ComplexF64}(undef, N_ops, length(ρ)) for i in 1:N_ops @@ -109,8 +108,8 @@ The function returns a real number if `O` is hermitian, and returns a complex nu Note that `ψ` can also be given as a list of [`QuantumObject`](@ref), it returns a list of expectation values. """ -variance(O::QuantumObject{OperatorQuantumObject}, ψ::QuantumObject) = expect(O^2, ψ) - expect(O, ψ)^2 -variance(O::QuantumObject{OperatorQuantumObject}, ψ::Vector{<:QuantumObject}) = expect(O^2, ψ) .- expect(O, ψ) .^ 2 +variance(O::QuantumObject{Operator}, ψ::QuantumObject) = expect(O^2, ψ) - expect(O, ψ)^2 +variance(O::QuantumObject{Operator}, ψ::Vector{<:QuantumObject}) = expect(O^2, ψ) .- expect(O, ψ) .^ 2 @doc raw""" to_dense(A::QuantumObject) @@ -170,7 +169,7 @@ Returns the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) ```jldoctest julia> a = destroy(20) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ @@ -190,7 +189,7 @@ julia> a.dims, O.dims function Base.kron( A::AbstractQuantumObject{OpType,<:Dimensions}, B::AbstractQuantumObject{OpType,<:Dimensions}, -) where {OpType<:Union{KetQuantumObject,BraQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{Ket,Bra,Operator}} QType = promote_op_type(A, B) _lazy_tensor_warning(A.data, B.data) return QType(kron(A.data, B.data), A.type, Dimensions((A.dimensions.to..., B.dimensions.to...))) @@ -202,14 +201,14 @@ for ADimType in (:Dimensions, :GeneralDimensions) if !(ADimType == BDimType == :Dimensions) # not for this case because it's already implemented @eval begin function Base.kron( - A::AbstractQuantumObject{OperatorQuantumObject,<:$ADimType}, - B::AbstractQuantumObject{OperatorQuantumObject,<:$BDimType}, + A::AbstractQuantumObject{Operator,<:$ADimType}, + B::AbstractQuantumObject{Operator,<:$BDimType}, ) QType = promote_op_type(A, B) _lazy_tensor_warning(A.data, B.data) return QType( kron(A.data, B.data), - Operator, + Operator(), GeneralDimensions( (get_dimensions_to(A)..., get_dimensions_to(B)...), (get_dimensions_from(A)..., get_dimensions_from(B)...), @@ -222,8 +221,8 @@ for ADimType in (:Dimensions, :GeneralDimensions) end # if A and B are different type (must return Operator with GeneralDimensions) -for AOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) - for BOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) +for AOpType in (:Ket, :Bra, :Operator) + for BOpType in (:Ket, :Bra, :Operator) if (AOpType != BOpType) @eval begin function Base.kron(A::AbstractQuantumObject{$AOpType}, B::AbstractQuantumObject{$BOpType}) @@ -231,7 +230,7 @@ for AOpType in (:KetQuantumObject, :BraQuantumObject, :OperatorQuantumObject) _lazy_tensor_warning(A.data, B.data) return QType( kron(A.data, B.data), - Operator, + Operator(), GeneralDimensions( (get_dimensions_to(A)..., get_dimensions_to(B)...), (get_dimensions_from(A)..., get_dimensions_from(B)...), @@ -263,23 +262,23 @@ end vec2mat(A::QuantumObject) vector_to_operator(A::QuantumObject) -Convert a quantum object from vector ([`OperatorKetQuantumObject`](@ref)-type) to matrix ([`OperatorQuantumObject`](@ref)-type) +Convert a quantum object from vector ([`OperatorKet`](@ref)-type) to matrix ([`Operator`](@ref)-type) !!! note `vector_to_operator` is a synonym of `vec2mat`. """ -vec2mat(A::QuantumObject{OperatorKetQuantumObject}) = QuantumObject(vec2mat(A.data), Operator, A.dimensions) +vec2mat(A::QuantumObject{OperatorKet}) = QuantumObject(vec2mat(A.data), Operator(), A.dimensions) @doc raw""" mat2vec(A::QuantumObject) operator_to_vector(A::QuantumObject) -Convert a quantum object from matrix ([`OperatorQuantumObject`](@ref)-type) to vector ([`OperatorKetQuantumObject`](@ref)-type) +Convert a quantum object from matrix ([`Operator`](@ref)-type) to vector ([`OperatorKet`](@ref)-type) !!! note `operator_to_vector` is a synonym of `mat2vec`. """ -mat2vec(A::QuantumObject{OperatorQuantumObject}) = QuantumObject(mat2vec(A.data), OperatorKet, A.dimensions) +mat2vec(A::QuantumObject{Operator}) = QuantumObject(mat2vec(A.data), OperatorKet(), A.dimensions) @doc raw""" mat2vec(A::AbstractMatrix) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index d7c460fdb..fb0c2041f 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -48,7 +48,7 @@ function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, : # Because inv(Λ) ⋅ R has real and strictly positive elements, Q · Λ is therefore Haar distributed. Λ = diag(R) # take the diagonal elements of R Λ ./= abs.(Λ) # rescaling the elements - return QuantumObject(to_dense(Q * Diagonal(Λ)); type = Operator, dims = dimensions) + return QuantumObject(to_dense(Q * Diagonal(Λ)); type = Operator(), dims = dimensions) end function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, ::Val{:exp}) N = prod(dimensions) @@ -57,7 +57,7 @@ function rand_unitary(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}, : Z = randn(ComplexF64, N, N) # generate Hermitian matrix - H = QuantumObject((Z + Z') / 2; type = Operator, dims = dimensions) + H = QuantumObject((Z + Z') / 2; type = Operator(), dims = dimensions) return to_dense(exp(-1.0im * H)) end @@ -73,8 +73,7 @@ Return the commutator (or `anti`-commutator) of the two [`QuantumObject`](@ref): Note that `A` and `B` must be [`Operator`](@ref) """ -commutator(A::QuantumObject{OperatorQuantumObject}, B::QuantumObject{OperatorQuantumObject}; anti::Bool = false) = - A * B - (-1)^anti * B * A +commutator(A::QuantumObject{Operator}, B::QuantumObject{Operator}; anti::Bool = false) = A * B - (-1)^anti * B * A @doc raw""" destroy(N::Int) @@ -88,7 +87,7 @@ This operator acts on a fock state as ``\hat{a} \ket{n} = \sqrt{n} \ket{n-1}``. ```jldoctest julia> a = destroy(20) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ @@ -100,7 +99,7 @@ julia> fock(20, 3)' * a * fock(20, 4) 2.0 + 0.0im ``` """ -destroy(N::Int) = QuantumObject(spdiagm(1 => Array{ComplexF64}(sqrt.(1:(N-1)))), Operator, N) +destroy(N::Int) = QuantumObject(spdiagm(1 => Array{ComplexF64}(sqrt.(1:(N-1)))), Operator(), N) @doc raw""" create(N::Int) @@ -114,7 +113,7 @@ This operator acts on a fock state as ``\hat{a}^\dagger \ket{n} = \sqrt{n+1} \ke ```jldoctest julia> a_d = create(20) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠈⠢⡀⠀⠀⠀⠀⠀⠀⎥ @@ -126,7 +125,7 @@ julia> fock(20, 4)' * a_d * fock(20, 3) 2.0 + 0.0im ``` """ -create(N::Int) = QuantumObject(spdiagm(-1 => Array{ComplexF64}(sqrt.(1:(N-1)))), Operator, N) +create(N::Int) = QuantumObject(spdiagm(-1 => Array{ComplexF64}(sqrt.(1:(N-1)))), Operator(), N) @doc raw""" displace(N::Int, α::Number) @@ -167,7 +166,7 @@ Bosonic number operator with Hilbert space cutoff `N`. This operator is defined as ``\hat{N}=\hat{a}^\dagger \hat{a}``, where ``\hat{a}`` is the bosonic annihilation operator. """ -num(N::Int) = QuantumObject(spdiagm(0 => Array{ComplexF64}(0:(N-1))), Operator, N) +num(N::Int) = QuantumObject(spdiagm(0 => Array{ComplexF64}(0:(N-1))), Operator(), N) @doc raw""" position(N::Int) @@ -223,7 +222,7 @@ function phase(N::Int, ϕ0::Real = 0) N_list = collect(0:(N-1)) ϕ = ϕ0 .+ (2 * π / N) .* N_list states = [exp.((1.0im * ϕ[m]) .* N_list) ./ sqrt(N) for m in 1:N] - return QuantumObject(sum([ϕ[m] * states[m] * states[m]' for m in 1:N]); type = Operator, dims = N) + return QuantumObject(sum([ϕ[m] * states[m] * states[m]' for m in 1:N]); type = Operator(), dims = N) end @doc raw""" @@ -244,21 +243,21 @@ Note that if the parameter `which` is not specified, returns a set of Spin-`j` o ```jldoctest julia> jmat(0.5, :x) -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries: ⋅ 0.5+0.0im 0.5+0.0im ⋅ julia> jmat(0.5, Val(:-)) -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=false 2×2 SparseMatrixCSC{ComplexF64, Int64} with 1 stored entry: ⋅ ⋅ 1.0+0.0im ⋅ julia> jmat(1.5, Val(:z)) -Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=true +Quantum Object: type=Operator() dims=[4] size=(4, 4) ishermitian=true 4×4 SparseMatrixCSC{ComplexF64, Int64} with 4 stored entries: 1.5+0.0im ⋅ ⋅ ⋅ ⋅ 0.5+0.0im ⋅ ⋅ @@ -277,7 +276,7 @@ function jmat(j::Real, ::Val{:x}) throw(ArgumentError("The spin quantum number (j) must be a non-negative integer or half-integer.")) σ = _jm(j) - return QuantumObject((σ' + σ) / 2, Operator, Int(J)) + return QuantumObject((σ' + σ) / 2, Operator(), Int(J)) end function jmat(j::Real, ::Val{:y}) J = 2 * j + 1 @@ -285,28 +284,28 @@ function jmat(j::Real, ::Val{:y}) throw(ArgumentError("The spin quantum number (j) must be a non-negative integer or half-integer.")) σ = _jm(j) - return QuantumObject((σ' - σ) / 2im, Operator, Int(J)) + return QuantumObject((σ' - σ) / 2im, Operator(), Int(J)) end function jmat(j::Real, ::Val{:z}) J = 2 * j + 1 ((floor(J) != J) || (j < 0)) && throw(ArgumentError("The spin quantum number (j) must be a non-negative integer or half-integer.")) - return QuantumObject(_jz(j), Operator, Int(J)) + return QuantumObject(_jz(j), Operator(), Int(J)) end function jmat(j::Real, ::Val{:+}) J = 2 * j + 1 ((floor(J) != J) || (j < 0)) && throw(ArgumentError("The spin quantum number (j) must be a non-negative integer or half-integer.")) - return QuantumObject(adjoint(_jm(j)), Operator, Int(J)) + return QuantumObject(adjoint(_jm(j)), Operator(), Int(J)) end function jmat(j::Real, ::Val{:-}) J = 2 * j + 1 ((floor(J) != J) || (j < 0)) && throw(ArgumentError("The spin quantum number (j) must be a non-negative integer or half-integer.")) - return QuantumObject(_jm(j), Operator, Int(J)) + return QuantumObject(_jm(j), Operator(), Int(J)) end jmat(j::Real, ::Val{T}) where {T} = throw(ArgumentError("Invalid spin operator: $(T)")) @@ -429,13 +428,9 @@ Note that `type` can only be either [`Operator`](@ref) or [`SuperOperator`](@ref !!! note `qeye` is a synonym of `eye`. """ -function eye( - N::Int; - type::ObjType = Operator, - dims = nothing, -) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +function eye(N::Int; type = Operator(), dims = nothing) if dims isa Nothing - dims = isa(type, OperatorQuantumObject) ? N : isqrt(N) + dims = isa(type, Operator) ? N : isqrt(N) end return QuantumObject(Diagonal(ones(ComplexF64, N)); type = type, dims = dims) end @@ -478,9 +473,9 @@ Note that we put ``\hat{\sigma}_{-} = \begin{pmatrix} 0 & 0 \\ 1 & 0 \end{pmatri """ fcreate(N::Union{Int,Val}, j::Int) = _Jordan_Wigner(N, j, sigmam()) -_Jordan_Wigner(N::Int, j::Int, op::QuantumObject{OperatorQuantumObject}) = _Jordan_Wigner(Val(N), j, op) +_Jordan_Wigner(N::Int, j::Int, op::QuantumObject{Operator}) = _Jordan_Wigner(Val(N), j, op) -function _Jordan_Wigner(::Val{N}, j::Int, op::QuantumObject{OperatorQuantumObject}) where {N} +function _Jordan_Wigner(::Val{N}, j::Int, op::QuantumObject{Operator}) where {N} (N < 1) && throw(ArgumentError("The total number of sites (N) cannot be less than 1")) ((j > N) || (j < 1)) && throw(ArgumentError("The site index (j) should satisfy: 1 ≤ j ≤ N")) @@ -490,7 +485,7 @@ function _Jordan_Wigner(::Val{N}, j::Int, op::QuantumObject{OperatorQuantumObjec S = 2^(N - j) I_tensor = sparse((1.0 + 0.0im) * LinearAlgebra.I, S, S) - return QuantumObject(kron(Z_tensor, op.data, I_tensor); type = Operator, dims = ntuple(i -> 2, Val(N))) + return QuantumObject(kron(Z_tensor, op.data, I_tensor); type = Operator(), dims = ntuple(i -> 2, Val(N))) end @doc raw""" @@ -499,7 +494,7 @@ end Generates the projection operator ``\hat{O} = |i \rangle\langle j|`` with Hilbert space dimension `N`. """ projection(N::Int, i::Int, j::Int) = - QuantumObject(sparse([i + 1], [j + 1], [1.0 + 0.0im], N, N), type = Operator, dims = N) + QuantumObject(sparse([i + 1], [j + 1], [1.0 + 0.0im], N, N), type = Operator(), dims = N) @doc raw""" tunneling(N::Int, m::Int=1; sparse::Union{Bool,Val{<:Bool}}=Val(false)) @@ -522,9 +517,9 @@ function tunneling(N::Int, m::Int = 1; sparse::Union{Bool,Val} = Val(false)) data = ones(ComplexF64, N - m) if getVal(sparse) - return QuantumObject(spdiagm(m => data, -m => data); type = Operator, dims = N) + return QuantumObject(spdiagm(m => data, -m => data); type = Operator(), dims = N) else - return QuantumObject(diagm(m => data, -m => data); type = Operator, dims = N) + return QuantumObject(diagm(m => data, -m => data); type = Operator(), dims = N) end end @@ -555,9 +550,9 @@ where ``\omega = \exp(\frac{2 \pi i}{N})``. !!! warning "Beware of type-stability!" It is highly recommended to use `qft(dimensions)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ -qft(dimensions::Int) = QuantumObject(_qft_op(dimensions), Operator, dimensions) +qft(dimensions::Int) = QuantumObject(_qft_op(dimensions), Operator(), dimensions) qft(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) = - QuantumObject(_qft_op(prod(dimensions)), Operator, dimensions) + QuantumObject(_qft_op(prod(dimensions)), Operator(), dimensions) function _qft_op(N::Int) ω = exp(2.0im * π / N) arr = 0:(N-1) diff --git a/src/qobj/quantum_object.jl b/src/qobj/quantum_object.jl index 763113776..053b9b050 100644 --- a/src/qobj/quantum_object.jl +++ b/src/qobj/quantum_object.jl @@ -25,7 +25,7 @@ Julia structure representing any time-independent quantum objects. For time-depe ```jldoctest julia> a = destroy(20) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ @@ -50,9 +50,11 @@ struct QuantumObject{ObjType<:QuantumObjectType,DimType<:AbstractDimensions,Data type::ObjType dimensions::DimType - function QuantumObject(data::DT, type::ObjType, dims) where {DT<:AbstractArray,ObjType<:QuantumObjectType} + function QuantumObject(data::DT, type, dims) where {DT<:AbstractArray} dimensions = _gen_dimensions(dims) + ObjType = _check_type(type) + _size = _get_size(data) _check_QuantumObject(type, dimensions, _size[1], _size[2]) @@ -69,31 +71,29 @@ Generate [`QuantumObject`](@ref) with a given `A::AbstractArray` and specified ` !!! note `Qobj` is a synonym of `QuantumObject`. """ -function QuantumObject( - A::AbstractMatrix{T}; - type::ObjType = nothing, - dims = nothing, -) where {T,ObjType<:Union{Nothing,QuantumObjectType}} +function QuantumObject(A::AbstractMatrix{T}; type = nothing, dims = nothing) where {T} _size = _get_size(A) + _check_type(type) + if type isa Nothing - type = (_size[1] == 1 && _size[2] > 1) ? Bra : Operator # default type - elseif type != Operator && type != SuperOperator && type != Bra && type != OperatorBra + type = (_size[1] == 1 && _size[2] > 1) ? Bra() : Operator() # default type + elseif !(type isa Operator) && !(type isa SuperOperator) && !(type isa Bra) && !(type isa OperatorBra) throw( ArgumentError( - "The argument type must be Operator, SuperOperator, Bra or OperatorBra if the input array is a matrix.", + "The argument type must be Operator(), SuperOperator(), Bra() or OperatorBra() if the input array is a matrix.", ), ) end if dims isa Nothing - if type isa BraQuantumObject + if type isa Bra dims = Dimensions(_size[2]) - elseif type isa OperatorQuantumObject + elseif type isa Operator dims = (_size[1] == _size[2]) ? Dimensions(_size[1]) : GeneralDimensions(SVector{2}(SVector{1}(_size[1]), SVector{1}(_size[2]))) - elseif type isa SuperOperatorQuantumObject || type isa OperatorBraQuantumObject + elseif type isa SuperOperator || type isa OperatorBra dims = Dimensions(isqrt(_size[2])) end end @@ -101,22 +101,19 @@ function QuantumObject( return QuantumObject(A, type, dims) end -function QuantumObject( - A::AbstractVector{T}; - type::ObjType = nothing, - dims = nothing, -) where {T,ObjType<:Union{Nothing,QuantumObjectType}} +function QuantumObject(A::AbstractVector{T}; type = nothing, dims = nothing) where {T} + _check_type(type) if type isa Nothing - type = Ket # default type - elseif type != Ket && type != OperatorKet - throw(ArgumentError("The argument type must be Ket or OperatorKet if the input array is a vector.")) + type = Ket() # default type + elseif !(type isa Ket) && !(type isa OperatorKet) + throw(ArgumentError("The argument type must be Ket() or OperatorKet() if the input array is a vector.")) end if dims isa Nothing _size = _get_size(A) - if type isa KetQuantumObject + if type isa Ket dims = Dimensions(_size[1]) - elseif type isa OperatorKetQuantumObject + elseif type isa OperatorKet dims = Dimensions(isqrt(_size[1])) end end @@ -124,17 +121,14 @@ function QuantumObject( return QuantumObject(A, type, dims) end -function QuantumObject( - A::AbstractArray{T,N}; - type::ObjType = nothing, - dims = nothing, -) where {T,N,ObjType<:Union{Nothing,QuantumObjectType}} +function QuantumObject(A::AbstractArray{T,N}; type = nothing, dims = nothing) where {T,N} throw(DomainError(size(A), "The size of the array is not compatible with vector or matrix.")) end -function QuantumObject(A::QuantumObject; type::ObjType = A.type, dims = A.dimensions) where {ObjType<:QuantumObjectType} +function QuantumObject(A::QuantumObject; type = A.type, dims = A.dimensions) _size = _get_size(A.data) dimensions = _gen_dimensions(dims) + _check_type(type) _check_QuantumObject(type, dimensions, _size[1], _size[2]) return QuantumObject(copy(A.data), type, dimensions) end @@ -142,15 +136,7 @@ end function Base.show( io::IO, QO::QuantumObject{OpType}, -) where { - OpType<:Union{ - BraQuantumObject, - KetQuantumObject, - OperatorBraQuantumObject, - OperatorKetQuantumObject, - SuperOperatorQuantumObject, - }, -} +) where {OpType<:Union{Bra,Ket,OperatorBra,OperatorKet,SuperOperator}} op_data = QO.data println( io, @@ -204,16 +190,13 @@ Here, `u` can be in either the following types: SciMLOperators.cache_operator( L::AbstractQuantumObject{OpType}, u::AbstractVector, -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = +) where {OpType<:Union{Operator,SuperOperator}} = get_typename_wrapper(L)(cache_operator(L.data, to_dense(similar(u))), L.type, L.dimensions) function SciMLOperators.cache_operator( L::AbstractQuantumObject{OpType}, u::QuantumObject{SType}, -) where { - OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - SType<:Union{KetQuantumObject,OperatorKetQuantumObject}, -} +) where {OpType<:Union{Operator,SuperOperator},SType<:Union{Ket,OperatorKet}} check_dimensions(L, u) if isoper(L) && isoperket(u) diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index b43dd06d4..08c3de46b 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -4,14 +4,7 @@ This file defines the AbstractQuantumObject structure, all the type structures f =# export AbstractQuantumObject -export QuantumObjectType, - BraQuantumObject, - KetQuantumObject, - OperatorQuantumObject, - OperatorBraQuantumObject, - OperatorKetQuantumObject, - SuperOperatorQuantumObject -export Bra, Ket, Operator, OperatorBra, OperatorKet, SuperOperator +export QuantumObjectType, SuperOperatorType, Bra, Ket, Operator, OperatorBra, OperatorKet, SuperOperator @doc raw""" abstract type AbstractQuantumObject{ObjType,DimType,DataType} @@ -28,95 +21,49 @@ abstract type AbstractQuantumObject{ObjType,DimType,DataType} end abstract type QuantumObjectType end -@doc raw""" - BraQuantumObject <: QuantumObjectType - -Constructor representing a bra state ``\langle\psi|``. -""" -struct BraQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::BraQuantumObject) = print(io, "Bra") +abstract type SuperOperatorType <: QuantumObjectType end @doc raw""" - const Bra = BraQuantumObject() + Bra <: QuantumObjectType -A constant representing the type of [`BraQuantumObject`](@ref): a bra state ``\langle\psi|`` +Constructor representing a bra state ``\langle\psi|``. """ -const Bra = BraQuantumObject() +struct Bra <: QuantumObjectType end @doc raw""" - KetQuantumObject <: QuantumObjectType + Ket <: QuantumObjectType Constructor representing a ket state ``|\psi\rangle``. """ -struct KetQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::KetQuantumObject) = print(io, "Ket") +struct Ket <: QuantumObjectType end @doc raw""" - const Ket = KetQuantumObject() - -A constant representing the type of [`KetQuantumObject`](@ref): a ket state ``|\psi\rangle`` -""" -const Ket = KetQuantumObject() - -@doc raw""" - OperatorQuantumObject <: QuantumObjectType + Operator <: QuantumObjectType Constructor representing an operator ``\hat{O}``. """ -struct OperatorQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::OperatorQuantumObject) = print(io, "Operator") - -@doc raw""" - const Operator = OperatorQuantumObject() - -A constant representing the type of [`OperatorQuantumObject`](@ref): an operator ``\hat{O}`` -""" -const Operator = OperatorQuantumObject() +struct Operator <: QuantumObjectType end @doc raw""" - SuperOperatorQuantumObject <: QuantumObjectType + SuperOperator <: SuperOperatorType Constructor representing a super-operator ``\hat{\mathcal{O}}`` acting on vectorized density operator matrices. """ -struct SuperOperatorQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::SuperOperatorQuantumObject) = print(io, "SuperOperator") +struct SuperOperator <: SuperOperatorType end @doc raw""" - const SuperOperator = SuperOperatorQuantumObject() - -A constant representing the type of [`SuperOperatorQuantumObject`](@ref): a super-operator ``\hat{\mathcal{O}}`` acting on vectorized density operator matrices -""" -const SuperOperator = SuperOperatorQuantumObject() - -@doc raw""" - OperatorBraQuantumObject <: QuantumObjectType + OperatorBra <: QuantumObjectType Constructor representing a bra state in the [`SuperOperator`](@ref) formalism ``\langle\langle\rho|``. """ -struct OperatorBraQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::OperatorBraQuantumObject) = print(io, "OperatorBra") +struct OperatorBra <: QuantumObjectType end @doc raw""" - const OperatorBra = OperatorBraQuantumObject() - -A constant representing the type of [`OperatorBraQuantumObject`](@ref): a bra state in the [`SuperOperator`](@ref) formalism ``\langle\langle\rho|``. -""" -const OperatorBra = OperatorBraQuantumObject() - -@doc raw""" - OperatorKetQuantumObject <: QuantumObjectType + OperatorKet <: QuantumObjectType Constructor representing a ket state in the [`SuperOperator`](@ref) formalism ``|\rho\rangle\rangle``. """ -struct OperatorKetQuantumObject <: QuantumObjectType end -Base.show(io::IO, ::OperatorKetQuantumObject) = print(io, "OperatorKet") - -@doc raw""" - const OperatorKet = OperatorKetQuantumObject() - -A constant representing the type of [`OperatorKetQuantumObject`](@ref): a ket state in the [`SuperOperator`](@ref) formalism ``|\rho\rangle\rangle`` -""" -const OperatorKet = OperatorKetQuantumObject() +struct OperatorKet <: QuantumObjectType end @doc raw""" size(A::AbstractQuantumObject) @@ -172,22 +119,14 @@ _check_QuantumObject( dimensions::GeneralDimensions, m::Int, n::Int, -) where { - ObjType<:Union{ - KetQuantumObject, - BraQuantumObject, - SuperOperatorQuantumObject, - OperatorBraQuantumObject, - OperatorKetQuantumObject, - }, -} = throw( +) where {ObjType<:Union{Ket,Bra,SuperOperator,OperatorBra,OperatorKet}} = throw( DomainError( _get_dims_string(dimensions), "The given `dims` is not compatible with type = $type, should be a single list of integers.", ), ) -function _check_QuantumObject(type::KetQuantumObject, dimensions::Dimensions, m::Int, n::Int) +function _check_QuantumObject(type::Ket, dimensions::Dimensions, m::Int, n::Int) (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Ket")) (prod(dimensions) != m) && throw( DimensionMismatch("Ket with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n))."), @@ -195,7 +134,7 @@ function _check_QuantumObject(type::KetQuantumObject, dimensions::Dimensions, m: return nothing end -function _check_QuantumObject(type::BraQuantumObject, dimensions::Dimensions, m::Int, n::Int) +function _check_QuantumObject(type::Bra, dimensions::Dimensions, m::Int, n::Int) (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with Bra")) (prod(dimensions) != n) && throw( DimensionMismatch("Bra with dims = $(_get_dims_string(dimensions)) does not fit the array size = $((m, n))."), @@ -203,7 +142,7 @@ function _check_QuantumObject(type::BraQuantumObject, dimensions::Dimensions, m: return nothing end -function _check_QuantumObject(type::OperatorQuantumObject, dimensions::Dimensions, m::Int, n::Int) +function _check_QuantumObject(type::Operator, dimensions::Dimensions, m::Int, n::Int) L = prod(dimensions) (L == m == n) || throw( DimensionMismatch( @@ -213,7 +152,7 @@ function _check_QuantumObject(type::OperatorQuantumObject, dimensions::Dimension return nothing end -function _check_QuantumObject(type::OperatorQuantumObject, dimensions::GeneralDimensions, m::Int, n::Int) +function _check_QuantumObject(type::Operator, dimensions::GeneralDimensions, m::Int, n::Int) ((m == 1) || (n == 1)) && throw(DomainError((m, n), "The size of the array is not compatible with Operator")) ((prod(dimensions.to) != m) || (prod(dimensions.from) != n)) && throw( DimensionMismatch( @@ -223,7 +162,7 @@ function _check_QuantumObject(type::OperatorQuantumObject, dimensions::GeneralDi return nothing end -function _check_QuantumObject(type::SuperOperatorQuantumObject, dimensions::Dimensions, m::Int, n::Int) +function _check_QuantumObject(type::SuperOperator, dimensions::Dimensions, m::Int, n::Int) (m != n) && throw(DomainError((m, n), "The size of the array is not compatible with SuperOperator")) (prod(dimensions) != sqrt(m)) && throw( DimensionMismatch( @@ -233,7 +172,7 @@ function _check_QuantumObject(type::SuperOperatorQuantumObject, dimensions::Dime return nothing end -function _check_QuantumObject(type::OperatorKetQuantumObject, dimensions::Dimensions, m::Int, n::Int) +function _check_QuantumObject(type::OperatorKet, dimensions::Dimensions, m::Int, n::Int) (n != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorKet")) (prod(dimensions) != sqrt(m)) && throw( DimensionMismatch( @@ -243,7 +182,7 @@ function _check_QuantumObject(type::OperatorKetQuantumObject, dimensions::Dimens return nothing end -function _check_QuantumObject(type::OperatorBraQuantumObject, dimensions::Dimensions, m::Int, n::Int) +function _check_QuantumObject(type::OperatorBra, dimensions::Dimensions, m::Int, n::Int) (m != 1) && throw(DomainError((m, n), "The size of the array is not compatible with OperatorBra")) (prod(dimensions) != sqrt(n)) && throw( DimensionMismatch( @@ -253,6 +192,11 @@ function _check_QuantumObject(type::OperatorBraQuantumObject, dimensions::Dimens return nothing end +_check_type(::T) where {T<:Union{Nothing,<:QuantumObjectType}} = T +_check_type(::Type{T}) where {T} = + throw(ArgumentError("The argument `$T` is not valid. You may probably want to use `$T()` instead.")) +_check_type(t) = throw(ArgumentError("The argument $t is not valid. It should be a subtype of `QuantumObjectType`.")) + function Base.getproperty(A::AbstractQuantumObject, key::Symbol) # a comment here to avoid bad render by JuliaFormatter if key === :dims @@ -263,22 +207,22 @@ function Base.getproperty(A::AbstractQuantumObject, key::Symbol) end # this returns `to` in GeneralDimensions representation -get_dimensions_to(A::AbstractQuantumObject{KetQuantumObject,<:Dimensions}) = A.dimensions.to -get_dimensions_to(A::AbstractQuantumObject{BraQuantumObject,<:Dimensions{N}}) where {N} = space_one_list(N) -get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions}) = A.dimensions.to -get_dimensions_to(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions}) = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{Ket,<:Dimensions}) = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{Bra,<:Dimensions{N}}) where {N} = space_one_list(N) +get_dimensions_to(A::AbstractQuantumObject{Operator,<:Dimensions}) = A.dimensions.to +get_dimensions_to(A::AbstractQuantumObject{Operator,<:GeneralDimensions}) = A.dimensions.to get_dimensions_to( A::AbstractQuantumObject{ObjType,<:Dimensions}, -) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject}} = A.dimensions.to +) where {ObjType<:Union{SuperOperator,OperatorBra,OperatorKet}} = A.dimensions.to # this returns `from` in GeneralDimensions representation -get_dimensions_from(A::AbstractQuantumObject{KetQuantumObject,<:Dimensions{N}}) where {N} = space_one_list(N) -get_dimensions_from(A::AbstractQuantumObject{BraQuantumObject,<:Dimensions}) = A.dimensions.to -get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:Dimensions}) = A.dimensions.to -get_dimensions_from(A::AbstractQuantumObject{OperatorQuantumObject,<:GeneralDimensions}) = A.dimensions.from +get_dimensions_from(A::AbstractQuantumObject{Ket,<:Dimensions{N}}) where {N} = space_one_list(N) +get_dimensions_from(A::AbstractQuantumObject{Bra,<:Dimensions}) = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{Operator,<:Dimensions}) = A.dimensions.to +get_dimensions_from(A::AbstractQuantumObject{Operator,<:GeneralDimensions}) = A.dimensions.from get_dimensions_from( A::AbstractQuantumObject{ObjType,<:Dimensions}, -) where {ObjType<:Union{SuperOperatorQuantumObject,OperatorBraQuantumObject,OperatorKetQuantumObject}} = A.dimensions.to +) where {ObjType<:Union{SuperOperator,OperatorBra,OperatorKet}} = A.dimensions.to # functions for getting Float or Complex element type _FType(A::AbstractQuantumObject) = _FType(eltype(A)) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 62b15b0b5..3e8edf301 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -29,7 +29,7 @@ This operator can be initialized in the same way as the QuTiP `QobjEvo` object. ```jldoctest qobjevo julia> a = tensor(destroy(10), qeye(2)) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: ⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ @@ -42,7 +42,7 @@ coef1 (generic function with 1 method) julia> op = QobjEvo(a, coef1) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +Quantum Object Evo.: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) ``` @@ -51,7 +51,7 @@ If there are more than 2 operators, we need to put each set of operator and coef ```jldoctest qobjevo julia> σm = tensor(qeye(10), sigmam()) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: ⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠂⡀⠀⠀⠀⠀⠀⠀⎥ @@ -64,7 +64,7 @@ coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +Quantum Object Evo.: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) ``` @@ -72,7 +72,7 @@ We can also concretize the operator at a specific time `t` ```jldoctest qobjevo julia> op1(0.1) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ @@ -91,7 +91,7 @@ coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +Quantum Object Evo.: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) julia> p = (ω1 = 1.0, ω2 = 0.5) @@ -99,7 +99,7 @@ julia> p = (ω1 = 1.0, ω2 = 0.5) julia> op1(p, 0.1) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ @@ -109,7 +109,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ``` """ struct QuantumObjectEvolution{ - ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + ObjType<:Union{Operator,SuperOperator}, DimType<:AbstractDimensions, DataType<:AbstractSciMLOperator, } <: AbstractQuantumObject{ObjType,DimType,DataType} @@ -117,12 +117,9 @@ struct QuantumObjectEvolution{ type::ObjType dimensions::DimType - function QuantumObjectEvolution( - data::DT, - type::ObjType, - dims, - ) where {DT<:AbstractSciMLOperator,ObjType<:QuantumObjectType} - (type == Operator || type == SuperOperator) || + function QuantumObjectEvolution(data::DT, type, dims) where {DT<:AbstractSciMLOperator} + ObjType = _check_type(type) + (type isa Operator || type isa SuperOperator) || throw(ArgumentError("The type $type is not supported for QuantumObjectEvolution.")) dimensions = _gen_dimensions(dims) @@ -153,22 +150,23 @@ function Base.show(io::IO, QO::QuantumObjectEvolution) end @doc raw""" - QobjEvo(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) - QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) + QobjEvo(data::AbstractSciMLOperator; type = Operator(), dims = nothing) + QuantumObjectEvolution(data::AbstractSciMLOperator; type = Operator(), dims = nothing) Generate a [`QuantumObjectEvolution`](@ref) object from a [`SciMLOperator`](https://github.com/SciML/SciMLOperators.jl), in the same way as [`QuantumObject`](@ref) for `AbstractArray` inputs. Note that `QobjEvo` is a synonym of `QuantumObjectEvolution` """ -function QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObjectType = Operator, dims = nothing) +function QuantumObjectEvolution(data::AbstractSciMLOperator; type = Operator(), dims = nothing) _size = _get_size(data) + _check_type(type) if dims isa Nothing - if type isa OperatorQuantumObject + if type isa Operator dims = (_size[1] == _size[2]) ? Dimensions(_size[1]) : GeneralDimensions(SVector{2}(SVector{1}(_size[1]), SVector{1}(_size[2]))) - elseif type isa SuperOperatorQuantumObject + elseif type isa SuperOperator dims = Dimensions(isqrt(_size[2])) end end @@ -177,8 +175,8 @@ function QuantumObjectEvolution(data::AbstractSciMLOperator; type::QuantumObject end @doc raw""" - QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing) - QuantumObjectEvolution(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type::Union{Nothing, QuantumObjectType}=nothing) + QobjEvo(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type=nothing) + QuantumObjectEvolution(op_func_list::Union{Tuple,AbstractQuantumObject}, α::Union{Nothing,Number}=nothing; type=nothing) Generate [`QuantumObjectEvolution`](@ref). @@ -199,7 +197,7 @@ This operator can be initialized in the same way as the QuTiP `QobjEvo` object. ```jldoctest qobjevo julia> a = tensor(destroy(10), qeye(2)) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: ⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ @@ -209,7 +207,7 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal julia> σm = tensor(qeye(10), sigmam()) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 10 stored entries: ⎡⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠂⡀⠀⠀⠀⠀⠀⠀⎥ @@ -225,7 +223,7 @@ coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +Quantum Object Evo.: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) ``` @@ -233,7 +231,7 @@ We can also concretize the operator at a specific time `t` ```jldoctest qobjevo julia> op1(0.1) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ @@ -252,7 +250,7 @@ coef2 (generic function with 1 method) julia> op1 = QobjEvo(((a, coef1), (σm, coef2))) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +Quantum Object Evo.: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) julia> p = (ω1 = 1.0, ω2 = 0.5) @@ -260,7 +258,7 @@ julia> p = (ω1 = 1.0, ω2 = 0.5) julia> op1(p, 0.1) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 28 stored entries: ⎡⠂⡑⢄⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠂⡑⢄⠀⠀⠀⠀⠀⎥ @@ -269,13 +267,11 @@ Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=fal ⎣⠀⠀⠀⠀⠀⠀⠀⠀⠂⡑⎦ ``` """ -function QuantumObjectEvolution( - op_func_list::Tuple, - α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, -) +function QuantumObjectEvolution(op_func_list::Tuple, α::Union{Nothing,Number} = nothing; type = nothing) op, data = _QobjEvo_generate_data(op_func_list, α) dims = op.dimensions + _check_type(type) + if type isa Nothing type = op.type end @@ -288,15 +284,12 @@ function QuantumObjectEvolution( end # this is a extra method if user accidentally specify `QuantumObjectEvolution( (op, func) )` or `QuantumObjectEvolution( ((op, func)) )` -QuantumObjectEvolution( - op_func::Tuple{QuantumObject,Function}, - α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, -) = QuantumObjectEvolution((op_func,), α; type = type) +QuantumObjectEvolution(op_func::Tuple{QuantumObject,Function}, α::Union{Nothing,Number} = nothing; type = nothing) = + QuantumObjectEvolution((op_func,), α; type = type) @doc raw""" - QuantumObjectEvolution(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type::Union{Nothing,QuantumObjectType} = nothing) - QobjEvo(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type::Union{Nothing,QuantumObjectType} = nothing) + QuantumObjectEvolution(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type = nothing) + QobjEvo(op::QuantumObject, f::Function, α::Union{Nothing,Number}=nothing; type = nothing) Generate [`QuantumObjectEvolution`](@ref). @@ -308,7 +301,7 @@ Generate [`QuantumObjectEvolution`](@ref). ```jldoctest julia> a = tensor(destroy(10), qeye(2)) -Quantum Object: type=Operator dims=[10, 2] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 18 stored entries: ⎡⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⎥ @@ -321,33 +314,23 @@ coef (generic function with 1 method) julia> op = QobjEvo(a, coef) -Quantum Object Evo.: type=Operator dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false +Quantum Object Evo.: type=Operator() dims=[10, 2] size=(20, 20) ishermitian=true isconstant=false ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) ``` """ -QuantumObjectEvolution( - op::QuantumObject, - f::Function, - α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, -) = QuantumObjectEvolution(((op, f),), α; type = type) - -function QuantumObjectEvolution( - op::QuantumObject, - α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, -) +QuantumObjectEvolution(op::QuantumObject, f::Function, α::Union{Nothing,Number} = nothing; type = nothing) = + QuantumObjectEvolution(((op, f),), α; type = type) + +function QuantumObjectEvolution(op::QuantumObject, α::Union{Nothing,Number} = nothing; type = nothing) + _check_type(type) if type isa Nothing type = op.type end return QuantumObjectEvolution(_make_SciMLOperator(op, α), type, op.dimensions) end -function QuantumObjectEvolution( - op::QuantumObjectEvolution, - α::Union{Nothing,Number} = nothing; - type::Union{Nothing,QuantumObjectType} = nothing, -) +function QuantumObjectEvolution(op::QuantumObjectEvolution, α::Union{Nothing,Number} = nothing; type = nothing) + _check_type(type) if type isa Nothing type = op.type elseif type != op.type @@ -482,7 +465,7 @@ Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the inpu # Arguments - `ψout::QuantumObject`: The output state. It must have the same type as `ψin`. -- `ψin::QuantumObject`: The input state. It must be either a [`KetQuantumObject`](@ref) or a [`OperatorKetQuantumObject`](@ref). +- `ψin::QuantumObject`: The input state. It must be either a [`Ket`](@ref) or a [`OperatorKet`](@ref). - `p`: The parameters of the time-dependent coefficients. - `t`: The time at which the coefficients are evaluated. @@ -493,7 +476,7 @@ Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the inpu ```jldoctest julia> a = destroy(20) -Quantum Object: type=Operator dims=[20] size=(20, 20) ishermitian=false +Quantum Object: type=Operator() dims=[20] size=(20, 20) ishermitian=false 20×20 SparseMatrixCSC{ComplexF64, Int64} with 19 stored entries: ⎡⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎤ ⎢⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⎥ @@ -509,7 +492,7 @@ coef2 (generic function with 1 method) julia> A = QobjEvo(((a, coef1), (a', coef2))) -Quantum Object Evo.: type=Operator dims=[20] size=(20, 20) ishermitian=true isconstant=false +Quantum Object Evo.: type=Operator() dims=[20] size=(20, 20) ishermitian=true isconstant=false (ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20) + ScalarOperator(0.0 + 0.0im) * MatrixOperator(20 × 20)) julia> ψ1 = fock(20, 3); @@ -525,7 +508,7 @@ function (A::QuantumObjectEvolution)( ψin::QuantumObject{QobjType}, p, t, -) where {QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} +) where {QobjType<:Union{Ket,OperatorKet}} check_dimensions(A, ψout, ψin) if isoper(A) && isoperket(ψin) @@ -548,11 +531,7 @@ end Apply the time-dependent [`QuantumObjectEvolution`](@ref) object `A` to the input state `ψ` at time `t` with parameters `p`. Out-of-place version of [`(A::QuantumObjectEvolution)(ψout, ψin, p, t)`](@ref). The output state is stored in a new [`QuantumObject`](@ref) object. This function mimics the behavior of a `AbstractSciMLOperator` object. """ -function (A::QuantumObjectEvolution)( - ψ::QuantumObject{QobjType}, - p, - t, -) where {QobjType<:Union{KetQuantumObject,OperatorKetQuantumObject}} +function (A::QuantumObjectEvolution)(ψ::QuantumObject{QobjType}, p, t) where {QobjType<:Union{Ket,OperatorKet}} ψout = QuantumObject(similar(ψ.data), ψ.type, ψ.dimensions) return A(ψout, ψ, p, t) end diff --git a/src/qobj/states.jl b/src/qobj/states.jl index 4474ebd16..b9bfe7ba6 100644 --- a/src/qobj/states.jl +++ b/src/qobj/states.jl @@ -19,9 +19,9 @@ The `dimensions` can be either the following types: !!! warning "Beware of type-stability!" It is highly recommended to use `zero_ket(dimensions)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ -zero_ket(dimensions::Int) = QuantumObject(zeros(ComplexF64, dimensions), Ket, dimensions) +zero_ket(dimensions::Int) = QuantumObject(zeros(ComplexF64, dimensions), Ket(), dimensions) zero_ket(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) = - QuantumObject(zeros(ComplexF64, prod(dimensions)), Ket, dimensions) + QuantumObject(zeros(ComplexF64, prod(dimensions)), Ket(), dimensions) @doc raw""" fock(N::Int, j::Int=0; dims::Union{Int,AbstractVector{Int},Tuple}=N, sparse::Union{Bool,Val}=Val(false)) @@ -39,7 +39,7 @@ function fock(N::Int, j::Int = 0; dims::Union{Int,AbstractVector{Int},Tuple} = N else array = [i == (j + 1) ? 1.0 + 0im : 0.0 + 0im for i in 1:N] end - return QuantumObject(array; type = Ket, dims = dims) + return QuantumObject(array; type = Ket(), dims = dims) end @doc raw""" @@ -79,7 +79,7 @@ rand_ket(dimensions::Int) = rand_ket(SVector(dimensions)) function rand_ket(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) N = prod(dimensions) ψ = rand(ComplexF64, N) .- (0.5 + 0.5im) - return QuantumObject(normalize!(ψ); type = Ket, dims = dimensions) + return QuantumObject(normalize!(ψ); type = Ket(), dims = dimensions) end @doc raw""" @@ -130,9 +130,9 @@ function thermal_dm(N::Int, n::Real; sparse::Union{Bool,Val} = Val(false)) N_list = Array{Float64}(0:(N-1)) data = exp.(-β .* N_list) if getVal(sparse) - return QuantumObject(spdiagm(0 => data ./ sum(data)), Operator, N) + return QuantumObject(spdiagm(0 => data ./ sum(data)), Operator(), N) else - return QuantumObject(diagm(0 => data ./ sum(data)), Operator, N) + return QuantumObject(diagm(0 => data ./ sum(data)), Operator(), N) end end @@ -148,10 +148,11 @@ The `dimensions` can be either the following types: !!! warning "Beware of type-stability!" If you want to keep type stability, it is recommended to use `maximally_mixed_dm(dimensions)` with `dimensions` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details. """ -maximally_mixed_dm(dimensions::Int) = QuantumObject(I(dimensions) / complex(dimensions), Operator, SVector(dimensions)) +maximally_mixed_dm(dimensions::Int) = + QuantumObject(I(dimensions) / complex(dimensions), Operator(), SVector(dimensions)) function maximally_mixed_dm(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}) N = prod(dimensions) - return QuantumObject(I(N) / complex(N), Operator, dimensions) + return QuantumObject(I(N) / complex(N), Operator(), dimensions) end @doc raw""" @@ -181,7 +182,7 @@ function rand_dm(dimensions::Union{Dimensions,AbstractVector{Int},Tuple}; rank:: X = _Ginibre_ensemble(N, rank) ρ = X * X' ρ /= tr(ρ) - return QuantumObject(ρ; type = Operator, dims = dimensions) + return QuantumObject(ρ; type = Operator(), dims = dimensions) end @doc raw""" @@ -254,7 +255,7 @@ Here, `x = 1` (`z = 1`) means applying Pauli-``X`` ( Pauli-``Z``) unitary transf ```jldoctest julia> bell_state(0, 0) -Quantum Object: type=Ket dims=[2, 2] size=(4,) +Quantum Object: type=Ket() dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im 0.0 + 0.0im @@ -263,7 +264,7 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) julia> bell_state(Val(1), Val(0)) -Quantum Object: type=Ket dims=[2, 2] size=(4,) +Quantum Object: type=Ket() dims=[2, 2] size=(4,) 4-element Vector{ComplexF64}: 0.0 + 0.0im 0.7071067811865475 + 0.0im @@ -275,10 +276,10 @@ Quantum Object: type=Ket dims=[2, 2] size=(4,) If you want to keep type stability, it is recommended to use `bell_state(Val(x), Val(z))` instead of `bell_state(x, z)`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) for more details. """ bell_state(x::Int, z::Int) = bell_state(Val(x), Val(z)) -bell_state(::Val{0}, ::Val{0}) = QuantumObject(ComplexF64[1, 0, 0, 1] / sqrt(2), Ket, (2, 2)) -bell_state(::Val{0}, ::Val{1}) = QuantumObject(ComplexF64[1, 0, 0, -1] / sqrt(2), Ket, (2, 2)) -bell_state(::Val{1}, ::Val{0}) = QuantumObject(ComplexF64[0, 1, 1, 0] / sqrt(2), Ket, (2, 2)) -bell_state(::Val{1}, ::Val{1}) = QuantumObject(ComplexF64[0, 1, -1, 0] / sqrt(2), Ket, (2, 2)) +bell_state(::Val{0}, ::Val{0}) = QuantumObject(ComplexF64[1, 0, 0, 1] / sqrt(2), Ket(), (2, 2)) +bell_state(::Val{0}, ::Val{1}) = QuantumObject(ComplexF64[1, 0, 0, -1] / sqrt(2), Ket(), (2, 2)) +bell_state(::Val{1}, ::Val{0}) = QuantumObject(ComplexF64[0, 1, 1, 0] / sqrt(2), Ket(), (2, 2)) +bell_state(::Val{1}, ::Val{1}) = QuantumObject(ComplexF64[0, 1, -1, 0] / sqrt(2), Ket(), (2, 2)) bell_state(::Val{T1}, ::Val{T2}) where {T1,T2} = throw(ArgumentError("Invalid Bell state: $(T1), $(T2)")) @doc raw""" @@ -286,7 +287,7 @@ bell_state(::Val{T1}, ::Val{T2}) where {T1,T2} = throw(ArgumentError("Invalid Be Return the two particle singlet state: ``\frac{1}{\sqrt{2}} ( |01\rangle - |10\rangle )`` """ -singlet_state() = QuantumObject(ComplexF64[0, 1, -1, 0] / sqrt(2), Ket, (2, 2)) +singlet_state() = QuantumObject(ComplexF64[0, 1, -1, 0] / sqrt(2), Ket(), (2, 2)) @doc raw""" triplet_states() @@ -299,9 +300,9 @@ Return a list of the two particle triplet states: """ function triplet_states() return QuantumObject[ - QuantumObject(ComplexF64[0, 0, 0, 1], Ket, (2, 2)), - QuantumObject(ComplexF64[0, 1, 1, 0] / sqrt(2), Ket, (2, 2)), - QuantumObject(ComplexF64[1, 0, 0, 0], Ket, (2, 2)), + QuantumObject(ComplexF64[0, 0, 0, 1], Ket(), (2, 2)), + QuantumObject(ComplexF64[0, 1, 1, 0] / sqrt(2), Ket(), (2, 2)), + QuantumObject(ComplexF64[1, 0, 0, 0], Ket(), (2, 2)), ] end @@ -320,7 +321,7 @@ Returns the `n`-qubit [W-state](https://en.wikipedia.org/wiki/W_state): function w_state(::Val{n}) where {n} nzind = 2 .^ (0:(n-1)) .+ 1 nzval = fill(ComplexF64(1 / sqrt(n)), n) - return QuantumObject(SparseVector(2^n, nzind, nzval), Ket, ntuple(x -> 2, Val(n))) + return QuantumObject(SparseVector(2^n, nzind, nzval), Ket(), ntuple(x -> 2, Val(n))) end w_state(n::Int) = w_state(Val(n)) @@ -341,6 +342,6 @@ Here, `d` specifies the dimension of each qudit. Default to `d=2` (qubit). function ghz_state(::Val{n}; d::Int = 2) where {n} nzind = collect((0:(d-1)) .* Int((d^n - 1) / (d - 1)) .+ 1) nzval = ones(ComplexF64, d) / sqrt(d) - return QuantumObject(SparseVector(d^n, nzind, nzval), Ket, ntuple(x -> d, Val(n))) + return QuantumObject(SparseVector(d^n, nzind, nzval), Ket(), ntuple(x -> d, Val(n))) end ghz_state(n::Int; d::Int = 2) = ghz_state(Val(n), d = d) diff --git a/src/qobj/superoperators.jl b/src/qobj/superoperators.jl index affc0a980..6bc9a71c3 100644 --- a/src/qobj/superoperators.jl +++ b/src/qobj/superoperators.jl @@ -77,8 +77,8 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spost`](@ref) and [`sprepost`](@ref). """ -spre(A::AbstractQuantumObject{OperatorQuantumObject}, Id_cache = I(size(A, 1))) = - get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator, A.dimensions) +spre(A::AbstractQuantumObject{Operator}, Id_cache = I(size(A, 1))) = + get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator(), A.dimensions) @doc raw""" spost(B::AbstractQuantumObject, Id_cache=I(size(B,1))) @@ -96,8 +96,8 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref) and [`sprepost`](@ref). """ -spost(B::AbstractQuantumObject{OperatorQuantumObject}, Id_cache = I(size(B, 1))) = - get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator, B.dimensions) +spost(B::AbstractQuantumObject{Operator}, Id_cache = I(size(B, 1))) = + get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator(), B.dimensions) @doc raw""" sprepost(A::AbstractQuantumObject, B::AbstractQuantumObject) @@ -113,9 +113,9 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r See also [`spre`](@ref) and [`spost`](@ref). """ -function sprepost(A::AbstractQuantumObject{OperatorQuantumObject}, B::AbstractQuantumObject{OperatorQuantumObject}) +function sprepost(A::AbstractQuantumObject{Operator}, B::AbstractQuantumObject{Operator}) check_dimensions(A, B) - return promote_op_type(A, B)(_sprepost(A.data, B.data), SuperOperator, A.dimensions) + return promote_op_type(A, B)(_sprepost(A.data, B.data), SuperOperator(), A.dimensions) end @doc raw""" @@ -132,11 +132,11 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr See also [`spre`](@ref), [`spost`](@ref), and [`sprepost`](@ref). """ -lindblad_dissipator(O::AbstractQuantumObject{OperatorQuantumObject}, Id_cache = I(size(O, 1))) = - get_typename_wrapper(O)(_lindblad_dissipator(O.data, Id_cache), SuperOperator, O.dimensions) +lindblad_dissipator(O::AbstractQuantumObject{Operator}, Id_cache = I(size(O, 1))) = + get_typename_wrapper(O)(_lindblad_dissipator(O.data, Id_cache), SuperOperator(), O.dimensions) # It is already a SuperOperator -lindblad_dissipator(O::AbstractQuantumObject{SuperOperatorQuantumObject}, Id_cache = nothing) = O +lindblad_dissipator(O::AbstractQuantumObject{SuperOperator}, Id_cache = nothing) = O @doc raw""" liouvillian(H::AbstractQuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dimensions))) @@ -161,7 +161,7 @@ function liouvillian( H::AbstractQuantumObject{OpType}, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, Id_cache = I(prod(H.dimensions)), -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{Operator,SuperOperator}} L = liouvillian(H, Id_cache) if !(c_ops isa Nothing) L += _sum_lindblad_dissipators(c_ops, Id_cache) @@ -174,10 +174,10 @@ liouvillian(H::Nothing, c_ops::Union{AbstractVector,Tuple}, Id_cache::Diagonal = liouvillian(H::Nothing, c_ops::Nothing) = 0 -liouvillian(H::AbstractQuantumObject{OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dimensions))) = - get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator, H.dimensions) +liouvillian(H::AbstractQuantumObject{Operator}, Id_cache::Diagonal = I(prod(H.dimensions))) = + get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator(), H.dimensions) -liouvillian(H::AbstractQuantumObject{SuperOperatorQuantumObject}, Id_cache::Diagonal) = H +liouvillian(H::AbstractQuantumObject{SuperOperator}, Id_cache::Diagonal) = H function _sum_lindblad_dissipators(c_ops, Id_cache::Diagonal) D = 0 diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 6171e4211..370d9d902 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -63,7 +63,7 @@ Matrix square root of [`Operator`](@ref) type of [`QuantumObject`](@ref) Note that for other types of [`QuantumObject`](@ref) use `sprt(A)` instead. """ -sqrtm(A::QuantumObject{OperatorQuantumObject}) = sqrt(A) +sqrtm(A::QuantumObject{Operator}) = sqrt(A) @doc raw""" logm(A::QuantumObject) @@ -72,7 +72,7 @@ Matrix logarithm of [`QuantumObject`](@ref) Note that this function is same as `log(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -logm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = log(A) +logm(A::QuantumObject{ObjType}) where {ObjType<:Union{Operator,SuperOperator}} = log(A) @doc raw""" expm(A::QuantumObject) @@ -81,7 +81,7 @@ Matrix exponential of [`QuantumObject`](@ref) Note that this function is same as `exp(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -expm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = exp(A) +expm(A::QuantumObject{ObjType}) where {ObjType<:Union{Operator,SuperOperator}} = exp(A) @doc raw""" sinm(A::QuantumObject) @@ -92,7 +92,7 @@ Matrix sine of [`QuantumObject`](@ref), defined as Note that this function is same as `sin(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -sinm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = sin(A) +sinm(A::QuantumObject{ObjType}) where {ObjType<:Union{Operator,SuperOperator}} = sin(A) @doc raw""" cosm(A::QuantumObject) @@ -103,4 +103,4 @@ Matrix cosine of [`QuantumObject`](@ref), defined as Note that this function is same as `cos(A)` and only supports for [`Operator`](@ref) and [`SuperOperator`](@ref). """ -cosm(A::QuantumObject{ObjType}) where {ObjType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = cos(A) +cosm(A::QuantumObject{ObjType}) where {ObjType<:Union{Operator,SuperOperator}} = cos(A) diff --git a/src/spectrum.jl b/src/spectrum.jl index 14bc88893..036d95b3d 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -30,8 +30,8 @@ PseudoInverse(; alg::SciMLLinearSolveAlgorithm = KrylovJL_GMRES()) = PseudoInver spectrum(H::QuantumObject, ωlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}; + A::QuantumObject{Operator}, + B::QuantumObject{Operator}; solver::SpectrumSolver=ExponentialSeries(), kwargs...) @@ -49,11 +49,11 @@ function spectrum( H::QuantumObject{HOpType}, ωlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple}, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}; + A::QuantumObject{Operator}, + B::QuantumObject{Operator}; solver::SpectrumSolver = ExponentialSeries(), kwargs..., -) where {HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {HOpType<:Union{Operator,SuperOperator}} return _spectrum(liouvillian(H, c_ops), ωlist, A, B, solver; kwargs...) end @@ -77,10 +77,10 @@ function _spectrum_get_rates_vecs_ss(L, solver::ExponentialSeries{T,false}) wher end function _spectrum( - L::QuantumObject{SuperOperatorQuantumObject}, + L::QuantumObject{SuperOperator}, ωlist::AbstractVector, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, solver::ExponentialSeries; kwargs..., ) @@ -103,10 +103,10 @@ function _spectrum( end function _spectrum( - L::QuantumObject{SuperOperatorQuantumObject}, + L::QuantumObject{SuperOperator}, ωlist::AbstractVector, - A::QuantumObject{OperatorQuantumObject}, - B::QuantumObject{OperatorQuantumObject}, + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, solver::PseudoInverse; kwargs..., ) diff --git a/src/spin_lattice.jl b/src/spin_lattice.jl index 1abfff235..f3f47b237 100644 --- a/src/spin_lattice.jl +++ b/src/spin_lattice.jl @@ -59,7 +59,7 @@ function multisite_operator(dims::Union{AbstractVector,Tuple}, pairs::Pair{<:Int end data = kron(data, I(prod(_dims[(sites[end]+1):end]))) - return QuantumObject(data; type = Operator, dims = dims) + return QuantumObject(data; type = Operator(), dims = dims) end function multisite_operator(N::Union{Integer,Val}, pairs::Pair{<:Integer,<:QuantumObject}...) dims = ntuple(j -> 2, makeVal(N)) diff --git a/src/steadystate.jl b/src/steadystate.jl index 1d25eda6b..d17ad4783 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -115,7 +115,7 @@ function steadystate( c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; solver::SteadyStateSolver = SteadyStateDirectSolver(), kwargs..., -) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} +) where {OpType<:Union{Operator,SuperOperator}} solver isa SSFloquetEffectiveLiouvillian && throw( ArgumentError( "The solver `SSFloquetEffectiveLiouvillian` is only available for the `steadystate_fourier` function.", @@ -127,7 +127,7 @@ function steadystate( return _steadystate(L, solver; kwargs...) end -function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateLinearSolver; kwargs...) +function _steadystate(L::QuantumObject{SuperOperator}, solver::SteadyStateLinearSolver; kwargs...) L_tmp = L.data N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) @@ -159,10 +159,10 @@ function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::Stea ρss = reshape(ρss_vec, N, N) ρss = (ρss + ρss') / 2 # Hermitianize - return QuantumObject(ρss, Operator, L.dimensions) + return QuantumObject(ρss, Operator(), L.dimensions) end -function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateEigenSolver; kwargs...) +function _steadystate(L::QuantumObject{SuperOperator}, solver::SteadyStateEigenSolver; kwargs...) N = prod(L.dimensions) kwargs = merge((sigma = 1e-8, eigvals = 1), (; kwargs...)) @@ -171,10 +171,10 @@ function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::Stea ρss = reshape(ρss_vec, N, N) ρss /= tr(ρss) ρss = (ρss + ρss') / 2 # Hermitianize - return QuantumObject(ρss, Operator, L.dimensions) + return QuantumObject(ρss, Operator(), L.dimensions) end -function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateDirectSolver) +function _steadystate(L::QuantumObject{SuperOperator}, solver::SteadyStateDirectSolver) L_tmp = L.data N = prod(L.dimensions) weight = norm(L_tmp, 1) / length(L_tmp) @@ -196,10 +196,10 @@ function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::Stea ρss_vec = L_tmp \ v0 # This is still not supported on GPU, yet ρss = reshape(ρss_vec, N, N) ρss = (ρss + ρss') / 2 # Hermitianize - return QuantumObject(ρss, Operator, L.dimensions) + return QuantumObject(ρss, Operator(), L.dimensions) end -function _steadystate(L::QuantumObject{SuperOperatorQuantumObject}, solver::SteadyStateODESolver; kwargs...) +function _steadystate(L::QuantumObject{SuperOperator}, solver::SteadyStateODESolver; kwargs...) tmax = solver.tmax ψ0 = isnothing(solver.ψ0) ? rand_ket(L.dimensions) : solver.ψ0 @@ -333,9 +333,9 @@ function steadystate_fourier( solver::FSolver = SteadyStateLinearSolver(), kwargs..., ) where { - OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - OpType2<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - OpType3<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + OpType1<:Union{Operator,SuperOperator}, + OpType2<:Union{Operator,SuperOperator}, + OpType3<:Union{Operator,SuperOperator}, R<:Real, FSolver<:SteadyStateSolver, } @@ -346,9 +346,9 @@ function steadystate_fourier( end function _steadystate_fourier( - L_0::QuantumObject{SuperOperatorQuantumObject}, - L_p::QuantumObject{SuperOperatorQuantumObject}, - L_m::QuantumObject{SuperOperatorQuantumObject}, + L_0::QuantumObject{SuperOperator}, + L_p::QuantumObject{SuperOperator}, + L_m::QuantumObject{SuperOperator}, ωd::Number, solver::SteadyStateLinearSolver; n_max::Integer = 1, @@ -402,13 +402,13 @@ function _steadystate_fourier( ρ0 = reshape(ρtot[(offset1+1):offset2], Ns, Ns) ρ0_tr = tr(ρ0) ρ0 = ρ0 / ρ0_tr - ρ0 = QuantumObject((ρ0 + ρ0') / 2, type = Operator, dims = L_0.dimensions) + ρ0 = QuantumObject((ρ0 + ρ0') / 2, type = Operator(), dims = L_0.dimensions) ρtot = ρtot / ρ0_tr ρ_list = [ρ0] for i in 0:(n_max-1) ρi_m = reshape(ρtot[(offset1-(i+1)*N+1):(offset1-i*N)], Ns, Ns) - ρi_m = QuantumObject(ρi_m, type = Operator, dims = L_0.dimensions) + ρi_m = QuantumObject(ρi_m, type = Operator(), dims = L_0.dimensions) push!(ρ_list, ρi_m) end @@ -416,9 +416,9 @@ function _steadystate_fourier( end function _steadystate_fourier( - L_0::QuantumObject{SuperOperatorQuantumObject}, - L_p::QuantumObject{SuperOperatorQuantumObject}, - L_m::QuantumObject{SuperOperatorQuantumObject}, + L_0::QuantumObject{SuperOperator}, + L_p::QuantumObject{SuperOperator}, + L_m::QuantumObject{SuperOperator}, ωd::Number, solver::SSFloquetEffectiveLiouvillian; n_max::Integer = 1, diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index 4a0f204b3..bc166a540 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -366,7 +366,7 @@ get_B(u::AbstractArray{T}, N::Integer, M::Integer) where {T} = reshape(view(u, ( @doc raw""" lr_mesolveProblem( - H::QuantumObject{OperatorQuantumObject}, + H::QuantumObject{Operator}, z::AbstractArray{T,2}, B::AbstractArray{T,2}, tlist::AbstractVector, @@ -391,7 +391,7 @@ Formulates the ODEproblem for the low-rank time evolution of the system. The fun - `kwargs`: Additional keyword arguments. """ function lr_mesolveProblem( - H::QuantumObject{OperatorQuantumObject}, + H::QuantumObject{Operator}, z::AbstractArray{T,2}, B::AbstractArray{T,2}, tlist::AbstractVector, @@ -497,7 +497,7 @@ end @doc raw""" lr_mesolve( - H::QuantumObject{OperatorQuantumObject}, + H::QuantumObject{Operator}, z::AbstractArray{T,2}, B::AbstractArray{T,2}, tlist::AbstractVector, @@ -522,7 +522,7 @@ Time evolution of an open quantum system using the low-rank master equation. For - `kwargs`: Additional keyword arguments. """ function lr_mesolve( - H::QuantumObject{OperatorQuantumObject}, + H::QuantumObject{Operator}, z::AbstractArray{T2,2}, B::AbstractArray{T2,2}, tlist::AbstractVector, @@ -546,7 +546,7 @@ function lr_mesolve(prob::ODEProblem; kwargs...) Bt = map(x -> get_B(x[1], N, x[2]), zip(sol.u, Ml)) zt = map(x -> get_z(x[1], N, x[2]), zip(sol.u, Ml)) - ρt = map(x -> Qobj(x[1] * x[2] * x[1]', type = Operator, dims = prob.p.Hdims), zip(zt, Bt)) + ρt = map(x -> Qobj(x[1] * x[2] * x[1]', type = Operator(), dims = prob.p.Hdims), zip(zt, Bt)) return TimeEvolutionLRSol( sol.t, diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 210cf37a7..7db7048ff 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -22,7 +22,7 @@ end function _normalize_state!(u, dims, normalize_states) getVal(normalize_states) && normalize!(u) - return QuantumObject(u, type = Ket, dims = dims) + return QuantumObject(u, type = Ket(), dims = dims) end function _mcsolve_make_Heff_QobjEvo(H::QuantumObject, c_ops) @@ -40,8 +40,8 @@ end @doc raw""" mcsolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -109,8 +109,8 @@ If the environmental measurements register a quantum jump, the wave function und - `prob`: The [`TimeEvolutionProblem`](@ref) containing the `ODEProblem` for the Monte Carlo wave function time evolution. """ function mcsolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -141,8 +141,8 @@ end @doc raw""" mcsolveEnsembleProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -220,8 +220,8 @@ If the environmental measurements register a quantum jump, the wave function und - `prob`: The [`TimeEvolutionProblem`](@ref) containing the Ensemble `ODEProblem` for the Monte Carlo wave function time evolution. """ function mcsolveEnsembleProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -264,8 +264,8 @@ end @doc raw""" mcsolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), @@ -349,8 +349,8 @@ If the environmental measurements register a quantum jump, the wave function und - `sol::TimeEvolutionMCSol`: The solution of the time evolution. See also [`TimeEvolutionMCSol`](@ref). """ function mcsolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; alg::OrdinaryDiffEqAlgorithm = Tsit5(), diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 968cc8aad..980148889 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -1,6 +1,6 @@ export mesolveProblem, mesolve -_mesolve_make_L_QobjEvo(H::Union{QuantumObject,Nothing}, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator) +_mesolve_make_L_QobjEvo(H::Union{QuantumObject,Nothing}, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator()) _mesolve_make_L_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}, c_ops) = liouvillian(QobjEvo(H), c_ops) _mesolve_make_L_QobjEvo(H::Nothing, c_ops::Nothing) = throw(ArgumentError("Both H and c_ops are Nothing. You are probably running the wrong function.")) @@ -64,10 +64,7 @@ function mesolveProblem( progress_bar::Union{Val,Bool} = Val(true), inplace::Union{Val,Bool} = Val(true), kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}, -} +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator,OperatorKet}} (isoper(H) && isket(ψ0) && isnothing(c_ops)) && return sesolveProblem( H, ψ0, @@ -173,10 +170,7 @@ function mesolve( progress_bar::Union{Val,Bool} = Val(true), inplace::Union{Val,Bool} = Val(true), kwargs..., -) where { - HOpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}, -} +) where {HOpType<:Union{Operator,SuperOperator},StateOpType<:Union{Ket,Operator,OperatorKet}} (isoper(H) && isket(ψ0) && isnothing(c_ops)) && return sesolve( H, ψ0, @@ -210,9 +204,9 @@ function mesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit # No type instabilities since `isoperket` is a Val, and so it is known at compile time if getVal(prob.kwargs.isoperket) - ρt = map(ϕ -> QuantumObject(ϕ, type = OperatorKet, dims = prob.dimensions), sol.u) + ρt = map(ϕ -> QuantumObject(ϕ, type = OperatorKet(), dims = prob.dimensions), sol.u) else - ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator, dims = prob.dimensions), sol.u) + ρt = map(ϕ -> QuantumObject(vec2mat(ϕ), type = Operator(), dims = prob.dimensions), sol.u) end return TimeEvolutionSol( diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index 66f1df343..c24970f75 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -1,15 +1,17 @@ export sesolveProblem, sesolve _sesolve_make_U_QobjEvo( - H::QuantumObjectEvolution{OperatorQuantumObject,DimsType,<:MatrixOperator}, -) where {DimsType<:AbstractDimensions} = QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dimensions, type = Operator) -_sesolve_make_U_QobjEvo(H::QuantumObject) = QobjEvo(MatrixOperator(-1im * H.data), dims = H.dimensions, type = Operator) + H::QuantumObjectEvolution{Operator,DimsType,<:MatrixOperator}, +) where {DimsType<:AbstractDimensions} = + QobjEvo(MatrixOperator(-1im * H.data.A), dims = H.dimensions, type = Operator()) +_sesolve_make_U_QobjEvo(H::QuantumObject) = + QobjEvo(MatrixOperator(-1im * H.data), dims = H.dimensions, type = Operator()) _sesolve_make_U_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}) = QobjEvo(H, -1im) @doc raw""" sesolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), @@ -47,8 +49,8 @@ Generate the ODEProblem for the Schrödinger time evolution of a quantum system: - `prob`: The [`TimeEvolutionProblem`](@ref) containing the `ODEProblem` for the Schrödinger time evolution of the system. """ function sesolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, params = NullParameters(), @@ -86,8 +88,8 @@ end @doc raw""" sesolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -128,8 +130,8 @@ Time evolution of a closed quantum system using the Schrödinger equation: - `sol::TimeEvolutionSol`: The solution of the time evolution. See also [`TimeEvolutionSol`](@ref) """ function sesolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector; alg::OrdinaryDiffEqAlgorithm = Tsit5(), e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -155,7 +157,7 @@ end function sesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit5()) sol = solve(prob.prob, alg) - ψt = map(ϕ -> QuantumObject(ϕ, type = Ket, dims = prob.dimensions), sol.u) + ψt = map(ϕ -> QuantumObject(ϕ, type = Ket(), dims = prob.dimensions), sol.u) return TimeEvolutionSol( prob.times, diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index ae28e4716..710029197 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -1,7 +1,7 @@ export smesolveProblem, smesolveEnsembleProblem, smesolve -_smesolve_generate_state(u, dims, isoperket::Val{false}) = QuantumObject(vec2mat(u), type = Operator, dims = dims) -_smesolve_generate_state(u, dims, isoperket::Val{true}) = QuantumObject(u, type = OperatorKet, dims = dims) +_smesolve_generate_state(u, dims, isoperket::Val{false}) = QuantumObject(vec2mat(u), type = Operator(), dims = dims) +_smesolve_generate_state(u, dims, isoperket::Val{true}) = QuantumObject(u, type = OperatorKet(), dims = dims) function _smesolve_update_coeff(u, p, t, op_vec) return 2 * real(dot(op_vec, u)) #this is Tr[Sn * ρ + ρ * Sn'] @@ -12,7 +12,7 @@ _smesolve_ScalarOperator(op_vec) = @doc raw""" smesolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + H::Union{AbstractQuantumObject{Operator},Tuple}, ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -74,7 +74,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `prob`: The [`TimeEvolutionProblem`](@ref) containing the `SDEProblem` for the Stochastic Master Equation time evolution. """ function smesolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + H::Union{AbstractQuantumObject{Operator},Tuple}, ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -85,7 +85,7 @@ function smesolveProblem( progress_bar::Union{Val,Bool} = Val(true), store_measurement::Union{Val,Bool} = Val(false), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}} +) where {StateOpType<:Union{Ket,Operator,OperatorKet}} haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) @@ -153,7 +153,7 @@ end @doc raw""" smesolveEnsembleProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + H::Union{AbstractQuantumObject{Operator},Tuple}, ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -223,7 +223,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `prob`: The [`TimeEvolutionProblem`](@ref) containing the Ensemble `SDEProblem` for the Stochastic Master Equation time evolution. """ function smesolveEnsembleProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + H::Union{AbstractQuantumObject{Operator},Tuple}, ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -238,7 +238,7 @@ function smesolveEnsembleProblem( progress_bar::Union{Val,Bool} = Val(true), store_measurement::Union{Val,Bool} = Val(false), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}} +) where {StateOpType<:Union{Ket,Operator,OperatorKet}} _prob_func = isnothing(prob_func) ? _ensemble_dispatch_prob_func( @@ -279,7 +279,7 @@ end @doc raw""" smesolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + H::Union{AbstractQuantumObject{Operator},Tuple}, ψ0::QuantumObject, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -351,7 +351,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `sol::TimeEvolutionStochasticSol`: The solution of the time evolution. See [`TimeEvolutionStochasticSol`](@ref). """ function smesolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, + H::Union{AbstractQuantumObject{Operator},Tuple}, ψ0::QuantumObject{StateOpType}, tlist::AbstractVector, c_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -367,7 +367,7 @@ function smesolve( progress_bar::Union{Val,Bool} = Val(true), store_measurement::Union{Val,Bool} = Val(false), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject,OperatorKetQuantumObject}} +) where {StateOpType<:Union{Ket,Operator,OperatorKet}} ensemble_prob = smesolveEnsembleProblem( H, ψ0, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index b29925481..6945dc81e 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -14,8 +14,8 @@ _ScalarOperator_e2_2(op, f = +) = @doc raw""" ssesolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -75,8 +75,8 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `prob`: The `SDEProblem` for the Stochastic Schrödinger time evolution of the system. """ function ssesolveProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -148,8 +148,8 @@ end @doc raw""" ssesolveEnsembleProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -218,8 +218,8 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is t - `prob::EnsembleProblem with SDEProblem`: The Ensemble SDEProblem for the Stochastic Shrödinger time evolution. """ function ssesolveEnsembleProblem( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; e_ops::Union{Nothing,AbstractVector,Tuple} = nothing, @@ -272,8 +272,8 @@ end @doc raw""" ssesolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; alg::Union{Nothing,StochasticDiffEqAlgorithm} = nothing, @@ -347,8 +347,8 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `sol::TimeEvolutionStochasticSol`: The solution of the time evolution. See [`TimeEvolutionStochasticSol`](@ref). """ function ssesolve( - H::Union{AbstractQuantumObject{OperatorQuantumObject},Tuple}, - ψ0::QuantumObject{KetQuantumObject}, + H::Union{AbstractQuantumObject{Operator},Tuple}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, sc_ops::Union{Nothing,AbstractVector,Tuple,AbstractQuantumObject} = nothing; alg::Union{Nothing,StochasticDiffEqAlgorithm} = nothing, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 2ab50893a..30730bc9d 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -423,9 +423,9 @@ end ####################################### function liouvillian_floquet( - L₀::QuantumObject{SuperOperatorQuantumObject}, - Lₚ::QuantumObject{SuperOperatorQuantumObject}, - Lₘ::QuantumObject{SuperOperatorQuantumObject}, + L₀::QuantumObject{SuperOperator}, + Lₚ::QuantumObject{SuperOperator}, + Lₘ::QuantumObject{SuperOperator}, ω::Real; n_max::Int = 3, tol::Real = 1e-15, @@ -443,9 +443,9 @@ function liouvillian_floquet( n_max::Int = 3, tol::Real = 1e-15, ) where { - OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - OpType2<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, - OpType3<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}, + OpType1<:Union{Operator,SuperOperator}, + OpType2<:Union{Operator,SuperOperator}, + OpType3<:Union{Operator,SuperOperator}, } return liouvillian_floquet(liouvillian(H, c_ops), liouvillian(Hₚ), liouvillian(Hₘ), ω, n_max = n_max, tol = tol) end @@ -459,7 +459,7 @@ Constructs the generalized Liouvillian for a system coupled to a bath of harmoni See, e.g., Settineri, Alessio, et al. "Dissipation and thermal noise in hybrid quantum systems in the ultrastrong-coupling regime." Physical Review A 98.5 (2018): 053834. """ function liouvillian_generalized( - H::QuantumObject{OperatorQuantumObject}, + H::QuantumObject{Operator}, fields::Vector, T_list::Vector{<:Real}; N_trunc::Union{Int,Nothing} = nothing, @@ -474,14 +474,14 @@ function liouvillian_generalized( E = real.(result.values[1:final_size]) U = QuantumObject(result.vectors, result.type, result.dimensions) - H_d = QuantumObject(Diagonal(complex(E)), type = Operator, dims = dims) + H_d = QuantumObject(Diagonal(complex(E)), type = Operator(), dims = dims) Ω = E' .- E Ωp = triu(to_sparse(Ω, tol), 1) # Filter in the Hilbert space σ = isnothing(σ_filter) ? 500 * maximum([norm(field) / length(field) for field in fields]) : σ_filter - F1 = QuantumObject(gaussian.(Ω, 0, σ), type = Operator, dims = dims) + F1 = QuantumObject(gaussian.(Ω, 0, σ), type = Operator(), dims = dims) F1 = to_sparse(F1, tol) # Filter in the Liouville space @@ -491,7 +491,7 @@ function liouvillian_generalized( Ω1 = kron(Ω, M1) Ω2 = kron(M1, Ω) Ωdiff = Ω1 .- Ω2 - F2 = QuantumObject(gaussian.(Ωdiff, 0, σ), SuperOperator, dims) + F2 = QuantumObject(gaussian.(Ωdiff, 0, σ), SuperOperator(), dims) F2 = to_sparse(F2, tol) L = liouvillian(H_d) @@ -505,9 +505,9 @@ function liouvillian_generalized( # Ohmic reservoir N_th = n_thermal.(Ωp, T_list[i]) - Sp₀ = QuantumObject(triu(X_op, 1), type = Operator, dims = dims) - Sp₁ = QuantumObject(droptol!((@. Ωp * N_th * Sp₀.data), tol), type = Operator, dims = dims) - Sp₂ = QuantumObject(droptol!((@. Ωp * (1 + N_th) * Sp₀.data), tol), type = Operator, dims = dims) + Sp₀ = QuantumObject(triu(X_op, 1), type = Operator(), dims = dims) + Sp₁ = QuantumObject(droptol!((@. Ωp * N_th * Sp₀.data), tol), type = Operator(), dims = dims) + Sp₂ = QuantumObject(droptol!((@. Ωp * (1 + N_th) * Sp₀.data), tol), type = Operator(), dims = dims) # S0 = QuantumObject( spdiagm(diag(X_op)), dims=dims ) L += @@ -522,9 +522,9 @@ function liouvillian_generalized( end function _liouvillian_floquet( - L₀::QuantumObject{SuperOperatorQuantumObject}, - Lₚ::QuantumObject{SuperOperatorQuantumObject}, - Lₘ::QuantumObject{SuperOperatorQuantumObject}, + L₀::QuantumObject{SuperOperator}, + Lₚ::QuantumObject{SuperOperator}, + Lₘ::QuantumObject{SuperOperator}, ω::Real, n_max::Int, tol::Real, @@ -543,6 +543,6 @@ function _liouvillian_floquet( T = -(L_0 + 1im * n_i * ω * I + L_p * T) \ L_m_dense end - tol == 0 && return QuantumObject(L_0 + L_m * S + L_p * T, SuperOperator, L₀.dimensions) - return QuantumObject(to_sparse(L_0 + L_m * S + L_p * T, tol), SuperOperator, L₀.dimensions) + tol == 0 && return QuantumObject(L_0 + L_m * S + L_p * T, SuperOperator(), L₀.dimensions) + return QuantumObject(to_sparse(L_0 + L_m * S + L_p * T, tol), SuperOperator(), L₀.dimensions) end diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index e46633239..2e07047da 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -148,7 +148,7 @@ function dfd_mesolveProblem( params::NamedTuple = NamedTuple(), tol_list::Vector{<:Number} = fill(1e-8, length(maxdims)), kwargs..., -) where {T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {T2<:Integer,StateOpType<:Union{Ket,Operator}} length(ψ0.dimensions) != length(maxdims) && throw(DimensionMismatch("`dim_list` and `maxdims` do not have the same dimension.")) @@ -219,7 +219,7 @@ function dfd_mesolve( params::NamedTuple = NamedTuple(), tol_list::Vector{<:Number} = fill(1e-8, length(maxdims)), kwargs..., -) where {T2<:Integer,StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {T2<:Integer,StateOpType<:Union{Ket,Operator}} dfd_prob = dfd_mesolveProblem( H, ψ0, @@ -239,7 +239,7 @@ function dfd_mesolve( idx = findfirst(>=(sol.t[i]), sol.prob.p.dim_list_evo_times) idx2 = isnothing(idx) ? length(sol.prob.p.dim_list_evo) : (idx == 1 ? 1 : idx - 1) - return QuantumObject(vec2mat(sol.u[i]), type = Operator, dims = sol.prob.p.dim_list_evo[idx2]) + return QuantumObject(vec2mat(sol.u[i]), type = Operator(), dims = sol.prob.p.dim_list_evo[idx2]) end return TimeEvolutionSol( @@ -350,7 +350,7 @@ function dsf_mesolveProblem( δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{Ket,Operator}} op_list = deepcopy(op_list) H₀ = H(op_list .+ α0_l, dsf_params) c_ops₀ = c_ops(op_list .+ α0_l, dsf_params) @@ -433,7 +433,7 @@ function dsf_mesolve( δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{Ket,Operator}} dsf_prob = dsf_mesolveProblem( H, ψ0, @@ -465,7 +465,7 @@ function dsf_mesolve( δα_list::Vector{<:Real} = fill(0.2, length(op_list)), krylov_dim::Int = max(6, min(10, cld(length(ket2dm(ψ0).data), 4))), kwargs..., -) where {StateOpType<:Union{KetQuantumObject,OperatorQuantumObject}} +) where {StateOpType<:Union{Ket,Operator}} c_ops = op_list -> () return dsf_mesolve( H, @@ -599,7 +599,7 @@ end function dsf_mcsolveEnsembleProblem( H::Function, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Function, op_list::Union{AbstractVector,Tuple}, @@ -694,7 +694,7 @@ Time evolution of a quantum system using the Monte Carlo wave function method an """ function dsf_mcsolve( H::Function, - ψ0::QuantumObject{KetQuantumObject}, + ψ0::QuantumObject{Ket}, tlist::AbstractVector, c_ops::Function, op_list::Union{AbstractVector,Tuple}, diff --git a/src/visualization.jl b/src/visualization.jl index f24485228..e43fb10dd 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -5,7 +5,7 @@ export plot_wigner, plot_fock_distribution state::QuantumObject{OpType}; library::Union{Val,Symbol}=Val(:Makie), kwargs... - ) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject} + ) where {OpType<:Union{Bra,Ket,Operator} Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`wigner`](@ref) function. @@ -26,14 +26,9 @@ plot_wigner( state::QuantumObject{OpType}; library::Union{Val,Symbol} = Val(:Makie), kwargs..., -) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = - plot_wigner(makeVal(library), state; kwargs...) +) where {OpType<:Union{Bra,Ket,Operator}} = plot_wigner(makeVal(library), state; kwargs...) -plot_wigner( - ::Val{T}, - state::QuantumObject{OpType}; - kwargs..., -) where {T,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = +plot_wigner(::Val{T}, state::QuantumObject{OpType}; kwargs...) where {T,OpType<:Union{Bra,Ket,Operator}} = throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) @doc raw""" @@ -41,7 +36,7 @@ plot_wigner( ρ::QuantumObject{SType}; library::Union{Val, Symbol} = Val(:Makie), kwargs... - ) where {SType<:Union{KetQuantumObject,OperatorQuantumObject}} + ) where {SType<:Union{Ket,Operator}} Plot the [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution of `ρ`. @@ -62,12 +57,7 @@ plot_fock_distribution( ρ::QuantumObject{SType}; library::Union{Val,Symbol} = Val(:Makie), kwargs..., -) where {SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = - plot_fock_distribution(makeVal(library), ρ; kwargs...) +) where {SType<:Union{Bra,Ket,Operator}} = plot_fock_distribution(makeVal(library), ρ; kwargs...) -plot_fock_distribution( - ::Val{T}, - ρ::QuantumObject{SType}; - kwargs..., -) where {T,SType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} = +plot_fock_distribution(::Val{T}, ρ::QuantumObject{SType}; kwargs...) where {T,SType<:Union{Bra,Ket,Operator}} = throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) diff --git a/src/wigner.jl b/src/wigner.jl index 813457b6e..b4954a9a4 100644 --- a/src/wigner.jl +++ b/src/wigner.jl @@ -25,7 +25,7 @@ Generates the [Wigner quasipropability distribution](https://en.wikipedia.org/wi The `method` parameter can be either `WignerLaguerre()` or `WignerClenshaw()`. The former uses the Laguerre polynomial expansion of the Wigner function, while the latter uses the Clenshaw algorithm. The Laguerre expansion is faster for sparse matrices, while the Clenshaw algorithm is faster for dense matrices. The `WignerLaguerre` method has an optional `parallel` parameter which defaults to `true` and uses multithreading to speed up the calculation. # Arguments -- `state::QuantumObject`: The quantum state for which the Wigner function is calculated. It can be either a [`KetQuantumObject`](@ref), [`BraQuantumObject`](@ref), or [`OperatorQuantumObject`](@ref). +- `state::QuantumObject`: The quantum state for which the Wigner function is calculated. It can be either a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). - `xvec::AbstractVector`: The x-coordinates of the phase space grid. - `yvec::AbstractVector`: The y-coordinates of the phase space grid. - `g::Real`: The scaling factor related to the value of ``\hbar`` in the commutation relation ``[x, y] = i \hbar`` via ``\hbar=2/g^2``. @@ -38,7 +38,7 @@ The `method` parameter can be either `WignerLaguerre()` or `WignerClenshaw()`. T ```jldoctest wigner julia> ψ = fock(10, 0) + fock(10, 1) |> normalize -Quantum Object: type=Ket dims=[10] size=(10,) +Quantum Object: type=Ket() dims=[10] size=(10,) 10-element Vector{ComplexF64}: 0.7071067811865475 + 0.0im 0.7071067811865475 + 0.0im @@ -72,7 +72,7 @@ function wigner( yvec::AbstractVector; g::Real = √2, method::WignerSolver = WignerClenshaw(), -) where {OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}} +) where {OpType<:Union{Bra,Ket,Operator}} ρ = ket2dm(state).data return _wigner(ρ, xvec, yvec, g, method) diff --git a/test/core-test/eigenvalues_and_operators.jl b/test/core-test/eigenvalues_and_operators.jl index 0b4959beb..a2fe6bcc5 100644 --- a/test/core-test/eigenvalues_and_operators.jl +++ b/test/core-test/eigenvalues_and_operators.jl @@ -8,14 +8,14 @@ λs, ψs, Ts = eigenstates(σx, sparse = true, eigvals = 2) λs1, ψs1, Ts1 = eigenstates(σx, sparse = true, eigvals = 1) - @test all([ψ.type isa KetQuantumObject for ψ in ψd]) + @test all([ψ.type isa Ket for ψ in ψd]) @test typeof(Td) <: AbstractMatrix @test typeof(Ts) <: AbstractMatrix @test typeof(Ts1) <: AbstractMatrix @test all(abs.(eigenenergies(σx, sparse = false)) .≈ abs.(λd)) @test all(abs.(eigenenergies(σx, sparse = true, eigvals = 2)) .≈ abs.(λs)) @test resstring == - "EigsolveResult: type=$(Operator) dims=$(result.dims)\nvalues:\n$(valstring)\nvectors:\n$vecsstring" + "EigsolveResult: type=$(Operator()) dims=$(result.dims)\nvalues:\n$(valstring)\nvectors:\n$vecsstring" N = 30 a = kron(destroy(N), qeye(2)) @@ -76,16 +76,16 @@ valstring = sprint((t, s) -> show(t, "text/plain", s), result.values) vecsstring = sprint((t, s) -> show(t, "text/plain", s), result.vectors) @test resstring == - "EigsolveResult: type=$(SuperOperator) dims=$(result.dims)\nvalues:\n$(valstring)\nvectors:\n$vecsstring" + "EigsolveResult: type=$(SuperOperator()) dims=$(result.dims)\nvalues:\n$(valstring)\nvectors:\n$vecsstring" vals2, vecs2 = eigenstates(L, sparse = false) idxs = sortperm(vals2, by = abs) vals2 = vals2[idxs][1:10] vecs2 = vecs2[idxs][1:10] - @test result.type isa SuperOperatorQuantumObject + @test result.type isa SuperOperator @test result.dims == L.dims - @test all([v.type isa OperatorKetQuantumObject for v in vecs]) + @test all([v.type isa OperatorKet for v in vecs]) @test typeof(result.vectors) <: AbstractMatrix @test isapprox(sum(abs2, vals), sum(abs2, vals2), atol = 1e-7) @test isapprox(abs2(vals2[1]), abs2(vals3[1]), atol = 1e-7) diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index 27066cdb1..07faf788f 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -9,7 +9,7 @@ M = latt.N + 1 # Number of states in the LR basis # Define initial state - ϕ = Vector{QuantumObject{KetQuantumObject,Dimensions{M - 1,NTuple{M - 1,Space}},Vector{ComplexF64}}}(undef, M) + ϕ = Vector{QuantumObject{Ket,Dimensions{M - 1,NTuple{M - 1,Space}},Vector{ComplexF64}}}(undef, M) ϕ[1] = kron(fill(basis(2, 1), N_modes)...) i = 1 @@ -68,7 +68,7 @@ mul!(C, z, sqrt(B)) mul!(σ, C', C) - return entropy_vn(Qobj(Hermitian(σ), type = Operator), base = 2) + return entropy_vn(Qobj(Hermitian(σ), type = Operator()), base = 2) end opt = (err_max = 1e-3, p0 = 0.0, atol_inv = 1e-6, adj_condition = "variational", Δt = 0.0, progress = false) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 0730b069f..c01eeca33 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -2,38 +2,38 @@ # ArgumentError: type is incompatible with vector or matrix @testset "ArgumentError" begin a = rand(ComplexF64, 2) - for t in [Operator, SuperOperator, Bra, OperatorBra] + for t in (Operator(), SuperOperator(), Bra(), OperatorBra()) @test_throws ArgumentError Qobj(a, type = t) end a = rand(ComplexF64, 2, 2) - @test_throws ArgumentError Qobj(a, type = Ket) - @test_throws ArgumentError Qobj(a, type = OperatorKet) + @test_throws ArgumentError Qobj(a, type = Ket()) + @test_throws ArgumentError Qobj(a, type = OperatorKet()) end # DomainError: incompatible between size of array and type @testset "DomainError" begin a = rand(ComplexF64, 3, 2) - for t in [SuperOperator, Bra, OperatorBra] + for t in [SuperOperator(), Bra(), OperatorBra()] @test_throws DomainError Qobj(a, type = t) end a = rand(ComplexF64, 2, 2, 2) - for t in [nothing, Ket, Bra, Operator, SuperOperator, OperatorBra, OperatorKet] + for t in (nothing, Ket(), Bra(), Operator(), SuperOperator(), OperatorBra(), OperatorKet()) @test_throws DomainError Qobj(a, type = t) end a = rand(ComplexF64, 1, 2) - @test_throws DomainError Qobj(a, type = Operator) - @test_throws DomainError Qobj(a, type = SuperOperator) + @test_throws DomainError Qobj(a, type = Operator()) + @test_throws DomainError Qobj(a, type = SuperOperator()) - @test_throws DomainError Qobj(rand(ComplexF64, 2, 1), type = Operator) # should be type = Bra + @test_throws DomainError Qobj(rand(ComplexF64, 2, 1), type = Operator()) # should be type = Bra # check that Ket, Bra, SuperOperator, OperatorKet, and OperatorBra don't support GeneralDimensions - @test_throws DomainError Qobj(rand(ComplexF64, 2), type = Ket, dims = ((2,), (1,))) - @test_throws DomainError Qobj(rand(ComplexF64, 1, 2), type = Bra, dims = ((1,), (2,))) - @test_throws DomainError Qobj(rand(ComplexF64, 4, 4), type = SuperOperator, dims = ((2,), (2,))) - @test_throws DomainError Qobj(rand(ComplexF64, 4), type = OperatorKet, dims = ((2,), (1,))) - @test_throws DomainError Qobj(rand(ComplexF64, 1, 4), type = OperatorBra, dims = ((1,), (2,))) + @test_throws DomainError Qobj(rand(ComplexF64, 2), type = Ket(), dims = ((2,), (1,))) + @test_throws DomainError Qobj(rand(ComplexF64, 1, 2), type = Bra(), dims = ((1,), (2,))) + @test_throws DomainError Qobj(rand(ComplexF64, 4, 4), type = SuperOperator(), dims = ((2,), (2,))) + @test_throws DomainError Qobj(rand(ComplexF64, 4), type = OperatorKet(), dims = ((2,), (1,))) + @test_throws DomainError Qobj(rand(ComplexF64, 1, 4), type = OperatorBra(), dims = ((1,), (2,))) end # unsupported type of dims @@ -84,7 +84,7 @@ a = sprand(ComplexF64, 100, 100, 0.1) a2 = Qobj(a) - a3 = Qobj(a, type = SuperOperator) + a3 = Qobj(a, type = SuperOperator()) a4 = Qobj(sprand(ComplexF64, 100, 10, 0.1)) # GeneralDimensions a5 = QuantumObject(rand(ComplexF64, 2*3*4, 5), dims = ((2, 3, 4), (5,))) @test isket(a2) == false @@ -130,7 +130,7 @@ ρ = Qobj(rand(ComplexF64, 2, 2)) ρ_ket = operator_to_vector(ρ) ρ_bra = ρ_ket' - @test ρ_bra == Qobj(operator_to_vector(ρ.data)', type = OperatorBra) + @test ρ_bra == Qobj(operator_to_vector(ρ.data)', type = OperatorBra()) @test ρ == vector_to_operator(ρ_ket) @test isket(ρ_ket) == false @test isbra(ρ_ket) == false @@ -155,8 +155,8 @@ @test L * ρ_ket ≈ -1im * (+(spre(H) * ρ_ket) - spost(H) * ρ_ket) @test (ρ_bra * L')' == L * ρ_ket @test sum((conj(ρ) .* ρ).data) ≈ dot(ρ_ket, ρ_ket) ≈ ρ_bra * ρ_ket - @test_throws DimensionMismatch Qobj(ρ_ket.data, type = OperatorKet, dims = 4) - @test_throws DimensionMismatch Qobj(ρ_bra.data, type = OperatorBra, dims = 4) + @test_throws DimensionMismatch Qobj(ρ_ket.data, type = OperatorKet(), dims = 4) + @test_throws DimensionMismatch Qobj(ρ_bra.data, type = OperatorBra(), dims = 4) end @testset "Checks on non-QuantumObjects" begin @@ -180,7 +180,7 @@ @testset "arithmetic" begin a = sprand(ComplexF64, 100, 100, 0.1) a2 = Qobj(a) - a3 = Qobj(a, type = SuperOperator) + a3 = Qobj(a, type = SuperOperator()) a4 = to_sparse(a2) @test isequal(a4, a2) == true @test isequal(a4, a3) == false @@ -262,7 +262,7 @@ a_size = size(a) a_isherm = isherm(a) @test opstring == - "\nQuantum Object: type=Operator dims=$a_dims size=$a_size ishermitian=$a_isherm\n$datastring" + "\nQuantum Object: type=Operator() dims=$a_dims size=$a_size ishermitian=$a_isherm\n$datastring" # GeneralDimensions Gop = tensor(a, ψ) @@ -272,7 +272,7 @@ Gop_size = size(Gop) Gop_isherm = isherm(Gop) @test opstring == - "\nQuantum Object: type=Operator dims=$Gop_dims size=$Gop_size ishermitian=$Gop_isherm\n$datastring" + "\nQuantum Object: type=Operator() dims=$Gop_dims size=$Gop_size ishermitian=$Gop_isherm\n$datastring" a = spre(a) opstring = sprint((t, s) -> show(t, "text/plain", s), a) @@ -280,42 +280,42 @@ a_dims = a.dims a_size = size(a) a_isherm = isherm(a) - @test opstring == "\nQuantum Object: type=SuperOperator dims=$a_dims size=$a_size\n$datastring" + @test opstring == "\nQuantum Object: type=SuperOperator() dims=$a_dims size=$a_size\n$datastring" opstring = sprint((t, s) -> show(t, "text/plain", s), ψ) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ.data) ψ_dims = ψ.dims ψ_size = size(ψ) - @test opstring == "\nQuantum Object: type=Ket dims=$ψ_dims size=$ψ_size\n$datastring" + @test opstring == "\nQuantum Object: type=Ket() dims=$ψ_dims size=$ψ_size\n$datastring" ψ = ψ' opstring = sprint((t, s) -> show(t, "text/plain", s), ψ) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ.data) ψ_dims = ψ.dims ψ_size = size(ψ) - @test opstring == "\nQuantum Object: type=Bra dims=$ψ_dims size=$ψ_size\n$datastring" + @test opstring == "\nQuantum Object: type=Bra() dims=$ψ_dims size=$ψ_size\n$datastring" - ψ2 = Qobj(rand(ComplexF64, 4), type = OperatorKet) + ψ2 = Qobj(rand(ComplexF64, 4), type = OperatorKet()) opstring = sprint((t, s) -> show(t, "text/plain", s), ψ2) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ2.data) ψ2_dims = ψ2.dims ψ2_size = size(ψ2) - @test opstring == "\nQuantum Object: type=OperatorKet dims=$ψ2_dims size=$ψ2_size\n$datastring" + @test opstring == "\nQuantum Object: type=OperatorKet() dims=$ψ2_dims size=$ψ2_size\n$datastring" ψ2 = ψ2' opstring = sprint((t, s) -> show(t, "text/plain", s), ψ2) datastring = sprint((t, s) -> show(t, "text/plain", s), ψ2.data) ψ2_dims = ψ2.dims ψ2_size = size(ψ2) - @test opstring == "\nQuantum Object: type=OperatorBra dims=$ψ2_dims size=$ψ2_size\n$datastring" + @test opstring == "\nQuantum Object: type=OperatorBra() dims=$ψ2_dims size=$ψ2_size\n$datastring" end @testset "matrix element" begin H = Qobj([1 2; 3 4]) L = liouvillian(H) - s0 = Qobj(basis(4, 0).data; type = OperatorKet) - s1 = Qobj(basis(4, 1).data; type = OperatorKet) - s_wrong = Qobj(basis(9, 0).data; type = OperatorKet) + s0 = Qobj(basis(4, 0).data; type = OperatorKet()) + s1 = Qobj(basis(4, 1).data; type = OperatorKet()) + s_wrong = Qobj(basis(9, 0).data; type = OperatorKet()) @test matrix_element(basis(2, 0), H, basis(2, 1)) == H[1, 2] @test matrix_element(s0, L, s1) == L[1, 2] @test_throws DimensionMismatch matrix_element(basis(3, 0), H, basis(2, 1)) @@ -351,32 +351,32 @@ end @testset "Type Inference (QuantumObject)" begin - for T in [ComplexF32, ComplexF64] + for T in (ComplexF32, ComplexF64) N = 4 a = rand(T, N) @inferred Qobj(a) - for type in [Ket, OperatorKet] + for type in (Ket(), OperatorKet()) @inferred Qobj(a, type = type) end UnionType = Union{ - QuantumObject{BraQuantumObject,Dimensions{1,Tuple{Space}},Matrix{T}}, - QuantumObject{OperatorQuantumObject,Dimensions{1,Tuple{Space}},Matrix{T}}, + QuantumObject{Bra,Dimensions{1,Tuple{Space}},Matrix{T}}, + QuantumObject{Operator,Dimensions{1,Tuple{Space}},Matrix{T}}, } a = rand(T, 1, N) @inferred UnionType Qobj(a) - for type in [Bra, OperatorBra] + for type in (Bra(), OperatorBra()) @inferred Qobj(a, type = type) end UnionType2 = Union{ - QuantumObject{OperatorQuantumObject,GeneralDimensions{1,1,Tuple{Space},Tuple{Space}},Matrix{T}}, - QuantumObject{OperatorQuantumObject,Dimensions{1,Tuple{Space}},Matrix{T}}, + QuantumObject{Operator,GeneralDimensions{1,1,Tuple{Space},Tuple{Space}},Matrix{T}}, + QuantumObject{Operator,Dimensions{1,Tuple{Space}},Matrix{T}}, } a = rand(T, N, N) @inferred UnionType Qobj(a) - @inferred UnionType2 Qobj(a, type = Operator) - @inferred Qobj(a, type = SuperOperator) + @inferred UnionType2 Qobj(a, type = Operator()) + @inferred Qobj(a, type = SuperOperator()) end @testset "Math Operation" begin diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 1e923cf31..14e5a3270 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -2,21 +2,21 @@ # DomainError: incompatible between size of array and type @testset "Thrown Errors" begin a = MatrixOperator(rand(ComplexF64, 3, 2)) - @test_throws DomainError QobjEvo(a, type = SuperOperator) + @test_throws DomainError QobjEvo(a, type = SuperOperator()) a = MatrixOperator(rand(ComplexF64, 4, 4)) - @test_throws DomainError QobjEvo(a, type = SuperOperator, dims = ((2,), (2,))) + @test_throws DomainError QobjEvo(a, type = SuperOperator(), dims = ((2,), (2,))) a = MatrixOperator(rand(ComplexF64, 3, 2)) - for t in (Ket, Bra, OperatorKet, OperatorBra) + for t in (Ket(), Bra(), OperatorKet(), OperatorBra()) @test_throws ArgumentError QobjEvo(a, type = t) end a = QobjEvo(destroy(20)) - @test_throws ArgumentError QobjEvo(a, type = SuperOperator) + @test_throws ArgumentError QobjEvo(a, type = SuperOperator()) a = MatrixOperator(rand(ComplexF64, 5, 5)) - @test_throws DimensionMismatch QobjEvo(a, type = SuperOperator) + @test_throws DimensionMismatch QobjEvo(a, type = SuperOperator()) ψ = fock(10, 3) @test_throws MethodError QobjEvo(ψ) @@ -38,7 +38,7 @@ @testset "Operator and SuperOperator" begin a = MatrixOperator(sprand(ComplexF64, 100, 100, 0.1)) a2 = QobjEvo(a) - a3 = QobjEvo(a, type = SuperOperator) + a3 = QobjEvo(a, type = SuperOperator()) @test isket(a2) == false @test isbra(a2) == false @@ -67,7 +67,7 @@ @testset "arithmetic" begin a = MatrixOperator(sprand(ComplexF64, 100, 100, 0.1)) a2 = QobjEvo(a) - a3 = QobjEvo(a, type = SuperOperator) + a3 = QobjEvo(a, type = SuperOperator()) @test +a2 == a2 @test -(-a2) == a2 @@ -91,7 +91,7 @@ N = 10 # We use MatrixOperator instead of directly using a Qobj to increase coverage - a = QobjEvo(MatrixOperator(sprand(ComplexF64, N, N, 5 / N)), Operator, N) + a = QobjEvo(MatrixOperator(sprand(ComplexF64, N, N, 5 / N)), Operator(), N) a_d = a' X = a + a_d # Y = 1im * (a - a_d) # Currently doesn't work. Fix in SciMLOperators.jl @@ -115,7 +115,7 @@ H_isherm = isherm(H) H_isconst = isconstant(H) @test opstring == - "\nQuantum Object Evo.: type=Operator dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst\n$datastring" + "\nQuantum Object Evo.: type=Operator() dims=$H_dims size=$H_size ishermitian=$H_isherm isconstant=$H_isconst\n$datastring" L = QobjEvo(spre(a)) opstring = sprint((t, s) -> show(t, "text/plain", s), L) @@ -125,7 +125,7 @@ L_isherm = isherm(L) L_isconst = isconstant(L) @test opstring == - "\nQuantum Object Evo.: type=SuperOperator dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst\n$datastring" + "\nQuantum Object Evo.: type=SuperOperator() dims=$L_dims size=$L_size ishermitian=$L_isherm isconstant=$L_isconst\n$datastring" end @testset "Type Inference (QobjEvo)" begin @@ -133,12 +133,12 @@ for T in [ComplexF32, ComplexF64] a = MatrixOperator(rand(T, N, N)) UnionType = Union{ - QuantumObjectEvolution{OperatorQuantumObject,GeneralDimensions{1,Tuple{Space},Tuple{Space}},typeof(a)}, - QuantumObjectEvolution{OperatorQuantumObject,Dimensions{1,Tuple{Space}},typeof(a)}, + QuantumObjectEvolution{Operator,GeneralDimensions{1,Tuple{Space},Tuple{Space}},typeof(a)}, + QuantumObjectEvolution{Operator,Dimensions{1,Tuple{Space}},typeof(a)}, } @inferred UnionType QobjEvo(a) - @inferred UnionType QobjEvo(a, type = Operator) - @inferred QobjEvo(a, type = SuperOperator) + @inferred UnionType QobjEvo(a, type = Operator()) + @inferred QobjEvo(a, type = SuperOperator()) end a = destroy(N) diff --git a/test/core-test/states_and_operators.jl b/test/core-test/states_and_operators.jl index 0cdfbd9e5..1fd77976a 100644 --- a/test/core-test/states_and_operators.jl +++ b/test/core-test/states_and_operators.jl @@ -320,8 +320,8 @@ @testset "identity operator" begin I_op1 = qeye(4) I_op2 = qeye(4, dims = (2, 2)) - I_su1 = qeye(4, type = SuperOperator) - I_su2 = qeye(4, type = SuperOperator, dims = 2) + I_su1 = qeye(4, type = SuperOperator()) + I_su2 = qeye(4, type = SuperOperator(), dims = 2) @test isunitary(I_op1) == true @test isunitary(I_op2) == true @test isunitary(I_su1) == false @@ -334,8 +334,8 @@ @test (I_op2 == I_su2) == false @test (I_su1 == I_su2) == true @test_throws DimensionMismatch qeye(4, dims = 2) - @test_throws DimensionMismatch qeye(2, type = SuperOperator) - @test_throws DimensionMismatch qeye(4, type = SuperOperator, dims = (2, 2)) + @test_throws DimensionMismatch qeye(2, type = SuperOperator()) + @test_throws DimensionMismatch qeye(4, type = SuperOperator(), dims = (2, 2)) end @testset "superoperators" begin From 5227e968b5c5b2c830ac8ec7da4dc0caa848195e Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 4 May 2025 04:58:41 +0900 Subject: [PATCH 261/329] [Doc] fix typo (#458) --- docs/src/users_guide/time_evolution/mesolve.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/src/users_guide/time_evolution/mesolve.md b/docs/src/users_guide/time_evolution/mesolve.md index 9ad924b96..9537d3bf8 100644 --- a/docs/src/users_guide/time_evolution/mesolve.md +++ b/docs/src/users_guide/time_evolution/mesolve.md @@ -31,17 +31,16 @@ In `QuantumToolbox`, given a Hamiltonian, we can calculate the unitary (non-diss ```@example mesolve H = 0.5 * sigmax() -state0 = basis(2, 0) # state vector -state0 = ket2dm(basis(2, 0)) # density matrix +state0 = basis(2, 0) # state vector tlist = LinRange(0.0, 10.0, 20) sol = mesolve(H, state0, tlist, e_ops = [sigmaz()]) ``` -!!! note "Type of initial state" - The initial state `state0` here can be given as a state vector ``|\psi(0)\rangle`` (in the type of [`Ket`](@ref)) or a density matrix ``\hat{\rho}(0)`` (in the type of [`Operator`](@ref)). If it is given as a [`Ket`](@ref), it will be transformed to density matrix ``\hat{\rho}(0) = |\psi(0)\rangle\langle\psi(0)|`` internally in [`mesolve`](@ref). +!!! note "Use sesolve for improved efficiency" + Here, if the Hamiltonian `H` is given as an [`Operator`](@ref), and the initial state `state0` is given as a state vector ``|\psi(0)\rangle`` (in the type of [`Ket`](@ref)), it will automatically call [`sesolve`](@ref) for improved efficiency. -The function returns [`TimeEvolutionSol`](@ref), as described in the previous section [Time Evolution Solutions](@ref doc-TE:Time-Evolution-Solutions). The stored `states` will always be in the type of [`Operator`](@ref) (density matrix). +The function returns [`TimeEvolutionSol`](@ref), as described in the previous section [Time Evolution Solutions](@ref doc-TE:Time-Evolution-Solutions). ```@example mesolve sol.states @@ -53,6 +52,7 @@ One can also specify `e_ops` and `saveat` separately: ```@example mesolve tlist = [0, 5, 10] +state0 = ket2dm(basis(2, 0)) # density matrix sol = mesolve(H, state0, tlist, e_ops = [sigmay()], saveat = tlist) ``` @@ -64,6 +64,8 @@ sol.expect sol.states ``` +Note that when the initial state `state0` is given as a density matrix ``|\psi(0)\rangle\langle\psi(0)|`` (in the type of [`Operator`](@ref)), the stored `states` will also be in the type of [`Operator`](@ref) (density matrix). + ## [The Lindblad master equation](@id doc-TE:The-Lindblad-master-equation) The standard approach for deriving the equations of motion for a system interacting with its environment is to expand the scope of the system to include the environment. The combined quantum system is then closed, and its evolution is also governed by the von Neumann equation From 243bff734b1e13aed78847ba32a778b924c9dc8d Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 3 May 2025 22:02:21 +0200 Subject: [PATCH 262/329] Bump to v0.31.0 (#459) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d949e3651..d4a8cfd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.31.0] +Release date: 2025-05-03 + - Return `sesolve` when `mesolve` allows it. ([#455]) - Simplify structure of `QuantumObjectType`s. ([#456]) @@ -152,6 +155,7 @@ Release date: 2024-11-13 [v0.29.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.1 [v0.30.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.0 [v0.30.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.1 +[v0.31.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index d51086d35..41b15d35c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.30.1" +version = "0.31.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From e2b34f1d9f464c5f592a3cd4b237cdf7511109b2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 5 May 2025 18:28:22 +0900 Subject: [PATCH 263/329] Introduce `QuantumToolbox.settings` and `auto_tidyup` (#460) --- CHANGELOG.md | 3 +++ docs/make.jl | 1 + docs/src/resources/api.md | 1 + docs/src/users_guide/settings.md | 36 +++++++++++++++++++++++++++ src/QuantumToolbox.jl | 1 + src/qobj/arithmetic_and_attributes.jl | 17 +++++++------ src/qobj/eigsolve.jl | 17 +++++++++---- src/settings.jl | 36 +++++++++++++++++++++++++++ 8 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 docs/src/users_guide/settings.md create mode 100644 src/settings.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a8cfd7b..0cb957438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Introduce `QuantumToolbox.settings` and `auto_tidyup`. ([#460]) + ## [v0.31.0] Release date: 2025-05-03 @@ -221,3 +223,4 @@ Release date: 2024-11-13 [#453]: https://github.com/qutip/QuantumToolbox.jl/issues/453 [#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 [#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456 +[#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460 diff --git a/docs/make.jl b/docs/make.jl index 21b051f04..218cf9653 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -64,6 +64,7 @@ const PAGES = [ "Hierarchical Equations of Motion" => "users_guide/HEOM.md", "Solving for Steady-State Solutions" => "users_guide/steadystate.md", "Two-time correlation functions" => "users_guide/two_time_corr_func.md", + "QuantumToolbox Settings" => "users_guide/settings.md", "Extensions" => [ "users_guide/extensions/cuda.md", "users_guide/extensions/cairomakie.md", diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index b9df5dbd9..0358ceb1d 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -299,6 +299,7 @@ AbstractLinearMap ## [Utility functions](@id doc-API:Utility-functions) ```@docs +QuantumToolbox.settings QuantumToolbox.versioninfo QuantumToolbox.about gaussian diff --git a/docs/src/users_guide/settings.md b/docs/src/users_guide/settings.md new file mode 100644 index 000000000..be8d914e9 --- /dev/null +++ b/docs/src/users_guide/settings.md @@ -0,0 +1,36 @@ +# [QuantumToolbox Settings](@id doc:QuantumToolbox-Settings) + +In this section, we introduce the default global settings used throughout the package and show how to modify them. + +All settings are stored in [`QuantumToolbox.settings`](@ref). + +!!! warning "Differences from QuTiP" + Due to the differences in programming languages, solving algorithms, and many other reasons, these global settings (including their default values and usage) may be very different from those in `Python QuTiP`. + +## List of settings + +Here, we list out each setting along with the specific functions that will use it. + +- `tidyup_tol::Float64 = 1e-14` : tolerance for [`tidyup`](@ref) and [`tidyup!`](@ref). +- `auto_tidyup::Bool = true` : Automatically tidyup during the following situations: + * Solving for eigenstates, including [`eigenstates`](@ref), [`eigsolve`](@ref), and [`eigsolve_al`](@ref). +- (to be announced) + +## Change default settings + +First, we can check the current [`QuantumToolbox.settings`](@ref): + +```@example settings +using QuantumToolbox + +QuantumToolbox.settings +``` + +Next, one can overwrite the default settings by + +```@example settings +QuantumToolbox.settings.tidyup_tol = 1e-10 +QuantumToolbox.settings.auto_tidyup = false + +QuantumToolbox.settings +``` \ No newline at end of file diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 409bcfa54..40bfc245f 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -75,6 +75,7 @@ export permute export cache_operator, iscached, isconstant # Utility +include("settings.jl") include("utilities.jl") include("versioninfo.jl") include("progress_bar.jl") diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 8eecc5acd..957ef786d 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -629,27 +629,28 @@ purity(ρ::QuantumObject{ObjType}) where {ObjType<:Union{Ket,Bra}} = sum(abs2, purity(ρ::QuantumObject{Operator}) = real(tr(ρ.data^2)) @doc raw""" - tidyup(A::QuantumObject, tol::Real=1e-14) + tidyup(A::QuantumObject, tol::Real=settings.tidyup_tol) Given a [`QuantumObject`](@ref) `A`, check the real and imaginary parts of each element separately. Remove the real or imaginary value if its absolute value is less than `tol`. """ -tidyup(A::QuantumObject, tol::T = 1e-14) where {T<:Real} = QuantumObject(tidyup(A.data, tol), A.type, A.dimensions) -tidyup(A::AbstractArray, tol::T2 = 1e-14) where {T2<:Real} = tidyup!(copy(A), tol) +tidyup(A::QuantumObject, tol::T = settings.tidyup_tol) where {T<:Real} = + QuantumObject(tidyup(A.data, tol), A.type, A.dimensions) +tidyup(A::AbstractArray, tol::T2 = settings.tidyup_tol) where {T2<:Real} = tidyup!(copy(A), tol) @doc raw""" - tidyup!(A::QuantumObject, tol::Real=1e-14) + tidyup!(A::QuantumObject, tol::Real=settings.tidyup_tol) Given a [`QuantumObject`](@ref) `A`, check the real and imaginary parts of each element separately. Remove the real or imaginary value if its absolute value is less than `tol`. Note that this function is an in-place version of [`tidyup`](@ref). """ -tidyup!(A::QuantumObject, tol::T = 1e-14) where {T<:Real} = (tidyup!(A.data, tol); A) -function tidyup!(A::AbstractSparseArray, tol::T2 = 1e-14) where {T2<:Real} +tidyup!(A::QuantumObject, tol::T = settings.tidyup_tol) where {T<:Real} = (tidyup!(A.data, tol); A) +function tidyup!(A::AbstractSparseArray, tol::T2 = settings.tidyup_tol) where {T2<:Real} tidyup!(nonzeros(A), tol) # tidyup A.nzval in-place (also support for CUDA sparse arrays) return dropzeros!(A) end -tidyup!(A::AbstractArray{T}, tol::T2 = 1e-14) where {T<:Real,T2<:Real} = @. A = T(abs(A) > tol) * A -tidyup!(A::AbstractArray{T}, tol::T2 = 1e-14) where {T,T2<:Real} = +tidyup!(A::AbstractArray{T}, tol::T2 = settings.tidyup_tol) where {T<:Real,T2<:Real} = @. A = T(abs(A) > tol) * A +tidyup!(A::AbstractArray{T}, tol::T2 = settings.tidyup_tol) where {T,T2<:Real} = @. A = T(abs(real(A)) > tol) * real(A) + 1im * T(abs(imag(A)) > tol) * imag(A) @doc raw""" diff --git a/src/qobj/eigsolve.jl b/src/qobj/eigsolve.jl index 1d8f256e3..856afa956 100644 --- a/src/qobj/eigsolve.jl +++ b/src/qobj/eigsolve.jl @@ -249,6 +249,7 @@ function _eigsolve( end mul!(cache1, Vₘ, M(Uₘ * VR)) vecs = cache1[:, 1:k] + settings.auto_tidyup && tidyup!(vecs) return EigsolveResult(vals, vecs, type, dimensions, iter, numops, (iter < maxiter)) end @@ -349,7 +350,10 @@ function eigsolve( vals = @. (1 + sigma * res.values) / res.values end - return EigsolveResult(vals, res.vectors, res.type, res.dimensions, res.iter, res.numops, res.converged) + vecs = res.vectors + settings.auto_tidyup && tidyup!(vecs) + + return EigsolveResult(vals, vecs, res.type, res.dimensions, res.iter, res.numops, res.converged) end @doc raw""" @@ -430,6 +434,8 @@ function eigsolve_al( @. vecs[:, i] = vec * exp(-1im * angle(vec[1])) end + settings.auto_tidyup && tidyup!(vecs) + return EigsolveResult(vals, vecs, res.type, res.dimensions, res.iter, res.numops, res.converged) end @@ -457,11 +463,11 @@ values: 2.8569700138728056 + 0.0im vectors: 5×5 Matrix{ComplexF64}: - 0.106101+0.0im -0.471249-0.0im … 0.471249-0.0im 0.106101-0.0im - -0.303127-0.0im 0.638838+0.0im 0.638838+0.0im 0.303127-0.0im - 0.537348+0.0im -0.279149-0.0im 0.279149-0.0im 0.537348-0.0im + 0.106101+0.0im -0.471249-0.0im … 0.471249+0.0im 0.106101+0.0im + -0.303127-0.0im 0.638838+0.0im 0.638838+0.0im 0.303127+0.0im + 0.537348+0.0im -0.279149-0.0im 0.279149+0.0im 0.537348+0.0im -0.638838-0.0im -0.303127-0.0im -0.303127-0.0im 0.638838+0.0im - 0.447214+0.0im 0.447214+0.0im -0.447214-0.0im 0.447214-0.0im + 0.447214+0.0im 0.447214+0.0im -0.447214-0.0im 0.447214+0.0im julia> expect(H, ψ[1]) ≈ E[1] true @@ -473,6 +479,7 @@ function LinearAlgebra.eigen(A::QuantumObject{OpType}; kwargs...) where {OpType< # This fixes a type inference issue. But doesn't work for GPU arrays E::mat2vec(to_dense(MT)) = F.values U::to_dense(MT) = F.vectors + settings.auto_tidyup && tidyup!(U) return EigsolveResult(E, U, A.type, A.dimensions, 0, 0, true) end diff --git a/src/settings.jl b/src/settings.jl new file mode 100644 index 000000000..b9be9b0b2 --- /dev/null +++ b/src/settings.jl @@ -0,0 +1,36 @@ +Base.@kwdef mutable struct Settings + tidyup_tol::Float64 = 1e-14 + auto_tidyup::Bool = true +end + +function Base.show(io::IO, s::Settings) + println(io, "QuantumToolbox.jl Settings") + println(io, "--------------------------") + map(x -> println(io, "$x = ", getfield(s, x)), fieldnames(Settings)) + return nothing +end + +@doc raw""" + QuantumToolbox.settings + +Contains all the default global settings of QuantumToolbox.jl. + +# List of settings + +- `tidyup_tol::Float64 = 1e-14` : tolerance for [`tidyup`](@ref) and [`tidyup!`](@ref). +- `auto_tidyup::Bool = true` : Automatically tidyup. + +For detailed explanation of each settings, see our documentation [here](https://qutip.org/QuantumToolbox.jl/stable/users_guide/settings). + +# Change default settings + +One can overwrite the default global settings by + +```julia +using QuantumToolbox + +QuantumToolbox.settings.tidyup_tol = 1e-10 +QuantumToolbox.settings.auto_tidyup = false +``` +""" +const settings = Settings() From c732008af114df89e22ce7eb6b740b253bc3be55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 11:28:44 +0200 Subject: [PATCH 264/329] Bump crate-ci/typos from 1.31.1 to 1.32.0 (#465) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 1f24114b0..12a54edb4 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.31.1 \ No newline at end of file + uses: crate-ci/typos@v1.32.0 \ No newline at end of file From e4ff0f7dd88f7aa911f0796383a86d326c85ceea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 10 May 2025 18:24:34 +0900 Subject: [PATCH 265/329] CompatHelper: bump compat for SciMLOperators to 0.4, (keep existing compat) (#466) --- Project.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 41b15d35c..06adc15ec 100644 --- a/Project.toml +++ b/Project.toml @@ -33,10 +33,10 @@ KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [extensions] -QuantumToolboxMakieExt = "Makie" -QuantumToolboxChainRulesCoreExt = "ChainRulesCore" QuantumToolboxCUDAExt = "CUDA" +QuantumToolboxChainRulesCoreExt = "ChainRulesCore" QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] +QuantumToolboxMakieExt = "Makie" [compat] ArrayInterface = "6, 7" @@ -59,7 +59,7 @@ OrdinaryDiffEqTsit5 = "1" Pkg = "1" Random = "1" SciMLBase = "2" -SciMLOperators = "0.3" +SciMLOperators = "0.3, 0.4" SparseArrays = "1" SpecialFunctions = "2" StaticArraysCore = "1" From d0dcc7808d8f0a87ad0c478cc6ac3dc02df907ee Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 16 May 2025 19:38:32 +0800 Subject: [PATCH 266/329] [Doc] Update to `DocumenterVitepress.jl v0.2` (#467) --- .github/workflows/documentation.yml | 3 + Makefile | 1 - docs/Project.toml | 3 + docs/README.md | 22 +- docs/make.jl | 14 +- docs/package.json | 6 +- docs/src/.vitepress/config.mts | 126 ++++--- docs/src/.vitepress/theme/VersionPicker.vue | 142 -------- docs/src/.vitepress/theme/index.ts | 21 -- docs/src/.vitepress/theme/style.css | 346 ++++++++------------ docs/src/{public => assets}/favicon.ico | Bin docs/src/{public => assets}/logo.png | Bin docs/src/index.md | 2 +- docs/src/resources/bibliography.md | 2 + docs/src/resources/contributing.md | 10 +- 15 files changed, 240 insertions(+), 458 deletions(-) delete mode 100644 docs/src/.vitepress/theme/VersionPicker.vue delete mode 100644 docs/src/.vitepress/theme/index.ts rename docs/src/{public => assets}/favicon.ico (100%) rename docs/src/{public => assets}/logo.png (100%) mode change 100755 => 100644 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3bc705b0e..222377e4b 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -21,6 +21,9 @@ on: - synchronize - ready_for_review + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: write diff --git a/Makefile b/Makefile index 4fd24cfcb..a7d4b1c6d 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,6 @@ docs: ${JULIA} --project=docs docs/make.jl vitepress: - npm --prefix docs i npm --prefix docs run docs:dev all: setup format changelog test docs vitepress diff --git a/docs/Project.toml b/docs/Project.toml index 078f2ad18..4ca77f49f 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,3 +6,6 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" + +[compat] +DocumenterVitepress = "0.2" \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 488aa85fb..c8d7ae7e8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,20 +3,21 @@ ## Working Directory All the commands should be run under the root folder of the package: `/path/to/QuantumToolbox.jl/` -The document pages will be generated in the directory: `/path/to/QuantumToolbox.jl/docs/build/` (which is ignored by git). +The document pages will be generated in the directory: `/path/to/QuantumToolbox.jl/docs/build/1/` (which is ignored by git). ## Method 1: Run with `make` command Run the following command to instantiate and build the documentation: +> [!NOTE] +> You need to install `Node.js` and `npm` first. ```shell make docs ``` -Run the following command to start Vitepress site of documentation: -> [!NOTE] -> You need to install `Node.js` and `npm` first. +Run the following command to start a local Vitepress site: ```shell make vitepress ``` +This will start a local Vitepress site of documentation at [http://localhost:5173](http://localhost:5173) in your computer. ## Method 2: Run commands manually @@ -29,20 +30,15 @@ julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.in ### Build Documentation Run the following command: -```shell -julia --project=docs docs/make.jl -``` - -### Start a local Vitepress site > [!NOTE] > You need to install `Node.js` and `npm` first. - -Install `npm` dependencies: ```shell -npm --prefix docs i +julia --project=docs docs/make.jl ``` +### Start a local Vitepress site Run the following command: ```shell npm --prefix docs run docs:dev -``` \ No newline at end of file +``` +This will start a local Vitepress site of documentation at [http://localhost:5173](http://localhost:5173) in your computer. \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 218cf9653..d07b3e2dc 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -46,8 +46,8 @@ const PAGES = [ ], "Users Guide" => [ "Basic Operations on Quantum Objects" => [ - "users_guide/QuantumObject/QuantumObject.md", - "users_guide/QuantumObject/QuantumObject_functions.md", + "Quantum Objects (Qobj)" => "users_guide/QuantumObject/QuantumObject.md", + "Functions operating on Qobj" => "users_guide/QuantumObject/QuantumObject_functions.md", ], "Manipulating States and Operators" => "users_guide/states_and_operators.md", "Tensor Products and Partial Traces" => "users_guide/tensor.md", @@ -66,8 +66,8 @@ const PAGES = [ "Two-time correlation functions" => "users_guide/two_time_corr_func.md", "QuantumToolbox Settings" => "users_guide/settings.md", "Extensions" => [ - "users_guide/extensions/cuda.md", - "users_guide/extensions/cairomakie.md", + "Extension for CUDA.jl" => "users_guide/extensions/cuda.md", + "Extension for the Makie.jl ecosystem" => "users_guide/extensions/cairomakie.md", ], ], "Resources" => [ @@ -90,15 +90,17 @@ makedocs(; pages = PAGES, format = DocumenterVitepress.MarkdownVitepress( repo = "github.com/qutip/QuantumToolbox.jl", + devbranch = "main", + devurl = "dev", ), draft = DRAFT, doctest = DOCTEST, plugins = [bib], ) -deploydocs(; +DocumenterVitepress.deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", - target = "build", # this is where Vitepress stores its output + target = joinpath(@__DIR__, "build"), devbranch = "main", branch = "gh-pages", push_preview = true, diff --git a/docs/package.json b/docs/package.json index 5633b4976..0ed871854 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,11 +5,11 @@ "docs:preview": "vitepress preview build/.documenter" }, "dependencies": { - "@shikijs/transformers": "^1.1.7", + "@nolebase/vitepress-plugin-enhanced-readabilities": "^2.14.0", "markdown-it": "^14.1.0", "markdown-it-footnote": "^4.0.0", "markdown-it-mathjax3": "^4.3.2", - "vitepress": "^1.1.4", - "vitepress-plugin-tabs": "^0.5.0" + "vitepress": "^1.6.3", + "vitepress-plugin-tabs": "^0.6.0" } } diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 9a0732bb5..18e32b4b5 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -2,9 +2,16 @@ import { defineConfig } from 'vitepress' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import mathjax3 from "markdown-it-mathjax3"; import footnote from "markdown-it-footnote"; +import path from 'path' + +function getBaseRepository(base: string): string { + if (!base || base === '/') return '/'; + const parts = base.split('/').filter(Boolean); + return parts.length > 0 ? `/${parts[0]}/` : '/'; +} const baseTemp = { - base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! } const navTemp = { @@ -22,55 +29,78 @@ const nav = [ // https://vitepress.dev/reference/site-config export default defineConfig({ - base: baseTemp.base, - title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', - description: 'REPLACE_ME_DOCUMENTER_VITEPRESS', - lastUpdated: true, - cleanUrls: true, - outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... - head: [ - ['link', { rel: 'icon', href: '/QuantumToolbox.jl/favicon.ico' }], - ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], - ['script', {src: `/QuantumToolbox.jl/versions.js`}], - ['script', {src: `${baseTemp.base}siteinfo.js`}] - ], - ignoreDeadLinks: true, - - markdown: { - math: true, + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! + title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + description: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + lastUpdated: true, + cleanUrls: true, + outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... + head: [ + ['link', { rel: 'icon', href: '/QuantumToolbox.jl/favicon.ico' }], + ['script', {src: `${getBaseRepository(baseTemp.base)}versions.js`}], + // ['script', {src: '/versions.js'], for custom domains, I guess if deploy_url is available. + ['script', {src: `${baseTemp.base}siteinfo.js`}] + ], + + vite: { + define: { + __DEPLOY_ABSPATH__: JSON.stringify('REPLACE_ME_DOCUMENTER_VITEPRESS_DEPLOY_ABSPATH'), + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '../components') + } + }, + optimizeDeps: { + exclude: [ + '@nolebase/vitepress-plugin-enhanced-readabilities/client', + 'vitepress', + '@nolebase/ui', + ], + }, + ssr: { + noExternal: [ + // If there are other packages that need to be processed by Vite, you can add them here. + '@nolebase/vitepress-plugin-enhanced-readabilities', + '@nolebase/ui', + ], + }, + }, + markdown: { + math: true, - // options for @mdit-vue/plugin-toc - // https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-toc#options - toc: { level: [2, 3, 4] }, // for API page, triggered by: [[toc]] + // options for @mdit-vue/plugin-toc + // https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-toc#options + toc: { level: [2, 3, 4] }, // for API page, triggered by: [[toc]] - config(md) { - md.use(tabsMarkdownPlugin), - md.use(mathjax3), - md.use(footnote) - }, - theme: { - light: "github-light", - dark: "github-dark" - } + config(md) { + md.use(tabsMarkdownPlugin), + md.use(mathjax3), + md.use(footnote) }, - themeConfig: { - outline: 'deep', - logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS', - search: { - provider: 'local', - options: { - detailedView: true - } - }, - nav, - sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', - editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS', - socialLinks: [ - { icon: 'github', link: 'REPLACE_ME_DOCUMENTER_VITEPRESS' } - ], - footer: { - message: 'Made with Documenter.jl, VitePress and DocumenterVitepress.jl
Released under the BSD 3-Clause License. Powered by the Julia Programming Language.
', - copyright: `© Copyright ${new Date().getUTCFullYear()} QuTiP.org.` - } + theme: { + light: "github-light", + dark: "github-dark" } + }, + themeConfig: { + outline: 'deep', + logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + search: { + provider: 'local', + options: { + detailedView: true + } + }, + nav, + sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + socialLinks: [ + { icon: 'github', link: 'REPLACE_ME_DOCUMENTER_VITEPRESS' } + ], + footer: { + message: 'Made with Documenter.jl, VitePress and DocumenterVitepress.jl
Released under the BSD 3-Clause License. Powered by the Julia Programming Language.
', + copyright: `© Copyright ${new Date().getUTCFullYear()} QuTiP.org.` + } + } }) diff --git a/docs/src/.vitepress/theme/VersionPicker.vue b/docs/src/.vitepress/theme/VersionPicker.vue deleted file mode 100644 index a38c51a5a..000000000 --- a/docs/src/.vitepress/theme/VersionPicker.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - diff --git a/docs/src/.vitepress/theme/index.ts b/docs/src/.vitepress/theme/index.ts deleted file mode 100644 index ae0c3d3a8..000000000 --- a/docs/src/.vitepress/theme/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -// .vitepress/theme/index.ts -import { h } from 'vue' -import type { Theme } from 'vitepress' -import DefaultTheme from 'vitepress/theme' -import VersionPicker from "./VersionPicker.vue" - -import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' -import './style.css' - -export default { - extends: DefaultTheme, - Layout() { - return h(DefaultTheme.Layout, null, { - // https://vitepress.dev/guide/extending-default-theme#layout-slots - }) - }, - enhanceApp({ app, router, siteData }) { - enhanceAppWithTabs(app); - app.component('VersionPicker', VersionPicker); - } -} satisfies Theme diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css index a520b4a11..72abe7357 100644 --- a/docs/src/.vitepress/theme/style.css +++ b/docs/src/.vitepress/theme/style.css @@ -1,269 +1,179 @@ /* Customize default theme styling by overriding CSS variables: -https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css - */ - -/* Layouts */ - -/* - :root { - --vp-layout-max-width: 1440px; -} */ +https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */ +/* Example */ +/* https://github.com/vuejs/vitepress/blob/main/template/.vitepress/theme/style.css */ .VPHero .clip { - white-space: pre; - max-width: 500px; + white-space: pre; + max-width: 600px; } /* Fonts */ - @font-face { - font-family: JuliaMono-Regular; - src: url("https://cdn.jsdelivr.net/gh/cormullion/juliamono/webfonts/JuliaMono-Regular.woff2"); + font-family: JuliaMono-Regular; + src: url("https://cdn.jsdelivr.net/gh/cormullion/juliamono/webfonts/JuliaMono-Regular.woff2"); } :root { - /* Typography */ - --vp-font-family-base: "Barlow", "Inter var experimental", "Inter var", - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, - Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - - /* Code Snippet font */ - --vp-font-family-mono: JuliaMono-Regular, monospace; +/* Typography */ +--vp-font-family-base: "Barlow", "Inter var experimental", "Inter var", + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; +/* Code Snippet font */ +--vp-font-family-mono: JuliaMono-Regular, monospace; } -/* - Disable contextual alternates (kind of like ligatures but different) in monospace, - which turns `/>` to an up arrow and `|>` (the Julia pipe symbol) to an up arrow as well. - This is pretty bad for Julia folks reading even though copy+paste retains the same text. - */ -/* Target elements with class 'mono' */ +/* Disable contextual alternates (kind of like ligatures but different) in monospace, + which turns `/>` to an up arrow and `|>` (the Julia pipe symbol) to an up arrow as well. */ .mono-no-substitutions { - font-family: "JuliaMono-Light", monospace; - font-feature-settings: "calt" off; +font-family: "JuliaMono-Regular", monospace; +font-feature-settings: "calt" off; } -/* Alternatively, you can use the following if you prefer: */ .mono-no-substitutions-alt { - font-family: "JuliaMono-Light", monospace; - font-variant-ligatures: none; +font-family: "JuliaMono-Regular", monospace; +font-variant-ligatures: none; } -/* If you want to apply this globally to all monospace text: */ -pre, -code { - font-family: "JuliaMono-Light", monospace; - font-feature-settings: "calt" off; +pre, code { +font-family: "JuliaMono-Regular", monospace; +font-feature-settings: "calt" off; } /* Colors */ - :root { - --julia-blue: #4063D8; - --julia-purple: #9558B2; - --julia-red: #CB3C33; - --julia-green: #389826; - - --vp-c-brand: #389826; - --vp-c-brand-light: #3dd027; - --vp-c-brand-lighter: #9499ff; - --vp-c-brand-lightest: #bcc0ff; - --vp-c-brand-dark: #535bf2; - --vp-c-brand-darker: #454ce1; - --vp-c-brand-dimm: #212425; -} - -/* Component: Button */ - + --julia-blue: #4063D8; + --julia-purple: #9558B2; + --julia-red: #CB3C33; + --julia-green: #389826; + + --vp-c-brand: #0087d7; + --vp-c-brand-1: #0890df; + --vp-c-brand-2: #0599ef; + --vp-c-brand-3: #0c9ff4; + --vp-c-brand-light: #0087d7; + --vp-c-brand-dark: #5fd7ff; + --vp-c-brand-dimm: #212425; + + /* Greens */ + --vp-dark-green: #155f3e; /* Main accent green */ + --vp-dark-green-dark: #2b855c; + --vp-dark-green-light: #42d392; + --vp-dark-green-lighter: #35eb9a; + /* Complementary Colors */ + --vp-dark-gray: #1e1e1e; + --vp-dark-gray-soft: #2a2a2a; + --vp-dark-gray-mute: #242424; + --vp-light-gray: #d1d5db; + --vp-tip-bg: rgb(254, 254, 254); + + /* Text Colors */ + --vp-dark-text: #e5e5e5; /* Primary text color */ + --vp-dark-subtext: #c1c1c1; /* Subtle text */ + --vp-source-text: #e5e5e5; + /* custom tip */ + --vp-custom-block-tip-border: var(--vp-c-brand-light); + --vp-custom-block-tip-bg: var(--vp-tip-bg); +} + + /* Component: Button */ :root { - --vp-button-brand-border: var(--vp-c-brand-light); - --vp-button-brand-text: var(--vp-c-white); - --vp-button-brand-bg: var(--vp-c-brand); - --vp-button-brand-hover-border: var(--vp-c-brand-light); - --vp-button-brand-hover-text: var(--vp-c-white); - --vp-button-brand-hover-bg: var(--vp-c-brand-light); - --vp-button-brand-active-border: var(--vp-c-brand-light); - --vp-button-brand-active-text: var(--vp-c-white); - --vp-button-brand-active-bg: var(--vp-button-brand-bg); + --vp-button-brand-border: var(--vp-light-gray); + --vp-button-brand-bg: var(--vp-c-brand-light); + --vp-button-brand-hover-border: var(--vp-c-bg-alt); + --vp-button-brand-hover-bg: var(--julia-blue); } /* Component: Home */ - :root { - --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: -webkit-linear-gradient(120deg, - #9558B2 30%, - #CB3C33); - - --vp-home-hero-image-background-image: none; /* remove the blur background */ - /* (default setting) - --vp-home-hero-image-background-image: linear-gradient(-45deg, - #9558B2 30%, - #389826 30%, - #CB3C33); - */ - --vp-home-hero-image-filter: blur(40px); -} - -@media (min-width: 640px) { - :root { - --vp-home-hero-image-filter: blur(56px); - } -} - -@media (min-width: 960px) { - :root { - --vp-home-hero-image-filter: blur(72px); - } + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #9558B2 30%, + #CB3C33 + ); + + --vp-home-hero-image-background-image: none; /* remove the blur background */ + /* (default setting) + --vp-home-hero-image-background-image: linear-gradient( + -145deg, + #9558b282 30%, + #3798269a 30%, + #cb3d33e3 + ); + */ + --vp-home-hero-image-filter: blur(40px); } -/* Component: Custom Block */ - +/* Hero Section */ :root.dark { - --vp-custom-block-tip-border: var(--vp-c-brand); - --vp-custom-block-tip-text: var(--vp-c-brand-lightest); - --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); - - /* // Tweak the color palette for blacks and dark grays */ - --vp-c-black: hsl(220 20% 9%); - --vp-c-black-pure: hsl(220, 24%, 4%); - --vp-c-black-soft: hsl(220 16% 13%); - --vp-c-black-mute: hsl(220 14% 17%); - --vp-c-gray: hsl(220 8% 56%); - --vp-c-gray-dark-1: hsl(220 10% 39%); - --vp-c-gray-dark-2: hsl(220 12% 28%); - --vp-c-gray-dark-3: hsl(220 12% 23%); - --vp-c-gray-dark-4: hsl(220 14% 17%); - --vp-c-gray-dark-5: hsl(220 16% 13%); - - /* // Backgrounds */ - /* --vp-c-bg: hsl(240, 2%, 11%); */ - --vp-custom-block-info-bg: hsl(220 14% 17%); - /* --vp-c-gutter: hsl(220 20% 9%); - - --vp-c-bg-alt: hsl(220 20% 9%); - --vp-c-bg-soft: hsl(220 14% 17%); - --vp-c-bg-mute: hsl(220 12% 23%); - */ -} - -/* Component: Algolia */ - -.DocSearch { - --docsearch-primary-color: var(--vp-c-brand) !important; -} - -/* Component: MathJax */ - -mjx-container>svg { - display: block; - margin: auto; -} - -mjx-container { - padding: 0.5rem 0; -} - -mjx-container { - display: inline; - margin: auto 2px -2px; + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #9558B2 30%, + #CB3C33 + ); + --vp-home-hero-image-background-image: none; /* remove the blur background */ + /* (default setting) + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + var(--vp-dark-green) 30%, + var(--vp-dark-green-light), + var(--vp-dark-gray) 30% + ); + */ + --vp-home-hero-image-filter: blur(56px); } -mjx-container>svg { - margin: auto; - display: inline-block; +:root.dark { + /* custom tip */ + --vp-custom-block-tip-border: var(--vp-dark-green-dark); + --vp-custom-block-tip-text: var(--vp-dark-subtext); + --vp-custom-block-tip-bg: var(--vp-dark-gray-mute); } /** - * Colors links - * -------------------------------------------------------------------------- */ - -:root { - --vp-c-brand-1: #CB3C33; - --vp-c-brand-2: #CB3C33; - --vp-c-brand-3: #CB3C33; - --vp-c-sponsor: #ca2971; - --vitest-c-sponsor-hover: #c13071; -} + * Colors links + * -------------------------------------------------------------------------- */ .dark { - --vp-c-brand-1: #91dd33; - --vp-c-brand-2: #91dd33; - --vp-c-brand-3: #91dd33; - --vp-c-sponsor: #91dd33; - --vitest-c-sponsor-hover: #e51370; -} - -/** - * Change images from light to dark theme - * -------------------------------------------------------------------------- */ - -:root:not(.dark) .dark-only { - display: none; -} - -:root:is(.dark) .light-only { - display: none; + --vp-c-brand: var(--vp-dark-green-light); + --vp-button-brand-border: var(--vp-dark-green-lighter); + --vp-button-brand-bg: var(--vp-dark-green); + --vp-c-brand-1: var(--vp-dark-green-light); + --vp-c-brand-2: var(--vp-dark-green-lighter); + --vp-c-brand-3: var(--vp-dark-green); } -/* https://bddxg.top/article/note/vitepress优化/一些细节上的优化.html#文档页面调整-加宽 */ - -.VPDoc.has-aside .content-container { - max-width: 100% !important; -} - -.aside { - max-width: 200px !important; - padding-left: 0 !important; -} - -.VPDoc { - padding-top: 15px !important; - padding-left: 5px !important; - +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } } -/* This one does the right menu */ - -.VPDocOutlineItem li { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - max-width: 200px; +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } } +/* Component: MathJax */ -.VPNavBar .title { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; +mjx-container > svg { + display: block; + margin: auto; } -@media (max-width: 960px) { - .VPDoc { - padding-left: 25px !important; - } +mjx-container { + padding: 0.5rem 0; } -/* This one does the left menu */ - -/* .VPSidebarItem .VPLink p { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - max-width: 200px; - } */ - - -/* Component: Docstring Custom Block */ - -.jldocstring.custom-block { - border: 1px solid var(--vp-c-gray-2); - color: var(--vp-c-text-1) +mjx-container { + display: inline; + margin: auto 2px -2px; } -.jldocstring.custom-block summary { - font-weight: 700; - cursor: pointer; - user-select: none; - margin: 0 0 8px; +mjx-container > svg { + margin: auto; + display: inline-block; } \ No newline at end of file diff --git a/docs/src/public/favicon.ico b/docs/src/assets/favicon.ico similarity index 100% rename from docs/src/public/favicon.ico rename to docs/src/assets/favicon.ico diff --git a/docs/src/public/logo.png b/docs/src/assets/logo.png old mode 100755 new mode 100644 similarity index 100% rename from docs/src/public/logo.png rename to docs/src/assets/logo.png diff --git a/docs/src/index.md b/docs/src/index.md index 6fa526ab8..345b70bdb 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,7 +5,7 @@ layout: home hero: name: "QuantumToolbox.jl" - tagline: A pure Julia framework designed for High-performance quantum physics simulations + tagline: A pure Julia framework designed for high-performance quantum physics simulations image: src: /logo.png alt: QuantumToolbox diff --git a/docs/src/resources/bibliography.md b/docs/src/resources/bibliography.md index 2c49f8218..bbb721388 100644 --- a/docs/src/resources/bibliography.md +++ b/docs/src/resources/bibliography.md @@ -1,3 +1,5 @@ +# [Bibliography](@id doc-Bibliography) + ```@meta CurrentModule = QuantumToolbox ``` diff --git a/docs/src/resources/contributing.md b/docs/src/resources/contributing.md index 0ff15b278..be2d20016 100644 --- a/docs/src/resources/contributing.md +++ b/docs/src/resources/contributing.md @@ -55,10 +55,13 @@ make format All the documentation source files [in markdown (`.md`) format] and build scripts should be located in the folder `docs` in the repository. -The document pages will be generated in the folder `docs/build` (which is ignored by `git`) in the repository. +The document pages will be generated in the folder `docs/build/1/` (which is ignored by `git`) in the repository. To instantiate and build the documentation, run the following command under the *__root directory of the repository__* you are working on: +!!! note "Requirements" + You need to install `Node.js` and `npm` first. + ```shell make docs ``` @@ -67,14 +70,11 @@ This command will automatically rebuild `Julia` and run the script located in `d To read the documentation in a browser, you can run the following command: -!!! note "Requirements" - You need to install `Node.js` and `npm` first. - ```shell make vitepress ``` -This will start a local Vitepress site of documentation at `http://localhost:5173/QuantumToolbox.jl/` in your computer. +This will start a local Vitepress site of documentation at `http://localhost:5173` in your computer. ## [Update ChangeLog](@id doc-Contribute:Update-ChangeLog) From a1d1627acbba053653808e0f5002b3191e80a25a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 16 May 2025 19:45:35 +0800 Subject: [PATCH 267/329] Bump to `v0.31.1` (#469) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb957438..145b17345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.31.1] +Release date: 2025-05-16 + - Introduce `QuantumToolbox.settings` and `auto_tidyup`. ([#460]) ## [v0.31.0] @@ -158,6 +161,7 @@ Release date: 2024-11-13 [v0.30.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.0 [v0.30.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.1 [v0.31.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.0 +[v0.31.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.1 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 06adc15ec..cdff9028e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.31.0" +version = "0.31.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 5e41ed36e8e0895347e2f3bebbe1029ef8dfa231 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 16 May 2025 20:07:48 +0800 Subject: [PATCH 268/329] [no ci] fix logo link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29b10f0dc..fbdc734aa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- QuantumToolbox.jl logo + QuantumToolbox.jl logo
# QuantumToolbox.jl From 9fff0169a3c44fea82219f6ec62c026075b97ac8 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Thu, 29 May 2025 02:56:17 +0200 Subject: [PATCH 269/329] Simplify `QobEvo` creation and time evolution (#477) --- src/qobj/quantum_object_evo.jl | 11 +---------- src/time_evolution/mesolve.jl | 7 +------ src/time_evolution/sesolve.jl | 7 +------ 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index 3e8edf301..f1c4c58dc 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -440,17 +440,8 @@ end _promote_to_scimloperator(data::AbstractMatrix) = MatrixOperator(data) _promote_to_scimloperator(data::AbstractSciMLOperator) = data -# TODO: The following special cases can be simplified after -# https://github.com/SciML/SciMLOperators.jl/pull/264 is merged _promote_to_scimloperator(α::Number, data::AbstractMatrix) = MatrixOperator(α * data) -function _promote_to_scimloperator(α::Number, data::MatrixOperator) - isconstant(data) && return MatrixOperator(α * data.A) - return ScaledOperator(α, data) # Going back to the generic case -end -function _promote_to_scimloperator(α::Number, data::ScaledOperator) - isconstant(data.λ) && return ScaledOperator(α * data.λ, data.L) - return ScaledOperator(data.λ, _promote_to_scimloperator(α, data.L)) # Try to propagate the rule -end +# We still have to define this for AddedOperator, as it is not present in SciMLOperators.jl function _promote_to_scimloperator(α::Number, data::AddedOperator) return AddedOperator(_promote_to_scimloperator.(α, data.ops)) # Try to propagate the rule end diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 980148889..32305cb1b 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -97,12 +97,7 @@ function mesolveProblem( tspan = (tlist[1], tlist[end]) - # TODO: Remove this when https://github.com/SciML/SciMLSensitivity.jl/issues/1181 is fixed - if haskey(kwargs3, :sensealg) - prob = ODEProblem{getVal(inplace)}(L, ρ0, tspan, params; kwargs3...) - else - prob = ODEProblem{getVal(inplace),FullSpecialize}(L, ρ0, tspan, params; kwargs3...) - end + prob = ODEProblem{getVal(inplace),FullSpecialize}(L, ρ0, tspan, params; kwargs3...) return TimeEvolutionProblem(prob, tlist, L_evo.dimensions, (isoperket = Val(isoperket(ψ0)),)) end diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index c24970f75..c5607f231 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -76,12 +76,7 @@ function sesolveProblem( tspan = (tlist[1], tlist[end]) - # TODO: Remove this when https://github.com/SciML/SciMLSensitivity.jl/issues/1181 is fixed - if haskey(kwargs3, :sensealg) - prob = ODEProblem{getVal(inplace)}(U, ψ0, tspan, params; kwargs3...) - else - prob = ODEProblem{getVal(inplace),FullSpecialize}(U, ψ0, tspan, params; kwargs3...) - end + prob = ODEProblem{getVal(inplace),FullSpecialize}(U, ψ0, tspan, params; kwargs3...) return TimeEvolutionProblem(prob, tlist, H_evo.dimensions) end From a965621b17b90a10920afa88deccb932391d12e6 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:24:29 +0800 Subject: [PATCH 270/329] Switch `Core` tests to "Test Item Framework" (#475) --- Project.toml | 7 --- docs/src/resources/contributing.md | 6 +++ src/qobj/quantum_object_base.jl | 12 +++++ test/Project.toml | 14 +++++ test/core-test/block_diagonal_form.jl | 2 +- test/core-test/code-quality/Project.toml | 1 + test/core-test/correlations_and_spectrum.jl | 2 +- test/core-test/dynamical-shifted-fock.jl | 2 +- .../dynamical_fock_dimension_mesolve.jl | 2 +- test/core-test/eigenvalues_and_operators.jl | 2 +- test/core-test/entropy_and_metric.jl | 8 +-- test/core-test/generalized_master_equation.jl | 4 +- test/core-test/low_rank_dynamics.jl | 8 +-- .../negativity_and_partial_transpose.jl | 2 +- test/core-test/progress_bar.jl | 2 +- test/core-test/quantum_objects.jl | 6 ++- test/core-test/quantum_objects_evo.jl | 7 ++- test/core-test/states_and_operators.jl | 6 ++- test/core-test/steady_state.jl | 2 +- test/core-test/time_evolution.jl | 5 +- test/core-test/utilities.jl | 2 +- test/core-test/wigner.jl | 2 +- test/ext-test/cpu/autodiff/Project.toml | 2 +- test/ext-test/cpu/autodiff/zygote.jl | 2 +- test/runtests.jl | 53 +++++++------------ 25 files changed, 96 insertions(+), 65 deletions(-) create mode 100644 test/Project.toml diff --git a/Project.toml b/Project.toml index cdff9028e..3159c0b3a 100644 --- a/Project.toml +++ b/Project.toml @@ -64,11 +64,4 @@ SparseArrays = "1" SpecialFunctions = "2" StaticArraysCore = "1" StochasticDiffEq = "6" -Test = "1" julia = "1.10" - -[extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[targets] -test = ["Test"] diff --git a/docs/src/resources/contributing.md b/docs/src/resources/contributing.md index be2d20016..5518c6998 100644 --- a/docs/src/resources/contributing.md +++ b/docs/src/resources/contributing.md @@ -38,6 +38,12 @@ The tests are divided into several test groups, where the group names are define make GROUP=Core test ``` +### [Test Item Framework for Core tests](@id doc-Contribute:Test-Item-Framework-for-Core-tests) + +The tests in `GROUP=Core` are provided using the [Test Item Framework](https://www.julia-vscode.org/docs/stable/userguide/testitems/), which structures the test codes into `@testitems` and makes it easier to run individually. + +The [VS Code](https://code.visualstudio.com/) and its [Julia extension](https://www.julia-vscode.org/) provides us with options to run individual `@testitems`. It is much easier to find the specific core test that failed since the [Julia extension](https://www.julia-vscode.org/) in [VS Code](https://code.visualstudio.com/) will collect all core test failures and then display them in a structured way, directly at the place in the code where a specific core test failed. See [here](https://www.julia-vscode.org/docs/stable/userguide/testitems/) for more details. + ## [Julia Code Format](@id doc-Contribute:Julia-Code-Format) We use [`JuliaFormatter.jl`](https://github.com/domluna/JuliaFormatter.jl) to format all the source codes. The code style and extra formatting options is defined in the file `.JuliaFormatter.toml` in the repository. diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index 08c3de46b..9fcbb0502 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -30,6 +30,8 @@ Constructor representing a bra state ``\langle\psi|``. """ struct Bra <: QuantumObjectType end +Base.show(io::IO, ::Bra) = print(io, "Bra()") + @doc raw""" Ket <: QuantumObjectType @@ -37,6 +39,8 @@ Constructor representing a ket state ``|\psi\rangle``. """ struct Ket <: QuantumObjectType end +Base.show(io::IO, ::Ket) = print(io, "Ket()") + @doc raw""" Operator <: QuantumObjectType @@ -44,6 +48,8 @@ Constructor representing an operator ``\hat{O}``. """ struct Operator <: QuantumObjectType end +Base.show(io::IO, ::Operator) = print(io, "Operator()") + @doc raw""" SuperOperator <: SuperOperatorType @@ -51,6 +57,8 @@ Constructor representing a super-operator ``\hat{\mathcal{O}}`` acting on vector """ struct SuperOperator <: SuperOperatorType end +Base.show(io::IO, ::SuperOperator) = print(io, "SuperOperator()") + @doc raw""" OperatorBra <: QuantumObjectType @@ -58,6 +66,8 @@ Constructor representing a bra state in the [`SuperOperator`](@ref) formalism `` """ struct OperatorBra <: QuantumObjectType end +Base.show(io::IO, ::OperatorBra) = print(io, "OperatorBra()") + @doc raw""" OperatorKet <: QuantumObjectType @@ -65,6 +75,8 @@ Constructor representing a ket state in the [`SuperOperator`](@ref) formalism `` """ struct OperatorKet <: QuantumObjectType end +Base.show(io::IO, ::OperatorKet) = print(io, "OperatorKet()") + @doc raw""" size(A::AbstractQuantumObject) size(A::AbstractQuantumObject, idx::Int) diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 000000000..6c91482ab --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,14 @@ +[deps] +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" + +[compat] +Pkg = "1" +Test = "1" +TestItemRunner = "1" diff --git a/test/core-test/block_diagonal_form.jl b/test/core-test/block_diagonal_form.jl index cc96a48d9..201b63ff2 100644 --- a/test/core-test/block_diagonal_form.jl +++ b/test/core-test/block_diagonal_form.jl @@ -1,4 +1,4 @@ -@testset "Block Diagonal Form" begin +@testitem "Block Diagonal Form" begin # Block Diagonal Form N = 20 Δ = 0 diff --git a/test/core-test/code-quality/Project.toml b/test/core-test/code-quality/Project.toml index b1ac4d754..5cadfefb9 100644 --- a/test/core-test/code-quality/Project.toml +++ b/test/core-test/code-quality/Project.toml @@ -2,6 +2,7 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] Aqua = "0.8" diff --git a/test/core-test/correlations_and_spectrum.jl b/test/core-test/correlations_and_spectrum.jl index 381cd9e08..d889e6d52 100644 --- a/test/core-test/correlations_and_spectrum.jl +++ b/test/core-test/correlations_and_spectrum.jl @@ -1,4 +1,4 @@ -@testset "Correlations and Spectrum" begin +@testitem "Correlations and Spectrum" begin N = 10 Id = qeye(N) a = destroy(N) diff --git a/test/core-test/dynamical-shifted-fock.jl b/test/core-test/dynamical-shifted-fock.jl index 663ac6494..ff9fd3ffc 100644 --- a/test/core-test/dynamical-shifted-fock.jl +++ b/test/core-test/dynamical-shifted-fock.jl @@ -1,4 +1,4 @@ -@testset "Dynamical Shifted Fock" begin +@testitem "Dynamical Shifted Fock" begin F = 3 Δ = 0.25 κ = 1 diff --git a/test/core-test/dynamical_fock_dimension_mesolve.jl b/test/core-test/dynamical_fock_dimension_mesolve.jl index 4c75eef0b..cd213ec00 100644 --- a/test/core-test/dynamical_fock_dimension_mesolve.jl +++ b/test/core-test/dynamical_fock_dimension_mesolve.jl @@ -1,5 +1,5 @@ ### DYNAMICAL FOCK DIMENSION ### -@testset "Dynamical Fock Dimension" begin +@testitem "Dynamical Fock Dimension" begin F, Δ, κ = 5, 0.25, 1 t_l = range(0, 15, length = 100) diff --git a/test/core-test/eigenvalues_and_operators.jl b/test/core-test/eigenvalues_and_operators.jl index a2fe6bcc5..c87702ae8 100644 --- a/test/core-test/eigenvalues_and_operators.jl +++ b/test/core-test/eigenvalues_and_operators.jl @@ -1,4 +1,4 @@ -@testset "Eigenvalues and Operators" begin +@testitem "Eigenvalues and Operators" begin σx = sigmax() result = eigenstates(σx, sparse = false) λd, ψd, Td = result diff --git a/test/core-test/entropy_and_metric.jl b/test/core-test/entropy_and_metric.jl index b90a059f7..79208ca7c 100644 --- a/test/core-test/entropy_and_metric.jl +++ b/test/core-test/entropy_and_metric.jl @@ -1,4 +1,4 @@ -@testset "entropy" begin +@testitem "entropy" begin base = 2 λ = rand() ψ = rand_ket(10) @@ -51,7 +51,7 @@ end end -@testset "entanglement and concurrence" begin +@testitem "entanglement and concurrence" begin # bell state ψb = bell_state(Val(1), Val(0)) ρb = ket2dm(ψb) @@ -87,7 +87,7 @@ end end end -@testset "trace and Hilbert-Schmidt distance" begin +@testitem "trace and Hilbert-Schmidt distance" begin ψz0 = basis(2, 0) ψz1 = basis(2, 1) ρz0 = to_sparse(ket2dm(ψz0)) @@ -117,7 +117,7 @@ end end end -@testset "fidelity, Bures metric, and Hellinger distance" begin +@testitem "fidelity, Bures metric, and Hellinger distance" begin M0 = rand_dm(5) ψ1 = rand_ket(5) ψ2 = rand_ket(5) diff --git a/test/core-test/generalized_master_equation.jl b/test/core-test/generalized_master_equation.jl index 03d2989e0..d3686a92a 100644 --- a/test/core-test/generalized_master_equation.jl +++ b/test/core-test/generalized_master_equation.jl @@ -1,4 +1,6 @@ -@testset "Generalized Master Equation" begin +@testitem "Generalized Master Equation" begin + using LinearAlgebra + N_c = 30 N_trunc = 10 tol = 1e-14 diff --git a/test/core-test/low_rank_dynamics.jl b/test/core-test/low_rank_dynamics.jl index 07faf788f..a939a8d27 100644 --- a/test/core-test/low_rank_dynamics.jl +++ b/test/core-test/low_rank_dynamics.jl @@ -1,4 +1,6 @@ -@testset "Low Rank Dynamics" begin +@testitem "Low Rank Dynamics" begin + using LinearAlgebra + # Define lattice Nx, Ny = 2, 3 latt = Lattice(Nx = Nx, Ny = Ny) @@ -14,12 +16,12 @@ i = 1 for j in 1:N_modes - i += 1 + global i += 1 i <= M && (ϕ[i] = multisite_operator(latt, j => sigmap()) * ϕ[1]) end for k in 1:(N_modes-1) for l in (k+1):N_modes - i += 1 + global i += 1 i <= M && (ϕ[i] = multisite_operator(latt, k => sigmap(), l => sigmap()) * ϕ[1]) end end diff --git a/test/core-test/negativity_and_partial_transpose.jl b/test/core-test/negativity_and_partial_transpose.jl index 475cd343e..44ad9deb6 100644 --- a/test/core-test/negativity_and_partial_transpose.jl +++ b/test/core-test/negativity_and_partial_transpose.jl @@ -1,4 +1,4 @@ -@testset "Negativity and Partial Transpose" verbose = true begin +@testitem "Negativity and Partial Transpose" begin @testset "negativity" begin rho = (1 / 40) * Qobj( [ diff --git a/test/core-test/progress_bar.jl b/test/core-test/progress_bar.jl index 274f5d57d..2792a6e0a 100644 --- a/test/core-test/progress_bar.jl +++ b/test/core-test/progress_bar.jl @@ -1,4 +1,4 @@ -@testset "Progress Bar" begin +@testitem "Progress Bar" begin bar_width = 30 strLength = 67 + bar_width # including "\r" in the beginning of the string prog = ProgressBar(bar_width, enable = true, bar_width = bar_width, interval = 0.2) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index c01eeca33..81afc501b 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -1,4 +1,8 @@ -@testset "Quantum Objects" verbose = true begin +@testitem "Quantum Objects" begin + using LinearAlgebra + using SparseArrays + using StaticArraysCore + # ArgumentError: type is incompatible with vector or matrix @testset "ArgumentError" begin a = rand(ComplexF64, 2) diff --git a/test/core-test/quantum_objects_evo.jl b/test/core-test/quantum_objects_evo.jl index 14e5a3270..a417f52ac 100644 --- a/test/core-test/quantum_objects_evo.jl +++ b/test/core-test/quantum_objects_evo.jl @@ -1,4 +1,9 @@ -@testset "Quantum Objects Evolution" verbose = true begin +@testitem "Quantum Objects Evolution" begin + using LinearAlgebra + using SparseArrays + using StaticArraysCore + using SciMLOperators + # DomainError: incompatible between size of array and type @testset "Thrown Errors" begin a = MatrixOperator(rand(ComplexF64, 3, 2)) diff --git a/test/core-test/states_and_operators.jl b/test/core-test/states_and_operators.jl index 1fd77976a..6d0aef108 100644 --- a/test/core-test/states_and_operators.jl +++ b/test/core-test/states_and_operators.jl @@ -1,4 +1,8 @@ -@testset "States and Operators" verbose = true begin +@testitem "States and Operators" begin + import QuantumToolbox: position, momentum + using LinearAlgebra + using SparseArrays + @testset "zero state" begin v1 = zero_ket(4) v2 = zero_ket((2, 2)) diff --git a/test/core-test/steady_state.jl b/test/core-test/steady_state.jl index 9981d5abb..d78c93c33 100644 --- a/test/core-test/steady_state.jl +++ b/test/core-test/steady_state.jl @@ -1,4 +1,4 @@ -@testset "Steady State" begin +@testitem "Steady State" begin N = 10 a = destroy(N) a_d = a' diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 5abb7d18d..a67c97706 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -1,4 +1,7 @@ -@testset "Time Evolution and Partial Trace" verbose = true begin +@testitem "Time Evolution and Partial Trace" begin + using Random + using SciMLOperators + # Global definition of the system N = 10 a = kron(destroy(N), qeye(2)) diff --git a/test/core-test/utilities.jl b/test/core-test/utilities.jl index 3b5f6173c..c14a023f4 100644 --- a/test/core-test/utilities.jl +++ b/test/core-test/utilities.jl @@ -1,4 +1,4 @@ -@testset "Utilities" verbose = true begin +@testitem "Utilities" begin @testset "n_thermal" begin ω1 = rand(Float64) ω2 = rand(Float64) diff --git a/test/core-test/wigner.jl b/test/core-test/wigner.jl index e48b53497..58e292781 100644 --- a/test/core-test/wigner.jl +++ b/test/core-test/wigner.jl @@ -1,4 +1,4 @@ -@testset "Wigner" begin +@testitem "Wigner" begin α = 0.5 + 0.8im ψ = coherent(30, α) ρ = to_sparse(ket2dm(ψ), 1e-6) diff --git a/test/ext-test/cpu/autodiff/Project.toml b/test/ext-test/cpu/autodiff/Project.toml index 314afe38b..e07db5ee7 100644 --- a/test/ext-test/cpu/autodiff/Project.toml +++ b/test/ext-test/cpu/autodiff/Project.toml @@ -2,4 +2,4 @@ Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" -Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" \ No newline at end of file diff --git a/test/ext-test/cpu/autodiff/zygote.jl b/test/ext-test/cpu/autodiff/zygote.jl index 824da7a12..bf2775a05 100644 --- a/test/ext-test/cpu/autodiff/zygote.jl +++ b/test/ext-test/cpu/autodiff/zygote.jl @@ -1,4 +1,4 @@ -@testset "Zygote.jl Autodiff" verbose=true begin +@testset "Zygote Extension" verbose=true begin @testset "sesolve" begin coef_Ω(p, t) = p[1] diff --git a/test/runtests.jl b/test/runtests.jl index 7407d82fb..b650417cf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,49 +1,28 @@ using Test +using TestItemRunner using Pkg -# Importing only the necessary functions to keep track the re-export of the functions -import LinearAlgebra: Diagonal, I, mul!, triu, tril, triu!, tril! -import SparseArrays: sparse, sprand, spzeros, spdiagm, nnz, SparseVector, SparseMatrixCSC, AbstractSparseMatrix -import StaticArraysCore: SVector +const GROUP_LIST = String["All", "Core", "Code-Quality", "AutoDiff_Ext", "Makie_Ext", "CUDA_Ext"] const GROUP = get(ENV, "GROUP", "All") +(GROUP in GROUP_LIST) || throw(ArgumentError("Unknown GROUP = $GROUP")) -const testdir = dirname(@__FILE__) - -# Put core tests in alphabetical order -core_tests = [ - "block_diagonal_form.jl", - "correlations_and_spectrum.jl", - "dynamical_fock_dimension_mesolve.jl", - "dynamical-shifted-fock.jl", - "eigenvalues_and_operators.jl", - "entropy_and_metric.jl", - "generalized_master_equation.jl", - "low_rank_dynamics.jl", - "negativity_and_partial_transpose.jl", - "progress_bar.jl", - "quantum_objects.jl", - "quantum_objects_evo.jl", - "states_and_operators.jl", - "steady_state.jl", - "time_evolution.jl", - "utilities.jl", - "wigner.jl", -] - +# Core tests if (GROUP == "All") || (GROUP == "Core") - using QuantumToolbox - import QuantumToolbox: position, momentum - import Random: MersenneTwister - import SciMLOperators: MatrixOperator, NullOperator, IdentityOperator + import QuantumToolbox QuantumToolbox.about() - for test in core_tests - include(joinpath(testdir, "core-test", test)) - end + println("\nStart running Core tests...\n") + @run_package_tests verbose=true end +######################################################################## +# Use traditional Test.jl instead of TestItemRunner.jl for other tests # +######################################################################## + +const testdir = dirname(@__FILE__) + if (GROUP == "All") || (GROUP == "Code-Quality") Pkg.activate("core-test/code-quality") Pkg.develop(PackageSpec(path = dirname(@__DIR__))) @@ -52,6 +31,8 @@ if (GROUP == "All") || (GROUP == "Code-Quality") using QuantumToolbox using Aqua, JET + (GROUP == "Code-Quality") && QuantumToolbox.about() # print version info. for code quality CI in GitHub + include(joinpath(testdir, "core-test", "code-quality", "code_quality.jl")) end @@ -65,6 +46,8 @@ if (GROUP == "AutoDiff_Ext") using Enzyme using SciMLSensitivity + QuantumToolbox.about() + include(joinpath(testdir, "ext-test", "cpu", "autodiff", "zygote.jl")) end @@ -86,6 +69,8 @@ if (GROUP == "CUDA_Ext") Pkg.instantiate() using QuantumToolbox + import LinearAlgebra: Diagonal + import StaticArraysCore: SVector using CUDA using CUDA.CUSPARSE # CUDA.allowscalar(false) # This is already set in the extension script From 6b6f674d073b5fa581195b5381d1bbdf8b08e5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matteo=20Secl=C3=AC?= Date: Wed, 4 Jun 2025 10:32:36 +0200 Subject: [PATCH 271/329] Lanczos-based `spectrum` method (#476) --- CHANGELOG.md | 3 + benchmarks/correlations_and_spectrum.jl | 5 + docs/src/resources/api.md | 1 + src/QuantumToolbox.jl | 2 +- src/spectrum.jl | 153 +++++++++++++++++++- test/core-test/correlations_and_spectrum.jl | 16 ++ test/ext-test/gpu/cuda_ext.jl | 23 +++ 7 files changed, 201 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 145b17345..c0197844e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Introduce `Lanczos` solver for `spectrum`. ([#476]) + ## [v0.31.1] Release date: 2025-05-16 @@ -228,3 +230,4 @@ Release date: 2024-11-13 [#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 [#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456 [#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460 +[#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 diff --git a/benchmarks/correlations_and_spectrum.jl b/benchmarks/correlations_and_spectrum.jl index 04f63997f..d3ae9e916 100644 --- a/benchmarks/correlations_and_spectrum.jl +++ b/benchmarks/correlations_and_spectrum.jl @@ -19,6 +19,8 @@ function benchmark_correlations_and_spectrum!(SUITE) PI_solver = PseudoInverse() + L_solver = Lanczos() + SUITE["Correlations and Spectrum"]["FFT Correlation"] = @benchmarkable _calculate_fft_spectrum($H, $t_l, $c_ops, $(a'), $a) @@ -28,5 +30,8 @@ function benchmark_correlations_and_spectrum!(SUITE) SUITE["Correlations and Spectrum"]["Spectrum"]["Pseudo Inverse"] = @benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $PI_solver) + SUITE["Correlations and Spectrum"]["Spectrum"]["Lanczos"] = + @benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $L_solver) + return nothing end diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 0358ceb1d..0c80945e1 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -248,6 +248,7 @@ spectrum_correlation_fft spectrum ExponentialSeries PseudoInverse +Lanczos ``` ## [Entropy and Metrics](@id doc-API:Entropy-and-Metrics) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 40bfc245f..7d6e9a0d7 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -115,7 +115,6 @@ include("time_evolution/time_evolution_dynamical.jl") # Others include("correlations.jl") -include("spectrum.jl") include("wigner.jl") include("spin_lattice.jl") include("arnoldi.jl") @@ -123,6 +122,7 @@ include("entropy.jl") include("metrics.jl") include("negativity.jl") include("steadystate.jl") +include("spectrum.jl") include("visualization.jl") # deprecated functions diff --git a/src/spectrum.jl b/src/spectrum.jl index 036d95b3d..367470605 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -1,5 +1,5 @@ export spectrum, spectrum_correlation_fft -export SpectrumSolver, ExponentialSeries, PseudoInverse +export SpectrumSolver, ExponentialSeries, PseudoInverse, Lanczos abstract type SpectrumSolver end @@ -26,6 +26,20 @@ end PseudoInverse(; alg::SciMLLinearSolveAlgorithm = KrylovJL_GMRES()) = PseudoInverse(alg) +@doc raw""" + Lanczos(; tol = 1e-8, maxiter = 5000, verbose = 0) + +A solver which solves [`spectrum`](@ref) by using a non-symmetric Lanczos variant of the algorithm in [Koch2011](https://www.cond-mat.de/events/correl11/manuscripts/koch.pdf). +The nonsymmetric Lanczos algorithm is adapted from Algorithm 6.6 in [Saad2011](https://www-users.cse.umn.edu/~saad/eig_book_2ndEd.pdf). +The running estimate is updated via a [Wallis-Euler recursion](https://en.wikipedia.org/wiki/Continued_fraction). +""" +Base.@kwdef struct Lanczos{T<:Real,SS<:Union{Nothing,<:SteadyStateSolver}} <: SpectrumSolver + tol::T = 1e-8 + maxiter::Int = 5000 + verbose::Int = 0 + steadystate_solver::SS = nothing +end + @doc raw""" spectrum(H::QuantumObject, ωlist::AbstractVector, @@ -44,6 +58,7 @@ S(\omega) = \int_{-\infty}^\infty \lim_{t \rightarrow \infty} \left\langle \hat{ See also the following list for `SpectrumSolver` docstrings: - [`ExponentialSeries`](@ref) - [`PseudoInverse`](@ref) +- [`Lanczos`](@ref) """ function spectrum( H::QuantumObject{HOpType}, @@ -144,6 +159,142 @@ function _spectrum( return spec end +function _spectrum( + L::QuantumObject{SuperOperator}, + ωlist::AbstractVector, + A::QuantumObject{Operator}, + B::QuantumObject{Operator}, + solver::Lanczos, +) + check_dimensions(L, A, B) + + # Define type shortcuts + fT = _FType(L) + cT = _CType(L) + + # Calculate |v₁> = B|ρss> + ρss = + isnothing(solver.steadystate_solver) ? mat2vec(steadystate(L)) : + mat2vec(steadystate(L; solver = solver.steadystate_solver)) + vₖ = (spre(B) * ρss).data + + # Define (possibly GPU) vector type + vT = typeof(vₖ) + + # Calculate and max(abs(x), abs(y)), maxNorm, Aₖ, Bₖ) + Aₖ ./= maxNorm + Bₖ ./= maxNorm + A₋₁ ./= maxNorm + B₋₁ ./= maxNorm + end + + # Check for convergence + + residueNorm = max(maximum(abs, lanczosFactor), maximum(abs, lanczosFactor₋₁)) + lanczosFactor₋₁ .-= lanczosFactor + maxResidue = maximum(abs, lanczosFactor₋₁) / residueNorm + if maxResidue <= solver.tol + if solver.verbose > 1 + println("spectrum(): solver::Lanczos converged after $(k) iterations") + end + break + end + + # (k+1)-th left/right vectors, orthogonal to previous ones + mul!(v₊₁, L.data, vₖ) + v₊₁ .= v₊₁ .- αₖ .* vₖ .- βₖ .* v₋₁ + w₊₁ .= w₊₁ .- conj(αₖ) .* wₖ .- conj(δₖ) .* w₋₁ + v₋₁, vₖ = vₖ, v₋₁ + vₖ, v₊₁ = v₊₁, vₖ + w₋₁, wₖ = wₖ, w₋₁ + wₖ, w₊₁ = w₊₁, wₖ + + # k-th off-diagonal elements + buf = dot(wₖ, vₖ) + δₖ = sqrt(abs(buf)) + βₖ = buf / δₖ + + # Normalize (k+1)-th left/right vectors + vₖ ./= δₖ + wₖ ./= conj(βₖ) + + # Update everything for the next cycle + A₋₂, A₋₁ = A₋₁, A₋₂ + A₋₁, Aₖ = Aₖ, A₋₁ + B₋₂, B₋₁ = B₋₁, B₋₂ + B₋₁, Bₖ = Bₖ, B₋₁ + end + + if solver.verbose > 0 && maxResidue > solver.tol + println("spectrum(): maxiter = $(solver.maxiter) reached before convergence!") + println("spectrum(): Max residue = $maxResidue") + println("spectrum(): Consider increasing maxiter and/or tol") + end + + # Restore the norm + lanczosFactor .= gfNorm .* lanczosFactor + + return -2 .* real(lanczosFactor) +end + @doc raw""" spectrum_correlation_fft(tlist, corr; inverse=false) diff --git a/test/core-test/correlations_and_spectrum.jl b/test/core-test/correlations_and_spectrum.jl index d889e6d52..bd9a2a918 100644 --- a/test/core-test/correlations_and_spectrum.jl +++ b/test/core-test/correlations_and_spectrum.jl @@ -13,10 +13,12 @@ ω_l2 = range(0, 3, length = 1000) spec2 = spectrum(H, ω_l2, c_ops, a', a) spec3 = spectrum(H, ω_l2, c_ops, a', a; solver = PseudoInverse()) + spec4 = spectrum(H, ω_l2, c_ops, a', a; solver = Lanczos()) spec1 = spec1 ./ maximum(spec1) spec2 = spec2 ./ maximum(spec2) spec3 = spec3 ./ maximum(spec3) + spec4 = spec4 ./ maximum(spec4) test_func1 = maximum(real.(spec1)) * (0.1 / 2)^2 ./ ((ω_l1 .- 1) .^ 2 .+ (0.1 / 2)^2) test_func2 = maximum(real.(spec2)) * (0.1 / 2)^2 ./ ((ω_l2 .- 1) .^ 2 .+ (0.1 / 2)^2) @@ -26,12 +28,26 @@ @test sum(abs2.(spec2[idxs2] .- test_func2[idxs2])) / sum(abs2.(test_func2[idxs2])) < 0.01 @test all(corr1 .≈ corr2) @test all(spec2 .≈ spec3) + @test all(spec2 .≈ spec4) @testset "Type Inference spectrum" begin @inferred correlation_2op_1t(H, nothing, t_l, c_ops, a', a; progress_bar = Val(false)) @inferred spectrum_correlation_fft(t_l, corr1) @inferred spectrum(H, ω_l2, c_ops, a', a) @inferred spectrum(H, ω_l2, c_ops, a', a; solver = PseudoInverse()) + @inferred spectrum(H, ω_l2, c_ops, a', a; solver = Lanczos()) + end + + @testset "Verbose mode Lanczos" begin + cout = stdout + r, w = redirect_stdout() + nout = @async read(r, String) + spectrum(H, ω_l2, c_ops, a', a; solver = Lanczos(verbose = 2, maxiter = 2, tol = 1e-16)); + redirect_stdout(cout) + close(w) + out = fetch(nout) + outlines = split(out, '\n', keepempty = false) + @test last(outlines) == "spectrum(): Consider increasing maxiter and/or tol" end # tlist and τlist checks diff --git a/test/ext-test/gpu/cuda_ext.jl b/test/ext-test/gpu/cuda_ext.jl index e6399e42a..1f60d5794 100644 --- a/test/ext-test/gpu/cuda_ext.jl +++ b/test/ext-test/gpu/cuda_ext.jl @@ -154,6 +154,29 @@ end @test ρ_ss_cpu.data ≈ Array(ρ_ss_gpu_csr.data) atol = 1e-8 * length(ρ_ss_cpu) end +@testset "CUDA spectrum" begin + N = 10 + a = cu(destroy(N)) + H = a' * a + c_ops = [sqrt(0.1 * (0.01 + 1)) * a, sqrt(0.1 * (0.01)) * a'] + solver = Lanczos(steadystate_solver = SteadyStateLinearSolver()) + + ω_l = range(0, 3, length = 1000) + spec = spectrum(H, ω_l, c_ops, a', a; solver = solver) + + spec = collect(spec) + spec = spec ./ maximum(spec) + + test_func = maximum(real.(spec)) * (0.1 / 2)^2 ./ ((ω_l .- 1) .^ 2 .+ (0.1 / 2)^2) + idxs = test_func .> 0.05 + @test sum(abs2.(spec[idxs] .- test_func[idxs])) / sum(abs2.(test_func[idxs])) < 0.01 + + # TODO: Fix this + # @testset "Type Inference spectrum" begin + # @inferred spectrum(H, ω_l, c_ops, a', a; solver = solver) + # end +end + @testset "CUDA ptrace" begin g = fock(2, 1) e = fock(2, 0) From fbe80da8ead53d3f5a46c45c6eccc0710d22318c Mon Sep 17 00:00:00 2001 From: Li-Xun Cai <157601901+TendonFFF@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:14:34 +0800 Subject: [PATCH 272/329] Bloch Redfield master equation implementation (#473) --- CHANGELOG.md | 2 + docs/src/resources/api.md | 3 + src/QuantumToolbox.jl | 1 + src/time_evolution/brmesolve.jl | 196 ++++++++++++++++++++++++++++++++ test/core-test/brmesolve.jl | 115 +++++++++++++++++++ 5 files changed, 317 insertions(+) create mode 100644 src/time_evolution/brmesolve.jl create mode 100644 test/core-test/brmesolve.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index c0197844e..e6e3dea72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Introduce `Lanczos` solver for `spectrum`. ([#476]) +- Add Bloch-Redfield master equation solver. ([#473]) ## [v0.31.1] Release date: 2025-05-16 @@ -230,4 +231,5 @@ Release date: 2024-11-13 [#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 [#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456 [#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460 +[#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473 [#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 0c80945e1..7f3d49e73 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -208,6 +208,9 @@ smesolve dfd_mesolve liouvillian liouvillian_generalized +bloch_redfield_tensor +brterm +brmesolve ``` ### [Steady State Solvers](@id doc-API:Steady-State-Solvers) diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 7d6e9a0d7..0944175b3 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -106,6 +106,7 @@ include("time_evolution/callback_helpers/mcsolve_callback_helpers.jl") include("time_evolution/callback_helpers/ssesolve_callback_helpers.jl") include("time_evolution/callback_helpers/smesolve_callback_helpers.jl") include("time_evolution/mesolve.jl") +include("time_evolution/brmesolve.jl") include("time_evolution/lr_mesolve.jl") include("time_evolution/sesolve.jl") include("time_evolution/mcsolve.jl") diff --git a/src/time_evolution/brmesolve.jl b/src/time_evolution/brmesolve.jl new file mode 100644 index 000000000..05a47a2d3 --- /dev/null +++ b/src/time_evolution/brmesolve.jl @@ -0,0 +1,196 @@ +export bloch_redfield_tensor, brterm, brmesolve + +@doc raw""" + bloch_redfield_tensor( + H::QuantumObject{Operator}, + a_ops::Union{AbstractVector, Tuple}, + c_ops::Union{AbstractVector, Tuple, Nothing}=nothing; + sec_cutoff::Real=0.1, + fock_basis::Union{Val,Bool}=Val(false) + ) + +Calculates the Bloch-Redfield tensor ([`SuperOperator`](@ref)) for a system given a set of operators and corresponding spectral functions that describes the system's coupling to its environment. + +## Arguments + +- `H`: The system Hamiltonian. Must be an [`Operator`](@ref) +- `a_ops`: Nested list with each element is a `Tuple` of operator-function pairs `(a_op, spectra)`, and the coupling [`Operator`](@ref) `a_op` must be hermitian with corresponding `spectra` being a `Function` of transition energy +- `c_ops`: List of collapse operators corresponding to Lindblad dissipators +- `sec_cutoff`: Cutoff for secular approximation. Use `-1` if secular approximation is not used when evaluating bath-coupling terms. +- `fock_basis`: Whether to return the tensor in the input (fock) basis or the diagonalized (eigen) basis. + +## Return + +The return depends on `fock_basis`. + +- `fock_basis=Val(true)`: return the Bloch-Redfield tensor (in the fock basis) only. +- `fock_basis=Val(false)`: return the Bloch-Redfield tensor (in the eigen basis) along with the transformation matrix from eigen to fock basis. +""" +function bloch_redfield_tensor( + H::QuantumObject{Operator}, + a_ops::Union{AbstractVector,Tuple}, + c_ops::Union{AbstractVector,Tuple,Nothing} = nothing; + sec_cutoff::Real = 0.1, + fock_basis::Union{Val,Bool} = Val(false), +) + rst = eigenstates(H) + U = QuantumObject(rst.vectors, Operator(), H.dimensions) + sec_cutoff = float(sec_cutoff) + + # in fock basis + R0 = liouvillian(H, c_ops) + + # set fock_basis=Val(false) and change basis together at the end + R1 = 0 + isempty(a_ops) || (R1 += mapreduce(x -> _brterm(rst, x[1], x[2], sec_cutoff, Val(false)), +, a_ops)) + + SU = sprepost(U, U') # transformation matrix from eigen basis back to fock basis + if getVal(fock_basis) + return R0 + SU * R1 * SU' + else + return SU' * R0 * SU + R1, U + end +end + +@doc raw""" + brterm( + H::QuantumObject{Operator}, + a_op::QuantumObject{Operator}, + spectra::Function; + sec_cutoff::Real=0.1, + fock_basis::Union{Bool, Val}=Val(false) + ) + +Calculates the contribution of one coupling operator to the Bloch-Redfield tensor. + +## Argument + +- `H`: The system Hamiltonian. Must be an [`Operator`](@ref) +- `a_op`: The operator coupling to the environment. Must be hermitian. +- `spectra`: The corresponding environment spectra as a `Function` of transition energy. +- `sec_cutoff`: Cutoff for secular approximation. Use `-1` if secular approximation is not used when evaluating bath-coupling terms. +- `fock_basis`: Whether to return the tensor in the input (fock) basis or the diagonalized (eigen) basis. + +## Return + +The return depends on `fock_basis`. + +- `fock_basis=Val(true)`: return the Bloch-Redfield term (in the fock basis) only. +- `fock_basis=Val(false)`: return the Bloch-Redfield term (in the eigen basis) along with the transformation matrix from eigen to fock basis. +""" +function brterm( + H::QuantumObject{Operator}, + a_op::QuantumObject{Operator}, + spectra::Function; + sec_cutoff::Real = 0.1, + fock_basis::Union{Bool,Val} = Val(false), +) + rst = eigenstates(H) + term = _brterm(rst, a_op, spectra, sec_cutoff, makeVal(fock_basis)) + if getVal(fock_basis) + return term + else + return term, Qobj(rst.vectors, Operator(), rst.dimensions) + end +end + +function _brterm( + rst::EigsolveResult, + a_op::T, + spectra::F, + sec_cutoff::Real, + fock_basis::Union{Val{true},Val{false}}, +) where {T<:QuantumObject{Operator},F<:Function} + _check_br_spectra(spectra) + + U = rst.vectors + Id = I(prod(rst.dimensions)) + + skew = @. rst.values - rst.values' |> real + spectrum = spectra.(skew) + + A_mat = U' * a_op.data * U + + ac_term = (A_mat .* spectrum) * A_mat + bd_term = A_mat * (A_mat .* trans(spectrum)) + + if sec_cutoff != -1 + m_cut = similar(skew) + map!(x -> abs(x) < sec_cutoff, m_cut, skew) + ac_term .*= m_cut + bd_term .*= m_cut + + vec_skew = vec(skew) + M_cut = @. abs(vec_skew - vec_skew') < sec_cutoff + end + + out = + 0.5 * ( + + _sprepost(A_mat .* trans(spectrum), A_mat) + _sprepost(A_mat, A_mat .* spectrum) - _spost(ac_term, Id) - + _spre(bd_term, Id) + ) + + (sec_cutoff != -1) && (out .*= M_cut) + + if getVal(fock_basis) + SU = _sprepost(U, U') + return QuantumObject(SU * out * SU', SuperOperator(), rst.dimensions) + else + return QuantumObject(out, SuperOperator(), rst.dimensions) + end +end + +@doc raw""" + brmesolve( + H::QuantumObject{Operator}, + ψ0::QuantumObject, + tlist::AbstractVector, + a_ops::Union{Nothing, AbstractVector, Tuple}, + c_ops::Union{Nothing, AbstractVector, Tuple}=nothing; + sec_cutoff::Real=0.1, + e_ops::Union{Nothing, AbstractVector}=nothing, + kwargs..., + ) + +Solves for the dynamics of a system using the Bloch-Redfield master equation, given an input Hamiltonian, Hermitian bath-coupling terms and their associated spectral functions, as well as possible Lindblad collapse operators. + +## Arguments + +- `H`: The system Hamiltonian. Must be an [`Operator`](@ref) +- `ψ0`: Initial state of the system $|\psi(0)\rangle$. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). +- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `a_ops`: Nested list with each element is a `Tuple` of operator-function pairs `(a_op, spectra)`, and the coupling [`Operator`](@ref) `a_op` must be hermitian with corresponding `spectra` being a `Function` of transition energy +- `c_ops`: List of collapse operators corresponding to Lindblad dissipators +- `sec_cutoff`: Cutoff for secular approximation. Use `-1` if secular approximation is not used when evaluating bath-coupling terms. +- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. +- `kwargs`: Keyword arguments for [`mesolve`](@ref). + +## Notes + +- This function will automatically generate the [`bloch_redfield_tensor`](@ref) and solve the time evolution with [`mesolve`](@ref). + +# Returns + +- `sol::TimeEvolutionSol`: The solution of the time evolution. See also [`TimeEvolutionSol`](@ref) +""" +function brmesolve( + H::QuantumObject{Operator}, + ψ0::QuantumObject, + tlist::AbstractVector, + a_ops::Union{Nothing,AbstractVector,Tuple}, + c_ops::Union{Nothing,AbstractVector,Tuple} = nothing; + sec_cutoff::Real = 0.1, + e_ops::Union{Nothing,AbstractVector} = nothing, + kwargs..., +) + R = bloch_redfield_tensor(H, a_ops, c_ops; sec_cutoff = sec_cutoff, fock_basis = Val(true)) + + return mesolve(R, ψ0, tlist, nothing; e_ops = e_ops, kwargs...) +end + +function _check_br_spectra(f::Function) + meths = methods(f, [Real]) + length(meths.ms) == 0 && + throw(ArgumentError("The following function must accept one argument: `$(meths.mt.name)(ω)` with ω<:Real")) + return nothing +end diff --git a/test/core-test/brmesolve.jl b/test/core-test/brmesolve.jl new file mode 100644 index 000000000..68cfad8c1 --- /dev/null +++ b/test/core-test/brmesolve.jl @@ -0,0 +1,115 @@ +@testitem "Bloch-Redfield tensor sec_cutoff" begin + N = 5 + H = num(N) + a = destroy(N) + A_op = a+a' + spectra(x) = (x>0) * 0.5 + for sec_cutoff in [0, 0.1, 1, 3, -1] + R = bloch_redfield_tensor(H, [(A_op, spectra)], [a^2], sec_cutoff = sec_cutoff, fock_basis = true) + R_eig, evecs = bloch_redfield_tensor(H, [(A_op, spectra)], [a^2], sec_cutoff = sec_cutoff, fock_basis = false) + @test isa(R, QuantumObject) + @test isa(R_eig, QuantumObject) + @test isa(evecs, QuantumObject) + + state = rand_dm(N) |> mat2vec + fock_computed = R * state + eig_computed = (sprepost(evecs, evecs') * R_eig * sprepost(evecs', evecs)) * state + @test isapprox(fock_computed, eig_computed, atol = 1e-15) + end +end + +@testitem "Compare brterm and Lindblad" begin + N = 5 + H = num(N) + a = destroy(N) + destroy(N)^2/2 + A_op = a+a' + spectra(x) = x>0 + + # this test applies for limited cutoff + lindblad = lindblad_dissipator(a) + computation = brterm(H, A_op, spectra, sec_cutoff = 1.5, fock_basis = true) + @test isapprox(lindblad, computation, atol = 1e-15) +end + +@testitem "brterm basis" begin + N = 5 + H = num(N) + a = destroy(N) + destroy(N)^2/2 + A_op = a+a' + spectra(x) = x>0 + for sec_cutoff in [0, 0.1, 1, 3, -1] + R = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = true) + R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = false) + @test isa(R, QuantumObject) + @test isa(R_eig, QuantumObject) + @test isa(evecs, QuantumObject) + + state = rand_dm(N) |> mat2vec + fock_computed = R * state + eig_computed = (sprepost(evecs, evecs') * R_eig * sprepost(evecs', evecs)) * state + @test isapprox(fock_computed, eig_computed, atol = 1e-15) + end; +end + +@testitem "brterm sprectra function" begin + f(x) = exp(x)/10 + function g(x) + nbar = n_thermal(abs(x), 1) + if x > 0 + return nbar + elseif x < 0 + return 1 + nbar + else + return 0.0 + end + end + + spectra_list = [ + x -> (x>0), # positive frequency filter + x -> one(x), # no filter + f, # smooth filter + g, # thermal field + ] + + N = 5 + H = num(N) + a = destroy(N) + destroy(N)^2/2 + A_op = a+a' + for spectra in spectra_list + R = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = true) + R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = false) + @test isa(R, QuantumObject) + @test isa(R_eig, QuantumObject) + @test isa(evecs, QuantumObject) + + state = rand_dm(N) |> mat2vec + fock_computed = R * state + eig_computed = (sprepost(evecs, evecs') * R_eig * sprepost(evecs', evecs)) * state + @test isapprox(fock_computed, eig_computed, atol = 1e-15) + end +end + +@testitem "simple qubit system" begin + pauli_vectors = [sigmax(), sigmay(), sigmaz()] + γ = 0.25 + spectra(x) = γ * (x>=0) + _m_c_op = √γ * sigmam() + _z_c_op = √γ * sigmaz() + _x_a_op = (sigmax(), spectra) + + arg_sets = [[[_m_c_op], [], [_x_a_op]], [[_m_c_op], [_m_c_op], []], [[_m_c_op, _z_c_op], [_z_c_op], [_x_a_op]]] + + δ = 0 + ϵ = 0.5 * 2π + e_ops = pauli_vectors + H = δ * 0.5 * sigmax() + ϵ * 0.5 * sigmaz() + ψ0 = unit(2basis(2, 0) + basis(2, 1)) + times = LinRange(0, 10, 100) + + for (me_c_ops, brme_c_ops, brme_a_ops) in arg_sets + me = mesolve(H, ψ0, times, me_c_ops, e_ops = e_ops, progress_bar = Val(false)) + brme = brmesolve(H, ψ0, times, brme_a_ops, brme_c_ops, e_ops = e_ops, progress_bar = Val(false)) + + @test all(me.expect .== brme.expect) + end +end; From 47deb2c2d702c3b3ca83ad3b9f6d7add5b24bffb Mon Sep 17 00:00:00 2001 From: Feroz Ahmed Mian Date: Thu, 5 Jun 2025 06:27:38 +0500 Subject: [PATCH 273/329] Implement Bloch Sphere rendering (#472) Co-authored-by: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> --- CHANGELOG.md | 2 + docs/make.jl | 1 + docs/src/resources/api.md | 9 + docs/src/users_guide/extensions/cairomakie.md | 3 +- .../users_guide/plotting_the_bloch_sphere.md | 160 +++++ ext/QuantumToolboxMakieExt.jl | 612 +++++++++++++++++- src/visualization.jl | 352 +++++++++- test/ext-test/cpu/makie/makie_ext.jl | 123 ++++ 8 files changed, 1259 insertions(+), 3 deletions(-) create mode 100644 docs/src/users_guide/plotting_the_bloch_sphere.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e3dea72..d231ec1ae 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 - Introduce `Lanczos` solver for `spectrum`. ([#476]) - Add Bloch-Redfield master equation solver. ([#473]) +- Implement Bloch Sphere rendering. ([#472]) ## [v0.31.1] Release date: 2025-05-16 @@ -231,5 +232,6 @@ Release date: 2024-11-13 [#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 [#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456 [#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460 +[#472]: https://github.com/qutip/QuantumToolbox.jl/issues/472 [#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473 [#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 diff --git a/docs/make.jl b/docs/make.jl index d07b3e2dc..6769ec0ea 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -51,6 +51,7 @@ const PAGES = [ ], "Manipulating States and Operators" => "users_guide/states_and_operators.md", "Tensor Products and Partial Traces" => "users_guide/tensor.md", + "Plotting on the Bloch Sphere" => "users_guide/plotting_the_bloch_sphere.md", "Time Evolution and Dynamics" => [ "Introduction" => "users_guide/time_evolution/intro.md", "Time Evolution Solutions" => "users_guide/time_evolution/solution.md", diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 7f3d49e73..e99247123 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -319,4 +319,13 @@ meshgrid ```@docs plot_wigner plot_fock_distribution +plot_bloch +Bloch +render +add_points! +add_vectors! +add_line! +add_arc! +clear! +add_states! ``` diff --git a/docs/src/users_guide/extensions/cairomakie.md b/docs/src/users_guide/extensions/cairomakie.md index af332ba11..aa4512f06 100644 --- a/docs/src/users_guide/extensions/cairomakie.md +++ b/docs/src/users_guide/extensions/cairomakie.md @@ -19,4 +19,5 @@ The supported plotting functions are listed as follows: | **Plotting Function** | **Description** | |:----------------------|:----------------| | [`plot_wigner`](@ref) | [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) | -| [`plot_fock_distribution`](@ref) | [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution | \ No newline at end of file +| [`plot_fock_distribution`](@ref) | [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution | +| [`plot_bloch`](@ref) | [Plotting on the Bloch Sphere](@ref doc:Plotting-on-the-Bloch-Sphere) | diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md new file mode 100644 index 000000000..44ff42c3e --- /dev/null +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -0,0 +1,160 @@ +# [Plotting on the Bloch Sphere](@id doc:Plotting-on-the-Bloch-Sphere) + +```@setup Bloch_sphere_rendering +using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) +``` + +## [Introduction](@id doc:Bloch_sphere_rendering) + +When studying the dynamics of a two-level system, it's often convenient to visualize the state of the system by plotting the state vector or density matrix on the Bloch sphere. + +In [QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/), this can be done using the [`Bloch`](@ref) or [`plot_bloch`](@ref) methods that provide same syntax as [QuTiP](https://qutip.readthedocs.io/en/stable/guide/guide-bloch.html). + +## Create a Bloch Sphere + +In [QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/), creating a [`Bloch`](@ref) sphere is accomplished by calling either: + +```@example Bloch_sphere_rendering +b = Bloch(); +``` + +which will load an instance of [`Bloch`](@ref). Before getting into the details of these objects, we can simply plot the blank [`Bloch`](@ref) sphere associated with these instances via: + +```@example Bloch_sphere_rendering +fig, _ = render(b); +fig +``` + +## Add a Single Data Point + +As an example, we can add a single data point via [`add_points!`](@ref): + +```@example Bloch_sphere_rendering +pnt = [1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3)]; +add_points!(b, pnt); +fig, _ = render(b); +fig +``` + +## Add a Single Vector + +and then a single vector via [`add_vectors!`](@ref): + +```@example Bloch_sphere_rendering +vec = [0, 1, 0]; +add_vectors!(b, vec) +fig, _ = render(b) +fig +``` + +and then add another vector corresponding to the ``|0\rangle`` state: + +```@example Bloch_sphere_rendering +x = basis(2, 0) +add_states!(b, [x]) +fig, _ = render(b) +fig +``` + +## Add Multiple Vectors + +We can also plot multiple points, vectors, and states at the same time by passing arrays instead of individual elements via [`add_vectors!](@ref). Before giving an example, we can use [`clear!`](@ref) to remove the current data from our [`Bloch`](@ref) sphere instead of creating a new instance: + +```@example Bloch_sphere_rendering +clear!(b) +fig, _ = render(b) +fig +``` + +Now on the same [`Bloch`](@ref) sphere, we can plot the three states via [`add_states!`](@ref) associated with the `x`, `y`, and `z` directions: + +```@example Bloch_sphere_rendering +x = basis(2, 0) + basis(2, 1) +y = basis(2, 0) - im * basis(2, 1) +z = basis(2, 0) +b = Bloch() +add_states!(b, [x, y, z]) +fig, _ = render(b) +fig +``` + +A similar method works for adding vectors: + +```@example Bloch_sphere_rendering +clear!(b) +vecs = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] +add_vectors!(b, vecs) +fig, _ = render(b) +fig +``` + +# Add Arc, Line, and Vector + +You can also add lines and arcs via [`add_line!`](@ref) and [`add_arc!`](@ref) respectively: + +```@example Bloch_sphere_rendering +clear!(b) +vec = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; +add_vectors!(b, vec); +add_line!(b, [1,0,0], [0,1,0]) +add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) +fig, _ = render(b) +fig +``` + +## Add Multiple Points + +Adding multiple points to the [`Bloch`](@ref) sphere works slightly differently than adding multiple states or vectors. For example, lets add a set of `20` points around the equator (after calling [`clear!`](@ref)): + +```@example Bloch_sphere_rendering +th = range(0, 2π; length=20); +clear!(b) +xp = cos.(th); +yp = sin.(th); +zp = zeros(20); +pnts = [xp, yp, zp]; +add_points!(b, pnts); +fig, ax = render(b); +fig +``` + +Notice that, in contrast to states or vectors, each point remains the same color as the initial point. This is because adding multiple data points using [`add_points!`](@ref) is interpreted, by default, to correspond to a single data point (single qubit state) plotted at different times. This is very useful when visualizing the dynamics of a qubit. If we want to plot additional qubit states we can call additional [`add_points!`](@ref): + +## Add Another Set of Points + +```@example Bloch_sphere_rendering +xz = zeros(20); +yz = sin.(th); +zz = cos.(th); +pnts = [xz, yz, zz]; +add_points!(b, pnts); +fig, ax = render(b); +fig +``` + +The color and shape of the data points is varied automatically by [`Bloch`](@ref). Notice how the color and point markers change for each set of data. + +What if we want to vary the color of our points. We can tell [`Bloch`](@ref) to vary the color of each point according to the colors listed in the `point_color` attribute. + +```@example Bloch_sphere_rendering +clear!(b) +xp = cos.(th); +yp = sin.(th); +zp = zeros(20); +pnts = [xp, yp, zp]; +add_points!(b, pnts, meth=:m); +fig, ax = render(b); +fig +``` + +Now, the data points cycle through a variety of predefined colors. Now lets add another set of points, but this time we want the set to be a single color, representing say a qubit going from the ``|0\rangle`` state to the ``|1\rangle`` state in the `y-z` plane: + +```@example Bloch_sphere_rendering +pnts = [xz, yz, zz] ; +add_points!(b, pnts); +fig, ax = render(b); +fig +``` diff --git a/ext/QuantumToolboxMakieExt.jl b/ext/QuantumToolboxMakieExt.jl index 41aefe2b9..352e1ec85 100644 --- a/ext/QuantumToolboxMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -1,8 +1,32 @@ module QuantumToolboxMakieExt using QuantumToolbox +using LinearAlgebra: cross, deg2rad, normalize, size using Makie: - Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, barplot!, GridPosition, @L_str, Reverse, ylims! + Axis, + Axis3, + Colorbar, + Figure, + GridLayout, + heatmap!, + surface!, + barplot!, + GridPosition, + @L_str, + Reverse, + ylims!, + RGBAf, + Sphere, + lines!, + scatter!, + arrows!, + text!, + Point3f, + mesh!, + RGBf, + Vec3f, + Point3f, + NoShading @doc raw""" plot_wigner( @@ -291,4 +315,590 @@ raw""" """ _figFromChildren(::Nothing) = throw(ArgumentError("No Figure has been found at the top of the layout hierarchy.")) +raw""" + _state_to_bloch(state::QuantumObject{<:Ket}) -> Vector{Float64} + +Convert a pure qubit state (`Ket`) to its Bloch vector representation. + +If the state is not normalized, it is automatically normalized before conversion. + +# Arguments +- `state`: A `Ket` representing a pure quantum state. + +# Returns +A 3-element `Vector{Float64}` representing the Bloch vector `[x, y, z]`. + +# Throws +- `ArgumentError` if the state dimension is not 2. +""" +function _state_to_bloch(state::QuantumObject{<:Ket}) + if !isapprox(norm(state), 1.0, atol = 1e-6) + @warn "State is not normalized. Normalizing before Bloch vector conversion." + state = normalize(state) + end + ψ = state.data + if length(ψ) != 2 + error("Bloch sphere visualization is only supported for qubit states (2-level systems)") + end + x = 2 * real(ψ[1] * conj(ψ[2])) + y = 2 * imag(ψ[1] * conj(ψ[2])) + z = abs2(ψ[1]) - abs2(ψ[2]) + return [x, y, z] +end + +raw""" + _dm_to_bloch(ρ::QuantumObject{<:Operator}) -> Vector{Float64} + +Convert a qubit density matrix (`Operator`) to its Bloch vector representation. + +This function assumes the input is Hermitian. If the density matrix is not Hermitian, a warning is issued. + +# Arguments +- `ρ`: A density matrix (`Operator`) representing a mixed or pure quantum state. + +# Returns +A 3-element `Vector{Float64}` representing the Bloch vector `[x, y, z]`. + +# Throws +- `ArgumentError` if the matrix dimension is not 2. +""" +function _dm_to_bloch(ρ::QuantumObject{<:Operator}) + if !ishermitian(ρ) + @warn "Density matrix is not Hermitian. Results may not be meaningful." + end + if size(ρ, 1) != 2 + error("Bloch sphere visualization is only supported for qubit states (2-level systems)") + end + x = real(ρ[1, 2] + ρ[2, 1]) + y = imag(ρ[2, 1] - ρ[1, 2]) + z = real(ρ[1, 1] - ρ[2, 2]) + return [x, y, z] +end + +function _render_bloch_makie(bloch_vec::Vector{Float64}; location = nothing, kwargs...) + b = Bloch() + add_vectors!(b, bloch_vec) + fig, location = _getFigAndLocation(location) + fig, ax = render(b; location = location, kwargs...) + return fig, ax +end + +@doc raw""" + add_line!( + b::QuantumToolbox.Bloch, + start_point_point::QuantumToolbox.QuantumObject{<:Union{QuantumToolbox.Ket, QuantumToolbox.Bra, QuantumToolbox.Operator}}, + end_point::QuantumToolbox.QuantumObject{<:Union{QuantumToolbox.Ket, QuantumToolbox.Bra, QuantumToolbox.Operator}}; + fmt = "k" + ) + +Add a line between two quantum states or operators on the Bloch sphere visualization. + +# Arguments + +- `b::Bloch`: The Bloch sphere object to modify. +- `start_point_point::QuantumObject`: The start_point_pointing quantum state or operator. Can be a `Ket`, `Bra`, or `Operator`. +- `end_point::QuantumObject`: The ending quantum state or operator. Can be a `Ket`, `Bra`, or `Operator`. +- `fmt::String="k"`: (optional) A format string specifying the line style and color (default is black `"k"`). + +# Description + +This function converts the given quantum objects (states or operators) into their Bloch vector representations and adds a line between these two points on the Bloch sphere visualization. + +# Example + +```julia +b = Bloch() +ψ₁ = normalize(basis(2, 0) + basis(2, 1)) +ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) +add_line!(b, ψ₁, ψ₂; fmt = "r--") +``` +""" +function QuantumToolbox.add_line!( + b::Bloch, + p1::QuantumObject{<:Union{Ket,Bra,Operator}}, + p2::QuantumObject{<:Union{Ket,Bra,Operator}}; + fmt = "k", +) + coords1 = isket(p1) || isbra(p1) ? _state_to_bloch(p1) : _dm_to_bloch(p1) + coords2 = isket(p2) || isbra(p2) ? _state_to_bloch(p2) : _dm_to_bloch(p2) + return add_line!(b, coords1, coords2; fmt = fmt) +end + +@doc raw""" + QuantumToolbox.add_states!(b::Bloch, states::QuantumObject...) + +Add one or more quantum states to the Bloch sphere visualization by converting them into Bloch vectors. + +# Arguments +- `b::Bloch`: The Bloch sphere object to modify +- `states::QuantumObject...`: One or more quantum states (Ket, Bra, or Operator) + +# Example + +```julia +x = basis(2, 0) + basis(2, 1); +y = basis(2, 0) - im * basis(2, 1); +z = basis(2, 0); +b = Bloch(); +add_states!(b, [x, y, z]) +``` +""" +function QuantumToolbox.add_states!(b::Bloch, states::Vector{<:QuantumObject}) + vecs = map(states) do state + if isket(state) || isbra(state) + return _state_to_bloch(state) + else + return _dm_to_bloch(state) + end + end + append!(b.vectors, [normalize(v) for v in vecs]) + return b.vectors +end + +@doc raw""" + render(b::QuantumToolbox.Bloch; location=nothing) + +Render the Bloch sphere visualization from the given `Bloch` object `b`. + +# Arguments + +- `b::QuantumToolbox.Bloch` + The Bloch sphere object containing states, vectors, and settings to visualize. + +- `location` (optional) + Specifies where to display or save the rendered figure. + - If `nothing` (default), the figure is displayed interactively. + - If a file path (String), the figure is saved to the specified location. + - Other values depend on backend support. + +# Returns + +- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. + These can be further manipulated or saved by the user. +""" +function QuantumToolbox.render(b::Bloch; location = nothing) + fig, ax = _setup_bloch_plot!(b, location) + _draw_bloch_sphere!(b, ax) + _draw_reference_circles!(ax) + _draw_axes!(ax) + _plot_points!(b, ax) + _plot_lines!(b, ax) + _plot_arcs!(b, ax) + _plot_vectors!(b, ax) + _add_labels!(b, ax) + return fig, ax +end + +raw""" + _setup_bloch_plot!(b::Bloch, location) -> (fig, ax) + +Initialize the figure and `3D` axis for Bloch sphere visualization. + +# Arguments +- `b::Bloch`: Bloch sphere object containing view parameters +- `location`: Figure layout position specification + +# Returns +- `fig`: Created Makie figure +- `ax`: Configured Axis3 object + +Sets up the `3D` coordinate system with appropriate limits and view angles. +""" +function _setup_bloch_plot!(b::Bloch, location) + fig, location = _getFigAndLocation(location) + bg_color = parse(RGBf, b.frame_color) + frame_color = RGBAf(bg_color, b.frame_alpha) + ax = Axis3( + location; + aspect = :data, + limits = (-b.frame_limit, b.frame_limit, -b.frame_limit, b.frame_limit, -b.frame_limit, b.frame_limit), + xgridvisible = false, + ygridvisible = false, + zgridvisible = false, + xticklabelsvisible = false, + yticklabelsvisible = false, + zticklabelsvisible = false, + xticksvisible = false, + yticksvisible = false, + zticksvisible = false, + xlabel = "", + ylabel = "", + zlabel = "", + backgroundcolor = frame_color, + xypanelvisible = false, + xzpanelvisible = false, + yzpanelvisible = false, + xspinesvisible = false, + yspinesvisible = false, + zspinesvisible = false, + protrusions = (0, 0, 0, 0), + viewmode = :fit, + ) + ax.azimuth[] = deg2rad(b.view_angles[1]) + ax.elevation[] = deg2rad(b.view_angles[2]) + return fig, ax +end + +raw""" + _draw_bloch_sphere!(b, ax) + +Draw the translucent sphere representing the Bloch sphere surface. +""" +function _draw_bloch_sphere!(b::Bloch, ax) + n_lon = 4 + n_lat = 4 + radius = 1.0f0 + base_color = parse(RGBf, b.sphere_color) + sphere_color = RGBAf(base_color, b.sphere_alpha) + sphere_mesh = Sphere(Point3f(0), radius) + mesh!(ax, sphere_mesh; color = sphere_color, shading = NoShading, transparency = true) + θ_vals = range(0.0f0, 2π, length = n_lon + 1)[1:(end-1)] + φ_curve = range(0.0f0, π, length = 600) + line_alpha = max(0.05, b.sphere_alpha * 0.5) + for θi in θ_vals + x_line = [radius * sin(ϕ) * cos(θi) for ϕ in φ_curve] + y_line = [radius * sin(ϕ) * sin(θi) for ϕ in φ_curve] + z_line = [radius * cos(ϕ) for ϕ in φ_curve] + lines!(ax, x_line, y_line, z_line; color = RGBAf(0.5, 0.5, 0.5, line_alpha), linewidth = 1, transparency = true) + end + φ_vals = range(0.0f0, π, length = n_lat + 2) + θ_curve = range(0.0f0, 2π, length = 600) + for ϕ in φ_vals + x_ring = [radius * sin(ϕ) * cos(θi) for θi in θ_curve] + y_ring = [radius * sin(ϕ) * sin(θi) for θi in θ_curve] + z_ring = fill(radius * cos(ϕ), length(θ_curve)) + lines!(ax, x_ring, y_ring, z_ring; color = RGBAf(0.5, 0.5, 0.5, line_alpha), linewidth = 1, transparency = true) + end +end + +raw""" + _draw_reference_circles!(ax) + +Draw the three great circles `(XY, YZ, XZ planes)` on the Bloch sphere. + +# Arguments +- `ax`: Makie Axis3 object for drawing + +Adds faint circular guidelines representing the three principal planes. +""" +function _draw_reference_circles!(ax) + wire_color = RGBAf(0.5, 0.5, 0.5, 0.4) + φ = range(0, 2π, length = 100) + # XY, YZ, XZ circles + circles = [ + [Point3f(sin(φi), -cos(φi), 0) for φi in φ], # XY + [Point3f(0, -cos(φi), sin(φi)) for φi in φ], # YZ + [Point3f(sin(φi), 0, cos(φi)) for φi in φ], # XZ + ] + for circle in circles + lines!(ax, circle; color = wire_color, linewidth = 1.0) + end +end + +raw""" + _draw_axes!(ax) + +Draw the three principal axes `(x, y, z)` of the Bloch sphere. + +# Arguments +- `ax`: Makie Axis3 object for drawing + +Creates visible axis lines extending slightly beyond the unit sphere. +""" +function _draw_axes!(ax) + axis_color = RGBAf(0.3, 0.3, 0.3, 0.8) + axis_width = 0.8 + axes = [ + ([Point3f(0, -1.0, 0), Point3f(0, 1.0, 0)], "y"), # Y-axis + ([Point3f(-1.0, 0, 0), Point3f(1.0, 0, 0)], "x"), # X-axis + ([Point3f(0, 0, -1.0), Point3f(0, 0, 1.0)], "z"), # Z-axis + ] + for (points, _) in axes + lines!(ax, points; color = axis_color, linewidth = axis_width) + end +end + +raw""" + _plot_points!(b::Bloch, ax) + +Plot all quantum state points on the Bloch sphere. + +# Arguments +- `b::Bloch`: Contains point data and styling information +- `ax`: Axis3 object for plotting + +Handles both scatter points and line traces based on style specifications. +""" +function _plot_points!(b::Bloch, ax) + for k in 1:length(b.points) + pts = b.points[k] + style = b.point_style[k] + alpha = b.point_alpha[k] + marker = b.point_marker[mod1(k, length(b.point_marker))] + N = size(pts, 2) + + raw_x = pts[2, :] + raw_y = -pts[1, :] + raw_z = pts[3, :] + + ds = vec(sqrt.(sum(abs2, pts; dims = 1))) + if !all(isapprox.(ds, ds[1]; rtol = 1e-12)) + indperm = sortperm(ds) + else + indperm = collect(1:N) + end + this_color = b.point_color[k] + if style == :m + defaults = b.point_default_color + L = length(defaults) + times = ceil(Int, N / L) + big_colors = repeat(b.point_default_color, times)[1:N] + big_colors = big_colors[indperm] + colors = big_colors + else + if this_color === nothing + defaults = b.point_default_color + colors = defaults[mod1(k, length(defaults))] + else + colors = this_color + end + end + if style in (:s, :m) + xs = raw_x[indperm] + ys = raw_y[indperm] + zs = raw_z[indperm] + scatter!( + ax, + xs, + ys, + zs; + color = colors, + markersize = b.point_size[mod1(k, length(b.point_size))], + marker = marker, + transparency = alpha < 1.0, + alpha = alpha, + strokewidth = 0.0, + ) + + elseif style == :l + xs = raw_x + ys = raw_y + zs = raw_z + c = isa(colors, Vector) ? colors[1] : colors + lines!(ax, xs, ys, zs; color = c, linewidth = 2.0, transparency = alpha < 1.0, alpha = alpha) + end + end +end + +raw""" + _plot_lines!(b::Bloch, ax) + +Draw all connecting lines between points on the Bloch sphere. + +# Arguments +- `b::Bloch`: Contains line data and formatting +- `ax`: Axis3 object for drawing + +Processes line style specifications and color mappings. +""" +function _plot_lines!(b::Bloch, ax) + color_map = + Dict("k" => :black, "r" => :red, "g" => :green, "b" => :blue, "c" => :cyan, "m" => :magenta, "y" => :yellow) + for (line, fmt) in b.lines + x, y, z = line + color_char = first(fmt) + color = get(color_map, color_char, :black) + linestyle = if occursin("--", fmt) + :dash + elseif occursin(":", fmt) + :dot + elseif occursin("-.", fmt) + :dashdot + else + :solid + end + lines!(ax, x, y, z; color = color, linewidth = 1.0, linestyle = linestyle) + end +end + +raw""" + _plot_arcs!(b::Bloch, ax) + +Draw circular arcs connecting points on the Bloch sphere surface. + +# Arguments +- `b::Bloch`: Contains arc data points +- `ax`: Axis3 object for drawing + +Calculates great circle arcs between specified points. +""" +function _plot_arcs!(b::Bloch, ax) + for arc_pts in b.arcs + length(arc_pts) >= 2 || continue + v1 = normalize(arc_pts[1]) + v2 = normalize(arc_pts[end]) + n = normalize(cross(v1, v2)) + θ = acos(clamp(dot(v1, v2), -1.0, 1.0)) + if length(arc_pts) == 3 + vm = normalize(arc_pts[2]) + dot(cross(v1, vm), n) < 0 && (θ = 2π - θ) + end + t_range = range(0, θ, length = 100) + arc_points = [Point3f((v1*cos(t) + cross(n, v1)*sin(t))...) for t in t_range] + lines!(ax, arc_points; color = RGBAf(0.8, 0.4, 0.1, 0.9), linewidth = 2.0, linestyle = :solid) + end +end + +raw""" + _plot_vectors!(b::Bloch, ax) + +Draw vectors from origin representing quantum states. + +# Arguments +- `b::Bloch`: Contains vector data +- `ax`: Axis3 object for drawing + +Scales vectors appropriately and adds `3D` arrow markers. +""" +function _plot_vectors!(b::Bloch, ax) + isempty(b.vectors) && return + arrowsize_vec = Vec3f(b.vector_arrowsize...) + r = 1.0 + for (i, v) in enumerate(b.vectors) + color = get(b.vector_color, i, RGBAf(0.2, 0.5, 0.8, 0.9)) + vec = Vec3f(v[2], -v[1], v[3]) + length = norm(vec) + max_length = r * 0.90 + vec = length > max_length ? (vec/length) * max_length : vec + arrows!( + ax, + [Point3f(0, 0, 0)], + [vec], + color = color, + linewidth = b.vector_width, + arrowsize = arrowsize_vec, + arrowcolor = color, + ) + end +end + +raw""" + _add_labels!(ax) + +Add axis labels and state labels to the Bloch sphere. + +# Arguments +- `ax`: Axis3 object for text placement + +Positions standard labels `(x, y, |0⟩, |1⟩)` at appropriate locations. +""" +function _add_labels!(b::Bloch, ax) + label_color = parse(RGBf, b.font_color) + label_size = b.font_size + offset_scale = b.frame_limit + if !isempty(b.xlabel) && !isempty(b.xlabel[1]) + text!( + ax, + L"\textbf{x}", + position = Point3f(0, -offset_scale * b.xlpos[1], 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + end + if length(b.xlabel) > 1 && !isempty(b.xlabel[2]) + text!( + ax, + L"\textbf{-x}", + position = Point3f(0, -offset_scale * b.xlpos[2], 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + end + if !isempty(b.ylabel) && !isempty(b.ylabel[1]) + text!( + ax, + L"\textbf{y}", + position = Point3f(offset_scale * b.ylpos[1], 0, 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + end + if length(b.ylabel) > 1 && !isempty(b.ylabel[2]) + text!( + ax, + L"\textbf{-y}", + position = Point3f(offset_scale * b.ylpos[2], 0, 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + end + if !isempty(b.zlabel) && !isempty(b.zlabel[1]) + text!( + ax, + L"\mathbf{|0\rangle}", + position = Point3f(0, 0, offset_scale * b.zlpos[1]), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + end + if length(b.zlabel) > 1 && !isempty(b.zlabel[2]) + text!( + ax, + L"\mathbf{|1\rangle}", + position = Point3f(0, 0, offset_scale * b.zlpos[2]), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + end +end + +@doc raw""" + plot_bloch(::Val{:Makie}, state::QuantumObject{<:Union{Ket,Bra}}; kwargs...) + +Plot a pure quantum state on the Bloch sphere using the `Makie` backend. + +# Arguments +- `state::QuantumObject{<:Union{Ket,Bra}}`: The quantum state to be visualized (`ket` or `bra`). +- `kwargs...`: Additional keyword arguments passed to `_render_bloch_makie`. + +# Details + +Converts the state to its Bloch vector representation and renders it on the Bloch sphere. +If the input is a bra, it is automatically converted to a ket before processing. + +!!! note "Internal function" + This is the `Makie`-specific implementation called by the main `plot_bloch` function. +""" +function QuantumToolbox.plot_bloch(::Val{:Makie}, state::QuantumObject{<:Union{Ket,Bra}}; kwargs...) + state = isbra(state) ? state' : state + bloch_vec = _state_to_bloch(state) + return _render_bloch_makie(bloch_vec; kwargs...) +end + +@doc raw""" + plot_bloch(::Val{:Makie}, ρ::QuantumObject{<:Operator}; kwargs...) + +Plot a density matrix on the Bloch sphere using the Makie backend. + +# Arguments +- `ρ::QuantumObject{<:Operator}`: The density matrix to be visualized. +- `kwargs...`: Additional keyword arguments passed to `_render_bloch_makie`. + +# Details +Converts the density matrix to its Bloch vector representation and renders it on the Bloch sphere. + +!!! note "Internal function" + This is the Makie-specific implementation called by the main `plot_bloch` function. +""" +function QuantumToolbox.plot_bloch(::Val{:Makie}, ρ::QuantumObject{<:Operator}; kwargs...) + bloch_vec = _dm_to_bloch(ρ) + return _render_bloch_makie(bloch_vec; kwargs...) +end + end diff --git a/src/visualization.jl b/src/visualization.jl index e43fb10dd..71c6fc499 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -1,4 +1,14 @@ -export plot_wigner, plot_fock_distribution +export plot_wigner, + plot_fock_distribution, + plot_bloch, + Bloch, + render, + add_points!, + add_vectors!, + add_line!, + add_arc!, + clear!, + add_states! @doc raw""" plot_wigner( @@ -61,3 +71,343 @@ plot_fock_distribution( plot_fock_distribution(::Val{T}, ρ::QuantumObject{SType}; kwargs...) where {T,SType<:Union{Bra,Ket,Operator}} = throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) + +@doc raw""" + Bloch() + +A structure representing a Bloch sphere visualization for quantum states. + +# Fields + +## Data storage +- `points::Vector{Matrix{Float64}}`: Points to plot on the Bloch sphere (3D coordinates) +- `vectors::Vector{Vector{Float64}}}`: Vectors to plot on the Bloch sphere +- `lines::Vector{Tuple{Vector{Vector{Float64}},String,Dict{Any,Any}}}`: Lines to draw on the sphere (points, style, properties) +- `arcs::Vector{Vector{Vector{Float64}}}}`: Arcs to draw on the sphere + +## Style properties + +- `font_color::String`: Color of axis labels and text +- `font_size::Int`: Font size for labels (default: 15) +- `frame_alpha::Float64`: Transparency of the frame background +- `frame_color::String`: Background color of the frame +- `frame_limit::Float64`: Axis limits for the 3D frame (symmetric around origin) + +## Point properties + +- `point_default_color::Vector{String}}`: Default color cycle for points +- `point_color::Vector{String}}`: Colors for point markers +- `point_marker::Vector{Symbol}}`: Marker shapes (default: [:circle, :rect, :diamond, :utriangle]) +- `point_size::Vector{Int}}`: Marker sizes +- `point_style::Vector{Symbol}}`: Marker styles +- `point_alpha::Vector{Float64}}`: Marker transparencies + +## Sphere properties + +- `sphere_color::String`: Color of Bloch sphere surface +- `sphere_alpha::Float64`: Transparency of sphere surface (default: 0.2) + +# Vector properties + +- `vector_color`::Vector{String}: Colors for vectors +- `vector_width`::Float64: Width of vectors +- `vector_arrowsize`::NTuple{3, Real}: Arrow size parameters as (head length, head width, stem width) + +## Layout properties + +- `view_angles::Tuple{Int,Int}}`: Azimuthal and elevation viewing angles in degrees (default: (-60, 30)) + +## Label properties +- `xlabel::Vector{AbstractString}}`: Labels for x-axis (default: [L"x", ""]) +- `xlpos::Vector{Float64}}`: Positions of x-axis labels (default: [1.0, -1.0]) +- `ylabel::Vector{AbstractString}}`: Labels for y-axis (default: [L"y", ""]) +- `ylpos::Vector{Float64}}`: Positions of y-axis labels (default: [1.0, -1.0]) +- `zlabel::Vector{AbstractString}}`: Labels for z-axis (default: [L"|0\rangle", L"|1\rangle"]) +- `zlpos::Vector{Float64}}`: Positions of z-axis labels (default: [1.0, -1.0]) +""" +@kwdef mutable struct Bloch + points::Vector{Matrix{Float64}} = Vector{Matrix{Float64}}() + vectors::Vector{Vector{Float64}} = Vector{Vector{Float64}}() + lines::Vector{Tuple{Vector{Vector{Float64}},String}} = Vector{Tuple{Vector{Vector{Float64}},String}}() + arcs::Vector{Vector{Vector{Float64}}} = Vector{Vector{Vector{Float64}}}() + font_color::String = "#333333" + font_size::Int = 15 + frame_alpha::Float64 = 0.0 + frame_color::String = "white" + frame_limit::Float64 = 1.14 + point_default_color::Vector{String} = ["blue", "red", "green", "orange"] + point_color::Vector{Union{Nothing,String}} = Union{Nothing,String}[] + point_marker::Vector{Symbol} = [:circle, :rect, :diamond, :utriangle] + point_size::Vector{Float64} = [5.5, 6.2, 6.5, 7.5] + point_style::Vector{Symbol} = Symbol[] + point_alpha::Vector{Float64} = Float64[] + sphere_alpha::Float64 = 0.2 + sphere_color::String = "#FFDDDD" + vector_color::Vector{String} = ["green", "orange", "blue", "red"] + vector_width::Float64 = 0.025 + vector_arrowsize::NTuple{3,Real} = (0.07, 0.08, 0.08) + view_angles::Tuple{Int,Int} = (-60, 30) + xlabel::Vector{AbstractString} = ["x", ""] + xlpos::Vector{Float64} = [1.0, -1.0] + ylabel::Vector{AbstractString} = ["y", ""] + ylpos::Vector{Float64} = [1.0, -1.0] + zlabel::Vector{AbstractString} = ["|0\rangle", "|1\rangle"] + zlpos::Vector{Float64} = [1.0, -1.0] +end + +@doc raw""" + add_vectors!(b::Bloch, vec::Vector{<:Real}) + +Add a single normalized vector to the Bloch sphere visualization. + +# Arguments +- `b::Bloch`: The Bloch sphere object to modify +- `vec::Vector{<:Real}`: A 3D vector to add (will be normalized) +- `vecs::Vector{<:Vector{<:Real}}}`: List of 3D vectors to add (each will be normalized) + +# Example +```jldoctest +julia> b = Bloch(); + +julia> add_vectors!(b, [1, 0, 0]) +1-element Vector{Vector{Float64}}: + [1.0, 0.0, 0.0] +``` + +We can also add multiple normalized vectors to the Bloch sphere visualization. + +```jldoctest +julia> b = Bloch(); + +julia> add_vectors!(b, [[1, 0, 0], [0, 1, 0]]) +2-element Vector{Vector{Float64}}: + [1.0, 0.0, 0.0] + [0.0, 1.0, 0.0] +``` +""" +function add_vectors!(b::Bloch, vec::Vector{<:Real}) + normalized_vec = normalize(convert(Vector{Float64}, vec)) + return push!(b.vectors, normalized_vec) +end +function add_vectors!(b::Bloch, vecs::Vector{<:Vector{<:Real}}) + return append!(b.vectors, [normalize(convert(Vector{Float64}, v)) for v in vecs]) +end + +@doc raw""" + add_points!(b::Bloch, pnt::Vector{<:Real}; meth::Symbol = :s, color = "blue", alpha = 1.0) + +Add a single point to the Bloch sphere visualization. + +# Arguments +- b::Bloch: The Bloch sphere object to modify +- pnt::Vector{Float64}: A 3D point to add +- meth::Symbol=:s: Display method (:s for single point, :m for multiple, :l for line) +- color: Color of the point (defaults to first default color if nothing) +- alpha=1.0: Transparency (1.0 = opaque, 0.0 = transparent) +""" +function add_points!(b::Bloch, pnt::Vector{Float64}; meth::Symbol = :s, color = nothing, alpha = 1.0) + return add_points!(b, reshape(pnt, 3, 1); meth, color, alpha) +end +function add_points!(b::Bloch, pnts::Vector{Vector{Float64}}; meth::Symbol = :s, color = nothing, alpha = 1.0) + return add_points!(b, Matrix(hcat(pnts...)'); meth, color, alpha) +end + +@doc raw""" + add_points!(b::Bloch, pnts::Matrix{Float64}; meth::Symbol = :s, color = nothing, alpha = 1.0) + +Add multiple points to the Bloch sphere visualization. + +# Arguments + +- b::Bloch: The Bloch sphere object to modify +- pnts::Matrix{Float64}: 3×N matrix of points (each column is a point) +- meth::Symbol=:s: Display method (:s for single point, :m for multiple, :l for line) +- color: Color of the points (defaults to first default color if nothing) +- alpha=1.0: Transparency (1.0 = opaque, 0.0 = transparent) +``` +""" +function add_points!( + b::Bloch, + pnts::Matrix{<:Real}; + meth::Symbol = :s, + color::Union{Nothing,String} = nothing, + alpha::Float64 = 1.0, +) + if size(pnts, 1) != 3 + error("Points must be a 3×N matrix where each column is [x; y; z]") + end + if !(meth in (:s, :m, :l)) + error("`meth` must be :s, :m, or :l") + end + push!(b.points, convert(Matrix{Float64}, pnts)) + push!(b.point_style, meth) + push!(b.point_alpha, alpha) + if color === nothing + push!(b.point_color, nothing) + else + push!(b.point_color, color) + end + return nothing +end + +@doc raw""" + add_line!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}; fmt = "k", kwargs...) + +Add a line between two points on the Bloch sphere. + +# Arguments +- b::Bloch: The Bloch sphere object to modify +- p1::Vector{<:Real}: First 3D point +- p2::Vector{<:Real}: Second 3D point +- fmt="k": Line format string (matplotlib style) +""" +function add_line!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}; fmt = "k") + if length(p1) != 3 || length(p2) != 3 + error("Points must be 3D vectors") + end + x = [p1[2], p2[2]] + y = [-p1[1], -p2[1]] + z = [p1[3], p2[3]] + push!(b.lines, (([x, y, z]), fmt)) + return b +end + +@doc raw""" + add_arc!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}, p3::Vector{<:Real}) + +Add a circular arc through three points on the Bloch sphere. + +# Arguments + +- b::Bloch: The Bloch sphere object to modify +- p1::Vector{<:Real}: First 3D point +- p2::Vector{<:Real}: Second 3D point (middle point) +- p3::Vector{<:Real}: Third 3D point + +# Examples + +```jldoctest +julia> b = Bloch(); + +julia> add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) +1-element Vector{Vector{Vector{Float64}}}: + [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] +``` +""" +function add_arc!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}) + return push!(b.arcs, [convert(Vector{Float64}, p1), convert(Vector{Float64}, p2)]) +end +function add_arc!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}, p3::Vector{<:Real}) + return push!(b.arcs, [convert(Vector{Float64}, p1), convert(Vector{Float64}, p2), convert(Vector{Float64}, p3)]) +end + +@doc raw""" + QuantumToolbox.add_states!(b::Bloch, states::QuantumObject...) + +Add one or more quantum states to the Bloch sphere visualization by converting them into Bloch vectors. + +# Arguments + +- `b::Bloch`: The Bloch sphere object to modify +- `states::QuantumObject...`: One or more quantum states (Ket, Bra, or Operator) + +""" +function add_states! end + +@doc raw""" + clear!(b::Bloch) + +Clear all graphical elements (points, vectors, lines, arcs) from the given Bloch sphere object `b`. + +# Arguments + +- `b::Bloch` + The Bloch sphere instance whose contents will be cleared. + +# Returns + +- The updated `Bloch` object `b` with all points, vectors, lines, and arcs removed. +""" +function clear!(b::Bloch) + empty!(b.points) + empty!(b.point_color) + empty!(b.point_style) + empty!(b.point_alpha) + empty!(b.vectors) + empty!(b.lines) + empty!(b.arcs) + return b +end + +@doc raw""" + render(b::QuantumToolbox.Bloch; location=nothing) + +Render the Bloch sphere visualization from the given `Bloch` object `b`. + +# Arguments + +- `b::QuantumToolbox.Bloch` + The Bloch sphere object containing states, vectors, and settings to visualize. + +- `location` (optional) + Specifies where to display or save the rendered figure. + - If `nothing` (default), the figure is displayed interactively. + - If a file path (String), the figure is saved to the specified location. + - Other values depend on backend support. + +# Returns + +- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. + These can be further manipulated or saved by the user. +""" +function render end + +@doc raw""" + plot_bloch( + state::QuantumObject{<:Union{Ket,Bra,Operator}}; + library::Union{Symbol, Val} = :Makie, + kwargs... + ) + +Plot the state of a two-level quantum system on the Bloch sphere. + +The `library` keyword argument specifies the plotting backend to use. The default is `:Makie`, which uses the [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) plotting library. This function internally dispatches to a type-stable version based on `Val(:Makie)` or other plotting backends. + +# Arguments +- `state::QuantumObject`: The quantum state to be visualized. Can be a ket, bra, or operator. +- `library::Union{Symbol, Val}`: The plotting backend, either as a `Symbol` (e.g. `:Makie`) or a `Val` (e.g. `Val(:Makie)`). Default is `:Makie`. +- `kwargs...`: Additional keyword arguments passed to the specific plotting implementation. + +!!! note "Import library first" + The plotting backend library must be imported before use. + +!!! warning "Beware of type-stability!" + For improved performance and type-stability, prefer passing `Val(:Makie)` instead of `:Makie`. See [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) for details. +""" +function plot_bloch(state::QuantumObject{<:Union{Ket,Bra,Operator}}; library::Union{Symbol,Val} = :Makie, kwargs...) + lib_val = library isa Symbol ? Val(library) : library + return plot_bloch(lib_val, state; kwargs...) +end + +@doc raw""" + plot_bloch(::Val{T}, state::QuantumObject; kwargs...) where {T} + +Fallback implementation for unsupported plotting backends. + +# Arguments +- `::Val{T}`: The unsupported backend specification. +- `state::QuantumObject`: The quantum state that was attempted to be plotted. +- `kwargs...`: Ignored keyword arguments. + +# Throws +- `ErrorException`: Always throws an error indicating the backend `T` is unsupported. + +# Note +This function serves as a fallback when an unsupported backend is requested. Currently supported backends include: +- `:Makie` (using `Makie.jl`) + +See the main `plot_bloch` documentation for supported backends. +""" +function plot_bloch(::Val{T}, state::QuantumObject; kwargs...) where {T} + return error("Unsupported backend: $T. Try :Makie or another supported library.") +end diff --git a/test/ext-test/cpu/makie/makie_ext.jl b/test/ext-test/cpu/makie/makie_ext.jl index 4f68e696f..7887c9da0 100644 --- a/test/ext-test/cpu/makie/makie_ext.jl +++ b/test/ext-test/cpu/makie/makie_ext.jl @@ -61,3 +61,126 @@ pos = fig[2, 3] fig1, ax = @test_logs (:warn,) plot_fock_distribution(ψ * 2; library = Val(:Makie), location = pos) end + +@testset "Makie Bloch sphere" begin + ρ = 0.7*ket2dm(basis(2, 0)) + 0.3*ket2dm(basis(2, 1)) + fig, ax = plot_bloch(ρ) + @test fig isa Figure + @test ax isa Axis3 + + ψ = (basis(2, 0) + basis(2, 1))/√2 + fig, ax = plot_bloch(ψ) + @test fig isa Figure + @test ax isa Axis3 + + ϕ = dag(ψ) + fig, ax = plot_bloch(ϕ) + @test fig isa Figure + @test ax isa Axis3 + + fig = Figure() + pos = fig[1, 1] + fig1, ax = plot_bloch(ψ; location = pos) + @test fig1 === fig + + b = Bloch() + add_points!(b, [0.0, 0.0, 1.0]) + @test length(b.points) == 1 + @test b.points[1] ≈ [0.0, 0.0, 1.0] + + pts = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] + add_points!(b, hcat(pts...)) + @test length(b.points) == 2 + @test b.points[2] ≈ hcat(pts...) + + b = Bloch() + add_vectors!(b, [1.0, 1.0, 0.0]) + @test length(b.vectors) == 1 + @test isapprox(norm(b.vectors[1]), 1.0) + + vecs = [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]] + add_vectors!(b, vecs) + @test length(b.vectors) == 3 + @test all(norm(v) ≈ 1.0 for v in b.vectors) + + b = Bloch() + add_line!(b, [0, 0, 0], [1, 0, 0]) + @test length(b.lines) == 1 + @test b.lines[1][1][3] ≈ [0.0, 0.0] + + add_arc!(b, [0, 0, 1], [0, 1, 0], [1, 0, 0]) + @test length(b.arcs) == 1 + @test b.arcs[1][3] == [1.0, 0.0, 0.0] + + b = Bloch() + add_points!(b, [0.0, 0.0, 1.0]) + add_vectors!(b, [1.0, 0.0, 0.0]) + add_line!(b, [0, 0, 0], [1, 0, 0]) + add_arc!(b, [0, 1, 0], [0, 0, 1], [1, 0, 0]) + clear!(b) + @test isempty(b.points) + @test isempty(b.vectors) + @test isempty(b.lines) + @test isempty(b.arcs) + b = Bloch() + add_points!(b, hcat([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0])) + add_vectors!(b, [[1, 1, 0], [0, 1, 1]]) + add_line!(b, [0, 0, 0], [1, 1, 1]) + add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) + try + fig, ax = QuantumToolbox.render(b) + @test !isnothing(fig) + @test !isnothing(ax) + catch e + @test false + @info "Render threw unexpected error" exception=e + end + b = Bloch() + ψ₁ = normalize(basis(2, 0) + basis(2, 1)) + ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) + add_line!(b, ψ₁, ψ₂; fmt = "r--") + try + fig, ax = QuantumToolbox.render(b) + @test !isnothing(fig) + @test !isnothing(ax) + catch e + @test false + @info "Render threw unexpected error" exception=e + end + b = Bloch() + x = basis(2, 0) + basis(2, 1) + y = basis(2, 0) - im * basis(2, 1) + z = basis(2, 0) + add_states!(b, [x, y, z]) + th = range(0, 2π; length = 20); + xp = cos.(th); + yp = sin.(th); + zp = zeros(20); + pnts = [xp, yp, zp]; + pnts = Matrix(hcat(xp, yp, zp)'); + add_points!(b, pnts); + vec = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; + add_vectors!(b, vec); + add_line!(b, [1, 0, 0], [0, 1, 0]) + add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) + try + fig, ax = render(b) + @test !isnothing(fig) + @test !isnothing(ax) + catch e + @test false + @info "Render threw unexpected error" exception=e + end + b = Bloch() + ψ₁ = normalize(basis(2, 0) + basis(2, 1)) + ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) + add_line!(b, ψ₁, ψ₂; fmt = "r--") + try + fig, ax = render(b) + @test !isnothing(fig) + @test !isnothing(ax) + catch e + @test false + @info "Render threw unexpected error" exception=e + end +end From 64ec15dea9f27fb8bae7c1558b6edcbda6c474bd Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 6 Jun 2025 01:26:23 +0800 Subject: [PATCH 274/329] Improve `Bloch` sphere code structure and docs (#480) Co-authored-by: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> --- CHANGELOG.md | 3 +- Project.toml | 2 + docs/make.jl | 6 +- docs/src/resources/api.md | 11 +- .../users_guide/plotting_the_bloch_sphere.md | 149 ++++--- ext/QuantumToolboxMakieExt.jl | 336 ++++----------- src/QuantumToolbox.jl | 1 + src/settings.jl | 4 +- src/visualization.jl | 396 ++++++++++++------ test/ext-test/cpu/makie/makie_ext.jl | 46 +- 10 files changed, 512 insertions(+), 442 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d231ec1ae..49e8cbeef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Introduce `Lanczos` solver for `spectrum`. ([#476]) - Add Bloch-Redfield master equation solver. ([#473]) -- Implement Bloch Sphere rendering. ([#472]) +- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480]) ## [v0.31.1] Release date: 2025-05-16 @@ -235,3 +235,4 @@ Release date: 2024-11-13 [#472]: https://github.com/qutip/QuantumToolbox.jl/issues/472 [#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473 [#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 +[#480]: https://github.com/qutip/QuantumToolbox.jl/issues/480 diff --git a/Project.toml b/Project.toml index 3159c0b3a..6f53877f9 100644 --- a/Project.toml +++ b/Project.toml @@ -12,6 +12,7 @@ Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895" +LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" @@ -51,6 +52,7 @@ GPUArrays = "10, 11" Graphs = "1.7" IncompleteLU = "0.2" KernelAbstractions = "0.9.2" +LaTeXStrings = "1.2" LinearAlgebra = "1" LinearSolve = "2, 3" Makie = "0.20, 0.21, 0.22" diff --git a/docs/make.jl b/docs/make.jl index 6769ec0ea..50f4696f8 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,8 +18,8 @@ end DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, doctest_setup; recursive = true) # some options for `makedocs` -const DRAFT = false # set `true` to disable cell evaluation -const DOCTEST = true # set `false` to skip doc tests +const DRAFT = get(ENV, "DRAFT", false) == "true" # `DRAFT = true` disables cell evaluation +const DOCTEST = get(ENV, "DOCTEST", true) == true # `DOCTEST = false` skips doc tests # generate bibliography bib = CitationBibliography( @@ -51,7 +51,6 @@ const PAGES = [ ], "Manipulating States and Operators" => "users_guide/states_and_operators.md", "Tensor Products and Partial Traces" => "users_guide/tensor.md", - "Plotting on the Bloch Sphere" => "users_guide/plotting_the_bloch_sphere.md", "Time Evolution and Dynamics" => [ "Introduction" => "users_guide/time_evolution/intro.md", "Time Evolution Solutions" => "users_guide/time_evolution/solution.md", @@ -65,6 +64,7 @@ const PAGES = [ "Hierarchical Equations of Motion" => "users_guide/HEOM.md", "Solving for Steady-State Solutions" => "users_guide/steadystate.md", "Two-time correlation functions" => "users_guide/two_time_corr_func.md", + "Plotting on the Bloch Sphere" => "users_guide/plotting_the_bloch_sphere.md", "QuantumToolbox Settings" => "users_guide/settings.md", "Extensions" => [ "Extension for CUDA.jl" => "users_guide/extensions/cuda.md", diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index e99247123..6b3b4584d 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -319,13 +319,18 @@ meshgrid ```@docs plot_wigner plot_fock_distribution -plot_bloch +``` + +### [Bloch Sphere](@id doc-API:Bloch-Sphere) + +```@docs Bloch +plot_bloch render add_points! add_vectors! add_line! add_arc! -clear! add_states! -``` +clear! +``` \ No newline at end of file diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md index 44ff42c3e..4bed2b208 100644 --- a/docs/src/users_guide/plotting_the_bloch_sphere.md +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -7,61 +7,65 @@ using CairoMakie CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) ``` -## [Introduction](@id doc:Bloch_sphere_rendering) +## Introduction When studying the dynamics of a two-level system, it's often convenient to visualize the state of the system by plotting the state vector or density matrix on the Bloch sphere. -In [QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/), this can be done using the [`Bloch`](@ref) or [`plot_bloch`](@ref) methods that provide same syntax as [QuTiP](https://qutip.readthedocs.io/en/stable/guide/guide-bloch.html). +In [`QuantumToolbox`](https://qutip.org/QuantumToolbox.jl/), this can be done using the [`Bloch`](@ref) or [`plot_bloch`](@ref) methods that provide same syntax as [QuTiP](https://qutip.readthedocs.io/en/stable/guide/guide-bloch.html). ## Create a Bloch Sphere -In [QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/), creating a [`Bloch`](@ref) sphere is accomplished by calling either: +In [`QuantumToolbox`](https://qutip.org/QuantumToolbox.jl/), creating a [`Bloch`](@ref) sphere is accomplished by calling either: ```@example Bloch_sphere_rendering -b = Bloch(); +b = Bloch() ``` which will load an instance of [`Bloch`](@ref). Before getting into the details of these objects, we can simply plot the blank [`Bloch`](@ref) sphere associated with these instances via: ```@example Bloch_sphere_rendering -fig, _ = render(b); +fig, _ = render(b) fig ``` -## Add a Single Data Point +See the [API documentation for Bloch sphere](@ref doc-API:Bloch-Sphere) for a full list of other available functions. + +## Add a single data point As an example, we can add a single data point via [`add_points!`](@ref): ```@example Bloch_sphere_rendering -pnt = [1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3)]; -add_points!(b, pnt); -fig, _ = render(b); +pnt = [1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3)] +add_points!(b, pnt) +fig, _ = render(b) fig ``` -## Add a Single Vector +## Add a single vector -and then a single vector via [`add_vectors!`](@ref): +Add a single vector via [`add_vectors!`](@ref): ```@example Bloch_sphere_rendering -vec = [0, 1, 0]; +vec = [0, 1, 0] add_vectors!(b, vec) fig, _ = render(b) fig ``` -and then add another vector corresponding to the ``|0\rangle`` state: +## Add a single quantum state + +Add another vector corresponding to the ``|0\rangle`` state: ```@example Bloch_sphere_rendering -x = basis(2, 0) -add_states!(b, [x]) +z0 = basis(2, 0) +add_states!(b, z0) fig, _ = render(b) fig ``` -## Add Multiple Vectors +## Add multiple data -We can also plot multiple points, vectors, and states at the same time by passing arrays instead of individual elements via [`add_vectors!](@ref). Before giving an example, we can use [`clear!`](@ref) to remove the current data from our [`Bloch`](@ref) sphere instead of creating a new instance: +We can also plot multiple points, vectors, and states at the same time by passing arrays instead of individual elements via [`add_points!`](@ref), [`add_vectors!`](@ref), and [`add_states!`](@ref), respectively. Before giving an example, we can use [`clear!`](@ref) to remove the current data from our [`Bloch`](@ref) sphere instead of creating a new instance: ```@example Bloch_sphere_rendering clear!(b) @@ -73,14 +77,16 @@ Now on the same [`Bloch`](@ref) sphere, we can plot the three states via [`add_s ```@example Bloch_sphere_rendering x = basis(2, 0) + basis(2, 1) -y = basis(2, 0) - im * basis(2, 1) +y = basis(2, 0) + im * basis(2, 1) z = basis(2, 0) -b = Bloch() add_states!(b, [x, y, z]) fig, _ = render(b) fig ``` +!!! note "State normalization" + The function [`add_states!`](@ref) will automatically normalize the given quantum state(s), while [`add_vectors!`](@ref) does not normalize the given vectors. + A similar method works for adding vectors: ```@example Bloch_sphere_rendering @@ -91,70 +97,107 @@ fig, _ = render(b) fig ``` -# Add Arc, Line, and Vector +# Add lines and arcs You can also add lines and arcs via [`add_line!`](@ref) and [`add_arc!`](@ref) respectively: ```@example Bloch_sphere_rendering -clear!(b) -vec = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; -add_vectors!(b, vec); -add_line!(b, [1,0,0], [0,1,0]) -add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) +add_line!(b, x, y) +add_arc!(b, y, z) fig, _ = render(b) fig ``` -## Add Multiple Points +## Add multiple points Adding multiple points to the [`Bloch`](@ref) sphere works slightly differently than adding multiple states or vectors. For example, lets add a set of `20` points around the equator (after calling [`clear!`](@ref)): ```@example Bloch_sphere_rendering -th = range(0, 2π; length=20); clear!(b) -xp = cos.(th); -yp = sin.(th); -zp = zeros(20); -pnts = [xp, yp, zp]; -add_points!(b, pnts); -fig, ax = render(b); + +th = LinRange(0, 2π, 20) +xp = cos.(th) +yp = sin.(th) +zp = zeros(20) +pnts = [xp, yp, zp] +add_points!(b, pnts) +fig, ax = render(b) fig ``` -Notice that, in contrast to states or vectors, each point remains the same color as the initial point. This is because adding multiple data points using [`add_points!`](@ref) is interpreted, by default, to correspond to a single data point (single qubit state) plotted at different times. This is very useful when visualizing the dynamics of a qubit. If we want to plot additional qubit states we can call additional [`add_points!`](@ref): - -## Add Another Set of Points +Notice that, in contrast to states or vectors, each point remains the same color as the initial point. This is because adding multiple data points using [`add_points!`](@ref) is interpreted, by default, to correspond to a single data point (single qubit state) plotted at different times. This is very useful when visualizing the dynamics of a qubit. If we want to plot additional qubit states we can call additional [`add_points!`](@ref) function: ```@example Bloch_sphere_rendering -xz = zeros(20); -yz = sin.(th); -zz = cos.(th); -pnts = [xz, yz, zz]; -add_points!(b, pnts); -fig, ax = render(b); +xz = zeros(20) +yz = sin.(th) +zz = cos.(th) +add_points!(b, [xz, yz, zz]) +fig, ax = render(b) fig ``` -The color and shape of the data points is varied automatically by [`Bloch`](@ref). Notice how the color and point markers change for each set of data. +The color and shape of the data points is varied automatically by [`Bloch`](@ref). Notice how the color and point markers change for each set of data. Again, we have had to call [`add_points!`](@ref) twice because adding more than one set of multiple data points is not supported by the [`add_points!`](@ref) function. -What if we want to vary the color of our points. We can tell [`Bloch`](@ref) to vary the color of each point according to the colors listed in the `point_color` attribute. +What if we want to vary the color of our points. We can tell [`Bloch`](@ref) to vary the color of each point according to the colors listed in the `point_color` field (see [Configuring the Bloch sphere](@ref doc:Configuring-the-Bloch-sphere) below). Again, after [`clear!`](@ref): ```@example Bloch_sphere_rendering clear!(b) -xp = cos.(th); -yp = sin.(th); -zp = zeros(20); -pnts = [xp, yp, zp]; -add_points!(b, pnts, meth=:m); -fig, ax = render(b); + +xp = cos.(th) +yp = sin.(th) +zp = zeros(20) +pnts = [xp, yp, zp] +add_points!(b, pnts, meth=:m) # add `meth=:m` to signify 'multi' colored points +fig, ax = render(b) fig ``` Now, the data points cycle through a variety of predefined colors. Now lets add another set of points, but this time we want the set to be a single color, representing say a qubit going from the ``|0\rangle`` state to the ``|1\rangle`` state in the `y-z` plane: ```@example Bloch_sphere_rendering -pnts = [xz, yz, zz] ; -add_points!(b, pnts); -fig, ax = render(b); +pnts = [xz, yz, zz] +add_points!(b, pnts) # no `meth=:m` +fig, ax = render(b) fig ``` + +## [Configuring the Bloch sphere](@id doc:Configuring-the-Bloch-sphere) + +At the end of the last section we saw that the colors and marker shapes of the data plotted on the Bloch sphere are automatically varied according to the number of points and vectors added. But what if you want a different choice of color, or you want your sphere to be purple with different axes labels? Well then you are in luck as the Bloch class has many attributes which one can control. Assuming `b = Bloch()`: + +| **Field** | **Description** | **Default setting** | +|:----------|:----------------|:--------------------| +| `b.points` | Points to plot on the Bloch sphere (3D coordinates) | `Vector{Matrix{Float64}}()` (empty) | +| `b.vectors` | Vectors to plot on the Bloch sphere | `Vector{Vector{Float64}}()` (empty) | +| `b.lines` | Lines to draw on the sphere (points, style, properties) | `Vector{Tuple{Vector{Vector{Float64}},String}}()` (empty) | +| `b.arcs` | Arcs to draw on the sphere | `Vector{Vector{Vector{Float64}}}()` (empty) | +| `b.font_color` | Color of axis labels and text | `"black"` | +| `b.font_size` | Font size for labels | `15` | +| `b.frame_alpha` | Transparency of the frame background | `0.1` | +| `b.frame_color` | Background color of the frame | `"gray"` | +| `b.frame_limit` | Axis limits for the 3D frame (symmetric around origin) | `1.2` | +| `b.point_default_color` | Default color cycle for points | `["blue", "red", "green", "#CC6600"]` | +| `b.point_color` | List of colors for Bloch point markers to cycle through | `Union{Nothing,String}[]` | +| `b.point_marker` | List of point marker shapes to cycle through | `[:circle, :rect, :diamond, :utriangle]` | +| `b.point_size` | List of point marker sizes (not all markers look the same size when plotted) | `[5.5, 6.2, 6.5, 7.5]` | +| `b.point_style` | List of marker styles | `Symbol[]` | +| `b.point_alpha` | List of marker transparencies | `Float64[]` | +| `b.sphere_color` | Color of Bloch sphere surface | `0.2` | +| `b.sphere_alpha` | Transparency of sphere surface | `"#FFDDDD"` | +| `b.vector_color` | Colors for vectors | `["green", "#CC6600", "blue", "red"]` | +| `b.vector_width` | Width of vectors | `0.025` | +| `b.vector_arrowsize` | Arrow size parameters as (head length, head width, stem width) | `[0.07, 0.08, 0.08]` | +| `b.view` | Azimuthal and elevation viewing angles in degrees | `[30, 30]` | +| `b.xlabel` | Labels for x-axis | `[L"x", ""]` (``+x`` and ``-x``) | +| `b.xlpos` | Positions of x-axis labels | `[1.0, -1.0]` | +| `b.ylabel` | Labels for y-axis | `[L"y", ""]` (``+y`` and ``-y``) | +| `b.ylpos` | Positions of y-axis labels | `[1.0, -1.0]` | +| `b.zlabel` | Labels for z-axis | `[L"\|0\rangle", L"\|1\rangle]"` (``+z`` and ``-z``) | +| `b.zlpos` | Positions of z-axis labels | `[1.0, -1.0]` | + +These properties can also be accessed via the `print` command: + +```@example Bloch_sphere_rendering +b = Bloch() +print(b) +``` \ No newline at end of file diff --git a/ext/QuantumToolboxMakieExt.jl b/ext/QuantumToolboxMakieExt.jl index 352e1ec85..8e4b83f98 100644 --- a/ext/QuantumToolboxMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -1,8 +1,10 @@ module QuantumToolboxMakieExt using QuantumToolbox -using LinearAlgebra: cross, deg2rad, normalize, size -using Makie: +import QuantumToolbox: _state_to_bloch + +import LinearAlgebra: cross, deg2rad, normalize, size +import Makie: Axis, Axis3, Colorbar, @@ -315,66 +317,6 @@ raw""" """ _figFromChildren(::Nothing) = throw(ArgumentError("No Figure has been found at the top of the layout hierarchy.")) -raw""" - _state_to_bloch(state::QuantumObject{<:Ket}) -> Vector{Float64} - -Convert a pure qubit state (`Ket`) to its Bloch vector representation. - -If the state is not normalized, it is automatically normalized before conversion. - -# Arguments -- `state`: A `Ket` representing a pure quantum state. - -# Returns -A 3-element `Vector{Float64}` representing the Bloch vector `[x, y, z]`. - -# Throws -- `ArgumentError` if the state dimension is not 2. -""" -function _state_to_bloch(state::QuantumObject{<:Ket}) - if !isapprox(norm(state), 1.0, atol = 1e-6) - @warn "State is not normalized. Normalizing before Bloch vector conversion." - state = normalize(state) - end - ψ = state.data - if length(ψ) != 2 - error("Bloch sphere visualization is only supported for qubit states (2-level systems)") - end - x = 2 * real(ψ[1] * conj(ψ[2])) - y = 2 * imag(ψ[1] * conj(ψ[2])) - z = abs2(ψ[1]) - abs2(ψ[2]) - return [x, y, z] -end - -raw""" - _dm_to_bloch(ρ::QuantumObject{<:Operator}) -> Vector{Float64} - -Convert a qubit density matrix (`Operator`) to its Bloch vector representation. - -This function assumes the input is Hermitian. If the density matrix is not Hermitian, a warning is issued. - -# Arguments -- `ρ`: A density matrix (`Operator`) representing a mixed or pure quantum state. - -# Returns -A 3-element `Vector{Float64}` representing the Bloch vector `[x, y, z]`. - -# Throws -- `ArgumentError` if the matrix dimension is not 2. -""" -function _dm_to_bloch(ρ::QuantumObject{<:Operator}) - if !ishermitian(ρ) - @warn "Density matrix is not Hermitian. Results may not be meaningful." - end - if size(ρ, 1) != 2 - error("Bloch sphere visualization is only supported for qubit states (2-level systems)") - end - x = real(ρ[1, 2] + ρ[2, 1]) - y = imag(ρ[2, 1] - ρ[1, 2]) - z = real(ρ[1, 1] - ρ[2, 2]) - return [x, y, z] -end - function _render_bloch_makie(bloch_vec::Vector{Float64}; location = nothing, kwargs...) b = Bloch() add_vectors!(b, bloch_vec) @@ -384,97 +326,21 @@ function _render_bloch_makie(bloch_vec::Vector{Float64}; location = nothing, kwa end @doc raw""" - add_line!( - b::QuantumToolbox.Bloch, - start_point_point::QuantumToolbox.QuantumObject{<:Union{QuantumToolbox.Ket, QuantumToolbox.Bra, QuantumToolbox.Operator}}, - end_point::QuantumToolbox.QuantumObject{<:Union{QuantumToolbox.Ket, QuantumToolbox.Bra, QuantumToolbox.Operator}}; - fmt = "k" - ) - -Add a line between two quantum states or operators on the Bloch sphere visualization. - -# Arguments - -- `b::Bloch`: The Bloch sphere object to modify. -- `start_point_point::QuantumObject`: The start_point_pointing quantum state or operator. Can be a `Ket`, `Bra`, or `Operator`. -- `end_point::QuantumObject`: The ending quantum state or operator. Can be a `Ket`, `Bra`, or `Operator`. -- `fmt::String="k"`: (optional) A format string specifying the line style and color (default is black `"k"`). - -# Description - -This function converts the given quantum objects (states or operators) into their Bloch vector representations and adds a line between these two points on the Bloch sphere visualization. - -# Example - -```julia -b = Bloch() -ψ₁ = normalize(basis(2, 0) + basis(2, 1)) -ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) -add_line!(b, ψ₁, ψ₂; fmt = "r--") -``` -""" -function QuantumToolbox.add_line!( - b::Bloch, - p1::QuantumObject{<:Union{Ket,Bra,Operator}}, - p2::QuantumObject{<:Union{Ket,Bra,Operator}}; - fmt = "k", -) - coords1 = isket(p1) || isbra(p1) ? _state_to_bloch(p1) : _dm_to_bloch(p1) - coords2 = isket(p2) || isbra(p2) ? _state_to_bloch(p2) : _dm_to_bloch(p2) - return add_line!(b, coords1, coords2; fmt = fmt) -end - -@doc raw""" - QuantumToolbox.add_states!(b::Bloch, states::QuantumObject...) - -Add one or more quantum states to the Bloch sphere visualization by converting them into Bloch vectors. - -# Arguments -- `b::Bloch`: The Bloch sphere object to modify -- `states::QuantumObject...`: One or more quantum states (Ket, Bra, or Operator) - -# Example - -```julia -x = basis(2, 0) + basis(2, 1); -y = basis(2, 0) - im * basis(2, 1); -z = basis(2, 0); -b = Bloch(); -add_states!(b, [x, y, z]) -``` -""" -function QuantumToolbox.add_states!(b::Bloch, states::Vector{<:QuantumObject}) - vecs = map(states) do state - if isket(state) || isbra(state) - return _state_to_bloch(state) - else - return _dm_to_bloch(state) - end - end - append!(b.vectors, [normalize(v) for v in vecs]) - return b.vectors -end - -@doc raw""" - render(b::QuantumToolbox.Bloch; location=nothing) + render(b::Bloch; location=nothing) -Render the Bloch sphere visualization from the given `Bloch` object `b`. +Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`. # Arguments -- `b::QuantumToolbox.Bloch` - The Bloch sphere object containing states, vectors, and settings to visualize. - -- `location` (optional) - Specifies where to display or save the rendered figure. +- `b::Bloch`: The Bloch sphere object containing states, vectors, and settings to visualize. +- `location`: Specifies where to display or save the rendered figure. - If `nothing` (default), the figure is displayed interactively. - If a file path (String), the figure is saved to the specified location. - Other values depend on backend support. # Returns -- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. - These can be further manipulated or saved by the user. +- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. These can be further manipulated or saved by the user. """ function QuantumToolbox.render(b::Bloch; location = nothing) fig, ax = _setup_bloch_plot!(b, location) @@ -532,10 +398,12 @@ function _setup_bloch_plot!(b::Bloch, location) yspinesvisible = false, zspinesvisible = false, protrusions = (0, 0, 0, 0), + perspectiveness = 0.2, viewmode = :fit, ) - ax.azimuth[] = deg2rad(b.view_angles[1]) - ax.elevation[] = deg2rad(b.view_angles[2]) + length(b.view) == 2 || throw(ArgumentError("The length of `Bloch.view` must be 2.")) + ax.azimuth[] = deg2rad(b.view[1]) + ax.elevation[] = deg2rad(b.view[2]) return fig, ax end @@ -551,7 +419,7 @@ function _draw_bloch_sphere!(b::Bloch, ax) base_color = parse(RGBf, b.sphere_color) sphere_color = RGBAf(base_color, b.sphere_alpha) sphere_mesh = Sphere(Point3f(0), radius) - mesh!(ax, sphere_mesh; color = sphere_color, shading = NoShading, transparency = true) + mesh!(ax, sphere_mesh; color = sphere_color, shading = NoShading, transparency = true, rasterize = 3) θ_vals = range(0.0f0, 2π, length = n_lon + 1)[1:(end-1)] φ_curve = range(0.0f0, π, length = 600) line_alpha = max(0.05, b.sphere_alpha * 0.5) @@ -586,9 +454,9 @@ function _draw_reference_circles!(ax) φ = range(0, 2π, length = 100) # XY, YZ, XZ circles circles = [ - [Point3f(sin(φi), -cos(φi), 0) for φi in φ], # XY - [Point3f(0, -cos(φi), sin(φi)) for φi in φ], # YZ - [Point3f(sin(φi), 0, cos(φi)) for φi in φ], # XZ + [Point3f(cos(φi), sin(φi), 0) for φi in φ], # XY + [Point3f(0, cos(φi), sin(φi)) for φi in φ], # YZ + [Point3f(cos(φi), 0, sin(φi)) for φi in φ], # XZ ] for circle in circles lines!(ax, circle; color = wire_color, linewidth = 1.0) @@ -609,9 +477,9 @@ function _draw_axes!(ax) axis_color = RGBAf(0.3, 0.3, 0.3, 0.8) axis_width = 0.8 axes = [ - ([Point3f(0, -1.0, 0), Point3f(0, 1.0, 0)], "y"), # Y-axis - ([Point3f(-1.0, 0, 0), Point3f(1.0, 0, 0)], "x"), # X-axis - ([Point3f(0, 0, -1.0), Point3f(0, 0, 1.0)], "z"), # Z-axis + ([Point3f(1.0, 0, 0), Point3f(-1.0, 0, 0)], "x"), # X-axis + ([Point3f(0, 1.0, 0), Point3f(0, -1.0, 0)], "y"), # Y-axis + ([Point3f(0, 0, 1.0), Point3f(0, 0, -1.0)], "z"), # Z-axis ] for (points, _) in axes lines!(ax, points; color = axis_color, linewidth = axis_width) @@ -637,8 +505,8 @@ function _plot_points!(b::Bloch, ax) marker = b.point_marker[mod1(k, length(b.point_marker))] N = size(pts, 2) - raw_x = pts[2, :] - raw_y = -pts[1, :] + raw_x = pts[1, :] + raw_y = pts[2, :] raw_z = pts[3, :] ds = vec(sqrt.(sum(abs2, pts; dims = 1))) @@ -741,10 +609,10 @@ function _plot_arcs!(b::Bloch, ax) θ = acos(clamp(dot(v1, v2), -1.0, 1.0)) if length(arc_pts) == 3 vm = normalize(arc_pts[2]) - dot(cross(v1, vm), n) < 0 && (θ = 2π - θ) + dot(cross(v1, vm), n) < 0 && (θ -= 2π) end t_range = range(0, θ, length = 100) - arc_points = [Point3f((v1*cos(t) + cross(n, v1)*sin(t))...) for t in t_range] + arc_points = [Point3f((v1 * cos(t) + cross(n, v1) * sin(t))) for t in t_range] lines!(ax, arc_points; color = RGBAf(0.8, 0.4, 0.1, 0.9), linewidth = 2.0, linestyle = :solid) end end @@ -766,7 +634,7 @@ function _plot_vectors!(b::Bloch, ax) r = 1.0 for (i, v) in enumerate(b.vectors) color = get(b.vector_color, i, RGBAf(0.2, 0.5, 0.8, 0.9)) - vec = Vec3f(v[2], -v[1], v[3]) + vec = Vec3f(v...) length = norm(vec) max_length = r * 0.90 vec = length > max_length ? (vec/length) * max_length : vec @@ -778,6 +646,7 @@ function _plot_vectors!(b::Bloch, ax) linewidth = b.vector_width, arrowsize = arrowsize_vec, arrowcolor = color, + rasterize = 3, ) end end @@ -793,112 +662,87 @@ Add axis labels and state labels to the Bloch sphere. Positions standard labels `(x, y, |0⟩, |1⟩)` at appropriate locations. """ function _add_labels!(b::Bloch, ax) + length(b.xlabel) == 2 || throw(ArgumentError("The length of `Bloch.xlabel` must be 2.")) + length(b.ylabel) == 2 || throw(ArgumentError("The length of `Bloch.ylabel` must be 2.")) + length(b.zlabel) == 2 || throw(ArgumentError("The length of `Bloch.zlabel` must be 2.")) + length(b.xlpos) == 2 || throw(ArgumentError("The length of `Bloch.xlpos` must be 2.")) + length(b.ylpos) == 2 || throw(ArgumentError("The length of `Bloch.ylpos` must be 2.")) + length(b.zlpos) == 2 || throw(ArgumentError("The length of `Bloch.zlpos` must be 2.")) + label_color = parse(RGBf, b.font_color) label_size = b.font_size offset_scale = b.frame_limit - if !isempty(b.xlabel) && !isempty(b.xlabel[1]) - text!( - ax, - L"\textbf{x}", - position = Point3f(0, -offset_scale * b.xlpos[1], 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - end - if length(b.xlabel) > 1 && !isempty(b.xlabel[2]) - text!( - ax, - L"\textbf{-x}", - position = Point3f(0, -offset_scale * b.xlpos[2], 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - end - if !isempty(b.ylabel) && !isempty(b.ylabel[1]) - text!( - ax, - L"\textbf{y}", - position = Point3f(offset_scale * b.ylpos[1], 0, 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - end - if length(b.ylabel) > 1 && !isempty(b.ylabel[2]) - text!( - ax, - L"\textbf{-y}", - position = Point3f(offset_scale * b.ylpos[2], 0, 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - end - if !isempty(b.zlabel) && !isempty(b.zlabel[1]) - text!( - ax, - L"\mathbf{|0\rangle}", - position = Point3f(0, 0, offset_scale * b.zlpos[1]), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - end - if length(b.zlabel) > 1 && !isempty(b.zlabel[2]) - text!( - ax, - L"\mathbf{|1\rangle}", - position = Point3f(0, 0, offset_scale * b.zlpos[2]), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - end + + (b.xlabel[1] == "") || text!( + ax, + b.xlabel[1], + position = Point3f(offset_scale * b.xlpos[1], 0, 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.xlabel[2] == "") || text!( + ax, + b.xlabel[2], + position = Point3f(offset_scale * b.xlpos[2], 0, 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.ylabel[1] == "") || text!( + ax, + b.ylabel[1], + position = Point3f(0, offset_scale * b.ylpos[1], 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.ylabel[2] == "") || text!( + ax, + b.ylabel[2], + position = Point3f(0, offset_scale * b.ylpos[2], 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.zlabel[1] == "") || text!( + ax, + b.zlabel[1], + position = Point3f(0, 0, offset_scale * b.zlpos[1]), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.zlabel[2] == "") || text!( + ax, + b.zlabel[2], + position = Point3f(0, 0, offset_scale * b.zlpos[2]), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + return nothing end @doc raw""" - plot_bloch(::Val{:Makie}, state::QuantumObject{<:Union{Ket,Bra}}; kwargs...) + plot_bloch(::Val{:Makie}, state::QuantumObject; kwargs...) Plot a pure quantum state on the Bloch sphere using the `Makie` backend. # Arguments -- `state::QuantumObject{<:Union{Ket,Bra}}`: The quantum state to be visualized (`ket` or `bra`). +- `state::QuantumObject{<:Union{Ket,Bra}}`: The quantum state ([`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref)) to be visualized. - `kwargs...`: Additional keyword arguments passed to `_render_bloch_makie`. -# Details - -Converts the state to its Bloch vector representation and renders it on the Bloch sphere. -If the input is a bra, it is automatically converted to a ket before processing. - !!! note "Internal function" This is the `Makie`-specific implementation called by the main `plot_bloch` function. """ -function QuantumToolbox.plot_bloch(::Val{:Makie}, state::QuantumObject{<:Union{Ket,Bra}}; kwargs...) - state = isbra(state) ? state' : state +function QuantumToolbox.plot_bloch( + ::Val{:Makie}, + state::QuantumObject{OpType}; + kwargs..., +) where {OpType<:Union{Ket,Bra,Operator}} bloch_vec = _state_to_bloch(state) return _render_bloch_makie(bloch_vec; kwargs...) end -@doc raw""" - plot_bloch(::Val{:Makie}, ρ::QuantumObject{<:Operator}; kwargs...) - -Plot a density matrix on the Bloch sphere using the Makie backend. - -# Arguments -- `ρ::QuantumObject{<:Operator}`: The density matrix to be visualized. -- `kwargs...`: Additional keyword arguments passed to `_render_bloch_makie`. - -# Details -Converts the density matrix to its Bloch vector representation and renders it on the Bloch sphere. - -!!! note "Internal function" - This is the Makie-specific implementation called by the main `plot_bloch` function. -""" -function QuantumToolbox.plot_bloch(::Val{:Makie}, ρ::QuantumObject{<:Operator}; kwargs...) - bloch_vec = _dm_to_bloch(ρ) - return _render_bloch_makie(bloch_vec; kwargs...) -end - end diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 0944175b3..915c4abe8 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -58,6 +58,7 @@ import Distributed: RemoteChannel import FFTW: fft, ifft, fftfreq, fftshift import Graphs: connected_components, DiGraph import IncompleteLU: ilu +import LaTeXStrings: @L_str import Pkg import Random: AbstractRNG, default_rng, seed! import SpecialFunctions: loggamma diff --git a/src/settings.jl b/src/settings.jl index b9be9b0b2..14f693af8 100644 --- a/src/settings.jl +++ b/src/settings.jl @@ -4,9 +4,11 @@ Base.@kwdef mutable struct Settings end function Base.show(io::IO, s::Settings) + # To align the output and make it easier to read + # we use rpad `11`, which is the length of string: `auto_tidyup` println(io, "QuantumToolbox.jl Settings") println(io, "--------------------------") - map(x -> println(io, "$x = ", getfield(s, x)), fieldnames(Settings)) + map(n -> println(io, rpad("$n", 11, " "), " = ", getfield(s, n)), fieldnames(Settings)) return nothing end diff --git a/src/visualization.jl b/src/visualization.jl index 71c6fc499..a75aab922 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -1,14 +1,6 @@ -export plot_wigner, - plot_fock_distribution, - plot_bloch, - Bloch, - render, - add_points!, - add_vectors!, - add_line!, - add_arc!, - clear!, - add_states! +export plot_wigner +export plot_fock_distribution +export plot_bloch, Bloch, render, add_points!, add_vectors!, add_line!, add_arc!, clear!, add_states! @doc raw""" plot_wigner( @@ -88,7 +80,7 @@ A structure representing a Bloch sphere visualization for quantum states. ## Style properties - `font_color::String`: Color of axis labels and text -- `font_size::Int`: Font size for labels (default: 15) +- `font_size::Int`: Font size for labels. Default: `15` - `frame_alpha::Float64`: Transparency of the frame background - `frame_color::String`: Background color of the frame - `frame_limit::Float64`: Axis limits for the 3D frame (symmetric around origin) @@ -96,46 +88,46 @@ A structure representing a Bloch sphere visualization for quantum states. ## Point properties - `point_default_color::Vector{String}}`: Default color cycle for points -- `point_color::Vector{String}}`: Colors for point markers -- `point_marker::Vector{Symbol}}`: Marker shapes (default: [:circle, :rect, :diamond, :utriangle]) -- `point_size::Vector{Int}}`: Marker sizes -- `point_style::Vector{Symbol}}`: Marker styles -- `point_alpha::Vector{Float64}}`: Marker transparencies +- `point_color::Vector{String}}`: List of colors for Bloch point markers to cycle through +- `point_marker::Vector{Symbol}}`: List of point marker shapes to cycle through. Default: `[:circle, :rect, :diamond, :utriangle]` +- `point_size::Vector{Int}}`: List of point marker sizes (not all markers look the same size when plotted) +- `point_style::Vector{Symbol}}`: List of marker styles +- `point_alpha::Vector{Float64}}`: List of marker transparencies ## Sphere properties - `sphere_color::String`: Color of Bloch sphere surface -- `sphere_alpha::Float64`: Transparency of sphere surface (default: 0.2) +- `sphere_alpha::Float64`: Transparency of sphere surface. Default: `0.2` # Vector properties - `vector_color`::Vector{String}: Colors for vectors - `vector_width`::Float64: Width of vectors -- `vector_arrowsize`::NTuple{3, Real}: Arrow size parameters as (head length, head width, stem width) +- `vector_arrowsize`::Vector{Float64}: Arrow size parameters as [head length, head width, stem width] ## Layout properties -- `view_angles::Tuple{Int,Int}}`: Azimuthal and elevation viewing angles in degrees (default: (-60, 30)) +- `view::Vector{Int}`: Azimuthal and elevation viewing angles in degrees. Default: `[30, 30]` ## Label properties -- `xlabel::Vector{AbstractString}}`: Labels for x-axis (default: [L"x", ""]) -- `xlpos::Vector{Float64}}`: Positions of x-axis labels (default: [1.0, -1.0]) -- `ylabel::Vector{AbstractString}}`: Labels for y-axis (default: [L"y", ""]) -- `ylpos::Vector{Float64}}`: Positions of y-axis labels (default: [1.0, -1.0]) -- `zlabel::Vector{AbstractString}}`: Labels for z-axis (default: [L"|0\rangle", L"|1\rangle"]) -- `zlpos::Vector{Float64}}`: Positions of z-axis labels (default: [1.0, -1.0]) +- `xlabel::Vector{AbstractString}`: Labels for x-axis. Default: `[L"x", ""]` +- `xlpos::Vector{Float64}`: Positions of x-axis labels. Default: `[1.0, -1.0]` +- `ylabel::Vector{AbstractString}`: Labels for y-axis. Default: `[L"y", ""]` +- `ylpos::Vector{Float64}`: Positions of y-axis labels. Default: `[1.0, -1.0]` +- `zlabel::Vector{AbstractString}`: Labels for z-axis. Default: `[L"|0\rangle", L"|1\rangle"]` +- `zlpos::Vector{Float64}`: Positions of z-axis labels. Default: `[1.0, -1.0]` """ @kwdef mutable struct Bloch points::Vector{Matrix{Float64}} = Vector{Matrix{Float64}}() vectors::Vector{Vector{Float64}} = Vector{Vector{Float64}}() lines::Vector{Tuple{Vector{Vector{Float64}},String}} = Vector{Tuple{Vector{Vector{Float64}},String}}() arcs::Vector{Vector{Vector{Float64}}} = Vector{Vector{Vector{Float64}}}() - font_color::String = "#333333" + font_color::String = "black" font_size::Int = 15 - frame_alpha::Float64 = 0.0 - frame_color::String = "white" - frame_limit::Float64 = 1.14 - point_default_color::Vector{String} = ["blue", "red", "green", "orange"] + frame_alpha::Float64 = 0.1 + frame_color::String = "gray" + frame_limit::Float64 = 1.2 + point_default_color::Vector{String} = ["blue", "red", "green", "#CC6600"] point_color::Vector{Union{Nothing,String}} = Union{Nothing,String}[] point_marker::Vector{Symbol} = [:circle, :rect, :diamond, :utriangle] point_size::Vector{Float64} = [5.5, 6.2, 6.5, 7.5] @@ -143,18 +135,35 @@ A structure representing a Bloch sphere visualization for quantum states. point_alpha::Vector{Float64} = Float64[] sphere_alpha::Float64 = 0.2 sphere_color::String = "#FFDDDD" - vector_color::Vector{String} = ["green", "orange", "blue", "red"] + vector_color::Vector{String} = ["green", "#CC6600", "blue", "red"] vector_width::Float64 = 0.025 - vector_arrowsize::NTuple{3,Real} = (0.07, 0.08, 0.08) - view_angles::Tuple{Int,Int} = (-60, 30) - xlabel::Vector{AbstractString} = ["x", ""] + vector_arrowsize::Vector{Float64} = [0.07, 0.08, 0.08] + view::Vector{Int} = [30, 30] + xlabel::Vector{AbstractString} = [L"x", ""] xlpos::Vector{Float64} = [1.0, -1.0] - ylabel::Vector{AbstractString} = ["y", ""] + ylabel::Vector{AbstractString} = [L"y", ""] ylpos::Vector{Float64} = [1.0, -1.0] - zlabel::Vector{AbstractString} = ["|0\rangle", "|1\rangle"] + zlabel::Vector{AbstractString} = [L"|0\rangle", L"|1\rangle"] zlpos::Vector{Float64} = [1.0, -1.0] end +const BLOCH_DATA_FIELDS = (:points, :vectors, :lines, :arcs) +function Base.show(io::IO, b::Bloch) + # To align the output and make it easier to read + # we use rpad `17` and `19` for Bloch sphere data and properties, respectively + # 17 is the length of string: `Number of vectors` + # 19 is the length of string: `point_default_color` + println(io, "Bloch Sphere\n") + println(io, "data:") + println(io, "-----") + map(n -> println(io, rpad("Number of $n", 17, " "), " = ", length(getfield(b, n))), BLOCH_DATA_FIELDS) + println(io, "") + println(io, "properties:") + println(io, "-----------") + map(n -> (n ∉ BLOCH_DATA_FIELDS) && (println(io, rpad("$n", 19, " "), " = ", getfield(b, n))), fieldnames(Bloch)) + return nothing +end + @doc raw""" add_vectors!(b::Bloch, vec::Vector{<:Real}) @@ -185,13 +194,8 @@ julia> add_vectors!(b, [[1, 0, 0], [0, 1, 0]]) [0.0, 1.0, 0.0] ``` """ -function add_vectors!(b::Bloch, vec::Vector{<:Real}) - normalized_vec = normalize(convert(Vector{Float64}, vec)) - return push!(b.vectors, normalized_vec) -end -function add_vectors!(b::Bloch, vecs::Vector{<:Vector{<:Real}}) - return append!(b.vectors, [normalize(convert(Vector{Float64}, v)) for v in vecs]) -end +add_vectors!(b::Bloch, vec::Vector{<:Real}) = push!(b.vectors, convert(Vector{Float64}, vec)) +add_vectors!(b::Bloch, vecs::Vector{<:Vector{<:Real}}) = append!(b.vectors, [convert(Vector{Float64}, v) for v in vecs]) @doc raw""" add_points!(b::Bloch, pnt::Vector{<:Real}; meth::Symbol = :s, color = "blue", alpha = 1.0) @@ -199,11 +203,11 @@ end Add a single point to the Bloch sphere visualization. # Arguments -- b::Bloch: The Bloch sphere object to modify -- pnt::Vector{Float64}: A 3D point to add -- meth::Symbol=:s: Display method (:s for single point, :m for multiple, :l for line) -- color: Color of the point (defaults to first default color if nothing) -- alpha=1.0: Transparency (1.0 = opaque, 0.0 = transparent) +- `b::Bloch`: The Bloch sphere object to modify +- `pnt::Vector{Float64}`: A 3D point to add +- `meth::Symbol=:s`: Display method (`:s` for single point, `:m` for multiple, `:l` for line) +- `color`: Color of the point (defaults to first default color if nothing) +- `alpha=1.0`: Transparency (`1.0` means opaque and `0.0` means transparent) """ function add_points!(b::Bloch, pnt::Vector{Float64}; meth::Symbol = :s, color = nothing, alpha = 1.0) return add_points!(b, reshape(pnt, 3, 1); meth, color, alpha) @@ -219,11 +223,11 @@ Add multiple points to the Bloch sphere visualization. # Arguments -- b::Bloch: The Bloch sphere object to modify -- pnts::Matrix{Float64}: 3×N matrix of points (each column is a point) -- meth::Symbol=:s: Display method (:s for single point, :m for multiple, :l for line) -- color: Color of the points (defaults to first default color if nothing) -- alpha=1.0: Transparency (1.0 = opaque, 0.0 = transparent) +- `b::Bloch`: The Bloch sphere object to modify +- `pnts::Matrix{Float64}`: `3×N` matrix of points (each column is a point) +- `meth::Symbol=:s`: Display method (`:s` for single point, `:m` for multiple, `:l` for line) +- `color`: Color of the points (defaults to first default color if nothing) +- `alpha=1.0`: Transparency (`1.0` means opaque and `0.0` means transparent) ``` """ function add_points!( @@ -233,20 +237,13 @@ function add_points!( color::Union{Nothing,String} = nothing, alpha::Float64 = 1.0, ) - if size(pnts, 1) != 3 - error("Points must be a 3×N matrix where each column is [x; y; z]") - end - if !(meth in (:s, :m, :l)) - error("`meth` must be :s, :m, or :l") - end + (size(pnts, 1) == 3) || throw(ArgumentError("Points must be a 3×N matrix where each column is [x; y; z]")) + (meth in (:s, :m, :l)) || throw(ArgumentError("`meth` must be :s, :m, or :l")) + push!(b.points, convert(Matrix{Float64}, pnts)) push!(b.point_style, meth) push!(b.point_alpha, alpha) - if color === nothing - push!(b.point_color, nothing) - else - push!(b.point_color, color) - end + push!(b.point_color, color) return nothing end @@ -256,22 +253,61 @@ end Add a line between two points on the Bloch sphere. # Arguments -- b::Bloch: The Bloch sphere object to modify -- p1::Vector{<:Real}: First 3D point -- p2::Vector{<:Real}: Second 3D point -- fmt="k": Line format string (matplotlib style) +- `b::Bloch`: The Bloch sphere object to modify +- `p1::Vector{<:Real}`: First 3D point +- `p2::Vector{<:Real}`: Second 3D point +- `fmt="k"`: Line format string (matplotlib style) """ function add_line!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}; fmt = "k") - if length(p1) != 3 || length(p2) != 3 - error("Points must be 3D vectors") - end - x = [p1[2], p2[2]] - y = [-p1[1], -p2[1]] + (length(p1) != 3 || length(p2) != 3) && throw(ArgumentError("Points must be 3D vectors")) + x = [p1[1], p2[1]] + y = [p1[2], p2[2]] z = [p1[3], p2[3]] - push!(b.lines, (([x, y, z]), fmt)) + push!(b.lines, ([x, y, z], fmt)) return b end +@doc raw""" + add_line!( + b::Bloch, + start_point::QuantumObject, + end_point::QuantumObject; + fmt = "k" + ) + +Add a line between two quantum states on the Bloch sphere visualization. + +# Arguments + +- `b::Bloch`: The Bloch sphere object to modify. +- `start_point::QuantumObject`: The starting quantum state. Can be a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). +- `end_point::QuantumObject`: The ending quantum state. Can be a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). +- `fmt::String="k"`: (optional) A format string specifying the line style and color (default is black `"k"`). + +# Description + +This function converts the given quantum states into their Bloch vector representations and adds a line between these two points on the Bloch sphere visualization. + +# Example + +```julia +b = Bloch() +ψ₁ = normalize(basis(2, 0) + basis(2, 1)) +ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) +add_line!(b, ψ₁, ψ₂; fmt = "r--") +``` +""" +function add_line!( + b::Bloch, + start_point::QuantumObject{OpType1}, + end_point::QuantumObject{OpType2}; + fmt = "k", +) where {OpType1<:Union{Ket,Bra,Operator},OpType2<:Union{Ket,Bra,Operator}} + coords1 = _state_to_bloch(start_point) + coords2 = _state_to_bloch(end_point) + return add_line!(b, coords1, coords2; fmt = fmt) +end + @doc raw""" add_arc!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}, p3::Vector{<:Real}) @@ -279,10 +315,10 @@ Add a circular arc through three points on the Bloch sphere. # Arguments -- b::Bloch: The Bloch sphere object to modify -- p1::Vector{<:Real}: First 3D point -- p2::Vector{<:Real}: Second 3D point (middle point) -- p3::Vector{<:Real}: Third 3D point +- `b::Bloch`: The Bloch sphere object to modify +- `p1::Vector{<:Real}`: Starting 3D point +- `p2::Vector{<:Real}`: [Optional] Middle 3D point +- `p3::Vector{<:Real}`: Ending 3D point # Examples @@ -295,34 +331,168 @@ julia> add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) ``` """ function add_arc!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}) + (length(p1) != 3 || length(p2) != 3) && throw(ArgumentError("Points must be 3D vectors")) return push!(b.arcs, [convert(Vector{Float64}, p1), convert(Vector{Float64}, p2)]) end function add_arc!(b::Bloch, p1::Vector{<:Real}, p2::Vector{<:Real}, p3::Vector{<:Real}) + (length(p1) != 3 || length(p2) != 3 || length(p3) != 3) && throw(ArgumentError("Points must be 3D vectors")) return push!(b.arcs, [convert(Vector{Float64}, p1), convert(Vector{Float64}, p2), convert(Vector{Float64}, p3)]) end @doc raw""" - QuantumToolbox.add_states!(b::Bloch, states::QuantumObject...) + add_arc!( + b::Bloch, + start_point::QuantumObject, + middle_point::QuantumObject, + end_point::QuantumObject + ) -Add one or more quantum states to the Bloch sphere visualization by converting them into Bloch vectors. +Add a circular arc through three points on the Bloch sphere. # Arguments +- `b::Bloch`: The Bloch sphere object to modify. +- `start_point::QuantumObject`: The starting quantum state. Can be a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). +- `middle_point::QuantumObject`: [Optional] The middle quantum state. Can be a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). +- `end_point::QuantumObject`: The ending quantum state. Can be a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). + +# Description + +This function converts the given quantum states into their Bloch vector representations and adds a arc between these two (or three) points on the Bloch sphere visualization. +""" +function add_arc!( + b::Bloch, + start_point::QuantumObject{OpType1}, + end_point::QuantumObject{OpType2}, +) where {OpType1<:Union{Ket,Bra,Operator},OpType2<:Union{Ket,Bra,Operator}} + coords1 = _state_to_bloch(start_point) + coords2 = _state_to_bloch(end_point) + return add_arc!(b, coords1, coords2) +end +function add_arc!( + b::Bloch, + start_point::QuantumObject{OpType1}, + middle_point::QuantumObject{OpType2}, + end_point::QuantumObject{OpType3}, +) where {OpType1<:Union{Ket,Bra,Operator},OpType2<:Union{Ket,Bra,Operator},OpType3<:Union{Ket,Bra,Operator}} + coords1 = _state_to_bloch(start_point) + coords2 = _state_to_bloch(middle_point) + coords3 = _state_to_bloch(end_point) + return add_arc!(b, coords1, coords2, coords3) +end + +@doc raw""" + add_states!(b::Bloch, states::Vector{QuantumObject}) + +Add one or more quantum states to the Bloch sphere visualization by converting them into Bloch vectors. + +# Arguments - `b::Bloch`: The Bloch sphere object to modify -- `states::QuantumObject...`: One or more quantum states (Ket, Bra, or Operator) +- `states::Vector{QuantumObject}`: One or more quantum states ([`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref)) + +# Example + +```julia +x = basis(2, 0) + basis(2, 1); +y = basis(2, 0) + im * basis(2, 1); +z = basis(2, 0); +b = Bloch(); +add_states!(b, [x, y, z]) +``` +""" +function add_states!(b::Bloch, states::Vector{<:QuantumObject}) + vecs = map(state -> _state_to_bloch(state), states) + append!(b.vectors, vecs) + return b.vectors +end + +function add_states!(b::Bloch, state::QuantumObject) + push!(b.vectors, _state_to_bloch(state)) + return b.vectors +end + +_state_to_bloch(state::QuantumObject{Ket}) = _ket_to_bloch(state) +_state_to_bloch(state::QuantumObject{Bra}) = _ket_to_bloch(state') +_state_to_bloch(state::QuantumObject{Operator}) = _dm_to_bloch(state) + +raw""" + _ket_to_bloch(state::QuantumObject{Ket}) -> Vector{Float64} + +Convert a pure qubit state (`Ket`) to its Bloch vector representation. + +If the state is not normalized, it is automatically normalized before conversion. + +# Arguments +- `state`: A `Ket` representing a pure quantum state. + +# Returns +A 3-element `Vector{Float64}` representing the Bloch vector `[x, y, z]`. + +# Throws +- `ArgumentError` if the state dimension is not 2. +""" +function _ket_to_bloch(state::QuantumObject{Ket}) + (size(state) == (2,)) || + throw(ArgumentError("Bloch sphere visualization is only supported for qubit states (2-level systems)")) + + state_norm = norm(state) + if !isapprox(state_norm, 1.0, atol = 1e-6) + @warn "State is not normalized. Normalizing before Bloch vector conversion." + ψ = state.data / state_norm + else + ψ = state.data + end + + c = conj(ψ[1]) * ψ[2] + x = 2 * real(c) + y = 2 * imag(c) + z = abs2(ψ[1]) - abs2(ψ[2]) + return [x, y, z] +end + +raw""" + _dm_to_bloch(ρ::QuantumObject{Operator}) -> Vector{Float64} + +Convert a qubit density matrix (`Operator`) to its Bloch vector representation. + +This function assumes the input is Hermitian. If the density matrix is not Hermitian, a warning is issued. + +# Arguments +- `ρ`: A density matrix (`Operator`) representing a mixed or pure quantum state. +# Returns +A 3-element `Vector{Float64}` representing the Bloch vector `[x, y, z]`. + +# Throws +- `ArgumentError` if the matrix dimension is not 2. """ -function add_states! end +function _dm_to_bloch(ρ::QuantumObject{Operator}) + (size(ρ) == (2, 2)) || + throw(ArgumentError("Bloch sphere visualization is only supported for qubit states (2-level systems)")) + + ishermitian(ρ) || (@warn "Density matrix is not Hermitian. Results may not be meaningful.") + + state_norm = norm(ρ) + if !isapprox(state_norm, 1.0, atol = 1e-6) + @warn "State is not normalized. Normalizing before Bloch vector conversion." + ρ2 = ρ / state_norm + else + ρ2 = ρ + end + x = real(ρ2[1, 2] + ρ2[2, 1]) + y = imag(ρ2[2, 1] - ρ2[1, 2]) + z = real(ρ2[1, 1] - ρ2[2, 2]) + return [x, y, z] +end @doc raw""" clear!(b::Bloch) -Clear all graphical elements (points, vectors, lines, arcs) from the given Bloch sphere object `b`. +Clear all graphical elements (points, vectors, lines, arcs) from the given [`Bloch`](@ref) sphere object `b`. # Arguments -- `b::Bloch` - The Bloch sphere instance whose contents will be cleared. +- `b::Bloch`: The Bloch sphere instance whose contents will be cleared. # Returns @@ -340,31 +510,27 @@ function clear!(b::Bloch) end @doc raw""" - render(b::QuantumToolbox.Bloch; location=nothing) + render(b::Bloch; location=nothing) -Render the Bloch sphere visualization from the given `Bloch` object `b`. +Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`. # Arguments -- `b::QuantumToolbox.Bloch` - The Bloch sphere object containing states, vectors, and settings to visualize. - -- `location` (optional) - Specifies where to display or save the rendered figure. +- `b::Bloch`: The Bloch sphere object containing states, vectors, and settings to visualize. +- `location`: Specifies where to display or save the rendered figure. - If `nothing` (default), the figure is displayed interactively. - If a file path (String), the figure is saved to the specified location. - Other values depend on backend support. # Returns -- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. - These can be further manipulated or saved by the user. +- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. These can be further manipulated or saved by the user. """ function render end @doc raw""" plot_bloch( - state::QuantumObject{<:Union{Ket,Bra,Operator}}; + state::QuantumObject; library::Union{Symbol, Val} = :Makie, kwargs... ) @@ -374,8 +540,8 @@ Plot the state of a two-level quantum system on the Bloch sphere. The `library` keyword argument specifies the plotting backend to use. The default is `:Makie`, which uses the [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) plotting library. This function internally dispatches to a type-stable version based on `Val(:Makie)` or other plotting backends. # Arguments -- `state::QuantumObject`: The quantum state to be visualized. Can be a ket, bra, or operator. -- `library::Union{Symbol, Val}`: The plotting backend, either as a `Symbol` (e.g. `:Makie`) or a `Val` (e.g. `Val(:Makie)`). Default is `:Makie`. +- `state::QuantumObject`: The quantum state to be visualized. Can be a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref). +- `library::Union{Val,Symbol}`: The plotting library to use. Default is `Val(:Makie)`. - `kwargs...`: Additional keyword arguments passed to the specific plotting implementation. !!! note "Import library first" @@ -384,30 +550,10 @@ The `library` keyword argument specifies the plotting backend to use. The defaul !!! warning "Beware of type-stability!" For improved performance and type-stability, prefer passing `Val(:Makie)` instead of `:Makie`. See [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) for details. """ -function plot_bloch(state::QuantumObject{<:Union{Ket,Bra,Operator}}; library::Union{Symbol,Val} = :Makie, kwargs...) - lib_val = library isa Symbol ? Val(library) : library - return plot_bloch(lib_val, state; kwargs...) -end - -@doc raw""" - plot_bloch(::Val{T}, state::QuantumObject; kwargs...) where {T} - -Fallback implementation for unsupported plotting backends. - -# Arguments -- `::Val{T}`: The unsupported backend specification. -- `state::QuantumObject`: The quantum state that was attempted to be plotted. -- `kwargs...`: Ignored keyword arguments. - -# Throws -- `ErrorException`: Always throws an error indicating the backend `T` is unsupported. - -# Note -This function serves as a fallback when an unsupported backend is requested. Currently supported backends include: -- `:Makie` (using `Makie.jl`) - -See the main `plot_bloch` documentation for supported backends. -""" -function plot_bloch(::Val{T}, state::QuantumObject; kwargs...) where {T} - return error("Unsupported backend: $T. Try :Makie or another supported library.") -end +plot_bloch( + state::QuantumObject{OpType}; + library::Union{Symbol,Val} = Val(:Makie), + kwargs..., +) where {OpType<:Union{Ket,Bra,Operator}} = plot_bloch(makeVal(library), state; kwargs...) +plot_bloch(::Val{T}, state::QuantumObject{OpType}; kwargs...) where {T,OpType<:Union{Ket,Bra,Operator}} = + throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) diff --git a/test/ext-test/cpu/makie/makie_ext.jl b/test/ext-test/cpu/makie/makie_ext.jl index 7887c9da0..60f247be9 100644 --- a/test/ext-test/cpu/makie/makie_ext.jl +++ b/test/ext-test/cpu/makie/makie_ext.jl @@ -3,9 +3,10 @@ xvec = yvec = -15.0:0.1:15.0 wig = transpose(wigner(ψ, xvec, yvec)) + # Makie unload errors @test_throws ArgumentError plot_wigner(ψ; library = :Makie, xvec = xvec, yvec = yvec) - @test_throws ArgumentError plot_fock_distribution(ψ; library = :Makie) + @test_throws ArgumentError plot_bloch(ψ; library = :Makie) using Makie @@ -63,12 +64,12 @@ end @testset "Makie Bloch sphere" begin - ρ = 0.7*ket2dm(basis(2, 0)) + 0.3*ket2dm(basis(2, 1)) + ρ = 0.7 * ket2dm(basis(2, 0)) + 0.3 * ket2dm(basis(2, 1)) fig, ax = plot_bloch(ρ) @test fig isa Figure @test ax isa Axis3 - ψ = (basis(2, 0) + basis(2, 1))/√2 + ψ = (basis(2, 0) + basis(2, 1)) / √2 fig, ax = plot_bloch(ψ) @test fig isa Figure @test ax isa Axis3 @@ -92,25 +93,37 @@ end add_points!(b, hcat(pts...)) @test length(b.points) == 2 @test b.points[2] ≈ hcat(pts...) + @test_throws ArgumentError add_points!(b, [1 2 3 4]) + @test_throws ArgumentError add_points!(b, pts; meth = :wrong) b = Bloch() add_vectors!(b, [1.0, 1.0, 0.0]) @test length(b.vectors) == 1 - @test isapprox(norm(b.vectors[1]), 1.0) + @test isapprox(norm(b.vectors[1]), √2) vecs = [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]] add_vectors!(b, vecs) @test length(b.vectors) == 3 - @test all(norm(v) ≈ 1.0 for v in b.vectors) + @test isapprox(norm(b.vectors[2]), 1.0) + @test isapprox(norm(b.vectors[3]), 1.0) + vec_correct = [1, 0, 0] + vec_wrong = [1, 0] b = Bloch() add_line!(b, [0, 0, 0], [1, 0, 0]) @test length(b.lines) == 1 @test b.lines[1][1][3] ≈ [0.0, 0.0] + @test_throws ArgumentError add_line!(b, vec_wrong, vec_correct) + @test_throws ArgumentError add_line!(b, vec_correct, vec_wrong) add_arc!(b, [0, 0, 1], [0, 1, 0], [1, 0, 0]) @test length(b.arcs) == 1 @test b.arcs[1][3] == [1.0, 0.0, 0.0] + @test_throws ArgumentError add_arc!(b, vec_wrong, vec_correct) + @test_throws ArgumentError add_arc!(b, vec_correct, vec_wrong) + @test_throws ArgumentError add_arc!(b, vec_wrong, vec_correct, vec_correct) + @test_throws ArgumentError add_arc!(b, vec_correct, vec_wrong, vec_correct) + @test_throws ArgumentError add_arc!(b, vec_correct, vec_correct, vec_wrong) b = Bloch() add_points!(b, [0.0, 0.0, 1.0]) @@ -147,12 +160,22 @@ end @test false @info "Render threw unexpected error" exception=e end + + # test `state to Bloch vector` conversion and `add_states!` function b = Bloch() - x = basis(2, 0) + basis(2, 1) - y = basis(2, 0) - im * basis(2, 1) - z = basis(2, 0) - add_states!(b, [x, y, z]) - th = range(0, 2π; length = 20); + Pauli_Ops = [sigmax(), sigmay(), sigmaz()] + ψ = rand_ket(2) + ρ = rand_dm(2) + x = basis(2, 0) + basis(2, 1) # unnormalized Ket + ρ1 = 0.3 * rand_dm(2) + 0.4 * rand_dm(2) # unnormalized density operator + ρ2 = Qobj(rand(ComplexF64, 2, 2)) # unnormalized and non-Hermitian Operator + add_states!(b, [ψ, ρ]) + @test_logs (:warn,) (:warn,) (:warn,) (:warn,) add_states!(b, [x, ρ1, ρ2]) + @test all(expect(Pauli_Ops, ψ) .≈ (b.vectors[1])) + @test all(expect(Pauli_Ops, ρ) .≈ (b.vectors[2])) + @test length(b.vectors) == 5 + + th = range(0, 2π; length = 20) xp = cos.(th); yp = sin.(th); zp = zeros(20); @@ -174,7 +197,10 @@ end b = Bloch() ψ₁ = normalize(basis(2, 0) + basis(2, 1)) ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) + ψ₃ = basis(2, 0) add_line!(b, ψ₁, ψ₂; fmt = "r--") + add_arc!(b, ψ₁, ψ₂) + add_arc!(b, ψ₂, ψ₃, ψ₁) try fig, ax = render(b) @test !isnothing(fig) From 826a03d71a24df641d5c6a2b3f16e44b09adf36d Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 9 Jun 2025 07:54:56 +0800 Subject: [PATCH 275/329] Implement `Base.copy` for `AbstractQuantumObject` (#486) --- CHANGELOG.md | 2 ++ src/qobj/quantum_object_base.jl | 4 +++- test/core-test/quantum_objects.jl | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49e8cbeef..b7902671f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Introduce `Lanczos` solver for `spectrum`. ([#476]) - Add Bloch-Redfield master equation solver. ([#473]) - Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480]) +- Add `Base.copy` method for `AbstractQuantumObject`. ([#486]) ## [v0.31.1] Release date: 2025-05-16 @@ -236,3 +237,4 @@ Release date: 2024-11-13 [#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473 [#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 [#480]: https://github.com/qutip/QuantumToolbox.jl/issues/480 +[#486]: https://github.com/qutip/QuantumToolbox.jl/issues/486 diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index 9fcbb0502..cf5fcd679 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -1,6 +1,6 @@ #= This file defines the AbstractQuantumObject structure, all the type structures for AbstractQuantumObject, and fundamental functions in Julia standard library: - - Base: show, length, size, eltype, getindex, setindex!, isequal, :(==), isapprox + - Base: show, length, size, copy, eltype, getindex, setindex!, isequal, :(==), isapprox =# export AbstractQuantumObject @@ -93,6 +93,8 @@ Optionally, you can specify an index (`idx`) to just get the corresponding dimen Base.size(A::AbstractQuantumObject) = size(A.data) Base.size(A::AbstractQuantumObject, idx::Int) = size(A.data, idx) +Base.copy(A::AbstractQuantumObject) = get_typename_wrapper(A)(copy(A.data), A.type, A.dimensions) + Base.getindex(A::AbstractQuantumObject, inds...) = getindex(A.data, inds...) Base.setindex!(A::AbstractQuantumObject, val, inds...) = setindex!(A.data, val, inds...) diff --git a/test/core-test/quantum_objects.jl b/test/core-test/quantum_objects.jl index 81afc501b..920f26970 100644 --- a/test/core-test/quantum_objects.jl +++ b/test/core-test/quantum_objects.jl @@ -186,9 +186,12 @@ a2 = Qobj(a) a3 = Qobj(a, type = SuperOperator()) a4 = to_sparse(a2) + a4_copy = copy(a4) + a4_copy[1] = rand(ComplexF64) @test isequal(a4, a2) == true @test isequal(a4, a3) == false @test a4 ≈ a2 + @test a4 != a4_copy @test real(a2).data == real(a) @test imag(a2).data == imag(a) From 22efb110b3cd3ea0a04e95b1966285cb80dfcef0 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:37:02 +0200 Subject: [PATCH 276/329] Use `LScene` instead of `Axis3` for `Bloch` Sphere (#485) Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> --- CHANGELOG.md | 3 +- .../users_guide/plotting_the_bloch_sphere.md | 8 +- ext/QuantumToolboxMakieExt.jl | 215 ++++++++---------- src/visualization.jl | 20 +- test/ext-test/cpu/makie/makie_ext.jl | 33 +-- 5 files changed, 131 insertions(+), 148 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7902671f..e0a62f3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Introduce `Lanczos` solver for `spectrum`. ([#476]) - Add Bloch-Redfield master equation solver. ([#473]) -- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480]) +- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485]) - Add `Base.copy` method for `AbstractQuantumObject`. ([#486]) ## [v0.31.1] @@ -237,4 +237,5 @@ Release date: 2024-11-13 [#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473 [#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 [#480]: https://github.com/qutip/QuantumToolbox.jl/issues/480 +[#485]: https://github.com/qutip/QuantumToolbox.jl/issues/485 [#486]: https://github.com/qutip/QuantumToolbox.jl/issues/486 diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md index 4bed2b208..abb7b6c6f 100644 --- a/docs/src/users_guide/plotting_the_bloch_sphere.md +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -121,7 +121,7 @@ yp = sin.(th) zp = zeros(20) pnts = [xp, yp, zp] add_points!(b, pnts) -fig, ax = render(b) +fig, lscene = render(b) fig ``` @@ -132,7 +132,7 @@ xz = zeros(20) yz = sin.(th) zz = cos.(th) add_points!(b, [xz, yz, zz]) -fig, ax = render(b) +fig, lscene = render(b) fig ``` @@ -148,7 +148,7 @@ yp = sin.(th) zp = zeros(20) pnts = [xp, yp, zp] add_points!(b, pnts, meth=:m) # add `meth=:m` to signify 'multi' colored points -fig, ax = render(b) +fig, lscene = render(b) fig ``` @@ -157,7 +157,7 @@ Now, the data points cycle through a variety of predefined colors. Now lets add ```@example Bloch_sphere_rendering pnts = [xz, yz, zz] add_points!(b, pnts) # no `meth=:m` -fig, ax = render(b) +fig, lscene = render(b) fig ``` diff --git a/ext/QuantumToolboxMakieExt.jl b/ext/QuantumToolboxMakieExt.jl index 8e4b83f98..4c215dd44 100644 --- a/ext/QuantumToolboxMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -7,6 +7,7 @@ import LinearAlgebra: cross, deg2rad, normalize, size import Makie: Axis, Axis3, + LScene, Colorbar, Figure, GridLayout, @@ -28,7 +29,12 @@ import Makie: RGBf, Vec3f, Point3f, - NoShading + NoShading, + cameracontrols, + update_cam!, + cam3d! + +import Makie.GeometryBasics: Tessellation @doc raw""" plot_wigner( @@ -340,23 +346,23 @@ Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`. # Returns -- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. These can be further manipulated or saved by the user. +- A tuple `(fig, lscene)` where `fig` is the figure object and `lscene` is the LScene object used for plotting. These can be further manipulated or saved by the user. """ function QuantumToolbox.render(b::Bloch; location = nothing) - fig, ax = _setup_bloch_plot!(b, location) - _draw_bloch_sphere!(b, ax) - _draw_reference_circles!(ax) - _draw_axes!(ax) - _plot_points!(b, ax) - _plot_lines!(b, ax) - _plot_arcs!(b, ax) - _plot_vectors!(b, ax) - _add_labels!(b, ax) - return fig, ax + fig, lscene = _setup_bloch_plot!(b, location) + _draw_bloch_sphere!(b, lscene) + _draw_reference_circles!(b, lscene) + _draw_axes!(b, lscene) + _plot_points!(b, lscene) + _plot_lines!(b, lscene) + _plot_arcs!(b, lscene) + _plot_vectors!(b, lscene) + _add_labels!(b, lscene) + return fig, lscene end raw""" - _setup_bloch_plot!(b::Bloch, location) -> (fig, ax) + _setup_bloch_plot!(b::Bloch, location) -> (fig, lscene) Initialize the figure and `3D` axis for Bloch sphere visualization. @@ -366,138 +372,116 @@ Initialize the figure and `3D` axis for Bloch sphere visualization. # Returns - `fig`: Created Makie figure -- `ax`: Configured Axis3 object +- `lscene`: Configured LScene object Sets up the `3D` coordinate system with appropriate limits and view angles. """ function _setup_bloch_plot!(b::Bloch, location) fig, location = _getFigAndLocation(location) - bg_color = parse(RGBf, b.frame_color) - frame_color = RGBAf(bg_color, b.frame_alpha) - ax = Axis3( - location; - aspect = :data, - limits = (-b.frame_limit, b.frame_limit, -b.frame_limit, b.frame_limit, -b.frame_limit, b.frame_limit), - xgridvisible = false, - ygridvisible = false, - zgridvisible = false, - xticklabelsvisible = false, - yticklabelsvisible = false, - zticklabelsvisible = false, - xticksvisible = false, - yticksvisible = false, - zticksvisible = false, - xlabel = "", - ylabel = "", - zlabel = "", - backgroundcolor = frame_color, - xypanelvisible = false, - xzpanelvisible = false, - yzpanelvisible = false, - xspinesvisible = false, - yspinesvisible = false, - zspinesvisible = false, - protrusions = (0, 0, 0, 0), - perspectiveness = 0.2, - viewmode = :fit, - ) + lscene = LScene(location, show_axis = false, scenekw = (clear = true,)) length(b.view) == 2 || throw(ArgumentError("The length of `Bloch.view` must be 2.")) - ax.azimuth[] = deg2rad(b.view[1]) - ax.elevation[] = deg2rad(b.view[2]) - return fig, ax + cam3d!(lscene.scene, center = false) + cam = cameracontrols(lscene) + cam.fov[] = 12 # Set field of view to 12 degrees + dist = 12 # Set distance from the camera to the Bloch sphere + update_cam!(lscene.scene, cam, deg2rad(b.view[1]), deg2rad(b.view[2]), dist) + return fig, lscene end raw""" - _draw_bloch_sphere!(b, ax) + _draw_bloch_sphere!(b, lscene) Draw the translucent sphere representing the Bloch sphere surface. """ -function _draw_bloch_sphere!(b::Bloch, ax) - n_lon = 4 - n_lat = 4 +function _draw_bloch_sphere!(b::Bloch, lscene) radius = 1.0f0 - base_color = parse(RGBf, b.sphere_color) - sphere_color = RGBAf(base_color, b.sphere_alpha) sphere_mesh = Sphere(Point3f(0), radius) - mesh!(ax, sphere_mesh; color = sphere_color, shading = NoShading, transparency = true, rasterize = 3) - θ_vals = range(0.0f0, 2π, length = n_lon + 1)[1:(end-1)] - φ_curve = range(0.0f0, π, length = 600) - line_alpha = max(0.05, b.sphere_alpha * 0.5) + mesh!( + lscene, + sphere_mesh; + color = b.sphere_color, + alpha = b.sphere_alpha, + shading = NoShading, + transparency = true, + rasterize = 3, + ) + θ_vals = range(0, π, 5)[1:(end-1)] + φ_curve = range(0, 2π, 600) for θi in θ_vals - x_line = [radius * sin(ϕ) * cos(θi) for ϕ in φ_curve] - y_line = [radius * sin(ϕ) * sin(θi) for ϕ in φ_curve] - z_line = [radius * cos(ϕ) for ϕ in φ_curve] - lines!(ax, x_line, y_line, z_line; color = RGBAf(0.5, 0.5, 0.5, line_alpha), linewidth = 1, transparency = true) + x_line = radius * sin.(φ_curve) .* cos(θi) + y_line = radius * sin.(φ_curve) .* sin(θi) + z_line = radius * cos.(φ_curve) + lines!(lscene, x_line, y_line, z_line; color = b.frame_color, alpha = b.frame_alpha) end - φ_vals = range(0.0f0, π, length = n_lat + 2) - θ_curve = range(0.0f0, 2π, length = 600) + φ_vals = range(0, π, 6) + θ_curve = range(0, 2π, 600) for ϕ in φ_vals - x_ring = [radius * sin(ϕ) * cos(θi) for θi in θ_curve] - y_ring = [radius * sin(ϕ) * sin(θi) for θi in θ_curve] + x_ring = radius * sin(ϕ) .* cos.(θ_curve) + y_ring = radius * sin(ϕ) .* sin.(θ_curve) z_ring = fill(radius * cos(ϕ), length(θ_curve)) - lines!(ax, x_ring, y_ring, z_ring; color = RGBAf(0.5, 0.5, 0.5, line_alpha), linewidth = 1, transparency = true) + lines!(lscene, x_ring, y_ring, z_ring; color = b.frame_color, alpha = b.frame_alpha) end end raw""" - _draw_reference_circles!(ax) + _draw_reference_circles!(b::Bloch, lscene) -Draw the three great circles `(XY, YZ, XZ planes)` on the Bloch sphere. +Draw the three great circles `(XY, XZ planes)` on the Bloch sphere. # Arguments -- `ax`: Makie Axis3 object for drawing +- `b::Bloch`: Bloch sphere object containing frame color +- `lscene`: Makie LScene object for drawing Adds faint circular guidelines representing the three principal planes. """ -function _draw_reference_circles!(ax) - wire_color = RGBAf(0.5, 0.5, 0.5, 0.4) +function _draw_reference_circles!(b::Bloch, lscene) + wire_color = b.frame_color φ = range(0, 2π, length = 100) - # XY, YZ, XZ circles + # XY, XZ circles circles = [ [Point3f(cos(φi), sin(φi), 0) for φi in φ], # XY - [Point3f(0, cos(φi), sin(φi)) for φi in φ], # YZ [Point3f(cos(φi), 0, sin(φi)) for φi in φ], # XZ ] for circle in circles - lines!(ax, circle; color = wire_color, linewidth = 1.0) + lines!(lscene, circle; color = wire_color, linewidth = 1.0) end end raw""" - _draw_axes!(ax) + _draw_axes!(b::Bloch, lscene) Draw the three principal axes `(x, y, z)` of the Bloch sphere. # Arguments -- `ax`: Makie Axis3 object for drawing +- `b::Bloch`: Bloch sphere object containing axis color +- `lscene`: Makie LScene object for drawing Creates visible axis lines extending slightly beyond the unit sphere. """ -function _draw_axes!(ax) - axis_color = RGBAf(0.3, 0.3, 0.3, 0.8) - axis_width = 0.8 +function _draw_axes!(b::Bloch, lscene) + axis_color = b.frame_color axes = [ - ([Point3f(1.0, 0, 0), Point3f(-1.0, 0, 0)], "x"), # X-axis - ([Point3f(0, 1.0, 0), Point3f(0, -1.0, 0)], "y"), # Y-axis - ([Point3f(0, 0, 1.0), Point3f(0, 0, -1.0)], "z"), # Z-axis + [Point3f(1.0, 0, 0), Point3f(-1.0, 0, 0)], # X-axis + [Point3f(0, 1.0, 0), Point3f(0, -1.0, 0)], # Y-axis + [Point3f(0, 0, 1.0), Point3f(0, 0, -1.0)], # Z-axis ] - for (points, _) in axes - lines!(ax, points; color = axis_color, linewidth = axis_width) + for points in axes + lines!(lscene, points; color = axis_color) end end raw""" - _plot_points!(b::Bloch, ax) + _plot_points!(b::Bloch, lscene) Plot all quantum state points on the Bloch sphere. # Arguments - `b::Bloch`: Contains point data and styling information -- `ax`: Axis3 object for plotting +- `lscene`: LScene object for plotting Handles both scatter points and line traces based on style specifications. """ -function _plot_points!(b::Bloch, ax) +function _plot_points!(b::Bloch, lscene) for k in 1:length(b.points) pts = b.points[k] style = b.point_style[k] @@ -536,7 +520,7 @@ function _plot_points!(b::Bloch, ax) ys = raw_y[indperm] zs = raw_z[indperm] scatter!( - ax, + lscene, xs, ys, zs; @@ -553,23 +537,23 @@ function _plot_points!(b::Bloch, ax) ys = raw_y zs = raw_z c = isa(colors, Vector) ? colors[1] : colors - lines!(ax, xs, ys, zs; color = c, linewidth = 2.0, transparency = alpha < 1.0, alpha = alpha) + lines!(lscene, xs, ys, zs; color = c, linewidth = 2.0, transparency = alpha < 1.0, alpha = alpha) end end end raw""" - _plot_lines!(b::Bloch, ax) + _plot_lines!(b::Bloch, lscene) Draw all connecting lines between points on the Bloch sphere. # Arguments - `b::Bloch`: Contains line data and formatting -- `ax`: Axis3 object for drawing +- `lscene`: LScene object for drawing Processes line style specifications and color mappings. """ -function _plot_lines!(b::Bloch, ax) +function _plot_lines!(b::Bloch, lscene) color_map = Dict("k" => :black, "r" => :red, "g" => :green, "b" => :blue, "c" => :cyan, "m" => :magenta, "y" => :yellow) for (line, fmt) in b.lines @@ -585,22 +569,22 @@ function _plot_lines!(b::Bloch, ax) else :solid end - lines!(ax, x, y, z; color = color, linewidth = 1.0, linestyle = linestyle) + lines!(lscene, x, y, z; color = color, linewidth = 1.0, linestyle = linestyle) end end raw""" - _plot_arcs!(b::Bloch, ax) + _plot_arcs!(b::Bloch, lscene) Draw circular arcs connecting points on the Bloch sphere surface. # Arguments - `b::Bloch`: Contains arc data points -- `ax`: Axis3 object for drawing +- `lscene`: LScene object for drawing Calculates great circle arcs between specified points. """ -function _plot_arcs!(b::Bloch, ax) +function _plot_arcs!(b::Bloch, lscene) for arc_pts in b.arcs length(arc_pts) >= 2 || continue v1 = normalize(arc_pts[1]) @@ -613,22 +597,22 @@ function _plot_arcs!(b::Bloch, ax) end t_range = range(0, θ, length = 100) arc_points = [Point3f((v1 * cos(t) + cross(n, v1) * sin(t))) for t in t_range] - lines!(ax, arc_points; color = RGBAf(0.8, 0.4, 0.1, 0.9), linewidth = 2.0, linestyle = :solid) + lines!(lscene, arc_points; color = RGBAf(0.8, 0.4, 0.1, 0.9), linewidth = 2.0, linestyle = :solid) end end raw""" - _plot_vectors!(b::Bloch, ax) + _plot_vectors!(b::Bloch, lscene) Draw vectors from origin representing quantum states. # Arguments - `b::Bloch`: Contains vector data -- `ax`: Axis3 object for drawing +- `lscene`: LScene object for drawing Scales vectors appropriately and adds `3D` arrow markers. """ -function _plot_vectors!(b::Bloch, ax) +function _plot_vectors!(b::Bloch, lscene) isempty(b.vectors) && return arrowsize_vec = Vec3f(b.vector_arrowsize...) r = 1.0 @@ -639,7 +623,7 @@ function _plot_vectors!(b::Bloch, ax) max_length = r * 0.90 vec = length > max_length ? (vec/length) * max_length : vec arrows!( - ax, + lscene, [Point3f(0, 0, 0)], [vec], color = color, @@ -652,16 +636,16 @@ function _plot_vectors!(b::Bloch, ax) end raw""" - _add_labels!(ax) + _add_labels!(lscene) Add axis labels and state labels to the Bloch sphere. # Arguments -- `ax`: Axis3 object for text placement +- `lscene`: LScene object for text placement Positions standard labels `(x, y, |0⟩, |1⟩)` at appropriate locations. """ -function _add_labels!(b::Bloch, ax) +function _add_labels!(b::Bloch, lscene) length(b.xlabel) == 2 || throw(ArgumentError("The length of `Bloch.xlabel` must be 2.")) length(b.ylabel) == 2 || throw(ArgumentError("The length of `Bloch.ylabel` must be 2.")) length(b.zlabel) == 2 || throw(ArgumentError("The length of `Bloch.zlabel` must be 2.")) @@ -671,52 +655,51 @@ function _add_labels!(b::Bloch, ax) label_color = parse(RGBf, b.font_color) label_size = b.font_size - offset_scale = b.frame_limit (b.xlabel[1] == "") || text!( - ax, + lscene, b.xlabel[1], - position = Point3f(offset_scale * b.xlpos[1], 0, 0), + position = Point3f(b.xlpos[1], 0, 0), color = label_color, fontsize = label_size, align = (:center, :center), ) (b.xlabel[2] == "") || text!( - ax, + lscene, b.xlabel[2], - position = Point3f(offset_scale * b.xlpos[2], 0, 0), + position = Point3f(b.xlpos[2], 0, 0), color = label_color, fontsize = label_size, align = (:center, :center), ) (b.ylabel[1] == "") || text!( - ax, + lscene, b.ylabel[1], - position = Point3f(0, offset_scale * b.ylpos[1], 0), + position = Point3f(0, b.ylpos[1], 0), color = label_color, fontsize = label_size, align = (:center, :center), ) (b.ylabel[2] == "") || text!( - ax, + lscene, b.ylabel[2], - position = Point3f(0, offset_scale * b.ylpos[2], 0), + position = Point3f(0, b.ylpos[2], 0), color = label_color, fontsize = label_size, align = (:center, :center), ) (b.zlabel[1] == "") || text!( - ax, + lscene, b.zlabel[1], - position = Point3f(0, 0, offset_scale * b.zlpos[1]), + position = Point3f(0, 0, b.zlpos[1]), color = label_color, fontsize = label_size, align = (:center, :center), ) (b.zlabel[2] == "") || text!( - ax, + lscene, b.zlabel[2], - position = Point3f(0, 0, offset_scale * b.zlpos[2]), + position = Point3f(0, 0, b.zlpos[2]), color = label_color, fontsize = label_size, align = (:center, :center), diff --git a/src/visualization.jl b/src/visualization.jl index a75aab922..412103bce 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -81,9 +81,8 @@ A structure representing a Bloch sphere visualization for quantum states. - `font_color::String`: Color of axis labels and text - `font_size::Int`: Font size for labels. Default: `15` -- `frame_alpha::Float64`: Transparency of the frame background -- `frame_color::String`: Background color of the frame -- `frame_limit::Float64`: Axis limits for the 3D frame (symmetric around origin) +- `frame_alpha::Float64`: Transparency of the wireframe +- `frame_color::String`: Color of the wireframe ## Point properties @@ -111,11 +110,11 @@ A structure representing a Bloch sphere visualization for quantum states. ## Label properties - `xlabel::Vector{AbstractString}`: Labels for x-axis. Default: `[L"x", ""]` -- `xlpos::Vector{Float64}`: Positions of x-axis labels. Default: `[1.0, -1.0]` +- `xlpos::Vector{Float64}`: Positions of x-axis labels. Default: `[1.2, -1.2]` - `ylabel::Vector{AbstractString}`: Labels for y-axis. Default: `[L"y", ""]` -- `ylpos::Vector{Float64}`: Positions of y-axis labels. Default: `[1.0, -1.0]` +- `ylpos::Vector{Float64}`: Positions of y-axis labels. Default: `[1.2, -1.2]` - `zlabel::Vector{AbstractString}`: Labels for z-axis. Default: `[L"|0\rangle", L"|1\rangle"]` -- `zlpos::Vector{Float64}`: Positions of z-axis labels. Default: `[1.0, -1.0]` +- `zlpos::Vector{Float64}`: Positions of z-axis labels. Default: `[1.2, -1.2]` """ @kwdef mutable struct Bloch points::Vector{Matrix{Float64}} = Vector{Matrix{Float64}}() @@ -126,7 +125,6 @@ A structure representing a Bloch sphere visualization for quantum states. font_size::Int = 15 frame_alpha::Float64 = 0.1 frame_color::String = "gray" - frame_limit::Float64 = 1.2 point_default_color::Vector{String} = ["blue", "red", "green", "#CC6600"] point_color::Vector{Union{Nothing,String}} = Union{Nothing,String}[] point_marker::Vector{Symbol} = [:circle, :rect, :diamond, :utriangle] @@ -140,11 +138,11 @@ A structure representing a Bloch sphere visualization for quantum states. vector_arrowsize::Vector{Float64} = [0.07, 0.08, 0.08] view::Vector{Int} = [30, 30] xlabel::Vector{AbstractString} = [L"x", ""] - xlpos::Vector{Float64} = [1.0, -1.0] + xlpos::Vector{Float64} = [1.2, -1.2] ylabel::Vector{AbstractString} = [L"y", ""] - ylpos::Vector{Float64} = [1.0, -1.0] + ylpos::Vector{Float64} = [1.2, -1.2] zlabel::Vector{AbstractString} = [L"|0\rangle", L"|1\rangle"] - zlpos::Vector{Float64} = [1.0, -1.0] + zlpos::Vector{Float64} = [1.2, -1.2] end const BLOCH_DATA_FIELDS = (:points, :vectors, :lines, :arcs) @@ -524,7 +522,7 @@ Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`. # Returns -- A tuple `(fig, axis)` where `fig` is the figure object and `axis` is the axis object used for plotting. These can be further manipulated or saved by the user. +- A tuple `(fig, lscene)` where `fig` is the figure object and `lscene` is the `LScene` object used for plotting. These can be further manipulated or saved by the user. """ function render end diff --git a/test/ext-test/cpu/makie/makie_ext.jl b/test/ext-test/cpu/makie/makie_ext.jl index 60f247be9..32ef267f9 100644 --- a/test/ext-test/cpu/makie/makie_ext.jl +++ b/test/ext-test/cpu/makie/makie_ext.jl @@ -65,23 +65,24 @@ end @testset "Makie Bloch sphere" begin ρ = 0.7 * ket2dm(basis(2, 0)) + 0.3 * ket2dm(basis(2, 1)) - fig, ax = plot_bloch(ρ) + fig, lscene = plot_bloch(ρ) @test fig isa Figure - @test ax isa Axis3 + @test lscene isa LScene ψ = (basis(2, 0) + basis(2, 1)) / √2 - fig, ax = plot_bloch(ψ) + fig, lscene = plot_bloch(ψ) @test fig isa Figure - @test ax isa Axis3 + @test lscene isa LScene ϕ = dag(ψ) - fig, ax = plot_bloch(ϕ) + fig, lscene = plot_bloch(ϕ) @test fig isa Figure - @test ax isa Axis3 + @test lscene isa LScene fig = Figure() - pos = fig[1, 1] - fig1, ax = plot_bloch(ψ; location = pos) + ax = Axis(fig[1, 1]) + pos = fig[1, 2] + fig1, lscene = plot_bloch(ψ; location = pos) @test fig1 === fig b = Bloch() @@ -141,9 +142,9 @@ end add_line!(b, [0, 0, 0], [1, 1, 1]) add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) try - fig, ax = QuantumToolbox.render(b) + fig, lscene = render(b) @test !isnothing(fig) - @test !isnothing(ax) + @test !isnothing(lscene) catch e @test false @info "Render threw unexpected error" exception=e @@ -153,9 +154,9 @@ end ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) add_line!(b, ψ₁, ψ₂; fmt = "r--") try - fig, ax = QuantumToolbox.render(b) + fig, lscene = render(b) @test !isnothing(fig) - @test !isnothing(ax) + @test !isnothing(lscene) catch e @test false @info "Render threw unexpected error" exception=e @@ -187,9 +188,9 @@ end add_line!(b, [1, 0, 0], [0, 1, 0]) add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1]) try - fig, ax = render(b) + fig, lscene = render(b) @test !isnothing(fig) - @test !isnothing(ax) + @test !isnothing(lscene) catch e @test false @info "Render threw unexpected error" exception=e @@ -202,9 +203,9 @@ end add_arc!(b, ψ₁, ψ₂) add_arc!(b, ψ₂, ψ₃, ψ₁) try - fig, ax = render(b) + fig, lscene = render(b) @test !isnothing(fig) - @test !isnothing(ax) + @test !isnothing(lscene) catch e @test false @info "Render threw unexpected error" exception=e From 97bf074e8ed881946028d84cc7f58496124892ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:14:31 +0800 Subject: [PATCH 277/329] Bump crate-ci/typos from 1.32.0 to 1.33.1 (#488) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 12a54edb4..531b0447e 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.32.0 \ No newline at end of file + uses: crate-ci/typos@v1.33.1 \ No newline at end of file From dc4ecde44c69ea77969d832839332f4c555de769 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 10 Jun 2025 04:40:10 +0800 Subject: [PATCH 278/329] Fix `Bloch` docstrings and improve visualization of the sphere (#487) --- CHANGELOG.md | 3 +- .../users_guide/plotting_the_bloch_sphere.md | 29 ++- ext/QuantumToolboxMakieExt.jl | 245 ++++++++---------- src/visualization.jl | 64 ++--- test/ext-test/cpu/makie/makie_ext.jl | 14 +- 5 files changed, 179 insertions(+), 176 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0a62f3a7..46997d6f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Introduce `Lanczos` solver for `spectrum`. ([#476]) - Add Bloch-Redfield master equation solver. ([#473]) -- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485]) +- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485], [#487]) - Add `Base.copy` method for `AbstractQuantumObject`. ([#486]) ## [v0.31.1] @@ -239,3 +239,4 @@ Release date: 2024-11-13 [#480]: https://github.com/qutip/QuantumToolbox.jl/issues/480 [#485]: https://github.com/qutip/QuantumToolbox.jl/issues/485 [#486]: https://github.com/qutip/QuantumToolbox.jl/issues/486 +[#487]: https://github.com/qutip/QuantumToolbox.jl/issues/487 diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md index abb7b6c6f..273ad810c 100644 --- a/docs/src/users_guide/plotting_the_bloch_sphere.md +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -17,6 +17,9 @@ In [`QuantumToolbox`](https://qutip.org/QuantumToolbox.jl/), this can be done us In [`QuantumToolbox`](https://qutip.org/QuantumToolbox.jl/), creating a [`Bloch`](@ref) sphere is accomplished by calling either: +!!! note "Import plotting libraries" + Remember to import plotting libraries first. Here, we demonstrate the functionalities with [`CairoMakie.jl`](https://docs.makie.org/stable/explanations/backends/cairomakie.html). + ```@example Bloch_sphere_rendering b = Bloch() ``` @@ -163,19 +166,25 @@ fig ## [Configuring the Bloch sphere](@id doc:Configuring-the-Bloch-sphere) -At the end of the last section we saw that the colors and marker shapes of the data plotted on the Bloch sphere are automatically varied according to the number of points and vectors added. But what if you want a different choice of color, or you want your sphere to be purple with different axes labels? Well then you are in luck as the Bloch class has many attributes which one can control. Assuming `b = Bloch()`: +At the end of the last section we saw that the colors and marker shapes of the data plotted on the Bloch sphere are automatically varied according to the number of points and vectors added. But what if you want a different choice of color, or you want your sphere to be purple with different axes labels? Well then you are in luck as the [`Bloch`](@ref) structure has many fields which one can control. Assuming `b = Bloch()`: + +### Data storage | **Field** | **Description** | **Default setting** | |:----------|:----------------|:--------------------| | `b.points` | Points to plot on the Bloch sphere (3D coordinates) | `Vector{Matrix{Float64}}()` (empty) | | `b.vectors` | Vectors to plot on the Bloch sphere | `Vector{Vector{Float64}}()` (empty) | -| `b.lines` | Lines to draw on the sphere (points, style, properties) | `Vector{Tuple{Vector{Vector{Float64}},String}}()` (empty) | +| `b.lines` | Lines to draw on the sphere with each line given as `([start_pt, end_pt], line_format)` | `Vector{Tuple{Vector{Vector{Float64}},String}}()` (empty) | | `b.arcs` | Arcs to draw on the sphere | `Vector{Vector{Vector{Float64}}}()` (empty) | + +### Properties + +| **Field** | **Description** | **Default setting** | +|:----------|:----------------|:--------------------| | `b.font_color` | Color of axis labels and text | `"black"` | -| `b.font_size` | Font size for labels | `15` | -| `b.frame_alpha` | Transparency of the frame background | `0.1` | -| `b.frame_color` | Background color of the frame | `"gray"` | -| `b.frame_limit` | Axis limits for the 3D frame (symmetric around origin) | `1.2` | +| `b.font_size` | Font size for labels | `20` | +| `b.frame_alpha` | Transparency of the wire frame | `0.1` | +| `b.frame_color` | Color of the wire frame | `"gray"` | | `b.point_default_color` | Default color cycle for points | `["blue", "red", "green", "#CC6600"]` | | `b.point_color` | List of colors for Bloch point markers to cycle through | `Union{Nothing,String}[]` | | `b.point_marker` | List of point marker shapes to cycle through | `[:circle, :rect, :diamond, :utriangle]` | @@ -186,14 +195,14 @@ At the end of the last section we saw that the colors and marker shapes of the d | `b.sphere_alpha` | Transparency of sphere surface | `"#FFDDDD"` | | `b.vector_color` | Colors for vectors | `["green", "#CC6600", "blue", "red"]` | | `b.vector_width` | Width of vectors | `0.025` | -| `b.vector_arrowsize` | Arrow size parameters as (head length, head width, stem width) | `[0.07, 0.08, 0.08]` | +| `b.vector_arrowsize` | Scales the size of the arrow head. The first two elements scale the radius (in `x/y` direction) and the last one is the length of the cone. | `[0.07, 0.08, 0.08]` | | `b.view` | Azimuthal and elevation viewing angles in degrees | `[30, 30]` | | `b.xlabel` | Labels for x-axis | `[L"x", ""]` (``+x`` and ``-x``) | -| `b.xlpos` | Positions of x-axis labels | `[1.0, -1.0]` | +| `b.xlpos` | Positions of x-axis labels | `[1.2, -1.2]` | | `b.ylabel` | Labels for y-axis | `[L"y", ""]` (``+y`` and ``-y``) | -| `b.ylpos` | Positions of y-axis labels | `[1.0, -1.0]` | +| `b.ylpos` | Positions of y-axis labels | `[1.2, -1.2]` | | `b.zlabel` | Labels for z-axis | `[L"\|0\rangle", L"\|1\rangle]"` (``+z`` and ``-z``) | -| `b.zlpos` | Positions of z-axis labels | `[1.0, -1.0]` | +| `b.zlpos` | Positions of z-axis labels | `[1.2, -1.2]` | These properties can also be accessed via the `print` command: diff --git a/ext/QuantumToolboxMakieExt.jl b/ext/QuantumToolboxMakieExt.jl index 4c215dd44..88832a6a7 100644 --- a/ext/QuantumToolboxMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -34,8 +34,6 @@ import Makie: update_cam!, cam3d! -import Makie.GeometryBasics: Tessellation - @doc raw""" plot_wigner( library::Val{:Makie}, @@ -339,10 +337,7 @@ Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`. # Arguments - `b::Bloch`: The Bloch sphere object containing states, vectors, and settings to visualize. -- `location`: Specifies where to display or save the rendered figure. - - If `nothing` (default), the figure is displayed interactively. - - If a file path (String), the figure is saved to the specified location. - - Other values depend on backend support. +- `location::Union{GridPosition,Nothing}`: The location of the plot in the layout. If `nothing`, the plot is created in a new figure. Default is `nothing`. # Returns @@ -351,13 +346,14 @@ Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`. function QuantumToolbox.render(b::Bloch; location = nothing) fig, lscene = _setup_bloch_plot!(b, location) _draw_bloch_sphere!(b, lscene) - _draw_reference_circles!(b, lscene) - _draw_axes!(b, lscene) - _plot_points!(b, lscene) + _add_labels!(b, lscene) + + # plot data fields in Bloch + _plot_vectors!(b, lscene) _plot_lines!(b, lscene) _plot_arcs!(b, lscene) - _plot_vectors!(b, lscene) - _add_labels!(b, lscene) + _plot_points!(b, lscene) # plot points at the end so that they will be on the very top (front) figure layer. + return fig, lscene end @@ -383,15 +379,15 @@ function _setup_bloch_plot!(b::Bloch, location) cam3d!(lscene.scene, center = false) cam = cameracontrols(lscene) cam.fov[] = 12 # Set field of view to 12 degrees - dist = 12 # Set distance from the camera to the Bloch sphere + dist = 12 # Set distance from the camera to the Bloch sphere update_cam!(lscene.scene, cam, deg2rad(b.view[1]), deg2rad(b.view[2]), dist) return fig, lscene end raw""" - _draw_bloch_sphere!(b, lscene) + _draw_bloch_sphere!(b::Bloch, lscene) -Draw the translucent sphere representing the Bloch sphere surface. +Draw the translucent sphere, axes, and reference circles representing the Bloch sphere surface. """ function _draw_bloch_sphere!(b::Bloch, lscene) radius = 1.0f0 @@ -405,15 +401,34 @@ function _draw_bloch_sphere!(b::Bloch, lscene) transparency = true, rasterize = 3, ) - θ_vals = range(0, π, 5)[1:(end-1)] + + # X, Y, and Z axes + axes = [ + [Point3f(1.0, 0, 0), Point3f(-1.0, 0, 0)], # X-axis + [Point3f(0, 1.0, 0), Point3f(0, -1.0, 0)], # Y-axis + [Point3f(0, 0, 1.0), Point3f(0, 0, -1.0)], # Z-axis + ] + for points in axes + lines!(lscene, points; color = b.frame_color) + end + + # highlight circles for XY and XZ planes + φ = range(0, 2π, length = 100) + lines!(lscene, [Point3f(cos(φi), sin(φi), 0) for φi in φ]; color = b.frame_color, linewidth = 1.0) # XY + lines!(lscene, [Point3f(cos(φi), 0, sin(φi)) for φi in φ]; color = b.frame_color, linewidth = 1.0) # XZ + + # other curves of longitude (with polar angle φ and azimuthal angle θ) φ_curve = range(0, 2π, 600) + θ_vals = [1, 2, 3] * π / 4 for θi in θ_vals x_line = radius * sin.(φ_curve) .* cos(θi) y_line = radius * sin.(φ_curve) .* sin(θi) z_line = radius * cos.(φ_curve) lines!(lscene, x_line, y_line, z_line; color = b.frame_color, alpha = b.frame_alpha) end - φ_vals = range(0, π, 6) + + # other curves of latitude (with polar angle φ and azimuthal angle θ) + φ_vals = [1, 3] * π / 4 # missing `2` because XY plane has already be handled above θ_curve = range(0, 2π, 600) for ϕ in φ_vals x_ring = radius * sin(ϕ) .* cos.(θ_curve) @@ -421,53 +436,79 @@ function _draw_bloch_sphere!(b::Bloch, lscene) z_ring = fill(radius * cos(ϕ), length(θ_curve)) lines!(lscene, x_ring, y_ring, z_ring; color = b.frame_color, alpha = b.frame_alpha) end + return nothing end raw""" - _draw_reference_circles!(b::Bloch, lscene) + _add_labels!(b::Bloch, lscene) -Draw the three great circles `(XY, XZ planes)` on the Bloch sphere. +Add axis labels and state labels to the Bloch sphere. # Arguments -- `b::Bloch`: Bloch sphere object containing frame color -- `lscene`: Makie LScene object for drawing +- `lscene`: LScene object for text placement -Adds faint circular guidelines representing the three principal planes. +Positions standard labels `(x, y, |0⟩, |1⟩)` at appropriate locations. """ -function _draw_reference_circles!(b::Bloch, lscene) - wire_color = b.frame_color - φ = range(0, 2π, length = 100) - # XY, XZ circles - circles = [ - [Point3f(cos(φi), sin(φi), 0) for φi in φ], # XY - [Point3f(cos(φi), 0, sin(φi)) for φi in φ], # XZ - ] - for circle in circles - lines!(lscene, circle; color = wire_color, linewidth = 1.0) - end -end - -raw""" - _draw_axes!(b::Bloch, lscene) - -Draw the three principal axes `(x, y, z)` of the Bloch sphere. +function _add_labels!(b::Bloch, lscene) + length(b.xlabel) == 2 || throw(ArgumentError("The length of `Bloch.xlabel` must be 2.")) + length(b.ylabel) == 2 || throw(ArgumentError("The length of `Bloch.ylabel` must be 2.")) + length(b.zlabel) == 2 || throw(ArgumentError("The length of `Bloch.zlabel` must be 2.")) + length(b.xlpos) == 2 || throw(ArgumentError("The length of `Bloch.xlpos` must be 2.")) + length(b.ylpos) == 2 || throw(ArgumentError("The length of `Bloch.ylpos` must be 2.")) + length(b.zlpos) == 2 || throw(ArgumentError("The length of `Bloch.zlpos` must be 2.")) -# Arguments -- `b::Bloch`: Bloch sphere object containing axis color -- `lscene`: Makie LScene object for drawing + label_color = parse(RGBf, b.font_color) + label_size = b.font_size -Creates visible axis lines extending slightly beyond the unit sphere. -""" -function _draw_axes!(b::Bloch, lscene) - axis_color = b.frame_color - axes = [ - [Point3f(1.0, 0, 0), Point3f(-1.0, 0, 0)], # X-axis - [Point3f(0, 1.0, 0), Point3f(0, -1.0, 0)], # Y-axis - [Point3f(0, 0, 1.0), Point3f(0, 0, -1.0)], # Z-axis - ] - for points in axes - lines!(lscene, points; color = axis_color) - end + (b.xlabel[1] == "") || text!( + lscene, + b.xlabel[1], + position = Point3f(b.xlpos[1], 0, 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.xlabel[2] == "") || text!( + lscene, + b.xlabel[2], + position = Point3f(b.xlpos[2], 0, 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.ylabel[1] == "") || text!( + lscene, + b.ylabel[1], + position = Point3f(0, b.ylpos[1], 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.ylabel[2] == "") || text!( + lscene, + b.ylabel[2], + position = Point3f(0, b.ylpos[2], 0), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.zlabel[1] == "") || text!( + lscene, + b.zlabel[1], + position = Point3f(0, 0, b.zlpos[1]), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + (b.zlabel[2] == "") || text!( + lscene, + b.zlabel[2], + position = Point3f(0, 0, b.zlpos[2]), + color = label_color, + fontsize = label_size, + align = (:center, :center), + ) + return nothing end raw""" @@ -482,6 +523,7 @@ Plot all quantum state points on the Bloch sphere. Handles both scatter points and line traces based on style specifications. """ function _plot_points!(b::Bloch, lscene) + isempty(b.points) && return nothing for k in 1:length(b.points) pts = b.points[k] style = b.point_style[k] @@ -540,6 +582,7 @@ function _plot_points!(b::Bloch, lscene) lines!(lscene, xs, ys, zs; color = c, linewidth = 2.0, transparency = alpha < 1.0, alpha = alpha) end end + return nothing end raw""" @@ -554,6 +597,7 @@ Draw all connecting lines between points on the Bloch sphere. Processes line style specifications and color mappings. """ function _plot_lines!(b::Bloch, lscene) + isempty(b.lines) && return nothing color_map = Dict("k" => :black, "r" => :red, "g" => :green, "b" => :blue, "c" => :cyan, "m" => :magenta, "y" => :yellow) for (line, fmt) in b.lines @@ -571,6 +615,7 @@ function _plot_lines!(b::Bloch, lscene) end lines!(lscene, x, y, z; color = color, linewidth = 1.0, linestyle = linestyle) end + return nothing end raw""" @@ -585,6 +630,7 @@ Draw circular arcs connecting points on the Bloch sphere surface. Calculates great circle arcs between specified points. """ function _plot_arcs!(b::Bloch, lscene) + isempty(b.arcs) && return nothing for arc_pts in b.arcs length(arc_pts) >= 2 || continue v1 = normalize(arc_pts[1]) @@ -599,6 +645,7 @@ function _plot_arcs!(b::Bloch, lscene) arc_points = [Point3f((v1 * cos(t) + cross(n, v1) * sin(t))) for t in t_range] lines!(lscene, arc_points; color = RGBAf(0.8, 0.4, 0.1, 0.9), linewidth = 2.0, linestyle = :solid) end + return nothing end raw""" @@ -613,97 +660,31 @@ Draw vectors from origin representing quantum states. Scales vectors appropriately and adds `3D` arrow markers. """ function _plot_vectors!(b::Bloch, lscene) - isempty(b.vectors) && return - arrowsize_vec = Vec3f(b.vector_arrowsize...) - r = 1.0 + isempty(b.vectors) && return nothing + + arrow_head_length = b.vector_arrowsize[3] for (i, v) in enumerate(b.vectors) color = get(b.vector_color, i, RGBAf(0.2, 0.5, 0.8, 0.9)) - vec = Vec3f(v...) - length = norm(vec) - max_length = r * 0.90 - vec = length > max_length ? (vec/length) * max_length : vec + nv = norm(v) + (arrow_head_length < nv) || throw( + ArgumentError( + "The length of vector arrow head (Bloch.vector_arrowsize[3]=$arrow_head_length) should be shorter than vector norm: $nv", + ), + ) + + # multiply by the following factor makes the end point of arrow head represent the actual vector position. + vec = (1 - arrow_head_length / nv) * Vec3f(v...) arrows!( lscene, [Point3f(0, 0, 0)], [vec], color = color, linewidth = b.vector_width, - arrowsize = arrowsize_vec, + arrowsize = Vec3f(b.vector_arrowsize...), arrowcolor = color, rasterize = 3, ) end -end - -raw""" - _add_labels!(lscene) - -Add axis labels and state labels to the Bloch sphere. - -# Arguments -- `lscene`: LScene object for text placement - -Positions standard labels `(x, y, |0⟩, |1⟩)` at appropriate locations. -""" -function _add_labels!(b::Bloch, lscene) - length(b.xlabel) == 2 || throw(ArgumentError("The length of `Bloch.xlabel` must be 2.")) - length(b.ylabel) == 2 || throw(ArgumentError("The length of `Bloch.ylabel` must be 2.")) - length(b.zlabel) == 2 || throw(ArgumentError("The length of `Bloch.zlabel` must be 2.")) - length(b.xlpos) == 2 || throw(ArgumentError("The length of `Bloch.xlpos` must be 2.")) - length(b.ylpos) == 2 || throw(ArgumentError("The length of `Bloch.ylpos` must be 2.")) - length(b.zlpos) == 2 || throw(ArgumentError("The length of `Bloch.zlpos` must be 2.")) - - label_color = parse(RGBf, b.font_color) - label_size = b.font_size - - (b.xlabel[1] == "") || text!( - lscene, - b.xlabel[1], - position = Point3f(b.xlpos[1], 0, 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - (b.xlabel[2] == "") || text!( - lscene, - b.xlabel[2], - position = Point3f(b.xlpos[2], 0, 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - (b.ylabel[1] == "") || text!( - lscene, - b.ylabel[1], - position = Point3f(0, b.ylpos[1], 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - (b.ylabel[2] == "") || text!( - lscene, - b.ylabel[2], - position = Point3f(0, b.ylpos[2], 0), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - (b.zlabel[1] == "") || text!( - lscene, - b.zlabel[1], - position = Point3f(0, 0, b.zlpos[1]), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) - (b.zlabel[2] == "") || text!( - lscene, - b.zlabel[2], - position = Point3f(0, 0, b.zlpos[2]), - color = label_color, - fontsize = label_size, - align = (:center, :center), - ) return nothing end diff --git a/src/visualization.jl b/src/visualization.jl index 412103bce..ad513dd35 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -65,24 +65,24 @@ plot_fock_distribution(::Val{T}, ρ::QuantumObject{SType}; kwargs...) where {T,S throw(ArgumentError("The specified plotting library $T is not available. Try running `using $T` first.")) @doc raw""" - Bloch() + Bloch(kwargs...) -A structure representing a Bloch sphere visualization for quantum states. +A structure representing a Bloch sphere visualization for quantum states. Available keyword arguments are listed in the following fields. -# Fields +# Fields: ## Data storage - `points::Vector{Matrix{Float64}}`: Points to plot on the Bloch sphere (3D coordinates) - `vectors::Vector{Vector{Float64}}}`: Vectors to plot on the Bloch sphere -- `lines::Vector{Tuple{Vector{Vector{Float64}},String,Dict{Any,Any}}}`: Lines to draw on the sphere (points, style, properties) +- `lines::Vector{Tuple{Vector{Vector{Float64}},String}}`: Lines to draw on the sphere with each line given as `([start_pt, end_pt], line_format)` - `arcs::Vector{Vector{Vector{Float64}}}}`: Arcs to draw on the sphere ## Style properties - `font_color::String`: Color of axis labels and text -- `font_size::Int`: Font size for labels. Default: `15` -- `frame_alpha::Float64`: Transparency of the wireframe -- `frame_color::String`: Color of the wireframe +- `font_size::Int`: Font size for labels. Default: `20` +- `frame_alpha::Float64`: Transparency of the wire frame +- `frame_color::String`: Color of the wire frame ## Point properties @@ -98,17 +98,18 @@ A structure representing a Bloch sphere visualization for quantum states. - `sphere_color::String`: Color of Bloch sphere surface - `sphere_alpha::Float64`: Transparency of sphere surface. Default: `0.2` -# Vector properties +## Vector properties -- `vector_color`::Vector{String}: Colors for vectors -- `vector_width`::Float64: Width of vectors -- `vector_arrowsize`::Vector{Float64}: Arrow size parameters as [head length, head width, stem width] +- `vector_color::Vector{String}`: Colors for vectors +- `vector_width::Float64`: Width of vectors +- `vector_arrowsize::Vector{Float64}`: Scales the size of the arrow head. The first two elements scale the radius (in `x/y` direction) and the last one is the length of the cone. ## Layout properties - `view::Vector{Int}`: Azimuthal and elevation viewing angles in degrees. Default: `[30, 30]` ## Label properties + - `xlabel::Vector{AbstractString}`: Labels for x-axis. Default: `[L"x", ""]` - `xlpos::Vector{Float64}`: Positions of x-axis labels. Default: `[1.2, -1.2]` - `ylabel::Vector{AbstractString}`: Labels for y-axis. Default: `[L"y", ""]` @@ -122,7 +123,7 @@ A structure representing a Bloch sphere visualization for quantum states. lines::Vector{Tuple{Vector{Vector{Float64}},String}} = Vector{Tuple{Vector{Vector{Float64}},String}}() arcs::Vector{Vector{Vector{Float64}}} = Vector{Vector{Vector{Float64}}}() font_color::String = "black" - font_size::Int = 15 + font_size::Int = 20 frame_alpha::Float64 = 0.1 frame_color::String = "gray" point_default_color::Vector{String} = ["blue", "red", "green", "#CC6600"] @@ -202,29 +203,29 @@ Add a single point to the Bloch sphere visualization. # Arguments - `b::Bloch`: The Bloch sphere object to modify -- `pnt::Vector{Float64}`: A 3D point to add +- `pnt::Vector{<:Real}`: A 3D point to add - `meth::Symbol=:s`: Display method (`:s` for single point, `:m` for multiple, `:l` for line) - `color`: Color of the point (defaults to first default color if nothing) - `alpha=1.0`: Transparency (`1.0` means opaque and `0.0` means transparent) """ -function add_points!(b::Bloch, pnt::Vector{Float64}; meth::Symbol = :s, color = nothing, alpha = 1.0) +function add_points!(b::Bloch, pnt::Vector{<:Real}; meth::Symbol = :s, color = nothing, alpha = 1.0) return add_points!(b, reshape(pnt, 3, 1); meth, color, alpha) end -function add_points!(b::Bloch, pnts::Vector{Vector{Float64}}; meth::Symbol = :s, color = nothing, alpha = 1.0) +function add_points!(b::Bloch, pnts::Vector{<:Vector{<:Real}}; meth::Symbol = :s, color = nothing, alpha = 1.0) return add_points!(b, Matrix(hcat(pnts...)'); meth, color, alpha) end @doc raw""" - add_points!(b::Bloch, pnts::Matrix{Float64}; meth::Symbol = :s, color = nothing, alpha = 1.0) + add_points!(b::Bloch, pnts::Matrix{<:Real}; meth::Symbol = :s, color = nothing, alpha = 1.0) Add multiple points to the Bloch sphere visualization. # Arguments - `b::Bloch`: The Bloch sphere object to modify -- `pnts::Matrix{Float64}`: `3×N` matrix of points (each column is a point) +- `pnts::Matrix{<:Real}`: `3×N` matrix of points (each column is a point) - `meth::Symbol=:s`: Display method (`:s` for single point, `:m` for multiple, `:l` for line) -- `color`: Color of the points (defaults to first default color if nothing) +- `color`: Color of the points (defaults to first default color if `nothing`) - `alpha=1.0`: Transparency (`1.0` means opaque and `0.0` means transparent) ``` """ @@ -380,13 +381,14 @@ function add_arc!( end @doc raw""" - add_states!(b::Bloch, states::Vector{QuantumObject}) + add_states!(b::Bloch, states::Vector{QuantumObject}; kind::Symbol = :vector, kwargs...) Add one or more quantum states to the Bloch sphere visualization by converting them into Bloch vectors. # Arguments - `b::Bloch`: The Bloch sphere object to modify - `states::Vector{QuantumObject}`: One or more quantum states ([`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref)) +- `kind::Symbol`: Type of object to plot (can be either `:vector` or `:point`). Default: `:vector` # Example @@ -398,16 +400,19 @@ b = Bloch(); add_states!(b, [x, y, z]) ``` """ -function add_states!(b::Bloch, states::Vector{<:QuantumObject}) +function add_states!(b::Bloch, states::Vector{<:QuantumObject}; kind::Symbol = :vector, kwargs...) vecs = map(state -> _state_to_bloch(state), states) - append!(b.vectors, vecs) - return b.vectors -end - -function add_states!(b::Bloch, state::QuantumObject) - push!(b.vectors, _state_to_bloch(state)) - return b.vectors + if kind == :vector + add_vectors!(b, vecs) + elseif kind == :point + add_points!(b, hcat(vecs...), kwargs...) + else + throw(ArgumentError("Invalid kind = :$kind")) + end + return nothing end +add_states!(b::Bloch, state::QuantumObject; kind::Symbol = :vector, kwargs...) = + add_states!(b, [state], kind = kind, kwargs...) _state_to_bloch(state::QuantumObject{Ket}) = _ket_to_bloch(state) _state_to_bloch(state::QuantumObject{Bra}) = _ket_to_bloch(state') @@ -515,10 +520,7 @@ Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`. # Arguments - `b::Bloch`: The Bloch sphere object containing states, vectors, and settings to visualize. -- `location`: Specifies where to display or save the rendered figure. - - If `nothing` (default), the figure is displayed interactively. - - If a file path (String), the figure is saved to the specified location. - - Other values depend on backend support. +- `location::Union{GridPosition,Nothing}`: The location of the plot in the layout. If `nothing`, the plot is created in a new figure. Default is `nothing`. # Returns diff --git a/test/ext-test/cpu/makie/makie_ext.jl b/test/ext-test/cpu/makie/makie_ext.jl index 32ef267f9..edcb7b192 100644 --- a/test/ext-test/cpu/makie/makie_ext.jl +++ b/test/ext-test/cpu/makie/makie_ext.jl @@ -136,6 +136,7 @@ end @test isempty(b.vectors) @test isempty(b.lines) @test isempty(b.arcs) + b = Bloch() add_points!(b, hcat([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0])) add_vectors!(b, [[1, 1, 0], [0, 1, 1]]) @@ -149,6 +150,9 @@ end @test false @info "Render threw unexpected error" exception=e end + b.vector_arrowsize = [0.7, 0.8, 1.5] # 1.5 is the length of arrow head (too long) + @test_throws ArgumentError render(b) + b = Bloch() ψ₁ = normalize(basis(2, 0) + basis(2, 1)) ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) @@ -167,14 +171,20 @@ end Pauli_Ops = [sigmax(), sigmay(), sigmaz()] ψ = rand_ket(2) ρ = rand_dm(2) + states = [ψ, ρ] x = basis(2, 0) + basis(2, 1) # unnormalized Ket ρ1 = 0.3 * rand_dm(2) + 0.4 * rand_dm(2) # unnormalized density operator ρ2 = Qobj(rand(ComplexF64, 2, 2)) # unnormalized and non-Hermitian Operator - add_states!(b, [ψ, ρ]) - @test_logs (:warn,) (:warn,) (:warn,) (:warn,) add_states!(b, [x, ρ1, ρ2]) + add_states!(b, states, kind = :vector) + add_states!(b, states, kind = :point) + @test length(b.vectors) == 2 + @test length(b.points) == 1 @test all(expect(Pauli_Ops, ψ) .≈ (b.vectors[1])) @test all(expect(Pauli_Ops, ρ) .≈ (b.vectors[2])) + @test all([b.vectors[j][k] ≈ b.points[1][k, j] for j in (1, 2) for k in (1, 2, 3)]) + @test_logs (:warn,) (:warn,) (:warn,) (:warn,) add_states!(b, [x, ρ1, ρ2]) @test length(b.vectors) == 5 + @test_throws ArgumentError add_states!(b, states, kind = :wrong) th = range(0, 2π; length = 20) xp = cos.(th); From a4620a940ffcca1471f9922912fa5cf3df66a88a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 12 Jun 2025 06:04:10 +0800 Subject: [PATCH 279/329] Improve `Bloch` alignment with qutip (#489) --- CHANGELOG.md | 3 +- .../users_guide/plotting_the_bloch_sphere.md | 3 +- ext/QuantumToolboxMakieExt.jl | 26 +++++++--------- src/visualization.jl | 30 ++++++++++--------- test/ext-test/cpu/makie/makie_ext.jl | 2 ++ 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46997d6f5..020c8e43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Introduce `Lanczos` solver for `spectrum`. ([#476]) - Add Bloch-Redfield master equation solver. ([#473]) -- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485], [#487]) +- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485], [#487], [#489]) - Add `Base.copy` method for `AbstractQuantumObject`. ([#486]) ## [v0.31.1] @@ -240,3 +240,4 @@ Release date: 2024-11-13 [#485]: https://github.com/qutip/QuantumToolbox.jl/issues/485 [#486]: https://github.com/qutip/QuantumToolbox.jl/issues/486 [#487]: https://github.com/qutip/QuantumToolbox.jl/issues/487 +[#489]: https://github.com/qutip/QuantumToolbox.jl/issues/489 diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md index 273ad810c..59411831a 100644 --- a/docs/src/users_guide/plotting_the_bloch_sphere.md +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -183,8 +183,9 @@ At the end of the last section we saw that the colors and marker shapes of the d |:----------|:----------------|:--------------------| | `b.font_color` | Color of axis labels and text | `"black"` | | `b.font_size` | Font size for labels | `20` | -| `b.frame_alpha` | Transparency of the wire frame | `0.1` | +| `b.frame_alpha` | Transparency of the wire frame | `0.2` | | `b.frame_color` | Color of the wire frame | `"gray"` | +| `b.frame_width` | Width of wire frame | `1.0` | | `b.point_default_color` | Default color cycle for points | `["blue", "red", "green", "#CC6600"]` | | `b.point_color` | List of colors for Bloch point markers to cycle through | `Union{Nothing,String}[]` | | `b.point_marker` | List of point marker shapes to cycle through | `[:circle, :rect, :diamond, :utriangle]` | diff --git a/ext/QuantumToolboxMakieExt.jl b/ext/QuantumToolboxMakieExt.jl index 88832a6a7..3fcaaeee5 100644 --- a/ext/QuantumToolboxMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -414,8 +414,8 @@ function _draw_bloch_sphere!(b::Bloch, lscene) # highlight circles for XY and XZ planes φ = range(0, 2π, length = 100) - lines!(lscene, [Point3f(cos(φi), sin(φi), 0) for φi in φ]; color = b.frame_color, linewidth = 1.0) # XY - lines!(lscene, [Point3f(cos(φi), 0, sin(φi)) for φi in φ]; color = b.frame_color, linewidth = 1.0) # XZ + lines!(lscene, [Point3f(cos(φi), sin(φi), 0) for φi in φ]; color = b.frame_color, linewidth = b.frame_width) # XY + lines!(lscene, [Point3f(cos(φi), 0, sin(φi)) for φi in φ]; color = b.frame_color, linewidth = b.frame_width) # XZ # other curves of longitude (with polar angle φ and azimuthal angle θ) φ_curve = range(0, 2π, 600) @@ -424,7 +424,7 @@ function _draw_bloch_sphere!(b::Bloch, lscene) x_line = radius * sin.(φ_curve) .* cos(θi) y_line = radius * sin.(φ_curve) .* sin(θi) z_line = radius * cos.(φ_curve) - lines!(lscene, x_line, y_line, z_line; color = b.frame_color, alpha = b.frame_alpha) + lines!(lscene, x_line, y_line, z_line; color = b.frame_color, alpha = b.frame_alpha, linewidth = b.frame_width) end # other curves of latitude (with polar angle φ and azimuthal angle θ) @@ -434,7 +434,7 @@ function _draw_bloch_sphere!(b::Bloch, lscene) x_ring = radius * sin(ϕ) .* cos.(θ_curve) y_ring = radius * sin(ϕ) .* sin.(θ_curve) z_ring = fill(radius * cos(ϕ), length(θ_curve)) - lines!(lscene, x_ring, y_ring, z_ring; color = b.frame_color, alpha = b.frame_alpha) + lines!(lscene, x_ring, y_ring, z_ring; color = b.frame_color, alpha = b.frame_alpha, linewidth = b.frame_width) end return nothing end @@ -558,14 +558,11 @@ function _plot_points!(b::Bloch, lscene) end end if style in (:s, :m) - xs = raw_x[indperm] - ys = raw_y[indperm] - zs = raw_z[indperm] scatter!( lscene, - xs, - ys, - zs; + raw_x[indperm], + raw_y[indperm], + raw_z[indperm]; color = colors, markersize = b.point_size[mod1(k, length(b.point_size))], marker = marker, @@ -575,11 +572,8 @@ function _plot_points!(b::Bloch, lscene) ) elseif style == :l - xs = raw_x - ys = raw_y - zs = raw_z c = isa(colors, Vector) ? colors[1] : colors - lines!(lscene, xs, ys, zs; color = c, linewidth = 2.0, transparency = alpha < 1.0, alpha = alpha) + lines!(lscene, raw_x, raw_y, raw_z; color = c, transparency = alpha < 1.0, alpha = alpha) end end return nothing @@ -613,7 +607,7 @@ function _plot_lines!(b::Bloch, lscene) else :solid end - lines!(lscene, x, y, z; color = color, linewidth = 1.0, linestyle = linestyle) + lines!(lscene, x, y, z; color = color, linestyle = linestyle) end return nothing end @@ -643,7 +637,7 @@ function _plot_arcs!(b::Bloch, lscene) end t_range = range(0, θ, length = 100) arc_points = [Point3f((v1 * cos(t) + cross(n, v1) * sin(t))) for t in t_range] - lines!(lscene, arc_points; color = RGBAf(0.8, 0.4, 0.1, 0.9), linewidth = 2.0, linestyle = :solid) + lines!(lscene, arc_points; color = "blue", linestyle = :solid) end return nothing end diff --git a/src/visualization.jl b/src/visualization.jl index ad513dd35..273a9c21a 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -79,30 +79,31 @@ A structure representing a Bloch sphere visualization for quantum states. Availa ## Style properties -- `font_color::String`: Color of axis labels and text +- `font_color::String`: Color of axis labels and text. Default: `"black"` - `font_size::Int`: Font size for labels. Default: `20` -- `frame_alpha::Float64`: Transparency of the wire frame -- `frame_color::String`: Color of the wire frame +- `frame_alpha::Float64`: Transparency of the wire frame. Default: `0.2` +- `frame_color::String`: Color of the wire frame. Default: `"gray"` +- `frame_width::Float64` : Width of wire frame. Default: `1.0` ## Point properties -- `point_default_color::Vector{String}}`: Default color cycle for points -- `point_color::Vector{String}}`: List of colors for Bloch point markers to cycle through +- `point_default_color::Vector{String}}`: Default color cycle for points. Default: `["blue", "red", "green", "#CC6600"]` +- `point_color::Vector{String}}`: List of colors for Bloch point markers to cycle through. Default: `Union{Nothing,String}[]` - `point_marker::Vector{Symbol}}`: List of point marker shapes to cycle through. Default: `[:circle, :rect, :diamond, :utriangle]` -- `point_size::Vector{Int}}`: List of point marker sizes (not all markers look the same size when plotted) -- `point_style::Vector{Symbol}}`: List of marker styles -- `point_alpha::Vector{Float64}}`: List of marker transparencies +- `point_size::Vector{Int}}`: List of point marker sizes (not all markers look the same size when plotted). Default: `[5.5, 6.2, 6.5, 7.5]` +- `point_style::Vector{Symbol}}`: List of marker styles. Default: `Symbol[]` +- `point_alpha::Vector{Float64}}`: List of marker transparencies. Default: `Float64[]` ## Sphere properties -- `sphere_color::String`: Color of Bloch sphere surface +- `sphere_color::String`: Color of Bloch sphere surface. Default: `"#FFDDDD"` - `sphere_alpha::Float64`: Transparency of sphere surface. Default: `0.2` ## Vector properties -- `vector_color::Vector{String}`: Colors for vectors -- `vector_width::Float64`: Width of vectors -- `vector_arrowsize::Vector{Float64}`: Scales the size of the arrow head. The first two elements scale the radius (in `x/y` direction) and the last one is the length of the cone. +- `vector_color::Vector{String}`: Colors for vectors. Default: `["green", "#CC6600", "blue", "red"]` +- `vector_width::Float64`: Width of vectors. Default: `0.025` +- `vector_arrowsize::Vector{Float64}`: Scales the size of the arrow head. The first two elements scale the radius (in `x/y` direction) and the last one is the length of the cone. Default: `[0.07, 0.08, 0.08]` ## Layout properties @@ -124,8 +125,9 @@ A structure representing a Bloch sphere visualization for quantum states. Availa arcs::Vector{Vector{Vector{Float64}}} = Vector{Vector{Vector{Float64}}}() font_color::String = "black" font_size::Int = 20 - frame_alpha::Float64 = 0.1 + frame_alpha::Float64 = 0.2 frame_color::String = "gray" + frame_width::Float64 = 1.0 point_default_color::Vector{String} = ["blue", "red", "green", "#CC6600"] point_color::Vector{Union{Nothing,String}} = Union{Nothing,String}[] point_marker::Vector{Symbol} = [:circle, :rect, :diamond, :utriangle] @@ -405,7 +407,7 @@ function add_states!(b::Bloch, states::Vector{<:QuantumObject}; kind::Symbol = : if kind == :vector add_vectors!(b, vecs) elseif kind == :point - add_points!(b, hcat(vecs...), kwargs...) + add_points!(b, hcat(vecs...); kwargs...) else throw(ArgumentError("Invalid kind = :$kind")) end diff --git a/test/ext-test/cpu/makie/makie_ext.jl b/test/ext-test/cpu/makie/makie_ext.jl index edcb7b192..6f4b0c607 100644 --- a/test/ext-test/cpu/makie/makie_ext.jl +++ b/test/ext-test/cpu/makie/makie_ext.jl @@ -205,6 +205,7 @@ end @test false @info "Render threw unexpected error" exception=e end + b = Bloch() ψ₁ = normalize(basis(2, 0) + basis(2, 1)) ψ₂ = normalize(basis(2, 0) - im * basis(2, 1)) @@ -212,6 +213,7 @@ end add_line!(b, ψ₁, ψ₂; fmt = "r--") add_arc!(b, ψ₁, ψ₂) add_arc!(b, ψ₂, ψ₃, ψ₁) + add_states!(b, [ψ₂, ψ₃], kind = :point, meth = :l) try fig, lscene = render(b) @test !isnothing(fig) From b5e8af1cca91557df525d5a267c43f6580748b33 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:41:26 +0800 Subject: [PATCH 280/329] CompatHelper: bump compat for Makie in [weakdeps] to 0.23, (keep existing compat) (#490) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6f53877f9..58f61d160 100644 --- a/Project.toml +++ b/Project.toml @@ -55,7 +55,7 @@ KernelAbstractions = "0.9.2" LaTeXStrings = "1.2" LinearAlgebra = "1" LinearSolve = "2, 3" -Makie = "0.20, 0.21, 0.22" +Makie = "0.20, 0.21, 0.22, 0.23" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" From 68e2d94a50e36705c205986a3a497d37b1d0bb02 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 15 Jun 2025 12:59:04 +0800 Subject: [PATCH 281/329] fix incorrect badge in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbdc734aa..0b233e279 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ and [Y.-T. Huang](https://github.com/ytdHuang). [license-img]: https://img.shields.io/badge/license-New%20BSD-blue.svg [license-url]: https://opensource.org/licenses/BSD-3-Clause -[cite-img]: https://img.shields.io/badge/cite-arXiv%3A2504.21440_(2023)-blue +[cite-img]: https://img.shields.io/badge/cite-arXiv%3A2504.21440_(2025)-blue [cite-url]: https://doi.org/10.48550/arXiv.2504.21440 [download-img]: https://img.shields.io/badge/dynamic/json?url=http%3A%2F%2Fjuliapkgstats.com%2Fapi%2Fv1%2Ftotal_downloads%2FQuantumToolbox&query=total_requests&label=Downloads From 29a3785ebe236860db9bff3a3f6d2eed48a1fab6 Mon Sep 17 00:00:00 2001 From: Li-Xun Cai <157601901+TendonFFF@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:36:52 +0800 Subject: [PATCH 282/329] fix typos in doc strings (#495) Co-authored-by: cailixun --- src/qobj/synonyms.jl | 4 ++-- src/time_evolution/time_evolution.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qobj/synonyms.jl b/src/qobj/synonyms.jl index 370d9d902..278fafe94 100644 --- a/src/qobj/synonyms.jl +++ b/src/qobj/synonyms.jl @@ -34,7 +34,7 @@ const trans = transpose const dag = adjoint @doc raw""" - matrix_element(i::QuantumObject, A::QuantumObject j::QuantumObject) + matrix_element(i::QuantumObject, A::QuantumObject, j::QuantumObject) Compute the generalized dot product `dot(i, A*j)` between three [`QuantumObject`](@ref): ``\langle i | \hat{A} | j \rangle`` @@ -61,7 +61,7 @@ const operator_to_vector = mat2vec Matrix square root of [`Operator`](@ref) type of [`QuantumObject`](@ref) -Note that for other types of [`QuantumObject`](@ref) use `sprt(A)` instead. +Note that for other types of [`QuantumObject`](@ref) use `sqrt(A)` instead. """ sqrtm(A::QuantumObject{Operator}) = sqrt(A) diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 30730bc9d..0116ff398 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -14,7 +14,7 @@ A Julia constructor for handling the `ODEProblem` of the time evolution of quant # Fields (Attributes) - `prob::AbstractSciMLProblem`: The `ODEProblem` of the time evolution. -- `times::Abstractvector`: The time list of the evolution. +- `times::AbstractVector`: The time list of the evolution. - `dimensions::AbstractDimensions`: The dimensions of the Hilbert space. - `kwargs::KWT`: Generic keyword arguments. From ee92a07610f62622a847200bf17f2015a96394f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:52:16 +0800 Subject: [PATCH 283/329] CompatHelper: bump compat for Makie in [weakdeps] to 0.24, (keep existing compat) (#496) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 58f61d160..b84e32489 100644 --- a/Project.toml +++ b/Project.toml @@ -55,7 +55,7 @@ KernelAbstractions = "0.9.2" LaTeXStrings = "1.2" LinearAlgebra = "1" LinearSolve = "2, 3" -Makie = "0.20, 0.21, 0.22, 0.23" +Makie = "0.20, 0.21, 0.22, 0.23, 0.24" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" From de3e2e88e3af280710d529e23c256a87a16eb0f3 Mon Sep 17 00:00:00 2001 From: Li-Xun Cai <157601901+TendonFFF@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:24:46 +0800 Subject: [PATCH 284/329] [Doc] Add documentation for Bloch-Redfield master equation (#494) Co-authored-by: cailixun --- .typos.toml | 1 + CHANGELOG.md | 2 + docs/make.jl | 1 + docs/src/resources/bibliography.bib | 20 ++ .../users_guide/time_evolution/brmesolve.md | 229 ++++++++++++++++++ docs/src/users_guide/time_evolution/intro.md | 5 + 6 files changed, 258 insertions(+) create mode 100644 docs/src/users_guide/time_evolution/brmesolve.md diff --git a/.typos.toml b/.typos.toml index e3264a3d3..cb4a74b2f 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,3 +1,4 @@ [default.extend-words] ket = "ket" sme = "sme" +nd = "nd" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 020c8e43b..b7d49a21f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add Bloch-Redfield master equation solver. ([#473]) - Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485], [#487], [#489]) - Add `Base.copy` method for `AbstractQuantumObject`. ([#486]) +- Add documentation for Bloch-Redfield master equation. ([#494]) ## [v0.31.1] Release date: 2025-05-16 @@ -241,3 +242,4 @@ Release date: 2024-11-13 [#486]: https://github.com/qutip/QuantumToolbox.jl/issues/486 [#487]: https://github.com/qutip/QuantumToolbox.jl/issues/487 [#489]: https://github.com/qutip/QuantumToolbox.jl/issues/489 +[#494]: https://github.com/qutip/QuantumToolbox.jl/issues/494 diff --git a/docs/make.jl b/docs/make.jl index 50f4696f8..7fb05c01b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -59,6 +59,7 @@ const PAGES = [ "Monte Carlo Solver" => "users_guide/time_evolution/mcsolve.md", "Stochastic Solver" => "users_guide/time_evolution/stochastic.md", "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", + "Bloch-Redfield master equation" => "users_guide/time_evolution/brmesolve.md", ], "Intensive parallelization on a Cluster" => "users_guide/cluster.md", "Hierarchical Equations of Motion" => "users_guide/HEOM.md", diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib index 9cb6b77e1..9af3e514b 100644 --- a/docs/src/resources/bibliography.bib +++ b/docs/src/resources/bibliography.bib @@ -119,3 +119,23 @@ @article{Hill-Wootters1997 doi = {10.1103/PhysRevLett.78.5022}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.78.5022} } + +@book{Cohen_Tannoudji_atomphoton, + address = {New York}, + author = {{Cohen-Tannoudji}, C. and {Grynberg}, G. and {Dupont-Roc}, J.}, + publisher = {Wiley}, + timestamp = {2010-12-01T16:20:40.000+0100}, + title = {Atom-Photon Interactions: Basic Processes and Applications }, + year = 1992 +} + +@book{breuer2002, + title = {The Theory of Open Quantum Systems}, + author = {Breuer, Heinz-Peter and Petruccione, Francesco}, + year = {2002}, + publisher = {Oxford university press}, + address = {Oxford New York}, + isbn = {978-0-19-852063-4}, + langid = {english}, + lccn = {530.12} +} diff --git a/docs/src/users_guide/time_evolution/brmesolve.md b/docs/src/users_guide/time_evolution/brmesolve.md new file mode 100644 index 000000000..c7a46e612 --- /dev/null +++ b/docs/src/users_guide/time_evolution/brmesolve.md @@ -0,0 +1,229 @@ +# [Bloch-Redfield master equation](@id doc-TE:Bloch-Redfield-master-equation) + +The [Lindblad master equation](@ref doc-TE:Lindblad-Master-Equation-Solver) introduced earlier is constructed so that it describes a physical evolution of the density matrix (i.e., trace and positivity preserving), but it does not provide a connection to any underlying microscopic physical model. The Lindblad operators (collapse operators) describe phenomenological processes, such as for example dephasing and spin flips, and the rates of these processes are arbitrary parameters in the model. In many situations the collapse operators and their corresponding rates have clear physical interpretation, such as dephasing and relaxation rates, and in those cases the Lindblad master equation is usually the method of choice. + +However, in some cases, for example systems with varying energy biases and eigenstates and that couple to an environment in some well-defined manner (through a physically motivated system-environment interaction operator), it is often desirable to derive the master equation from more fundamental physical principles, and relate it to for example the noise-power spectrum of the environment. + +The Bloch-Redfield formalism is one such approach to derive a master equation from a microscopic system. It starts from a combined system-environment perspective, and derives a perturbative master equation for the system alone, under the assumption of weak system-environment coupling. One advantage of this approach is that the dissipation processes and rates are obtained directly from the properties of the environment. On the downside, it does not intrinsically guarantee that the resulting master equation unconditionally preserves the physical properties of the density matrix (because it is a perturbative method). The Bloch-Redfield master equation must therefore be used with care, and the assumptions made in the derivation must be honored. (The Lindblad master equation is in a sense more robust -- it always results in a physical density matrix -- although some collapse operators might not be physically justified). For a full derivation of the Bloch Redfield master equation, see e.g. [Cohen_Tannoudji_atomphoton](@citet) or [breuer2002](@citet). Here we present only a brief version of the derivation, with the intention of introducing the notation and how it relates to the implementation in `QuantumToolbox.jl`. + + +## [Brief Derivation and Definitions](@id doc-TE:Brief-Derivation-and-Definitions) + +The starting point of the Bloch-Redfield formalism is the total Hamiltonian for the system and the environment (bath): ``\hat{H} = \hat{H}_{\rm S} + \hat{H}_{\rm B} + \hat{H}_{\rm I}``, where ``\hat{H}`` is the total system+bath Hamiltonian, ``\hat{H}_{\rm S}`` and ``\hat{H}_{\rm B}`` are the system and bath Hamiltonians, respectively, and ``\hat{H}_{\rm I}`` is the interaction Hamiltonian. + +The most general form of a master equation for the system dynamics is obtained by tracing out the bath from the von-Neumann equation of motion for the combined system (``\frac{d}{dt}\hat{\rho} = -i\hbar^{-1}[\hat{H}, \hat{\rho}]``). In the interaction picture the result is + +```math + \frac{d}{dt}\hat{\rho}_{\textrm{S}}(t) = - \hbar^{-2}\int_0^t d\tau\; \textrm{Tr}_{\textrm{B}} [\hat{H}_{\textrm{I}}(t), [\hat{H}_{\textrm{I}}(\tau), \hat{\rho}_{\textrm{S}}(\tau)\otimes\hat{\rho}_{\textrm{B}}]], +``` + +where the additional assumption that the total system-bath density matrix can be factorized as ``\hat{\rho}(t) \approx \hat{\rho}_{\textrm{S}}(t) \otimes \hat{\rho}_{\textrm{B}}``. This assumption is known as the Born approximation, and it implies that there never is any entanglement between the system and the bath, neither in the initial state nor at any time during the evolution. *It is justified for weak system-bath interaction.* + +The master equation above is non-Markovian, i.e., the change in the density matrix at a time ``t`` depends on states at all times ``\tau < t``, making it intractable to solve both theoretically and numerically. To make progress towards a manageable master equation, we now introduce the Markovian approximation, in which ``\hat{\rho}_{\textrm{S}}(\tau)`` is replaced by ``\hat{\rho}_{\textrm{S}}(t)``. The result is the Redfield equation + +```math + \frac{d}{dt}\hat{\rho}_{\textrm{S}}(t) = - \hbar^{-2}\int_0^t d\tau\; \textrm{Tr}_{\textrm{B}} [\hat{H}_{\textrm{I}}(t), [\hat{H}_{\textrm{I}}(\tau), \hat{\rho}_{\textrm{S}}(t)\otimes\hat{\rho}_{\textrm{B}}]], +``` + +which is local in time with respect the density matrix, but still not Markovian since it contains an implicit dependence on the initial state. By extending the integration to infinity and substituting ``\tau \rightarrow t-\tau``, a fully Markovian master equation is obtained: + +```math + \frac{d}{dt}\hat{\rho}_{\textrm{S}}(t) = - \hbar^{-2}\int_0^\infty d\tau\; \textrm{Tr}_{\textrm{B}} [\hat{H}_{\textrm{I}}(t), [\hat{H}_{\textrm{I}}(t-\tau), \hat{\rho}_{\textrm{S}}(t)\otimes\hat{\rho}_{\textrm{B}}]]. +``` + +The two Markovian approximations introduced above are valid if the time-scale with which the system dynamics changes is large compared to the time-scale with which correlations in the bath decays (corresponding to a "short-memory" bath, which results in Markovian system dynamics). + +The Markovian master equation above is still on a too general form to be suitable for numerical implementation. We therefore assume that the system-bath interaction takes the form ``\hat{H}_{\textrm{I}} = \sum_\alpha \hat{A}_\alpha \otimes \hat{B}_\alpha`` and where ``\hat{A}_\alpha`` are system operators and ``\hat{B}_\alpha`` are bath operators. This allows us to write master equation in terms of system operators and bath correlation functions: + +```math +\begin{split}\frac{d}{dt}\hat{\rho}_{\textrm{S}}(t) = +-\hbar^{-2} +\sum_{\alpha\beta} +\int_0^\infty d\tau\; +\left\{ +g_{\alpha\beta}(\tau) \left[\hat{A}_\alpha(t)\hat{A}_\beta(t-\tau)\hat{\rho}_{\textrm{S}}(t) - \hat{A}_\beta(t-\tau)\hat{\rho}_{\textrm{S}}(t)\hat{A}_\alpha(t)\right] +\right. \nonumber\\ +\left. ++ g_{\alpha\beta}(-\tau) \left[\hat{\rho}_{\textrm{S}}(t)\hat{A}_\alpha(t-\tau)\hat{A}_\beta(t) - \hat{A}_\beta(t)\hat{\rho}_{\textrm{S}}(t)\hat{A}_\beta(t-\alpha)\right] +\right\},\end{split} +``` + +where ``g_{\alpha\beta}(\tau) = \textrm{Tr}_{\textrm{B}}\left[\hat{B}_\alpha(t)\hat{B}_\beta(t-\tau)\hat{\rho}_{\textrm{B}}\right] = \langle\hat{B}_\alpha(\tau)\hat{B}_\beta(0)\rangle``, since the bath state ``\hat{\rho}_{\textrm{B}}`` is a steady state. + +In the eigenbasis of the system Hamiltonian, where ``A_{mn}(t) = A_{mn} e^{i\omega_{mn}t}``, ``\omega_{mn} = \omega_m - \omega_n`` and ``\omega_m`` are the eigenfrequencies corresponding to the eigenstate ``|m\rangle``, we obtain in matrix form in the Schrödinger picture + +```math +\begin{aligned} + \frac{d}{dt} \rho_{ab}(t) = & -i\omega_{ab}\rho_{ab}(t)\\ + &-\hbar^{-2} \sum_{\alpha,\beta} \sum_{c,d}^{\textrm{sec}} \int_0^\infty d\tau\; + \left\{ + g_{\alpha\beta}(\tau) + \left[\delta_{bd}\sum_nA^\alpha_{an}A^\beta_{nc}e^{i\omega_{cn}\tau} + - + A^\beta_{ac} A^\alpha_{db} e^{i\omega_{ca}\tau} + \right] + \right. \\ + &+ + \left. + g_{\alpha\beta}(-\tau) + \left[\delta_{ac}\sum_n A^\alpha_{dn}A^\beta_{nb} e^{i\omega_{nd}\tau} + - + A^\beta_{ac}A^\alpha_{db}e^{i\omega_{bd}\tau} + \right] + \right\} \rho_{cd}(t), +\end{aligned} +``` + +where the "sec" above the summation symbol indicate summation of the secular terms which satisfy ``|\omega_{ab}-\omega_{cd}| \ll \tau_ {\rm decay}``. This is an almost-useful form of the master equation. The final step before arriving at the form of the Bloch-Redfield master equation that is implemented in `QuantumToolbox.jl`, involves rewriting the bath correlation function ``g(\tau)`` in terms of the noise-power spectrum of the environment ``S(\omega) = \int_{-\infty}^\infty d\tau e^{i\omega\tau} g(\tau)``: + +```math + \int_0^\infty d\tau\; g_{\alpha\beta}(\tau) e^{i\omega\tau} = \frac{1}{2}S_{\alpha\beta}(\omega) + i\lambda_{\alpha\beta}(\omega), +``` + +where ``\lambda_{ab}(\omega)`` is an energy shift that is neglected here. The final form of the Bloch-Redfield master equation is + +```math + \frac{d}{dt}\rho_{ab}(t) + = + -i\omega_{ab}\rho_{ab}(t) + + + \sum_{c,d}^{\textrm{sec}}R_{abcd}\rho_{cd}(t), +``` + +where + +```math +\begin{aligned} + R_{abcd} = -\frac{\hbar^{-2}}{2} \sum_{\alpha,\beta} + \left\{ + \delta_{bd}\sum_nA^\alpha_{an}A^\beta_{nc}S_{\alpha\beta}(\omega_{cn}) + - + A^\beta_{ac} A^\alpha_{db} S_{\alpha\beta}(\omega_{ca}) + \right. \nonumber\\ + + + \left. + \delta_{ac}\sum_n A^\alpha_{dn}A^\beta_{nb} S_{\alpha\beta}(\omega_{dn}) + - + A^\beta_{ac}A^\alpha_{db} S_{\alpha\beta}(\omega_{db}) + \right\}, +\end{aligned} +``` + +is the Bloch-Redfield tensor. + +The Bloch-Redfield master equation in this form is suitable for numerical implementation. The input parameters are the system Hamiltonian ``\hat{H}_{\textrm{S}}``, the system operators through which the environment couples to the system ``\hat{A}_\alpha``, and the noise-power spectrum ``S_{\alpha\beta}(\omega)`` associated with each system-environment interaction term. + +To simplify the numerical implementation we often assume that ``\hat{A}_\alpha`` are Hermitian and that cross-correlations between different environment operators vanish, resulting in + +```math +\begin{aligned} + R_{abcd} = -\frac{\hbar^{-2}}{2} \sum_{\alpha} + \left\{ + \delta_{bd}\sum_nA^\alpha_{an}A^\alpha_{nc}S_{\alpha}(\omega_{cn}) + - + A^\alpha_{ac} A^\alpha_{db} S_{\alpha}(\omega_{ca}) + \right. \nonumber\\ + + + \left. + \delta_{ac}\sum_n A^\alpha_{dn}A^\alpha_{nb} S_{\alpha}(\omega_{dn}) + - + A^\alpha_{ac}A^\alpha_{db} S_{\alpha}(\omega_{db}) + \right\}. +\end{aligned} +``` + +## [Bloch-Redfield master equation in `QuantumToolbox.jl`](@id Bloch-Redfield-master-equation-in-QuantumToolbox-jl) + +### Preparing the Bloch-Redfield tensor + +In `QuantumToolbox.jl`, the Bloch-redfield master equation can be calculated using the function [`bloch_redfield_tensor`](@ref). It takes two mandatory arguments: The system Hamiltonian ``\hat{H}`` and a nested list `a_ops` consist of tuples as `(A, spec)` with `A::QuantumObject` being the [`Operator`](@ref) ``\hat{A}_\alpha`` and `spec::Function` being the spectral density function ``S_\alpha(\omega)``. + +It is possible to also get the ``\alpha``-th term for the bath directly using [`brterm`](@ref). This function takes only one Hermitian coupling operator ``\hat{A}_\alpha`` and spectral response function ``S_\alpha(\omega)``. + +To illustrate how to calculate the Bloch-Redfield tensor, let's consider a two-level atom + +```math + \hat{H}_{\textrm{S}} = -\frac{1}{2}\Delta\hat{\sigma}_x - \frac{1}{2}\varepsilon_0\hat{\sigma}_z +``` + +```@setup brmesolve +using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) +``` + +```@example brmesolve +Δ = 0.2 * 2π +ε0 = 1.0 * 2π +γ1 = 0.5 + +H = -Δ/2.0 * sigmax() - ε0/2 * sigmaz() + +ohmic_spectrum(ω) = (ω == 0.0) ? γ1 : γ1 / 2 * (ω / (2 * π)) * (ω > 0.0) + +R, U = bloch_redfield_tensor(H, [(sigmax(), ohmic_spectrum)]) + +R +``` + +Note that it is also possible to add Lindblad dissipation superoperators in the Bloch-Refield tensor by passing the operators via the third argument `c_ops` like you would in the [`mesolve`](@ref) or [`mcsolve`](@ref) functions. For convenience, when the keyword argument `fock_basis = false`, the function [`bloch_redfield_tensor`](@ref) also returns the basis transformation operator `U`, the eigen vector matrix, since they are calculated in the process of generating the Bloch-Redfield tensor `R`, and the `U` are usually needed again later when transforming operators between the laboratory basis and the eigen basis. The tensor can be obtained in the laboratory basis by setting `fock_basis = true`, in that case, the transformation operator `U` is not returned. + +### Time evolution + +The evolution of a wave function or density matrix, according to the Bloch-Redfield master equation, can be calculated using the function [`mesolve`](@ref) with Bloch-Refield tensor `R` in the laboratory basis instead of a [`liouvillian`](@ref). For example, to evaluate the expectation values of the ``\hat{\sigma}_x``, ``\hat{\sigma}_y``, and ``\hat{\sigma}_z`` operators for the example above, we can use the following code: + +```@example brmesolve +Δ = 0.2 * 2 * π +ϵ0 = 1.0 * 2 * π +γ1 = 0.5 + +H = - Δ/2.0 * sigmax() - ϵ0/2.0 * sigmaz() + +ohmic_spectrum(ω) = (ω == 0.0) ? γ1 : γ1 / 2 * (ω / (2 * π)) * (ω > 0.0) + +a_ops = ((sigmax(), ohmic_spectrum),) +e_ops = [sigmax(), sigmay(), sigmaz()] + +# same initial random ket state in QuTiP doc +ψ0 = Qobj([ + 0.05014193+0.66000276im, + 0.67231376+0.33147603im +]) + +tlist = LinRange(0, 15.0, 1000) +sol = brmesolve(H, ψ0, tlist, a_ops, e_ops=e_ops) +expt_list = real(sol.expect) + +# plot the evolution of state on Bloch sphere +sphere = Bloch() +add_points!(sphere, [expt_list[1,:], expt_list[2,:], expt_list[3,:]]) +sphere.vector_color = ["red"] + +add_vectors!(sphere, [Δ, 0, ϵ0] / √(Δ^2 + ϵ0^2)) + +fig, _ = render(sphere) +fig +``` + +The two steps of calculating the Bloch-Redfield tensor `R` and evolving according to the corresponding master equation can be combined into one by using the function [`brmesolve`](@ref), in addition to the same arguments as [`mesolve`](@ref) and [`mcsolve`](@ref), the nested list of operator-spectrum tuple should be given under `a_ops`. + +```@example brmesolve +sol = brmesolve(H, ψ0, tlist, ((sigmax(),ohmic_spectrum),); e_ops=e_ops) +``` + +The resulting `sol` is of the `struct` [`TimeEvolutionSol`](@ref) as [`mesolve`](@ref). + +!!! note "Secular cutoff" + While the code example simulates the Bloch-Redfield equation in the secular approximation, `QuantumToolbox`'s implementation allows the user to simulate the non-secular version of the Bloch-Redfield equation by setting `sec_cutoff=-1`, as well as do a partial secular approximation by setting it to a `Float64` , this float number will become the cutoff for the summation (``\sum_{c,d}^{\textrm{sec}}``) in the previous equations, meaning that terms with ``\omega_{ab} - \omega_{cd}`` greater than the `sec_cutoff` will be neglected. Its default value is `0.1` which corresponds to the secular approximation. + +For example, the command + +```julia +sol = brmesolve(H, ψ0, tlist, ((sigmax(),ohmic_spectrum),); e_ops=e_ops, sec_cutoff=-1) +``` + +will simulate the same example as above without the secular approximation. + +!!! warning "Secular cutoff" + Using the non-secular version may lead to negativity issues. diff --git a/docs/src/users_guide/time_evolution/intro.md b/docs/src/users_guide/time_evolution/intro.md index e1755183d..ae716173e 100644 --- a/docs/src/users_guide/time_evolution/intro.md +++ b/docs/src/users_guide/time_evolution/intro.md @@ -28,6 +28,10 @@ - [Generate QobjEvo](@ref doc-TE:Generate-QobjEvo) - [QobjEvo fields (attributes)](@ref doc-TE:QobjEvo-fields-(attributes)) - [Using parameters](@ref doc-TE:Using-parameters) +- [Bloch-Redfield master equation](@ref doc-TE:Bloch-Redfield-master-equation) + - [Brief Derivation and Definitions](@ref doc-TE:Brief-Derivation-and-Definitions) + - [Bloch-Redfield master equation in `QuantumToolbox.jl`](@ref Bloch-Redfield-master-equation-in-QuantumToolbox-jl) + # [Introduction](@id doc-TE:Introduction) @@ -44,6 +48,7 @@ The following table lists the solvers provided by `QuantumToolbox` for dynamic q | Monte Carlo evolution | [`mcsolve`](@ref) | [`mcsolveProblem`](@ref) [`mcsolveEnsembleProblem`](@ref) | [`TimeEvolutionMCSol`](@ref) | | Stochastic Schrödinger equation | [`ssesolve`](@ref) | [`ssesolveProblem`](@ref) [`ssesolveEnsembleProblem`](@ref) | [`TimeEvolutionStochasticSol`](@ref) | | Stochastic master equation | [`smesolve`](@ref) | [`smesolveProblem`](@ref) [`smesolveEnsembleProblem`](@ref) | [`TimeEvolutionStochasticSol`](@ref) | +| Bloch-Redfield master equation | [`brmesolve`](@ref) | - | [`TimeEvolutionSol`](@ref) | !!! note "Solving dynamics with pre-defined problems" `QuantumToolbox` provides two different methods to solve the dynamics. One can use the function calls listed above by either taking all the operators (like Hamiltonian and collapse operators, etc.) as inputs directly, or generating the `prob`lems by yourself and take it as an input of the function call, e.g., `sesolve(prob)`. From 27722071e592a782d760c49ab796077903759f4e Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:28:56 +0200 Subject: [PATCH 285/329] [no ci] Bump version to 0.32.0 (#497) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7d49a21f..54d1c580f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.32.0] +Release date: 2025-06-23 + - Introduce `Lanczos` solver for `spectrum`. ([#476]) - Add Bloch-Redfield master equation solver. ([#473]) - Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485], [#487], [#489]) @@ -168,6 +171,7 @@ Release date: 2024-11-13 [v0.30.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.1 [v0.31.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.0 [v0.31.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.1 +[v0.32.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index b84e32489..a3085abb7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.31.1" +version = "0.32.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 3028fe0a4af5b9ec3952fbefa737e6c0e0c369b3 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 24 Jun 2025 10:13:11 +0800 Subject: [PATCH 286/329] Bump version to v0.32.1 (#498) --- CHANGELOG.md | 6 ++++++ Project.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54d1c580f..64145e2db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.32.1] +Release date: 2025-06-24 + +This is a release just for updating documentation. + ## [v0.32.0] Release date: 2025-06-23 @@ -172,6 +177,7 @@ Release date: 2024-11-13 [v0.31.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.0 [v0.31.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.1 [v0.32.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.0 +[v0.32.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.1 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index a3085abb7..5d2435573 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.32.0" +version = "0.32.1" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 0066cdbb468119a25cd79dc051013234ce2bd9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matteo=20Secl=C3=AC?= Date: Sat, 5 Jul 2025 11:44:09 +0200 Subject: [PATCH 287/329] Check for orthogonality breakdown in `Lanczos` (#502) --- CHANGELOG.md | 3 +++ src/spectrum.jl | 30 ++++++++++++++------- test/core-test/correlations_and_spectrum.jl | 4 +++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64145e2db..f7dee851a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Check for orthogonality breakdown in `Lanczos` solver for `spectrum`. ([#501]) + ## [v0.32.1] Release date: 2025-06-24 @@ -253,3 +255,4 @@ Release date: 2024-11-13 [#487]: https://github.com/qutip/QuantumToolbox.jl/issues/487 [#489]: https://github.com/qutip/QuantumToolbox.jl/issues/489 [#494]: https://github.com/qutip/QuantumToolbox.jl/issues/494 +[#501]: https://github.com/qutip/QuantumToolbox.jl/issues/501 diff --git a/src/spectrum.jl b/src/spectrum.jl index 367470605..b97488575 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -186,10 +186,14 @@ function _spectrum( Ivec = SparseVector(D^2, [1 + n * (D + 1) for n in 0:(D-1)], ones(cT, D)) # same as vec(system_identity_matrix) wₖ = spre(A).data' * vT(Ivec) - # Store the norm of the Green's function before renormalizing |v₁> and and 0 + @warn "spectrum(): solver::Lanczos experienced orthogonality breakdown after $(k) iterations" + @warn "spectrum(): βₖδₖ = $(βₖδₖ)" + end + break + end + δₖ = sqrt(abs(βₖδₖ)) + βₖ = βₖδₖ / δₖ # Normalize (k+1)-th left/right vectors vₖ ./= δₖ diff --git a/test/core-test/correlations_and_spectrum.jl b/test/core-test/correlations_and_spectrum.jl index bd9a2a918..71c875034 100644 --- a/test/core-test/correlations_and_spectrum.jl +++ b/test/core-test/correlations_and_spectrum.jl @@ -50,6 +50,10 @@ @test last(outlines) == "spectrum(): Consider increasing maxiter and/or tol" end + @testset "Orthogonal input vectors Lanczos" begin + @test_throws AssertionError spectrum(H, ω_l2, [c_ops[1]], a', a; solver = Lanczos()) + end + # tlist and τlist checks t_fft_wrong = [0, 1, 10] t_wrong1 = [1, 2, 3] From a2ade17784e425bc72ed81eb3d6655a993baefc5 Mon Sep 17 00:00:00 2001 From: Li-Xun Cai <157601901+TendonFFF@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:11:25 +0800 Subject: [PATCH 288/329] Excitation number restricted (`ENR`) state space implementation (#500) --- CHANGELOG.md | 2 + docs/src/resources/api.md | 6 + .../QuantumObject/QuantumObject.md | 8 +- src/QuantumToolbox.jl | 1 + src/negativity.jl | 18 +- src/qobj/arithmetic_and_attributes.jl | 6 + src/qobj/dimensions.jl | 10 +- src/qobj/energy_restricted.jl | 264 ++++++++++++++++++ src/qobj/quantum_object_base.jl | 10 +- src/qobj/space.jl | 16 -- test/core-test/enr_state_operator.jl | 119 ++++++++ 11 files changed, 427 insertions(+), 33 deletions(-) create mode 100644 src/qobj/energy_restricted.jl create mode 100644 test/core-test/enr_state_operator.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index f7dee851a..9e1d0d804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Implement `EnrSpace` and corresponding functionality. ([#500]) - Check for orthogonality breakdown in `Lanczos` solver for `spectrum`. ([#501]) ## [v0.32.1] @@ -255,4 +256,5 @@ Release date: 2024-11-13 [#487]: https://github.com/qutip/QuantumToolbox.jl/issues/487 [#489]: https://github.com/qutip/QuantumToolbox.jl/issues/489 [#494]: https://github.com/qutip/QuantumToolbox.jl/issues/494 +[#500]: https://github.com/qutip/QuantumToolbox.jl/issues/500 [#501]: https://github.com/qutip/QuantumToolbox.jl/issues/501 diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 6b3b4584d..8c9b8c209 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -18,6 +18,7 @@ end ```@docs Space +EnrSpace Dimensions GeneralDimensions AbstractQuantumObject @@ -122,6 +123,8 @@ coherent_dm thermal_dm maximally_mixed_dm rand_dm +enr_fock +enr_thermal_dm spin_state spin_coherent bell_state @@ -152,6 +155,8 @@ QuantumToolbox.momentum phase fdestroy fcreate +enr_destroy +enr_identity tunneling qft eye @@ -312,6 +317,7 @@ PhysicalConstants convert_unit row_major_reshape meshgrid +enr_state_dictionaries ``` ## [Visualization](@id doc-API:Visualization) diff --git a/docs/src/users_guide/QuantumObject/QuantumObject.md b/docs/src/users_guide/QuantumObject/QuantumObject.md index 8ca8104e0..4abd95e88 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject.md @@ -66,14 +66,16 @@ Manually specifying the data for each quantum object is inefficient. Even more s ### States - [`zero_ket`](@ref): zero ket vector -- [`fock`](@ref) or [`basis`](@ref): fock state ket vector -- [`fock_dm`](@ref): density matrix of a fock state +- [`fock`](@ref) or [`basis`](@ref): Fock state ket vector +- [`fock_dm`](@ref): density matrix of a Fock state - [`coherent`](@ref): coherent state ket vector - [`rand_ket`](@ref): random ket vector - [`coherent_dm`](@ref): density matrix of a coherent state - [`thermal_dm`](@ref): density matrix of a thermal state - [`maximally_mixed_dm`](@ref): density matrix of a maximally mixed state - [`rand_dm`](@ref): random density matrix +- [`enr_fock`](@ref): Fock state in the excitation number restricted (ENR) space +- [`enr_thermal_dm`](@ref): thermal state in the excitation number restricted (ENR) space - [`spin_state`](@ref): spin state - [`spin_coherent`](@ref): coherent spin state - [`bell_state`](@ref): Bell state @@ -108,6 +110,8 @@ Manually specifying the data for each quantum object is inefficient. Even more s - [`spin_J_set`](@ref): a set of Spin-`j` operators ``(S_x, S_y, S_z)`` - [`fdestroy`](@ref): fermion destruction operator - [`fcreate`](@ref): fermion creation operator +- [`enr_destroy`](@ref): destruction operator in the excitation number restricted (ENR) space +- [`enr_identity`](@ref): identity operator in the excitation number restricted (ENR) space - [`commutator`](@ref): commutator or anti-commutator - [`tunneling`](@ref): tunneling operator - [`qft`](@ref): discrete quantum Fourier transform matrix diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index 915c4abe8..a4d0b7841 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -84,6 +84,7 @@ include("linear_maps.jl") # Quantum Object include("qobj/space.jl") +include("qobj/energy_restricted.jl") include("qobj/dimensions.jl") include("qobj/quantum_object_base.jl") include("qobj/quantum_object.jl") diff --git a/src/negativity.jl b/src/negativity.jl index e19afa341..77e5d37d9 100644 --- a/src/negativity.jl +++ b/src/negativity.jl @@ -71,18 +71,20 @@ Return the partial transpose of a density matrix ``\rho``, where `mask` is an ar - `ρ_pt::QuantumObject`: The density matrix with the selected subsystems transposed. """ function partial_transpose(ρ::QuantumObject{Operator}, mask::Vector{Bool}) - if length(mask) != length(ρ.dimensions) + any(s -> s isa EnrSpace, ρ.dimensions.to) && throw(ArgumentError("partial_transpose does not support EnrSpace")) + + (length(mask) != length(ρ.dimensions)) && throw(ArgumentError("The length of \`mask\` should be equal to the length of \`ρ.dims\`.")) - end - return _partial_transpose(ρ, mask) -end -# for dense matrices -function _partial_transpose(ρ::QuantumObject{Operator}, mask::Vector{Bool}) isa(ρ.dimensions, GeneralDimensions) && (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) + return _partial_transpose(ρ, mask) +end + +# for dense matrices +function _partial_transpose(ρ::QuantumObject{Operator}, mask::Vector{Bool}) mask2 = [1 + Int(i) for i in mask] # mask2 has elements with values equal to 1 or 2 # 1 - the subsystem don't need to be transposed @@ -107,10 +109,6 @@ function _partial_transpose( ρ::QuantumObject{Operator,DimsType,<:AbstractSparseArray}, mask::Vector{Bool}, ) where {DimsType<:AbstractDimensions} - isa(ρ.dimensions, GeneralDimensions) && - (get_dimensions_to(ρ) != get_dimensions_from(ρ)) && - throw(ArgumentError("Invalid partial transpose for dims = $(_get_dims_string(ρ.dimensions))")) - M, N = size(ρ) dimsTuple = Tuple(dimensions_to_dims(get_dimensions_to(ρ))) colptr = ρ.data.colptr diff --git a/src/qobj/arithmetic_and_attributes.jl b/src/qobj/arithmetic_and_attributes.jl index 957ef786d..1f6c245c1 100644 --- a/src/qobj/arithmetic_and_attributes.jl +++ b/src/qobj/arithmetic_and_attributes.jl @@ -505,6 +505,8 @@ Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true ``` """ function ptrace(QO::QuantumObject{Ket}, sel::Union{AbstractVector{Int},Tuple}) + any(s -> s isa EnrSpace, QO.dimensions.to) && throw(ArgumentError("ptrace does not support EnrSpace")) + _non_static_array_warning("sel", sel) if length(sel) == 0 # return full trace for empty sel @@ -527,6 +529,8 @@ end ptrace(QO::QuantumObject{Bra}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel) function ptrace(QO::QuantumObject{Operator}, sel::Union{AbstractVector{Int},Tuple}) + any(s -> s isa EnrSpace, QO.dimensions.to) && throw(ArgumentError("ptrace does not support EnrSpace")) + # TODO: support for special cases when some of the subsystems have same `to` and `from` space isa(QO.dimensions, GeneralDimensions) && (get_dimensions_to(QO) != get_dimensions_from(QO)) && @@ -714,6 +718,8 @@ function SparseArrays.permute( A::QuantumObject{ObjType}, order::Union{AbstractVector{Int},Tuple}, ) where {ObjType<:Union{Ket,Bra,Operator}} + any(s -> s isa EnrSpace, A.dimensions.to) && throw(ArgumentError("permute does not support EnrSpace")) + (length(order) != length(A.dimensions)) && throw(ArgumentError("The order list must have the same length as the number of subsystems (A.dims)")) diff --git a/src/qobj/dimensions.jl b/src/qobj/dimensions.jl index ba72659e1..692da0f05 100644 --- a/src/qobj/dimensions.jl +++ b/src/qobj/dimensions.jl @@ -17,7 +17,7 @@ struct Dimensions{N,T<:Tuple} <: AbstractDimensions{N,N} to::T # make sure the elements in the tuple are all AbstractSpace - Dimensions(to::NTuple{N,T}) where {N,T<:AbstractSpace} = new{N,typeof(to)}(to) + Dimensions(to::NTuple{N,AbstractSpace}) where {N} = new{N,typeof(to)}(to) end function Dimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Integer,N} _non_static_array_warning("dims", dims) @@ -43,12 +43,11 @@ Dimensions(dims::Any) = throw( A structure that describes the left-hand side (`to`) and right-hand side (`from`) Hilbert [`Space`](@ref) of an [`Operator`](@ref). """ struct GeneralDimensions{M,N,T1<:Tuple,T2<:Tuple} <: AbstractDimensions{M,N} - # note that the number `N` should be the same for both `to` and `from` to::T1 # space acting on the left from::T2 # space acting on the right # make sure the elements in the tuple are all AbstractSpace - GeneralDimensions(to::NTuple{M,T1}, from::NTuple{N,T2}) where {M,N,T1<:AbstractSpace,T2<:AbstractSpace} = + GeneralDimensions(to::NTuple{M,AbstractSpace}, from::NTuple{N,AbstractSpace}) where {M,N} = new{M,N,typeof(to),typeof(from)}(to, from) end function GeneralDimensions(dims::Union{AbstractVector{T},NTuple{N,T}}) where {T<:Union{AbstractVector,NTuple},N} @@ -98,3 +97,8 @@ function _get_dims_string(dimensions::GeneralDimensions) return "[$(string(dims[1])), $(string(dims[2]))]" end _get_dims_string(::Nothing) = "nothing" # for EigsolveResult.dimensions = nothing + +Base.:(==)(dim1::Dimensions, dim2::Dimensions) = dim1.to == dim2.to +Base.:(==)(dim1::GeneralDimensions, dim2::GeneralDimensions) = (dim1.to == dim2.to) && (dim1.from == dim2.from) +Base.:(==)(dim1::Dimensions, dim2::GeneralDimensions) = false +Base.:(==)(dim1::GeneralDimensions, dim2::Dimensions) = false diff --git a/src/qobj/energy_restricted.jl b/src/qobj/energy_restricted.jl new file mode 100644 index 000000000..f65ea84ca --- /dev/null +++ b/src/qobj/energy_restricted.jl @@ -0,0 +1,264 @@ +#= +This file defines the energy restricted space structure. +=# + +export EnrSpace, enr_state_dictionaries +export enr_fock, enr_thermal_dm, enr_destroy, enr_identity + +@doc raw""" + struct EnrSpace{N} <: AbstractSpace + size::Int + dims::NTuple{N,Int} + n_excitations::Int + state2idx::Dict{SVector{N,Int},Int} + idx2state::Dict{Int,SVector{N,Int}} + end + +A structure that describes an excitation number restricted (ENR) state space, where `N` is the number of sub-systems. + +# Fields + +- `size`: Number of states in the excitation number restricted state space +- `dims`: A list of the number of states in each sub-system +- `n_excitations`: Maximum number of excitations +- `state2idx`: A dictionary for looking up a state index from a state (`SVector`) +- `idx2state`: A dictionary for looking up state (`SVector`) from a state index + +# Functions + +With this `EnrSpace`, one can use the following functions to construct states or operators in the excitation number restricted (ENR) space: + +- [`enr_fock`](@ref) +- [`enr_thermal_dm`](@ref) +- [`enr_destroy`](@ref) +- [`enr_identity`](@ref) + +# Example + +To construct an `EnrSpace`, we only need to specify the `dims` and `n_excitations`, namely + +```jldoctest +julia> dims = (2, 2, 3); + +julia> n_excitations = 3; + +julia> EnrSpace(dims, n_excitations) +EnrSpace([2, 2, 3], 3) +``` + +!!! warning "Beware of type-stability!" + It is highly recommended to use `EnrSpace(dims, n_excitations)` with `dims` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +struct EnrSpace{N} <: AbstractSpace + size::Int + dims::SVector{N,Int} + n_excitations::Int + state2idx::Dict{SVector{N,Int},Int} + idx2state::Dict{Int,SVector{N,Int}} + + function EnrSpace(dims::Union{AbstractVector{T},NTuple{N,T}}, n_excitations::Int) where {T<:Integer,N} + # all arguments will be checked in `enr_state_dictionaries` + size, state2idx, idx2state = enr_state_dictionaries(dims, n_excitations) + + L = length(dims) + return new{L}(size, SVector{L}(dims), n_excitations, state2idx, idx2state) + end +end + +function Base.show(io::IO, s::EnrSpace) + print(io, "EnrSpace($(s.dims), $(s.n_excitations))") + return nothing +end + +Base.:(==)(s_enr1::EnrSpace, s_enr2::EnrSpace) = (s_enr1.size == s_enr2.size) && (s_enr1.dims == s_enr2.dims) + +dimensions_to_dims(s_enr::EnrSpace) = s_enr.dims + +@doc raw""" + enr_state_dictionaries(dims, n_excitations) + +Return the number of states, and lookup-dictionaries for translating a state (`SVector`) to a state index, and vice versa, for a system with a given number of components and maximum number of excitations. + +# Arguments +- `dims::Union{AbstractVector,Tuple}`: A list of the number of states in each sub-system +- `n_excitations::Int`: Maximum number of excitations + +# Returns +- `nstates`: Number of states +- `state2idx`: A dictionary for looking up a state index from a state (`SVector`) +- `idx2state`: A dictionary for looking up state (`SVector`) from a state index +""" +function enr_state_dictionaries(dims::Union{AbstractVector{T},NTuple{N,T}}, n_excitations::Int) where {T<:Integer,N} + # argument checks + _non_static_array_warning("dims", dims) + L = length(dims) + (L > 0) || throw(DomainError(dims, "The argument dims must be of non-zero length")) + all(>=(1), dims) || throw(DomainError(dims, "All the elements of dims must be non-zero integers (≥ 1)")) + (n_excitations > 0) || + throw(DomainError(n_excitations, "The argument n_excitations must be a non-zero integer (≥ 1)")) + + nvec = zeros(Int, L) # Vector + nexc = 0 + + # in the following, all `nvec` (Vector) will first be converted (copied) to SVector and then push to `result` + result = SVector{L,Int}[nvec] + while true + idx = L + nvec[end] += 1 + nexc += 1 + (nvec[idx] < dims[idx]) && push!(result, nvec) + while (nexc == n_excitations) || (nvec[idx] == dims[idx]) + idx -= 1 + + # if idx < 1, break while-loop and return + if idx < 1 + enr_size = length(result) + return (enr_size, Dict(zip(result, 1:enr_size)), Dict(zip(1:enr_size, result))) + end + + nexc -= nvec[idx+1] - 1 + nvec[idx+1] = 0 + nvec[idx] += 1 + (nvec[idx] < dims[idx]) && push!(result, nvec) + end + end +end + +@doc raw""" + enr_fock(dims::Union{AbstractVector,Tuple}, n_excitations::Int, state::AbstractVector; sparse::Union{Bool,Val}=Val(false)) + enr_fock(s_enr::EnrSpace, state::AbstractVector; sparse::Union{Bool,Val}=Val(false)) + +Generate the Fock state representation ([`Ket`](@ref)) in an excitation number restricted state space ([`EnrSpace`](@ref)). + +The arguments `dims` and `n_excitations` are used to generate [`EnrSpace`](@ref). + +The `state` argument is a list of integers that specifies the state (in the number basis representation) for which to generate the Fock state representation. + +!!! warning "Beware of type-stability!" + It is highly recommended to use `enr_fock(dims, n_excitations, state)` with `dims` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +function enr_fock( + dims::Union{AbstractVector{T},NTuple{N,T}}, + n_excitations::Int, + state::AbstractVector{T}; + sparse::Union{Bool,Val} = Val(false), +) where {T<:Integer,N} + s_enr = EnrSpace(dims, n_excitations) + return enr_fock(s_enr, state, sparse = sparse) +end +function enr_fock(s_enr::EnrSpace, state::AbstractVector{T}; sparse::Union{Bool,Val} = Val(false)) where {T<:Integer} + if getVal(sparse) + array = sparsevec([s_enr.state2idx[[state...]]], [1.0 + 0im], s_enr.size) + else + j = s_enr.state2idx[state] + array = [i == j ? 1.0 + 0im : 0.0 + 0im for i in 1:(s_enr.size)] + end + + return QuantumObject(array, Ket(), s_enr) +end + +@doc raw""" + enr_thermal_dm(dims::Union{AbstractVector,Tuple}, n_excitations::Int, n::Union{Real,AbstractVector}; sparse::Union{Bool,Val}=Val(false)) + enr_thermal_dm(s_enr::EnrSpace, n::Union{Real,AbstractVector}; sparse::Union{Bool,Val}=Val(false)) + +Generate the thermal state (density [`Operator`](@ref)) in an excitation number restricted state space ([`EnrSpace`](@ref)). + +The arguments `dims` and `n_excitations` are used to generate [`EnrSpace`](@ref). + +The argument `n` is a list that specifies the expectation values for number of particles in each sub-system. If `n` is specified as a real number, it will apply to each sub-system. + +!!! warning "Beware of type-stability!" + It is highly recommended to use `enr_thermal_dm(dims, n_excitations, n)` with `dims` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +function enr_thermal_dm( + dims::Union{AbstractVector{T1},NTuple{N,T1}}, + n_excitations::Int, + n::Union{T2,AbstractVector{T2}}; + sparse::Union{Bool,Val} = Val(false), +) where {T1<:Integer,T2<:Real,N} + s_enr = EnrSpace(dims, n_excitations) + return enr_thermal_dm(s_enr, n; sparse = sparse) +end +function enr_thermal_dm( + s_enr::EnrSpace{N}, + n::Union{T,AbstractVector{T}}; + sparse::Union{Bool,Val} = Val(false), +) where {N,T<:Real} + if n isa Real + nvec = fill(n, N) + else + (length(n) == N) || throw(ArgumentError("The length of the vector `n` should be the same as `dims`.")) + nvec = n + end + + D = s_enr.size + idx2state = s_enr.idx2state + + diags = ComplexF64[prod((nvec ./ (1 .+ nvec)) .^ idx2state[idx]) for idx in 1:D] + diags /= sum(diags) + + if getVal(sparse) + return QuantumObject(spdiagm(0 => diags), Operator(), s_enr) + else + return QuantumObject(diagm(0 => diags), Operator(), s_enr) + end +end + +@doc raw""" + enr_destroy(dims::Union{AbstractVector,Tuple}, n_excitations::Int) + enr_destroy(s_enr::EnrSpace) + +Generate a `Tuple` of annihilation operators for each sub-system in an excitation number restricted state space ([`EnrSpace`](@ref)). Thus, the return `Tuple` will have the same length as `dims`. + +The arguments `dims` and `n_excitations` are used to generate [`EnrSpace`](@ref). + +!!! warning "Beware of type-stability!" + It is highly recommended to use `enr_destroy(dims, n_excitations)` with `dims` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +function enr_destroy(dims::Union{AbstractVector{T},NTuple{N,T}}, n_excitations::Int) where {T<:Integer,N} + s_enr = EnrSpace(dims, n_excitations) + return enr_destroy(s_enr) +end +function enr_destroy(s_enr::EnrSpace{N}) where {N} + D = s_enr.size + idx2state = s_enr.idx2state + state2idx = s_enr.state2idx + + I_list = [Int64[] for _ in 1:N] + J_list = [Int64[] for _ in 1:N] + V_list = [ComplexF64[] for _ in 1:N] + + for (n1, state1) in idx2state + for (idx, s) in pairs(state1) + # if s > 0, the annihilation operator of mode idx has a non-zero + # entry with one less excitation in mode idx in the final state + if s > 0 + state2 = Vector(state1) + state2[idx] -= 1 + n2 = state2idx[state2] + push!(I_list[idx], n2) + push!(J_list[idx], n1) + push!(V_list[idx], √s) + end + end + end + + return ntuple(i -> QuantumObject(sparse(I_list[i], J_list[i], V_list[i], D, D), Operator(), s_enr), Val(N)) +end + +@doc raw""" + enr_identity(dims::Union{AbstractVector,Tuple}, n_excitations::Int) + enr_identity(s_enr::EnrSpace) + +Generate the identity operator in an excitation number restricted state space ([`EnrSpace`](@ref)). + +The arguments `dims` and `n_excitations` are used to generate [`EnrSpace`](@ref). + +!!! warning "Beware of type-stability!" + It is highly recommended to use `enr_identity(dims, n_excitations)` with `dims` as `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) to keep type stability. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. +""" +function enr_identity(dims::Union{AbstractVector{T},NTuple{N,T}}, n_excitations::Int) where {T<:Integer,N} + s_enr = EnrSpace(dims, n_excitations) + return enr_identity(s_enr) +end +enr_identity(s_enr::EnrSpace) = QuantumObject(Diagonal(ones(ComplexF64, s_enr.size)), Operator(), s_enr) diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index cf5fcd679..deff612c4 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -222,7 +222,7 @@ end # this returns `to` in GeneralDimensions representation get_dimensions_to(A::AbstractQuantumObject{Ket,<:Dimensions}) = A.dimensions.to -get_dimensions_to(A::AbstractQuantumObject{Bra,<:Dimensions{N}}) where {N} = space_one_list(N) +get_dimensions_to(A::AbstractQuantumObject{Bra,<:Dimensions}) = space_one_list(A.dimensions.to) get_dimensions_to(A::AbstractQuantumObject{Operator,<:Dimensions}) = A.dimensions.to get_dimensions_to(A::AbstractQuantumObject{Operator,<:GeneralDimensions}) = A.dimensions.to get_dimensions_to( @@ -230,7 +230,7 @@ get_dimensions_to( ) where {ObjType<:Union{SuperOperator,OperatorBra,OperatorKet}} = A.dimensions.to # this returns `from` in GeneralDimensions representation -get_dimensions_from(A::AbstractQuantumObject{Ket,<:Dimensions{N}}) where {N} = space_one_list(N) +get_dimensions_from(A::AbstractQuantumObject{Ket,<:Dimensions}) = space_one_list(A.dimensions.to) get_dimensions_from(A::AbstractQuantumObject{Bra,<:Dimensions}) = A.dimensions.to get_dimensions_from(A::AbstractQuantumObject{Operator,<:Dimensions}) = A.dimensions.to get_dimensions_from(A::AbstractQuantumObject{Operator,<:GeneralDimensions}) = A.dimensions.from @@ -238,6 +238,12 @@ get_dimensions_from( A::AbstractQuantumObject{ObjType,<:Dimensions}, ) where {ObjType<:Union{SuperOperator,OperatorBra,OperatorKet}} = A.dimensions.to +# this creates a list of Space(1), it is used to generate `from` for Ket, and `to` for Bra +space_one_list(dimensions::NTuple{N,AbstractSpace}) where {N} = + ntuple(i -> Space(1), Val(sum(_get_dims_length, dimensions))) +_get_dims_length(::Space) = 1 +_get_dims_length(::EnrSpace{N}) where {N} = N + # functions for getting Float or Complex element type _FType(A::AbstractQuantumObject) = _FType(eltype(A)) _CType(A::AbstractQuantumObject) = _CType(eltype(A)) diff --git a/src/qobj/space.jl b/src/qobj/space.jl index 0b90ecf55..90b199759 100644 --- a/src/qobj/space.jl +++ b/src/qobj/space.jl @@ -23,19 +23,3 @@ struct Space <: AbstractSpace end dimensions_to_dims(s::Space) = SVector{1,Int}(s.size) - -# this creates a list of Space(1), it is used to generate `from` for Ket, and `to` for Bra) -space_one_list(N::Int) = ntuple(i -> Space(1), Val(N)) - -# TODO: introduce energy restricted space -#= -struct EnrSpace{N} <: AbstractSpace - size::Int - dims::SVector{N,Int} - n_excitations::Int - state2idx - idx2state -end - -dimensions_to_dims(s::EnrSpace) = s.dims -=# diff --git a/test/core-test/enr_state_operator.jl b/test/core-test/enr_state_operator.jl new file mode 100644 index 000000000..50bed88c8 --- /dev/null +++ b/test/core-test/enr_state_operator.jl @@ -0,0 +1,119 @@ +@testitem "Excitation number restricted state space" begin + using StaticArraysCore + + @testset "EnrSpace" begin + s_enr = EnrSpace((2, 2, 3), 3) + + # check if the idx2state is the same as qutip + qutip_idx2state = Dict( + 1 => SVector{3}(0, 0, 0), + 2 => SVector{3}(0, 0, 1), + 3 => SVector{3}(0, 0, 2), + 4 => SVector{3}(0, 1, 0), + 5 => SVector{3}(0, 1, 1), + 6 => SVector{3}(0, 1, 2), + 7 => SVector{3}(1, 0, 0), + 8 => SVector{3}(1, 0, 1), + 9 => SVector{3}(1, 0, 2), + 10 => SVector{3}(1, 1, 0), + 11 => SVector{3}(1, 1, 1), + ) + @test s_enr.idx2state == qutip_idx2state + end + + @testset "kron" begin + # normal Space + D1 = 4 + D2 = 5 + dims_s = (D1, D2) + ρ_s = rand_dm(dims_s) + I_s = qeye(D1) ⊗ qeye(D2) + size_s = prod(dims_s) + space_s = (Space(D1), Space(D2)) + + # EnrSpace + dims_enr = (2, 2, 3) + excitations = 3 + space_enr = EnrSpace(dims_enr, excitations) + ρ_enr = enr_thermal_dm(space_enr, rand(3)) + I_enr = enr_identity(space_enr) + size_enr = space_enr.size + + # tensor between normal and ENR space + ρ_tot = tensor(ρ_s, ρ_enr) + opstring = sprint((t, s) -> show(t, "text/plain", s), ρ_tot) + datastring = sprint((t, s) -> show(t, "text/plain", s), ρ_tot.data) + ρ_tot_dims = [dims_s..., dims_enr...] + ρ_tot_size = size_s * size_enr + ρ_tot_isherm = isherm(ρ_tot) + @test opstring == + "\nQuantum Object: type=Operator() dims=$ρ_tot_dims size=$((ρ_tot_size, ρ_tot_size)) ishermitian=$ρ_tot_isherm\n$datastring" + + # use GeneralDimensions to do partial trace + new_dims1 = GeneralDimensions((Space(1), Space(1), space_enr), (Space(1), Space(1), space_enr)) + ρ_enr_compound = Qobj(zeros(ComplexF64, size_enr, size_enr), dims = new_dims1) + basis_list = [tensor(basis(D1, i), basis(D2, j)) for i in 0:(D1-1) for j in 0:(D2-1)] + for b in basis_list + ρ_enr_compound += tensor(b', I_enr) * ρ_tot * tensor(b, I_enr) + end + new_dims2 = + GeneralDimensions((space_s..., Space(1), Space(1), Space(1)), (space_s..., Space(1), Space(1), Space(1))) + ρ_s_compound = Qobj(zeros(ComplexF64, size_s, size_s), dims = new_dims2) + basis_list = [enr_fock(space_enr, space_enr.idx2state[idx]) for idx in 1:space_enr.size] + for b in basis_list + ρ_s_compound += tensor(I_s, b') * ρ_tot * tensor(I_s, b) + end + @test ρ_enr.data ≈ ρ_enr_compound.data + @test ρ_s.data ≈ ρ_s_compound.data + end + + @testset "mesolve, steadystate, and eigenstates" begin + ε = 2π + ωc = 2π + g = 0.1ωc + γ = 0.01ωc + tlist = range(0, 20, 100) + N_cut = 2 + + # normal mesolve and steadystate + sz = sigmaz() ⊗ qeye(N_cut) + sm = sigmam() ⊗ qeye(N_cut) + a = qeye(2) ⊗ destroy(N_cut) + H_JC = 0.5ε * sz + ωc * a' * a + g * (sm' * a + a' * sm) + c_ops_JC = (√γ * a,) + ψ0_JC = basis(2, 0) ⊗ fock(N_cut, 0) + sol_JC = mesolve(H_JC, ψ0_JC, tlist, c_ops_JC; e_ops = [sz], progress_bar = Val(false)) + ρ_ss_JC = steadystate(H_JC, c_ops_JC) + + # ENR mesolve and steadystate + N_exc = 1 + dims = (2, N_cut) + sm_enr, a_enr = enr_destroy(dims, N_exc) + sz_enr = 2 * sm_enr' * sm_enr - 1 + ψ0_enr = enr_fock(dims, N_exc, [1, 0]) + H_enr = ε * sm_enr' * sm_enr + ωc * a_enr' * a_enr + g * (sm_enr' * a_enr + a_enr' * sm_enr) + c_ops_enr = (√γ * a_enr,) + sol_enr = mesolve(H_enr, ψ0_enr, tlist, c_ops_enr; e_ops = [sz_enr], progress_bar = Val(false)) + ρ_ss_enr = steadystate(H_enr, c_ops_enr) + + # check mesolve result + @test all(isapprox.(sol_JC.expect, sol_enr.expect, atol = 1e-4)) + + # check steadystate result + @test expect(sz, ρ_ss_JC) ≈ expect(sz_enr, ρ_ss_enr) atol=1e-4 + + # check eigenstates + λ, v = eigenstates(H_enr) + @test all([H_enr * v[k] ≈ λ[k] * v[k] for k in eachindex(λ)]) + end + + @testset "Type Inference" begin + N = 3 + dims = (2, 2, 3) + excitations = 3 + @inferred enr_identity(dims, excitations) + @inferred enr_fock(dims, excitations, zeros(Int, N)) + @inferred enr_destroy(dims, excitations) + @inferred enr_thermal_dm(dims, excitations, rand(N)) + end +end From 37f4e0d0cf416ef5727c185fb111cdff601af436 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:18:09 +0900 Subject: [PATCH 289/329] Bump crate-ci/typos from 1.33.1 to 1.34.0 (#503) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/SpellCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 531b0447e..4d96e0e0e 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.33.1 \ No newline at end of file + uses: crate-ci/typos@v1.34.0 \ No newline at end of file From 2e9fb5aaa446a89f6b55f6b043cdf12ad523088c Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:15:06 +0800 Subject: [PATCH 290/329] separate time evolution tests into individual `@testitems` (#505) --- test/core-test/time_evolution.jl | 1531 +++++++++++++++--------------- 1 file changed, 781 insertions(+), 750 deletions(-) diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index a67c97706..71642227c 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -1,6 +1,6 @@ -@testitem "Time Evolution and Partial Trace" begin +@testmodule TESetup begin + using QuantumToolbox using Random - using SciMLOperators # Global definition of the system N = 10 @@ -24,804 +24,835 @@ sme_η = 0.7 # Efficiency of the homodyne detector for smesolve c_ops_sme = [sqrt(1 - sme_η) * op for op in c_ops] sc_ops_sme = [sqrt(sme_η) * op for op in c_ops] + # The following definition is to test the case of `sc_ops` as an `AbstractQuantumObject` c_ops_sme2 = c_ops[2:end] sc_ops_sme2 = c_ops[1] ψ0_int = Qobj(round.(Int, real.(ψ0.data)), dims = ψ0.dims) # Used for testing the type inference - @testset "sesolve" verbose = true begin - tlist = range(0, 20 * 2π / g, 1000) - saveat_idxs = 500:900 - saveat = tlist[saveat_idxs] - - prob = sesolveProblem(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) - sol = sesolve(prob) - sol2 = sesolve(H, ψ0, tlist, progress_bar = Val(false)) - sol3 = sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) - sol_string = sprint((t, s) -> show(t, "text/plain", s), sol) - sol_string2 = sprint((t, s) -> show(t, "text/plain", s), sol2) - - ## Analytical solution for the expectation value of a' * a - Ω_rabi = sqrt(g^2 + ((ωc - ωq) / 2)^2) - amp_rabi = g^2 / Ω_rabi^2 - ## - - @test prob.prob.f.f isa MatrixOperator - @test sum(abs.(sol.expect[1, :] .- amp_rabi .* sin.(Ω_rabi * tlist) .^ 2)) / length(tlist) < 0.1 - @test length(sol.times) == length(tlist) - @test length(sol.states) == 1 - @test size(sol.expect) == (length(e_ops), length(tlist)) - @test length(sol2.times) == length(tlist) - @test length(sol2.states) == length(tlist) - @test sol2.expect === nothing - @test length(sol3.times) == length(tlist) - @test length(sol3.states) == length(saveat) - @test size(sol3.expect) == (length(e_ops), length(tlist)) - @test sol.expect[1, saveat_idxs] ≈ expect(e_ops[1], sol3.states) atol = 1e-6 - @test sol_string == - "Solution of time evolution\n" * - "(return code: $(sol.retcode))\n" * - "--------------------------\n" * - "num_states = $(length(sol.states))\n" * - "num_expect = $(size(sol.expect, 1))\n" * - "ODE alg.: $(sol.alg)\n" * - "abstol = $(sol.abstol)\n" * - "reltol = $(sol.reltol)\n" - @test sol_string2 == - "Solution of time evolution\n" * - "(return code: $(sol2.retcode))\n" * - "--------------------------\n" * - "num_states = $(length(sol2.states))\n" * - "num_expect = 0\n" * - "ODE alg.: $(sol2.alg)\n" * - "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)) - @test allocs_tot < 110 - - allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up - allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) - @test allocs_tot < 90 - end - - @testset "Type Inference sesolve" begin - @inferred sesolveProblem(H, ψ0, tlist, progress_bar = Val(false)) - @inferred sesolveProblem(H, ψ0, [0, 10], progress_bar = Val(false)) - @inferred sesolveProblem(H, ψ0_int, tlist, progress_bar = Val(false)) - @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) - @inferred sesolve(H, ψ0, tlist, progress_bar = Val(false)) - @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) - @inferred sesolve(H, ψ0, tlist, e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types - end + ψ_wrong = kron(fock(N - 1, 0), fock(2, 0)) + + rng = MersenneTwister(12) + + # QobjEvo + ωd = 1.02 + F = 0.05 + coef1(p, t) = p.F * exp(1im * p.ωd * t) + coef2(p, t) = p.F * exp(-1im * p.ωd * t) + p = (F = F, ωd = ωd) + H_td = (H, (a, coef1), (a', coef2)) + H_td2 = QobjEvo(H_td) + L_td = liouvillian(H_td2) + + # time list and saveat + tlist = range(0, 10 / γ, 100) + saveat_idxs = 50:90 + saveat = tlist[saveat_idxs] + + # time list for testing exceptions + tlist1 = Float64[] + tlist2 = [0, 0.2, 0.1] + tlist3 = [0, 0.1, 0.1, 0.2] + + # mesolve solution used for comparing results from mcsolve, ssesolve, and smesolve with mesolve + prob_me = mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_me = mesolve(prob_me) +end + +@testitem "sesolve" setup=[TESetup] begin + using SciMLOperators + + # Get parameters from TESetup to simplify the code + H = TESetup.H + ψ0 = TESetup.ψ0 + e_ops = TESetup.e_ops + + tlist = range(0, 20 * 2π / TESetup.g, 1000) + saveat_idxs = 500:900 + saveat = tlist[saveat_idxs] + + prob = sesolveProblem(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + sol = sesolve(prob) + sol2 = sesolve(H, ψ0, tlist, progress_bar = Val(false)) + sol3 = sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) + + ## Analytical solution for the expectation value of a' * a + Ω_rabi = sqrt(TESetup.g^2 + ((TESetup.ωc - TESetup.ωq) / 2)^2) + amp_rabi = TESetup.g^2 / Ω_rabi^2 + ## + + @test prob.prob.f.f isa MatrixOperator + @test sum(abs.(sol.expect[1, :] .- amp_rabi .* sin.(Ω_rabi * tlist) .^ 2)) / length(tlist) < 0.1 + @test length(sol.times) == length(tlist) + @test length(sol.states) == 1 + @test size(sol.expect) == (length(e_ops), length(tlist)) + @test length(sol2.times) == length(tlist) + @test length(sol2.states) == length(tlist) + @test sol2.expect === nothing + @test length(sol3.times) == length(tlist) + @test length(sol3.states) == length(saveat) + @test size(sol3.expect) == (length(e_ops), length(tlist)) + @test sol.expect[1, saveat_idxs] ≈ expect(e_ops[1], sol3.states) atol = 1e-6 + + sol_string = sprint((t, s) -> show(t, "text/plain", s), sol) + @test sol_string == + "Solution of time evolution\n" * + "(return code: $(sol.retcode))\n" * + "--------------------------\n" * + "num_states = $(length(sol.states))\n" * + "num_expect = $(size(sol.expect, 1))\n" * + "ODE alg.: $(sol.alg)\n" * + "abstol = $(sol.abstol)\n" * + "reltol = $(sol.reltol)\n" + + sol_string2 = sprint((t, s) -> show(t, "text/plain", s), sol2) + @test sol_string2 == + "Solution of time evolution\n" * + "(return code: $(sol2.retcode))\n" * + "--------------------------\n" * + "num_states = $(length(sol2.states))\n" * + "num_expect = 0\n" * + "ODE alg.: $(sol2.alg)\n" * + "abstol = $(sol2.abstol)\n" * + "reltol = $(sol2.reltol)\n" + + @test_throws ArgumentError sesolve(H, ψ0, TESetup.tlist1, progress_bar = Val(false)) + @test_throws ArgumentError sesolve(H, ψ0, TESetup.tlist2, progress_bar = Val(false)) + @test_throws ArgumentError sesolve(H, ψ0, TESetup.tlist3, progress_bar = Val(false)) + @test_throws ArgumentError sesolve(H, ψ0, tlist, save_idxs = [1, 2], progress_bar = Val(false)) + @test_throws DimensionMismatch sesolve(H, TESetup.ψ_wrong, tlist, 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)) + @test allocs_tot < 110 + + allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations sesolve(H, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) + @test allocs_tot < 90 + end + + @testset "Type Inference sesolve" begin + @inferred sesolveProblem(H, ψ0, tlist, progress_bar = Val(false)) + @inferred sesolveProblem(H, ψ0, [0, 10], progress_bar = Val(false)) + @inferred sesolveProblem(H, TESetup.ψ0_int, tlist, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) + @inferred sesolve(H, ψ0, tlist, e_ops = (TESetup.a' * TESetup.a, TESetup.a'), progress_bar = Val(false)) # We test the type inference for Tuple of different types + end +end + +@testitem "mesolve" setup=[TESetup] begin + using SciMLOperators + + # Get parameters from TESetup to simplify the code + H = TESetup.H + ψ0 = TESetup.ψ0 + tlist = TESetup.tlist + c_ops = TESetup.c_ops + e_ops = TESetup.e_ops + saveat = TESetup.saveat + sol_me = TESetup.sol_me + + sol_me2 = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) + sol_me3 = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) + + # For testing the `OperatorKet` input + sol_me4 = mesolve(H, operator_to_vector(ket2dm(ψ0)), tlist, c_ops, saveat = saveat, progress_bar = Val(false)) + + # Redirect to `sesolve` + sol_me5 = mesolve(H, ψ0, tlist, progress_bar = Val(false)) + + @test TESetup.prob_me.prob.f.f isa MatrixOperator + @test isket(sol_me5.states[1]) + @test length(sol_me.times) == length(tlist) + @test length(sol_me.states) == 1 + @test size(sol_me.expect) == (length(e_ops), length(tlist)) + @test length(sol_me2.times) == length(tlist) + @test length(sol_me2.states) == length(tlist) + @test sol_me2.expect === nothing + @test length(sol_me3.times) == length(tlist) + @test length(sol_me3.states) == length(saveat) + @test size(sol_me3.expect) == (length(e_ops), length(tlist)) + @test sol_me3.expect[1, TESetup.saveat_idxs] ≈ expect(e_ops[1], sol_me3.states) atol = 1e-6 + @test all([sol_me3.states[i] ≈ vector_to_operator(sol_me4.states[i]) for i in eachindex(saveat)]) + + sol_me_string = sprint((t, s) -> show(t, "text/plain", s), sol_me) + @test sol_me_string == + "Solution of time evolution\n" * + "(return code: $(sol_me.retcode))\n" * + "--------------------------\n" * + "num_states = $(length(sol_me.states))\n" * + "num_expect = $(size(sol_me.expect, 1))\n" * + "ODE alg.: $(sol_me.alg)\n" * + "abstol = $(sol_me.abstol)\n" * + "reltol = $(sol_me.reltol)\n" + + @test_throws ArgumentError mesolve(H, ψ0, TESetup.tlist1, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mesolve(H, ψ0, TESetup.tlist2, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mesolve(H, ψ0, TESetup.tlist3, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mesolve(H, ψ0, tlist, c_ops, save_idxs = [1, 2], progress_bar = Val(false)) + @test_throws DimensionMismatch mesolve(H, TESetup.ψ_wrong, tlist, c_ops, progress_bar = Val(false)) + + @testset "Memory Allocations (mesolve)" begin + a = TESetup.a + p = TESetup.p + + # We predefine the Liouvillian to avoid to count the allocations of the liouvillian function + L = liouvillian(H, c_ops) + L_td = QobjEvo((liouvillian(H, c_ops), (liouvillian(a), TESetup.coef1), (liouvillian(a'), TESetup.coef2))) + + allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + @test allocs_tot < 180 + + allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) + @test allocs_tot < 110 + + allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) # Warm-up + allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) + @test allocs_tot < 180 + + allocs_tot = @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) # Warm-up + allocs_tot = @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) + @test allocs_tot < 110 + end + + @testset "Type Inference (mesolve)" begin + a = TESetup.a + p = TESetup.p + + coef(p, t) = exp(-t) + ad_t = QobjEvo(a', coef) + @inferred mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolveProblem(H, ψ0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolveProblem(H, TESetup.ψ0_int, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + @inferred mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) + @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) + @inferred mesolve(H, ψ0, tlist, (a, ad_t), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple + @inferred mesolve(TESetup.H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + @inferred mesolve(TESetup.H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + @inferred mesolve(TESetup.L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) end +end + +@testitem "mcsolve" setup=[TESetup] begin + using SciMLOperators + + # Get parameters from TESetup to simplify the code + H = TESetup.H + ψ0 = TESetup.ψ0 + tlist = TESetup.tlist + c_ops = TESetup.c_ops + e_ops = TESetup.e_ops + saveat = TESetup.saveat + saveat_idxs = TESetup.saveat_idxs + sol_me = TESetup.sol_me + + prob_mc = mcsolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_mc2 = mcsolve( + H, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + progress_bar = Val(false), + jump_callback = DiscreteLindbladJumpCallback(), + ) + sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, saveat = saveat, progress_bar = Val(false)) + sol_mc_states2 = mcsolve( + H, + ψ0, + tlist, + c_ops, + saveat = saveat, + progress_bar = Val(false), + jump_callback = DiscreteLindbladJumpCallback(), + ) + + ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] + expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) + expect_mc_states_mean = sum(expect_mc_states, dims = 2) / size(expect_mc_states, 2) + + ρt_mc2 = [ket2dm.(normalize.(states)) for states in sol_mc_states2.states] + expect_mc_states2 = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc2) + expect_mc_states_mean2 = sum(expect_mc_states2, dims = 2) / size(expect_mc_states2, 2) + + @test prob_mc.prob.f.f isa MatrixOperator + @test sum(abs, sol_mc.expect .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, sol_mc2.expect .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 + @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 + @test length(sol_mc.times) == length(tlist) + @test size(sol_mc.expect) == (length(e_ops), length(tlist)) + @test length(sol_mc_states.times) == length(tlist) + @test sol_mc_states.expect === nothing + + sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) + sol_mc_string_states = sprint((t, s) -> show(t, "text/plain", s), sol_mc_states) + @test sol_mc_string == + "Solution of quantum trajectories\n" * + "(converged: $(sol_mc.converged))\n" * + "--------------------------------\n" * + "num_trajectories = $(sol_mc.ntraj)\n" * + "num_states = $(length(sol_mc.states[1]))\n" * + "num_expect = $(size(sol_mc.expect, 1))\n" * + "ODE alg.: $(sol_mc.alg)\n" * + "abstol = $(sol_mc.abstol)\n" * + "reltol = $(sol_mc.reltol)\n" + @test sol_mc_string_states == + "Solution of quantum trajectories\n" * + "(converged: $(sol_mc_states.converged))\n" * + "--------------------------------\n" * + "num_trajectories = $(sol_mc_states.ntraj)\n" * + "num_states = $(length(sol_mc_states.states[1]))\n" * + "num_expect = 0\n" * + "ODE alg.: $(sol_mc_states.alg)\n" * + "abstol = $(sol_mc_states.abstol)\n" * + "reltol = $(sol_mc_states.reltol)\n" + + @test_throws ArgumentError mcsolve(H, ψ0, TESetup.tlist1, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mcsolve(H, ψ0, TESetup.tlist2, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mcsolve(H, ψ0, TESetup.tlist3, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError mcsolve(H, ψ0, tlist, c_ops, save_idxs = [1, 2], progress_bar = Val(false)) + @test_throws DimensionMismatch mcsolve(H, TESetup.ψ_wrong, tlist, c_ops, progress_bar = Val(false)) + + @testset "Memory Allocations (mcsolve)" begin + ntraj = 100 + allocs_tot = @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) + @test allocs_tot < 120 * ntraj + 400 # 150 allocations per trajectory + 500 for initialization + + allocs_tot = + @allocations mcsolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up + allocs_tot = + @allocations mcsolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) + @test allocs_tot < 110 * ntraj + 300 # 100 allocations per trajectory + 300 for initialization + end + + @testset "Type Inference (mcsolve)" begin + a = TESetup.a + rng = TESetup.rng - @testset "mesolve, mcsolve, ssesolve and smesolve" verbose = true begin - tlist = range(0, 10 / γ, 100) - saveat_idxs = 50:90 - saveat = tlist[saveat_idxs] - - prob_me = mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_me = mesolve(prob_me) - sol_me2 = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) - sol_me3 = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = saveat, progress_bar = Val(false)) - prob_mc = mcsolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_mc2 = mcsolve( + @inferred mcsolveEnsembleProblem( H, ψ0, tlist, c_ops, + ntraj = 5, e_ops = e_ops, progress_bar = Val(false), - jump_callback = DiscreteLindbladJumpCallback(), + rng = rng, ) - sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, saveat = saveat, progress_bar = Val(false)) - sol_mc_states2 = mcsolve( - H, + @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, e_ops = e_ops, progress_bar = Val(false), rng = rng) + @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, progress_bar = Val(true), rng = rng) + @inferred mcsolve(H, ψ0, [0, 10], c_ops, ntraj = 5, progress_bar = Val(false), rng = rng) + @inferred mcsolve(H, TESetup.ψ0_int, tlist, c_ops, ntraj = 5, progress_bar = Val(false), rng = rng) + @inferred mcsolve(H, ψ0, tlist, (a, a'), e_ops = (a' * a, a'), ntraj = 5, progress_bar = Val(false), rng = rng) # We test the type inference for Tuple of different types + @inferred mcsolve( + TESetup.H_td, ψ0, tlist, c_ops, - saveat = saveat, + ntraj = 5, + e_ops = e_ops, progress_bar = Val(false), - jump_callback = DiscreteLindbladJumpCallback(), + params = TESetup.p, + rng = rng, ) - sol_sse = ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_sse2 = ssesolve( + end +end + +@testitem "ssesolve" setup=[TESetup] begin + # Get parameters from TESetup to simplify the code + H = TESetup.H + ψ0 = TESetup.ψ0 + tlist = TESetup.tlist + c_ops = TESetup.c_ops + e_ops = TESetup.e_ops + sol_me = TESetup.sol_me + + sol_sse = ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_sse2 = ssesolve( + H, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + ntraj = 20, + progress_bar = Val(false), + store_measurement = Val(true), + ) + + @test sum(abs, sol_sse.expect .- sol_me.expect) / length(tlist) < 0.1 + @test length(sol_sse.times) == length(tlist) + @test size(sol_sse.expect) == (length(e_ops), length(tlist)) + @test isnothing(sol_sse.measurement) + @test size(sol_sse2.measurement) == (length(c_ops), 20, length(tlist) - 1) + + sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) + @test sol_sse_string == + "Solution of stochastic quantum trajectories\n" * + "(converged: $(sol_sse.converged))\n" * + "--------------------------------\n" * + "num_trajectories = $(sol_sse.ntraj)\n" * + "num_states = $(length(sol_sse.states[1]))\n" * + "num_expect = $(size(sol_sse.expect, 1))\n" * + "SDE alg.: $(sol_sse.alg)\n" * + "abstol = $(sol_sse.abstol)\n" * + "reltol = $(sol_sse.reltol)\n" + + @test_throws ArgumentError ssesolve(H, ψ0, TESetup.tlist1, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError ssesolve(H, ψ0, TESetup.tlist2, c_ops, progress_bar = Val(false)) + @test_throws ArgumentError ssesolve(H, ψ0, TESetup.tlist3, c_ops, progress_bar = Val(false)) + + @testset "Memory Allocations (ssesolve)" begin + ntraj = 100 + allocs_tot = @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up + allocs_tot = @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) + @test allocs_tot < 1100 * ntraj + 400 # TODO: Fix this high number of allocations + + allocs_tot = + @allocations ssesolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up + allocs_tot = + @allocations ssesolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) + @test allocs_tot < 1000 * ntraj + 300 # TODO: Fix this high number of allocations + end + + @testset "Type Inference (ssesolve)" begin + a = TESetup.a + rng = TESetup.rng + p = TESetup.p + + c_ops_tuple = Tuple(c_ops) # To avoid type instability, we must have a Tuple instead of a Vector + @inferred ssesolveEnsembleProblem( H, ψ0, tlist, - c_ops, + c_ops_tuple, + ntraj = 5, e_ops = e_ops, - ntraj = 20, progress_bar = Val(false), - store_measurement = Val(true), + rng = rng, ) - sol_sme = smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, e_ops = e_ops, progress_bar = Val(false)) - sol_sme2 = smesolve( + @inferred ssesolve(H, ψ0, tlist, c_ops_tuple, ntraj = 5, e_ops = e_ops, progress_bar = Val(false), rng = rng) + @inferred ssesolve(H, ψ0, tlist, c_ops_tuple, ntraj = 5, progress_bar = Val(true), rng = rng) + @inferred ssesolve(H, ψ0, [0, 10], c_ops_tuple, ntraj = 5, progress_bar = Val(false), rng = rng) + @inferred ssesolve(H, TESetup.ψ0_int, tlist, c_ops_tuple, ntraj = 5, progress_bar = Val(false), rng = rng) + @inferred ssesolve( + H, + ψ0, + tlist, + c_ops_tuple, + ntraj = 5, + e_ops = (a' * a, a'), + progress_bar = Val(false), + rng = rng, + ) # We test the type inference for Tuple of different types + @inferred ssesolve( + TESetup.H_td, + ψ0, + tlist, + c_ops_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + params = p, + rng = rng, + ) + end +end + +@testitem "smesolve" setup=[TESetup] begin + using Random + + # Get parameters from TESetup to simplify the code + H = TESetup.H + ψ0 = TESetup.ψ0 + tlist = TESetup.tlist + c_ops = TESetup.c_ops + c_ops_sme = TESetup.c_ops_sme + sc_ops_sme = TESetup.sc_ops_sme + c_ops_sme2 = TESetup.c_ops_sme2 + sc_ops_sme2 = TESetup.sc_ops_sme2 + e_ops = TESetup.e_ops + sol_me = TESetup.sol_me + saveat = TESetup.saveat + + sol_sme = smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, e_ops = e_ops, progress_bar = Val(false)) + sol_sme2 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + e_ops = e_ops, + ntraj = 20, + progress_bar = Val(false), + store_measurement = Val(true), + ) + sol_sme3 = smesolve(H, ψ0, tlist, c_ops_sme2, sc_ops_sme2, e_ops = e_ops, progress_bar = Val(false)) + + # For testing the `OperatorKet` input + sol_sme4 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + saveat = saveat, + ntraj = 10, + progress_bar = Val(false), + rng = MersenneTwister(12), + ) + sol_sme5 = smesolve( + H, + operator_to_vector(ket2dm(ψ0)), + tlist, + c_ops_sme, + sc_ops_sme, + saveat = saveat, + ntraj = 10, + progress_bar = Val(false), + rng = MersenneTwister(12), + ) + + @test sum(abs, sol_sme.expect .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, sol_sme3.expect .- sol_me.expect) / length(tlist) < 0.1 + @test length(sol_sme.times) == length(tlist) + @test size(sol_sme.expect) == (length(e_ops), length(tlist)) + @test isnothing(sol_sme.measurement) + @test size(sol_sme2.measurement) == (length(sc_ops_sme), 20, length(tlist) - 1) + @test all([sol_sme4.states[j][i] ≈ vector_to_operator(sol_sme5.states[j][i]) for i in eachindex(saveat), j in 1:10]) + + sol_sme_string = sprint((t, s) -> show(t, "text/plain", s), sol_sme) + @test sol_sme_string == + "Solution of stochastic quantum trajectories\n" * + "(converged: $(sol_sme.converged))\n" * + "--------------------------------\n" * + "num_trajectories = $(sol_sme.ntraj)\n" * + "num_states = $(length(sol_sme.states[1]))\n" * + "num_expect = $(size(sol_sme.expect, 1))\n" * + "SDE alg.: $(sol_sme.alg)\n" * + "abstol = $(sol_sme.abstol)\n" * + "reltol = $(sol_sme.reltol)\n" + + @test_throws ArgumentError smesolve(H, ψ0, TESetup.tlist1, c_ops_sme, sc_ops_sme, progress_bar = Val(false)) + @test_throws ArgumentError smesolve(H, ψ0, TESetup.tlist2, c_ops_sme, sc_ops_sme, progress_bar = Val(false)) + @test_throws ArgumentError smesolve(H, ψ0, TESetup.tlist3, c_ops_sme, sc_ops_sme, progress_bar = Val(false)) + + @testset "Memory Allocations (smesolve)" begin + ntraj = 100 + allocs_tot = @allocations smesolve( H, ψ0, tlist, c_ops_sme, sc_ops_sme, e_ops = e_ops, - ntraj = 20, + ntraj = ntraj, + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + e_ops = e_ops, + ntraj = ntraj, progress_bar = Val(false), - store_measurement = Val(true), ) - sol_sme3 = smesolve(H, ψ0, tlist, c_ops_sme2, sc_ops_sme2, e_ops = e_ops, progress_bar = Val(false)) + @test allocs_tot < 1100 * ntraj + 1800 # TODO: Fix this high number of allocations - # For testing the `OperatorKet` input - sol_me4 = mesolve(H, operator_to_vector(ket2dm(ψ0)), tlist, c_ops, saveat = saveat, progress_bar = Val(false)) - sol_sme4 = smesolve( + allocs_tot = @allocations smesolve( H, ψ0, tlist, c_ops_sme, sc_ops_sme, - saveat = saveat, - ntraj = 10, + ntraj = ntraj, + saveat = [tlist[end]], progress_bar = Val(false), - rng = MersenneTwister(12), - ) - sol_sme5 = smesolve( + ) # Warm-up + allocs_tot = @allocations smesolve( H, - operator_to_vector(ket2dm(ψ0)), + ψ0, tlist, c_ops_sme, sc_ops_sme, - saveat = saveat, - ntraj = 10, + ntraj = ntraj, + saveat = [tlist[end]], progress_bar = Val(false), - rng = MersenneTwister(12), ) + @test allocs_tot < 1000 * ntraj + 1500 # TODO: Fix this high number of allocations - # Redirect to `sesolve` - sol_me5 = mesolve(H, ψ0, tlist, progress_bar = Val(false)) - - ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] - expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) - expect_mc_states_mean = sum(expect_mc_states, dims = 2) / size(expect_mc_states, 2) - - ρt_mc2 = [ket2dm.(normalize.(states)) for states in sol_mc_states2.states] - expect_mc_states2 = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc2) - expect_mc_states_mean2 = sum(expect_mc_states2, dims = 2) / size(expect_mc_states2, 2) - - sol_me_string = sprint((t, s) -> show(t, "text/plain", s), sol_me) - sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) - sol_mc_string_states = sprint((t, s) -> show(t, "text/plain", s), sol_mc_states) - sol_sse_string = sprint((t, s) -> show(t, "text/plain", s), sol_sse) - sol_sme_string = sprint((t, s) -> show(t, "text/plain", s), sol_sme) - @test prob_me.prob.f.f isa MatrixOperator - @test prob_mc.prob.f.f isa MatrixOperator - @test isket(sol_me5.states[1]) - @test sum(abs, sol_mc.expect .- sol_me.expect) / length(tlist) < 0.1 - @test sum(abs, sol_mc2.expect .- sol_me.expect) / length(tlist) < 0.1 - @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 - @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 - @test sum(abs, sol_sse.expect .- sol_me.expect) / length(tlist) < 0.1 - @test sum(abs, sol_sme.expect .- sol_me.expect) / length(tlist) < 0.1 - @test sum(abs, sol_sme3.expect .- sol_me.expect) / length(tlist) < 0.1 - @test length(sol_me.times) == length(tlist) - @test length(sol_me.states) == 1 - @test size(sol_me.expect) == (length(e_ops), length(tlist)) - @test length(sol_me2.times) == length(tlist) - @test length(sol_me2.states) == length(tlist) - @test sol_me2.expect === nothing - @test length(sol_me3.times) == length(tlist) - @test length(sol_me3.states) == length(saveat) - @test size(sol_me3.expect) == (length(e_ops), length(tlist)) - @test sol_me3.expect[1, saveat_idxs] ≈ expect(e_ops[1], sol_me3.states) atol = 1e-6 - @test all([sol_me3.states[i] ≈ vector_to_operator(sol_me4.states[i]) for i in eachindex(saveat)]) - @test length(sol_mc.times) == length(tlist) - @test size(sol_mc.expect) == (length(e_ops), length(tlist)) - @test length(sol_mc_states.times) == length(tlist) - @test sol_mc_states.expect === nothing - @test length(sol_sse.times) == length(tlist) - @test size(sol_sse.expect) == (length(e_ops), length(tlist)) - @test length(sol_sme.times) == length(tlist) - @test size(sol_sme.expect) == (length(e_ops), length(tlist)) - @test isnothing(sol_sse.measurement) - @test isnothing(sol_sme.measurement) - @test size(sol_sse2.measurement) == (length(c_ops), 20, length(tlist) - 1) - @test size(sol_sme2.measurement) == (length(sc_ops_sme), 20, length(tlist) - 1) - @test all([ - sol_sme4.states[j][i] ≈ vector_to_operator(sol_sme5.states[j][i]) for i in eachindex(saveat), j in 1:10 - ]) - - @test sol_me_string == - "Solution of time evolution\n" * - "(return code: $(sol_me.retcode))\n" * - "--------------------------\n" * - "num_states = $(length(sol_me.states))\n" * - "num_expect = $(size(sol_me.expect, 1))\n" * - "ODE alg.: $(sol_me.alg)\n" * - "abstol = $(sol_me.abstol)\n" * - "reltol = $(sol_me.reltol)\n" - @test sol_mc_string == - "Solution of quantum trajectories\n" * - "(converged: $(sol_mc.converged))\n" * - "--------------------------------\n" * - "num_trajectories = $(sol_mc.ntraj)\n" * - "num_states = $(length(sol_mc.states[1]))\n" * - "num_expect = $(size(sol_mc.expect, 1))\n" * - "ODE alg.: $(sol_mc.alg)\n" * - "abstol = $(sol_mc.abstol)\n" * - "reltol = $(sol_mc.reltol)\n" - @test sol_mc_string_states == - "Solution of quantum trajectories\n" * - "(converged: $(sol_mc_states.converged))\n" * - "--------------------------------\n" * - "num_trajectories = $(sol_mc_states.ntraj)\n" * - "num_states = $(length(sol_mc_states.states[1]))\n" * - "num_expect = 0\n" * - "ODE alg.: $(sol_mc_states.alg)\n" * - "abstol = $(sol_mc_states.abstol)\n" * - "reltol = $(sol_mc_states.reltol)\n" - @test sol_sse_string == - "Solution of stochastic quantum trajectories\n" * - "(converged: $(sol_sse.converged))\n" * - "--------------------------------\n" * - "num_trajectories = $(sol_sse.ntraj)\n" * - "num_states = $(length(sol_sse.states[1]))\n" * - "num_expect = $(size(sol_sse.expect, 1))\n" * - "SDE alg.: $(sol_sse.alg)\n" * - "abstol = $(sol_sse.abstol)\n" * - "reltol = $(sol_sse.reltol)\n" - @test sol_sme_string == - "Solution of stochastic quantum trajectories\n" * - "(converged: $(sol_sme.converged))\n" * - "--------------------------------\n" * - "num_trajectories = $(sol_sme.ntraj)\n" * - "num_states = $(length(sol_sme.states[1]))\n" * - "num_expect = $(size(sol_sme.expect, 1))\n" * - "SDE alg.: $(sol_sme.alg)\n" * - "abstol = $(sol_sme.abstol)\n" * - "reltol = $(sol_sme.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)) - @test_throws ArgumentError smesolve(H, ψ0, tlist1, c_ops_sme, sc_ops_sme, progress_bar = Val(false)) - @test_throws ArgumentError smesolve(H, ψ0, tlist2, c_ops_sme, sc_ops_sme, progress_bar = Val(false)) - @test_throws ArgumentError smesolve(H, ψ0, tlist3, c_ops_sme, sc_ops_sme, 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. - - ωd = 1.02 - F = 0.05 - - # Time Evolution in the drive frame - - H_dr_fr = H - ωd * a' * a - ωd * σz / 2 + F * (a + a') - - rng = MersenneTwister(12) - - tlist = range(0, 10 / γ, 1000) - - sol_se = sesolve(H_dr_fr, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) - sol_me = mesolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - sol_mc = mcsolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) - # sol_sse = ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) - - # Time Evolution in the lab frame - - coef1(p, t) = p.F * exp(1im * p.ωd * t) - coef2(p, t) = p.F * exp(-1im * p.ωd * t) - - H_td = (H, (a, coef1), (a', coef2)) - p = (F = F, ωd = ωd) - - sol_se_td = sesolve(H_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) - sol_me_td = mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) - sol_mc_td = mcsolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) - # sol_sse_td = ssesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) - - @test sol_se.expect ≈ sol_se_td.expect atol = 1e-6 * length(tlist) - @test sol_me.expect ≈ sol_me_td.expect atol = 1e-6 * length(tlist) - @test sol_mc.expect ≈ sol_mc_td.expect atol = 1e-2 * length(tlist) - # @test sol_sse.expect ≈ sol_sse_td.expect atol = 1e-2 * length(tlist) - - H_td2 = QobjEvo(H_td) - L_td = liouvillian(H_td2) - - sol_se_td2 = sesolve(H_td2, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) - sol_me_td2 = mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) - sol_mc_td2 = mcsolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) - # sol_sse_td2 = - # ssesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) - - @test sol_se.expect ≈ sol_se_td2.expect atol = 1e-6 * length(tlist) - @test sol_me.expect ≈ sol_me_td2.expect atol = 1e-6 * length(tlist) - @test sol_mc.expect ≈ sol_mc_td2.expect atol = 1e-2 * length(tlist) - # @test sol_sse.expect ≈ sol_sse_td2.expect atol = 1e-2 * length(tlist) - - @testset "Memory Allocations (mesolve)" begin - # We predefine the Liouvillian to avoid to count the allocations of the liouvillian function - L = liouvillian(H, c_ops) - L_td = QobjEvo((liouvillian(H, c_ops), (liouvillian(a), coef1), (liouvillian(a'), coef2))) - - allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) # Warm-up - allocs_tot = @allocations mesolve(L, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) - @test allocs_tot < 180 - - allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up - allocs_tot = @allocations mesolve(L, ψ0, tlist, saveat = [tlist[end]], progress_bar = Val(false)) - @test allocs_tot < 110 - - allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) # Warm-up - allocs_tot = @allocations mesolve(L_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) - @test allocs_tot < 180 - - allocs_tot = - @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) # Warm-up - allocs_tot = - @allocations mesolve(L_td, ψ0, tlist, progress_bar = Val(false), saveat = [tlist[end]], params = p) - @test allocs_tot < 110 - end - - @testset "Memory Allocations (mcsolve)" begin - ntraj = 100 - allocs_tot = - @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up - allocs_tot = - @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) - @test allocs_tot < 120 * ntraj + 400 # 150 allocations per trajectory + 500 for initialization - - allocs_tot = @allocations mcsolve( - H, - ψ0, - tlist, - c_ops, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations mcsolve( - H, - ψ0, - tlist, - c_ops, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) - @test allocs_tot < 110 * ntraj + 300 # 100 allocations per trajectory + 300 for initialization - end - - @testset "Memory Allocations (ssesolve)" begin - ntraj = 100 - allocs_tot = - @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up - allocs_tot = - @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) - @test allocs_tot < 1100 * ntraj + 400 # TODO: Fix this high number of allocations - - allocs_tot = @allocations ssesolve( - H, - ψ0, - tlist, - c_ops, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations ssesolve( - H, - ψ0, - tlist, - c_ops, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) - @test allocs_tot < 1000 * ntraj + 300 # TODO: Fix this high number of allocations - end - - @testset "Memory Allocations (smesolve)" begin - ntraj = 100 - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - e_ops = e_ops, - ntraj = ntraj, - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - e_ops = e_ops, - ntraj = ntraj, - progress_bar = Val(false), - ) - @test allocs_tot < 1100 * ntraj + 1800 # TODO: Fix this high number of allocations - - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) - @test allocs_tot < 1000 * ntraj + 1500 # TODO: Fix this high number of allocations - - # Diagonal Noise Case - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - e_ops = e_ops, - ntraj = ntraj, - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - e_ops = e_ops, - ntraj = 1, - progress_bar = Val(false), - ) - @test allocs_tot < 600 * ntraj + 1400 # TODO: Fix this high number of allocations - - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - ntraj = 1, - saveat = [tlist[end]], - progress_bar = Val(false), - ) - @test allocs_tot < 550 * ntraj + 1000 # TODO: Fix this high number of allocations - end - - @testset "Type Inference mesolve" begin - coef(p, t) = exp(-t) - ad_t = QobjEvo(a', coef) - @inferred mesolveProblem(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolveProblem(H, ψ0, [0, 10], c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolveProblem(H, ψ0_int, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) - @inferred mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) - @inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false)) - @inferred mesolve(H, ψ0, tlist, (a, ad_t), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple - @inferred mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) - @inferred mesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) - @inferred mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) - end - - @testset "Type Inference mcsolve" begin - @inferred mcsolveEnsembleProblem( - H, - ψ0, - tlist, - c_ops, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, e_ops = e_ops, progress_bar = Val(false), rng = rng) - @inferred mcsolve(H, ψ0, tlist, c_ops, ntraj = 5, progress_bar = Val(true), rng = rng) - @inferred mcsolve(H, ψ0, [0, 10], c_ops, ntraj = 5, progress_bar = Val(false), rng = rng) - @inferred mcsolve(H, ψ0_int, tlist, c_ops, ntraj = 5, progress_bar = Val(false), rng = rng) - @inferred mcsolve( - H, - ψ0, - tlist, - (a, a'), - e_ops = (a' * a, a'), - ntraj = 5, - progress_bar = Val(false), - rng = rng, - ) # We test the type inference for Tuple of different types - @inferred mcsolve( - H_td, - ψ0, - tlist, - c_ops, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - params = p, - rng = rng, - ) - end - - @testset "Type Inference ssesolve" begin - c_ops_tuple = Tuple(c_ops) # To avoid type instability, we must have a Tuple instead of a Vector - @inferred ssesolveEnsembleProblem( - H, - ψ0, - tlist, - c_ops_tuple, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - @inferred ssesolve( - H, - ψ0, - tlist, - c_ops_tuple, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - @inferred ssesolve(H, ψ0, tlist, c_ops_tuple, ntraj = 5, progress_bar = Val(true), rng = rng) - @inferred ssesolve(H, ψ0, [0, 10], c_ops_tuple, ntraj = 5, progress_bar = Val(false), rng = rng) - @inferred ssesolve(H, ψ0_int, tlist, c_ops_tuple, ntraj = 5, progress_bar = Val(false), rng = rng) - @inferred ssesolve( - H, - ψ0, - tlist, - c_ops_tuple, - ntraj = 5, - e_ops = (a' * a, a'), - progress_bar = Val(false), - rng = rng, - ) # We test the type inference for Tuple of different types - @inferred ssesolve( - H_td, - ψ0, - tlist, - c_ops_tuple, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - params = p, - rng = rng, - ) - end - - @testset "Type Inference smesolve" begin - # To avoid type instability, we must have a Tuple instead of a Vector - c_ops_sme_tuple = Tuple(c_ops_sme) - sc_ops_sme_tuple = Tuple(sc_ops_sme) - c_ops_sme2_tuple = Tuple(c_ops_sme2) - sc_ops_sme2_tuple = sc_ops_sme2 # This is an `AbstractQuantumObject` - @inferred smesolveEnsembleProblem( - H, - ψ0, - tlist, - c_ops_sme_tuple, - sc_ops_sme_tuple, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - @inferred smesolve( - H, - ψ0, - tlist, - c_ops_sme_tuple, - sc_ops_sme_tuple, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - @inferred smesolve( - H, - ψ0, - tlist, - c_ops_sme2_tuple, - sc_ops_sme2_tuple, - ntraj = 5, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - @inferred smesolve( - H, - ψ0, - tlist, - c_ops_sme_tuple, - sc_ops_sme_tuple, - ntraj = 5, - progress_bar = Val(true), - rng = rng, - ) - @inferred smesolve( - H, - ψ0, - [0, 10], - c_ops_sme_tuple, - sc_ops_sme_tuple, - ntraj = 5, - progress_bar = Val(false), - rng = rng, - ) - @inferred smesolve( - H, - ψ0_int, - tlist, - c_ops_sme_tuple, - sc_ops_sme_tuple, - ntraj = 5, - progress_bar = Val(false), - rng = rng, - ) - @inferred smesolve( - H, - ψ0, - tlist, - c_ops_sme_tuple, - sc_ops_sme_tuple, - ntraj = 5, - e_ops = (a' * a, a'), - progress_bar = Val(false), - rng = rng, - ) # We test the type inference for Tuple of different types - end - - @testset "mcsolve, ssesolve and smesolve reproducibility" begin - rng = MersenneTwister(1234) - sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) - rng = MersenneTwister(1234) - sol_sse1 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) - rng = MersenneTwister(1234) - sol_sme1 = smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - ntraj = 50, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - - rng = MersenneTwister(1234) - sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) - rng = MersenneTwister(1234) - sol_sse2 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) - rng = MersenneTwister(1234) - sol_sme2 = smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - ntraj = 50, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - - rng = MersenneTwister(1234) - sol_mc3 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) - rng = MersenneTwister(1234) - sol_sse3 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 60, e_ops = e_ops, progress_bar = Val(false), rng = rng) - rng = MersenneTwister(1234) - sol_sme3 = smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - ntraj = 60, - e_ops = e_ops, - progress_bar = Val(false), - rng = rng, - ) - - @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 - @test sol_mc1.runs_expect ≈ sol_mc2.runs_expect atol = 1e-10 - @test sol_mc1.col_times ≈ sol_mc2.col_times atol = 1e-10 - @test sol_mc1.col_which ≈ sol_mc2.col_which atol = 1e-10 - - @test sol_mc1.runs_expect ≈ sol_mc3.runs_expect[:, 1:500, :] atol = 1e-10 - - @test sol_sse1.expect ≈ sol_sse2.expect atol = 1e-10 - @test sol_sse1.runs_expect ≈ sol_sse2.runs_expect atol = 1e-10 - - @test sol_sse1.runs_expect ≈ sol_sse3.runs_expect[:, 1:50, :] atol = 1e-10 - - @test sol_sme1.expect ≈ sol_sme2.expect atol = 1e-10 - @test sol_sme1.runs_expect ≈ sol_sme2.runs_expect atol = 1e-10 - - @test sol_sme1.runs_expect ≈ sol_sme3.runs_expect[:, 1:50, :] atol = 1e-10 - end - end + # Diagonal Noise Case + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + e_ops = e_ops, + ntraj = 1, + progress_bar = Val(false), + ) + @test allocs_tot < 600 * ntraj + 1400 # TODO: Fix this high number of allocations - @testset "exceptions" begin - N = 10 - a = destroy(N) - H = a' * a - c_ops = [sqrt(0.1) * a] - psi0 = basis(N, 3) - t_l = LinRange(0, 100, 1000) - psi_wrong = basis(N - 1, 3) - @test_throws DimensionMismatch sesolve(H, psi_wrong, t_l) - @test_throws DimensionMismatch mesolve(H, psi_wrong, t_l, c_ops) - @test_throws DimensionMismatch mcsolve(H, psi_wrong, t_l, c_ops) - @test_throws ArgumentError sesolve(H, psi0, t_l, save_idxs = [1, 2]) - @test_throws ArgumentError mesolve(H, psi0, t_l, c_ops, save_idxs = [1, 2]) - @test_throws ArgumentError mcsolve(H, psi0, t_l, c_ops, save_idxs = [1, 2]) + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + ntraj = 1, + saveat = [tlist[end]], + progress_bar = Val(false), + ) + @test allocs_tot < 550 * ntraj + 1000 # TODO: Fix this high number of allocations end - @testset "example" begin - sp1 = kron(sigmap(), qeye(2)) - sm1 = sp1' - sx1 = sm1 + sp1 - sy1 = 1im * (sm1 - sp1) - sz1 = sp1 * sm1 - sm1 * sp1 - sp2 = kron(qeye(2), sigmap()) - sm2 = sp2' - sx2 = sm2 + sp2 - sy2 = 1im * (sm2 - sp2) - sz2 = sp2 * sm2 - sm2 * sp2 - ωq1, ωq2 = 1, 1 - γ1, γ2 = 0.05, 0.1 - H = 0.5 * ωq1 * sz1 + 0.5 * ωq2 * sz2 - c_ops = [sqrt(γ1) * sm1, sqrt(γ2) * sm2] - psi0_1 = normalize(fock(2, 0) + fock(2, 1)) - psi0_2 = normalize(fock(2, 0) + fock(2, 1)) - psi0 = kron(psi0_1, psi0_2) - t_l = LinRange(0, 20 / γ1, 1000) - sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = false) # Here we don't put Val(false) because we want to test the support for Bool type - sol_mc = mcsolve(H, psi0, t_l, c_ops, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = Val(false)) - @test sum(abs.(sol_mc.expect[1:2, :] .- sol_me.expect[1:2, :])) / length(t_l) < 0.1 - @test expect(sp1 * sm1, sol_me.states[end]) ≈ expect(sigmap() * sigmam(), ptrace(sol_me.states[end], 1)) + @testset "Type Inference (smesolve)" begin + a = TESetup.a + rng = TESetup.rng + + # To avoid type instability, we must have a Tuple instead of a Vector + c_ops_sme_tuple = Tuple(c_ops_sme) + sc_ops_sme_tuple = Tuple(sc_ops_sme) + c_ops_sme2_tuple = Tuple(c_ops_sme2) + sc_ops_sme2_tuple = sc_ops_sme2 # This is an `AbstractQuantumObject` + @inferred smesolveEnsembleProblem( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme2_tuple, + sc_ops_sme2_tuple, + ntraj = 5, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + progress_bar = Val(true), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + [0, 10], + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + TESetup.ψ0_int, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + progress_bar = Val(false), + rng = rng, + ) + @inferred smesolve( + H, + ψ0, + tlist, + c_ops_sme_tuple, + sc_ops_sme_tuple, + ntraj = 5, + e_ops = (a' * a, a'), + progress_bar = Val(false), + rng = rng, + ) # We test the type inference for Tuple of different types end end + +@testitem "Time-dependent Hamiltonian" setup=[TESetup] begin + # Get parameters from TESetup to simplify the code + ωd = TESetup.ωd + F = TESetup.F + a = TESetup.a + σz = TESetup.σz + H = TESetup.H + H_td = TESetup.H_td + H_td2 = TESetup.H_td2 + L_td = TESetup.L_td + ψ0 = TESetup.ψ0 + c_ops = TESetup.c_ops + e_ops = TESetup.e_ops + p = TESetup.p + rng = TESetup.rng + + # 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. + + # Time Evolution in the drive frame + + H_dr_fr = H - ωd * a' * a - ωd * σz / 2 + F * (a + a') + + tlist = range(0, 10 / TESetup.γ, 1000) + + sol_se = sesolve(H_dr_fr, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false)) + sol_me = mesolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false)) + sol_mc = mcsolve(H_dr_fr, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) + # sol_sse = ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + # Time Evolution in the lab frame + + sol_se_td = sesolve(H_td, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) + sol_me_td = mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + sol_mc_td = mcsolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + # sol_sse_td = ssesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + + @test sol_se.expect ≈ sol_se_td.expect atol = 1e-6 * length(tlist) + @test sol_me.expect ≈ sol_me_td.expect atol = 1e-6 * length(tlist) + @test sol_mc.expect ≈ sol_mc_td.expect atol = 1e-2 * length(tlist) + # @test sol_sse.expect ≈ sol_sse_td.expect atol = 1e-2 * length(tlist) + + sol_se_td2 = sesolve(H_td2, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p) + sol_me_td2 = mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p) + sol_mc_td2 = mcsolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + # sol_sse_td2 = + # ssesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p, rng = rng) + + @test sol_se.expect ≈ sol_se_td2.expect atol = 1e-6 * length(tlist) + @test sol_me.expect ≈ sol_me_td2.expect atol = 1e-6 * length(tlist) + @test sol_mc.expect ≈ sol_mc_td2.expect atol = 1e-2 * length(tlist) + # @test sol_sse.expect ≈ sol_sse_td2.expect atol = 1e-2 * length(tlist) +end + +@testitem "mcsolve, ssesolve and smesolve reproducibility" setup=[TESetup] begin + using Random + + # Get parameters from TESetup to simplify the code + H = TESetup.H + ψ0 = TESetup.ψ0 + tlist = TESetup.tlist + c_ops = TESetup.c_ops + c_ops_sme = TESetup.c_ops_sme + sc_ops_sme = TESetup.sc_ops_sme + e_ops = TESetup.e_ops + rng = TESetup.rng + + rng = MersenneTwister(1234) + sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sse1 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sme1 = + smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + rng = MersenneTwister(1234) + sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sse2 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sme2 = + smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + rng = MersenneTwister(1234) + sol_mc3 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sse3 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 60, e_ops = e_ops, progress_bar = Val(false), rng = rng) + rng = MersenneTwister(1234) + sol_sme3 = + smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 60, e_ops = e_ops, progress_bar = Val(false), rng = rng) + + @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 + @test sol_mc1.runs_expect ≈ sol_mc2.runs_expect atol = 1e-10 + @test sol_mc1.col_times ≈ sol_mc2.col_times atol = 1e-10 + @test sol_mc1.col_which ≈ sol_mc2.col_which atol = 1e-10 + + @test sol_mc1.runs_expect ≈ sol_mc3.runs_expect[:, 1:500, :] atol = 1e-10 + + @test sol_sse1.expect ≈ sol_sse2.expect atol = 1e-10 + @test sol_sse1.runs_expect ≈ sol_sse2.runs_expect atol = 1e-10 + + @test sol_sse1.runs_expect ≈ sol_sse3.runs_expect[:, 1:50, :] atol = 1e-10 + + @test sol_sme1.expect ≈ sol_sme2.expect atol = 1e-10 + @test sol_sme1.runs_expect ≈ sol_sme2.runs_expect atol = 1e-10 + + @test sol_sme1.runs_expect ≈ sol_sme3.runs_expect[:, 1:50, :] atol = 1e-10 +end + +@testitem "example" begin + sp1 = kron(sigmap(), qeye(2)) + sm1 = sp1' + sx1 = sm1 + sp1 + sy1 = 1im * (sm1 - sp1) + sz1 = sp1 * sm1 - sm1 * sp1 + sp2 = kron(qeye(2), sigmap()) + sm2 = sp2' + sx2 = sm2 + sp2 + sy2 = 1im * (sm2 - sp2) + sz2 = sp2 * sm2 - sm2 * sp2 + ωq1, ωq2 = 1, 1 + γ1, γ2 = 0.05, 0.1 + H = 0.5 * ωq1 * sz1 + 0.5 * ωq2 * sz2 + c_ops = [sqrt(γ1) * sm1, sqrt(γ2) * sm2] + psi0_1 = normalize(fock(2, 0) + fock(2, 1)) + psi0_2 = normalize(fock(2, 0) + fock(2, 1)) + psi0 = kron(psi0_1, psi0_2) + t_l = LinRange(0, 20 / γ1, 1000) + sol_me = mesolve(H, psi0, t_l, c_ops, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = false) # Here we don't put Val(false) because we want to test the support for Bool type + sol_mc = mcsolve(H, psi0, t_l, c_ops, e_ops = [sp1 * sm1, sp2 * sm2], progress_bar = Val(false)) + @test sum(abs.(sol_mc.expect[1:2, :] .- sol_me.expect[1:2, :])) / length(t_l) < 0.1 + @test expect(sp1 * sm1, sol_me.states[end]) ≈ expect(sigmap() * sigmam(), ptrace(sol_me.states[end], 1)) +end From 3057e3e002096e30b356fe88228c2edb58d888f8 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Thu, 17 Jul 2025 04:49:40 +0800 Subject: [PATCH 291/329] Fix errors in `Julia v1.12` (#507) --- .github/workflows/CI-Julia-nightly.yml | 1 + CHANGELOG.md | 2 ++ src/qobj/quantum_object_evo.jl | 8 ++++---- src/time_evolution/brmesolve.jl | 5 ++--- test/core-test/time_evolution.jl | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI-Julia-nightly.yml b/.github/workflows/CI-Julia-nightly.yml index 1e23477eb..e389ed2e8 100644 --- a/.github/workflows/CI-Julia-nightly.yml +++ b/.github/workflows/CI-Julia-nightly.yml @@ -58,3 +58,4 @@ jobs: - uses: julia-actions/julia-runtest@v1 env: GROUP: ${{ matrix.group }} + JULIA_NUM_THREADS: auto diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e1d0d804..5df138717 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 - Implement `EnrSpace` and corresponding functionality. ([#500]) - Check for orthogonality breakdown in `Lanczos` solver for `spectrum`. ([#501]) +- Fix errors in `Julia v1.12`. ([#507]) ## [v0.32.1] Release date: 2025-06-24 @@ -258,3 +259,4 @@ Release date: 2024-11-13 [#494]: https://github.com/qutip/QuantumToolbox.jl/issues/494 [#500]: https://github.com/qutip/QuantumToolbox.jl/issues/500 [#501]: https://github.com/qutip/QuantumToolbox.jl/issues/501 +[#507]: https://github.com/qutip/QuantumToolbox.jl/issues/507 diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index f1c4c58dc..a4111f7c7 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -381,7 +381,7 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` op = :(op_func_list[$i][1]) dims_expr = (dims_expr..., :($op.dimensions)) - func_methods_expr = (func_methods_expr..., :(methods(op_func_list[$i][2], [Any, Real]))) # [Any, Real] means each func must accept 2 arguments + func_methods_expr = (func_methods_expr..., :(methods(op_func_list[$i][2], [Any, Real]).ms)) # [Any, Real] means each func must accept 2 arguments if i == 1 first_op = :($op) end @@ -406,10 +406,10 @@ Parse the `op_func_list` and generate the data for the `QuantumObjectEvolution` # check if each func accepts 2 arguments func_methods = tuple($(func_methods_expr...)) - for f_method in func_methods - length(f_method.ms) == 0 && throw( + for i in eachindex(func_methods) + length(func_methods[i]) == 0 && throw( ArgumentError( - "The following function must accept two arguments: `$(f_method.mt.name)(p, t)` with t<:Real", + "The following function must only accept two arguments: `$(nameof(op_func_list[i][2]))(p, t)` with t<:Real", ), ) end diff --git a/src/time_evolution/brmesolve.jl b/src/time_evolution/brmesolve.jl index 05a47a2d3..cf2a15a25 100644 --- a/src/time_evolution/brmesolve.jl +++ b/src/time_evolution/brmesolve.jl @@ -189,8 +189,7 @@ function brmesolve( end function _check_br_spectra(f::Function) - meths = methods(f, [Real]) - length(meths.ms) == 0 && - throw(ArgumentError("The following function must accept one argument: `$(meths.mt.name)(ω)` with ω<:Real")) + length(methods(f, [Real]).ms) == 0 && + throw(ArgumentError("The following function must only accept one argument: `$(nameof(f))(ω)` with ω<:Real")) return nothing end diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 71642227c..883716405 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -568,7 +568,7 @@ end ntraj = ntraj, progress_bar = Val(false), ) - @test allocs_tot < 1100 * ntraj + 1800 # TODO: Fix this high number of allocations + @test allocs_tot < 1100 * ntraj + 2300 # TODO: Fix this high number of allocations allocs_tot = @allocations smesolve( H, From 5e1707df94a157d1bc80175e58303102991dae83 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Tue, 22 Jul 2025 12:15:24 +0800 Subject: [PATCH 292/329] Store both `times` and `times_states` in time evolution solutions (#506) --- CHANGELOG.md | 3 ++ .../src/users_guide/time_evolution/mcsolve.md | 24 +++++++------- .../src/users_guide/time_evolution/mesolve.md | 11 +++---- .../src/users_guide/time_evolution/sesolve.md | 13 +++++--- .../users_guide/time_evolution/solution.md | 14 ++++++-- src/correlations.jl | 4 +-- src/spectrum.jl | 2 +- src/time_evolution/brmesolve.jl | 2 +- src/time_evolution/lr_mesolve.jl | 14 +++++--- src/time_evolution/mcsolve.jl | 7 ++-- src/time_evolution/mesolve.jl | 5 +-- src/time_evolution/sesolve.jl | 5 +-- src/time_evolution/smesolve.jl | 7 ++-- src/time_evolution/ssesolve.jl | 7 ++-- src/time_evolution/time_evolution.jl | 33 ++++++++++++------- .../time_evolution_dynamical.jl | 1 + test/core-test/brmesolve.jl | 6 ++-- test/core-test/time_evolution.jl | 10 ++++++ 18 files changed, 105 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5df138717..d5c2c198f 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 - Implement `EnrSpace` and corresponding functionality. ([#500]) - Check for orthogonality breakdown in `Lanczos` solver for `spectrum`. ([#501]) +- Store both `times` and `times_states` in time evolution solutions. ([#506], [#504]) - Fix errors in `Julia v1.12`. ([#507]) ## [v0.32.1] @@ -259,4 +260,6 @@ Release date: 2024-11-13 [#494]: https://github.com/qutip/QuantumToolbox.jl/issues/494 [#500]: https://github.com/qutip/QuantumToolbox.jl/issues/500 [#501]: https://github.com/qutip/QuantumToolbox.jl/issues/501 +[#504]: https://github.com/qutip/QuantumToolbox.jl/issues/504 +[#506]: https://github.com/qutip/QuantumToolbox.jl/issues/506 [#507]: https://github.com/qutip/QuantumToolbox.jl/issues/507 diff --git a/docs/src/users_guide/time_evolution/mcsolve.md b/docs/src/users_guide/time_evolution/mcsolve.md index e5a3e0309..4f9aaa723 100644 --- a/docs/src/users_guide/time_evolution/mcsolve.md +++ b/docs/src/users_guide/time_evolution/mcsolve.md @@ -59,7 +59,7 @@ CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) ``` ```@example mcsolve -times = LinRange(0.0, 10.0, 200) +tlist = LinRange(0.0, 10.0, 200) ψ0 = tensor(fock(2, 0), fock(10, 8)) a = tensor(qeye(2), destroy(10)) @@ -69,7 +69,7 @@ H = 2 * π * a' * a + 2 * π * σm' * σm + 2 * π * 0.25 * (σm * a' + σm' * a c_ops = [sqrt(0.1) * a] e_ops = [a' * a, σm' * σm] -sol_500 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops) +sol_500 = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops) # plot by CairoMakie.jl fig = Figure(size = (500, 350)) @@ -78,8 +78,8 @@ ax = Axis(fig[1, 1], ylabel = "Expectation values", title = "Monte Carlo time evolution (500 trajectories)", ) -lines!(ax, times, real(sol_500.expect[1,:]), label = "cavity photon number", linestyle = :solid) -lines!(ax, times, real(sol_500.expect[2,:]), label = "atom excitation probability", linestyle = :dash) +lines!(ax, tlist, real(sol_500.expect[1,:]), label = "cavity photon number", linestyle = :solid) +lines!(ax, tlist, real(sol_500.expect[2,:]), label = "atom excitation probability", linestyle = :dash) axislegend(ax, position = :rt) @@ -93,9 +93,9 @@ We can also change the number of trajectories (`ntraj`). This can be used to exp ```@example mcsolve e_ops = [a' * a] -sol_1 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ntraj = 1) -sol_10 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ntraj = 10) -sol_100 = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ntraj = 100) +sol_1 = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj = 1) +sol_10 = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj = 10) +sol_100 = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj = 100) # plot by CairoMakie.jl fig = Figure(size = (500, 350)) @@ -104,9 +104,9 @@ ax = Axis(fig[1, 1], ylabel = "Expectation values", title = "Monte Carlo time evolution", ) -lines!(ax, times, real(sol_1.expect[1,:]), label = "1 trajectory", linestyle = :dashdot) -lines!(ax, times, real(sol_10.expect[1,:]), label = "10 trajectories", linestyle = :dash) -lines!(ax, times, real(sol_100.expect[1,:]), label = "100 trajectories", linestyle = :solid) +lines!(ax, tlist, real(sol_1.expect[1,:]), label = "1 trajectory", linestyle = :dashdot) +lines!(ax, tlist, real(sol_10.expect[1,:]), label = "10 trajectories", linestyle = :dash) +lines!(ax, tlist, real(sol_100.expect[1,:]), label = "100 trajectories", linestyle = :solid) axislegend(ax, position = :rt) @@ -126,8 +126,8 @@ Monte Carlo evolutions often need hundreds of trajectories to obtain sufficient See the [documentation of `DifferentialEquations.jl`](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/) for more details. Also, see Julia's documentation for more details about multithreading and adding more processes. ```julia -sol_serial = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ensemblealg=EnsembleSerial()) -sol_parallel = mcsolve(H, ψ0, times, c_ops, e_ops=e_ops, ensemblealg=EnsembleThreads()); +sol_serial = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ensemblealg=EnsembleSerial()) +sol_parallel = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ensemblealg=EnsembleThreads()); ``` !!! tip "Parallelization on a Cluster" diff --git a/docs/src/users_guide/time_evolution/mesolve.md b/docs/src/users_guide/time_evolution/mesolve.md index 9537d3bf8..dc5b3eb13 100644 --- a/docs/src/users_guide/time_evolution/mesolve.md +++ b/docs/src/users_guide/time_evolution/mesolve.md @@ -128,15 +128,14 @@ sol = mesolve(H, ψ0, tlist, c_ops, e_ops = [sigmaz(), sigmay()]) We can therefore plot the expectation values: ```@example mesolve -times = sol.times expt_z = real(sol.expect[1,:]) expt_y = real(sol.expect[2,:]) # plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") -lines!(ax, times, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) -lines!(ax, times, expt_y, label = L"\langle\hat{\sigma}_y\rangle", linestyle = :dash) +lines!(ax, tlist, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) +lines!(ax, tlist, expt_y, label = L"\langle\hat{\sigma}_y\rangle", linestyle = :dash) axislegend(ax, position = :rt) @@ -210,8 +209,6 @@ tlist = LinRange(0.0, 10.0, 200) L = liouvillian(H_a + H_c + H_I, c_ops) sol = mesolve(L, ψ0, tlist, e_ops=[σm' * σm, a' * a]) -times = sol.times - # expectation value of Number operator N_atom = real(sol.expect[1,:]) N_cavity = real(sol.expect[2,:]) @@ -219,8 +216,8 @@ N_cavity = real(sol.expect[2,:]) # plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") -lines!(ax, times, N_atom, label = "atom excitation probability", linestyle = :solid) -lines!(ax, times, N_cavity, label = "cavity photon number", linestyle = :dash) +lines!(ax, tlist, N_atom, label = "atom excitation probability", linestyle = :solid) +lines!(ax, tlist, N_cavity, label = "cavity photon number", linestyle = :dash) axislegend(ax, position = :rt) diff --git a/docs/src/users_guide/time_evolution/sesolve.md b/docs/src/users_guide/time_evolution/sesolve.md index d3ba8a21c..70372a7f0 100644 --- a/docs/src/users_guide/time_evolution/sesolve.md +++ b/docs/src/users_guide/time_evolution/sesolve.md @@ -59,8 +59,8 @@ sol = sesolve(H, ψ0, tlist, e_ops = [sigmaz(), sigmay()]) Here, we call [`sesolve`](@ref) directly instead of pre-defining [`sesolveProblem`](@ref) first (as shown previously). ```@example sesolve -times = sol.times -print(size(times)) +println(size(sol.times)) # time points corresponds to stored expectation values +println(size(sol.times_states)) # time points corresponds to stored states ``` ```@example sesolve @@ -77,8 +77,8 @@ expt_y = real(expt[2,:]) # plot by CairoMakie.jl fig = Figure(size = (500, 350)) ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values") -lines!(ax, times, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) -lines!(ax, times, expt_y, label = L"\langle\hat{\sigma}_y\rangle", linestyle = :dash) +lines!(ax, tlist, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid) +lines!(ax, tlist, expt_y, label = L"\langle\hat{\sigma}_y\rangle", linestyle = :dash) axislegend(ax, position = :rb) @@ -91,6 +91,9 @@ If the keyword argument `e_ops` is not specified (or given as an empty `Vector`) tlist = [0, 10] sol = sesolve(H, ψ0, tlist) # or specify: e_ops = [] +println(size(sol.times)) +println(size(sol.times_states)) + sol.states ``` @@ -104,9 +107,11 @@ sol = sesolve(H, ψ0, tlist, e_ops = [sigmay()], saveat = tlist) ``` ```@example sesolve +println(size(sol.times)) sol.expect ``` ```@example sesolve +println(size(sol.times_states)) sol.states ``` diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index b6a694837..94609f18a 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -12,8 +12,9 @@ CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) | **Fields (Attributes)** | **Description** | |:------------------------|:----------------| -| `sol.times` | The time list of the evolution. | -| `sol.states` | The list of result states. | +| `sol.times` | The list of time points at which the expectation values are calculated during the evolution. | +| `sol.times_states` | The list of time points at which the states are stored during the evolution. | +| `sol.states` | The list of result states corresponding to each time point in `sol.times_states`. | | `sol.expect` | The expectation values corresponding to each time point in `sol.times`. | | `sol.alg` | The algorithm which is used during the solving process. | | `sol.abstol` | The absolute tolerance which is used during the solving process. | @@ -54,7 +55,7 @@ nothing # hide Recall that `Julia` uses `Fortran`-style indexing that begins with one (i.e., `[1,:]` represents the 1-st observable, where `:` represents all values corresponding to `tlist`). -Together with the array of times at which these expectation values are calculated: +Together with the list of time points at which these expectation values are calculated: ```@example TE-solution times = sol.times @@ -83,6 +84,13 @@ State vectors, or density matrices, are accessed in a similar manner: sol.states ``` +Together with the list of time points at which these states are stored: + +```@example TE-solution +times = sol.times_states +nothing # hide +``` + Here, the solution contains only one (final) state. Because the `states` will be saved depend on the keyword argument `saveat` in `kwargs`. If `e_ops` is empty, the default value of `saveat=tlist` (saving the states corresponding to `tlist`), otherwise, `saveat=[tlist[end]]` (only save the final state). One can also specify `e_ops` and `saveat` separately. Some other solvers can have other output. diff --git a/src/correlations.jl b/src/correlations.jl index 3eae092b5..a8ac92f27 100644 --- a/src/correlations.jl +++ b/src/correlations.jl @@ -19,7 +19,7 @@ end C::QuantumObject; kwargs...) -Returns the two-times correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \hat{C}(t) \right\rangle`` for a given initial state ``|\psi_0\rangle``. +Returns the two-time correlation function of three operators ``\hat{A}``, ``\hat{B}`` and ``\hat{C}``: ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \hat{C}(t) \right\rangle`` for a given initial state ``|\psi_0\rangle``. If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). """ @@ -96,7 +96,7 @@ end reverse::Bool=false, kwargs...) -Returns the two-times correlation function of two operators ``\hat{A}`` and ``\hat{B}`` : ``\left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle`` for a given initial state ``|\psi_0\rangle``. +Returns the two-time correlation function of two operators ``\hat{A}`` and ``\hat{B}`` : ``\left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle`` for a given initial state ``|\psi_0\rangle``. If the initial state `ψ0` is given as `nothing`, then the [`steadystate`](@ref) will be used as the initial state. Note that this is only implemented if `H` is constant ([`QuantumObject`](@ref)). diff --git a/src/spectrum.jl b/src/spectrum.jl index b97488575..9a6313ad4 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -313,7 +313,7 @@ end Calculate the power spectrum corresponding to a two-time correlation function using fast Fourier transform (FFT). # Parameters -- `tlist::AbstractVector`: List of times at which the two-time correlation function is given. +- `tlist::AbstractVector`: List of time points at which the two-time correlation function is given. - `corr::AbstractVector`: List of two-time correlations corresponding to the given time point in `tlist`. - `inverse::Bool`: Whether to use the inverse Fourier transform or not. Default to `false`. diff --git a/src/time_evolution/brmesolve.jl b/src/time_evolution/brmesolve.jl index cf2a15a25..85ffb2594 100644 --- a/src/time_evolution/brmesolve.jl +++ b/src/time_evolution/brmesolve.jl @@ -158,7 +158,7 @@ Solves for the dynamics of a system using the Bloch-Redfield master equation, gi - `H`: The system Hamiltonian. Must be an [`Operator`](@ref) - `ψ0`: Initial state of the system $|\psi(0)\rangle$. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `a_ops`: Nested list with each element is a `Tuple` of operator-function pairs `(a_op, spectra)`, and the coupling [`Operator`](@ref) `a_op` must be hermitian with corresponding `spectra` being a `Function` of transition energy - `c_ops`: List of collapse operators corresponding to Lindblad dissipators - `sec_cutoff`: Cutoff for secular approximation. Use `-1` if secular approximation is not used when evaluating bath-coupling terms. diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index bc166a540..1c73010e1 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -7,10 +7,11 @@ A structure storing the results and some information from solving low-rank maste # Fields (Attributes) -- `times::AbstractVector`: The time list of the evolution. -- `states::Vector{QuantumObject}`: The list of result states. +- `times::AbstractVector`: The list of time points at which the expectation values are calculated during the evolution. +- `times_states::AbstractVector`: The list of time points at which the states are stored during the evolution. +- `states::Vector{QuantumObject}`: The list of result states corresponding to each time point in `times_states`. - `expect::Matrix`: The expectation values corresponding to each time point in `times`. -- `fexpect::Matrix`: The function values at each time point. +- `fexpect::Matrix`: The function values corresponding to each time point in `times`. - `retcode`: The return code from the solver. - `alg`: The algorithm which is used during the solving process. - `abstol::Real`: The absolute tolerance which is used during the solving process. @@ -19,7 +20,8 @@ A structure storing the results and some information from solving low-rank maste - `B::Vector{QuantumObject}`: The `B` matrix of the low-rank algorithm at each time point. """ struct TimeEvolutionLRSol{ - TT<:AbstractVector{<:Real}, + TT1<:AbstractVector{<:Real}, + TT2<:AbstractVector{<:Real}, TS<:AbstractVector, TE<:Matrix{ComplexF64}, RetT<:Enum, @@ -29,7 +31,8 @@ struct TimeEvolutionLRSol{ TSZB<:AbstractVector, TM<:Vector{<:Integer}, } - times::TT + times::TT1 + times_states::TT2 states::TS expect::TE fexpect::TE @@ -549,6 +552,7 @@ function lr_mesolve(prob::ODEProblem; kwargs...) ρt = map(x -> Qobj(x[1] * x[2] * x[1]', type = Operator(), dims = prob.p.Hdims), zip(zt, Bt)) return TimeEvolutionLRSol( + prob.p.times, sol.t, ρt, prob.p.expvals, diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index 7db7048ff..fe8da0876 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -89,7 +89,7 @@ If the environmental measurements register a quantum jump, the wave function und - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. @@ -195,7 +195,7 @@ If the environmental measurements register a quantum jump, the wave function und - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. @@ -320,7 +320,7 @@ If the environmental measurements register a quantum jump, the wave function und - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm to use for the ODE solver. Default to `Tsit5()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. @@ -412,6 +412,7 @@ function mcsolve( return TimeEvolutionMCSol( ntraj, ens_prob_mc.times, + _sol_1.t, states, expvals, expvals, # This is average_expect diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 32305cb1b..85d6ed613 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -34,7 +34,7 @@ where - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. @@ -132,7 +132,7 @@ where - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_n\}_n``. It can be either a `Vector` or a `Tuple`. - `alg`: The algorithm for the ODE solver. The default value is `Tsit5()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. @@ -206,6 +206,7 @@ function mesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit return TimeEvolutionSol( prob.times, + sol.t, ρt, _get_expvals(sol, SaveFuncMESolve), sol.retcode, diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index c5607f231..ef9d98f9b 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -30,7 +30,7 @@ Generate the ODEProblem for the Schrödinger time evolution of a quantum system: - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. @@ -104,7 +104,7 @@ Time evolution of a closed quantum system using the Schrödinger equation: - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `alg`: The algorithm for the ODE solver. The default is `Tsit5()`. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: Parameters to pass to the solver. This argument is usually expressed as a `NamedTuple` or `AbstractVector` of parameters. For more advanced usage, any custom struct can be used. @@ -156,6 +156,7 @@ function sesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit return TimeEvolutionSol( prob.times, + sol.t, ψt, _get_expvals(sol, SaveFuncSESolve), sol.retcode, diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 710029197..aa6dbae9b 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -49,7 +49,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. @@ -194,7 +194,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. @@ -321,7 +321,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref). -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `c_ops`: List of collapse operators ``\{\hat{C}_i\}_i``. It can be either a `Vector` or a `Tuple`. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `alg`: The algorithm to use for the stochastic differential equation. Default is `SRIW1()` if `sc_ops` is an [`AbstractQuantumObject`](@ref) (diagonal noise), and `SRA2()` otherwise (non-diagonal noise). @@ -425,6 +425,7 @@ function smesolve( return TimeEvolutionStochasticSol( ntraj, ens_prob.times, + _sol_1.t, states, expvals, expvals, # This is average_expect diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 6945dc81e..e68987c02 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -51,7 +51,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. @@ -189,7 +189,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is t - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. - `params`: `NullParameters` of parameters to pass to the solver. @@ -317,7 +317,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `H`: Hamiltonian of the system ``\hat{H}``. It can be either a [`QuantumObject`](@ref), a [`QuantumObjectEvolution`](@ref), or a `Tuple` of operator-function pairs. - `ψ0`: Initial state of the system ``|\psi(0)\rangle``. -- `tlist`: List of times at which to save either the state or the expectation values of the system. +- `tlist`: List of time points at which to save either the state or the expectation values of the system. - `sc_ops`: List of stochastic collapse operators ``\{\hat{S}_n\}_n``. It can be either a `Vector`, a `Tuple` or a [`AbstractQuantumObject`](@ref). It is recommended to use the last case when only one operator is provided. - `alg`: The algorithm to use for the stochastic differential equation. Default is `SRIW1()` if `sc_ops` is an [`AbstractQuantumObject`](@ref) (diagonal noise), and `SRA2()` otherwise (non-diagonal noise). - `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`. @@ -419,6 +419,7 @@ function ssesolve( return TimeEvolutionStochasticSol( ntraj, ens_prob.times, + _sol_1.t, states, expvals, expvals, # This is average_expect diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index 0116ff398..bfdd6942b 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -46,8 +46,9 @@ A structure storing the results and some information from solving time evolution # Fields (Attributes) -- `times::AbstractVector`: The time list of the evolution. -- `states::Vector{QuantumObject}`: The list of result states. +- `times::AbstractVector`: The list of time points at which the expectation values are calculated during the evolution. +- `times_states::AbstractVector`: The list of time points at which the states are stored during the evolution. +- `states::Vector{QuantumObject}`: The list of result states corresponding to each time point in `times_states`. - `expect::Union{AbstractMatrix,Nothing}`: The expectation values corresponding to each time point in `times`. - `retcode`: The return code from the solver. - `alg`: The algorithm which is used during the solving process. @@ -55,7 +56,8 @@ A structure storing the results and some information from solving time evolution - `reltol::Real`: The relative tolerance which is used during the solving process. """ struct TimeEvolutionSol{ - TT<:AbstractVector{<:Real}, + TT1<:AbstractVector{<:Real}, + TT2<:AbstractVector{<:Real}, TS<:AbstractVector, TE<:Union{AbstractMatrix,Nothing}, RETT<:Enum, @@ -63,7 +65,8 @@ struct TimeEvolutionSol{ AT<:Real, RT<:Real, } - times::TT + times::TT1 + times_states::TT2 states::TS expect::TE retcode::RETT @@ -96,8 +99,9 @@ A structure storing the results and some information from solving quantum trajec # Fields (Attributes) - `ntraj::Int`: Number of trajectories -- `times::AbstractVector`: The time list of the evolution. -- `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. +- `times::AbstractVector`: The list of time points at which the expectation values are calculated during the evolution. +- `times_states::AbstractVector`: The list of time points at which the states are stored during the evolution. +- `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory and each time point in `times_states`. - `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. - `average_expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. - `runs_expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` @@ -109,7 +113,8 @@ A structure storing the results and some information from solving quantum trajec - `reltol::Real`: The relative tolerance which is used during the solving process. """ struct TimeEvolutionMCSol{ - TT<:AbstractVector{<:Real}, + TT1<:AbstractVector{<:Real}, + TT2<:AbstractVector{<:Real}, TS<:AbstractVector, TE<:Union{AbstractMatrix,Nothing}, TEA<:Union{AbstractArray,Nothing}, @@ -120,7 +125,8 @@ struct TimeEvolutionMCSol{ RT<:Real, } ntraj::Int - times::TT + times::TT1 + times_states::TT2 states::TS expect::TE average_expect::TE # Currently just a synonym for `expect` @@ -158,8 +164,9 @@ A structure storing the results and some information from solving trajectories o # Fields (Attributes) - `ntraj::Int`: Number of trajectories -- `times::AbstractVector`: The time list of the evolution. -- `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory. +- `times::AbstractVector`: The list of time points at which the expectation values are calculated during the evolution. +- `times_states::AbstractVector`: The list of time points at which the states are stored during the evolution. +- `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory and each time point in `times_states`. - `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. - `average_expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. - `runs_expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` @@ -169,7 +176,8 @@ A structure storing the results and some information from solving trajectories o - `reltol::Real`: The relative tolerance which is used during the solving process. """ struct TimeEvolutionStochasticSol{ - TT<:AbstractVector{<:Real}, + TT1<:AbstractVector{<:Real}, + TT2<:AbstractVector{<:Real}, TS<:AbstractVector, TE<:Union{AbstractMatrix,Nothing}, TEA<:Union{AbstractArray,Nothing}, @@ -179,7 +187,8 @@ struct TimeEvolutionStochasticSol{ RT<:Real, } ntraj::Int - times::TT + times::TT1 + times_states::TT2 states::TS expect::TE average_expect::TE # Currently just a synonym for `expect` diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 2e07047da..98960764e 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -244,6 +244,7 @@ function dfd_mesolve( return TimeEvolutionSol( dfd_prob.times, + sol.t, ρt, _get_expvals(sol, SaveFuncMESolve), sol.retcode, diff --git a/test/core-test/brmesolve.jl b/test/core-test/brmesolve.jl index 68cfad8c1..63b6ba02c 100644 --- a/test/core-test/brmesolve.jl +++ b/test/core-test/brmesolve.jl @@ -104,11 +104,11 @@ end e_ops = pauli_vectors H = δ * 0.5 * sigmax() + ϵ * 0.5 * sigmaz() ψ0 = unit(2basis(2, 0) + basis(2, 1)) - times = LinRange(0, 10, 100) + tlist = LinRange(0, 10, 100) for (me_c_ops, brme_c_ops, brme_a_ops) in arg_sets - me = mesolve(H, ψ0, times, me_c_ops, e_ops = e_ops, progress_bar = Val(false)) - brme = brmesolve(H, ψ0, times, brme_a_ops, brme_c_ops, e_ops = e_ops, progress_bar = Val(false)) + me = mesolve(H, ψ0, tlist, me_c_ops, e_ops = e_ops, progress_bar = Val(false)) + brme = brmesolve(H, ψ0, tlist, brme_a_ops, brme_c_ops, e_ops = e_ops, progress_bar = Val(false)) @test all(me.expect .== brme.expect) end diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 883716405..e17db89c2 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -85,12 +85,15 @@ end @test prob.prob.f.f isa MatrixOperator @test sum(abs.(sol.expect[1, :] .- amp_rabi .* sin.(Ω_rabi * tlist) .^ 2)) / length(tlist) < 0.1 @test length(sol.times) == length(tlist) + @test length(sol.times_states) == 1 @test length(sol.states) == 1 @test size(sol.expect) == (length(e_ops), length(tlist)) @test length(sol2.times) == length(tlist) + @test length(sol2.times_states) == length(tlist) @test length(sol2.states) == length(tlist) @test sol2.expect === nothing @test length(sol3.times) == length(tlist) + @test length(sol3.times_states) == length(saveat) @test length(sol3.states) == length(saveat) @test size(sol3.expect) == (length(e_ops), length(tlist)) @test sol.expect[1, saveat_idxs] ≈ expect(e_ops[1], sol3.states) atol = 1e-6 @@ -168,12 +171,15 @@ end @test TESetup.prob_me.prob.f.f isa MatrixOperator @test isket(sol_me5.states[1]) @test length(sol_me.times) == length(tlist) + @test length(sol_me.times_states) == 1 @test length(sol_me.states) == 1 @test size(sol_me.expect) == (length(e_ops), length(tlist)) @test length(sol_me2.times) == length(tlist) + @test length(sol_me2.times_states) == length(tlist) @test length(sol_me2.states) == length(tlist) @test sol_me2.expect === nothing @test length(sol_me3.times) == length(tlist) + @test length(sol_me3.times_states) == length(saveat) @test length(sol_me3.states) == length(saveat) @test size(sol_me3.expect) == (length(e_ops), length(tlist)) @test sol_me3.expect[1, TESetup.saveat_idxs] ≈ expect(e_ops[1], sol_me3.states) atol = 1e-6 @@ -289,8 +295,10 @@ end @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 @test length(sol_mc.times) == length(tlist) + @test length(sol_mc.times_states) == 1 @test size(sol_mc.expect) == (length(e_ops), length(tlist)) @test length(sol_mc_states.times) == length(tlist) + @test length(sol_mc_states.times_states) == length(saveat) @test sol_mc_states.expect === nothing sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) @@ -391,6 +399,7 @@ end @test sum(abs, sol_sse.expect .- sol_me.expect) / length(tlist) < 0.1 @test length(sol_sse.times) == length(tlist) + @test length(sol_sse.times_states) == 1 @test size(sol_sse.expect) == (length(e_ops), length(tlist)) @test isnothing(sol_sse.measurement) @test size(sol_sse2.measurement) == (length(c_ops), 20, length(tlist) - 1) @@ -525,6 +534,7 @@ end @test sum(abs, sol_sme.expect .- sol_me.expect) / length(tlist) < 0.1 @test sum(abs, sol_sme3.expect .- sol_me.expect) / length(tlist) < 0.1 @test length(sol_sme.times) == length(tlist) + @test length(sol_sme.times_states) == 1 @test size(sol_sme.expect) == (length(e_ops), length(tlist)) @test isnothing(sol_sme.measurement) @test size(sol_sme2.measurement) == (length(sc_ops_sme), 20, length(tlist) - 1) From bc76e9f90b190fe6bd1a45f403ccbbe45539abf9 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:24:53 -0400 Subject: [PATCH 293/329] Bump to v0.33.0 (#508) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c2c198f..ac58b3409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.33.0] +Release date: 2025-07-22 + - Implement `EnrSpace` and corresponding functionality. ([#500]) - Check for orthogonality breakdown in `Lanczos` solver for `spectrum`. ([#501]) - Store both `times` and `times_states` in time evolution solutions. ([#506], [#504]) @@ -183,6 +186,7 @@ Release date: 2024-11-13 [v0.31.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.1 [v0.32.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.0 [v0.32.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.1 +[v0.33.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.33.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 5d2435573..8b33fbb74 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.32.1" +version = "0.33.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 52b9148dadd541fe4035f356b2aa17d9fa1d730d Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Fri, 25 Jul 2025 11:43:51 +0800 Subject: [PATCH 294/329] [Docs] minor changes in sesolve doc (#511) --- docs/src/users_guide/time_evolution/sesolve.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/src/users_guide/time_evolution/sesolve.md b/docs/src/users_guide/time_evolution/sesolve.md index 70372a7f0..7e4b3cb1e 100644 --- a/docs/src/users_guide/time_evolution/sesolve.md +++ b/docs/src/users_guide/time_evolution/sesolve.md @@ -93,7 +93,9 @@ sol = sesolve(H, ψ0, tlist) # or specify: e_ops = [] println(size(sol.times)) println(size(sol.times_states)) +``` +```@example sesolve sol.states ``` @@ -107,11 +109,17 @@ sol = sesolve(H, ψ0, tlist, e_ops = [sigmay()], saveat = tlist) ``` ```@example sesolve -println(size(sol.times)) +print(size(sol.times)) +``` + +```@example sesolve sol.expect ``` ```@example sesolve -println(size(sol.times_states)) +print(size(sol.times_states)) +``` + +```@example sesolve sol.states ``` From 7a6cc5034c47956f95668fecb7430cc920bf5092 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Fri, 25 Jul 2025 21:44:30 -0400 Subject: [PATCH 295/329] Improve efficiency of generation of Bloch-Redfield tensor and fix documentation typos (#509) --- CHANGELOG.md | 3 + docs/src/users_guide/settings.md | 3 +- .../users_guide/time_evolution/brmesolve.md | 6 +- src/time_evolution/brmesolve.jl | 81 +++++++++++++------ test/core-test/brmesolve.jl | 17 ++-- 5 files changed, 74 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac58b3409..71fe85710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Improve efficiency of `bloch_redfield_tensor` by avoiding unnecessary conversions. ([#509]) + ## [v0.33.0] Release date: 2025-07-22 @@ -267,3 +269,4 @@ Release date: 2024-11-13 [#504]: https://github.com/qutip/QuantumToolbox.jl/issues/504 [#506]: https://github.com/qutip/QuantumToolbox.jl/issues/506 [#507]: https://github.com/qutip/QuantumToolbox.jl/issues/507 +[#509]: https://github.com/qutip/QuantumToolbox.jl/issues/509 diff --git a/docs/src/users_guide/settings.md b/docs/src/users_guide/settings.md index be8d914e9..c2c530223 100644 --- a/docs/src/users_guide/settings.md +++ b/docs/src/users_guide/settings.md @@ -13,7 +13,8 @@ Here, we list out each setting along with the specific functions that will use i - `tidyup_tol::Float64 = 1e-14` : tolerance for [`tidyup`](@ref) and [`tidyup!`](@ref). - `auto_tidyup::Bool = true` : Automatically tidyup during the following situations: - * Solving for eigenstates, including [`eigenstates`](@ref), [`eigsolve`](@ref), and [`eigsolve_al`](@ref). + * Solving for eigenstates, including [`eigenstates`](@ref), [`eigsolve`](@ref), [`eigsolve_al`](@ref) + * Creating [`bloch_redfield_tensor`](@ref) or [`brterm`](@ref), and solving [`brmesolve`](@ref). - (to be announced) ## Change default settings diff --git a/docs/src/users_guide/time_evolution/brmesolve.md b/docs/src/users_guide/time_evolution/brmesolve.md index c7a46e612..681463d60 100644 --- a/docs/src/users_guide/time_evolution/brmesolve.md +++ b/docs/src/users_guide/time_evolution/brmesolve.md @@ -162,7 +162,7 @@ H = -Δ/2.0 * sigmax() - ε0/2 * sigmaz() ohmic_spectrum(ω) = (ω == 0.0) ? γ1 : γ1 / 2 * (ω / (2 * π)) * (ω > 0.0) -R, U = bloch_redfield_tensor(H, [(sigmax(), ohmic_spectrum)]) +R, U = bloch_redfield_tensor(H, ((sigmax(), ohmic_spectrum), )) R ``` @@ -183,6 +183,8 @@ H = - Δ/2.0 * sigmax() - ϵ0/2.0 * sigmaz() ohmic_spectrum(ω) = (ω == 0.0) ? γ1 : γ1 / 2 * (ω / (2 * π)) * (ω > 0.0) a_ops = ((sigmax(), ohmic_spectrum),) +R = bloch_redfield_tensor(H, a_ops; fock_basis = Val(true)) + e_ops = [sigmax(), sigmay(), sigmaz()] # same initial random ket state in QuTiP doc @@ -192,7 +194,7 @@ e_ops = [sigmax(), sigmay(), sigmaz()] ]) tlist = LinRange(0, 15.0, 1000) -sol = brmesolve(H, ψ0, tlist, a_ops, e_ops=e_ops) +sol = mesolve(R, ψ0, tlist, e_ops=e_ops) expt_list = real(sol.expect) # plot the evolution of state on Bloch sphere diff --git a/src/time_evolution/brmesolve.jl b/src/time_evolution/brmesolve.jl index 85ffb2594..1ef3720aa 100644 --- a/src/time_evolution/brmesolve.jl +++ b/src/time_evolution/brmesolve.jl @@ -37,18 +37,28 @@ function bloch_redfield_tensor( U = QuantumObject(rst.vectors, Operator(), H.dimensions) sec_cutoff = float(sec_cutoff) - # in fock basis - R0 = liouvillian(H, c_ops) + H_new = getVal(fock_basis) ? H : QuantumObject(Diagonal(rst.values), Operator(), H.dimensions) + c_ops_new = isnothing(c_ops) ? nothing : map(x -> getVal(fock_basis) ? x : U' * x * U, c_ops) + L0 = liouvillian(H_new, c_ops_new) - # set fock_basis=Val(false) and change basis together at the end - R1 = 0 - isempty(a_ops) || (R1 += mapreduce(x -> _brterm(rst, x[1], x[2], sec_cutoff, Val(false)), +, a_ops)) + # Check whether we can rotate the terms to the eigenbasis directly in the Hamiltonian space + fock_basis_hamiltonian = getVal(fock_basis) && sec_cutoff == -1 - SU = sprepost(U, U') # transformation matrix from eigen basis back to fock basis + R = isempty(a_ops) ? 0 : sum(x -> _brterm(rst, x[1], x[2], sec_cutoff, fock_basis_hamiltonian), a_ops) + + # If in fock basis, we need to transform the terms back to the fock basis + # Note: we can transform the terms in the Hamiltonian space only if sec_cutoff is -1 + # otherwise, we need to use the SU superoperator below to transform the entire Liouvillian + # at the end, due to the action of M_cut if getVal(fock_basis) - return R0 + SU * R1 * SU' + if fock_basis_hamiltonian + return L0 + R # Already rotated in the Hamiltonian space + else + SU = sprepost(U, U') + return L0 + SU * R * SU' + end else - return SU' * R0 * SU + R1, U + return L0 + R, U end end @@ -86,11 +96,21 @@ function brterm( fock_basis::Union{Bool,Val} = Val(false), ) rst = eigenstates(H) - term = _brterm(rst, a_op, spectra, sec_cutoff, makeVal(fock_basis)) + U = QuantumObject(rst.vectors, Operator(), H.dimensions) + + # Check whether we can rotate the terms to the eigenbasis directly in the Hamiltonian space + fock_basis_hamiltonian = getVal(fock_basis) && sec_cutoff == -1 + + term = _brterm(rst, a_op, spectra, sec_cutoff, fock_basis_hamiltonian) if getVal(fock_basis) - return term + if fock_basis_hamiltonian + return term # Already rotated in the Hamiltonian space + else + SU = sprepost(U, U') + return SU * term * SU' + end else - return term, Qobj(rst.vectors, Operator(), rst.dimensions) + return term, U end end @@ -99,7 +119,7 @@ function _brterm( a_op::T, spectra::F, sec_cutoff::Real, - fock_basis::Union{Val{true},Val{false}}, + fock_basis_hamiltonian::Union{Bool,Val}, ) where {T<:QuantumObject{Operator},F<:Function} _check_br_spectra(spectra) @@ -110,9 +130,11 @@ function _brterm( spectrum = spectra.(skew) A_mat = U' * a_op.data * U + A_mat_spec = A_mat .* spectrum + A_mat_spec_t = A_mat .* transpose(spectrum) - ac_term = (A_mat .* spectrum) * A_mat - bd_term = A_mat * (A_mat .* trans(spectrum)) + ac_term = A_mat_spec * A_mat + bd_term = A_mat * A_mat_spec_t if sec_cutoff != -1 m_cut = similar(skew) @@ -124,20 +146,29 @@ function _brterm( M_cut = @. abs(vec_skew - vec_skew') < sec_cutoff end - out = - 0.5 * ( - + _sprepost(A_mat .* trans(spectrum), A_mat) + _sprepost(A_mat, A_mat .* spectrum) - _spost(ac_term, Id) - - _spre(bd_term, Id) - ) + # Rotate the terms to the eigenbasis if possible + if getVal(fock_basis_hamiltonian) + A_mat = U * A_mat * U' + A_mat_spec = U * A_mat_spec * U' + A_mat_spec_t = U * A_mat_spec_t * U' + ac_term = U * ac_term * U' + bd_term = U * bd_term * U' + end + + # Remove small values before passing in the Liouville space + if settings.auto_tidyup + tidyup!(A_mat) + tidyup!(A_mat_spec) + tidyup!(A_mat_spec_t) + tidyup!(ac_term) + tidyup!(bd_term) + end + + out = (_sprepost(A_mat_spec_t, A_mat) + _sprepost(A_mat, A_mat_spec) - _spost(ac_term, Id) - _spre(bd_term, Id)) / 2 (sec_cutoff != -1) && (out .*= M_cut) - if getVal(fock_basis) - SU = _sprepost(U, U') - return QuantumObject(SU * out * SU', SuperOperator(), rst.dimensions) - else - return QuantumObject(out, SuperOperator(), rst.dimensions) - end + return QuantumObject(out, SuperOperator(), rst.dimensions) end @doc raw""" diff --git a/test/core-test/brmesolve.jl b/test/core-test/brmesolve.jl index 63b6ba02c..2ac87a0b1 100644 --- a/test/core-test/brmesolve.jl +++ b/test/core-test/brmesolve.jl @@ -5,8 +5,9 @@ A_op = a+a' spectra(x) = (x>0) * 0.5 for sec_cutoff in [0, 0.1, 1, 3, -1] - R = bloch_redfield_tensor(H, [(A_op, spectra)], [a^2], sec_cutoff = sec_cutoff, fock_basis = true) - R_eig, evecs = bloch_redfield_tensor(H, [(A_op, spectra)], [a^2], sec_cutoff = sec_cutoff, fock_basis = false) + R = bloch_redfield_tensor(H, ((A_op, spectra),), [a^2], sec_cutoff = sec_cutoff, fock_basis = Val(true)) + R_eig, evecs = + bloch_redfield_tensor(H, ((A_op, spectra),), [a^2], sec_cutoff = sec_cutoff, fock_basis = Val(false)) @test isa(R, QuantumObject) @test isa(R_eig, QuantumObject) @test isa(evecs, QuantumObject) @@ -27,7 +28,7 @@ end # this test applies for limited cutoff lindblad = lindblad_dissipator(a) - computation = brterm(H, A_op, spectra, sec_cutoff = 1.5, fock_basis = true) + computation = brterm(H, A_op, spectra, sec_cutoff = 1.5, fock_basis = Val(true)) @test isapprox(lindblad, computation, atol = 1e-15) end @@ -38,8 +39,8 @@ end A_op = a+a' spectra(x) = x>0 for sec_cutoff in [0, 0.1, 1, 3, -1] - R = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = true) - R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = false) + R = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = Val(true)) + R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = Val(false)) @test isa(R, QuantumObject) @test isa(R_eig, QuantumObject) @test isa(evecs, QuantumObject) @@ -76,8 +77,8 @@ end a = destroy(N) + destroy(N)^2/2 A_op = a+a' for spectra in spectra_list - R = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = true) - R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = false) + R = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = Val(true)) + R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = Val(false)) @test isa(R, QuantumObject) @test isa(R_eig, QuantumObject) @test isa(evecs, QuantumObject) @@ -112,4 +113,4 @@ end @test all(me.expect .== brme.expect) end -end; +end From e2c6d3e99435f41b8f38286f45aff2724fd20d36 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 21:54:00 -0400 Subject: [PATCH 296/329] CompatHelper: bump compat for SciMLOperators to 1, (keep existing compat) (#470) Co-authored-by: CompatHelper Julia Co-authored-by: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Co-authored-by: Yi-Te Huang Co-authored-by: Alberto Mercurio --- CHANGELOG.md | 2 ++ Project.toml | 4 ++-- src/QuantumToolbox.jl | 1 + src/qobj/quantum_object_evo.jl | 2 +- src/time_evolution/time_evolution.jl | 33 +++++++++++++--------------- test/core-test/time_evolution.jl | 1 - 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71fe85710..153eb036f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) - Improve efficiency of `bloch_redfield_tensor` by avoiding unnecessary conversions. ([#509]) +- Support `SciMLOperators v1.4+`. ([#470]) ## [v0.33.0] Release date: 2025-07-22 @@ -255,6 +256,7 @@ Release date: 2024-11-13 [#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 [#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456 [#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460 +[#470]: https://github.com/qutip/QuantumToolbox.jl/issues/470 [#472]: https://github.com/qutip/QuantumToolbox.jl/issues/472 [#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473 [#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 diff --git a/Project.toml b/Project.toml index 8b33fbb74..b93326f38 100644 --- a/Project.toml +++ b/Project.toml @@ -55,13 +55,13 @@ KernelAbstractions = "0.9.2" LaTeXStrings = "1.2" LinearAlgebra = "1" LinearSolve = "2, 3" -Makie = "0.20, 0.21, 0.22, 0.23, 0.24" +Makie = "0.20, 0.21, 0.22, 0.23" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" Random = "1" SciMLBase = "2" -SciMLOperators = "0.3, 0.4" +SciMLOperators = "1.4" SparseArrays = "1" SpecialFunctions = "2" StaticArraysCore = "1" diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index a4d0b7841..afdbad0ed 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -16,6 +16,7 @@ import SciMLBase: u_modified!, NullParameters, ODEFunction, + SDEFunction, ODEProblem, SDEProblem, EnsembleProblem, diff --git a/src/qobj/quantum_object_evo.jl b/src/qobj/quantum_object_evo.jl index a4111f7c7..b7a165b4f 100644 --- a/src/qobj/quantum_object_evo.jl +++ b/src/qobj/quantum_object_evo.jl @@ -512,7 +512,7 @@ function (A::QuantumObjectEvolution)( ) end - A.data(ψout.data, ψin.data, p, t) + A.data(ψout.data, ψin.data, nothing, p, t) return ψout end diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index bfdd6942b..aa68cfc00 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -395,7 +395,7 @@ end A struct to represent the diffusion operator. This is used to perform the diffusion process on N different Wiener processes. =# -struct DiffusionOperator{T,OpType<:Tuple{Vararg{AbstractSciMLOperator}}} <: AbstractSciMLOperator{T} +struct DiffusionOperator{T,OpType<:Tuple{Vararg{AbstractSciMLOperator}}} ops::OpType function DiffusionOperator(ops::OpType) where {OpType} T = mapreduce(eltype, promote_type, ops) @@ -403,32 +403,29 @@ struct DiffusionOperator{T,OpType<:Tuple{Vararg{AbstractSciMLOperator}}} <: Abst end end -@generated function update_coefficients!(L::DiffusionOperator, u, p, t) - ops_types = L.parameters[2].parameters - N = length(ops_types) - return quote - Base.@nexprs $N i -> begin - update_coefficients!(L.ops[i], u, p, t) - end - - nothing - end -end - -@generated function LinearAlgebra.mul!(v::AbstractVecOrMat, L::DiffusionOperator, u::AbstractVecOrMat) +@generated function (L::DiffusionOperator)(w, v, p, t) ops_types = L.parameters[2].parameters N = length(ops_types) quote - M = length(u) - S = (size(v, 1), size(v, 2)) # This supports also `v` as a `Vector` + M = length(v) + S = (size(w, 1), size(w, 2)) # This supports also `w` as a `Vector` (S[1] == M && S[2] == $N) || throw(DimensionMismatch("The size of the output vector is incorrect.")) Base.@nexprs $N i -> begin - mul!(@view(v[:, i]), L.ops[i], u) + op = L.ops[i] + op(@view(w[:, i]), v, v, p, t) end - return v + return w end end +#TODO: Remove when a new release of SciMLBase.jl >2.104.0 is available +(f::SDEFunction)(du, u, p, t) = + if f.f isa AbstractSciMLOperator + f.f(du, u, u, p, t) + else + f.f(du, u, p, t) + end + ####################################### function liouvillian_floquet( diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index e17db89c2..99bbb198c 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -484,7 +484,6 @@ end H = TESetup.H ψ0 = TESetup.ψ0 tlist = TESetup.tlist - c_ops = TESetup.c_ops c_ops_sme = TESetup.c_ops_sme sc_ops_sme = TESetup.sc_ops_sme c_ops_sme2 = TESetup.c_ops_sme2 From c6283feebeacbb8b0446f16e9fa39dc1d8a67fff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 17:50:21 +0800 Subject: [PATCH 297/329] CompatHelper: bump compat for Makie in [weakdeps] to 0.24 (#513) Co-authored-by: CompatHelper Julia Co-authored-by: Alberto Mercurio --- CHANGELOG.md | 2 ++ Project.toml | 2 +- .../users_guide/plotting_the_bloch_sphere.md | 5 +++-- ext/QuantumToolboxMakieExt.jl | 21 ++++++------------- src/visualization.jl | 10 +++++---- test/ext-test/cpu/makie/makie_ext.jl | 6 ++---- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 153eb036f..208ea97e8 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 - Improve efficiency of `bloch_redfield_tensor` by avoiding unnecessary conversions. ([#509]) - Support `SciMLOperators v1.4+`. ([#470]) +- Fix compatibility with `Makie v0.24+`. ([#513]) ## [v0.33.0] Release date: 2025-07-22 @@ -272,3 +273,4 @@ Release date: 2024-11-13 [#506]: https://github.com/qutip/QuantumToolbox.jl/issues/506 [#507]: https://github.com/qutip/QuantumToolbox.jl/issues/507 [#509]: https://github.com/qutip/QuantumToolbox.jl/issues/509 +[#513]: https://github.com/qutip/QuantumToolbox.jl/issues/513 diff --git a/Project.toml b/Project.toml index b93326f38..745abe013 100644 --- a/Project.toml +++ b/Project.toml @@ -55,7 +55,7 @@ KernelAbstractions = "0.9.2" LaTeXStrings = "1.2" LinearAlgebra = "1" LinearSolve = "2, 3" -Makie = "0.20, 0.21, 0.22, 0.23" +Makie = "0.24" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md index 59411831a..8bb5facfe 100644 --- a/docs/src/users_guide/plotting_the_bloch_sphere.md +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -195,8 +195,9 @@ At the end of the last section we saw that the colors and marker shapes of the d | `b.sphere_color` | Color of Bloch sphere surface | `0.2` | | `b.sphere_alpha` | Transparency of sphere surface | `"#FFDDDD"` | | `b.vector_color` | Colors for vectors | `["green", "#CC6600", "blue", "red"]` | -| `b.vector_width` | Width of vectors | `0.025` | -| `b.vector_arrowsize` | Scales the size of the arrow head. The first two elements scale the radius (in `x/y` direction) and the last one is the length of the cone. | `[0.07, 0.08, 0.08]` | +| `b.vector_width` | Width of vectors | `0.02` | +| `b.vector_tiplength` | Length of vector arrow head | `0.08` | +| `b.vector_tipradius` | Radius of vector arrow head | `0.05` | | `b.view` | Azimuthal and elevation viewing angles in degrees | `[30, 30]` | | `b.xlabel` | Labels for x-axis | `[L"x", ""]` (``+x`` and ``-x``) | | `b.xlpos` | Positions of x-axis labels | `[1.2, -1.2]` | diff --git a/ext/QuantumToolboxMakieExt.jl b/ext/QuantumToolboxMakieExt.jl index 3fcaaeee5..4c6e8cc0b 100644 --- a/ext/QuantumToolboxMakieExt.jl +++ b/ext/QuantumToolboxMakieExt.jl @@ -22,7 +22,7 @@ import Makie: Sphere, lines!, scatter!, - arrows!, + arrows3d!, text!, Point3f, mesh!, @@ -656,26 +656,17 @@ Scales vectors appropriately and adds `3D` arrow markers. function _plot_vectors!(b::Bloch, lscene) isempty(b.vectors) && return nothing - arrow_head_length = b.vector_arrowsize[3] for (i, v) in enumerate(b.vectors) color = get(b.vector_color, i, RGBAf(0.2, 0.5, 0.8, 0.9)) - nv = norm(v) - (arrow_head_length < nv) || throw( - ArgumentError( - "The length of vector arrow head (Bloch.vector_arrowsize[3]=$arrow_head_length) should be shorter than vector norm: $nv", - ), - ) - # multiply by the following factor makes the end point of arrow head represent the actual vector position. - vec = (1 - arrow_head_length / nv) * Vec3f(v...) - arrows!( + arrows3d!( lscene, [Point3f(0, 0, 0)], - [vec], + [v], color = color, - linewidth = b.vector_width, - arrowsize = Vec3f(b.vector_arrowsize...), - arrowcolor = color, + shaftradius = b.vector_width, + tiplength = b.vector_tiplength, + tipradius = b.vector_tipradius, rasterize = 3, ) end diff --git a/src/visualization.jl b/src/visualization.jl index 273a9c21a..396f76959 100644 --- a/src/visualization.jl +++ b/src/visualization.jl @@ -102,8 +102,9 @@ A structure representing a Bloch sphere visualization for quantum states. Availa ## Vector properties - `vector_color::Vector{String}`: Colors for vectors. Default: `["green", "#CC6600", "blue", "red"]` -- `vector_width::Float64`: Width of vectors. Default: `0.025` -- `vector_arrowsize::Vector{Float64}`: Scales the size of the arrow head. The first two elements scale the radius (in `x/y` direction) and the last one is the length of the cone. Default: `[0.07, 0.08, 0.08]` +- `vector_width::Float64`: Width of vectors. Default: `0.02` +- `vector_tiplength::Float64`: Length of vector arrow head. Default: `0.08` +- `vector_tipradius::Float64`: Radius of vector arrow head. Default: `0.05` ## Layout properties @@ -137,8 +138,9 @@ A structure representing a Bloch sphere visualization for quantum states. Availa sphere_alpha::Float64 = 0.2 sphere_color::String = "#FFDDDD" vector_color::Vector{String} = ["green", "#CC6600", "blue", "red"] - vector_width::Float64 = 0.025 - vector_arrowsize::Vector{Float64} = [0.07, 0.08, 0.08] + vector_width::Float64 = 0.02 + vector_tiplength::Float64 = 0.08 + vector_tipradius::Float64 = 0.05 view::Vector{Int} = [30, 30] xlabel::Vector{AbstractString} = [L"x", ""] xlpos::Vector{Float64} = [1.2, -1.2] diff --git a/test/ext-test/cpu/makie/makie_ext.jl b/test/ext-test/cpu/makie/makie_ext.jl index 6f4b0c607..cf4e3eaf9 100644 --- a/test/ext-test/cpu/makie/makie_ext.jl +++ b/test/ext-test/cpu/makie/makie_ext.jl @@ -15,14 +15,14 @@ @test fig isa Figure @test ax isa Axis @test hm isa Heatmap - @test all(isapprox.(hm[3].val, wig, atol = 1e-6)) + @test all(isapprox.(hm[3].value.x, wig, atol = 1e-6)) fig, ax, surf = plot_wigner(ψ; library = Val(:Makie), xvec = xvec, yvec = yvec, projection = Val(:three_dim), colorbar = true) @test fig isa Figure @test ax isa Axis3 @test surf isa Surface - @test all(isapprox.(surf[3].val, wig, atol = 1e-6)) + @test all(isapprox.(surf[3].value.x, wig, atol = 1e-6)) fig = Figure() pos = fig[2, 3] @@ -150,8 +150,6 @@ end @test false @info "Render threw unexpected error" exception=e end - b.vector_arrowsize = [0.7, 0.8, 1.5] # 1.5 is the length of arrow head (too long) - @test_throws ArgumentError render(b) b = Bloch() ψ₁ = normalize(basis(2, 0) + basis(2, 1)) From 0bc7621facad7d691298d7ee056c417567845677 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Sun, 27 Jul 2025 21:46:14 +0800 Subject: [PATCH 298/329] Add `keep_runs_results` option for multi-trajectory solvers to align with qutip (#512) Co-authored-by: Alberto Mercurio --- CHANGELOG.md | 10 + Project.toml | 2 + docs/src/resources/api.md | 3 + .../users_guide/time_evolution/solution.md | 46 +- src/QuantumToolbox.jl | 1 + src/time_evolution/mcsolve.jl | 25 +- src/time_evolution/mesolve.jl | 4 +- src/time_evolution/sesolve.jl | 4 +- src/time_evolution/smesolve.jl | 24 +- src/time_evolution/ssesolve.jl | 20 +- src/time_evolution/time_evolution.jl | 138 +++++- .../time_evolution_dynamical.jl | 4 +- test/Project.toml | 1 + test/core-test/time_evolution.jl | 467 ++++++++++++------ 14 files changed, 543 insertions(+), 206 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 208ea97e8..8671bd3c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve efficiency of `bloch_redfield_tensor` by avoiding unnecessary conversions. ([#509]) - Support `SciMLOperators v1.4+`. ([#470]) - Fix compatibility with `Makie v0.24+`. ([#513]) +- Add `keep_runs_results` option for multi-trajectory solvers to align with `QuTiP`. ([#512]) + - Breaking changes for multi-trajectory solutions: + - the original fields `expect` and `states` now store the results depend on keyword argument `keep_runs_results` (decide whether to store all trajectories results or not). + - remove field `average_expect` + - remove field `runs_expect` + - New statistical analysis functions: + - `average_states` + - `average_expect` + - `std_expect` ## [v0.33.0] Release date: 2025-07-22 @@ -273,4 +282,5 @@ Release date: 2024-11-13 [#506]: https://github.com/qutip/QuantumToolbox.jl/issues/506 [#507]: https://github.com/qutip/QuantumToolbox.jl/issues/507 [#509]: https://github.com/qutip/QuantumToolbox.jl/issues/509 +[#512]: https://github.com/qutip/QuantumToolbox.jl/issues/512 [#513]: https://github.com/qutip/QuantumToolbox.jl/issues/513 diff --git a/Project.toml b/Project.toml index 745abe013..81acac6c1 100644 --- a/Project.toml +++ b/Project.toml @@ -24,6 +24,7 @@ SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" [weakdeps] @@ -65,5 +66,6 @@ SciMLOperators = "1.4" SparseArrays = "1" SpecialFunctions = "2" StaticArraysCore = "1" +Statistics = "1" StochasticDiffEq = "6" julia = "1.10" diff --git a/docs/src/resources/api.md b/docs/src/resources/api.md index 8c9b8c209..da800fc76 100644 --- a/docs/src/resources/api.md +++ b/docs/src/resources/api.md @@ -197,6 +197,9 @@ TimeEvolutionProblem TimeEvolutionSol TimeEvolutionMCSol TimeEvolutionStochasticSol +average_states +average_expect +std_expect sesolveProblem mesolveProblem mcsolveProblem diff --git a/docs/src/users_guide/time_evolution/solution.md b/docs/src/users_guide/time_evolution/solution.md index 94609f18a..e57d30362 100644 --- a/docs/src/users_guide/time_evolution/solution.md +++ b/docs/src/users_guide/time_evolution/solution.md @@ -97,7 +97,47 @@ Some other solvers can have other output. ## [Multiple trajectories solution](@id doc-TE:Multiple-trajectories-solution) -This part is still under construction, please read the docstrings for the following functions first: +The solutions are different for solvers which compute multiple trajectories, such as the [`TimeEvolutionMCSol`](@ref) (Monte Carlo) or the [`TimeEvolutionStochasticSol`](@ref) (stochastic methods). The storage of expectation values and states depends on the keyword argument `keep_runs_results`, which determines whether the results of all trajectories are stored in the solution. -- [`TimeEvolutionMCSol`](@ref) -- [`TimeEvolutionStochasticSol`](@ref) \ No newline at end of file +When the keyword argument `keep_runs_results` is passed as `Val(false)` to a multi-trajectory solver, the `states` and `expect` fields store only the average results (averaged over all trajectories). The results can be accessed by the following index-order: + +```julia +sol.states[time_idx] +sol.expect[e_op,time_idx] +``` + +For example: + +```@example TE-solution +tlist = LinRange(0, 1, 11) +c_ops = (destroy(2),) +e_ops = (num(2),) + +sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj=25, keep_runs_results=Val(false), progress_bar=Val(false)) + +size(sol_mc1.expect) +``` + +If the keyword argument `keep_runs_results = Val(true)`, the results for each trajectory and each time are stored, and the index-order of the elements in fields `states` and `expect` are: + + +```julia +sol.states[trajectory,time_idx] +sol.expect[e_op,trajectory,time_idx] +``` + +For example: + +```@example TE-solution +sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj=25, keep_runs_results=Val(true), progress_bar=Val(false)) + +size(sol_mc2.expect) +``` + +We also provide the following functions for statistical analysis of multi-trajectory `sol`utions: + +| **Functions** | **Description** | +|:------------|:----------------| +| [`average_states(sol)`](@ref average_states) | Return the trajectory-averaged result states (as density [`Operator`](@ref)) at each time point. | +| [`average_expect(sol)`](@ref average_expect) | Return the trajectory-averaged expectation values at each time point. | +| [`std_expect(sol)`](@ref std_expect) | Return the trajectory-wise standard deviation of the expectation values at each time point. | diff --git a/src/QuantumToolbox.jl b/src/QuantumToolbox.jl index afdbad0ed..c3d9be754 100644 --- a/src/QuantumToolbox.jl +++ b/src/QuantumToolbox.jl @@ -5,6 +5,7 @@ using LinearAlgebra import LinearAlgebra: BlasInt, BlasFloat, checksquare import LinearAlgebra.LAPACK: hseqr! using SparseArrays +import Statistics: mean, std # SciML packages (for QobjEvo, OrdinaryDiffEq, and LinearSolve) import SciMLBase: diff --git a/src/time_evolution/mcsolve.jl b/src/time_evolution/mcsolve.jl index fe8da0876..647be42d4 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -22,7 +22,7 @@ end function _normalize_state!(u, dims, normalize_states) getVal(normalize_states) && normalize!(u) - return QuantumObject(u, type = Ket(), dims = dims) + return QuantumObject(u, Ket(), dims) end function _mcsolve_make_Heff_QobjEvo(H::QuantumObject, c_ops) @@ -278,6 +278,7 @@ end progress_bar::Union{Val,Bool} = Val(true), prob_func::Union{Function, Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, + keep_runs_results::Union{Val,Bool} = Val(false), normalize_states::Union{Val,Bool} = Val(true), kwargs..., ) @@ -332,6 +333,7 @@ If the environmental measurements register a quantum jump, the wave function und - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. - `prob_func`: Function to use for generating the ODEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. +- `keep_runs_results`: Whether to save the results of each trajectory. Default to `Val(false)`. - `normalize_states`: Whether to normalize the states. Default to `Val(true)`. - `kwargs`: The keyword arguments for the ODEProblem. @@ -363,6 +365,7 @@ function mcsolve( progress_bar::Union{Val,Bool} = Val(true), prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, + keep_runs_results::Union{Val,Bool} = Val(false), normalize_states::Union{Val,Bool} = Val(true), kwargs..., ) where {TJC<:LindbladJumpCallbackType} @@ -384,7 +387,7 @@ function mcsolve( kwargs..., ) - return mcsolve(ens_prob_mc, alg, ntraj, ensemblealg, normalize_states) + return mcsolve(ens_prob_mc, alg, ntraj, ensemblealg, makeVal(keep_runs_results), normalize_states) end function mcsolve( @@ -392,6 +395,7 @@ function mcsolve( alg::OrdinaryDiffEqAlgorithm = Tsit5(), ntraj::Int = 500, ensemblealg::EnsembleAlgorithm = EnsembleThreads(), + keep_runs_results = Val(false), normalize_states = Val(true), ) sol = _ensemble_dispatch_solve(ens_prob_mc, alg, ensemblealg, ntraj) @@ -403,25 +407,24 @@ function mcsolve( _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _get_expvals(sol[:, i], SaveFuncMCSolve), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP - states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) + + # stack to transform Vector{Vector{QuantumObject}} -> Matrix{QuantumObject} + states_all = stack(map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)), dims = 1) + col_times = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.col_times, eachindex(sol)) col_which = map(i -> _mc_get_jump_callback(sol[:, i]).affect!.col_which, eachindex(sol)) - expvals = _expvals_sol_1 isa Nothing ? nothing : dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) - return TimeEvolutionMCSol( ntraj, ens_prob_mc.times, _sol_1.t, - states, - expvals, - expvals, # This is average_expect - expvals_all, + _store_multitraj_states(states_all, keep_runs_results), + _store_multitraj_expect(expvals_all, keep_runs_results), col_times, col_which, sol.converged, _sol_1.alg, - NamedTuple(_sol_1.prob.kwargs).abstol, - NamedTuple(_sol_1.prob.kwargs).reltol, + _sol_1.prob.kwargs[:abstol], + _sol_1.prob.kwargs[:reltol], ) end diff --git a/src/time_evolution/mesolve.jl b/src/time_evolution/mesolve.jl index 85d6ed613..9dfd22273 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -211,7 +211,7 @@ function mesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit _get_expvals(sol, SaveFuncMESolve), sol.retcode, sol.alg, - NamedTuple(sol.prob.kwargs).abstol, - NamedTuple(sol.prob.kwargs).reltol, + sol.prob.kwargs[:abstol], + sol.prob.kwargs[:reltol], ) end diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index ef9d98f9b..cd635a705 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -161,7 +161,7 @@ function sesolve(prob::TimeEvolutionProblem, alg::OrdinaryDiffEqAlgorithm = Tsit _get_expvals(sol, SaveFuncSESolve), sol.retcode, sol.alg, - NamedTuple(sol.prob.kwargs).abstol, - NamedTuple(sol.prob.kwargs).reltol, + sol.prob.kwargs[:abstol], + sol.prob.kwargs[:reltol], ) end diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index aa6dbae9b..1d28d4e02 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -293,6 +293,7 @@ end prob_func::Union{Function, Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + keep_runs_results::Union{Val,Bool} = Val(false), store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) @@ -333,6 +334,7 @@ Above, ``\hat{C}_i`` represent the collapse operators related to pure dissipatio - `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `keep_runs_results`: Whether to save the results of each trajectory. Default to `Val(false)`. - `store_measurement`: Whether to store the measurement expectation values. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. @@ -365,6 +367,7 @@ function smesolve( prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + keep_runs_results::Union{Val,Bool} = Val(false), store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) where {StateOpType<:Union{Ket,Operator,OperatorKet}} @@ -392,7 +395,7 @@ function smesolve( alg = sc_ops_isa_Qobj ? SRIW1() : SRA2() end - return smesolve(ensemble_prob, alg, ntraj, ensemblealg) + return smesolve(ensemble_prob, alg, ntraj, ensemblealg, makeVal(keep_runs_results)) end function smesolve( @@ -400,6 +403,7 @@ function smesolve( alg::StochasticDiffEqAlgorithm = SRA2(), ntraj::Int = 500, ensemblealg::EnsembleAlgorithm = EnsembleThreads(), + keep_runs_results = Val(false), ) sol = _ensemble_dispatch_solve(ens_prob, alg, ensemblealg, ntraj) @@ -412,24 +416,22 @@ function smesolve( _expvals_sol_1 isa Nothing ? nothing : map(i -> _get_expvals(sol[:, i], SaveFuncMESolve), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP - states = map(i -> _smesolve_generate_state.(sol[:, i].u, Ref(dims), ens_prob.kwargs.isoperket), eachindex(sol)) + # stack to transform Vector{Vector{QuantumObject}} -> Matrix{QuantumObject} + states_all = stack( + map(i -> _smesolve_generate_state.(sol[:, i].u, Ref(dims), ens_prob.kwargs.isoperket), eachindex(sol)), + dims = 1, + ) _m_expvals = _m_expvals_sol_1 isa Nothing ? nothing : map(i -> _get_m_expvals(sol[:, i], SaveFuncSMESolve), eachindex(sol)) - m_expvals = _m_expvals isa Nothing ? nothing : stack(_m_expvals, dims = 2) - - expvals = - _get_expvals(_sol_1, SaveFuncMESolve) isa Nothing ? nothing : - dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) + m_expvals = _m_expvals isa Nothing ? nothing : stack(_m_expvals, dims = 2) # Stack on dimension 2 to align with QuTiP return TimeEvolutionStochasticSol( ntraj, ens_prob.times, _sol_1.t, - states, - expvals, - expvals, # This is average_expect - expvals_all, + _store_multitraj_states(states_all, keep_runs_results), + _store_multitraj_expect(expvals_all, keep_runs_results), m_expvals, # Measurement expectation values sol.converged, _sol_1.alg, diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index e68987c02..487a3a06a 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -285,6 +285,7 @@ end prob_func::Union{Function, Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + keep_runs_results::Union{Val,Bool} = Val(false), store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) @@ -328,6 +329,7 @@ Above, ``\hat{S}_n`` are the stochastic collapse operators and ``dW_n(t)`` is th - `prob_func`: Function to use for generating the SDEProblem. - `output_func`: a `Tuple` containing the `Function` to use for generating the output of a single trajectory, the (optional) `ProgressBar` object, and the (optional) `RemoteChannel` object. - `progress_bar`: Whether to show the progress bar. Using non-`Val` types might lead to type instabilities. +- `keep_runs_results`: Whether to save the results of each trajectory. Default to `Val(false)`. - `store_measurement`: Whether to store the measurement results. Default is `Val(false)`. - `kwargs`: The keyword arguments for the ODEProblem. @@ -360,6 +362,7 @@ function ssesolve( prob_func::Union{Function,Nothing} = nothing, output_func::Union{Tuple,Nothing} = nothing, progress_bar::Union{Val,Bool} = Val(true), + keep_runs_results::Union{Val,Bool} = Val(false), store_measurement::Union{Val,Bool} = Val(false), kwargs..., ) @@ -386,7 +389,7 @@ function ssesolve( alg = sc_ops_isa_Qobj ? SRIW1() : SRA2() end - return ssesolve(ens_prob, alg, ntraj, ensemblealg) + return ssesolve(ens_prob, alg, ntraj, ensemblealg, makeVal(keep_runs_results)) end function ssesolve( @@ -394,6 +397,7 @@ function ssesolve( alg::StochasticDiffEqAlgorithm = SRA2(), ntraj::Int = 500, ensemblealg::EnsembleAlgorithm = EnsembleThreads(), + keep_runs_results = Val(false), ) sol = _ensemble_dispatch_solve(ens_prob, alg, ensemblealg, ntraj) @@ -406,24 +410,20 @@ function ssesolve( _expvals_all = _expvals_sol_1 isa Nothing ? nothing : map(i -> _get_expvals(sol[:, i], SaveFuncSSESolve), eachindex(sol)) expvals_all = _expvals_all isa Nothing ? nothing : stack(_expvals_all, dims = 2) # Stack on dimension 2 to align with QuTiP - states = map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)) + + # stack to transform Vector{Vector{QuantumObject}} -> Matrix{QuantumObject} + states_all = stack(map(i -> _normalize_state!.(sol[:, i].u, Ref(dims), normalize_states), eachindex(sol)), dims = 1) _m_expvals = _m_expvals_sol_1 isa Nothing ? nothing : map(i -> _get_m_expvals(sol[:, i], SaveFuncSSESolve), eachindex(sol)) m_expvals = _m_expvals isa Nothing ? nothing : stack(_m_expvals, dims = 2) - expvals = - _get_expvals(_sol_1, SaveFuncSSESolve) isa Nothing ? nothing : - dropdims(sum(expvals_all, dims = 2), dims = 2) ./ length(sol) - return TimeEvolutionStochasticSol( ntraj, ens_prob.times, _sol_1.t, - states, - expvals, - expvals, # This is average_expect - expvals_all, + _store_multitraj_states(states_all, keep_runs_results), + _store_multitraj_expect(expvals_all, keep_runs_results), m_expvals, # Measurement expectation values sol.converged, _sol_1.alg, diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index aa68cfc00..d04c7338b 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -1,4 +1,6 @@ -export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionStochasticSol +export TimeEvolutionSol +export TimeEvolutionMultiTrajSol, TimeEvolutionMCSol, TimeEvolutionStochasticSol +export average_states, average_expect, std_expect export liouvillian_floquet, liouvillian_generalized @@ -6,6 +8,8 @@ const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-3, reltol = 2e-3, save_everystep = false, save_end = true) const COL_TIMES_WHICH_INIT_SIZE = 200 +abstract type TimeEvolutionMultiTrajSol{Tstates,Texpect} end + @doc raw""" struct TimeEvolutionProblem @@ -92,7 +96,7 @@ function Base.show(io::IO, sol::TimeEvolutionSol) end @doc raw""" - struct TimeEvolutionMCSol + struct TimeEvolutionMCSol <: TimeEvolutionMultiTrajSol A structure storing the results and some information from solving quantum trajectories of the Monte Carlo wave function time evolution. @@ -101,36 +105,49 @@ A structure storing the results and some information from solving quantum trajec - `ntraj::Int`: Number of trajectories - `times::AbstractVector`: The list of time points at which the expectation values are calculated during the evolution. - `times_states::AbstractVector`: The list of time points at which the states are stored during the evolution. -- `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory and each time point in `times_states`. -- `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `average_expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `runs_expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` +- `states::AbstractVecOrMat`: The list of result states in each trajectory and each time point in `times_states`. +- `expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times`. - `col_times::Vector{Vector{Real}}`: The time records of every quantum jump occurred in each trajectory. - `col_which::Vector{Vector{Int}}`: The indices of which collapse operator was responsible for each quantum jump in `col_times`. - `converged::Bool`: Whether the solution is converged or not. - `alg`: The algorithm which is used during the solving process. - `abstol::Real`: The absolute tolerance which is used during the solving process. - `reltol::Real`: The relative tolerance which is used during the solving process. + +# Notes + +When the keyword argument `keep_runs_results` is passed as `Val(false)` to a multi-trajectory solver, the `states` and `expect` fields store only the average results (averaged over all trajectories). The results can be accessed by the following index-order: + +- `sol.states[time_idx]` +- `sol.expect[e_op,time_idx]` + +If the keyword argument `keep_runs_results = Val(true)`, the results for each trajectory and each time are stored, and the index-order of the elements in fields `states` and `expect` are: + +- `sol.states[trajectory,time_idx]` +- `sol.expect[e_op,trajectory,time_idx]` + +We also provide the following functions for statistical analysis of multi-trajectory solutions: + +- [`average_states`](@ref) +- [`average_expect`](@ref) +- [`std_expect`](@ref) """ struct TimeEvolutionMCSol{ TT1<:AbstractVector{<:Real}, TT2<:AbstractVector{<:Real}, - TS<:AbstractVector, - TE<:Union{AbstractMatrix,Nothing}, - TEA<:Union{AbstractArray,Nothing}, + TS<:AbstractVecOrMat, + TE<:Union{AbstractArray,Nothing}, TJT<:Vector{<:Vector{<:Real}}, TJW<:Vector{<:Vector{<:Integer}}, AlgT<:OrdinaryDiffEqAlgorithm, AT<:Real, RT<:Real, -} +} <: TimeEvolutionMultiTrajSol{TS,TE} ntraj::Int times::TT1 times_states::TT2 states::TS expect::TE - average_expect::TE # Currently just a synonym for `expect` - runs_expect::TEA col_times::TJT col_which::TJW converged::Bool @@ -144,11 +161,11 @@ function Base.show(io::IO, sol::TimeEvolutionMCSol) print(io, "(converged: $(sol.converged))\n") print(io, "--------------------------------\n") print(io, "num_trajectories = $(sol.ntraj)\n") - print(io, "num_states = $(length(sol.states[1]))\n") + print(io, "num_states = $(size(sol.states, ndims(sol.states)))\n") # get the size of last dimension if sol.expect isa Nothing print(io, "num_expect = 0\n") else - print(io, "num_expect = $(size(sol.average_expect, 1))\n") + print(io, "num_expect = $(size(sol.expect, 1))\n") end print(io, "ODE alg.: $(sol.alg)\n") print(io, "abstol = $(sol.abstol)\n") @@ -166,33 +183,46 @@ A structure storing the results and some information from solving trajectories o - `ntraj::Int`: Number of trajectories - `times::AbstractVector`: The list of time points at which the expectation values are calculated during the evolution. - `times_states::AbstractVector`: The list of time points at which the states are stored during the evolution. -- `states::Vector{Vector{QuantumObject}}`: The list of result states in each trajectory and each time point in `times_states`. -- `expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `average_expect::Union{AbstractMatrix,Nothing}`: The expectation values (averaging all trajectories) corresponding to each time point in `times`. -- `runs_expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times` +- `states::AbstractVecOrMat`: The list of result states in each trajectory and each time point in `times_states`. +- `expect::Union{AbstractArray,Nothing}`: The expectation values corresponding to each trajectory and each time point in `times`. - `converged::Bool`: Whether the solution is converged or not. - `alg`: The algorithm which is used during the solving process. - `abstol::Real`: The absolute tolerance which is used during the solving process. - `reltol::Real`: The relative tolerance which is used during the solving process. + +# Notes + +When the keyword argument `keep_runs_results` is passed as `Val(false)` to a multi-trajectory solver, the `states` and `expect` fields store only the average results (averaged over all trajectories). The results can be accessed by the following index-order: + +- `sol.states[time_idx]` +- `sol.expect[e_op,time_idx]` + +If the keyword argument `keep_runs_results = Val(true)`, the results for each trajectory and each time are stored, and the index-order of the elements in fields `states` and `expect` are: + +- `sol.states[trajectory,time_idx]` +- `sol.expect[e_op,trajectory,time_idx]` + +We also provide the following functions for statistical analysis of multi-trajectory solutions: + +- [`average_states`](@ref) +- [`average_expect`](@ref) +- [`std_expect`](@ref) """ struct TimeEvolutionStochasticSol{ TT1<:AbstractVector{<:Real}, TT2<:AbstractVector{<:Real}, - TS<:AbstractVector, - TE<:Union{AbstractMatrix,Nothing}, - TEA<:Union{AbstractArray,Nothing}, + TS<:AbstractVecOrMat, + TE<:Union{AbstractArray,Nothing}, TEM<:Union{AbstractArray,Nothing}, AlgT<:StochasticDiffEqAlgorithm, AT<:Real, RT<:Real, -} +} <: TimeEvolutionMultiTrajSol{TS,TE} ntraj::Int times::TT1 times_states::TT2 states::TS expect::TE - average_expect::TE # Currently just a synonym for `expect` - runs_expect::TEA measurement::TEM converged::Bool alg::AlgT @@ -205,11 +235,11 @@ function Base.show(io::IO, sol::TimeEvolutionStochasticSol) print(io, "(converged: $(sol.converged))\n") print(io, "--------------------------------\n") print(io, "num_trajectories = $(sol.ntraj)\n") - print(io, "num_states = $(length(sol.states[1]))\n") + print(io, "num_states = $(size(sol.states, ndims(sol.states)))\n") # get the size of last dimension if sol.expect isa Nothing print(io, "num_expect = 0\n") else - print(io, "num_expect = $(size(sol.average_expect, 1))\n") + print(io, "num_expect = $(size(sol.expect, 1))\n") end print(io, "SDE alg.: $(sol.alg)\n") print(io, "abstol = $(sol.abstol)\n") @@ -217,6 +247,62 @@ function Base.show(io::IO, sol::TimeEvolutionStochasticSol) return nothing end +@doc raw""" + average_states(sol::TimeEvolutionMultiTrajSol) + +Return the trajectory-averaged result states (as density [`Operator`](@ref)) at each time point. +""" +average_states(sol::TimeEvolutionMultiTrajSol{<:Matrix{<:QuantumObject}}) = _average_traj_states(sol.states) +average_states(sol::TimeEvolutionMultiTrajSol{<:Vector{<:QuantumObject}}) = sol.states # this case should already be averaged over all trajectories + +# TODO: Check if broadcasting division ./ size(states, 1) is type stable +_average_traj_states(states::Matrix{<:QuantumObject{Ket}}) = + map(x -> x / size(states, 1), dropdims(sum(ket2dm, states, dims = 1), dims = 1)) +_average_traj_states(states::Matrix{<:QuantumObject{ObjType}}) where {ObjType<:Union{Operator,OperatorKet}} = + map(x -> x / size(states, 1), dropdims(sum(states, dims = 1), dims = 1)) + +@doc raw""" + average_expect(sol::TimeEvolutionMultiTrajSol) + +Return the trajectory-averaged expectation values at each time point. +""" +average_expect(sol::TimeEvolutionMultiTrajSol{TS,Array{T,3}}) where {TS,T<:Number} = _average_traj_expect(sol.expect) +average_expect(sol::TimeEvolutionMultiTrajSol{TS,Matrix{T}}) where {TS,T<:Number} = sol.expect # this case should already be averaged over all trajectories +average_expect(::TimeEvolutionMultiTrajSol{TS,Nothing}) where {TS} = nothing + +_average_traj_expect(expvals::Array{T,3}) where {T<:Number} = + dropdims(sum(expvals, dims = 2), dims = 2) ./ size(expvals, 2) + +# these are used in multi-trajectory solvers before returning solutions +_store_multitraj_states(states::Matrix{<:QuantumObject}, keep_runs_results::Val{false}) = _average_traj_states(states) +_store_multitraj_states(states::Matrix{<:QuantumObject}, keep_runs_results::Val{true}) = states +_store_multitraj_expect(expvals::Array{T,3}, keep_runs_results::Val{false}) where {T<:Number} = + _average_traj_expect(expvals) +_store_multitraj_expect(expvals::Array{T,3}, keep_runs_results::Val{true}) where {T<:Number} = expvals +_store_multitraj_expect(expvals::Nothing, keep_runs_results) = nothing + +@doc raw""" + std_expect(sol::TimeEvolutionMultiTrajSol) + +Return the trajectory-wise standard deviation of the expectation values at each time point. +""" +function std_expect(sol::TimeEvolutionMultiTrajSol{TS,Array{T,3}}) where {TS,T<:Number} + # the following standard deviation (std) is defined as the square-root of variance instead of pseudo-variance + # i.e., it is equivalent to (even for complex expectation values): + # dropdims( + # sqrt.(mean(abs2.(sol.expect), dims = 2) .- abs2.(mean(sol.expect, dims = 2))), + # dims = 2 + # ) + # [this should be included in the runtest] + return dropdims(std(sol.expect, corrected = false, dims = 2), dims = 2) +end +std_expect(::TimeEvolutionMultiTrajSol{TS,Matrix{T}}) where {TS,T<:Number} = throw( + ArgumentError( + "Can not compute the standard deviation without the expectation values of each trajectory. Try to specify keyword argument `keep_runs_results=Val(true)` to the solver.", + ), +) +std_expect(::TimeEvolutionMultiTrajSol{TS,Nothing}) where {TS} = nothing + ####################################### #= Callbacks for Monte Carlo quantum trajectories diff --git a/src/time_evolution/time_evolution_dynamical.jl b/src/time_evolution/time_evolution_dynamical.jl index 98960764e..305822611 100644 --- a/src/time_evolution/time_evolution_dynamical.jl +++ b/src/time_evolution/time_evolution_dynamical.jl @@ -249,8 +249,8 @@ function dfd_mesolve( _get_expvals(sol, SaveFuncMESolve), sol.retcode, sol.alg, - NamedTuple(sol.prob.kwargs).abstol, - NamedTuple(sol.prob.kwargs).reltol, + sol.prob.kwargs[:abstol], + sol.prob.kwargs[:reltol], ) end diff --git a/test/Project.toml b/test/Project.toml index 6c91482ab..b783960dd 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -5,6 +5,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" diff --git a/test/core-test/time_evolution.jl b/test/core-test/time_evolution.jl index 99bbb198c..d10a7c6d6 100644 --- a/test/core-test/time_evolution.jl +++ b/test/core-test/time_evolution.jl @@ -248,6 +248,7 @@ end @testitem "mcsolve" setup=[TESetup] begin using SciMLOperators + using Statistics # Get parameters from TESetup to simplify the code H = TESetup.H @@ -270,7 +271,9 @@ end progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), ) - sol_mc_states = mcsolve(H, ψ0, tlist, c_ops, saveat = saveat, progress_bar = Val(false)) + sol_mc3 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), keep_runs_results = Val(true)) + sol_mc_states = + mcsolve(H, ψ0, tlist, c_ops, saveat = saveat, progress_bar = Val(false), keep_runs_results = Val(true)) sol_mc_states2 = mcsolve( H, ψ0, @@ -279,26 +282,31 @@ end saveat = saveat, progress_bar = Val(false), jump_callback = DiscreteLindbladJumpCallback(), + keep_runs_results = Val(true), ) - ρt_mc = [ket2dm.(normalize.(states)) for states in sol_mc_states.states] - expect_mc_states = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc) - expect_mc_states_mean = sum(expect_mc_states, dims = 2) / size(expect_mc_states, 2) - - ρt_mc2 = [ket2dm.(normalize.(states)) for states in sol_mc_states2.states] - expect_mc_states2 = mapreduce(states -> expect.(Ref(e_ops[1]), states), hcat, ρt_mc2) - expect_mc_states_mean2 = sum(expect_mc_states2, dims = 2) / size(expect_mc_states2, 2) + # also test function average_states + # average the states from all trajectories, and then calculate the expectation value + expect_mc_states_mean = expect.(Ref(e_ops[1]), average_states(sol_mc_states)) + expect_mc_states_mean2 = expect.(Ref(e_ops[1]), average_states(sol_mc_states2)) @test prob_mc.prob.f.f isa MatrixOperator @test sum(abs, sol_mc.expect .- sol_me.expect) / length(tlist) < 0.1 @test sum(abs, sol_mc2.expect .- sol_me.expect) / length(tlist) < 0.1 - @test sum(abs, vec(expect_mc_states_mean) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 - @test sum(abs, vec(expect_mc_states_mean2) .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 + @test sum(abs, average_expect(sol_mc3) .- sol_me.expect) / length(tlist) < 0.1 + @test sum(abs, expect_mc_states_mean .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 + @test sum(abs, expect_mc_states_mean2 .- vec(sol_me.expect[1, saveat_idxs])) / length(tlist) < 0.1 @test length(sol_mc.times) == length(tlist) @test length(sol_mc.times_states) == 1 @test size(sol_mc.expect) == (length(e_ops), length(tlist)) + @test size(sol_mc.states) == (1,) + @test length(sol_mc3.times) == length(tlist) + @test length(sol_mc3.times_states) == 1 + @test size(sol_mc3.expect) == (length(e_ops), 500, length(tlist)) # ntraj = 500 + @test size(sol_mc3.states) == (500, 1) # ntraj = 500 @test length(sol_mc_states.times) == length(tlist) @test length(sol_mc_states.times_states) == length(saveat) + @test size(sol_mc_states.states) == (500, length(saveat)) # ntraj = 500 @test sol_mc_states.expect === nothing sol_mc_string = sprint((t, s) -> show(t, "text/plain", s), sol_mc) @@ -308,7 +316,7 @@ end "(converged: $(sol_mc.converged))\n" * "--------------------------------\n" * "num_trajectories = $(sol_mc.ntraj)\n" * - "num_states = $(length(sol_mc.states[1]))\n" * + "num_states = $(size(sol_mc.states, ndims(sol_mc.states)))\n" * "num_expect = $(size(sol_mc.expect, 1))\n" * "ODE alg.: $(sol_mc.alg)\n" * "abstol = $(sol_mc.abstol)\n" * @@ -318,7 +326,7 @@ end "(converged: $(sol_mc_states.converged))\n" * "--------------------------------\n" * "num_trajectories = $(sol_mc_states.ntraj)\n" * - "num_states = $(length(sol_mc_states.states[1]))\n" * + "num_states = $(size(sol_mc_states.states, ndims(sol_mc_states.states)))\n" * "num_expect = 0\n" * "ODE alg.: $(sol_mc_states.alg)\n" * "abstol = $(sol_mc_states.abstol)\n" * @@ -330,17 +338,73 @@ end @test_throws ArgumentError mcsolve(H, ψ0, tlist, c_ops, save_idxs = [1, 2], progress_bar = Val(false)) @test_throws DimensionMismatch mcsolve(H, TESetup.ψ_wrong, tlist, c_ops, progress_bar = Val(false)) + # test average_states, average_expect, and std_expect + expvals_all = sol_mc3.expect[:, :, 2:end] # ignore testing initial time point since its standard deviation is a very small value (basically zero) + stdvals = std_expect(sol_mc3) + @test average_states(sol_mc) == sol_mc.states + @test average_expect(sol_mc) == sol_mc.expect + @test size(stdvals) == (length(e_ops), length(tlist)) + @test all( + isapprox.( + stdvals[:, 2:end], # ignore testing initial time point since its standard deviation is a very small value (basically zero) + dropdims(sqrt.(mean(abs2.(expvals_all), dims = 2) .- abs2.(mean(expvals_all, dims = 2))), dims = 2); + atol = 1e-6, + ), + ) + @test average_expect(sol_mc_states) === nothing + @test std_expect(sol_mc_states) === nothing + @test_throws ArgumentError std_expect(sol_mc) + @testset "Memory Allocations (mcsolve)" begin ntraj = 100 - allocs_tot = @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up - allocs_tot = @allocations mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) - @test allocs_tot < 120 * ntraj + 400 # 150 allocations per trajectory + 500 for initialization - - allocs_tot = - @allocations mcsolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up - allocs_tot = - @allocations mcsolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) - @test allocs_tot < 110 * ntraj + 300 # 100 allocations per trajectory + 300 for initialization + for keep_runs_results in (Val(false), Val(true)) + n1 = QuantumToolbox.getVal(keep_runs_results) ? 120 : 140 + n2 = QuantumToolbox.getVal(keep_runs_results) ? 110 : 130 + + allocs_tot = @allocations mcsolve( + H, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + keep_runs_results = Val(true), + ) # Warm-up + allocs_tot = @allocations mcsolve( + H, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + keep_runs_results = Val(true), + ) + @test allocs_tot < n1 * ntraj + 400 # 150 allocations per trajectory + 500 for initialization + + allocs_tot = @allocations mcsolve( + H, + ψ0, + tlist, + c_ops, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = Val(true), + ) # Warm-up + allocs_tot = @allocations mcsolve( + H, + ψ0, + tlist, + c_ops, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = Val(true), + ) + @test allocs_tot < n2 * ntraj + 300 # 100 allocations per trajectory + 300 for initialization + end end @testset "Type Inference (mcsolve)" begin @@ -400,6 +464,7 @@ end @test sum(abs, sol_sse.expect .- sol_me.expect) / length(tlist) < 0.1 @test length(sol_sse.times) == length(tlist) @test length(sol_sse.times_states) == 1 + @test size(sol_sse.states) == (1,) # ntraj = 500 but keep_runs_results = Val(false) @test size(sol_sse.expect) == (length(e_ops), length(tlist)) @test isnothing(sol_sse.measurement) @test size(sol_sse2.measurement) == (length(c_ops), 20, length(tlist) - 1) @@ -410,7 +475,7 @@ end "(converged: $(sol_sse.converged))\n" * "--------------------------------\n" * "num_trajectories = $(sol_sse.ntraj)\n" * - "num_states = $(length(sol_sse.states[1]))\n" * + "num_states = $(size(sol_sse.states, ndims(sol_sse.states)))\n" * "num_expect = $(size(sol_sse.expect, 1))\n" * "SDE alg.: $(sol_sse.alg)\n" * "abstol = $(sol_sse.abstol)\n" * @@ -422,15 +487,54 @@ end @testset "Memory Allocations (ssesolve)" begin ntraj = 100 - allocs_tot = @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) # Warm-up - allocs_tot = @allocations ssesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, ntraj = ntraj, progress_bar = Val(false)) - @test allocs_tot < 1100 * ntraj + 400 # TODO: Fix this high number of allocations - - allocs_tot = - @allocations ssesolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) # Warm-up - allocs_tot = - @allocations ssesolve(H, ψ0, tlist, c_ops, ntraj = ntraj, saveat = [tlist[end]], progress_bar = Val(false)) - @test allocs_tot < 1000 * ntraj + 300 # TODO: Fix this high number of allocations + for keep_runs_results in (Val(false), Val(true)) + n1 = QuantumToolbox.getVal(keep_runs_results) ? 1100 : 1120 + n2 = QuantumToolbox.getVal(keep_runs_results) ? 1000 : 1020 + + allocs_tot = @allocations ssesolve( + H, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) # Warm-up + allocs_tot = @allocations ssesolve( + H, + ψ0, + tlist, + c_ops, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) + @test allocs_tot < n1 * ntraj + 400 # TODO: Fix this high number of allocations + + allocs_tot = @allocations ssesolve( + H, + ψ0, + tlist, + c_ops, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) # Warm-up + allocs_tot = @allocations ssesolve( + H, + ψ0, + tlist, + c_ops, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) + @test allocs_tot < n2 * ntraj + 300 # TODO: Fix this high number of allocations + end end @testset "Type Inference (ssesolve)" begin @@ -534,10 +638,11 @@ end @test sum(abs, sol_sme3.expect .- sol_me.expect) / length(tlist) < 0.1 @test length(sol_sme.times) == length(tlist) @test length(sol_sme.times_states) == 1 + @test size(sol_sme.states) == (1,) # ntraj = 500 but keep_runs_results = Val(false) @test size(sol_sme.expect) == (length(e_ops), length(tlist)) @test isnothing(sol_sme.measurement) @test size(sol_sme2.measurement) == (length(sc_ops_sme), 20, length(tlist) - 1) - @test all([sol_sme4.states[j][i] ≈ vector_to_operator(sol_sme5.states[j][i]) for i in eachindex(saveat), j in 1:10]) + @test all([sol_sme4.states[i] ≈ vector_to_operator(sol_sme5.states[i]) for i in eachindex(saveat)]) sol_sme_string = sprint((t, s) -> show(t, "text/plain", s), sol_sme) @test sol_sme_string == @@ -545,7 +650,7 @@ end "(converged: $(sol_sme.converged))\n" * "--------------------------------\n" * "num_trajectories = $(sol_sme.ntraj)\n" * - "num_states = $(length(sol_sme.states[1]))\n" * + "num_states = $(size(sol_sme.states, ndims(sol_sme.states)))\n" * "num_expect = $(size(sol_sme.expect, 1))\n" * "SDE alg.: $(sol_sme.alg)\n" * "abstol = $(sol_sme.abstol)\n" * @@ -557,94 +662,109 @@ end @testset "Memory Allocations (smesolve)" begin ntraj = 100 - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - e_ops = e_ops, - ntraj = ntraj, - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - e_ops = e_ops, - ntraj = ntraj, - progress_bar = Val(false), - ) - @test allocs_tot < 1100 * ntraj + 2300 # TODO: Fix this high number of allocations - - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme, - sc_ops_sme, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) - @test allocs_tot < 1000 * ntraj + 1500 # TODO: Fix this high number of allocations - - # Diagonal Noise Case - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - e_ops = e_ops, - ntraj = ntraj, - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - e_ops = e_ops, - ntraj = 1, - progress_bar = Val(false), - ) - @test allocs_tot < 600 * ntraj + 1400 # TODO: Fix this high number of allocations - - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - ntraj = ntraj, - saveat = [tlist[end]], - progress_bar = Val(false), - ) # Warm-up - allocs_tot = @allocations smesolve( - H, - ψ0, - tlist, - c_ops_sme2, - sc_ops_sme2, - ntraj = 1, - saveat = [tlist[end]], - progress_bar = Val(false), - ) - @test allocs_tot < 550 * ntraj + 1000 # TODO: Fix this high number of allocations + for keep_runs_results in (Val(false), Val(true)) + n1 = QuantumToolbox.getVal(keep_runs_results) ? 1100 : 1120 + n2 = QuantumToolbox.getVal(keep_runs_results) ? 1000 : 1020 + n3 = QuantumToolbox.getVal(keep_runs_results) ? 600 : 620 + n4 = QuantumToolbox.getVal(keep_runs_results) ? 550 : 570 + + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) + @test allocs_tot < n1 * ntraj + 2300 # TODO: Fix this high number of allocations + + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) + @test allocs_tot < n2 * ntraj + 1500 # TODO: Fix this high number of allocations + + # Diagonal Noise Case + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + e_ops = e_ops, + ntraj = ntraj, + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + e_ops = e_ops, + ntraj = 1, + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) + @test allocs_tot < n3 * ntraj + 1400 # TODO: Fix this high number of allocations + + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + ntraj = ntraj, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) # Warm-up + allocs_tot = @allocations smesolve( + H, + ψ0, + tlist, + c_ops_sme2, + sc_ops_sme2, + ntraj = 1, + saveat = [tlist[end]], + progress_bar = Val(false), + keep_runs_results = keep_runs_results, + ) + @test allocs_tot < n4 * ntraj + 1000 # TODO: Fix this high number of allocations + end end @testset "Type Inference (smesolve)" begin @@ -800,45 +920,114 @@ end rng = TESetup.rng rng = MersenneTwister(1234) - sol_mc1 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc1 = + mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng, keep_runs_results = Val(true)) rng = MersenneTwister(1234) - sol_sse1 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sse1 = ssesolve( + H, + ψ0, + tlist, + c_ops, + ntraj = 50, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + keep_runs_results = Val(true), + ) rng = MersenneTwister(1234) - sol_sme1 = - smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sme1 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 50, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + keep_runs_results = Val(true), + ) rng = MersenneTwister(1234) - sol_mc2 = mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc2 = + mcsolve(H, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), rng = rng, keep_runs_results = Val(true)) rng = MersenneTwister(1234) - sol_sse2 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sse2 = ssesolve( + H, + ψ0, + tlist, + c_ops, + ntraj = 50, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + keep_runs_results = Val(true), + ) rng = MersenneTwister(1234) - sol_sme2 = - smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 50, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sme2 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 50, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + keep_runs_results = Val(true), + ) rng = MersenneTwister(1234) - sol_mc3 = mcsolve(H, ψ0, tlist, c_ops, ntraj = 510, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_mc3 = mcsolve( + H, + ψ0, + tlist, + c_ops, + ntraj = 510, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + keep_runs_results = Val(true), + ) rng = MersenneTwister(1234) - sol_sse3 = ssesolve(H, ψ0, tlist, c_ops, ntraj = 60, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sse3 = ssesolve( + H, + ψ0, + tlist, + c_ops, + ntraj = 60, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + keep_runs_results = Val(true), + ) rng = MersenneTwister(1234) - sol_sme3 = - smesolve(H, ψ0, tlist, c_ops_sme, sc_ops_sme, ntraj = 60, e_ops = e_ops, progress_bar = Val(false), rng = rng) + sol_sme3 = smesolve( + H, + ψ0, + tlist, + c_ops_sme, + sc_ops_sme, + ntraj = 60, + e_ops = e_ops, + progress_bar = Val(false), + rng = rng, + keep_runs_results = Val(true), + ) @test sol_mc1.expect ≈ sol_mc2.expect atol = 1e-10 - @test sol_mc1.runs_expect ≈ sol_mc2.runs_expect atol = 1e-10 @test sol_mc1.col_times ≈ sol_mc2.col_times atol = 1e-10 @test sol_mc1.col_which ≈ sol_mc2.col_which atol = 1e-10 - @test sol_mc1.runs_expect ≈ sol_mc3.runs_expect[:, 1:500, :] atol = 1e-10 + @test sol_mc1.expect ≈ sol_mc3.expect[:, 1:500, :] atol = 1e-10 @test sol_sse1.expect ≈ sol_sse2.expect atol = 1e-10 - @test sol_sse1.runs_expect ≈ sol_sse2.runs_expect atol = 1e-10 - @test sol_sse1.runs_expect ≈ sol_sse3.runs_expect[:, 1:50, :] atol = 1e-10 + @test sol_sse1.expect ≈ sol_sse3.expect[:, 1:50, :] atol = 1e-10 @test sol_sme1.expect ≈ sol_sme2.expect atol = 1e-10 - @test sol_sme1.runs_expect ≈ sol_sme2.runs_expect atol = 1e-10 - @test sol_sme1.runs_expect ≈ sol_sme3.runs_expect[:, 1:50, :] atol = 1e-10 + @test sol_sme1.expect ≈ sol_sme3.expect[:, 1:50, :] atol = 1e-10 end @testitem "example" begin From 5ba859ac5231a58a44193dba6475c2821f8ae9c5 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 27 Jul 2025 11:31:23 -0400 Subject: [PATCH 299/329] Add support to ForwarDiff differentiation (#515) --- CHANGELOG.md | 2 + src/qobj/quantum_object_base.jl | 4 +- src/spectrum.jl | 10 +-- src/steadystate.jl | 2 +- src/time_evolution/lr_mesolve.jl | 2 +- src/time_evolution/mcsolve.jl | 2 +- src/time_evolution/mesolve.jl | 6 +- src/time_evolution/sesolve.jl | 4 +- src/time_evolution/smesolve.jl | 6 +- src/time_evolution/ssesolve.jl | 4 +- src/utilities.jl | 42 ++++----- test/ext-test/cpu/autodiff/Project.toml | 3 +- test/ext-test/cpu/autodiff/autodiff.jl | 115 ++++++++++++++++++++++++ test/ext-test/cpu/autodiff/zygote.jl | 84 ----------------- test/runtests.jl | 3 +- 15 files changed, 163 insertions(+), 126 deletions(-) create mode 100644 test/ext-test/cpu/autodiff/autodiff.jl delete mode 100644 test/ext-test/cpu/autodiff/zygote.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 8671bd3c8..21dbd00a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `average_states` - `average_expect` - `std_expect` +- Add support to ForwardDiff.jl for `sesolve` and `mesolve`. ([#515]) ## [v0.33.0] Release date: 2025-07-22 @@ -284,3 +285,4 @@ Release date: 2024-11-13 [#509]: https://github.com/qutip/QuantumToolbox.jl/issues/509 [#512]: https://github.com/qutip/QuantumToolbox.jl/issues/512 [#513]: https://github.com/qutip/QuantumToolbox.jl/issues/513 +[#515]: https://github.com/qutip/QuantumToolbox.jl/issues/515 diff --git a/src/qobj/quantum_object_base.jl b/src/qobj/quantum_object_base.jl index deff612c4..6ae1024d3 100644 --- a/src/qobj/quantum_object_base.jl +++ b/src/qobj/quantum_object_base.jl @@ -245,5 +245,5 @@ _get_dims_length(::Space) = 1 _get_dims_length(::EnrSpace{N}) where {N} = N # functions for getting Float or Complex element type -_FType(A::AbstractQuantumObject) = _FType(eltype(A)) -_CType(A::AbstractQuantumObject) = _CType(eltype(A)) +_float_type(A::AbstractQuantumObject) = _float_type(eltype(A)) +_complex_float_type(A::AbstractQuantumObject) = _complex_float_type(eltype(A)) diff --git a/src/spectrum.jl b/src/spectrum.jl index 9a6313ad4..c9473bc17 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -127,9 +127,9 @@ function _spectrum( ) check_dimensions(L, A, B) - ωList = convert(Vector{_FType(L)}, ωlist) # Convert it to support GPUs and avoid type instabilities + ωList = convert(Vector{_float_type(L)}, ωlist) # Convert it to support GPUs and avoid type instabilities Length = length(ωList) - spec = Vector{_FType(L)}(undef, Length) + spec = Vector{_float_type(L)}(undef, Length) # calculate vectorized steadystate, multiply by operator B on the left (spre) ρss = mat2vec(steadystate(L)) @@ -137,7 +137,7 @@ function _spectrum( # multiply by operator A on the left (spre) and then perform trace operation D = prod(L.dimensions) - _tr = SparseVector(D^2, [1 + n * (D + 1) for n in 0:(D-1)], ones(_CType(L), D)) # same as vec(system_identity_matrix) + _tr = SparseVector(D^2, [1 + n * (D + 1) for n in 0:(D-1)], ones(_complex_float_type(L), D)) # same as vec(system_identity_matrix) _tr_A = transpose(_tr) * spre(A).data Id = I(D^2) @@ -169,8 +169,8 @@ function _spectrum( check_dimensions(L, A, B) # Define type shortcuts - fT = _FType(L) - cT = _CType(L) + fT = _float_type(L) + cT = _complex_float_type(L) # Calculate |v₁> = B|ρss> ρss = diff --git a/src/steadystate.jl b/src/steadystate.jl index d17ad4783..a81fe3b3f 100644 --- a/src/steadystate.jl +++ b/src/steadystate.jl @@ -206,7 +206,7 @@ function _steadystate(L::QuantumObject{SuperOperator}, solver::SteadyStateODESol abstol = haskey(kwargs, :abstol) ? kwargs[:abstol] : DEFAULT_ODE_SOLVER_OPTIONS.abstol reltol = haskey(kwargs, :reltol) ? kwargs[:reltol] : DEFAULT_ODE_SOLVER_OPTIONS.reltol - ftype = _FType(ψ0) + ftype = _float_type(ψ0) _terminate_func = SteadyStateODECondition(similar(mat2vec(ket2dm(ψ0)).data)) cb = TerminateSteadyState(abstol, reltol, _terminate_func) sol = mesolve( diff --git a/src/time_evolution/lr_mesolve.jl b/src/time_evolution/lr_mesolve.jl index 1c73010e1..de49df963 100644 --- a/src/time_evolution/lr_mesolve.jl +++ b/src/time_evolution/lr_mesolve.jl @@ -412,7 +412,7 @@ function lr_mesolveProblem( c_ops = get_data.(c_ops) e_ops = get_data.(e_ops) - t_l = _check_tlist(tlist, _FType(H)) + t_l = _check_tlist(tlist, _float_type(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 647be42d4..ddd4379cb 100644 --- a/src/time_evolution/mcsolve.jl +++ b/src/time_evolution/mcsolve.jl @@ -125,7 +125,7 @@ function mcsolveProblem( c_ops isa Nothing && throw(ArgumentError("The list of collapse operators must be provided. Use sesolveProblem instead.")) - tlist = _check_tlist(tlist, _FType(ψ0)) + tlist = _check_tlist(tlist, _float_type(ψ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 9dfd22273..1ab033bd3 100644 --- a/src/time_evolution/mesolve.jl +++ b/src/time_evolution/mesolve.jl @@ -79,16 +79,16 @@ function mesolveProblem( haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) - tlist = _check_tlist(tlist, _FType(ψ0)) + tlist = _check_tlist(tlist, _float_type(ψ0)) L_evo = _mesolve_make_L_QobjEvo(H, c_ops) check_dimensions(L_evo, ψ0) T = Base.promote_eltype(L_evo, ψ0) ρ0 = if isoperket(ψ0) # Convert it to dense vector with complex element type - to_dense(_CType(T), copy(ψ0.data)) + to_dense(_complex_float_type(T), copy(ψ0.data)) else - to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) + to_dense(_complex_float_type(T), mat2vec(ket2dm(ψ0).data)) end L = L_evo.data diff --git a/src/time_evolution/sesolve.jl b/src/time_evolution/sesolve.jl index cd635a705..b447307a3 100644 --- a/src/time_evolution/sesolve.jl +++ b/src/time_evolution/sesolve.jl @@ -61,14 +61,14 @@ function sesolveProblem( haskey(kwargs, :save_idxs) && throw(ArgumentError("The keyword argument \"save_idxs\" is not supported in QuantumToolbox.")) - tlist = _check_tlist(tlist, _FType(ψ0)) + tlist = _check_tlist(tlist, _float_type(ψ0)) H_evo = _sesolve_make_U_QobjEvo(H) # Multiply by -i isoper(H_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) check_dimensions(H_evo, ψ0) T = Base.promote_eltype(H_evo, ψ0) - ψ0 = to_dense(_CType(T), get_data(ψ0)) # Convert it to dense vector with complex element type + ψ0 = to_dense(_complex_float_type(T), get_data(ψ0)) # Convert it to dense vector with complex element type U = H_evo.data kwargs2 = _merge_saveat(tlist, e_ops, DEFAULT_ODE_SOLVER_OPTIONS; kwargs...) diff --git a/src/time_evolution/smesolve.jl b/src/time_evolution/smesolve.jl index 1d28d4e02..1f1aa3363 100644 --- a/src/time_evolution/smesolve.jl +++ b/src/time_evolution/smesolve.jl @@ -94,7 +94,7 @@ function smesolveProblem( sc_ops_list = _make_c_ops_list(sc_ops) # If it is an AbstractQuantumObject but we need to iterate sc_ops_isa_Qobj = sc_ops isa AbstractQuantumObject # We can avoid using non-diagonal noise if sc_ops is just an AbstractQuantumObject - tlist = _check_tlist(tlist, _FType(ψ0)) + tlist = _check_tlist(tlist, _float_type(ψ0)) L_evo = _mesolve_make_L_QobjEvo(H, c_ops) + _mesolve_make_L_QobjEvo(nothing, sc_ops_list) check_dimensions(L_evo, ψ0) @@ -102,9 +102,9 @@ function smesolveProblem( T = Base.promote_eltype(L_evo, ψ0) ρ0 = if isoperket(ψ0) # Convert it to dense vector with complex element type - to_dense(_CType(T), copy(ψ0.data)) + to_dense(_complex_float_type(T), copy(ψ0.data)) else - to_dense(_CType(T), mat2vec(ket2dm(ψ0).data)) + to_dense(_complex_float_type(T), mat2vec(ket2dm(ψ0).data)) end progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) diff --git a/src/time_evolution/ssesolve.jl b/src/time_evolution/ssesolve.jl index 487a3a06a..962a831ad 100644 --- a/src/time_evolution/ssesolve.jl +++ b/src/time_evolution/ssesolve.jl @@ -94,14 +94,14 @@ function ssesolveProblem( sc_ops_list = _make_c_ops_list(sc_ops) # If it is an AbstractQuantumObject but we need to iterate sc_ops_isa_Qobj = sc_ops isa AbstractQuantumObject # We can avoid using non-diagonal noise if sc_ops is just an AbstractQuantumObject - tlist = _check_tlist(tlist, _FType(ψ0)) + tlist = _check_tlist(tlist, _float_type(ψ0)) H_eff_evo = _mcsolve_make_Heff_QobjEvo(H, sc_ops_list) isoper(H_eff_evo) || throw(ArgumentError("The Hamiltonian must be an Operator.")) check_dimensions(H_eff_evo, ψ0) dims = H_eff_evo.dimensions - ψ0 = to_dense(_CType(ψ0), get_data(ψ0)) + ψ0 = to_dense(_complex_float_type(ψ0), get_data(ψ0)) progr = ProgressBar(length(tlist), enable = getVal(progress_bar)) diff --git a/src/utilities.jl b/src/utilities.jl index ebe9481f4..e30d6785c 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -46,7 +46,7 @@ where ``\hbar`` is the reduced Planck constant, and ``k_B`` is the Boltzmann con function n_thermal(ω::T1, ω_th::T2) where {T1<:Real,T2<:Real} x = exp(ω / ω_th) n = ((x != 1) && (ω_th > 0)) ? 1 / (x - 1) : 0 - return _FType(promote_type(T1, T2))(n) + return _float_type(promote_type(T1, T2))(n) end @doc raw""" @@ -125,7 +125,7 @@ julia> round(convert_unit(1, :meV, :mK), digits=4) function convert_unit(value::T, unit1::Symbol, unit2::Symbol) where {T<:Real} !haskey(_energy_units, unit1) && throw(ArgumentError("Invalid unit :$(unit1)")) !haskey(_energy_units, unit2) && throw(ArgumentError("Invalid unit :$(unit2)")) - return _FType(T)(value * (_energy_units[unit1] / _energy_units[unit2])) + return _float_type(T)(value * (_energy_units[unit1] / _energy_units[unit2])) end get_typename_wrapper(A) = Base.typename(typeof(A)).wrapper @@ -174,24 +174,26 @@ for AType in (:AbstractArray, :AbstractSciMLOperator) end # functions for getting Float or Complex element type -_FType(::AbstractArray{T}) where {T<:Number} = _FType(T) -_FType(::Type{Int32}) = Float32 -_FType(::Type{Int64}) = Float64 -_FType(::Type{Float32}) = Float32 -_FType(::Type{Float64}) = Float64 -_FType(::Type{Complex{Int32}}) = Float32 -_FType(::Type{Complex{Int64}}) = Float64 -_FType(::Type{Complex{Float32}}) = Float32 -_FType(::Type{Complex{Float64}}) = Float64 -_CType(::AbstractArray{T}) where {T<:Number} = _CType(T) -_CType(::Type{Int32}) = ComplexF32 -_CType(::Type{Int64}) = ComplexF64 -_CType(::Type{Float32}) = ComplexF32 -_CType(::Type{Float64}) = ComplexF64 -_CType(::Type{Complex{Int32}}) = ComplexF32 -_CType(::Type{Complex{Int64}}) = ComplexF64 -_CType(::Type{Complex{Float32}}) = ComplexF32 -_CType(::Type{Complex{Float64}}) = ComplexF64 +_float_type(::AbstractArray{T}) where {T<:Number} = _float_type(T) +_float_type(::Type{Int32}) = Float32 +_float_type(::Type{Int64}) = Float64 +_float_type(::Type{Float32}) = Float32 +_float_type(::Type{Float64}) = Float64 +_float_type(::Type{Complex{Int32}}) = Float32 +_float_type(::Type{Complex{Int64}}) = Float64 +_float_type(::Type{Complex{Float32}}) = Float32 +_float_type(::Type{Complex{Float64}}) = Float64 +_float_type(T::Type{<:Real}) = T # Allow other untracked Real types, like ForwardDiff.Dual +_complex_float_type(::AbstractArray{T}) where {T<:Number} = _complex_float_type(T) +_complex_float_type(::Type{Int32}) = ComplexF32 +_complex_float_type(::Type{Int64}) = ComplexF64 +_complex_float_type(::Type{Float32}) = ComplexF32 +_complex_float_type(::Type{Float64}) = ComplexF64 +_complex_float_type(::Type{Complex{Int32}}) = ComplexF32 +_complex_float_type(::Type{Complex{Int64}}) = ComplexF64 +_complex_float_type(::Type{Complex{Float32}}) = ComplexF32 +_complex_float_type(::Type{Complex{Float64}}) = ComplexF64 +_complex_float_type(T::Type{<:Complex}) = T # Allow other untracked Complex types, like ForwardDiff.Dual _convert_eltype_wordsize(::Type{T}, ::Val{64}) where {T<:Int} = Int64 _convert_eltype_wordsize(::Type{T}, ::Val{32}) where {T<:Int} = Int32 diff --git a/test/ext-test/cpu/autodiff/Project.toml b/test/ext-test/cpu/autodiff/Project.toml index e07db5ee7..f52a45c2b 100644 --- a/test/ext-test/cpu/autodiff/Project.toml +++ b/test/ext-test/cpu/autodiff/Project.toml @@ -1,5 +1,6 @@ [deps] Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" -Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" \ No newline at end of file +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" diff --git a/test/ext-test/cpu/autodiff/autodiff.jl b/test/ext-test/cpu/autodiff/autodiff.jl new file mode 100644 index 000000000..57bf60b5b --- /dev/null +++ b/test/ext-test/cpu/autodiff/autodiff.jl @@ -0,0 +1,115 @@ +@testset "Autodiff" verbose=true begin + @testset "sesolve" verbose=true begin + ψ0 = fock(2, 1) + t_max = 10 + tlist = range(0, t_max, 100) + + # For direct Forward differentiation + function my_f_sesolve_direct(p) + H = p[1] * sigmax() + sol = sesolve(H, ψ0, tlist, progress_bar = Val(false)) + + return real(expect(projection(2, 0, 0), sol.states[end])) + end + + # For SciMLSensitivity.jl + coef_Ω(p, t) = p[1] + H_evo = QobjEvo(sigmax(), coef_Ω) + + function my_f_sesolve(p) + sol = sesolve( + H_evo, + ψ0, + tlist, + progress_bar = Val(false), + params = p, + sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), + ) + + return real(expect(projection(2, 0, 0), sol.states[end])) + end + + # Analytical solution + my_f_analytic(Ω) = abs2(sin(Ω * t_max)) + my_f_analytic_deriv(Ω) = 2 * t_max * sin(Ω * t_max) * cos(Ω * t_max) + + Ω = 1.0 + params = [Ω] + + my_f_sesolve_direct(params) + my_f_sesolve(params) + + grad_exact = [my_f_analytic_deriv(params[1])] + + @testset "ForwardDiff.jl" begin + grad_qt = ForwardDiff.gradient(my_f_sesolve_direct, params) + + @test grad_qt ≈ grad_exact atol=1e-6 + end + + @testset "Zygote.jl" begin + grad_qt = Zygote.gradient(my_f_sesolve, params)[1] + + @test grad_qt ≈ grad_exact atol=1e-6 + end + end + + @testset "mesolve" verbose=true begin + N = 20 + a = destroy(N) + ψ0 = fock(N, 0) + tlist = range(0, 40, 100) + + # For direct Forward differentiation + function my_f_mesolve_direct(p) + H = p[1] * a' * a + p[2] * (a + a') + c_ops = [sqrt(p[3]) * a] + sol = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) + return real(expect(a' * a, sol.states[end])) + end + + # For SciMLSensitivity.jl + coef_Δ(p, t) = p[1] + coef_F(p, t) = p[2] + coef_γ(p, t) = sqrt(p[3]) + H = QobjEvo(a' * a, coef_Δ) + QobjEvo(a + a', coef_F) + c_ops = [QobjEvo(a, coef_γ)] + L = liouvillian(H, c_ops) + + function my_f_mesolve(p) + sol = mesolve( + L, + ψ0, + tlist, + progress_bar = Val(false), + params = p, + sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), + ) + + return real(expect(a' * a, sol.states[end])) + end + + # Analytical solution + n_ss(Δ, F, γ) = abs2(F / (Δ + 1im * γ / 2)) + + Δ = 1.0 + F = 1.0 + γ = 1.0 + params = [Δ, F, γ] + + my_f_mesolve_direct(params) + my_f_mesolve(params) + + grad_exact = Zygote.gradient((p) -> n_ss(p[1], p[2], p[3]), params)[1] + + @testset "ForwardDiff.jl" begin + grad_qt = ForwardDiff.gradient(my_f_mesolve_direct, params) + @test grad_qt ≈ grad_exact atol=1e-6 + end + + @testset "Zygote.jl" begin + grad_qt = Zygote.gradient(my_f_mesolve, params)[1] + @test grad_qt ≈ grad_exact atol=1e-6 + end + end +end diff --git a/test/ext-test/cpu/autodiff/zygote.jl b/test/ext-test/cpu/autodiff/zygote.jl deleted file mode 100644 index bf2775a05..000000000 --- a/test/ext-test/cpu/autodiff/zygote.jl +++ /dev/null @@ -1,84 +0,0 @@ -@testset "Zygote Extension" verbose=true begin - @testset "sesolve" begin - coef_Ω(p, t) = p[1] - - H = QobjEvo(sigmax(), coef_Ω) - ψ0 = fock(2, 1) - t_max = 10 - - function my_f_sesolve(p) - tlist = range(0, t_max, 100) - - sol = sesolve( - H, - ψ0, - tlist, - progress_bar = Val(false), - params = p, - sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), - ) - - return real(expect(projection(2, 0, 0), sol.states[end])) - end - - # Analytical solution - my_f_analytic(Ω) = abs2(sin(Ω * t_max)) - my_f_analytic_deriv(Ω) = 2 * t_max * sin(Ω * t_max) * cos(Ω * t_max) - - Ω = 1.0 - params = [Ω] - - my_f_analytic(Ω) - my_f_sesolve(params) - - grad_qt = Zygote.gradient(my_f_sesolve, params)[1] - grad_exact = [my_f_analytic_deriv(params[1])] - - @test grad_qt ≈ grad_exact atol=1e-6 - end - - @testset "mesolve" begin - N = 20 - a = destroy(N) - - coef_Δ(p, t) = p[1] - coef_F(p, t) = p[2] - coef_γ(p, t) = sqrt(p[3]) - - H = QobjEvo(a' * a, coef_Δ) + QobjEvo(a + a', coef_F) - c_ops = [QobjEvo(a, coef_γ)] - L = liouvillian(H, c_ops) - - ψ0 = fock(N, 0) - - function my_f_mesolve(p) - tlist = range(0, 40, 100) - - sol = mesolve( - L, - ψ0, - tlist, - progress_bar = Val(false), - params = p, - sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), - ) - - return real(expect(a' * a, sol.states[end])) - end - - # Analytical solution - n_ss(Δ, F, γ) = abs2(F / (Δ + 1im * γ / 2)) - - Δ = 1.0 - F = 1.0 - γ = 1.0 - params = [Δ, F, γ] - - # The factor 2 is due to a bug - grad_qt = Zygote.gradient(my_f_mesolve, params)[1] - - grad_exact = Zygote.gradient((p) -> n_ss(p[1], p[2], p[3]), params)[1] - - @test grad_qt ≈ grad_exact atol=1e-6 - end -end diff --git a/test/runtests.jl b/test/runtests.jl index b650417cf..eab6514c8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -42,13 +42,14 @@ if (GROUP == "AutoDiff_Ext") Pkg.instantiate() using QuantumToolbox + using ForwardDiff using Zygote using Enzyme using SciMLSensitivity QuantumToolbox.about() - include(joinpath(testdir, "ext-test", "cpu", "autodiff", "zygote.jl")) + include(joinpath(testdir, "ext-test", "cpu", "autodiff", "autodiff.jl")) end if (GROUP == "Makie_Ext") From 8fb7406e55faba207c233ba72816ff6b1731b2a3 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang <44385685+ytdHuang@users.noreply.github.com> Date: Mon, 28 Jul 2025 01:19:24 +0800 Subject: [PATCH 300/329] Bump compat of `SciMLBase` to fix piracy error (#516) --- Project.toml | 2 +- src/time_evolution/time_evolution.jl | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index 81acac6c1..0e02eea04 100644 --- a/Project.toml +++ b/Project.toml @@ -61,7 +61,7 @@ OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" Pkg = "1" Random = "1" -SciMLBase = "2" +SciMLBase = "2.105" SciMLOperators = "1.4" SparseArrays = "1" SpecialFunctions = "2" diff --git a/src/time_evolution/time_evolution.jl b/src/time_evolution/time_evolution.jl index d04c7338b..3b09bd443 100644 --- a/src/time_evolution/time_evolution.jl +++ b/src/time_evolution/time_evolution.jl @@ -504,14 +504,6 @@ end end end -#TODO: Remove when a new release of SciMLBase.jl >2.104.0 is available -(f::SDEFunction)(du, u, p, t) = - if f.f isa AbstractSciMLOperator - f.f(du, u, u, p, t) - else - f.f(du, u, p, t) - end - ####################################### function liouvillian_floquet( From 536434bdd29f809631f73fbd9c023b096e95c86b Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:16:31 +0200 Subject: [PATCH 301/329] Add documentation on Forward and Reverse differentiation (#517) --- CHANGELOG.md | 2 + docs/Project.toml | 6 +- docs/make.jl | 1 + docs/src/index.md | 9 ++ docs/src/users_guide/autodiff.md | 195 +++++++++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 docs/src/users_guide/autodiff.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 21dbd00a0..fe776e5f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `average_expect` - `std_expect` - Add support to ForwardDiff.jl for `sesolve` and `mesolve`. ([#515]) +- Add documentation about automatic differentiation. ([#517]) ## [v0.33.0] Release date: 2025-07-22 @@ -286,3 +287,4 @@ Release date: 2024-11-13 [#512]: https://github.com/qutip/QuantumToolbox.jl/issues/512 [#513]: https://github.com/qutip/QuantumToolbox.jl/issues/513 [#515]: https://github.com/qutip/QuantumToolbox.jl/issues/515 +[#517]: https://github.com/qutip/QuantumToolbox.jl/issues/517 diff --git a/docs/Project.toml b/docs/Project.toml index 4ca77f49f..3fc508c90 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,7 +5,11 @@ Changelog = "5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" +SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [compat] -DocumenterVitepress = "0.2" \ No newline at end of file +DocumenterVitepress = "0.2" diff --git a/docs/make.jl b/docs/make.jl index 7fb05c01b..d9ac8be98 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -61,6 +61,7 @@ const PAGES = [ "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", "Bloch-Redfield master equation" => "users_guide/time_evolution/brmesolve.md", ], + "Automatic Differentiation" => "users_guide/autodiff.md", "Intensive parallelization on a Cluster" => "users_guide/cluster.md", "Hierarchical Equations of Motion" => "users_guide/HEOM.md", "Solving for Steady-State Solutions" => "users_guide/steadystate.md", diff --git a/docs/src/index.md b/docs/src/index.md index 345b70bdb..70e2a2f96 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -46,6 +46,10 @@ features: title: Distributed Computing details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. link: /users_guide/cluster + - icon: + title: Differentiable Programming + details: Enable gradient-based optimization for quantum algorithms. Compute gradients of quantum dynamics with respect to their parameters using automatic differentiation. + link: /users_guide/autodiff --- ``` @@ -95,6 +99,11 @@ In order to get a better experience and take full advantage of `QuantumToolbox` - [`SlurmClusterManager.jl`](https://github.com/JuliaParallel/SlurmClusterManager.jl) - Plotting Libraries: - [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) +- Automatic Differentiation: + - [`SciMLSensitivity.jl`](https://github.com/SciML/SciMLSensitivity.jl) + - [`Zygote.jl`](https://github.com/FluxML/Zygote.jl) + - [`Enzyme.jl`](https://github.com/EnzymeAD/Enzyme.jl) + - [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) - Packages for other advanced usage: - [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) - [`SciMLOperators.jl`](https://github.com/SciML/SciMLOperators.jl) diff --git a/docs/src/users_guide/autodiff.md b/docs/src/users_guide/autodiff.md new file mode 100644 index 000000000..058b38cd2 --- /dev/null +++ b/docs/src/users_guide/autodiff.md @@ -0,0 +1,195 @@ +# [Automatic Differentiation](@id doc:autodiff) + +Automatic differentiation (AD) has emerged as a key technique in computational science, enabling exact and efficient computation of derivatives for functions defined by code. Unlike symbolic differentiation, which may produce complex and inefficient expressions, or finite-difference methods, which suffer from numerical instability and poor scalability, AD leverages the chain rule at the level of elementary operations to provide machine-precision gradients with minimal overhead. + +In `QuantumToolbox.jl`, we have introduced preliminary support for automatic differentiation. Many of the core functions are compatible with AD engines such as [`Zygote.jl`](https://github.com/FluxML/Zygote.jl), [`Enzyme.jl`](https://github.com/EnzymeAD/Enzyme.jl) or [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl), allowing users to compute gradients of observables or cost functionals involving the time evolution of open quantum systems. Although `QuantumToolbox.jl` was not originally designed with AD in mind, its architecture—rooted in Julia’s multiple dispatch and generic programming model—facilitated the integration of AD capabilities. Many core functions were already compatible with AD engines out of the box. + +!!! warning "Experimental Functionality" + At present, this functionality is considered experimental and not all parts of the library are AD-compatible. Here we provide a brief overview of the current state of AD support in `QuantumToolbox.jl` and how to use it. + + +## [Forward versus Reverse Mode AD](@id doc:autodiff:forward-versus-reverse) + +Automatic differentiation can be broadly categorized into two modes: forward mode and reverse mode. The choice between these modes depends on the nature of the function being differentiated and the number of inputs and outputs: + +- **Forward Mode AD**: This mode is particularly efficient for functions with many outputs and few inputs. It works by propagating derivatives from the inputs through the computational graph to the outputs. Forward mode is often preferred when the number of input variables is small, as it computes the derivative of each output with respect to each input in a single pass. + +- **Reverse Mode AD**: In contrast, reverse mode is more efficient for functions with many inputs and few outputs. It operates by first computing the function's output and then propagating derivatives backward through the computational graph. This mode is commonly used in machine learning and optimization applications, where the loss function (output) depends on a large number of parameters (inputs). + +Understanding the differences between these two modes can help users choose the most appropriate approach for their specific use case in `QuantumToolbox.jl`. + +## [Differentiate the master equation](@id doc:autodiff:master-equation) + +One of the primary use cases for automatic differentiation in `QuantumToolbox.jl` is the differentiation of the master equation. The master equation describes the time evolution of a quantum system's density matrix under the influence of non-unitary dynamics, such as dissipation and decoherence. Let's consider a set of parameters $\mathbf{p} = (p_1, p_2, \ldots, p_n)$ that influence the system's dynamics. The Hamiltonian and the dissipators will depend on these parameters + +```math +\hat{H} = \hat{H}(\mathbf{p}), \qquad \hat{L}_j = \hat{L}_j(\mathbf{p}), +``` + +Hence, the density matrix will evolve according to the master equation + +```@raw html + +``` +```math +\begin{align} +\frac{d \hat{\rho}(\mathbf{p}, t)}{dt} =& -i[\hat{H}(\mathbf{p}), \hat{\rho}(\mathbf{p}, t)] \\ +&+ \sum_j \hat{L}_j(\mathbf{p}) \hat{\rho}(\mathbf{p}, t) \hat{L}_j(\mathbf{p})^\dagger - \frac{1}{2} \left\{ \hat{L}_j(\mathbf{p})^\dagger \hat{L}_j(\mathbf{p}), \hat{\rho}(\mathbf{p}, t) \right\} \, , +\end{align} \tag{1} +``` + +which depends on the parameters $\mathbf{p}$ and time $t$. + +We now want to compute the expectation value of an observable $\hat{O}$ at time $t$: + +```math +\langle \hat{O}(\mathbf{p}, t) \rangle = \text{Tr}[\hat{O} \hat{\rho}(\mathbf{p}, t)] \, , +``` + +which will also depend on the parameters $\mathbf{p}$ and time $t$. + +Our goal is to compute the derivative of the expectation value with respect to the parameters: + +```math +\frac{\partial \langle \hat{O}(\mathbf{p}, t) \rangle}{\partial p_j} = \frac{\partial}{\partial p_j} \text{Tr}[\hat{O} \hat{\rho}(\mathbf{p}, t)] \, , +``` + +and to achieve this, we can use an AD engine like [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) (forward mode) or [`Zygote.jl`](https://github.com/FluxML/Zygote.jl) (reverse mode). + +Let's apply this to a simple example of a driven-dissipative quantum harmonic oscillator. The Hamiltonian in the drive frame is given by + +```math +\hat{H} = \Delta \hat{a}^\dagger \hat{a} + F \left( \hat{a} + \hat{a}^\dagger \right) \, , +``` + +where $\Delta = \omega_0 - \omega_d$ is the cavity-drive detuning, $F$ is the drive strength, and $\hat{a}$ and $\hat{a}^\dagger$ are the annihilation and creation operators, respectively. The system is subject to a single dissipative channel with a Lindblad operator $\hat{L} = \sqrt{\gamma} \hat{a}$, where $\gamma$ is the dissipation rate. If we start from the ground state $\hat{\rho}(0) = \vert 0 \rangle \langle 0 \vert$, the systems evolves according to the master equation in [Eq. (1)](#eq:master-equation). + +We now want to study the number of photons at the steady state, and how it varies with $\mathbf{p} = (\Delta, F, \gamma)$, namely $\nabla_\mathbf{p} \langle \hat{a}^\dagger \hat{a} \rangle (\mathbf{p}, t \to \infty)$. We can extract an analytical expression, in order to verify the correctness of the AD implementation: + +```math +\langle \hat{a}^\dagger \hat{a} \rangle_\mathrm{ss} = \frac{F^2}{\Delta^2 + \frac{\gamma^2}{4}} \, , +``` + +with the gradient given by + +```math +\nabla_\mathbf{p} \langle \hat{a}^\dagger \hat{a} \rangle_\mathrm{ss} = +\begin{pmatrix} +\frac{-2 F^2 \Delta}{(\Delta^2 + \frac{\gamma^2}{4})^2} \\ +\frac{2 F}{\Delta^2 + \frac{\gamma^2}{4}} \\ +\frac{-F^2 \gamma}{2 (\Delta^2 + \frac{\gamma^2}{4})^2} +\end{pmatrix} \, . +``` + +Although `QuantumToolbox.jl` has the [`steadystate`](@ref) function to directly compute the steady state without explicitly solving the master equation, here we use the [`mesolve`](@ref) function to integrate up to a long time $t_\mathrm{max}$, and then compute the expectation value of the number operator. We will demonstrate how to compute the gradient using both [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and [`Zygote.jl`](https://github.com/FluxML/Zygote.jl). + +### [Forward Mode AD with ForwardDiff.jl](@id doc:autodiff:forward) + +```@setup autodiff +using QuantumToolbox +``` + +We start by importing [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and defining the parameters and operators: + +```@example autodiff +using ForwardDiff + +const N = 20 +const a = destroy(N) +const ψ0 = fock(N, 0) +const t_max = 40 +const tlist = range(0, t_max, 100) +``` + +Then, we define a function that take the parameters `p` as an input and returns the expectation value of the number operator at `t_max`. We also define the analytical solution of the steady state photon number and its gradient for comparison: + +```@example autodiff +function my_f_mesolve_direct(p) + H = p[1] * a' * a + p[2] * (a + a') + c_ops = [sqrt(p[3]) * a] + sol = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) + return real(expect(a' * a, sol.states[end])) +end + +# Analytical solution +function my_f_analytical(p) + Δ, F, γ = p + return F^2 / (Δ^2 + γ^2 / 4) +end +function my_grad_analytical(p) + Δ, F, γ = p + return [ + -2 * F^2 * Δ / (Δ^2 + γ^2 / 4)^2, + 2 * F / (Δ^2 + γ^2 / 4), + -F^2 * γ / (2 * (Δ^2 + γ^2 / 4)^2) + ] +end +``` + +The gradient can be computed using `ForwardDiff.gradient`: + +```@example autodiff +Δ = 1.5 +F = 1.5 +γ = 1.5 +params = [Δ, F, γ] + +grad_exact = my_grad_analytical(params) +grad_fd = ForwardDiff.gradient(my_f_mesolve_direct, params) +``` + +and test if the results match: + +```@example autodiff +isapprox(grad_exact, grad_fd; atol = 1e-5) +``` + +### [Reverse Mode AD with Zygote.jl](@id doc:autodiff:reverse) + +Reverse-mode differentiation is significantly more challenging than forward-mode when dealing ODEs, as the complexity arises from the need to propagate gradients backward through the entire time evolution of the quantum state. + +`QuantumToolbox.jl` leverages the advanced capabilities of [`SciMLSensitivity.jl`](https://github.com/SciML/SciMLSensitivity.jl) to handle this complexity. [`SciMLSensitivity.jl`](https://github.com/SciML/SciMLSensitivity.jl) implements sophisticated methods for computing gradients of ODE solutions, such as the adjoint method, which computes gradients by solving an additional "adjoint" ODE backward in time. For more details on the adjoint method and other sensitivity analysis techniques, please refer to the [`SciMLSensitivity.jl` documentation](https://docs.sciml.ai/SciMLSensitivity/stable/). + +In order to reverse-differentiate the master equation, we need to define the operators as [`QuantumObjectEvolution`](@ref) objects, which use [`SciMLOperators.jl`](https://github.com/SciML/SciMLOperators.jl) to represent parameter-dependent operators. + +```@example autodiff +using Zygote +using SciMLSensitivity + +# For SciMLSensitivity.jl +coef_Δ(p, t) = p[1] +coef_F(p, t) = p[2] +coef_γ(p, t) = sqrt(p[3]) +H = QobjEvo(a' * a, coef_Δ) + QobjEvo(a + a', coef_F) +c_ops = [QobjEvo(a, coef_γ)] +const L = liouvillian(H, c_ops) + +function my_f_mesolve(p) + sol = mesolve( + L, + ψ0, + tlist, + progress_bar = Val(false), + params = p, + sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), + ) + + return real(expect(a' * a, sol.states[end])) +end +``` + +And the gradient can be computed using `Zygote.gradient`: + +```@example autodiff +grad_zygote = Zygote.gradient(my_f_mesolve, params)[1] +``` + +Finally, we can compare the results from [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and [`Zygote.jl`](https://github.com/FluxML/Zygote.jl): + +```@example autodiff +isapprox(grad_fd, grad_zygote; atol = 1e-5) +``` + +## [Conclusion](@id doc:autodiff:conclusion) + +In this section, we have explored the integration of automatic differentiation into `QuantumToolbox.jl`, enabling users to compute gradients of observables and cost functionals involving the time evolution of open quantum systems. We demonstrated how to differentiate the master equation using both forward mode with [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and reverse mode with [`Zygote.jl`](https://github.com/FluxML/Zygote.jl), showcasing the flexibility and power of automatic differentiation in quantum computing applications. AD can be applied to other functions in `QuantumToolbox.jl`, although the support is still experimental and not all functions are guaranteed to be compatible. We encourage users to experiment with AD in their quantum simulations and contribute to the ongoing development of this feature. \ No newline at end of file From 13709a17a1269f45dba6742843d75bdd76c8653f Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:21:00 +0200 Subject: [PATCH 302/329] Bump to v0.34.0 (#518) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe776e5f9..3ed85a05b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +## [v0.34.0] +Release date: 2025-07-29 + - Improve efficiency of `bloch_redfield_tensor` by avoiding unnecessary conversions. ([#509]) - Support `SciMLOperators v1.4+`. ([#470]) - Fix compatibility with `Makie v0.24+`. ([#513]) @@ -202,6 +205,7 @@ Release date: 2024-11-13 [v0.32.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.0 [v0.32.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.1 [v0.33.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.33.0 +[v0.34.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.34.0 [#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 [#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 [#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 diff --git a/Project.toml b/Project.toml index 0e02eea04..60f71f7c3 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" authors = ["Alberto Mercurio", "Yi-Te Huang"] -version = "0.33.0" +version = "0.34.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" From 5110d93ce0389d116d30c88984c6267d03112023 Mon Sep 17 00:00:00 2001 From: Li-Xun Cai <157601901+TendonFFF@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:42:09 +0800 Subject: [PATCH 303/329] Improve Bloch sphere rendering for animation (#520) --- CHANGELOG.md | 3 + .../users_guide/plotting_the_bloch_sphere.md | 64 ++++++++++++++++++- ext/QuantumToolboxMakieExt.jl | 44 ++++++++++--- test/ext-test/cpu/makie/makie_ext.jl | 8 +++ 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ed85a05b..46b6bb5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) +- Improve Bloch sphere rendering for animation. ([#520]) + ## [v0.34.0] Release date: 2025-07-29 @@ -292,3 +294,4 @@ Release date: 2024-11-13 [#513]: https://github.com/qutip/QuantumToolbox.jl/issues/513 [#515]: https://github.com/qutip/QuantumToolbox.jl/issues/515 [#517]: https://github.com/qutip/QuantumToolbox.jl/issues/517 +[#520]: https://github.com/qutip/QuantumToolbox.jl/issues/520 diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md index 8bb5facfe..a306d2977 100644 --- a/docs/src/users_guide/plotting_the_bloch_sphere.md +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -211,4 +211,66 @@ These properties can also be accessed via the `print` command: ```@example Bloch_sphere_rendering b = Bloch() print(b) -``` \ No newline at end of file +``` + +## Animating with the Bloch sphere + +The [`Bloch`](@ref) structure was designed from the outset to generate animations. To animate a set of vectors or data points, the basic idea is: plot the data at time ``t_1``, save the sphere, clear the sphere, plot data at ``t_2``, and so on. The easiest way to animate data on the Bloch sphere is to use the `record` function provided by [`Makie.jl`](https://docs.makie.org/stable/). We will demonstrate this functionality with the following example: the decay of a qubit on the Bloch sphere. + +```@example Bloch_sphere_rendering +# system parameters +ω = 2π +θ = 0.2π +n_th = 0.5 # temperature +γ1 = 0.5 +γ2 = 0.2 + +# operators and the Hamiltonian +sx = sigmax() +sy = sigmay() +sz = sigmaz() +sm = sigmam() +H = ω * (cos(θ) * sz + sin(θ) * sx) + +# collapse operators +c_op_list = ( + √(γ1 * (n_th + 1)) * sm, + √(γ1 * n_th) * sm', + √γ2 * sz +) + +# solving evolution +ψ0 = basis(2, 0) +tlist = LinRange(0, 4, 250) +sol = mesolve(H, ψ0, tlist, c_op_list, e_ops = (sx, sy, sz), progress_bar = Val(false)) +``` + +To animate a set of vectors or data points, we use the `record` function provided by [`Makie.jl`](https://docs.makie.org/stable/): + +```@example Bloch_sphere_rendering +# expectation values +x = real(sol.expect[1,:]) +y = real(sol.expect[2,:]) +z = real(sol.expect[3,:]) + +# create Bloch sphere +b = Bloch() +b.view = [50,30] +fig, lscene = render(b) + +# save animation +record(fig, "qubit_decay.mp4", eachindex(tlist), framerate = 20) do idx + clear!(b) + add_vectors!(b, [sin(θ), 0, cos(θ)]) + add_points!(b, [x[1:idx], y[1:idx], z[1:idx]]) + render(b, location = lscene) +end +nothing # hide +``` + +```@raw html +