-
Notifications
You must be signed in to change notification settings - Fork 2
/
circuits.py
1489 lines (1344 loc) · 69 KB
/
circuits.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Class to handle logical qubits for the Steane and Bacon Shor code"""
from typing import List
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from helper_functions import (
validate_integer,
calculate_parity_matrix_totals,
calculate_simple_parity_bits
)
class SteaneCodeLogicalQubit(QuantumCircuit):
"""Generates the gates for one or two logical Qubits of the Steane code
Parameters
----------
d : int
Number of logical "data" qubits to be initialised.
Should be either 1 or 2 at present.
parity_check_matrix : list
Holds the parity check matrix from which the gates will be constructed.
codewords : list
Valid codewords for the Steane code
ancilla : bool
True if need to set up ancilla.
For some circuits these are not needed.
extend_ancilla : bool
True if need to add extra ancilla for error correction
without using MCT gates
fault_tolerant_b : bool
True if need to set up scheme c for fault tolerant encoding
with three rounds of measurement
on the second logical qubit.
fault_tolerant_c : bool
True if need to set up an extra qubit for fault tolerance
fault_tolerant_ancilla : bool
True if need to set up fault tolerant ancilla
ancilla_rounds : int
Number of rounds of ancilla measurement for
fault tolerant ancilla
data_round : int
Number of rounds of ancilla measurement for
fault tolerant encoding
self_barrier : bool
If true then barriers will be set.
Notes
-----
Uses super to inherit methods from QuantumCircuit parent.
The code is derived from the parity matrix.
The parity matrix is validated to ensure each row
is orthogonal to each valid codeword.
The number of data qubits is calculated from the length
of rows in the parity matrix.
The number of ancilla is calculated from the number
of columns in the parity matrix.
Ancilla qubits are only set up if needed.
For error correction without MCT gates extra ancilla are set up.
An extra qubit can be added for a fault tolerant logical zero.
Extra classical measurement ancilla are set up if there is more than one
ancilla measurement round.
"""
def __init__(self, d, parity_check_matrix, codewords, ancilla = True, extend_ancilla = False,
fault_tolerant_b = False, fault_tolerant_c = False,
fault_tolerant_ancilla = False, ancilla_rounds = 1, data_rounds = 1,
set_barrier = True
):
"""Initialise qubit"""
validate_integer(d)
if d > 1:
if extend_ancilla:
raise ValueError("Can't set up extra ancilla with two logical qubits due to memory size restrictions")
if fault_tolerant_ancilla:
raise ValueError("Can't set up fault tolerant ancilla with two logical qubits due to memory size restrictions")
if not ancilla:
if fault_tolerant_ancilla:
raise ValueError("Need to select ancillas to set up fault tolerant ancilla")
if extend_ancilla:
raise ValueError("Need to select ancillas to extend ancilla")
if not fault_tolerant_ancilla:
if ancilla_rounds > 1:
raise ValueError("More than one round of measurement only needed with fault tolerant ancilla measurement")
if not fault_tolerant_b:
if not fault_tolerant_c:
if data_rounds > 1:
raise ValueError("More than one round of measurement only needed with fault tolerant ancilla measurement")
if fault_tolerant_b:
if fault_tolerant_c:
raise ValueError("Fault tolerant preparation of the logical qubit must follow scheme B or C2")
validate_integer(ancilla_rounds)
validate_integer(data_rounds)
self.__d = d
self.__ancilla = ancilla
self.__extend_ancilla = extend_ancilla
self.__fault_tolerant_b = fault_tolerant_b
self.__fault_tolerant_c = fault_tolerant_c
self.__fault_tolerant_ancilla = fault_tolerant_ancilla
self.__num_ancilla_rounds = ancilla_rounds
self.__num_data_rounds = data_rounds
#number of data qubits is length of rows parity matrix
self.__num_data = len(parity_check_matrix[0])
#number of ancilla qubits is number of columns in the parity matrix.
self.__num_ancilla = len(parity_check_matrix)
self.__num_extra_ancilla = 4 #four extra ancilla for decoding
self.__num_ftc = 1 #one ft ancilla for encoding in Scheme C
self.__num_ft_anc = 4 # four fault tolerant ancilla
self.__parity_check_matrix = parity_check_matrix
self.__codewords = codewords
self.__number_of_logical_qubits = d
self.__set_barrier = set_barrier
self.define_data()
list_of_all_registers = self.define_registers(d)
# extend quantum circuits
super().__init__(*list_of_all_registers)
self.validate_parity_matrix()
def define_data(self):
"""define standing data
Notes
-----
The ancilla qubits for the X operator are self.__mx
The ancilla qubits for the Z operator are self.__mz
There are more ancilla qubits if there are fault tolerant ancilla.
In this case the classical measurement bits can support
multiple rounds of measurement.
Also, multiple classical rounds of measurement are supported
for Goto's schemes b and c. In scheme b
the multiple classical rounds are only on the second qubit.
"""
self.__data = [] #data qubits
if self.__fault_tolerant_b:
# split out the second list item
# under scheme b repeated measurment is only needed of the second qubit
self.__data_classical = [ [], [[] for i in range(self.__num_data_rounds)]]
else:
self.__data_classical = []
if self.__fault_tolerant_c:
self.__ftc = []
self.__ftc_classical = [[] for i in range(self.__num_data_rounds)]
if self.__ancilla:
if self.__fault_tolerant_ancilla:
self.__mx = [[] for i in range(self.__d)]
self.__mz = [[] for i in range(self.__d)]
self.__mx_classical = [[[] for i in range(self.__num_ancilla)] for j in range(self.__d) ]
self.__mz_classical = [[[] for i in range(self.__num_ancilla)] for j in range(self.__d) ]
else:
self.__mx = [] #ancilla qubits to detect X operator
self.__mz = [] #ancilla qubits to detect Z operator
self.__mx_classical = []
self.__mz_classical = []
if self.__extend_ancilla:
self.__extra_ancilla = []
self.__extra_ancilla_classical = []
def define_registers(self, d):
"""Set up registers to be used based on number of logical qubits
and whether error checking is needed.
Parameters
----------
d : int
Number of logical "data" qubits to be initialised.
Should be either 0 or 1 at present.
Notes
-----
The quantum and classical registers are stored in a list so
that they can be indexed by logical qubit
to simplify subsequent code.
The registers needed depend on the number of logical qubits,
whether extra ancilla qubits
are needed for fault tolerance and the number of round of
measurements needed.
"""
list_of_all_registers = []
for index1 in range(d):
#define label for logical qubits
self.__qubit_no1 = str(index1)
# Quantum registers
self.__data.append(QuantumRegister(self.__num_data, "data " + self.__qubit_no1))
list_of_all_registers.append(self.__data[index1])
if self.__ancilla:
#if ancilla needed
if self.__fault_tolerant_ancilla:
#if fault tolerant ancilla
for index2 in range(self.__num_ancilla):
self.__qubit_no2 = ' ' + str(index2)
self.__mx[index1].append(QuantumRegister(self.__num_ft_anc, "ancilla X " + self.__qubit_no1
+ self.__qubit_no2))
list_of_all_registers.append(self.__mx[index1][index2])
for index2 in range(self.__num_ancilla):
self.__qubit_no2 = ' ' + str(index2)
self.__mz[index1].append(QuantumRegister(self.__num_ft_anc, "ancilla Z " + self.__qubit_no1
+ self.__qubit_no2))
list_of_all_registers.append(self.__mz[index1][index2])
else:
#non FT ancilla
self.__mx.append(QuantumRegister(self.__num_ancilla, "ancilla X " + self.__qubit_no1))
self.__mz.append(QuantumRegister(self.__num_ancilla, "ancilla Z " + self.__qubit_no1))
list_of_all_registers.append(self.__mx[index1])
list_of_all_registers.append(self.__mz[index1])
if self.__extend_ancilla:
self.__extra_ancilla.append(QuantumRegister(self.__num_extra_ancilla, name="extra ancilla" + self.__qubit_no1))
list_of_all_registers.append(self.__extra_ancilla[index1])
if self.__fault_tolerant_c:
#fault tolerant scheme C with one extra qubit
self.__ftc.append(QuantumRegister(self.__num_ftc, "fault tolerant " + self.__qubit_no1))
list_of_all_registers.append(self.__ftc[index1])
# Classical registers
if self.__fault_tolerant_b:
#fault tolerant scheme B with measurement three times on logical qubit 1
if index1 == 1:
#add three classical registers to allow for three measurements of second register
for index3 in range(self.__num_data_rounds):
self.__qubit_no3 = str(index3)
self.__data_classical[index1][index3] = (ClassicalRegister(self.__num_data, "measure_data "
+ self.__qubit_no1 + self.__qubit_no3))
list_of_all_registers.append(self.__data_classical[index1][index3])
else:
self.__data_classical[index1] = (ClassicalRegister(self.__num_data, "measure_data " + self.__qubit_no1))
list_of_all_registers.append(self.__data_classical[index1])
else:
self.__data_classical.append(ClassicalRegister(self.__num_data, "measure_data " + self.__qubit_no1))
list_of_all_registers.append(self.__data_classical[index1])
if self.__fault_tolerant_c:
#one classical register for each of the three rounds of measurement
for index3 in range(self.__num_data_rounds):
self.__qubit_no3 = str(index3)
self.__ftc_classical[index1].append(ClassicalRegister(self.__num_ftc, "measure_ft_data "
+ self.__qubit_no1 + self.__qubit_no3))
list_of_all_registers.append(self.__ftc_classical[index1][index3])
if self.__ancilla:
#if ancilla
if self.__fault_tolerant_ancilla:
for index2 in range(self.__num_ancilla):
for index3 in range(self.__num_ancilla_rounds):
self.__qubit_no2 = str(index2)
self.__qubit_no3 = str(index3)
self.__mx_classical[index1][index2].append(ClassicalRegister(self.__num_ft_anc, "measure_ancilla_X "
+ self.__qubit_no1 + self.__qubit_no2 + self.__qubit_no3))
list_of_all_registers.append(self.__mx_classical[index1][index2][index3])
for index2 in range(self.__num_ancilla):
for index3 in range(self.__num_ancilla_rounds):
self.__qubit_no2 = str(index2)
self.__qubit_no3 = str(index3)
self.__mz_classical[index1][index2].append(ClassicalRegister(self.__num_ft_anc, "measure_ancilla_Z "
+ self.__qubit_no1 + self.__qubit_no2 + self.__qubit_no3))
list_of_all_registers.append(self.__mz_classical[index1][index2][index3])
else:
self.__mx_classical.append(ClassicalRegister(self.__num_ancilla, "measure_ancilla_X " + self.__qubit_no1))
self.__mz_classical.append(ClassicalRegister(self.__num_ancilla, "measure_ancilla_Z " + self.__qubit_no1))
list_of_all_registers.append(self.__mx_classical[index1])
list_of_all_registers.append(self.__mz_classical[index1])
if self.__extend_ancilla:
self.__extra_ancilla_classical.append(
ClassicalRegister(self.__num_extra_ancilla, "measure_extra_ancilla " + self.__qubit_no1))
list_of_all_registers.append(self.__extra_ancilla_classical[index1])
return (list_of_all_registers)
def validate_parity_matrix(self):
"""Validate the parity matrix against the allowed codewords"""
if self.__parity_check_matrix == []:
raise ValueError('Parity check matrix must be specified')
for parity_string in self.__parity_check_matrix:
if len(parity_string) != self.__num_data:
raise ValueError('Parity check matrix rows incorrect length')
if self.__ancilla:
if len(self.__parity_check_matrix) != self.__num_ancilla:
raise ValueError('Parity check matrix has incorrect number of rows')
for codeword_string in self.__codewords:
if len(codeword_string) != self.__num_data:
raise ValueError("Code word rows incorrect length")
for parity_string in self.__parity_check_matrix:
bit_store = False
for codeword_bit_string in codeword_string:
if codeword_bit_string not in ['0','1']:
raise ValueError("Code word entries must be 0 or 1")
for parity_bit_string in parity_string:
if parity_bit_string not in ['0','1']:
raise ValueError("Parity matrix entries must be 0 or 1")
bit_store = bit_store ^ bool(int(codeword_bit_string)) ^ bool(int(parity_bit_string))
if bit_store:
raise ValueError("Code word rows must be orthogonal to the parity matrix")
def set_up_logical_zero(self, logical_qubit = 0, reduced = True, logical_one = False):
"""Set up logical zero for data qubit
Parameters
----------
logical_qubit: int
Number of the logical "data" qubit to initialise.
Should be either 0 or 1 at present.
reduced : bool
Checks to see if any gates are duplicated
logical_one : bool
Add extra gates to set up a logical one
Notes
-----
Columns of the parity matrix with only one entry are
prepared in the + state.
CX gates are set up from these + state to
data qubits associated with
parity matrix entries
in the same row which are unity.
If reduced = True possible unnecessary
duplicate CX gates are identified.
If possible two CX gates are removed and
replaced by one new CX gates.
"""
self._validate_logical_qubit_number(logical_qubit)
parity_matrix_totals = calculate_parity_matrix_totals()
count = 0
if reduced:
#find entries in the parity matrix where the CX gates needed are duplicated and store
duplicate_entries = []
for column_index1 in range(self.__num_ancilla):
parity_row1 = self.__parity_check_matrix[column_index1]
for bit_index in range(self.__num_data):
if parity_matrix_totals[bit_index] == 1 and parity_row1[bit_index] == '1':
h_qubit1 = bit_index
for column_index2 in range(column_index1 + 1, self.__num_ancilla, 1):
parity_row2 = self.__parity_check_matrix[column_index2]
for bit_index in range(self.__num_data):
if parity_matrix_totals[bit_index] == 1 and parity_row2[bit_index] == '1':
h_qubit2 = bit_index
h_qubit_start = max(h_qubit1, h_qubit2)
for bit_index in range(h_qubit_start + 1, self.__num_data):
bit1 = parity_row1[bit_index]
bit2 = parity_row2[bit_index]
if bit1 == '1' and bit2 == '1':
duplicate_entries.append([[h_qubit1, bit_index], [h_qubit2, bit_index]])
#specify that each bit is zero
#put entries into cx_gates for each CNOT based on relevant entry in the parity matrix
cx_gates = []
for index in range (self.__num_data):
#for each column of the parity matrix
self.reset(self.__data[logical_qubit][index])
#reset each corresponding qubit to |0>
if parity_matrix_totals[index] == 1:
#if there is exactly one non-zero entries in this column:
count = count + 1
#set up |+> state if there is one non-zero parity matrix entry.
self.h(self.__data[logical_qubit][index])
#set up Hadamard gates for corresponding data qubit
for parity_row in self.__parity_check_matrix:
#loop over rows in parity matrix
# from the |+> state qubits build the rest of the ancilla.
if parity_row[index] == '1':
for column_number in range(self.__num_data):
if column_number != index:
#ignore the current column
if parity_row[column_number] == '1':
#if the entry for row and column is non-zero:
cx_gates.append([index,column_number])
if logical_one:
logical_one_list = []
for index in range(self.__num_data):
#parity matrix colums with two rows are involved in logical zero calculation
if parity_matrix_totals[index] == 2:
logical_one_list.append(index)
for index in range(self.__num_data):
#if reduced append matrix columns with three rows
if reduced:
if parity_matrix_totals[index] == 3:
logical_one_list.append(index)
if reduced:
if len(logical_one_list) != 4:
raise ValueError('not able to compute logical zero')
else:
if len(logical_one_list) != 3:
raise ValueError('not able to compute logical zero')
self.x(self.__data[logical_qubit][logical_one_list[0]])
for j in range(len(logical_one_list) - 1):
self.cx(self.__data[logical_qubit][logical_one_list[0]],
self.__data[logical_qubit][logical_one_list[j + 1]])
if count != self.__num_ancilla:
raise ValueError(f'Unable to construct matrix as parity matrix does not match the ancilla needed. Count = {count}')
removed_gates = []
if reduced:
#if the reduced encoding circuit is needed
#if duplicate entries remove two gates and add one new gate pair
cx_gates_added = []
#cx_gates holds the list of cx_gates to replace those deleted
number_of_duplicates = len(duplicate_entries)
if (number_of_duplicates % 2) != 0:
raise ValueError(f'Expect an even number of entries in the duplicates. {number_of_duplicates} entries found')
for index in range(int(number_of_duplicates / 2)):
support = duplicate_entries[2 * index]
duplicates = duplicate_entries[2 * index + 1]
if duplicates[0] in cx_gates and duplicates[1] in cx_gates:
#check if any of the cx_gates can be removed
if duplicates[0] not in removed_gates and duplicates[1] not in removed_gates:
for i in range(2):
removed_gates.append(duplicates[i])
if duplicates[0][1] != duplicates[1][1]:
raise ValueError('Error removing duplicates from parity matrix')
if support[0][1] != support[1][1]:
raise ValueError('Error removing duplicates from parity matrix')
cx_gates_added.append([support[0][1], duplicates[0][1]])
for items in cx_gates:
if items not in removed_gates:
index = items[0]
column_number = items[1]
self.cx(self.__data[logical_qubit][index],
self.__data[logical_qubit][column_number])
if reduced:
for items in cx_gates_added:
index = items[0]
column_number = items[1]
self.cx(self.__data[logical_qubit][index],
self.__data[logical_qubit][column_number])
def logical_data_reset (self, logical_qubit = 0):
""" Resets the data for a logical qubit
Parameters
----------
logical_qubit: int
Number of the logical "data" qubits to reset.
Should be either 0 or 1 at present.
Notes
-----
This function is needed for a fault tolerant encoding scheme.
Usually the data qubits are reset as part of setting up the logical zero.
In one scheme the logical zero is not set up, but a reset
is still needed.
"""
for index in range (self.__num_data):
self.reset(self.__data[logical_qubit][index])
def force_X_error(self, physical_qubit, logical_qubit = 0):
""" Introduce an X error on one physical qubit
Parameters
----------
physical_qubit : int
Number of qubit to force X error on.
logical_qubit: int
Number of the logical "data" qubits to force error on.
Should be either 0 or 1 at present.
"""
self._validate_physical_qubit_number(physical_qubit)
self._validate_logical_qubit_number(logical_qubit)
self.x(self.__data[logical_qubit][physical_qubit])
def force_Z_error(self, physical_qubit, logical_qubit = 0):
""" Introduce Z error on one physical qubit
Parameters
----------
physical_qubit : int
Number of qubit to force Z error on.
logical_qubit: int
Number of the logical "data" qubits to force error on.
Should be either 0 or 1 at present.
"""
self._validate_physical_qubit_number(physical_qubit)
self._validate_logical_qubit_number(logical_qubit)
self.z(self.__data[logical_qubit][physical_qubit])
def set_up_ancilla(self, logical_qubit = 0):
"""Set up gates for ancilla based on entries in the parity matrix
Parameters
----------
logical_qubit: int
Number of the logical "data" qubits to set up ancilla gates for.
Should be either 0 or 1 at present.
Notes
-----
The ancilla needed are determined from the parity matrix.
Fault tolerant logic sets up four
ancilla qubits, set these up in a GHZ, and then
apply a CZ gate to each one individually and then
decomputes the GHZ gate.
"""
self._validate_logical_qubit_number(logical_qubit)
#specify that each bit is zero
for index in range (self.__num_ancilla):
if self.__fault_tolerant_ancilla:
for index2 in range(self.__num_ft_anc):
self.reset(self.__mx[logical_qubit][index][index2])
self.reset(self.__mz[logical_qubit][index][index2])
else:
self.reset(self.__mx[logical_qubit][index])
self.reset(self.__mz[logical_qubit][index])
if self.__set_barrier:
self.barrier()
#set up Hadamard gates for each qubit
if self.__fault_tolerant_ancilla:
for index in range (self.__num_ancilla):
self.h(self.__mx[logical_qubit][index][0])
self.h(self.__mz[logical_qubit][index][0])
else:
self.h(self.__mx[logical_qubit])
self.h(self.__mz[logical_qubit])
if self.__set_barrier:
self.barrier()
if self.__fault_tolerant_ancilla:
#if fault tolerant ancilla are needed:
#set up GHZ state
for index in range (self.__num_ancilla):
for index2 in range(self.__num_ft_anc - 1):
self.cx(self.__mx[logical_qubit][index][index2],
self.__mx[logical_qubit][index][index2 + 1])
self.cx(self.__mz[logical_qubit][index][index2],
self.__mz[logical_qubit][index][index2 + 1])
if self.__set_barrier:
self.barrier()
#apply CX gates according to the parity matrix
if self.__fault_tolerant_ancilla:
#lists to keep count of which sub-ancilla to use next
next_x_ancilla = [0 for index in range(self.__num_ancilla)]
next_z_ancilla = [0 for index in range(self.__num_ancilla)]
for index in range (self.__num_ancilla):
#loop over rows in parity matrix:
parity_row = self.__parity_check_matrix[index]
for column_number in range(self.__num_data):
#loop over columns in parity matrix:
if parity_row[column_number] == '1':
if self.__fault_tolerant_ancilla:
#for index2 in range(self.__num_ft_anc):
index2 = next_x_ancilla[index]
self.cx(self.__mx[logical_qubit][index][index2],
self.__data[logical_qubit][column_number])
next_x_ancilla[index] = next_x_ancilla[index] + 1
#create CX gate from data qubit for row to the next ancilla qubit for the column
else:
self.cx(self.__mx[logical_qubit][index],self.__data[logical_qubit][column_number])
#create CX gate from data qubit to the ancilla qubit for the column
if self.__set_barrier:
self.barrier()
#apply CZ gates according to the parity matrix
#done separately so Qiskit diagram is easier to review
for index in range (self.__num_ancilla):
parity_row = self.__parity_check_matrix[index]
for column_number in range(self.__num_data):
if parity_row[column_number] == '1':
if self.__fault_tolerant_ancilla:
index2 = next_z_ancilla[index]
self.cz(self.__mz[logical_qubit][index][index2],
self.__data[logical_qubit][column_number])
next_z_ancilla[index] = next_z_ancilla[index] + 1
else:
self.cz(self.__mz[logical_qubit][index],self.__data[logical_qubit][column_number])
if self.__set_barrier:
self.barrier()
if self.__fault_tolerant_ancilla:
#un-encode set up GHZ state
for index in range (self.__num_ancilla):
for index2 in range(self.__num_ft_anc - 1, 0, -1):
self.cx(self.__mx[logical_qubit][index][index2 - 1],
self.__mx[logical_qubit][index][index2])
self.cx(self.__mz[logical_qubit][index][index2 - 1],
self.__mz[logical_qubit][index][index2])
if self.__set_barrier:
self.barrier()
#set up Hadamard gates
if self.__fault_tolerant_ancilla:
for index in range (self.__num_ancilla):
self.h(self.__mx[logical_qubit][index][0])
self.h(self.__mz[logical_qubit][index][0])
else:
self.h(self.__mx[logical_qubit])
self.h(self.__mz[logical_qubit])
if self.__set_barrier:
self.barrier()
def logical_measure_data(self, logical_qubit = 0):
"""Makes measurement of the data qubits of a logical qubit.
Parameters
----------
logical_qubit : int
Number of the logical "data" qubits to measure.
Should be either 0 or 1 at present.
measure_round : int
Round of data measurement.
Can be more than one for scheme B or C.
Notes
-----
For Scheme B there are normally three rounds of measuremement for the
second logical qubit and three classical measurement bits are created,
one for each round. For Scheme C there are also normally three rounds
of measurement.
"""
self._validate_logical_qubit_number(logical_qubit)
#need to measure the ancilla for each round
#validate_integer(measure_round)
for index in range(self.__num_data):
self.measure(self.__data[logical_qubit][index],
self.__data_classical[logical_qubit][index])
def logical_measure_data_FT(self, logical_qubit = 0, measure_round = 1):
"""Makes measurement of the data qubits of a logical qubit needed for FT schemes
Parameters
----------
logical_qubit : int
Number of the logical "data" qubits to measure.
Should be either 0 or 1 at present.
measure_round : int
Round of data measurement. Can be more than one for scheme B or C.
Notes
-----
For Scheme B there are normally three rounds of measuremement for the second
logical qubit and three classical measurement bits are created,
one for each round.
For Scheme C there are also normally three rounds of measurement.
"""
if not self.__fault_tolerant_b:
if not self.__fault_tolerant_c:
raise ValueError('Please only use FT meaurement for when FT logical encoding is used')
self._validate_logical_qubit_number(logical_qubit)
#need to measure the ancilla for each round
validate_integer(measure_round)
for index in range(self.__num_data):
if self.__fault_tolerant_b:
if logical_qubit == 1:
round_index = measure_round - 1
self.measure(self.__data[logical_qubit][index],
self.__data_classical[logical_qubit][round_index][index])
else:
raise ValueError('for error scheme B only carry out measurement on qubit 1 ')
if self.__fault_tolerant_c:
if measure_round > self.__num_data_rounds:
raise ValueError (f'Data measurement qubits for only {self.__num_data_rounds} round(s) are available')
measure_index = measure_round - 1
for index in range(self.__num_ftc):
self.measure(self.__ftc[logical_qubit][index],
self.__ftc_classical[logical_qubit][measure_index][index])
def logical_measure_ancilla(self, logical_qubit = 0, ancilla_round = 1):
"""Makes measurement of the ancilla qubits of a logical qubit.
Parameters
----------
logical_qubit : int
Number of the logical "data" qubits to measure.
Should be either 0 or 1 at present.
ancilla_round : int
Round of ancilla measurement.
Can be more than one for fault tolerant ancilla.
Notes
-----
If there is more than one ancilla round of measurement the classical
measurements bit created above can be used.
For example, if there are three rounds of measuremement
three classical measurement bits are created,
one for each round.
"""
if not self.__ancilla:
raise ValueError('No qubits set up for ancilla measurements')
self._validate_logical_qubit_number(logical_qubit)
#need to measure the ancilla for each round
validate_integer(ancilla_round)
if ancilla_round > self.__num_ancilla_rounds:
raise ValueError (f'Ancilla measurement qubits for only {self.__num_ancilla_rounds} round(s) are available')
round_index = ancilla_round - 1
if self.__fault_tolerant_ancilla:
for index1 in range(self.__num_ancilla):
for index2 in range(self.__num_ft_anc):
self.measure(self.__mx[logical_qubit][index1][index2],
self.__mx_classical[logical_qubit][index1][round_index][index2])
self.measure(self.__mz[logical_qubit][index1][index2],
self.__mz_classical[logical_qubit][index1][round_index][index2])
else:
for index1 in range(self.__num_ancilla):
self.measure(self.__mx[logical_qubit][index1],
self.__mx_classical[logical_qubit][index1])
self.measure(self.__mz[logical_qubit][index1],
self.__mz_classical[logical_qubit][index1])
if self.__set_barrier:
self.barrier()
if ancilla_round == 1:
#only need to measure extended ancilla qubits once
if self.__extend_ancilla:
for index in range(self.__num_extra_ancilla):
self.measure(self.__extra_ancilla[logical_qubit][index],
self.__extra_ancilla_classical[logical_qubit][index])
def correct_errors(self, logical_qubit = 0, mct = False):
""" Produces circuit to correct errors.
Parameters
----------
logical_qubit: int
Number of the logical "data" qubits on which to correct error.
Should be either 0 or 1 at present.
mct: bool
Controls whether an MCT gate shall be used
Notes
-----
Need to swap ancilla bits to match how printed out.
Reads through parity matrix to determine the corrections to be applied.
The error correcting circuit is either set up with MCT gates,
which is simpler but needs
more gates, or without MCT gates, which is more difficult to
program but needs less gates.
In the latter case corrections
already applied need to be taken into account
when looking at
two or three bit corrections.
In both cases the error correcting gates are determined from the parity matrix.
The errors detected by the Z operators are bit flips,
so are corrected by CX gates.
The errors detected by the X operators are phase flips, so
are corrected by CZ gates.
"""
if mct:
if logical_qubit not in [0, 1]:
raise ValueError("errors can only be corrected for one or two logical qubit at present")
else:
if logical_qubit !=0:
raise ValueError("errors can only be corrected for one logical qubit at present if MCT as not used ")
if not self.__extend_ancilla:
raise ValueError("extra ancilla are needed to correct errors without MCT")
transpose_parity = self._transpose_parity()
qubit_data = {i: {"count":0} for i in range(self.__num_data)}
single_CX_updates_list = []
for qubit in qubit_data:
# read the relevant column of the parity check matrix
count = 0
bit_list = transpose_parity[qubit]
for bits in bit_list:
if bits == '1':
count = count + 1
qubit_data.update({qubit:{"count":count}})
if mct:
for qubit in range(self.__num_data):
bit_list = transpose_parity[qubit]
#x ancilla errors
#flip zero ancilla bit to one
for bit_index in range(self.__num_ancilla):
if bit_list[bit_index] == '0':
self.x(self.__mz[logical_qubit][bit_index])
# use MCT gate to bit flip incorrect qubit
self.mct([self.__mz[logical_qubit][0],
self.__mz[logical_qubit][1],
self.__mz[logical_qubit][2]],
self.__data[logical_qubit][qubit])
#flip zero ancilla bits back
for bit_index in range(self.__num_ancilla):
if bit_list[bit_index] == '0':
#bit flip identified with Z logical operator corrected with bit flip
self.x(self.__mz[logical_qubit][bit_index])
#z ancilla errors
#flip zero ancilla bit to one
for bit_index in range(self.__num_ancilla):
if bit_list[bit_index] == '0':
#bit flip identified with Z logical operator corrected with bit flip
self.x(self.__mx[logical_qubit][bit_index])
# use MCT gate and Hadamard to bit flip incorrect qubit
self.h(self.__data[logical_qubit][qubit])
self.mct([self.__mx[logical_qubit][0],
self.__mx[logical_qubit][1],
self.__mx[logical_qubit][2]],
self.__data[logical_qubit][qubit])
self.h(self.__data[logical_qubit][qubit])
#flip zero ancilla bits back
for bit_index in range(self.__num_ancilla):
if bit_list[bit_index] == '0':
self.x(self.__mx[logical_qubit][bit_index])
else:
for qubit in range(self.__num_data):
bit_list = transpose_parity[qubit]
qubit_data_item = qubit_data.get(qubit)
count = qubit_data_item.get("count")
if count == 1:
for bit_index in range(self.__num_ancilla):
if bit_list[bit_index] == '1':
self.cx(self.__mz[logical_qubit][bit_index],
self.__data[logical_qubit][qubit])
single_CX_updates_list.append([qubit, bit_index])
self.cz(self.__mx[logical_qubit][bit_index],
self.__data[logical_qubit][qubit])
extra_ancilla = 0
for qubit in range(self.__num_data):
bit_list = transpose_parity[qubit]
qubit_data_item = qubit_data.get(qubit)
count = qubit_data_item.get("count")
if count == 2:
#find entries where both bits are '1'
#initialise variables
first_bit = 0
second_bit = 0
for bit_index1 in range(self.__num_ancilla):
if bit_list[bit_index1] == '1':
# need a CCNOT gate
for bit_index2 in range(self.__num_ancilla):
if bit_index1 != bit_index2:
if bit_list[bit_index2] == '1':
first_bit = bit_index1
second_bit = bit_index2
self.ccx(self.__mz[logical_qubit][first_bit],
self.__mz[logical_qubit][second_bit],
self.__extra_ancilla[logical_qubit][extra_ancilla])
self.cx(self.__extra_ancilla[logical_qubit][extra_ancilla],
self.__data[logical_qubit][qubit])
self.cz(self.__extra_ancilla[logical_qubit][extra_ancilla],
self.__data[logical_qubit][qubit])
for items in single_CX_updates_list:
other_impacted_qubit = items[0]
bit_index = items[1]
if first_bit == bit_index or second_bit == bit_index:
self.cx(self.__extra_ancilla[logical_qubit][extra_ancilla],
self.__data[logical_qubit][other_impacted_qubit])
self.cz(self.__extra_ancilla[logical_qubit][extra_ancilla],
self.__data[logical_qubit][other_impacted_qubit])
extra_ancilla = extra_ancilla + 1
for qubit in range(self.__num_data):
qubit_data_item = qubit_data.get(qubit)
count = qubit_data_item.get("count")
if count == 3:
self.ccx(self.__extra_ancilla[logical_qubit][0],
self.__extra_ancilla[logical_qubit][1],
self.__extra_ancilla[logical_qubit][3])
self.ccx(self.__extra_ancilla[logical_qubit][0],
self.__extra_ancilla[logical_qubit][2],
self.__extra_ancilla[logical_qubit][3])
self.ccx(self.__extra_ancilla[logical_qubit][1],
self.__extra_ancilla[logical_qubit][2],
self.__extra_ancilla[logical_qubit][3])
#need to undo impact of all gates made earlier
#as odd parity by inspection of codes.
#maybe later could add code to check the changes made,
#and show they have odd parity.
for gate_needed in range(self.__num_data):
self.cx(self.__extra_ancilla[logical_qubit][3],
self.__data[logical_qubit][gate_needed])
self.cz(self.__extra_ancilla[logical_qubit][3],
self.__data[logical_qubit][gate_needed])
#need to reverse CCX gates
for qubit in range(self.__num_data):
qubit_data_item = qubit_data.get(qubit)
count = qubit_data_item.get("count")
if count == 3:
self.ccx(self.__extra_ancilla[logical_qubit][0],
self.__extra_ancilla[logical_qubit][1],
self.__extra_ancilla[logical_qubit][3])
self.ccx(self.__extra_ancilla[logical_qubit][0],
self.__extra_ancilla[logical_qubit][2],
self.__extra_ancilla[logical_qubit][3])
self.ccx(self.__extra_ancilla[logical_qubit][1],
self.__extra_ancilla[logical_qubit][2],
self.__extra_ancilla[logical_qubit][3])
extra_ancilla = 0
for qubit in range(self.__num_data):
bit_list = transpose_parity[qubit]
qubit_data_item = qubit_data.get(qubit)
count = qubit_data_item.get("count")
if count == 2:
#find entries where both bits are '1'
#initialise variables
first_bit = 0
second_bit = 0
for bit_index1 in range(self.__num_ancilla):
if bit_list[bit_index1] == '1':
# need a CCNOT gate
for bit_index2 in range(self.__num_ancilla):
if bit_index1 != bit_index2:
if bit_list[bit_index2] == '1':
first_bit = bit_index1
second_bit = bit_index2
## need to add a ccx gate
self.ccx(self.__mz[logical_qubit][first_bit],
self.__mz[logical_qubit][second_bit],
self.__extra_ancilla[logical_qubit][extra_ancilla])
extra_ancilla = extra_ancilla + 1
def decode(self, logical_qubit = 0, reduced = True, simple = False):
"""Un-computes setting up logical zero for data qubit.
Parameters
----------
logical_qubit: int
Number of the logical "data" qubits to set up logical zero for.
Should be either 0 or 1 at present.
reduced : bool
Checks to see if any gates are duplicated
simple : bool
Simple decoding scheme when full logical zero is not required.
Notes
-----
The full circuit reverses the encoding circuit.
The gates needed are determined from the parity matrix.
For the simple decoding the logical status is determined from
the parity of three bits.
"""
self._validate_logical_qubit_number(logical_qubit)
parity_matrix_totals = calculate_parity_matrix_totals()
count = 0
if simple:
qubit_list = calculate_simple_parity_bits()
if len(qubit_list) == 3:
self.cx(self.__data[logical_qubit][qubit_list[1]],
self.__data[logical_qubit][qubit_list[0]])
self.cx(self.__data[logical_qubit][qubit_list[2]],
self.__data[logical_qubit][qubit_list[0]])
else:
raise ValueError('Error reading parity matrix for simple decoding')
else:
if reduced:
#find duplicated entries
duplicate_entries = []
for column_index1 in range(self.__num_ancilla):
parity_row1 = self.__parity_check_matrix[column_index1]
for bit_index in range(self.__num_data):
if parity_matrix_totals[bit_index] == 1 and parity_row1[bit_index] == '1':
h_qubit1 = bit_index
for column_index2 in range(column_index1 + 1, self.__num_ancilla, 1):
parity_row2 = self.__parity_check_matrix[column_index2]
for bit_index in range(self.__num_data):
if parity_matrix_totals[bit_index] == 1 and parity_row2[bit_index] == '1':
h_qubit2 = bit_index
h_qubit_start = max(h_qubit1, h_qubit2)
for bit_index in range(h_qubit_start + 1, self.__num_data):
bit1 = parity_row1[bit_index]
bit2 = parity_row2[bit_index]
if bit1 == '1' and bit2 == '1':
duplicate_entries.append([[h_qubit1, bit_index], [h_qubit2, bit_index]])
#specify that each bit is zero
#put entries into cx_gates for each CNOT based on
#relevant entry in the parity matrix
cx_gates = []
for index in range (self.__num_data):
#specify that each bit is zero
if parity_matrix_totals[index] == 1:
count = count + 1
#set up |+> state if count is one
for parity_row in self.__parity_check_matrix:
# from the |+> state qubits build the rest of the ancilla.
if parity_row[index] == '1':
for column_number in range(self.__num_data):
if column_number != index:
if parity_row[column_number] == '1':
#cx from |+> state to qubit
#if there is a 1 in the parity matrix.
cx_gates.append([index,column_number])
if count != self.__num_ancilla:
raise ValueError(f'Unable to construct matrix as parity matrix does not match the ancilla needed. Count = {count}')
removed_gates = []
if reduced:
#if duplicate entries remove two gates and add one new gate pair
cx_gates_added = []
#cx_gates holds the list of cx_gates to replace those deleted
number_of_duplicates = len(duplicate_entries)
if (number_of_duplicates % 2) != 0:
raise ValueError(f'Expect an even number of entries in the duplicates. {number_of_duplicates} entries found')
for index in range(int(number_of_duplicates / 2)):
support = duplicate_entries[2 * index]
duplicates = duplicate_entries[2 * index + 1]