# T2 Hahn Characterization (CPMG)¶

The purpose of the \(T_2\) Hahn Echo experiment is to determine \(T_2\) qubit property.

In this experiment, we would like to get a more precise estimate of the qubit’s decay time. \(T_2\) represents the amount of time required for a single qubit Bloch vector projection on the XY plane, to fall to approximately 37% (\(\frac{1}{e}\)) of its initial amplitude. In Ramsey Experiment we were introduced to the term detuning frequency (The difference between the frequency used for the control rotation, and the precise frequency). Hahn Echo experiment and CPMG sequence are experiments to estimate \(T_2\) which are robust to the detuning frequency. The decay in amplitude causes the probability function to take the following form:

The difference between Hahn Echo and CPMG sequence is that in Hahn Echo experiment, there is only one echo sequence while in CPMG there are multiple echo sequences.

## 1. Decoherence Time¶

Decoherence time is the time taken for off-diagonal components of the
density matrix to fall to approximately 37% (\(\frac{1}{e}\)). For
\(t\gg T_2\), the qubit statistics behave like a random bit. It gets
the value of `0`

with probability of \(p\) and the value of `1`

with probability of \(1-p\).

Since the qubit is exposed to other types of noise (like T1), we are using \(Rx(\pi)\) pulses for decoupling and to solve our inaccuracy for the qubit frequency estimation.

```
import qiskit
from qiskit_experiments.library.characterization.t2hahn import T2Hahn
```

The circuit used for an experiment with \(N\) echoes comprises the following components:

1.\(Rx\left(\frac{\pi}{2} \right)\) gate 2. \(N\) times Echo sequence : (a) \(Delay \left(t_{0} \right)\) gate (b) \(Rx \left(\pi \right)\) gate (c) \(Delay \left(t_{0} \right)\) gate 3. \(Rx \left(\pm \frac{\pi}{2} \right)\) gate (sign depends on the number of echoes) 4. Measurement gate

The user provides as input a series of delays in seconds. During the delay, we expect the qubit to precess about the z-axis. Because of the echo gate (\(Rx(\pi)\)) for each echo, the angle after the delay gates will be \(\theta_{new} = \theta_{old} + \pi\). After waiting the same delay time, the angle will be approximately \(0\) or \(\pi\). By varying the extension of the delays, we get a series of decaying measurements. We can draw the graph of the resulting function and can analytically extract the desired values.

```
qubit = 0
conversion_factor = 1e-6 # our delay will be in micro-sec
delays = list(range(0, 50, 1) )
delays = [float(_) * conversion_factor for _ in delays]
number_of_echoes = 1
# Create a T2Hahn experiment. Print the first circuit as an example
exp1 = T2Hahn(qubit=qubit, delays=delays, num_echoes=number_of_echoes)
print(exp1.circuits()[0])
```

```
┌─────────┐┌───────────────┐┌───────┐┌───────────────┐┌─────────┐┌─┐
q: ┤ Rx(π/2) ├┤ Delay(0.0[s]) ├┤ Rx(π) ├┤ Delay(0.0[s]) ├┤ Rx(π/2) ├┤M├
└─────────┘└───────────────┘└───────┘└───────────────┘└─────────┘└╥┘
c: 1/══════════════════════════════════════════════════════════════════╩═
0
```

We run the experiment on a simple, simulated backend, tailored specifically for this experiment.

```
from qiskit_experiments.test.t2hahn_backend import T2HahnBackend
estimated_t2hahn = 20 * conversion_factor
# The behavior of the backend is determined by the following parameters
backend = T2HahnBackend(
t2hahn=[estimated_t2hahn],
frequency=[100100],
initialization_error=[0.0],
readout0to1=[0.02],
readout1to0=[0.02],
)
```

The resulting graph will have the form:
\(f(t) = A \cdot e^{-\frac{t}{T_2}}+ B\) where *t* is the delay and
\(T_2\) is the decay factor.

```
exp1.analysis.set_options(p0=None, plot=True)
expdata1 = exp1.run(backend=backend, shots=2000, seed_simulator=101)
expdata1.block_for_results() # Wait for job/analysis to finish.
# Display the figure
display(expdata1.figure(0))
```

```
# Print results
for result in expdata1.analysis_results():
print(result)
```

```
AnalysisResult
- name: @Parameters_T2HahnAnalysis
- value: CurveFitResult:
- fitting method: least_squares
- number of sub-models: 1
* F_exp_decay(x) = amp * exp(-x/tau) + base
- success: True
- number of function evals: 10
- degree of freedom: 47
- chi-square: 43.277307213724036
- reduced chi-square: 0.9207937705047667
- Akaike info crit.: -1.2197295311515601
- Bayesian info crit.: 4.516339485132877
- init params:
* amp = 0.49025487256371814
* tau = 0.00017612671467590602
* base = 0.48800599700149927
- fit params:
* amp = 0.4773064559814377 ± 0.003609256953419932
* tau = 2.0056958093488713e-05 ± 4.6802648353553334e-07
* base = 0.5010130382407908 ± 0.002920382486339474
- correlations:
* (tau, base) = -0.75242493274202
* (amp, base) = -0.6565410156472773
* (amp, tau) = 0.26755992436028153
- quality: good
- device_components: ['Q0']
- verified: False
AnalysisResult
- name: T2
- value: (2.01+/-0.05)e-05
- χ²: 0.9207937705047667
- quality: good
- extra: <1 items>
- device_components: ['Q0']
- verified: False
```

### 2. Providing initial user estimates¶

The user can provide initial estimates for the parameters to help the
analysis process. In the initial guess, the keys `{amp, tau, base}`

correspond to the parameters `{A, T_2, B}`

respectively. Because the
curve is expected to decay toward \(0.5\), the natural choice for
parameter \(B\) is \(0.5\). When there is no \(T_2\) error,
we would expect that the probability to measure `1`

is \(100\%\),
therefore we will guess that A is \(0.5\). In this experiment,
`t2hahn`

is the parameter of interest. Good estimate for it is the
value computed in previous experiments on this qubit or a similar value
computed for other qubits.

```
exp_with_p0 = T2Hahn(qubit=qubit, delays=delays, num_echoes=number_of_echoes)
exp_with_p0.analysis.set_options(p0={"amp": 0.5, "tau": estimated_t2hahn, "base": 0.5})
expdata_with_p0 = exp_with_p0.run(backend=backend, shots=2000, seed_simulator=101)
expdata_with_p0.block_for_results()
# Display fit figure
display(expdata_with_p0.figure(0))
```

```
# Print results
for result in expdata_with_p0.analysis_results():
print(result)
```

```
AnalysisResult
- name: @Parameters_T2HahnAnalysis
- value: CurveFitResult:
- fitting method: least_squares
- number of sub-models: 1
* F_exp_decay(x) = amp * exp(-x/tau) + base
- success: True
- number of function evals: 8
- degree of freedom: 47
- chi-square: 28.714016508349935
- reduced chi-square: 0.6109365214542539
- Akaike info crit.: -21.73188108997145
- Bayesian info crit.: -15.995812073687011
- init params:
* amp = 0.5
* tau = 1.9999999999999998e-05
* base = 0.5
- fit params:
* amp = 0.479645079872613 ± 0.003584193674152389
* tau = 2.0121748032767705e-05 ± 4.6497851918130666e-07
* base = 0.5010251547320723 ± 0.002924440903401175
- correlations:
* (tau, base) = -0.7531226111865792
* (amp, base) = -0.6655758514048682
* (amp, tau) = 0.27850920911403854
- quality: good
- device_components: ['Q0']
- verified: False
AnalysisResult
- name: T2
- value: (2.01+/-0.05)e-05
- χ²: 0.6109365214542539
- quality: good
- extra: <1 items>
- device_components: ['Q0']
- verified: False
```

### 3. Number of echoes¶

The user can provide the number of echoes that the circuit will perform.
This will determine the amount of delay and echo gates. As the number of
echoes increases, the total time of the circuit will grow. The echoes
decrease the effects of \(T_{1}\) noise and frequency inaccuracy
estimation. Due to that, the Hahn Echo experiment improves our estimate
for \(T_{2}\). In the following code, we will compare results of the
Hahn experiment with `0`

echoes and `1`

echo. The analysis should
fail for the circuit with `0`

echoes. In order to see it, we will add
frequency to the qubit and see how it affect the estimated \(T_2\).
The list `delays`

is the times provided to each delay gate, not the
total delay time.

```
import numpy as np
qubit2 = 0
# set the desired delays
conversion_factor = 1e-6
# The delays aren't equally spaced due the behavior of exponential decay curve where the change in the result
# in earlier times is larger than later times. In addition, since the total delay is 'delay * 2 * num_of_echoes',
# the construction of the delays for each experiment will be different, such that their total length will be the same.
# Delays for Hahn Echo Experiment with 0 echoes
delays2 = np.append(
(np.linspace(0.0, 51.0, num=26)).astype(float),
(np.linspace(53, 100.0, num=25)).astype(float),
)
delays2 = [float(_) * conversion_factor for _ in delays2]
# Delays for Hahn Echo Experiment with 1 echo
delays3 = np.append(
(np.linspace(0.0, 25.5, num=26)).astype(float),
(np.linspace(26.5, 50, num=25)).astype(float),
)
delays3 = [float(_) * conversion_factor for _ in delays3]
num_echoes = 1
estimated_t2hahn2 = 30 * conversion_factor
# Create a T2Hahn experiment with 0 echoes
exp2_0echoes = T2Hahn(qubit2, delays2, num_echoes=0)
exp2_0echoes.analysis.set_options(p0={"amp": 0.5, "tau": estimated_t2hahn2, "base": 0.5})
print("The first circuit of hahn echo experiment with 0 echoes:")
print(exp2_0echoes.circuits()[0])
# Create a T2Hahn experiment with 1 echo. Print the first circuit as an example
exp2_1echoes = T2Hahn(qubit2, delays3, num_echoes=num_echoes)
exp2_1echoes.analysis.set_options(p0={"amp": 0.5, "tau": estimated_t2hahn2, "base": 0.5})
print("The first circuit of hahn echo experiment with 1 echo:")
print(exp2_1echoes.circuits()[0])
```

```
The first circuit of hahn echo experiment with 0 echoes:
┌─────────┐┌───────────────┐┌──────────┐┌─┐
q: ┤ Rx(π/2) ├┤ Delay(0.0[s]) ├┤ Rx(-π/2) ├┤M├
└─────────┘└───────────────┘└──────────┘└╥┘
c: 1/═════════════════════════════════════════╩═
0
The first circuit of hahn echo experiment with 1 echo:
┌─────────┐┌───────────────┐┌───────┐┌───────────────┐┌─────────┐┌─┐
q: ┤ Rx(π/2) ├┤ Delay(0.0[s]) ├┤ Rx(π) ├┤ Delay(0.0[s]) ├┤ Rx(π/2) ├┤M├
└─────────┘└───────────────┘└───────┘└───────────────┘└─────────┘└╥┘
c: 1/══════════════════════════════════════════════════════════════════╩═
0
```

```
from qiskit_experiments.test.t2hahn_backend import T2HahnBackend
detuning_frequency = 2 * np.pi * 10000
# The behavior of the backend is determined by the following parameters
backend2 = T2HahnBackend(
t2hahn=[estimated_t2hahn2],
frequency=[detuning_frequency],
initialization_error=[0.0],
readout0to1=[0.02],
readout1to0=[0.02],)
# Analysis for Hahn Echo experiment with 0 echoes.
expdata2_0echoes = exp2_0echoes.run(backend=backend2, shots=2000, seed_simulator=101)
expdata2_0echoes.block_for_results() # Wait for job/analysis to finish.
# Analysis for Hahn Echo experiment with 1 echo
expdata2_1echoes = exp2_1echoes.run(backend=backend2, shots=2000, seed_simulator=101)
expdata2_1echoes.block_for_results() # Wait for job/analysis to finish.
# Display the figure
print("Hahn Echo with 0 echoes:")
display(expdata2_0echoes.figure(0))
print("Hahn Echo with 1 echo:")
display(expdata2_1echoes.figure(0))
```

```
Hahn Echo with 0 echoes:
```

```
Hahn Echo with 1 echo:
```

We see that the estimate \(T_2\) is different in the two plots. The mock backend for this experiment used \(T_{2} = 30[\mu s]\), which is close to the estimate of the 1 echo experiment.

```
import qiskit.tools.jupyter
%qiskit_copyright
```

### This code is a part of Qiskit

© Copyright IBM 2017, 2022.

This code is licensed under the Apache License, Version 2.0. You may

obtain a copy of this license in the LICENSE.txt file in the root directory

of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this

copyright notice, and modified files need to carry a notice indicating

that they have been altered from the originals.