13
13
# limitations under the License.
14
14
import enum
15
15
16
- from typing import Any , Optional , TYPE_CHECKING
16
+ from typing import Any , List , Optional , TYPE_CHECKING , Union
17
17
18
18
import pandas as pd
19
19
import sympy
@@ -35,17 +35,18 @@ class ExperimentType(enum.Enum):
35
35
_T2_COLUMNS = ['delay_ns' , 0 , 1 ]
36
36
37
37
38
- def t2_decay (
39
- sampler : work .Sampler ,
40
- * ,
41
- qubit : devices .GridQubit ,
42
- experiment_type : 'ExperimentType' = ExperimentType .RAMSEY ,
43
- num_points : int ,
44
- max_delay : 'cirq.DURATION_LIKE' ,
45
- min_delay : 'cirq.DURATION_LIKE' = None ,
46
- repetitions : int = 1000 ,
47
- delay_sweep : Optional [study .Sweep ] = None ,
48
- ) -> 'cirq.experiments.T2DecayResult' :
38
+ def t2_decay (sampler : work .Sampler ,
39
+ * ,
40
+ qubit : devices .GridQubit ,
41
+ experiment_type : 'ExperimentType' = ExperimentType .RAMSEY ,
42
+ num_points : int ,
43
+ max_delay : 'cirq.DURATION_LIKE' ,
44
+ min_delay : 'cirq.DURATION_LIKE' = None ,
45
+ repetitions : int = 1000 ,
46
+ delay_sweep : Optional [study .Sweep ] = None ,
47
+ num_pulses : List [int ] = None
48
+ ) -> Union ['cirq.experiments.T2DecayResult' ,
49
+ List ['cirq.experiments.T2DecayResult' ]]:
49
50
"""Runs a t2 transverse relaxation experiment.
50
51
51
52
Initializes a qubit into a superposition state, evolves the system using
@@ -58,7 +59,7 @@ def t2_decay(
58
59
prepared with a square root Y gate (`cirq.Y ** 0.5`) and then waits for
59
60
a variable amount of time. After this time, it will do basic state
60
61
tomography to measure the expectation of the Pauli-X and Pauli-Y operators
61
- by performing either a `cirq.Y ** -0.5` or `cirq.X ** - 0.5`. The square of
62
+ by performing either a `cirq.Y ** -0.5` or `cirq.X ** 0.5`. The square of
62
63
these two measurements is summed to determine the length of the Bloch
63
64
vector. This experiment measures the phase decoherence of the system under
64
65
free evolution.
@@ -68,10 +69,41 @@ def t2_decay(
68
69
However, during the mid-point of the delay time being measured, a pi-pulse
69
70
(`cirq.X`) gate will be applied to cancel out inhomogeneous dephasing.
70
71
The same method of measuring the final state as Ramsey experiment is applied
71
- after the second half of the delay period.
72
-
73
- CPMG, or the Carr-Purcell-Meiboom-Gill sequence, is currently not
74
- implemented.
72
+ after the second half of the delay period. See the animation on the wiki
73
+ page https://en.wikipedia.org/wiki/Spin_echo for a visual illustration
74
+ of this experiment.
75
+
76
+ CPMG, or the Carr-Purcell-Meiboom-Gill sequence, involves using a sqrt(Y)
77
+ followed by a sequence of pi pulses (X gates) in a specific timing pattern:
78
+
79
+ π/2, t, π, 2t, π, ... 2t, π, t
80
+
81
+ The first pulse, a sqrt(Y) gate, will put the qubit's state on the Bloch
82
+ equator. After a delay, successive X gates will refocus dehomogenous
83
+ phase effects by causing them to precess in opposite directions and
84
+ averaging their effects across the entire pulse train.
85
+
86
+ This pulse pattern has two variables that can be adjusted. The first,
87
+ denoted as 't' in the above sequence, is delay, which can be specified
88
+ with `delay_min` and `delay_max` or by using a `delay_sweep`, similar to
89
+ the other experiments. The second variable is the number of pi pulses
90
+ (X gates). This can be specified as a list of integers using the
91
+ `num_pulses` parameter. If multiple different pulses are specified,
92
+ the data will be presented in a data frame with two
93
+ indices (delay_ns and num_pulses).
94
+
95
+ See the following reference for more information about CPMG pulse trains:
96
+ Meiboom, S., and D. Gill, “Modified spin-echo method for measuring nuclear
97
+ relaxation times”, Rev. Sci. Inst., 29, 688–691 (1958).
98
+ https://doi.org/10.1063/1.1716296
99
+
100
+ Note that interpreting T2 data is fairly tricky and subtle, as it can
101
+ include other effects that need to be accounted for. For instance,
102
+ amplitude damping (T1) will present as T2 noise and needs to be
103
+ appropriately compensated for to find a true measure of T2. Due to this
104
+ subtlety and lack of standard way to interpret the data, the fitting
105
+ of the data to an exponential curve and the extrapolation of an actual
106
+ T2 time value is left as an exercise to the reader.
75
107
76
108
Args:
77
109
sampler: The quantum engine or simulator to run the circuits.
@@ -87,6 +119,8 @@ def t2_decay(
87
119
of nanoseconds. If specified, this will override the max_delay and
88
120
min_delay parameters. If not specified, the experiment will sweep
89
121
from min_delay to max_delay with linear steps.
122
+ num_pulses: For CPMG, a list of the number of pulses to use.
123
+ If multiple pulses are specified, each will be swept on.
90
124
Returns:
91
125
A T2DecayResult object that stores and can plot the data.
92
126
"""
@@ -100,11 +134,14 @@ def t2_decay(
100
134
raise ValueError ('max_delay < min_delay' )
101
135
if min_delay_dur < 0 :
102
136
raise ValueError ('min_delay < 0' )
137
+ if num_pulses and experiment_type != ExperimentType .CPMG :
138
+ raise ValueError ('num_pulses is only valid for CPMG experiments.' )
103
139
104
140
# Initialize values used in sweeps
105
141
delay_var = sympy .Symbol ('delay_ns' )
106
142
inv_x_var = sympy .Symbol ('inv_x' )
107
143
inv_y_var = sympy .Symbol ('inv_y' )
144
+ max_pulses = max (num_pulses ) if num_pulses else 0
108
145
109
146
if not delay_sweep :
110
147
delay_sweep = study .Linspace (delay_var ,
@@ -124,60 +161,122 @@ def t2_decay(
124
161
circuit = circuits .Circuit (
125
162
ops .Y (qubit )** 0.5 ,
126
163
ops .WaitGate (value .Duration (nanos = delay_var ))(qubit ),
127
- ops .X (qubit )** inv_x_var ,
128
- ops .Y (qubit )** inv_y_var ,
129
- ops .measure (qubit , key = 'output' ),
130
- )
131
- tomography_sweep = study .Zip (
132
- study .Points ('inv_x' , [0.0 , - 0.5 ]),
133
- study .Points ('inv_y' , [- 0.5 , 0.0 ]),
134
- )
135
- sweep = study .Product (delay_sweep , tomography_sweep )
136
- elif experiment_type == ExperimentType .HAHN_ECHO :
137
- # Hahn / Spin Echo T2 experiment
138
- # Use sqrt(Y) to flip to the equator.
139
- # Evolve the state for half the given amount of delay time
140
- # Flip the state using an X gate
141
- # Evolve the state for half the given amount of delay time
142
- # Then measure the state in both X and Y bases.
143
-
144
- circuit = circuits .Circuit (
145
- ops .Y (qubit )** 0.5 ,
146
- ops .WaitGate (value .Duration (nanos = 0.5 * delay_var ))(qubit ),
147
- ops .X (qubit ),
148
- ops .WaitGate (value .Duration (nanos = 0.5 * delay_var ))(qubit ),
149
- ops .X (qubit )** inv_x_var ,
150
- ops .Y (qubit )** inv_y_var ,
151
- ops .measure (qubit , key = 'output' ),
152
164
)
153
- tomography_sweep = study .Zip (
154
- study .Points ('inv_x' , [0.0 , 0.5 ]),
155
- study .Points ('inv_y' , [- 0.5 , 0.0 ]),
156
- )
157
- sweep = study .Product (delay_sweep , tomography_sweep )
158
165
else :
159
- raise ValueError (f'Experiment type { experiment_type } not supported' )
166
+ if experiment_type == ExperimentType .HAHN_ECHO :
167
+ # Hahn / Spin Echo T2 experiment
168
+ # Use sqrt(Y) to flip to the equator.
169
+ # Evolve the state for the given amount of delay time
170
+ # Flip the state using an X gate
171
+ # Evolve the state for the given amount of delay time
172
+ # Then measure the state in both X and Y bases.
173
+ num_pulses = [0 ]
174
+ # This is equivalent to a CPMG experiment with zero pulses
175
+ # and will follow the same code path.
176
+
177
+ # Carr-Purcell-Meiboom-Gill sequence.
178
+ # Performs the following sequence
179
+ # π/2 - wait(t) - π - wait(2t) - ... - π - wait(t)
180
+ # There will be N π pulses (X gates)
181
+ # where N sweeps over the values of num_pulses
182
+ #
183
+ if not num_pulses :
184
+ raise ValueError ('At least one value must be given '
185
+ 'for num_pulses in a CPMG experiment' )
186
+ circuit = _cpmg_circuit (qubit , delay_var , max_pulses )
187
+
188
+ # Add simple state tomography
189
+ circuit .append (ops .X (qubit )** inv_x_var )
190
+ circuit .append (ops .Y (qubit )** inv_y_var )
191
+ circuit .append (ops .measure (qubit , key = 'output' ))
192
+ tomography_sweep = study .Zip (
193
+ study .Points ('inv_x' , [0.0 , 0.5 ]),
194
+ study .Points ('inv_y' , [- 0.5 , 0.0 ]),
195
+ )
196
+
197
+ if num_pulses and max_pulses > 0 :
198
+ pulse_sweep = _cpmg_sweep (num_pulses )
199
+ sweep = study .Product (delay_sweep , pulse_sweep , tomography_sweep )
200
+ else :
201
+ sweep = study .Product (delay_sweep , tomography_sweep )
160
202
161
203
# Tabulate measurements into a histogram
162
204
results = sampler .sample (circuit , params = sweep , repetitions = repetitions )
163
205
164
- y_basis_measurements = results [abs (results .inv_y ) > 0 ]
165
- x_basis_measurements = results [abs (results .inv_x ) > 0 ]
166
- x_basis_tabulation = pd .crosstab (x_basis_measurements .delay_ns ,
167
- x_basis_measurements .output ).reset_index ()
168
- y_basis_tabulation = pd .crosstab (y_basis_measurements .delay_ns ,
169
- y_basis_measurements .output ).reset_index ()
206
+ y_basis_measurements = results [abs (results .inv_y ) > 0 ].copy ()
207
+ x_basis_measurements = results [abs (results .inv_x ) > 0 ].copy ()
170
208
171
- # If all measurements are 1 or 0, fill in the missing column with all zeros.
172
- for tab in [x_basis_tabulation , y_basis_tabulation ]:
173
- for col_index , name in [(1 , 0 ), (2 , 1 )]:
174
- if name not in tab :
175
- tab .insert (col_index , name , [0 ] * tab .shape [0 ])
209
+ if num_pulses and len (num_pulses ) > 1 :
210
+ cols = tuple (f'pulse_{ t } ' for t in range (max_pulses ))
211
+ x_basis_measurements [
212
+ 'num_pulses' ] = x_basis_measurements .loc [:, cols ].sum (axis = 1 )
213
+ y_basis_measurements [
214
+ 'num_pulses' ] = y_basis_measurements .loc [:, cols ].sum (axis = 1 )
215
+
216
+ x_basis_tabulation = _create_tabulation (x_basis_measurements )
217
+ y_basis_tabulation = _create_tabulation (y_basis_measurements )
176
218
177
219
# Return the results in a container object
178
220
return T2DecayResult (x_basis_tabulation , y_basis_tabulation )
179
221
180
222
223
+ def _create_tabulation (measurements : pd .DataFrame ) -> pd .DataFrame :
224
+ """Returns a sum of 0 and 1 results per index from a list of measurements.
225
+ """
226
+ if 'num_pulses' in measurements .columns :
227
+ cols = [measurements .delay_ns , measurements .num_pulses ]
228
+ else :
229
+ cols = [measurements .delay_ns ]
230
+ tabulation = pd .crosstab (cols , measurements .output ).reset_index ()
231
+ # If all measurements are 1 or 0, fill in the missing column with all zeros.
232
+ for col_index , name in [(1 , 0 ), (2 , 1 )]:
233
+ if name not in tabulation :
234
+ tabulation .insert (col_index , name , [0 ] * tabulation .shape [0 ])
235
+ return tabulation
236
+
237
+
238
+ def _cpmg_circuit (qubit : devices .GridQubit , delay_var : sympy .Symbol ,
239
+ max_pulses : int ) -> 'cirq.Circuit' :
240
+ """Creates a CPMG circuit for a given qubit.
241
+
242
+ The circuit will look like:
243
+
244
+ sqrt(Y) - wait(delay_var) - X - wait(2*delay_var) - ... - wait(delay_var)
245
+
246
+ with max_pulses number of X gates.
247
+
248
+ The X gates are paramterizd by 'pulse_N' symbols so that pulses can be
249
+ turned on and off. This is done to combine circuits with different pulses
250
+ into the same paramterized circuit.
251
+ """
252
+ circuit = circuits .Circuit (
253
+ ops .Y (qubit )** 0.5 ,
254
+ ops .WaitGate (value .Duration (nanos = delay_var ))(qubit ), ops .X (qubit ))
255
+ for n in range (max_pulses ):
256
+ pulse_n_on = sympy .Symbol (f'pulse_{ n } ' )
257
+ circuit .append (
258
+ ops .WaitGate (value .Duration (nanos = 2 * delay_var *
259
+ pulse_n_on ))(qubit ))
260
+ circuit .append (ops .X (qubit )** pulse_n_on )
261
+ circuit .append (ops .WaitGate (value .Duration (nanos = delay_var ))(qubit ))
262
+ return circuit
263
+
264
+
265
+ def _cpmg_sweep (num_pulses : List [int ]):
266
+ """Returns a sweep for a circuit created by _cpmg_circuit.
267
+
268
+ The circuit in _cpmg_circuit parameterizes the pulses, so this function
269
+ fills in the parameters for each pulse. For instance, if we want 3 pulses,
270
+ pulse_0, pulse_1, and pulse_2 should be 1 and the rest of the pulses should
271
+ be 0.
272
+ """
273
+ pulse_points = []
274
+ for n in range (max (num_pulses )):
275
+ pulse_points .append (
276
+ study .Points (f'pulse_{ n } ' , [1 if p > n else 0 for p in num_pulses ]))
277
+ return study .Zip (* pulse_points )
278
+
279
+
181
280
class T2DecayResult :
182
281
"""Results from a T2 decay experiment.
183
282
@@ -206,21 +305,29 @@ def __init__(self, x_basis_data: pd.DataFrame, y_basis_data: pd.DataFrame):
206
305
self ._expectation_pauli_x = self ._expectation (x_basis_data )
207
306
self ._expectation_pauli_y = self ._expectation (y_basis_data )
208
307
209
- def _expectation (self , data ) -> pd .DataFrame :
308
+ def _expectation (self , data : pd . DataFrame ) -> pd .DataFrame :
210
309
"""Calculates the expected value of the Pauli operator.
211
310
212
311
Assuming that the data is measured in the Pauli basis of the operator,
213
312
then the expectation of the Pauli operator would be +1 if the
214
313
measurement is all ones and -1 if the measurement is all zeros.
215
314
216
315
Returns:
217
- Data frame with two columns 'delay_ns' and 'value'
316
+ Data frame with columns 'delay_ns', 'num_pulses' and 'value'
317
+ The num_pulses column will only exist if multiple pulses
318
+ were requestd in the T2 experiment.
218
319
"""
219
- xs = data ['delay_ns' ]
320
+ delay = data ['delay_ns' ]
220
321
ones = data [1 ]
221
322
zeros = data [0 ]
222
323
pauli_expectation = (2 * (ones / (ones + zeros ))) - 1.0
223
- return pd .DataFrame ({'delay_ns' : xs , 'value' : pauli_expectation })
324
+ if 'num_pulses' in data .columns :
325
+ return pd .DataFrame ({
326
+ 'delay_ns' : delay ,
327
+ 'num_pulses' : data ['num_pulses' ],
328
+ 'value' : pauli_expectation
329
+ })
330
+ return pd .DataFrame ({'delay_ns' : delay , 'value' : pauli_expectation })
224
331
225
332
@property
226
333
def expectation_pauli_x (self ) -> pd .DataFrame :
0 commit comments