Skip to content

Commit

Permalink
Merge branch 'main' into rbali_print
Browse files Browse the repository at this point in the history
  • Loading branch information
Krastanov authored Nov 14, 2024
2 parents 499236f + 9928655 commit 3005796
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 45 deletions.
22 changes: 15 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# News

## v0.4.4 - 2024-08-13
## v0.4.6 - dev

- Operators now print with hat
- Operators now print with a hat

## v0.4.5 - 2024-11-14

- Updated compat lower bounds for Symbolics to v6 (and for SymbolicUtils and TermInterface)

## v0.4.4 - 2024-09-16

- Implement squeezing with `SqueezeOp` and `SqueezedState`.

## v0.4.3 - 2024-08-13

Expand All @@ -23,14 +31,14 @@
- 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

- Added `tr` and `ptrace` functionalities.
- New symbolic superoperator representations.
- Added linear algebra operations `exp`, `vec`, `conj`, `transpose`.
- Created a getting-started guide in docs.

## v0.3.3 - 2024-07-12

- Added single qubit simplification rules.
Expand All @@ -51,7 +59,7 @@
- Implement zero objects.
- Equality for commutative operations, hashing, and lexicographic ordering when printing.
- Added tests.

## v0.3.0 - 2024-06-12

- Bump compat for symbolics-related foundational packages.
Expand All @@ -78,12 +86,12 @@
- Add all conditional Paulis.
- Remove `stab_to_ket` in favor of directly using the `Ket` constructor.

## v0.2.2 - 2023-06-28
## v0.2.2 - 2023-06-28

- Bump `QuantumInterface` compat.
- Upstream some `apply!` definitions to QuantumOpticsBase.

## v0.2.1 - 2023-06-11
## v0.2.1 - 2023-06-11

- Bump `QuantumInterface` compat.

Expand Down
8 changes: 4 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "QuantumSymbolics"
uuid = "efa7fd63-0460-4890-beb7-be1bbdfbaeae"
authors = ["QuantumSymbolics.jl contributors"]
version = "0.4.3"
version = "0.4.5"

[deps]
Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
Expand Down Expand Up @@ -30,7 +30,7 @@ PrecompileTools = "1.2"
QuantumClifford = "0.8.19, 0.9"
QuantumInterface = "0.3.3"
QuantumOpticsBase = "0.4.22, 0.5"
SymbolicUtils = "2.0.2, 3"
Symbolics = "5.30.3"
TermInterface = "0.4, 0.5, 1, 2"
SymbolicUtils = "3.7"
Symbolics = "6"
TermInterface = "2"
julia = "1.10"
25 changes: 17 additions & 8 deletions docs/src/QHO.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ In this section, we describe symbolic representations of bosonic systems in Quan

## 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:
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`](@ref) with 3 quanta in an infinite-dimension Fock space:

```jldoctest
julia> f = FockState(3)
Expand All @@ -22,7 +22,7 @@ Both vacuum (ground) and single-photon states are defined as constants in both u
- `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)`:
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`](@ref):

```jldoctest
julia> c = CoherentState(im)
Expand All @@ -31,9 +31,15 @@ julia> c = CoherentState(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.

Summarized below are supported bosonic states.

- Fock state: `FockState(idx::Int)`,
- Coherent state: `CoherentState(alpha::Number)`,
- Squeezed vacuum state: `SqueezedState(z::Number)`.

## 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:
Operations on bosonic states are supported, and can be simplified with [`qsimplify`](@ref) 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);
Expand Down Expand Up @@ -68,8 +74,10 @@ Constants are defined for number and ladder operators in unicode and ASCII:
- `Create = âꜛ` $=\hat{a}^{\dagger}$,
- `Destroy = â` $=\hat{a}$.

Phase-shift $U(\theta)$ and displacement $D(\alpha)$ operators, defined respectively as
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
Expand All @@ -85,19 +93,20 @@ 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$.
Here, we generated a coherent state $|i\rangle$ from the vacuum state $|0\rangle$ by applying the displacement operator defined by [`DisplaceOp`](@ref). Then, we shifted its phase by $\pi$ with the phase shift operator (which is called with [`PhaseShiftOp`](@ref)) 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)`.
- Displacement operator: `DisplaceOp(alpha::Number)`,
- Squeezing operator: `SqueezeOp(z::Number)`.

## Numerical Conversions to QuantumOptics.jl

Bosonic systems can be translated to the ket representation with `express`. For instance:
Bosonic systems can be translated to the ket representation with [`express`](@ref). For instance:

```jldoctest
julia> f = FockState(1);
Expand Down Expand Up @@ -132,7 +141,7 @@ Ket(dim=3)
```

!!! 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`.
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`](@ref).

If we wish to specify a different numerical cutoff, say 4, to the previous examples, then we rewrite them as follows:

Expand Down
50 changes: 50 additions & 0 deletions docs/src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,56 @@ Below, we state all of the supported linear algebra operations on quantum object
- exponential of an operator: [`exp`](@ref),
- vectorization of an operator: [`vec`](@ref).

## Predefined Quantum Objects

So far in this tutorial, we have considered arbitrary kets, bras, operators, and their corresponding operations. This package supports predefined quantum objects and operations in several formalisms, which are discussed in detail in other sections (see, for example, the [quantum harmonic oscillators](@ref Quantum-Harmonic-Oscillators) or [qubit basis](@ref Typical-Qubit-Bases) pages). To get a taste of what's available, let us consider a few symbolic examples. For a complete description, see the [full API page](@ref Full-API).

Quantum gates and their basis states can be represented symbolically:

```jldoctest
julia> CNOT # CNOT Gate
CNOT
julia> X, Y, Z, I # Pauli operators
(X, Y, Z, 𝕀)
julia> X1, X2 # Eigenstates of the Pauli X operator
(|X₁⟩, |X₂⟩)
julia> CPHASE * (Z1 ⊗ Z2) # Application of CPHASE gate on |01⟩
CPHASE|Z₁⟩|Z₂⟩
```

We also have symbolic representations of bosonic systems:

```jldoctest
julia> FockState(4) # Fock state with 4 excitation quanta
|4⟩
julia> Create, Destroy # creation and annihilation operators
(a†, a)
julia> DisplaceOp(im) # Displacement operator for single bosonic mode
D(im)
julia> N * vac # Application of number operator on vacuum state
n|0⟩
```

If we want to substitute a predefined quantum object into a general symbolic expression, we can use the [`substitute`](https://symbolics.juliasymbolics.org/v3.5/manual/expression_manipulation/#SymbolicUtils.substitute) command from [`Symbolics.jl`](https://github.com/JuliaSymbolics/Symbolics.jl):

```jldoctest
julia> using Symbolics
julia> @op A; @ket k;
julia> ex = 2*A + projector(k)
(2A+𝐏[|k⟩])
julia> substitute(ex, Dict([A => X, k => X1]))
(2X+𝐏[|X₁⟩])
```

## Simplifying Expressions

For predefined objects such as the Pauli operators [`X`](@ref), [`Y`](@ref), and [`Z`](@ref), additional simplification can be performed with the [`qsimplify`](@ref) function. Take the following example:
Expand Down
2 changes: 2 additions & 0 deletions ext/QuantumOpticsExt/QuantumOpticsExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@ function finite_basis(s,r)
end
express_nolookup(s::FockState, r::QuantumOpticsRepr) = fockstate(finite_basis(s,r),s.idx)
express_nolookup(s::CoherentState, r::QuantumOpticsRepr) = coherentstate(finite_basis(s,r),s.alpha)
express_nolookup(s::SqueezedState, r::QuantumOpticsRepr) = (b = finite_basis(s,r); squeeze(b, s.z)*fockstate(b, 0))
express_nolookup(o::NumberOp, r::QuantumOpticsRepr) = number(finite_basis(o,r))
express_nolookup(o::CreateOp, r::QuantumOpticsRepr) = create(finite_basis(o,r))
express_nolookup(o::DestroyOp, r::QuantumOpticsRepr) = destroy(finite_basis(o,r))
express_nolookup(o::DisplaceOp, r::QuantumOpticsRepr) = displace(finite_basis(o,r), o.alpha)
express_nolookup(o::SqueezeOp, r::QuantumOpticsRepr) = squeeze(finite_basis(o,r), o.z)
express_nolookup(x::MixedState, r::QuantumOpticsRepr) = identityoperator(finite_basis(x,r))/length(finite_basis(x,r)) # TODO there is probably a more efficient way to represent it
express_nolookup(x::IdentityOp, r::QuantumOpticsRepr) = identityoperator(finite_basis(x,r))

Expand Down
6 changes: 3 additions & 3 deletions src/QSymbolicsBase/QSymbolicsBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export SymQObj,QObj,
MixedState,IdentityOp,
SApplyKet,SApplyBra,SMulOperator,SSuperOpApply,SCommutator,SAnticommutator,SBraKet,SOuterKetBra,
HGate,XGate,YGate,ZGate,CPHASEGate,CNOTGate,
XBasisState,YBasisState,ZBasisState,FockState,CoherentState,
NumberOp,CreateOp,DestroyOp,PhaseShiftOp,DisplaceOp,
XBasisState,YBasisState,ZBasisState,FockState,CoherentState,SqueezedState,
NumberOp,CreateOp,DestroyOp,PhaseShiftOp,DisplaceOp,SqueezeOp,
XCXGate,XCYGate,XCZGate,YCXGate,YCYGate,YCZGate,ZCXGate,ZCYGate,ZCZGate,
qsimplify,qsimplify_pauli,qsimplify_commutator,qsimplify_anticommutator,qsimplify_fock,
qexpand,
Expand Down Expand Up @@ -99,7 +99,7 @@ Base.:(-)(x::SymQObj) = (-1)*x
Base.:(-)(x::SymQObj,y::SymQObj) = x + (-y)
Base.hash(x::SymQObj, h::UInt) = isexpr(x) ? hash((head(x), arguments(x)), h) :
hash((typeof(x),symbollabel(x),basis(x)), h)
maketerm(::Type{<:SymQObj}, f, a, t, m) = f(a...)
maketerm(::Type{<:SymQObj}, f, a, m) = f(a...)

function Base.isequal(x::X,y::Y) where {X<:SymQObj, Y<:SymQObj}
if X==Y
Expand Down
2 changes: 1 addition & 1 deletion src/QSymbolicsBase/basic_ops_inhomogeneous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Base.:(*)(b::Symbolic{AbstractBra}, k::SZeroKet) = 0
Base.:(*)(b::SZeroBra, k::SZeroKet) = 0
Base.show(io::IO, x::SBraKet) = begin print(io,x.bra); print(io,x.ket) end
Base.hash(x::SBraKet, h::UInt) = hash((head(x), arguments(x)), h)
maketerm(::Type{SBraKet}, f, a, t, m) = f(a...)
maketerm(::Type{SBraKet}, f, a, m) = f(a...)
Base.isequal(x::SBraKet, y::SBraKet) = isequal(x.bra, y.bra) && isequal(x.ket, y.ket)

"""Symbolic outer product of a ket and a bra.
Expand Down
2 changes: 1 addition & 1 deletion src/QSymbolicsBase/latexify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ end
@latexrecipe function f(x::Union{SpecialKet,SKet})
return Expr(:latexifymerge, "\\left|", symbollabel(x), "\\right\\rangle")
end
@latexrecipe function f(x::Union{SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,AbstractSingleQubitOp,AbstractTwoQubitOp,AbstractSingleBosonGate})
@latexrecipe function f(x::Union{SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,AbstractSingleQubitOp,AbstractTwoQubitOp,AbstractSingleBosonOp})
return LaTeXString("\\hat $(symbollabel(x))")
end
@latexrecipe function f(x::SZero)
Expand Down
27 changes: 26 additions & 1 deletion src/QSymbolicsBase/predefined_fock.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ end
CoherentState(alpha::Number) = CoherentState(alpha, inf_fock_basis)
symbollabel(x::CoherentState) = "$(x.alpha)"

"""Squeezed vacuum state in defined Fock basis."""
@withmetadata struct SqueezedState <: SpecialKet
z::Number
basis::FockBasis
end
SqueezedState(z::Number) = SqueezedState(z, inf_fock_basis)
symbollabel(x::SqueezedState) = "0,$(x.z)"

const inf_fock_basis = FockBasis(Inf,0.)
"""Vacuum basis state of n"""
const vac = const F₀ = const F0 = FockState(0)
Expand Down Expand Up @@ -136,4 +144,21 @@ const N = const n̂ = NumberOp()
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()
const Destroy = const= DestroyOp()

"""Squeezing operator in defined Fock basis.
```jldoctest
julia> S = SqueezeOp(pi)
S(π)
julia> qsimplify(S*vac, rewriter=qsimplify_fock)
|0,π⟩
```
"""
@withmetadata struct SqueezeOp <: AbstractSingleBosonOp
z::Number
basis::FockBasis
end
SqueezeOp(z::Number) = SqueezeOp(z, inf_fock_basis)
symbollabel(x::SqueezeOp) = "S($(x.z))"
3 changes: 2 additions & 1 deletion src/QSymbolicsBase/rules.jl
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ RULES_FOCK = [
@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))
@rule(~o::_isa(DisplaceOp) * ~k::((x->(isa(x,FockState) && x.idx == 0))) => CoherentState((~o).alpha)),
@rule(~o::_isa(SqueezeOp) * ~k::_isequal(vac) => SqueezedState((~o).z, (~o).basis))
]

RULES_SIMPLIFY = [RULES_PAULI; RULES_COMMUTATOR; RULES_ANTICOMMUTATOR; RULES_FOCK]
Expand Down
6 changes: 5 additions & 1 deletion test/test_express_opt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
express(state)
nocache = @timed express(state2)
withcache = @timed express(state2)
@test nocache.time > 50*withcache.time
@test nocache.time > 20*withcache.time
@test withcache.bytes == 0
@test nocache.value withcache.value express(state2)

Expand All @@ -43,4 +43,8 @@
@test express(Create*F1) express(Create)*express(F1)
@test express(Destroy*F1) express(Destroy)*express(F1)
@test express(displace*cstate) express(displace)*express(cstate)

squeezed = SqueezedState(pi/4)
squeezeop = SqueezeOp(pi/4)
@test express(squeezed) express(squeezeop)*express(vac)
end
6 changes: 6 additions & 0 deletions test/test_fock.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
phase1 = PhaseShiftOp(0)
phase2 = PhaseShiftOp(pi)
displace = DisplaceOp(im)
squeezeop = SqueezeOp(pi)
sstate = SqueezedState(pi)

@testset "ladder and number operators" begin
@test isequal(qsimplify(Destroy*vac, rewriter=qsimplify_fock), SZeroKet())
Expand All @@ -23,4 +25,8 @@
@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

@testset "Squeeze operators" begin
@test isequal(qsimplify(squeezeop*vac, rewriter=qsimplify_fock), sstate)
end
end
19 changes: 1 addition & 18 deletions test/test_jet.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,9 @@
using JET
using QuantumOptics, QuantumClifford # to load the extensions

using JET: ReportPass, BasicPass, InferenceErrorReport, UncaughtExceptionReport

# Custom report pass that ignores `UncaughtExceptionReport`
# Too coarse currently, but it serves to ignore the various
# "may throw" messages for runtime errors we raise on purpose
# (mostly on malformed user input)
struct MayThrowIsOk <: ReportPass end

# ignores `UncaughtExceptionReport` analyzed by `JETAnalyzer`
(::MayThrowIsOk)(::Type{UncaughtExceptionReport}, @nospecialize(_...)) = return

# forward to `BasicPass` for everything else
function (::MayThrowIsOk)(report_type::Type{<:InferenceErrorReport}, @nospecialize(args...))
BasicPass()(report_type, args...)
end

using InteractiveUtils, Latexify, SymbolicUtils

rep = report_package("QuantumSymbolics";
report_pass=MayThrowIsOk(), # TODO have something more fine grained than a generic "do not care about thrown errors"
ignored_modules=(
AnyFrameModule(InteractiveUtils),
AnyFrameModule(Latexify),
Expand All @@ -30,5 +13,5 @@ rep = report_package("QuantumSymbolics";
)
@show rep
@test_broken length(JET.get_reports(rep)) == 0
@test length(JET.get_reports(rep)) <= 7
@test length(JET.get_reports(rep)) <= 6
end

0 comments on commit 3005796

Please sign in to comment.