From 80ce567481f4c9e11ce2f70bdec7898a6388e270 Mon Sep 17 00:00:00 2001 From: Andrew Kille <68079167+apkille@users.noreply.github.com> Date: Tue, 6 Aug 2024 18:06:06 -0400 Subject: [PATCH] Further development of Fock states (#69) * fock rules * Fock ops * docs and tests * reorganize predef file and docstrings * express and tests * update metadata * add fock.md to make.jl * review changes * rm space * some minor stylistic changes and and also breaking your code with a non-working example * update changelog and kwdef QuantumOpticsRepr --------- Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 6 +- Project.toml | 2 +- docs/make.jl | 1 + docs/src/QHO.md | 159 +++++++++++++++++++++++ docs/src/express.md | 27 +++- ext/QuantumOpticsExt/QuantumOpticsExt.jl | 39 ++---- src/QSymbolicsBase/QSymbolicsBase.jl | 12 +- src/QSymbolicsBase/express.jl | 4 +- src/QSymbolicsBase/predefined.jl | 48 ------- src/QSymbolicsBase/predefined_fock.jl | 139 ++++++++++++++++++++ src/QSymbolicsBase/rules.jl | 25 +++- test/test_express_opt.jl | 8 ++ test/test_fock.jl | 26 ++++ 13 files changed, 405 insertions(+), 91 deletions(-) create mode 100644 docs/src/QHO.md create mode 100644 src/QSymbolicsBase/predefined_fock.jl create mode 100644 test/test_fock.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d71bd9..6f5c779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # News -## v0.3.5 - dev +## v0.4.0 - 2024-08-03 - Cleaned up metadata decoration of struct definitions. +- Added documentation for quantum harmonic oscillators. +- Added phase-shift and displacement operators `DisplaceOp` and `PhaseShiftOp`. +- Simplification rules for Fock objects. +- **(breaking)** `FockBasisState` was renamed to `FockState`. ## v0.3.4 - 2024-07-22 diff --git a/Project.toml b/Project.toml index 1ed77ea..ad0c256 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumSymbolics" uuid = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" authors = ["QuantumSymbolics.jl contributors"] -version = "0.3.5-dev" +version = "0.4.0" [deps] Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" diff --git a/docs/make.jl b/docs/make.jl index e37a056..6bce4c3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -27,6 +27,7 @@ function main() "Getting Started with QuantumSymbolics.jl" => "introduction.md", "Express Functionality" => "express.md", "Qubit Basis Choice" => "qubit_basis.md", + "Quantum Harmonic Oscillators" => "QHO.md", "API" => "API.md", ] ) diff --git a/docs/src/QHO.md b/docs/src/QHO.md new file mode 100644 index 0000000..764e9eb --- /dev/null +++ b/docs/src/QHO.md @@ -0,0 +1,159 @@ +# Quantum Harmonic Oscillators + +```@meta +DocTestSetup = quote + using QuantumSymbolics, QuantumOptics +end +``` + +In this section, we describe symbolic representations of bosonic systems in QuantumSymbolics, which can be numerically translated to [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl). + +## States + +A Fock state is a state with well defined number of excitation quanta of a single quantum harmonic oscillator (an eigenstate of the number operator). In the following example, we create a `FockState` with 3 quanta in an infinite-dimension Fock space: + +```jldoctest +julia> f = FockState(3) +|3⟩ +``` + +Both vacuum (ground) and single-photon states are defined as constants in both unicode and ASCII for convenience: + +- `vac = F₀ = F0` $=|0\rangle$ in the number state representation, +- `F₁ = F1` $=|1\rangle$ in the number state representation. + +To create quantum analogues of a classical harmonic oscillator, or monochromatic electromagnetic waves, we can define a coherent (a.k.a. semi-classical) state $|\alpha\rangle$, where $\alpha$ is a complex amplitude, with `CoherentState(α::Number)`: + +```jldoctest +julia> c = CoherentState(im) +|im⟩ +``` +!!! note "Naming convention for quantum harmonic oscillator bases" + The defined basis for arbitrary symbolic bosonic states is a `FockBasis` object, due to a shared naming interface for Quantum physics packages. For instance, the command `basis(CoherentState(im))` will output `Fock(cutoff=Inf)`. This may lead to confusion, as not all bosonic states are Fock states. However, this is simply a naming convention for the basis, and symbolic and numerical results are not affected by it. + +## Operators + +Operations on bosonic states are supported, and can be simplified with `qsimplify` and its rewriter `qsimplify_fock`. For instance, we can apply the raising (creation) $\hat{a}^{\dagger}$ and lowering (annihilation or destroy) $\hat{a}$ operators on a Fock state as follows: + +```jldoctest +julia> f = FockState(3); + +julia> raise = Create*f +a†|3⟩ + +julia> qsimplify(raise, rewriter=qsimplify_fock) +(sqrt(4))|4⟩ + +julia> lower = Destroy*f +a|3⟩ + +julia> qsimplify(lower, rewriter=qsimplify_fock) +(sqrt(3))|2⟩ +``` +Or, we can apply the number operator $\hat{n}$ to our Fock state: + +```jldoctest +julia> f = FockState(3); + +julia> num = N*f +n|3⟩ + +julia> qsimplify(num, rewriter=qsimplify_fock) +3|3⟩ +``` + +Constants are defined for number and ladder operators in unicode and ASCII: + +- `N = n̂` $=\hat{n}$, +- `Create = âꜛ` $=\hat{a}^{\dagger}$, +- `Destroy = â` $=\hat{a}$. + +Phase-shift $U(\theta)$ and displacement $D(\alpha)$ operators, defined respectively as +$$U(\theta) = \exp\left(-i\theta\hat{n}\right) \quad \text{and} \quad D(\alpha) = \exp\left(\alpha\hat{a}^{\dagger} - \alpha\hat{a}\right),$$ +can be defined with usual simplification rules. Consider the following example: + +```jldoctest +julia> displace = DisplaceOp(im) +D(im) + +julia> c = qsimplify(displace*vac, rewriter=qsimplify_fock) +|im⟩ + +julia> phase = PhaseShiftOp(pi) +U(π) + +julia> qsimplify(phase*c, rewriter=qsimplify_fock) +|1.2246467991473532e-16 - 1.0im⟩ +``` +Here, we generated a coherent state $|i\rangle$ from the vacuum state $|0\rangle$ by applying the displacement operator defined by `DisplaceOp`. Then, we shifted its phase by $\pi$ with the phase shift operator (which is called with `PhaseShiftOp`) to get the result $|-i\rangle$. + +Summarized below are supported bosonic operators. + +- Number operator: `NumberOp()`, +- Creation operator: `CreateOp()`, +- Annihilation operator: `DestroyOp()`, +- Phase-shift operator: `PhaseShiftOp(phase::Number)`, +- Displacement operator: `DisplaceOp(alpha::Number)`. + +## Numerical Conversions to QuantumOptics.jl + +Bosonic systems can be translated to the ket representation with `express`. For instance: + +```jldoctest +julia> f = FockState(1); + +julia> express(f) +Ket(dim=3) + basis: Fock(cutoff=2) + 0.0 + 0.0im + 1.0 + 0.0im + 0.0 + 0.0im + +julia> express(Create) |> dense +Operator(dim=3x3) + basis: Fock(cutoff=2) + 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 1.41421+0.0im 0.0+0.0im + +julia> express(Create*f) +Ket(dim=3) + basis: Fock(cutoff=2) + 0.0 + 0.0im + 0.0 + 0.0im + 1.4142135623730951 + 0.0im + +julia> express(Destroy*f) +Ket(dim=3) + basis: Fock(cutoff=2) + 1.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im +``` + +!!! warning "Cutoff specifications for numerical representations of quantum harmonic oscillators" + Symbolic bosonic states and operators are naturally represented in an infinite dimension basis. For numerical conversions of such quantum objects, a finite cutoff of the highest allowed state must be defined. By default, the basis dimension of numerical conversions is set to 3 (so the number representation cutoff is 2), as demonstrated above. To define a different cutoff, one must customize the `QuantumOpticsRepr` instance, e.g. provide `QuantumOpticsRepr(cutoff=n::Int)` to `express`. + +If we wish to specify a different numerical cutoff, say 4, to the previous examples, then we rewrite them as follows: + +```jldoctest +julia> f = FockState(1); + +julia> express(f, QuantumOpticsRepr(cutoff=4)) +Ket(dim=5) + basis: Fock(cutoff=4) + 0.0 + 0.0im + 1.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + 0.0 + 0.0im + +julia> express(Create, QuantumOpticsRepr(4)) |> dense +Operator(dim=5x5) + basis: Fock(cutoff=4) + 0.0+0.0im 0.0+0.0im 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 1.41421+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im + 0.0+0.0im 0.0+0.0im 1.73205+0.0im 0.0+0.0im 0.0+0.0im + 0.0+0.0im 0.0+0.0im 0.0+0.0im 2.0+0.0im 0.0+0.0im +``` \ No newline at end of file diff --git a/docs/src/express.md b/docs/src/express.md index 8fbb1e8..a8af033 100644 --- a/docs/src/express.md +++ b/docs/src/express.md @@ -6,7 +6,7 @@ DocTestSetup = quote end ``` -A principle feature of `QuantumSymbolics` is to numerically represent symbolic quantum expressions in various formalisms using [`express`](@ref). In particular, one can translate symbolic logic to back-end toolboxes such as `QuantumOptics.jl` or `QuantumClifford.jl` for simulating quantum systems with great flexibiity. +A principle feature of `QuantumSymbolics` is to numerically represent symbolic quantum expressions in various formalisms using [`express`](@ref). In particular, one can translate symbolic logic to back-end toolboxes such as [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) or [`QuantumClifford.jl`](https://github.com/QuantumSavory/QuantumClifford.jl) for simulating quantum systems with great flexibiity. As a straightforward example, consider the spin-up state $|\uparrow\rangle = |0\rangle$, the eigenstate of the Pauli operator $Z$, which can be expressed in `QuantumSymbolics` as follows: @@ -14,7 +14,8 @@ As a straightforward example, consider the spin-up state $|\uparrow\rangle = |0\ using QuantumSymbolics, QuantumClifford, QuantumOptics # hide ψ = Z1 ``` -Using [`express`](@ref), we can translate this symbolic object into its numerical state vector form in `QuantumOptics.jl`. + +Using [`express`](@ref), we can translate this symbolic object into its numerical state vector form in [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl). ```@example 1 express(ψ) @@ -26,7 +27,7 @@ By default, [`express`](@ref) converts a quantum object with `QuantumOpticRepr`. ψ.metadata ``` -The caching feature of [`express`](@ref) prevents a specific representation for a symbolic quantum object from being computed more than once. This becomes handy for translations of more complex operations, which can become computationally expensive. We also have the ability to express $|Z_1\rangle$ in the Clifford formalism with `QuantumClifford.jl`: +The caching feature of [`express`](@ref) prevents a specific representation for a symbolic quantum object from being computed more than once. This becomes handy for translations of more complex operations, which can become computationally expensive. We also have the ability to express $|Z_1\rangle$ in the Clifford formalism with [`QuantumClifford.jl`](https://github.com/QuantumSavory/QuantumClifford.jl): ```@example 1 express(ψ, CliffordRepr()) @@ -56,4 +57,24 @@ julia> express(σʸ, CliffordRepr(), UseAsObservable()) julia> express(σʸ, CliffordRepr(), UseAsOperation()) sY +``` + +Another edge case is translations with `QuantumOpticsRepr`, where we can additionally define a finite cutoff for bosonic states and operators, as discussed in the [quantum harmonic oscillators page](@ref Quantum-Harmonic-Oscillators). The default cutoff for such objects is 2, however a different cutoff can be specified by passing an integer to `QuantumOpticsRepr` in an `express` call. Let us see an example with the number operator: + +```jldoctest +julia> express(N) |> dense +Operator(dim=3x3) + basis: Fock(cutoff=2) + 0.0+0.0im 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 2.0+0.0im + +julia> express(N, QuantumOpticsRepr(cutoff=4)) |> dense +Operator(dim=5x5) + basis: Fock(cutoff=4) + 0.0+0.0im 0.0+0.0im 0.0+0.0im 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 2.0+0.0im 0.0+0.0im 0.0+0.0im + 0.0+0.0im 0.0+0.0im 0.0+0.0im 3.0+0.0im 0.0+0.0im + 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 4.0+0.0im ``` \ No newline at end of file diff --git a/ext/QuantumOpticsExt/QuantumOpticsExt.jl b/ext/QuantumOpticsExt/QuantumOpticsExt.jl index ec0188d..311e171 100644 --- a/ext/QuantumOpticsExt/QuantumOpticsExt.jl +++ b/ext/QuantumOpticsExt/QuantumOpticsExt.jl @@ -1,15 +1,16 @@ module QuantumOpticsExt using QuantumInterface, QuantumOpticsBase +using QuantumInterface: samebases using QuantumSymbolics using QuantumSymbolics: HGate, XGate, YGate, ZGate, CPHASEGate, CNOTGate, PauliP, PauliM, XCXGate, XCYGate, XCZGate, YCXGate, YCYGate, YCZGate, ZCXGate, ZCYGate, ZCZGate, XBasisState, YBasisState, ZBasisState, NumberOp, CreateOp, DestroyOp, - FockBasisState, + FockState, MixedState, IdentityOp, - qubit_basis, inf_fock_basis + qubit_basis import QuantumSymbolics: express, express_nolookup using TermInterface using TermInterface: isexpr, head, operation, arguments, metadata @@ -70,34 +71,14 @@ express_nolookup(s::XBasisState, ::QuantumOpticsRepr) = (_s₊,_s₋)[s.idx] express_nolookup(s::YBasisState, ::QuantumOpticsRepr) = (_i₊,_i₋)[s.idx] express_nolookup(s::ZBasisState, ::QuantumOpticsRepr) = (_l0,_l1)[s.idx] -function express_nolookup(o::FockBasisState, r::QuantumOpticsRepr) - @warn "Fock space cutoff is not specified so we default to 2" - @assert o.idx<2 "without a specified cutoff you can not create states higher than 1 photon" - return (_f0₂,_f1₂)[o.idx+1] -end -function express_nolookup(o::NumberOp, r::QuantumOpticsRepr) - @warn "Fock space cutoff is not specified so we default to 2" - return _n₂ -end -function express_nolookup(o::CreateOp, r::QuantumOpticsRepr) - @warn "Fock space cutoff is not specified so we default to 2" - return _ad₂ -end -function express_nolookup(o::DestroyOp, r::QuantumOpticsRepr) - @warn "Fock space cutoff is not specified so we default to 2" - return _a₂ -end - +express_nolookup(s::FockState, r::QuantumOpticsRepr) = fockstate(FockBasis(r.cutoff),s.idx) +express_nolookup(s::CoherentState, r::QuantumOpticsRepr) = coherentstate(FockBasis(r.cutoff),s.alpha) +express_nolookup(o::NumberOp, r::QuantumOpticsRepr) = number(FockBasis(r.cutoff)) +express_nolookup(o::CreateOp, r::QuantumOpticsRepr) = create(FockBasis(r.cutoff)) +express_nolookup(o::DestroyOp, r::QuantumOpticsRepr) = destroy(FockBasis(r.cutoff)) +express_nolookup(o::DisplaceOp, r::QuantumOpticsRepr) = displace(FockBasis(r.cutoff), o.alpha) express_nolookup(x::MixedState, ::QuantumOpticsRepr) = identityoperator(basis(x))/length(basis(x)) # TODO there is probably a more efficient way to represent it -function express_nolookup(x::IdentityOp, ::QuantumOpticsRepr) - b = basis(x) - if b!=inf_fock_basis - return identityoperator(basis(x)) # TODO there is probably a more efficient way to represent it - else - @warn "Fock space cutoff is not specified so we default to 2" - return identityoperator(_bf2) - end -end +express_nolookup(x::IdentityOp, r::QuantumOpticsRepr) = identityoperator(FockBasis(r.cutoff)) express_nolookup(p::PauliNoiseCPTP, ::QuantumOpticsRepr) = LazySuperSum(SpinBasis(1//2), [1-p.px-p.py-p.pz,p.px,p.py,p.pz], [LazyPrePost(_id,_id),LazyPrePost(_x,_x),LazyPrePost(_y,_y),LazyPrePost(_z,_z)]) diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index 985d02e..915f28b 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -1,5 +1,5 @@ using Symbolics -import Symbolics: simplify +import Symbolics: simplify,Term using SymbolicUtils import SymbolicUtils: Symbolic,_isone,flatten_term,isnotflat,Chain,Fixpoint,Prewalk,sorted_arguments using TermInterface @@ -28,7 +28,7 @@ export SymQObj,QObj, I,X,Y,Z,σˣ,σʸ,σᶻ,Pm,Pp,σ₋,σ₊, H,CNOT,CPHASE,XCX,XCY,XCZ,YCX,YCY,YCZ,ZCX,ZCY,ZCZ, X1,X2,Y1,Y2,Z1,Z2,X₁,X₂,Y₁,Y₂,Z₁,Z₂,L0,L1,Lp,Lm,Lpi,Lmi,L₀,L₁,L₊,L₋,L₊ᵢ,L₋ᵢ, - vac,F₀,F0,F₁,F1, + vac,F₀,F0,F₁,F1,inf_fock_basis, N,n̂,Create,âꜛ,Destroy,â,basis,SpinBasis,FockBasis, SBra,SKet,SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,SSuperOperator, @ket,@bra,@op,@superop, @@ -40,10 +40,10 @@ export SymQObj,QObj, MixedState,IdentityOp, SApplyKet,SApplyBra,SMulOperator,SSuperOpApply,SCommutator,SAnticommutator,SBraKet,SOuterKetBra, HGate,XGate,YGate,ZGate,CPHASEGate,CNOTGate, - XBasisState,YBasisState,ZBasisState, - NumberOp,CreateOp,DestroyOp, + XBasisState,YBasisState,ZBasisState,FockState,CoherentState, + NumberOp,CreateOp,DestroyOp,PhaseShiftOp,DisplaceOp, XCXGate,XCYGate,XCZGate,YCXGate,YCYGate,YCZGate,ZCXGate,ZCYGate,ZCZGate, - qsimplify,qsimplify_pauli,qsimplify_commutator,qsimplify_anticommutator, + qsimplify,qsimplify_pauli,qsimplify_commutator,qsimplify_anticommutator,qsimplify_fock, qexpand, isunitary, KrausRepr,kraus @@ -119,7 +119,6 @@ end Base.isequal(::SymQObj, ::Symbolic{Complex}) = false Base.isequal(::Symbolic{Complex}, ::SymQObj) = false - # TODO check that this does not cause incredibly bad runtime performance # use a macro to provide specializations if that is indeed the case propsequal(x,y) = all(n->(n==:metadata || isequal(getproperty(x,n),getproperty(y,n))), propertynames(x)) @@ -142,6 +141,7 @@ include("basic_superops.jl") include("linalg.jl") include("predefined.jl") include("predefined_CPTP.jl") +include("predefined_fock.jl") ## # Symbolic and simplification rules diff --git a/src/QSymbolicsBase/express.jl b/src/QSymbolicsBase/express.jl index 8fd6466..e40b2f0 100644 --- a/src/QSymbolicsBase/express.jl +++ b/src/QSymbolicsBase/express.jl @@ -78,7 +78,9 @@ end ## """Representation using kets, bras, density matrices, and superoperators governed by `QuantumOptics.jl`.""" -struct QuantumOpticsRepr <: AbstractRepresentation end +@kwdef struct QuantumOpticsRepr <: AbstractRepresentation + cutoff::Int = 2 +end """Similar to `QuantumOpticsRepr`, but using trajectories instead of superoperators.""" struct QuantumMCRepr <: AbstractRepresentation end """Representation using tableaux governed by `QuantumClifford.jl`""" diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 2eb194d..789f045 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -25,24 +25,6 @@ symbollabel(x::YBasisState) = "Y$(num_to_sub(x.idx))" end symbollabel(x::ZBasisState) = "Z$(num_to_sub(x.idx))" -@withmetadata struct FockBasisState <: SpecialKet - idx::Int - basis::Basis -end -symbollabel(x::FockBasisState) = "$(x.idx)" - -@withmetadata struct DiscreteCoherentState <: SpecialKet - alpha::Number # TODO parameterize - basis::Basis -end -symbollabel(x::DiscreteCoherentState) = "$(x.alpha)" - -@withmetadata struct ContinuousCoherentState <: SpecialKet - alpha::Number # TODO parameterize - basis::Basis -end -symbollabel(x::ContinuousCoherentState) = "$(x.alpha)" - @withmetadata struct MomentumEigenState <: SpecialKet p::Number # TODO parameterize basis::Basis @@ -69,13 +51,6 @@ const Z1 = const Z₁ = const L0 = const L₀ = ZBasisState(1, qubit_basis) """Basis state of σᶻ""" const Z2 = const Z₂ = const L1 = const L₁ = ZBasisState(2, qubit_basis) -const inf_fock_basis = FockBasis(Inf,0.) -"""Vacuum basis state of n""" -const vac = const F₀ = const F0 = FockBasisState(0,inf_fock_basis) -"""Single photon basis state of n""" -const F₁ = const F1 = FockBasisState(1,inf_fock_basis) - - ## # Gates and Operators on qubits ## @@ -173,29 +148,6 @@ const CNOT = CNOTGate() """CPHASE gate""" const CPHASE = CPHASEGate() -## -# Gates and Operators on harmonic oscillators -## - -abstract type AbstractSingleBosonOp <: Symbolic{AbstractOperator} end -abstract type AbstractSingleBosonGate <: AbstractSingleBosonOp end # TODO maybe an IsUnitaryTrait is a better choice -isexpr(::AbstractSingleBosonGate) = false -basis(x::AbstractSingleBosonOp) = inf_fock_basis - -@withmetadata struct NumberOp <: AbstractSingleBosonOp end -symbollabel(::NumberOp) = "n" -@withmetadata struct CreateOp <: AbstractSingleBosonOp end -symbollabel(::CreateOp) = "a†" -@withmetadata struct DestroyOp <: AbstractSingleBosonOp end -symbollabel(::DestroyOp) = "a" - -"""Number operator, also available as the constant `n̂`""" -const N = const n̂ = NumberOp() -"""Creation operator, also available as the constant `âꜛ` - there is no unicode dagger superscript, so we use the uparrow""" -const Create = const âꜛ = CreateOp() -"""Annihilation operator, also available as the constant `â`""" -const Destroy = const â = DestroyOp() - ## # Other special or useful objects ## diff --git a/src/QSymbolicsBase/predefined_fock.jl b/src/QSymbolicsBase/predefined_fock.jl new file mode 100644 index 0000000..5434194 --- /dev/null +++ b/src/QSymbolicsBase/predefined_fock.jl @@ -0,0 +1,139 @@ +## +# Predefined objects in the Fock space. +## + +"""Fock state in defined Fock basis.""" +@withmetadata struct FockState <: SpecialKet + idx::Int + basis::FockBasis +end +FockState(idx::Int) = FockState(idx, inf_fock_basis) +symbollabel(x::FockState) = "$(x.idx)" + +"""Coherent state in defined Fock basis.""" +@withmetadata struct CoherentState <: SpecialKet + alpha::Number # TODO parameterize + basis::FockBasis +end +CoherentState(alpha::Number) = CoherentState(alpha, inf_fock_basis) +symbollabel(x::CoherentState) = "$(x.alpha)" + +const inf_fock_basis = FockBasis(Inf,0.) +"""Vacuum basis state of n""" +const vac = const F₀ = const F0 = FockState(0) +"""Single photon basis state of n""" +const F₁ = const F1 = FockState(1) + +## +# Gates and Operators on harmonic oscillators +## + +abstract type AbstractSingleBosonOp <: Symbolic{AbstractOperator} end +abstract type AbstractSingleBosonGate <: AbstractSingleBosonOp end # TODO maybe an IsUnitaryTrait is a better choice +isexpr(::AbstractSingleBosonGate) = false +basis(x::AbstractSingleBosonOp) = inf_fock_basis + +"""Number operator. + +```jldoctest +julia> f = FockState(2) +|2⟩ + +julia> num = NumberOp() +n + +julia> qsimplify(num*f, rewriter=qsimplify_fock) +2|2⟩ +``` +""" +@withmetadata struct NumberOp <: AbstractSingleBosonOp + basis::FockBasis +end +NumberOp() = NumberOp(inf_fock_basis) +symbollabel(::NumberOp) = "n" + +"""Creation (raising) operator. + +```jldoctest +julia> f = FockState(2) +|2⟩ + +julia> create = CreateOp() +a† + +julia> qsimplify(create*f, rewriter=qsimplify_fock) +(sqrt(3))|3⟩ +``` +""" +@withmetadata struct CreateOp <: AbstractSingleBosonOp + basis::FockBasis +end +CreateOp() = CreateOp(inf_fock_basis) +symbollabel(::CreateOp) = "a†" + +"""Annihilation (lowering or destroy) operator in defined Fock basis. + +```jldoctest +julia> f = FockState(2) +|2⟩ + +julia> destroy = DestroyOp() +a + +julia> qsimplify(destroy*f, rewriter=qsimplify_fock) +(sqrt(2))|1⟩ +``` +""" +@withmetadata struct DestroyOp <: AbstractSingleBosonOp + basis::FockBasis +end +DestroyOp() = DestroyOp(inf_fock_basis) +symbollabel(::DestroyOp) = "a" + +"""Phase-shift operator in defined Fock basis. + +```jldoctest +julia> c = CoherentState(im) +|im⟩ + +julia> phase = PhaseShiftOp(pi) +U(π) + +julia> qsimplify(phase*c, rewriter=qsimplify_fock) +|1.2246467991473532e-16 - 1.0im⟩ +``` +""" +@withmetadata struct PhaseShiftOp <: AbstractSingleBosonOp + phase::Number + basis::FockBasis +end +PhaseShiftOp(phase::Number) = PhaseShiftOp(phase, inf_fock_basis) +symbollabel(x::PhaseShiftOp) = "U($(x.phase))" + +"""Displacement operator in defined Fock basis. + +```jldoctest +julia> f = FockState(0) +|0⟩ + +julia> displace = DisplaceOp(im) +D(im) + +julia> qsimplify(displace*f, rewriter=qsimplify_fock) +|im⟩ +``` +""" +@withmetadata struct DisplaceOp <: AbstractSingleBosonOp + alpha::Number + basis::FockBasis +end +DisplaceOp(alpha::Number) = DisplaceOp(alpha, inf_fock_basis) +symbollabel(x::DisplaceOp) = "D($(x.alpha))" + +"""Number operator, also available as the constant `n̂`, in an infinite dimension Fock basis.""" +const N = const n̂ = NumberOp() +"""Creation operator, also available as the constant `âꜛ`, in an infinite dimension Fock basis. +There is no unicode dagger superscript, so we use the uparrow""" +const Create = const âꜛ = CreateOp() +"""Annihilation operator, also available as the constant `â`, in an infinite dimension Fock basis.""" +const Destroy = const â = DestroyOp() \ No newline at end of file diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index 98b3d4f..ac31f95 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -85,10 +85,30 @@ RULES_ANTICOMMUTATOR = [ @rule(anticommutator(~o1::_isa(ZGate), ~o2::_isa(XGate)) => 0), @rule(anticommutator(~o1::_isa(YGate), ~o2::_isa(XGate)) => 0), @rule(anticommutator(~o1::_isa(ZGate), ~o2::_isa(YGate)) => 0), - @rule(anticommutator(~o1::_isa(XGate), ~o2::_isa(ZGate)) => 0) + @rule(anticommutator(~o1::_isa(XGate), ~o2::_isa(ZGate)) => 0), + @rule(commutator(~o1::_isa(DestroyOp), ~o2::_isa(CreateOp)) => IdentityOp((~o1).basis)), + @rule(commutator(~o1::_isa(CreateOp), ~o2::_isa(DestroyOp)) => -IdentityOp((~o1).basis)), + @rule(commutator(~o1::_isa(NumberOp), ~o2::_isa(DestroyOp)) => -(~o2)), + @rule(commutator(~o1::_isa(DestroyOp), ~o2::_isa(NumberOp)) => (~o1)), + @rule(commutator(~o1::_isa(NumberOp), ~o2::_isa(CreateOp)) => (~o2)), + @rule(commutator(~o1::_isa(CreateOp), ~o2::_isa(NumberOp)) => -(~o1)) ] -RULES_SIMPLIFY = [RULES_PAULI; RULES_COMMUTATOR; RULES_ANTICOMMUTATOR] +RULES_FOCK = [ + @rule(~o::_isa(DestroyOp) * ~k::_isequal(vac) => SZeroKet()), + @rule(~o::_isa(CreateOp) * ~k::_isa(FockState) => Term(sqrt,[(~k).idx+1])*FockState((~k).idx+1)), + @rule(~o::_isa(DestroyOp) * ~k::_isa(FockState) => Term(sqrt,[(~k).idx])*FockState((~k).idx-1)), + @rule(~o::_isa(NumberOp) * ~k::_isa(FockState) => (~k).idx*(~k)), + @rule(~o::_isa(DestroyOp) * ~k::_isa(CoherentState) => (~k).alpha*(~k)), + @rule(~o::_isa(PhaseShiftOp) * ~k::_isa(CoherentState) => CoherentState((~k).alpha * exp(-im*(~o).phase))), + @rule(dagger(~o1::_isa(PhaseShiftOp)) * ~o2::_isa(DestroyOp) * ~o1 => ~o2*exp(-im*((~o1).phase))), + @rule(~o1::_isa(PhaseShiftOp) * ~o2::_isa(DestroyOp) * dagger(~o1) => ~o2*exp(im*((~o1).phase))), + @rule(dagger(~o1::_isa(DisplaceOp)) * ~o2::_isa(DestroyOp) * ~o1 => (~o2) + (~o1).alpha*IdentityOp((~o2).basis)), + @rule(dagger(~o1::_isa(DisplaceOp)) * ~o2::_isa(CreateOp) * ~o1 => (~o2) + conj((~o1).alpha)*IdentityOp((~o2).basis)), + @rule(~o::_isa(DisplaceOp) * ~k::((x->(isa(x,FockState) && x.idx == 0))) => CoherentState((~o).alpha)) +] + +RULES_SIMPLIFY = [RULES_PAULI; RULES_COMMUTATOR; RULES_ANTICOMMUTATOR; RULES_FOCK] ## # Simplification rewriters @@ -97,6 +117,7 @@ RULES_SIMPLIFY = [RULES_PAULI; RULES_COMMUTATOR; RULES_ANTICOMMUTATOR] qsimplify_pauli = Chain(RULES_PAULI) qsimplify_commutator = Chain(RULES_COMMUTATOR) qsimplify_anticommutator = Chain(RULES_ANTICOMMUTATOR) +qsimplify_fock = Chain(RULES_FOCK) """ qsimplify(s; rewriter=nothing) diff --git a/test/test_express_opt.jl b/test/test_express_opt.jl index 9d86060..845148d 100644 --- a/test/test_express_opt.jl +++ b/test/test_express_opt.jl @@ -35,4 +35,12 @@ @test express(op*state) ≈ express(F1⊗Z2) state = (3im*(2*dagger(Z1)+dagger(Y1))) * (3im*(2*X1+X2)) + + cstate = CoherentState(im, inf_fock_basis) + displace = DisplaceOp(im,inf_fock_basis) + phase = PhaseShiftOp(im, inf_fock_basis) + @test express(N*F1) ≈ express(N)*express(F1) + @test express(Create*F1) ≈ express(Create)*express(F1) + @test express(Destroy*F1) ≈ express(Destroy)*express(F1) + @test express(displace*cstate) ≈ express(displace)*express(cstate) end diff --git a/test/test_fock.jl b/test/test_fock.jl new file mode 100644 index 0000000..9ea691b --- /dev/null +++ b/test/test_fock.jl @@ -0,0 +1,26 @@ +@testitem "Fock" begin + using Symbolics: Term + state1 = FockState(1) + state2 = FockState(2) + cstate = CoherentState(im) + phase1 = PhaseShiftOp(0) + phase2 = PhaseShiftOp(pi) + displace = DisplaceOp(im) + + @testset "ladder and number operators" begin + @test isequal(qsimplify(Destroy*vac, rewriter=qsimplify_fock), SZeroKet()) + @test isequal(qsimplify(Create*state1, rewriter=qsimplify_fock), Term(sqrt, [2])*state2) + @test isequal(qsimplify(Destroy*state2, rewriter=qsimplify_fock), Term(sqrt, [2])*state1) + @test isequal(qsimplify(N*state2, rewriter=qsimplify_fock), 2*state2) + @test isequal(qsimplify(Destroy*cstate, rewriter=qsimplify_fock), im*cstate) + end + + @testset "Displacement and phase operators" begin + @test isequal(qsimplify(phase1*cstate, rewriter=qsimplify_fock), CoherentState(im)) + @test isequal(qsimplify(dagger(phase2)*Destroy*phase2, rewriter=qsimplify_fock), Destroy*exp(-im*pi)) + @test isequal(qsimplify(phase2*Destroy*dagger(phase2), rewriter=qsimplify_fock), Destroy*exp(im*pi)) + @test isequal(qsimplify(dagger(displace)*Destroy*displace, rewriter=qsimplify_fock), Destroy + im*IdentityOp(inf_fock_basis)) + @test isequal(qsimplify(dagger(displace)*Create*displace, rewriter=qsimplify_fock), Create - im*IdentityOp(inf_fock_basis)) + @test isequal(qsimplify(displace*vac, rewriter=qsimplify_fock), cstate) + end +end \ No newline at end of file