Skip to content

Commit 054fa07

Browse files
authored
Add CPMG to T2 experiment (#3255)
* Add CPMG to T2 experiment - This adds Carr-Purcell-Meiboom-Gill pulse sequences to the T2 experiment. - This sequence has the following sequence: π/2, t, π, 2t, π, ... 2t, π, t Since there are two variables (N pulses and t delay), this allows the user to modify either variable or both to create a 2d scan. In order to accomplish this efficiently, the pi-pulses are encoded as parameterized X gates, e.g. X ** sympy.symbol('pulse_3') which is only turned on for more than 3 pulses. - Fixes #3001
1 parent e2f1ec5 commit 054fa07

File tree

2 files changed

+278
-74
lines changed

2 files changed

+278
-74
lines changed

cirq/experiments/t2_decay_experiment.py

Lines changed: 170 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414
import enum
1515

16-
from typing import Any, Optional, TYPE_CHECKING
16+
from typing import Any, List, Optional, TYPE_CHECKING, Union
1717

1818
import pandas as pd
1919
import sympy
@@ -35,17 +35,18 @@ class ExperimentType(enum.Enum):
3535
_T2_COLUMNS = ['delay_ns', 0, 1]
3636

3737

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']]:
4950
"""Runs a t2 transverse relaxation experiment.
5051
5152
Initializes a qubit into a superposition state, evolves the system using
@@ -58,7 +59,7 @@ def t2_decay(
5859
prepared with a square root Y gate (`cirq.Y ** 0.5`) and then waits for
5960
a variable amount of time. After this time, it will do basic state
6061
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
6263
these two measurements is summed to determine the length of the Bloch
6364
vector. This experiment measures the phase decoherence of the system under
6465
free evolution.
@@ -68,10 +69,41 @@ def t2_decay(
6869
However, during the mid-point of the delay time being measured, a pi-pulse
6970
(`cirq.X`) gate will be applied to cancel out inhomogeneous dephasing.
7071
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.
75107
76108
Args:
77109
sampler: The quantum engine or simulator to run the circuits.
@@ -87,6 +119,8 @@ def t2_decay(
87119
of nanoseconds. If specified, this will override the max_delay and
88120
min_delay parameters. If not specified, the experiment will sweep
89121
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.
90124
Returns:
91125
A T2DecayResult object that stores and can plot the data.
92126
"""
@@ -100,11 +134,14 @@ def t2_decay(
100134
raise ValueError('max_delay < min_delay')
101135
if min_delay_dur < 0:
102136
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.')
103139

104140
# Initialize values used in sweeps
105141
delay_var = sympy.Symbol('delay_ns')
106142
inv_x_var = sympy.Symbol('inv_x')
107143
inv_y_var = sympy.Symbol('inv_y')
144+
max_pulses = max(num_pulses) if num_pulses else 0
108145

109146
if not delay_sweep:
110147
delay_sweep = study.Linspace(delay_var,
@@ -124,60 +161,122 @@ def t2_decay(
124161
circuit = circuits.Circuit(
125162
ops.Y(qubit)**0.5,
126163
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'),
152164
)
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)
158165
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)
160202

161203
# Tabulate measurements into a histogram
162204
results = sampler.sample(circuit, params=sweep, repetitions=repetitions)
163205

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()
170208

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)
176218

177219
# Return the results in a container object
178220
return T2DecayResult(x_basis_tabulation, y_basis_tabulation)
179221

180222

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+
181280
class T2DecayResult:
182281
"""Results from a T2 decay experiment.
183282
@@ -206,21 +305,29 @@ def __init__(self, x_basis_data: pd.DataFrame, y_basis_data: pd.DataFrame):
206305
self._expectation_pauli_x = self._expectation(x_basis_data)
207306
self._expectation_pauli_y = self._expectation(y_basis_data)
208307

209-
def _expectation(self, data) -> pd.DataFrame:
308+
def _expectation(self, data: pd.DataFrame) -> pd.DataFrame:
210309
"""Calculates the expected value of the Pauli operator.
211310
212311
Assuming that the data is measured in the Pauli basis of the operator,
213312
then the expectation of the Pauli operator would be +1 if the
214313
measurement is all ones and -1 if the measurement is all zeros.
215314
216315
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.
218319
"""
219-
xs = data['delay_ns']
320+
delay = data['delay_ns']
220321
ones = data[1]
221322
zeros = data[0]
222323
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})
224331

225332
@property
226333
def expectation_pauli_x(self) -> pd.DataFrame:

0 commit comments

Comments
 (0)