Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate normalized and unnormalized objects #50

Merged
merged 9 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/StatesZoo/StatesZoo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ using QuantumSymbolics, QuantumOpticsBase
using QuantumSymbolics: withmetadata, @withmetadata, Metadata
import QuantumSymbolics: express_nolookup

export SingleRailMidSwapBell, DualRailMidSwapBell, ZALMSpinPair
using LinearAlgebra
import LinearAlgebra: tr

export SingleRailMidSwapBellU, SingleRailMidSwapBellN, DualRailMidSwapBellU, DualRailMidSwapBellN, ZALMSpinPairU, ZALMSpinPairN

abstract type AbstractTwoQubitState <: QuantumSymbolics.AbstractTwoQubitOp end #For representing density matrices
Base.show(io::IO, x::AbstractTwoQubitState) = print(io, "$(symbollabel(x))")
Expand Down
107 changes: 96 additions & 11 deletions src/StatesZoo/single_dual_rail_midswap/single_dual_rail_midswap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Fields:

$FIELDS

Generates the spin-spin density matrix for linear photonic entanglement swap
Generates the unnormalized spin-spin density matrix for linear photonic entanglement swap
with emissive memories emitting single rail photonic qubits from the paper [prajit2023entangling](@cite)
It takes the following parameters:
- eA, eB: Link efficiencies for memories A and B upto the swap (include link loss, detector efficiency, etc.)
Expand All @@ -45,12 +45,12 @@ It takes the following parameters:
```jldoctest
julia> r = Register(2)

julia> initialize!(r[1:2], SingleRailMidSwapBell(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))
julia> initialize!(r[1:2], SingleRailMidSwapBellU(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))

julia> observable(r[1:2], Z⊗Z)
```
"""
@withmetadata struct SingleRailMidSwapBell <: AbstractTwoQubitState
@withmetadata struct SingleRailMidSwapBellU <: AbstractTwoQubitState
eA::Float64
eB::Float64
gA::Float64
Expand All @@ -59,7 +59,7 @@ julia> observable(r[1:2], Z⊗Z)
Vis::Float64
end

symbollabel(x::SingleRailMidSwapBell) = "ρˢʳᵐˢ"
symbollabel(x::SingleRailMidSwapBellU) = "ρˢʳᵐˢᵁ"


"""
Expand All @@ -69,7 +69,42 @@ Fields:

$FIELDS

Generates the spin-spin density matrix for linear photonic entanglement swap with emissive
Generates the normalized spin-spin density matrix for linear photonic entanglement swap
with emissive memories emitting single rail photonic qubits from the paper [prajit2023entangling](@cite)
It takes the following parameters:
- eA, eB: Link efficiencies for memories A and B upto the swap (include link loss, detector efficiency, etc.)
- gA, gB: Memory initialization parameter for memories A and B
- Pd: Detector dark count probability per photonic mode (assumed to be the same for both detectors)
- Vis: Interferometer visibility for the midpoint swap' can be complex to account for phase instability

```jldoctest
julia> r = Register(2)

julia> initialize!(r[1:2], SingleRailMidSwapBellN(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))

julia> observable(r[1:2], Z⊗Z)
```
"""
@withmetadata struct SingleRailMidSwapBellN <: AbstractTwoQubitState
eA::Float64
eB::Float64
gA::Float64
gB::Float64
Pd::Float64
Vis::Float64
end

symbollabel(x::SingleRailMidSwapBellN) = "ρˢʳᵐˢᴺ"


"""
$TYPEDEF

Fields:

$FIELDS

Generates the unnormalized spin-spin density matrix for linear photonic entanglement swap with emissive
memories emitting dual rail photonic qubits from the paper [prajit2023entangling](@cite).
It takes the following parameters:
- eA, eB: Link efficiencies for memories A and B upto the swap (include link loss, detector efficiency, etc.)
Expand All @@ -80,12 +115,12 @@ Generates the spin-spin density matrix for linear photonic entanglement swap wit
```jldoctest
julia> r = Register(2)

julia> initialize!(r[1:2], DualRailMidSwapBell(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))
julia> initialize!(r[1:2], DualRailMidSwapBellU(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))

julia> observable(r[1:2], Z⊗Z)
```
"""
@withmetadata struct DualRailMidSwapBell <: AbstractTwoQubitState
@withmetadata struct DualRailMidSwapBellU <: AbstractTwoQubitState
eA::Float64
eB::Float64
gA::Float64
Expand All @@ -94,17 +129,67 @@ julia> observable(r[1:2], Z⊗Z)
Vis::Float64
end

symbollabel(x::DualRailMidSwapBell) = "ρᵈʳᵐˢ"
symbollabel(x::DualRailMidSwapBellU) = "ρᵈʳᵐˢᵁ"


"""
$TYPEDEF

Fields:

$FIELDS

Generates the normalized spin-spin density matrix for linear photonic entanglement swap with emissive
memories emitting dual rail photonic qubits from the paper [prajit2023entangling](@cite).
It takes the following parameters:
- eA, eB: Link efficiencies for memories A and B upto the swap (include link loss, detector efficiency, etc.)
- gA, gB: Memory initialization parameter for memories A and B
- Pd: Detector dark count probability per photonic mode (assumed to be the same for both detectors)
- Vis: Interferometer visibility for the midpoint swap

```jldoctest
julia> r = Register(2)

julia> initialize!(r[1:2], DualRailMidSwapBellN(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))

julia> observable(r[1:2], Z⊗Z)
```
"""
@withmetadata struct DualRailMidSwapBellN <: AbstractTwoQubitState
eA::Float64
eB::Float64
gA::Float64
gB::Float64
Pd::Float64
Vis::Float64
end

symbollabel(x::DualRailMidSwapBellN) = "ρᵈʳᵐˢᴺ"


## express

function express_nolookup(x::SingleRailMidSwapBell, ::QuantumOpticsRepr)
function express_nolookup(x::SingleRailMidSwapBellU, ::QuantumOpticsRepr)
data = midswap_single_rail(x.eA, x.eB, x.gA, x.gB, x.Pd, x.Vis)
return SparseOperator(_bspin⊗_bspin, Complex.(data))
end

function express_nolookup(x::DualRailMidSwapBell, ::QuantumOpticsRepr)
function express_nolookup(x::SingleRailMidSwapBellN, ::QuantumOpticsRepr)
data = midswap_single_rail(x.eA, x.eB, x.gA, x.gB, x.Pd, x.Vis)
return SparseOperator(_bspin⊗_bspin, Complex.(data/tr(data)))
end

function express_nolookup(x::DualRailMidSwapBellU, ::QuantumOpticsRepr)
data = midswap_dual_rail(x.eA, x.eB, x.gA, x.gB, x.Pd, x.Vis)
return SparseOperator(_bspin⊗_bspin, Complex.(data))
end
end

function express_nolookup(x::DualRailMidSwapBellN, ::QuantumOpticsRepr)
data = midswap_dual_rail(x.eA, x.eB, x.gA, x.gB, x.Pd, x.Vis)
return SparseOperator(_bspin⊗_bspin, Complex.(data/tr(data)))
end

## trace

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets remove these -- unless it is some special constant, it does not make sense to implicitly turn a symbolic object into a float

tr(x::SingleRailMidSwapBellU) = Float64(tr(express(x).data))
tr(x::DualRailMidSwapBellU) = Float64(tr(express(x).data))
73 changes: 67 additions & 6 deletions src/StatesZoo/zalm_pair/zalm_pair.jl
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ Fields:

$FIELDS

Generate symbolic object for the spin-spin density matrix for a
Generate symbolic object for the unnormalized spin-spin density matrix for a
cascaded source swapped with emissive spin memories. The cascaded
source from papers [prajit2022heralded](@cite) and [kevin2023zero](@cite)
is stored in spin memories as discussed in [prajit2023entangling](@cite).
Expand All @@ -589,12 +589,12 @@ It takes the following parameters:
```jldoctest
julia> r = Register(2)

julia> initialize!(r[1:2], ZALMSpinPair(1e-3, 0.5, 0.5, 1, 1, 1, 1, 0.9, 1e-8, 1e-8, 1e-8, 0.99))
julia> initialize!(r[1:2], ZALMSpinPairU(1e-3, 0.5, 0.5, 1, 1, 1, 1, 0.9, 1e-8, 1e-8, 1e-8, 0.99))

juilia> observable(r[1:2], Z⊗Z)
```
"""
@withmetadata struct ZALMSpinPair <: AbstractTwoQubitState
@withmetadata struct ZALMSpinPairU <: AbstractTwoQubitState
Ns::Float64
gA::Float64
gB::Float64
Expand All @@ -609,9 +609,70 @@ juilia> observable(r[1:2], Z⊗Z)
VisF::Float64
end

symbollabel(x::ZALMSpinPair) = "ρᶻᵃˡᵐ"
symbollabel(x::ZALMSpinPairU) = "ρᶻᵃˡᵐᵁ"

function express_nolookup(x::ZALMSpinPair, ::QuantumOpticsRepr)

"""
$TYPEDEF

Fields:

$FIELDS

Generate symbolic object for the normalized spin-spin density matrix for a
cascaded source swapped with emissive spin memories. The cascaded
source from papers [prajit2022heralded](@cite) and [kevin2023zero](@cite)
is stored in spin memories as discussed in [prajit2023entangling](@cite).
It takes the following parameters:
- Ns: mean photon number per mode of the cascaded source model
- gA: qubit initialization parameter on Alice's side
- gB: qubit initialization parameter on Bob's side
- eAm: memory out-coupling efficiency for Alice's side (Allowed range: [0,1])
- eBm: memory out-coupling efficiency for Bob's side (Allowed range: [0,1])
- eAs: source out-coupling efficiency for Alice's side (Allowed range: [0,1])
- eBs: source out-coupling efficiency for Bob's side (Allowed range: [0,1])
- eD: detector efficiency (Allowed range: [0,1])
- Pd: dark click probability per photonic mode on source's swap
- Pdo1: dark click probability per photonic mode on Alice side swap
- Pdo2: dark click probability per photonic mode on Bob side swap
- VisF: product of visibilities of all three interferometers (Allowed range: [0,1])

```jldoctest
julia> r = Register(2)

julia> initialize!(r[1:2], ZALMSpinPairN(1e-3, 0.5, 0.5, 1, 1, 1, 1, 0.9, 1e-8, 1e-8, 1e-8, 0.99))

juilia> observable(r[1:2], Z⊗Z)
```
"""
@withmetadata struct ZALMSpinPairN <: AbstractTwoQubitState
Ns::Float64
gA::Float64
gB::Float64
eAm::Float64
eBm::Float64
eAs::Float64
eBs::Float64
eD::Float64
Pd::Float64
Pdo1::Float64
Pdo2::Float64
VisF::Float64
end

symbollabel(x::ZALMSpinPairN) = "ρᶻᵃˡᵐᴺ"

## express

function express_nolookup(x::ZALMSpinPairU, ::QuantumOpticsRepr)
data = cascaded_source_spin(x.Ns, x.gA, x.gB, x.eAm, x.eBm, x.eAs, x.eBs, x.eD, x.Pd, x.Pdo1, x.Pdo2, x.VisF)
return SparseOperator(_bspin⊗_bspin, Complex.(data))
end
end

function express_nolookup(x::ZALMSpinPairN, ::QuantumOpticsRepr)
data = cascaded_source_spin(x.Ns, x.gA, x.gB, x.eAm, x.eBm, x.eAs, x.eBs, x.eD, x.Pd, x.Pdo1, x.Pdo2, x.VisF)
return SparseOperator(_bspin⊗_bspin, Complex.(data/tr(data)))
end

## trace
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

tr(x::ZALMSpinPairU) = Float64(tr(express(x).data))
49 changes: 39 additions & 10 deletions test/test_stateszoo_api.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
using QuantumSavory
using QuantumSavory.StatesZoo: ZALMSpinPair, SingleRailMidSwapBell, DualRailMidSwapBell
using QuantumSavory.StatesZoo: ZALMSpinPairU, ZALMSpinPairN, SingleRailMidSwapBellU, SingleRailMidSwapBellN, DualRailMidSwapBellU, DualRailMidSwapBellN
using LinearAlgebra
using Test

r_zalm = Register(2)
initialize!(r_zalm[1:2], ZALMSpinPair(1e-3, 0.5, 0.5, 1, 1, 1, 1, 0.9, 1e-8, 1e-8, 1e-8, 0.99))
@test ! iszero(observable(r_zalm[1:2], Z⊗Z))
zalmU = ZALMSpinPairU(1e-3, 0.5, 0.5, 1, 1, 1, 1, 0.9, 1e-8, 1e-8, 1e-8, 0.99)
zalmN = ZALMSpinPairN(1e-3, 0.5, 0.5, 1, 1, 1, 1, 0.9, 1e-8, 1e-8, 1e-8, 0.99)
srmsU = SingleRailMidSwapBellU(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99)
srmsN = SingleRailMidSwapBellN(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99)
drmsU = DualRailMidSwapBellU(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99)
drmsN = DualRailMidSwapBellN(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99)

r_srms = Register(2)
initialize!(r_srms[1:2], SingleRailMidSwapBell(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))
@test ! iszero(observable(r_srms[1:2], Z⊗Z))
r_zalmU = Register(2)
initialize!(r_zalmU[1:2], zalmU)
@test ! iszero(observable(r_zalmU[1:2], Z⊗Z))

r_drms = Register(2)
initialize!(r_drms[1:2], DualRailMidSwapBell(0.9, 0.9, 0.5, 0.5, 1e-8, 0.99))
@test ! iszero(observable(r_drms[1:2], Z⊗Z))
r_zalmN = Register(2)
initialize!(r_zalmN[1:2], zalmN)
@test ! iszero(observable(r_zalmN[1:2], Z⊗Z))

r_srmsU = Register(2)
initialize!(r_srmsU[1:2], srmsU)
@test ! iszero(observable(r_srmsU[1:2], Z⊗Z))

r_srmsN = Register(2)
initialize!(r_srmsN[1:2], srmsN)
@test ! iszero(observable(r_srmsN[1:2], Z⊗Z))

r_drmsU = Register(2)
initialize!(r_drmsU[1:2], drmsU)
@test ! iszero(observable(r_drmsU[1:2], Z⊗Z))

r_drmsN = Register(2)
initialize!(r_drmsN[1:2], drmsN)
@test ! iszero(observable(r_drmsN[1:2], Z⊗Z))

@test tr(zalmU) < 1.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the other hand, these should be exactly equal to 1 thanks to a special dispatch rule (so that express does not need to be used, nor .data)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we still need to use express to convert it to QuantumOptics formalism so that this specialised method can be called

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if we do not rely on quantumoptics.jl at all for that.

In symbolic CAS tools usually 1 and 0 are treated more specially than other numbers. For the normalized density matrices here it is guaranteed that the trace is 1, so I would like to have a special-cased method for them that completely shortcuts any expression functionality.

@test tr(rho) = tr(express(rho)) should work for them.

Please add these special-cased methods for tr and I should be able to merge.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Abhishek-1Bhatt , bump

@test tr(express(zalmN).data) ≈ 1

@test tr(srmsU) < 1.0
@test tr(express(srmsN).data) ≈ 1.0

@test tr(drmsU) < 1.0
@test tr(express(drmsN).data) ≈ 1.0
Loading