Exemplo n.º 1
0
    def __init__(
        self,
        measurement_duration: 'cirq.DURATION_LIKE',
        twoq_gates_duration: 'cirq.DURATION_LIKE',
        oneq_gates_duration: 'cirq.DURATION_LIKE',
        qubits: Iterable[devices.LineQubit],
    ) -> None:
        """Initializes the description of an ion trap device.

        Args:
            measurement_duration: The maximum duration of a measurement.
            twoq_gates_duration: The maximum duration of a two qubit operation.
            oneq_gates_duration: The maximum duration of a single qubit
            operation.
            qubits: Qubits on the device, identified by their x location.

        Raises:
            TypeError: If not all the qubits supplied are `cirq.LineQubit`s.
        """
        self._measurement_duration = value.Duration(measurement_duration)
        self._twoq_gates_duration = value.Duration(twoq_gates_duration)
        self._oneq_gates_duration = value.Duration(oneq_gates_duration)
        if not all(isinstance(qubit, devices.LineQubit) for qubit in qubits):
            raise TypeError(
                "All qubits were not of type cirq.LineQubit, instead were "
                f"{set(type(qubit) for qubit in qubits)}")
        self.qubits = frozenset(qubits)
        self.gateset = get_ion_gateset()
Exemplo n.º 2
0
    def __init__(
        self,
        measurement_duration: 'cirq.DURATION_LIKE',
        twoq_gates_duration: 'cirq.DURATION_LIKE',
        oneq_gates_duration: 'cirq.DURATION_LIKE',
        qubits: Iterable[devices.LineQubit],
    ) -> None:
        """Initializes the description of an ion trap device.

        Args:
            measurement_duration: The maximum duration of a measurement.
            twoq_gates_duration: The maximum duration of a two qubit operation.
            oneq_gates_duration: The maximum duration of a single qubit
            operation.
            qubits: Qubits on the device, identified by their x location.

        Raises:
            TypeError: If not all the qubits supplied are `cirq.LineQubit`s.
        """
        self._measurement_duration = value.Duration(measurement_duration)
        self._twoq_gates_duration = value.Duration(twoq_gates_duration)
        self._oneq_gates_duration = value.Duration(oneq_gates_duration)
        if not all(isinstance(qubit, devices.LineQubit) for qubit in qubits):
            raise TypeError(
                "All qubits were not of type cirq.LineQubit, instead were "
                f"{set(type(qubit) for qubit in qubits)}"
            )
        self.qubits = frozenset(qubits)
        self.gateset = _IonTargetGateset()

        graph = nx.Graph()
        graph.add_edges_from([(a, b) for a in qubits for b in qubits if a != b], directed=False)
        self._metadata = devices.DeviceMetadata(self.qubits, graph)
Exemplo n.º 3
0
def _cpmg_circuit(qubit: devices.GridQubit, delay_var: sympy.Symbol,
                  max_pulses: int) -> 'cirq.Circuit':
    """Creates a CPMG circuit for a given qubit.

    The circuit will look like:

      sqrt(Y) - wait(delay_var) - X - wait(2*delay_var) - ... - wait(delay_var)

    with max_pulses number of X gates.

    The X gates are paramterizd by 'pulse_N' symbols so that pulses can be
    turned on and off.  This is done to combine circuits with different pulses
    into the same paramterized circuit.
    """
    circuit = circuits.Circuit(
        ops.Y(qubit)**0.5,
        ops.WaitGate(value.Duration(nanos=delay_var))(qubit), ops.X(qubit))
    for n in range(max_pulses):
        pulse_n_on = sympy.Symbol(f'pulse_{n}')
        circuit.append(
            ops.WaitGate(value.Duration(nanos=2 * delay_var *
                                        pulse_n_on))(qubit))
        circuit.append(ops.X(qubit)**pulse_n_on)
    circuit.append(ops.WaitGate(value.Duration(nanos=delay_var))(qubit))
    return circuit
Exemplo n.º 4
0
def t1_decay(
    sampler: 'cirq.Sampler',
    *,
    qubit: 'cirq.Qid',
    num_points: int,
    max_delay: 'cirq.DURATION_LIKE',
    min_delay: 'cirq.DURATION_LIKE' = None,
    repetitions: int = 1000,
) -> 'cirq.experiments.T1DecayResult':
    """Runs a t1 decay experiment.

    Initializes a qubit into the |1⟩ state, waits for a variable amount of time,
    and measures the qubit. Plots how often the |1⟩ state is observed for each
    amount of waiting.

    Args:
        sampler: The quantum engine or simulator to run the circuits.
        qubit: The qubit under test.
        num_points: The number of evenly spaced delays to test.
        max_delay: The largest delay to test.
        min_delay: The smallest delay to test. Defaults to no delay.
        repetitions: The number of repetitions of the circuit for each delay.

    Returns:
        A T1DecayResult object that stores and can plot the data.
    """
    min_delay_dur = value.Duration(min_delay)
    max_delay_dur = value.Duration(max_delay)

    if repetitions <= 0:
        raise ValueError('repetitions <= 0')
    if max_delay_dur < min_delay_dur:
        raise ValueError('max_delay < min_delay')
    if min_delay_dur < 0:
        raise ValueError('min_delay < 0')
    var = sympy.Symbol('delay_ns')

    sweep = study.Linspace(var,
                           start=min_delay_dur.total_nanos(),
                           stop=max_delay_dur.total_nanos(),
                           length=num_points)

    circuit = circuits.Circuit(
        ops.X(qubit),
        ops.wait(qubit, nanos=var),
        ops.measure(qubit, key='output'),
    )

    results = sampler.sample(circuit, params=sweep, repetitions=repetitions)

    # Cross tabulate into a delay_ns, false_count, true_count table.
    tab = pd.crosstab(results.delay_ns, results.output)
    tab.rename_axis(None, axis="columns", inplace=True)
    tab = tab.rename(columns={0: 'false_count', 1: 'true_count'}).reset_index()
    for col_index, name in [(1, 'false_count'), (2, 'true_count')]:
        if name not in tab:
            tab.insert(col_index, name, [0] * tab.shape[0])

    return T1DecayResult(tab)
Exemplo n.º 5
0
    def __init__(self,
                 duration: 'cirq.DURATION_LIKE',
                 num_qubits: Optional[int] = None,
                 qid_shape: Tuple[int, ...] = None) -> None:
        """Initialize a wait gate with the given duration.

        Args:
            duration: A constant or parameterized wait duration. This can be
                an instance of `datetime.timedelta` or `cirq.Duration`.
        """
        self.duration = value.Duration(duration)
        if not protocols.is_parameterized(self.duration) and self.duration < 0:
            raise ValueError('duration < 0')
        if qid_shape is None:
            if num_qubits is None:
                # Assume one qubit for backwards compatibility
                qid_shape = (2, )
            else:
                qid_shape = (2, ) * num_qubits
        if num_qubits is None:
            num_qubits = len(qid_shape)
        if not qid_shape:
            raise ValueError('Waiting on an empty set of qubits.')
        if num_qubits != len(qid_shape):
            raise ValueError('len(qid_shape) != num_qubits')
        self._qid_shape = qid_shape
Exemplo n.º 6
0
    def __init__(self, measurement_duration: 'cirq.DURATION_LIKE',
                 exp_w_duration: 'cirq.DURATION_LIKE',
                 exp_11_duration: 'cirq.DURATION_LIKE',
                 qubits: Iterable[GridQubit]) -> None:
        """Initializes the description of an xmon device.

        Args:
            measurement_duration: The maximum duration of a measurement.
            exp_w_duration: The maximum duration of an ExpW operation.
            exp_11_duration: The maximum duration of an ExpZ operation.
            qubits: Qubits on the device, identified by their x, y location.
        """
        self._measurement_duration = value.Duration(measurement_duration)
        self._exp_w_duration = value.Duration(exp_w_duration)
        self._exp_z_duration = value.Duration(exp_11_duration)
        self.qubits = frozenset(qubits)
Exemplo n.º 7
0
def wait(*target: 'cirq.Qid',
         duration: 'cirq.DURATION_LIKE' = None,
         picos: Union[int, float, sympy.Basic] = 0,
         nanos: Union[int, float, sympy.Basic] = 0,
         micros: Union[int, float, sympy.Basic] = 0,
         millis: Union[int, float, sympy.Basic] = 0) -> raw_types.Operation:
    """Creates a WaitGate applied to all the given qubits.

    The duration can be specified as a DURATION_LIKE or using keyword args with
    numbers in the appropriate units. See Duration for details.

    Args:
        *target: The qubits that should wait.
        value: Wait duration (see Duration).
        picos: Picoseconds to wait (see Duration).
        nanos: Picoseconds to wait (see Duration).
        micros: Picoseconds to wait (see Duration).
        millis: Picoseconds to wait (see Duration).
    """
    return WaitGate(
        duration=value.Duration(
            duration,
            picos=picos,
            nanos=nanos,
            micros=micros,
            millis=millis,
        ),
        qid_shape=protocols.qid_shape(target),
    ).on(*target)
Exemplo n.º 8
0
    def __init__(self, measurement_duration: 'cirq.DURATION_LIKE',
                 twoq_gates_duration: 'cirq.DURATION_LIKE',
                 oneq_gates_duration: 'cirq.DURATION_LIKE',
                 qubits: Iterable[devices.LineQubit]) -> None:
        """Initializes the description of an ion trap device.

        Args:
            measurement_duration: The maximum duration of a measurement.
            twoq_gates_duration: The maximum duration of a two qubit operation.
            oneq_gates_duration: The maximum duration of a single qubit
            operation.
            qubits: Qubits on the device, identified by their x, y location.
        """
        self._measurement_duration = value.Duration(measurement_duration)
        self._twoq_gates_duration = value.Duration(twoq_gates_duration)
        self._oneq_gates_duration = value.Duration(oneq_gates_duration)
        self.qubits = frozenset(qubits)
Exemplo n.º 9
0
    def __init__(self, duration: 'cirq.DURATION_LIKE') -> None:
        """Initialize a wait gate with the given duration.

        Args:
            duration: A constant or parameterized wait duration. This can be
                an instance of `datetime.timedelta` or `cirq.Duration`.
        """
        self.duration = value.Duration(duration)
        if not protocols.is_parameterized(self.duration) and self.duration < 0:
            raise ValueError('duration < 0')
Exemplo n.º 10
0
 def duration_of(self, operation):
     if isinstance(operation.gate, ops.CZPowGate):
         return self._exp_z_duration
     if isinstance(operation.gate, ops.MeasurementGate):
         return self._measurement_duration
     if isinstance(operation.gate, (ops.XPowGate, ops.YPowGate, ops.PhasedXPowGate)):
         return self._exp_w_duration
     if isinstance(operation.gate, ops.ZPowGate):
         # Z gates are performed in the control software.
         return value.Duration()
     raise ValueError('Unsupported gate type: {!r}'.format(operation))
Exemplo n.º 11
0
    def __init__(
        self,
        duration: 'cirq.DURATION_LIKE',
        num_qubits: Optional[int] = None,
        qid_shape: Tuple[int, ...] = None,
    ) -> None:
        """Initialize a wait gate with the given duration.

        Args:
            duration: A constant or parameterized wait duration. This can be
                an instance of `datetime.timedelta` or `cirq.Duration`.
            num_qubits: The number of qubits the gate operates on. If None and `qid_shape` is None,
                this defaults to one qubit.
            qid_shape: Can be specified instead of `num_qubits` for the case that the gate should
                act on qudits.

        Raises:
            ValueError: If the `qid_shape` provided is empty or `num_qubits` contradicts
                `qid_shape`.
        """
        self._duration = value.Duration(duration)
        if not protocols.is_parameterized(self.duration) and self.duration < 0:
            raise ValueError('duration < 0')
        if qid_shape is None:
            if num_qubits is None:
                # Assume one qubit for backwards compatibility
                qid_shape = (2, )
            else:
                qid_shape = (2, ) * num_qubits
        if num_qubits is None:
            num_qubits = len(qid_shape)
        if not qid_shape:
            raise ValueError('Waiting on an empty set of qubits.')
        if num_qubits != len(qid_shape):
            raise ValueError('len(qid_shape) != num_qubits')
        self._qid_shape = qid_shape
Exemplo n.º 12
0
 def duration_of(self, operation: ops.Operation) -> value.Duration:
     return value.Duration(picos=0)
Exemplo n.º 13
0
        args=[]),
    op_deserializer.GateOpDeserializer(
        serialized_gate_id='inv_fsim_pi_4',
        gate_constructor=lambda: ops.FSimGate(theta=-np.pi / 4, phi=0),
        args=[]),
]

#
# WaitGate serializer and deserializer
#
WAIT_GATE_SERIALIZER = op_serializer.GateOpSerializer(
    gate_type=ops.WaitGate,
    serialized_gate_id='wait',
    args=[
        op_serializer.SerializingArg(
            serialized_name='nanos',
            serialized_type=float,
            op_getter=lambda op: cast(ops.WaitGate, op.gate
                                      ).duration.total_nanos()),
    ])
WAIT_GATE_DESERIALIZER = op_deserializer.GateOpDeserializer(
    serialized_gate_id='wait',
    gate_constructor=ops.WaitGate,
    args=[
        op_deserializer.DeserializingArg(
            serialized_name='nanos',
            constructor_arg_name='duration',
            value_func=lambda nanos: value.Duration(nanos=cast(
                Union[int, float, sympy.Basic], nanos)))
    ])
Exemplo n.º 14
0
 def duration_of(self, operation):
     return value.Duration(picos=0)
Exemplo n.º 15
0
def t2_decay(
    sampler: 'cirq.Sampler',
    *,
    qubit: 'cirq.Qid',
    experiment_type: 'ExperimentType' = ExperimentType.RAMSEY,
    num_points: int,
    max_delay: 'cirq.DURATION_LIKE',
    min_delay: 'cirq.DURATION_LIKE' = None,
    repetitions: int = 1000,
    delay_sweep: Optional[study.Sweep] = None,
    num_pulses: List[int] = None
) -> Union['cirq.experiments.T2DecayResult',
           List['cirq.experiments.T2DecayResult']]:
    """Runs a t2 transverse relaxation experiment.

    Initializes a qubit into a superposition state, evolves the system using
    rules determined by the experiment type and by the delay parameters,
    then rotates back for measurement.  This will measure the phase decoherence
    decay.  This experiment has three types of T2 metrics, each which measure
    a different slice of the noise spectrum.

    For the Ramsey experiment type (often denoted T2*), the state will be
    prepared with a square root Y gate (`cirq.Y ** 0.5`) and then waits for
    a variable amount of time.  After this time, it will do basic state
    tomography to measure the expectation of the Pauli-X and Pauli-Y operators
    by performing either a `cirq.Y ** -0.5` or `cirq.X ** 0.5`.  The square of
    these two measurements is summed to determine the length of the Bloch
    vector. This experiment measures the phase decoherence of the system under
    free evolution.

    For the Hahn echo experiment (often denoted T2 or spin echo), the state
    will also be prepared with a square root Y gate (`cirq.Y ** 0.5`).
    However, during the mid-point of the delay time being measured, a pi-pulse
    (`cirq.X`) gate will be applied to cancel out inhomogeneous dephasing.
    The same method of measuring the final state as Ramsey experiment is applied
    after the second half of the delay period.  See the animation on the wiki
    page https://en.wikipedia.org/wiki/Spin_echo for a visual illustration
    of this experiment.

    CPMG, or the Carr-Purcell-Meiboom-Gill sequence, involves using a sqrt(Y)
    followed by a sequence of pi pulses (X gates) in a specific timing pattern:

        π/2, t, π, 2t, π, ... 2t, π, t

    The first pulse, a sqrt(Y) gate, will put the qubit's state on the Bloch
    equator.  After a delay, successive X gates will refocus dehomogenous
    phase effects by causing them to precess in opposite directions and
    averaging their effects across the entire pulse train.

    This pulse pattern has two variables that can be adjusted.  The first,
    denoted as 't' in the above sequence, is delay, which can be specified
    with `delay_min` and `delay_max` or by using a `delay_sweep`, similar to
    the other experiments.  The second variable is the number of pi pulses
    (X gates).  This can be specified as a list of integers using the
    `num_pulses` parameter.  If multiple different pulses are specified,
    the data will be presented in a data frame with two
    indices (delay_ns and num_pulses).

    See the following reference for more information about CPMG pulse trains:
    Meiboom, S., and D. Gill, “Modified spin-echo method for measuring nuclear
    relaxation times”, Rev. Sci. Inst., 29, 688–691 (1958).
    https://doi.org/10.1063/1.1716296

    Note that interpreting T2 data is fairly tricky and subtle, as it can
    include other effects that need to be accounted for.  For instance,
    amplitude damping (T1) will present as T2 noise and needs to be
    appropriately compensated for to find a true measure of T2.  Due to this
    subtlety and lack of standard way to interpret the data, the fitting
    of the data to an exponential curve and the extrapolation of an actual
    T2 time value is left as an exercise to the reader.

    Args:
        sampler: The quantum engine or simulator to run the circuits.
        qubit: The qubit under test.
        experiment_type: The type of T2 test to run.
        num_points: The number of evenly spaced delays to test.
        max_delay: The largest delay to test.
        min_delay: The smallest delay to test. Defaults to no delay.
        repetitions: The number of repetitions of the circuit
             for each delay and for each tomography result.
        delay_sweep: Optional range of time delays to sweep across.  This should
             be a SingleSweep using the 'delay_ns' with values in integer number
             of nanoseconds.  If specified, this will override the max_delay and
             min_delay parameters.  If not specified, the experiment will sweep
             from min_delay to max_delay with linear steps.
        num_pulses: For CPMG, a list of the number of pulses to use.
             If multiple pulses are specified, each will be swept on.
    Returns:
        A T2DecayResult object that stores and can plot the data.
    """
    min_delay_dur = value.Duration(min_delay)
    max_delay_dur = value.Duration(max_delay)

    # Input validation
    if repetitions <= 0:
        raise ValueError('repetitions <= 0')
    if max_delay_dur < min_delay_dur:
        raise ValueError('max_delay < min_delay')
    if min_delay_dur < 0:
        raise ValueError('min_delay < 0')
    if num_pulses and experiment_type != ExperimentType.CPMG:
        raise ValueError('num_pulses is only valid for CPMG experiments.')

    # Initialize values used in sweeps
    delay_var = sympy.Symbol('delay_ns')
    inv_x_var = sympy.Symbol('inv_x')
    inv_y_var = sympy.Symbol('inv_y')
    max_pulses = max(num_pulses) if num_pulses else 0

    if not delay_sweep:
        delay_sweep = study.Linspace(delay_var,
                                     start=min_delay_dur.total_nanos(),
                                     stop=max_delay_dur.total_nanos(),
                                     length=num_points)
    if delay_sweep.keys != ['delay_ns']:
        raise ValueError('delay_sweep must be a SingleSweep '
                         'with delay_ns parameter')

    if experiment_type == ExperimentType.RAMSEY:
        # Ramsey T2* experiment
        # Use sqrt(Y) to flip to the equator.
        # Evolve the state for a given amount of delay time
        # Then measure the state in both X and Y bases.

        circuit = circuits.Circuit(
            ops.Y(qubit)**0.5,
            ops.wait(qubit, nanos=delay_var),
        )
    else:
        if experiment_type == ExperimentType.HAHN_ECHO:
            # Hahn / Spin Echo T2 experiment
            # Use sqrt(Y) to flip to the equator.
            # Evolve the state for the given amount of delay time
            # Flip the state using an X gate
            # Evolve the state for the given amount of delay time
            # Then measure the state in both X and Y bases.
            num_pulses = [0]
            # This is equivalent to a CPMG experiment with zero pulses
            # and will follow the same code path.

        # Carr-Purcell-Meiboom-Gill sequence.
        # Performs the following sequence
        # π/2 - wait(t) - π - wait(2t) - ... - π - wait(t)
        # There will be N π pulses (X gates)
        # where N sweeps over the values of num_pulses
        #
        if not num_pulses:
            raise ValueError('At least one value must be given '
                             'for num_pulses in a CPMG experiment')
        circuit = _cpmg_circuit(qubit, delay_var, max_pulses)

    # Add simple state tomography
    circuit.append(ops.X(qubit)**inv_x_var)
    circuit.append(ops.Y(qubit)**inv_y_var)
    circuit.append(ops.measure(qubit, key='output'))
    tomography_sweep = study.Zip(
        study.Points('inv_x', [0.0, 0.5]),
        study.Points('inv_y', [-0.5, 0.0]),
    )

    if num_pulses and max_pulses > 0:
        pulse_sweep = _cpmg_sweep(num_pulses)
        sweep = study.Product(delay_sweep, pulse_sweep, tomography_sweep)
    else:
        sweep = study.Product(delay_sweep, tomography_sweep)

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

    y_basis_measurements = results[abs(results.inv_y) > 0].copy()
    x_basis_measurements = results[abs(results.inv_x) > 0].copy()

    if num_pulses and len(num_pulses) > 1:
        cols = tuple(f'pulse_{t}' for t in range(max_pulses))
        x_basis_measurements[
            'num_pulses'] = x_basis_measurements.loc[:, cols].sum(axis=1)
        y_basis_measurements[
            'num_pulses'] = y_basis_measurements.loc[:, cols].sum(axis=1)

    x_basis_tabulation = _create_tabulation(x_basis_measurements)
    y_basis_tabulation = _create_tabulation(y_basis_measurements)

    # Return the results in a container object
    return T2DecayResult(x_basis_tabulation, y_basis_tabulation)
Exemplo n.º 16
0
 def duration_of(self, operation: 'cirq.Operation') -> 'cirq.Duration':
     return value.Duration(picos=0)
Exemplo n.º 17
0
def t2_decay(
    sampler: work.Sampler,
    *,
    qubit: devices.GridQubit,
    experiment_type: 'ExperimentType' = ExperimentType.RAMSEY,
    num_points: int,
    max_delay: 'cirq.DURATION_LIKE',
    min_delay: 'cirq.DURATION_LIKE' = None,
    repetitions: int = 1000,
    delay_sweep: Optional[study.Sweep] = None,
) -> 'cirq.experiments.T2DecayResult':
    """Runs a t2 transverse relaxation experiment.

    Initializes a qubit into a superposition state, evolves the system using
    rules determined by the experiment type and by the delay parameters,
    then rotates back for measurement.  This will measure the phase decoherence
    decay.  This experiment has three types of T2 metrics, each which measure
    a different slice of the noise spectrum.

    For the Ramsey experiment type (often denoted T2*), the state will be
    prepared with a square root Y gate (`cirq.Y ** 0.5`) and then waits for
    a variable amount of time.  After this time, it will do basic state
    tomography to measure the expectation of the Pauli-X and Pauli-Y operators
    by performing either a `cirq.Y ** -0.5` or `cirq.X ** -0.5`.  The square of
    these two measurements is summed to determine the length of the Bloch
    vector. This experiment measures the phase decoherence of the system under
    free evolution.

    For the Hahn echo experiment (often denoted T2 or spin echo), the state
    will also be prepared with a square root Y gate (`cirq.Y ** 0.5`).
    However, during the mid-point of the delay time being measured, a pi-pulse
    (`cirq.X`) gate will be applied to cancel out inhomogeneous dephasing.
    The same method of measuring the final state as Ramsey experiment is applied
    after the second half of the delay period.

    CPMG, or the Carr-Purcell-Meiboom-Gill sequence, is currently not
    implemented.

    Args:
        sampler: The quantum engine or simulator to run the circuits.
        qubit: The qubit under test.
        experiment_type: The type of T2 test to run.
        num_points: The number of evenly spaced delays to test.
        max_delay: The largest delay to test.
        min_delay: The smallest delay to test. Defaults to no delay.
        repetitions: The number of repetitions of the circuit
             for each delay and for each tomography result.
        delay_sweep: Optional range of time delays to sweep across.  This should
             be a SingleSweep using the 'delay_ns' with values in integer number
             of nanoseconds.  If specified, this will override the max_delay and
             min_delay parameters.  If not specified, the experiment will sweep
             from min_delay to max_delay with linear steps.
    Returns:
        A T2DecayResult object that stores and can plot the data.
    """
    min_delay_dur = value.Duration(min_delay)
    max_delay_dur = value.Duration(max_delay)

    # Input validation
    if repetitions <= 0:
        raise ValueError('repetitions <= 0')
    if max_delay_dur < min_delay_dur:
        raise ValueError('max_delay < min_delay')
    if min_delay_dur < 0:
        raise ValueError('min_delay < 0')

    # Initialize values used in sweeps
    delay_var = sympy.Symbol('delay_ns')
    inv_x_var = sympy.Symbol('inv_x')
    inv_y_var = sympy.Symbol('inv_y')

    if not delay_sweep:
        delay_sweep = study.Linspace(delay_var,
                                     start=min_delay_dur.total_nanos(),
                                     stop=max_delay_dur.total_nanos(),
                                     length=num_points)
    if delay_sweep.keys != ['delay_ns']:
        raise ValueError('delay_sweep must be a SingleSweep '
                         'with delay_ns parameter')

    if experiment_type == ExperimentType.RAMSEY:
        # Ramsey T2* experiment
        # Use sqrt(Y) to flip to the equator.
        # Evolve the state for a given amount of delay time
        # Then measure the state in both X and Y bases.

        circuit = circuits.Circuit(
            ops.Y(qubit)**0.5,
            ops.WaitGate(value.Duration(nanos=delay_var))(qubit),
            ops.X(qubit)**inv_x_var,
            ops.Y(qubit)**inv_y_var,
            ops.measure(qubit, key='output'),
        )
        tomography_sweep = study.Zip(
            study.Points('inv_x', [0.0, -0.5]),
            study.Points('inv_y', [-0.5, 0.0]),
        )
        sweep = study.Product(delay_sweep, tomography_sweep)
    elif experiment_type == ExperimentType.HAHN_ECHO:
        # Hahn / Spin Echo T2 experiment
        # Use sqrt(Y) to flip to the equator.
        # Evolve the state for half the given amount of delay time
        # Flip the state using an X gate
        # Evolve the state for half the given amount of delay time
        # Then measure the state in both X and Y bases.

        circuit = circuits.Circuit(
            ops.Y(qubit)**0.5,
            ops.WaitGate(value.Duration(nanos=0.5 * delay_var))(qubit),
            ops.X(qubit),
            ops.WaitGate(value.Duration(nanos=0.5 * delay_var))(qubit),
            ops.X(qubit)**inv_x_var,
            ops.Y(qubit)**inv_y_var,
            ops.measure(qubit, key='output'),
        )
        tomography_sweep = study.Zip(
            study.Points('inv_x', [0.0, 0.5]),
            study.Points('inv_y', [-0.5, 0.0]),
        )
        sweep = study.Product(delay_sweep, tomography_sweep)
    else:
        raise ValueError(f'Experiment type {experiment_type} not supported')

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

    y_basis_measurements = results[abs(results.inv_y) > 0]
    x_basis_measurements = results[abs(results.inv_x) > 0]
    x_basis_tabulation = pd.crosstab(
        x_basis_measurements.delay_ns,
        x_basis_measurements.output).reset_index()
    y_basis_tabulation = pd.crosstab(
        y_basis_measurements.delay_ns,
        y_basis_measurements.output).reset_index()

    # If all measurements are 1 or 0, fill in the missing column with all zeros.
    for tab in [x_basis_tabulation, y_basis_tabulation]:
        for col_index, name in [(1, 0), (2, 1)]:
            if name not in tab:
                tab.insert(col_index, name, [0] * tab.shape[0])

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