Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

T1 constant calculation #4290

Merged
merged 12 commits into from
Jul 14, 2021
Merged
39 changes: 38 additions & 1 deletion cirq-core/cirq/experiments/t1_decay_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import pandas as pd
import sympy
from matplotlib import pyplot as plt
import numpy as np
from scipy import optimize


from cirq import circuits, ops, study, value
from cirq._compat import proper_repr
Expand Down Expand Up @@ -102,12 +105,41 @@ def data(self) -> pd.DataFrame:
"""A data frame with delay_ns, false_count, true_count columns."""
return self._data

def plot(self, ax: Optional[plt.Axes] = None, **plot_kwargs: Any) -> plt.Axes:
@property
def constant(self) -> float:
""" The t1 decay constant."""
asmuzsoy marked this conversation as resolved.
Show resolved Hide resolved

def exp_decay(x, t1):
return np.exp(-x / t1)

xs = self._data['delay_ns']
ts = self._data['true_count']
fs = self._data['false_count']
probs = ts / (fs + ts)

## find point closest to a probability of 1/e
## to serve as a guess for the curve fit
asmuzsoy marked this conversation as resolved.
Show resolved Hide resolved
find_guess = lambda index: abs(probs[index] - 1 / np.e)
guess_index = min(range(len(xs)), key=find_guess)
asmuzsoy marked this conversation as resolved.
Show resolved Hide resolved
t1_guess = xs[guess_index]

## fit to exponential decay to find the t1 constant
asmuzsoy marked this conversation as resolved.
Show resolved Hide resolved
try:
popt, _ = optimize.curve_fit(exp_decay, xs, probs, p0=[t1_guess])
t1 = popt[0]
return t1
except RuntimeError:
raise RuntimeWarning("Optimal parameters could not be found for curve fit")

def plot(
self, ax: Optional[plt.Axes] = None, include_fit=False, **plot_kwargs: Any
asmuzsoy marked this conversation as resolved.
Show resolved Hide resolved
) -> plt.Axes:
"""Plots the excited state probability vs the amount of delay.

Args:
ax: the plt.Axes to plot on. If not given, a new figure is created,
plotted on, and shown.
include_fit: boolean to include exponential decay fit on graph
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.

Returns:
Expand All @@ -124,6 +156,11 @@ def plot(self, ax: Optional[plt.Axes] = None, **plot_kwargs: Any) -> plt.Axes:
fs = self._data['false_count']

ax.plot(xs, ts / (fs + ts), 'ro-', **plot_kwargs)

if include_fit:
ax.plot(xs, np.exp(-xs / self.constant), label='curve fit')
plt.legend()

ax.set_xlabel(r"Delay between initialization and measurement (nanoseconds)")
ax.set_ylabel('Excited State Probability')
ax.set_title('T1 Decay Experiment Data')
Expand Down
68 changes: 68 additions & 0 deletions cirq-core/cirq/experiments/t1_decay_experiment_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import cirq

import numpy as np


def test_init_result():
data = pd.DataFrame(
Expand Down Expand Up @@ -165,6 +167,72 @@ def test_all_off_results():
)


def test_constant():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth looking into parameterizing this test with @pytest.mark.parameterize so that you can remove some of the code duplication between the 100 and 400 cases, and just turn the relevant values between these two results into parameters for the test. Here's a quick example: https://github.com/quantumlib/Cirq/blob/master/cirq-core/cirq/ops/common_gates_test.py#L60

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this suggestion! I've added this to the test, along with the explicitly exponential data from your other comment.

result_100 = cirq.experiments.T1DecayResult(
data=pd.DataFrame(
columns=['delay_ns', 'false_count', 'true_count'],
index=range(4),
data=[
[100.0, 6, 4],
[400.0, 10, 0],
[700.0, 10, 0],
[1000.0, 10, 0],
],
)
)
assert np.isclose(result_100.constant, 100, 5)

result_400 = cirq.experiments.T1DecayResult(
data=pd.DataFrame(
columns=['delay_ns', 'false_count', 'true_count'],
index=range(4),
data=[
[100.0, 0, 10],
[400.0, 6, 4],
[700.0, 10, 0],
[1000.0, 10, 0],
],
)
)
assert np.isclose(result_400.constant, 400, 5)

MichaelBroughton marked this conversation as resolved.
Show resolved Hide resolved

def test_curve_fit_plot():
good_fit = cirq.experiments.T1DecayResult(
data=pd.DataFrame(
columns=['delay_ns', 'false_count', 'true_count'],
index=range(4),
data=[
[100.0, 6, 4],
[400.0, 10, 0],
[700.0, 10, 0],
[1000.0, 10, 0],
],
)
)

good_fit.plot(include_fit=True)

bad_fit = cirq.experiments.T1DecayResult(
data=pd.DataFrame(
columns=['delay_ns', 'false_count', 'true_count'],
index=range(4),
data=[
[100.0, 10, 0],
[400.0, 10, 0],
[700.0, 10, 0],
[1000.0, 10, 0],
],
)
)

try:
bad_fit.plot(include_fit=True)
assert False
except RuntimeWarning as warning:
assert warning is not None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to split this error case off into a separate test method. You can also make use of

with pytest.raises(..., match='whatever the error string is'):
    ... something that breaks...

to verify an error gets raised in the test.



def test_bad_args():
with pytest.raises(ValueError, match='repetitions <= 0'):
_ = cirq.experiments.t1_decay(
Expand Down