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

Added Superdense endcoding and decoding circuits to CircuitZoo #34

Merged
merged 13 commits into from
Sep 18, 2023
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ PrecompileTools = "1"
QuantumClifford = "0.8"
QuantumInterface = "0.3.0"
QuantumOptics = "1.0.5"
QuantumOpticsBase = "0.4"
QuantumOpticsBase = "0.4.14"
QuantumSymbolics = "0.2.4"
Reexport = "1.2.2"
ResumableFunctions = "0.6.1"
Expand Down
71 changes: 71 additions & 0 deletions src/CircuitZoo/CircuitZoo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -851,4 +851,75 @@ function (circuit::PurifyExpedientNode)(purified,sacrificed...)
[a..., b..., c..., d...]
end

"""
$TYPEDEF

Fields:

$FIELDS

The circuit for Superdense Coding to encode the 2 (classical) bit message
to its corresponding bell pair representation. `reg` is a single qubit register containing
Alice's half of the entangled bell pair and message is the 2 bit message Alice intends to send to Bob.
Based on the 2 bit message the state of the qubit in the register is mutated in place with `apply!`

```jldoctest
julia> reg = Register(1)

julia> initialize!(reg[1], Z1)

julia> message = [1, 1]

julia> SDEncode()(reg, message)
```
"""

struct SDEncode <: AbstractCircuit
end

function (circuit::SDEncode)(reg, message)
if message[1] == 1
apply!(reg[1], Z)
end
if message[2] == 1
apply!(reg[1], X)
end
end


"""
$TYPEDEF

Fields:

$FIELDS

The circuit for Superdense Coding to decode the 2 (classical) bit message
using the entangled bell pair stored in the registers regA and regB after Alice's encoding of the first qubit.
Returns a Tuple of the decoded message.

```jldoctest
julia> regA = Register(1)

julia> regB = Register(1)

julia> initalize!(regA[1], Z1)

julia> initalize!(regB[1], Z1)

julia> SDDecode()(regA, regB)
```
"""

struct SDDecode <: AbstractCircuit
end

function (circuit::SDDecode)(regA, regB)
apply!((regB[1], regA[1]), CNOT)
apply!(regB[1], H)
Copy link
Member Author

Choose a reason for hiding this comment

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

Everything works fine upto this line, but when we apply H to Bob's qubit, the result is the same as what we would get if we applied H to Alice's qubit on paper, i.e., something like

Ket(dim=4)
  basis: [Spin(1/2)  Spin(1/2)]
  0.49999999999999994 + 0.0im
 -0.49999999999999994 + 0.0im
 -0.49999999999999994 + 0.0im
  0.49999999999999994 + 0.0im

which gives us the random results.
But if we apply H to regA[1] , which is Alice's qubit here, it returns the correct results. I am not sure why it behaves this way, opposite to what we get when we solve it on paper, but it resolves the issue here

Copy link
Member

Choose a reason for hiding this comment

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

Could you elaborate a bit? What is the state before the wrong step, what is the step that seems to break things, and what is the state after that step (and what should it be)?

Copy link
Member Author

@ba2tro ba2tro Aug 31, 2023

Choose a reason for hiding this comment

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

For the final part of the decoder circuit, we want to apply H to the second qubit (Bob's qubit), and H is a 1 qubit operator. But under the hood our register stores a 4 element ket vector which (if the classical message is 1 1) would be:

Ket(dim=4)
  basis: [Spin(1/2)  Spin(1/2)]
                 0.0 + 0.0im
                 0.0 + 0.0im
  0.7071067811865475 + 0.0im
 -0.7071067811865475 + 0.0im

To apply the symbolic H to the second qubit, we call the apply! method at

function apply!(regs::Vector{Register}, indices::Vector{Int}, operation; time=nothing)

Here(
state_indices = [r.stateindices[i] for (r,i) in zip(regs, indices)]
) the state_indices parameter of our ket determines whether the final operator would be I⊗H(correct) or H⊗I(wrong) given state_indices is 1 or 2 respectively. Since, Bob's qubit is the 2nd qubit of the subsystem state_indices is set to 2 and it finally hits the apply! methods in QuantumOpticsBase.jl. So, because of state_indices being 2, the final operator here (in QuantumOpticsBase.jl) becomes H⊗I which gives us the wrong answer in my previous comment.

So, [sorry for the huge description : ) ] to be short, the embed method here expects to be provided with the index where we want to put the identity operator(index 1) instead of the index of the qubit on which we're performing the single qubit operation(index 2), as described by its docstring here
Hence, I think we have a bug in this line

state_indices = [r.stateindices[i] for (r,i) in zip(regs, indices)]

and state_indices should be a vector of all the indices of the state excluding the index of our qubit, so that embed could put identity for all those indices

b1 = project_traceout!(regA, 1, Z)
b2 = project_traceout!(regB, 1, Z)
return b1-1, b2-1
end

end # module
1 change: 1 addition & 0 deletions src/backends/quantumoptics/quantumoptics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function observable(state::Union{<:Ket,<:Operator}, indices, operation)
expect(op, state)
end


function project_traceout!(state::Union{Ket,Operator},stateindex,psis::Base.AbstractVecOrTuple{<:Ket})
if nsubsystems(state) == 1 # TODO is there a way to do this in a single function, instead of _overlap vs _project_and_drop
_overlaps = [_overlap(psi,state) for psi in psis]
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA
@doset "observable"
@doset "noninstant_and_backgrounds_qubit"
@doset "noninstant_and_backgrounds_qumode"
@doset "circuitzoo_superdense"
@doset "circuitzoo_purification"
@doset "examples"
get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true" && @doset "plotting_cairo"
Expand Down
22 changes: 22 additions & 0 deletions test/test_circuitzoo_superdense.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using QuantumSavory
using QuantumSavory.CircuitZoo: SDEncode, SDDecode
using Test

## Set up an entangled bell pair
ra = Register(1)
rb = Register(1)

initialize!(ra[1], Z1)
initialize!(rb[1], Z1)

apply!(ra[1], H)
apply!((ra[1], rb[1]), CNOT)

# Random 2 bit classical message
message = Tuple(rand(0:1, 2))

# Use the circuits to encode and decode the message
SDEncode()(ra, message)
rec = SDDecode()(ra, rb)

@test message == rec