Skip to content
Snippets Groups Projects
Commit 1d4ed796 authored by Valentin Bruch's avatar Valentin Bruch
Browse files

surface 17 dephasing: adapted for SS22

parent 8ce81b44
No related branches found
No related tags found
No related merge requests found
......@@ -2,68 +2,97 @@
import numpy as np
import matplotlib.pyplot as plt
# logical X in symplectic notation
xL = 0b001010100
# We need to define for logical operators and for stabilizers the set qubits on
# which they act.
# This can be done using binary representation of an integer, in which the last
# bit indicates the physical qubit D1, and so on. For example, a logical
# X operator that acts on qubits D1, D4, and D7 is defined as:
xL = 0b001001001
# X stabilizer generators in symplectic notation
s1 = 0b000011011
s2 = 0b000100100
s3 = 0b001001000
s4 = 0b110110000
# qubit:987654321
s1 = 0b000000110
s2 = 0b000011011
s3 = 0b110110000
s4 = 0b011000000
stabilizerGenerators = [s1, s2, s3, s4]
def getCommutator(a, b):
# For two multiqubit Pauli X- or Z- operators we want to know
# whether they commute (0) or anticommute (1). Because single qubit Paulis
# anticommute we just sum up (mod 2) the mutual ones in the bitstings
# that represent the multiqubit operators in symplectic notation
"""
Check whether two operators made of Pauli operators commute or anticommute.
a consists of Pauli X operators on a subset of the data qubits, b consists
of Pauli Z operators on a different subset of data qubits. The data qubits
are defined by the bits in the integers a and b.
E.g. a=0b0101 has Pauli X operators on data qubits 1 and 3.
Return 0 if a and b commute and 1 otherwise.
"""
# Because single qubit Paulis anticommute we just sum up (mod 2) the mutual
# ones in the bitstings that represent the multiqubit operators in
# symplectic notation
# In python 3.10 you can uncomment the following line to make the code run faster
#return (a & b).bit_count() % 2
return bin(a & b)[2:].count('1')%2
def getSyndrome(configuration):
# commutation/anticommutation with a stabilizer is marked as 0/1 in syndrome
"""
Given a configuration of phase flip errors on the data qubits (defined by
the binary representation of the integer "configuration"), compute the
syndrome.
Commutation/anticommutation with stabilizer n is marked as 0/1 in bit n
of the syndrome. E.g. if an error anticommutes with stabilizers 1 and 3,
the syndrome will be 0b0101.
"""
syndrome = 0
for i in range(len(stabilizerGenerators)):
syndrome += getCommutator(stabilizerGenerators[i], configuration)*2**i
for i, s in enumerate(stabilizerGenerators):
syndrome += getCommutator(s, configuration)*2**i
return syndrome
def getCorrection(syndrome):
"""
Given a syndrome, return the correction. The nth bit of the correction
is 1 if data qubit n should obtain a phase flip in the correction, and 0 if
data qubit n should not be altered.
"""
# Look Up Table: apply minimal weight correction based on syndrome information
correction = []
bin_correction = 0
if syndrome == 0b0001: correction = [0]
if syndrome == 0b1110: correction = [5,6]
if syndrome == 0b0010: correction = [2]
if syndrome == 0b1000: correction = [8]
if syndrome == 0b0100: correction = [6]
if syndrome == 0: return 0
# TASK: add the correct syndromes and corrections
if syndrome == ...: correction = [3]
if syndrome == ...: correction = [2,4]
if syndrome == 0b1101: correction = ...
if syndrome == 0b0001: correction = [3]
if syndrome == 0b0010: correction = [1]
if syndrome == 0b0011: correction = [2]
if syndrome == 0b0100: correction = [9]
if syndrome == 0b0101: correction = [3,9]
if syndrome == 0b0110: correction = ...
if syndrome == 0b0111: correction = [2,9]
if syndrome == 0b1000: correction = ...
if syndrome == 0b1001: correction = ...
if syndrome == 0b1010: correction = [5]
if syndrome == 0b1100: correction = [6,8]
if syndrome == 0b0111: correction = [5,6]
if syndrome == 0b0011: correction = [0,2]
if syndrome == 0b0110: correction = [2,6]
if syndrome == 0b1111: correction = [3,5]
if syndrome == 0b1010: correction = [1,7]
if syndrome == 0b1011: correction = [2,7]
if syndrome == 0b1100: correction = ...
if syndrome == 0b1101: correction = [3,8]
if syndrome == 0b1110: correction = [1,8]
if syndrome == 0b1111: correction = [2,8]
for i in correction:
bin_correction += 2**i
bin_correction += 2**(i-1)
return bin_correction
# set range of physical error rates to MC sample
# set range of physical error rates to montecarlo sample
pRange = np.logspace(-3, 0, 7)
fail_log = []
logical_failure_rate = []
for p in range(len(pRange)):
for p in pRange:
fail_log.append([])
# sample a reasonable amount of times given the probability of errors
for _ in range(int(1000/pRange[p])):
for _ in range(int(1000/p)):
# start out with clean state and randomly place Z-errors on data qubits
configuration = 0
for i in range(9):
if np.random.random() < pRange[p]: configuration += 2**i
if np.random.random() < p:
configuration += 2**i
# TASK: add syndrome readout and apply correction operators according to look up table
syndrome = ...
......@@ -76,6 +105,7 @@ for p in range(len(pRange)):
else:
fail_log[-1].append(1)
# You can uncomment these lines to debug your code
#print('configuration', format(configuration,'09b'))
#print('syndrome', format(syndrome,'04b'))
#print('correction', format(correction,'09b'))
......@@ -85,17 +115,11 @@ for p in range(len(pRange)):
# calculate logical failure rate from samples
logical_failure_rate.append( np.sum(fail_log[-1]) / len(fail_log[-1]) )
#print('physical error rates', pRange)
#print('failure', logical_failure_rate)
#plt.rcParams['text.usetex'] = True
#plt.rcParams.update({'font.size': 18})
# Plot the results
plt.grid()
plt.xlabel('$p$')
plt.ylabel('$p_L$')
plt.title('Pseudothreshold of Surface-17')
plt.loglog(pRange, logical_failure_rate, '-x')
plt.loglog(np.logspace(-3,0,4),np.logspace(-3,0,4),'k:',alpha=0.5)
plt.tight_layout()
plt.loglog(pRange, pRange, 'k:', alpha=0.5)
plt.show()
#!/usr/bin/env python3
import numpy as np
from random import random
import matplotlib.pyplot as plt
from numba import jit, prange
# We need to define for logical operators and for stabilizers the set qubits on
# which they act.
# This can be done using binary representation of an integer, in which the last
# bit indicates the physical qubit D1, and so on. For example, a logical
# X operator that acts on qubits D1, D4, and D7 is defined as:
xL = 0b001001001
# X stabilizer generators in symplectic notation
# qubit:987654321
s1 = 0b000000110
s2 = 0b000011011
s3 = 0b110110000
s4 = 0b011000000
stabilizerGenerators = (s1, s2, s3, s4)
# Lookup table of number of bits:
# parity_table[x] = 1 if x has an odd number of ones, 0 otherwise
# This implies getCommutator(a, b) == parity_table[a & b]
parity_table = bytes(bin(x).count('1')%2 for x in range(1<<9))
@jit(nopython=True)
def getSyndrome(configuration):
"""
Given a configuration of phase flip errors on the data qubits (defined by
the binary representation of the integer "configuration"), compute the
syndrome.
Commutation/anticommutation with stabilizer n is marked as 0/1 in bit n
of the syndrome. E.g. if an error anticommutes with stabilizers 1 and 3,
the syndrome will be 0b0101.
"""
syndrome = 0
for i, s in enumerate(stabilizerGenerators):
syndrome |= parity_table[s & configuration] << i
return syndrome
# Lookup table of corrections:
# correction_table[syndrome] == getCorrection(syndrome)
correction_table = (
# TASK: add the correct syndromes and corrections
0b000000000, # syndmome = 0, correction = []
0b000000100, # syndrome = 0b0001, correction = [3]
0b000000001, # syndrome = 0b0010, correction = [1]
0b000000010, # syndrome = 0b0011, correction = [2]
0b100000000, # syndrome = 0b0100, correction = [9]
0b100000100, # syndrome = 0b0101, correction = [3,9]
... , # syndrome = 0b0110, correction = ...
0b100000010, # syndrome = 0b0111, correction = [2,9]
... , # syndrome = 0b1000, correction = ...
... , # syndrome = 0b1001, correction = ...
0b001000001, # syndrome = 0b1010, correction = [1,7]
0b001000010, # syndrome = 0b1011, correction = [2,7]
... , # syndrome = 0b1100, correction = ...
0b010000100, # syndrome = 0b1101, correction = [3,8]
0b010000001, # syndrome = 0b1110, correction = [1,8]
0b010000010, # syndrome = 0b1111, correction = [2,8]
)
@jit(nopython=True, cache=False, parallel=True)
def simulate(p, runs):
"""
Simulate n runs with error probability p.
Return logical failure rate.
"""
failures = 0
# sample a reasonable amount of times given the probability of errors
for _ in prange(runs):
# start out with clean state and randomly place Z-errors on data qubits
configuration = 0
for i in range(9):
if random() < p:
configuration |= 1<<i
# TASK: add syndrome readout and apply correction operators according to look up table
syndrome = ...
correction = ...
after_correction = configuration ^ correction
# TASK: check if the resulting configuration has caused a logical Z error
if ... :
failures += 1
# calculate logical failure rate from samples
return failures / runs
def main():
# set range of physical error rates to MC sample
pRange = np.logspace(-3, 0, 21)
# Run the simulation
logical_failure_rate = [simulate(p, int(5000/p)) for p in pRange]
# Plot the results
plt.grid()
plt.xlabel('$p$')
plt.ylabel('$p_L$')
plt.title('Pseudothreshold of Surface-17')
plt.loglog(pRange, logical_failure_rate, '-x')
plt.loglog(pRange, pRange, 'k:', alpha=0.5)
plt.show()
if __name__ == '__main__':
main()
#!/usr/bin/env python3
import numpy as np
from random import random
import matplotlib.pyplot as plt
from numba import jit, prange
# We need to define for logical operators and for stabilizers the set qubits on
# which they act.
# This can be done using binary representation of an integer, in which the last
# bit indicates the physical qubit D1, and so on. For example, a logical
# X operator that acts on qubits D1, D4, and D7 is defined as:
xL = 0b001001001
# X stabilizer generators in symplectic notation
# qubit:987654321
s1 = 0b000000110
s2 = 0b000011011
s3 = 0b110110000
s4 = 0b011000000
stabilizerGenerators = (s1, s2, s3, s4)
# Lookup table of number of bits:
# parity_table[x] = 1 if x has an odd number of ones, 0 otherwise
# This implies getCommutator(a, b) == parity_table[a & b]
parity_table = bytes(bin(x).count('1')%2 for x in range(1<<9))
@jit(nopython=True)
def getSyndrome(configuration):
"""
Given a configuration of phase flip errors on the data qubits (defined by
the binary representation of the integer "configuration"), compute the
syndrome.
Commutation/anticommutation with stabilizer n is marked as 0/1 in bit n
of the syndrome. E.g. if an error anticommutes with stabilizers 1 and 3,
the syndrome will be 0b0101.
"""
syndrome = 0
for i, s in enumerate(stabilizerGenerators):
syndrome |= parity_table[s & configuration] << i
return syndrome
# Lookup table of corrections:
# correction_table[syndrome] == getCorrection(syndrome)
correction_table = (
# TASK: add the correct syndromes and corrections
0b000000000, # syndmome = 0, correction = []
0b000000100, # syndrome = 0b0001, correction = [3]
0b000000001, # syndrome = 0b0010, correction = [1]
0b000000010, # syndrome = 0b0011, correction = [2]
0b100000000, # syndrome = 0b0100, correction = [9]
0b100000100, # syndrome = 0b0101, correction = [3,9]
0b000010000, # syndrome = 0b0110, correction = [5]
0b100000010, # syndrome = 0b0111, correction = [2,9]
0b001000000, # syndrome = 0b1000, correction = [7]
0b001000100, # syndrome = 0b1001, correction = [3,7]
0b001000001, # syndrome = 0b1010, correction = [1,7]
0b001000010, # syndrome = 0b1011, correction = [2,7]
0b010000000, # syndrome = 0b1100, correction = [8]
0b010000100, # syndrome = 0b1101, correction = [3,8]
0b010000001, # syndrome = 0b1110, correction = [1,8]
0b010000010, # syndrome = 0b1111, correction = [2,8]
)
@jit(nopython=True, cache=False, parallel=True)
def simulate(p, runs):
"""
Simulate n runs with error probability p.
Return logical failure rate.
"""
failures = 0
# sample a reasonable amount of times given the probability of errors
for _ in prange(runs):
# start out with clean state and randomly place Z-errors on data qubits
configuration = 0
for i in range(9):
if random() < p:
configuration |= 1<<i
# TASK: add syndrome readout and apply correction operators according to look up table
syndrome = getSyndrome(configuration)
correction = correction_table[syndrome]
after_correction = configuration ^ correction
# TASK: check if the resulting configuration has caused a logical Z error
if parity_table[after_correction & xL]:
failures += 1
# calculate logical failure rate from samples
return failures / runs
def main():
# set range of physical error rates to MC sample
pRange = np.logspace(-3, 0, 21)
# Run the simulation
logical_failure_rate = [simulate(p, int(5000/p)) for p in pRange]
# Plot the results
plt.grid()
plt.xlabel('$p$')
plt.ylabel('$p_L$')
plt.title('Pseudothreshold of Surface-17')
plt.loglog(pRange, logical_failure_rate, '-x')
plt.loglog(pRange, pRange, 'k:', alpha=0.5)
plt.show()
if __name__ == '__main__':
main()
#!/usr/bin/env python3
'''
Solution for exercise 13.1f, Monte Carlo simulation of surface-17 error
quantum error correction for dephasing noise.
A more efficient implementation of this can be found in the solution for
depolarizing noise.
'''
import numpy as np
import matplotlib.pyplot as plt
# logical X in symplectic notation
xL = 0b001010100
# We need to define for logical operators and for stabilizers the set qubits on
# which they act.
# This can be done using binary representation of an integer, in which the last
# bit indicates the physical qubit D1, and so on. For example, a logical
# X operator that acts on qubits D1, D4, and D7 is defined as:
xL = 0b001001001
# X stabilizer generators in symplectic notation
s1 = 0b000011011
s2 = 0b000100100
s3 = 0b001001000
s4 = 0b110110000
# qubit:987654321
s1 = 0b000000110
s2 = 0b000011011
s3 = 0b110110000
s4 = 0b011000000
stabilizerGenerators = [s1, s2, s3, s4]
def getCommutator(a, b):
# For two multiqubit Pauli X- or Z- operators we want to know
# whether they commute (0) or anticommute (1). Because single qubit Paulis
# anticommute we just sum up (mod 2) the mutual ones in the bitstings
# that represent the multiqubit operators in symplectic notation
"""
Check whether two operators made of Pauli operators commute or anticommute.
a consists of Pauli X operators on a subset of the data qubits, b consists
of Pauli Z operators on a different subset of data qubits. The data qubits
are defined by the bits in the integers a and b.
E.g. a=0b0101 has Pauli X operators on data qubits 1 and 3.
Return 0 if a and b commute and 1 otherwise.
"""
# Because single qubit Paulis anticommute we just sum up (mod 2) the mutual
# ones in the bitstings that represent the multiqubit operators in
# symplectic notation
# In python 3.10 you can uncomment the following line to make the code run faster
#return (a & b).bit_count() % 2
return bin(a & b)[2:].count('1')%2
def getSyndrome(configuration):
# commutation/anticommutation with a stabilizer is marked as 0/1 in syndrome
"""
Given a configuration of phase flip errors on the data qubits (defined by
the binary representation of the integer "configuration"), compute the
syndrome.
Commutation/anticommutation with stabilizer n is marked as 0/1 in bit n
of the syndrome. E.g. if an error anticommutes with stabilizers 1 and 3,
the syndrome will be 0b0101.
"""
syndrome = 0
for i in range(len(stabilizerGenerators)):
syndrome += getCommutator(stabilizerGenerators[i], configuration)*2**i
for i, s in enumerate(stabilizerGenerators):
syndrome += getCommutator(s, configuration)*2**i
return syndrome
def getCorrection(syndrome):
"""
Given a syndrome, return the correction. The nth bit of the correction
is 1 if data qubit n should obtain a phase flip in the correction, and 0 if
data qubit n should not be altered.
"""
# Look Up Table: apply minimal weight correction based on syndrome information
correction = []
bin_correction = 0
if syndrome == 0b0001: correction = [0] # could also be [1]
if syndrome == 0b1110: correction = [5,6]
if syndrome == 0b0010: correction = [2]
if syndrome == 0b1000: correction = [8] # could also be [7]
if syndrome == 0b0100: correction = [6]
if syndrome == 0b0101: correction = [3]
if syndrome == 0b1011: correction = [2,4]
if syndrome == 0b1101: correction = [4,6] # could also be [3,8]
if syndrome == 0b1001: correction = [4]
if syndrome == 0b1010: correction = [5]
if syndrome == 0b1100: correction = [6,8]
if syndrome == 0b0111: correction = [5,6]
if syndrome == 0b0011: correction = [0,2]
if syndrome == 0b0110: correction = [2,6]
if syndrome == 0b1111: correction = [3,5]
if syndrome == 0: return 0
# TASK: add the correct syndromes and corrections
if syndrome == 0b0001: correction = [3]
if syndrome == 0b0010: correction = [1]
if syndrome == 0b0011: correction = [2]
if syndrome == 0b0100: correction = [9]
if syndrome == 0b0101: correction = [3,9]
if syndrome == 0b0110: correction = [5]
if syndrome == 0b0111: correction = [2,9]
if syndrome == 0b1000: correction = [7]
if syndrome == 0b1001: correction = [3,7]
if syndrome == 0b1010: correction = [1,7]
if syndrome == 0b1011: correction = [2,7]
if syndrome == 0b1100: correction = [8]
if syndrome == 0b1101: correction = [3,8]
if syndrome == 0b1110: correction = [1,8]
if syndrome == 0b1111: correction = [2,8]
for i in correction:
bin_correction += 2**i
bin_correction += 2**(i-1)
return bin_correction
# set range of physical error rates to MC sample
# set range of physical error rates to montecarlo sample
pRange = np.logspace(-3, 0, 7)
fail_log = []
logical_failure_rate = []
for p in range(len(pRange)):
for p in pRange:
fail_log.append([])
# sample a reasonable amount of times given the probability of errors
for _ in range(int(1000/pRange[p])):
for _ in range(int(1000/p)):
# start out with clean state and randomly place Z-errors on data qubits
configuration = 0
for i in range(9):
if np.random.random() < pRange[p]: configuration += 2**i
if np.random.random() < p:
configuration += 2**i
# read syndrome and apply correction operators according to look up table
# TASK: add syndrome readout and apply correction operators according to look up table
syndrome = getSyndrome(configuration)
correction = getCorrection(syndrome)
after_correction = configuration ^ correction
# check if the resulting configuration has caused a logical Z error
# we notice that we induced a logical Z by applying the correction
# by checking if the resulting multiqubit Z-operator commutes with logical X
# TASK: check if the resulting configuration has caused a logical Z error
if getCommutator(after_correction, xL) == 0:
fail_log[-1].append(0)
else:
......@@ -92,17 +114,11 @@ for p in range(len(pRange)):
# calculate logical failure rate from samples
logical_failure_rate.append(np.sum(fail_log[-1])/len(fail_log[-1]))
#print('physical error rates', pRange)
#print('failure', logical_failure_rate)
#plt.rcParams['text.usetex'] = True
#plt.rcParams.update({'font.size': 18})
# Plot the results
plt.grid()
plt.xlabel('$p$')
plt.ylabel('$p_L$')
plt.title('Pseudothreshold of Surface-17')
plt.loglog(pRange, logical_failure_rate, '-x')
plt.loglog(np.logspace(-3,0,4),np.logspace(-3,0,4),'k:',alpha=0.5)
plt.tight_layout()
plt.loglog(pRange, pRange, 'k:', alpha=0.5)
plt.show()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment