diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..f938118 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,3 @@ +- Created an abstract noise operation class `AbstractNoiseBellOp` +- Implemented `PauliZOp` for phase damping errors and `MixedStateOp` for amplitude damping errors +- Added tests for `PauliZOp` and `MixedStateOp` for comparison to exact density matrix computation \ No newline at end of file diff --git a/Project.toml b/Project.toml index 95f251f..6f0e196 100644 --- a/Project.toml +++ b/Project.toml @@ -17,4 +17,4 @@ BPGatesQuantikzExt = "Quantikz" Quantikz = "1.3.1" QuantumClifford = "0.8, 0.9" Random = "1" -julia = "1.9" \ No newline at end of file +julia = "1.9" diff --git a/src/BPGates.jl b/src/BPGates.jl index facaaec..9213c6a 100644 --- a/src/BPGates.jl +++ b/src/BPGates.jl @@ -9,7 +9,8 @@ export BellState, BellSinglePermutation, BellDoublePermutation, BellPauliPermutation, BellMeasure, bellmeasure!, BellGate, CNOTPerm, GoodSingleQubitPerm, - PauliNoiseOp, PauliNoiseBellGate, NoisyBellMeasure, NoisyBellMeasureNoisyReset + PauliNoiseOp, PauliZOp, PauliNoiseBellGate, NoisyBellMeasure, NoisyBellMeasureNoisyReset, AbstractNoiseBellOp, + MixedStateOp, AsymmDepOp function int_to_bit(int,digits) int = int - 1 # -1 so that we use julia indexing conventions @@ -168,6 +169,7 @@ struct BellMeasure <: QuantumClifford.AbstractMeasurement sidx::Int function BellMeasure(p,s) 1 <= p <= 3 || throw(ArgumentError("The basis measurement index needs to be between 1 and 3")) + # why is there no check here for the measured bell pair? ensuring that the index is >= 1? new(p,s) end end @@ -214,9 +216,13 @@ end """The permutations realized by [`BellPauliPermutation`](@ref).""" const pauli_perm_tuple = ( + # 3 is actually 2; 2 is actually 3 (1, 2, 3, 4), + # Z gate? (3, 4, 1, 2), ## X flip + # X gate? (2, 1, 4, 3), ## Z flip + # bit and phase flip (4, 3, 2, 1) ## Y flip ) @@ -226,9 +232,16 @@ const pauli_perm_qc = (sId1,sX,sZ,sY) function QuantumClifford.apply!(state::BellState, op::BellPauliPermutation) # TODO abstract away the permutation application as it is used by other gates too phase = state.phases @inbounds phase_idx = bit_to_int(phase[op.sidx*2-1],phase[op.sidx*2]) + # println("QuantumClifford.apply!, BellPauliPermutation, op.sidx: $(op.sidx)") + # println("QuantumClifford.apply!, BellPauliPermutation, phase_idx: $phase_idx") + # println("QuantumClifford.apply!, BellPauliPermutation, op.pidx: $(op.pidx)") @inbounds perm = pauli_perm_tuple[op.pidx] @inbounds permuted_idx = perm[phase_idx] + # println("QuantumClifford.apply!, BellPauliPermutation, permuted_idx: $permuted_idx") + # this makes no sense. Make bit 2 go before bit1. bit1, bit2 = int_to_bit(permuted_idx,Val(2)) + # println("QuantumClifford.apply!, BellPauliPermutation, bit1: $bit1") + # println("QuantumClifford.apply!, BellPauliPermutation, bit2: $bit2") @inbounds phase[op.sidx*2-1] = bit1 @inbounds phase[op.sidx*2] = bit2 return state @@ -321,6 +334,7 @@ julia> bellmeasure!(BellState([0,1,1,1]), BellMeasure(2,1)) function bellmeasure!(state::BellState, op::BellMeasure) # TODO document which index corresponds to which measurement phase = state.phases result = measure_tuple[bit_to_int(phase[op.sidx*2-1],phase[op.sidx*2])][op.midx] + # TODO: introduce latency here? phase[op.sidx*2-1:op.sidx*2] .= 0 # reset the measured pair to 00 return state, result end @@ -418,6 +432,8 @@ const good_perm_qc = ( # From the appendix of Optimized Entanglement Purificatio (h*ph,p*hp) ) +# Does this only modify the second bell pair? No phase kickback?-- interesting. +# phase kickback here contributes to a global phase const cnot_perm = (1, 2, 11, 12, 6, 5, 16, 15, 9, 10, 3, 4, 14, 13, 8, 7) function QuantumClifford.apply!(state::BellState, g::CNOTPerm) # TODO abstract away the permutation application as it is used by other gates too @@ -471,7 +487,10 @@ end """A wrapper for `BellGate` that implements Pauli noise in addition to the gate.""" struct PauliNoiseBellGate{G} <: BellOp where {G<:BellOp} # TODO make it work with the QuantumClifford noise ops + # to do the above TODO, probably have to make it match some interface? g::G + # probability that the state changes in these bases after the noise is applied + # probability that the qubit at the specified index changes to an X gate. px::Float64 py::Float64 pz::Float64 @@ -518,6 +537,112 @@ function QuantumClifford.apply!(state::BellState, g::PauliNoiseOp) return state end +# Type for abstracting away noisy operations on bell states. +abstract type AbstractNoiseBellOp <: BellOp end + +""" +PauliZOp(idx, pz) causes qubit at idx `idx` to have a Z flip with probabilty `pz`. +""" +struct PauliZOp <: AbstractNoiseBellOp + idx::Int + # assert that 0 <= pz <= 1? not strictly necessary. it wouldn't make sense, but it + # also wouldn't break the code. natural floor and ceiling here. + pz::Float64 +end + +function QuantumClifford.apply!(state::BellState, g::PauliZOp) + i = g.idx + r = rand() + if r