Skip to content

Commit

Permalink
Merge branch 'main' into rb
Browse files Browse the repository at this point in the history
  • Loading branch information
coruscating authored Sep 6, 2023
2 parents d641a53 + c294aa1 commit 0d50b5f
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 51 deletions.
61 changes: 45 additions & 16 deletions qiskit_experiments/library/characterization/half_angle.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,48 @@ class HalfAngle(BaseExperiment):
This sequence measures angle errors where the axis of the :code:`sx` and :code:`x`
rotation are not parallel. A similar experiment is described in Ref.~[1] where the
gate sequence :code:`x - y` is repeated to amplify errors caused by non-orthogonal
:code:`x` and :code:`y` rotation axes. Such errors can occur due to phase errors.
For example, the non-linearities in the mixer's skew for :math:`\pi/2` pulses may
be different from the :math:`\pi` pulse.
:code:`x` and :code:`y` rotation axes.
One cause of such errors is non-linearity in the microwave mixer used
to produce the pulses for the ``x`` and ``sx`` gates. Typically, these
gates are calibrated to have the same duration and so have different
pulse amplitudes. Non-linearities in the mixer's skew can cause the
angle to differ for these different pulse amplitudes.
The way the experiment works is that the initial ``Ry(π/2)`` puts the
qubit close to the :math:`+X` state, with a deviation :math:`δθ`, due
to the misalignment between ``sx`` and ``x`` (``Ry(π/2)`` is
implemented with ``sx`` as described below). The first ``sx - sx`` do
nothing as they should be rotations about the axis the qubit is
pointing along. The first ``y`` then mirrors the qubit about the
:math:`y` axis in the :math:`xy` plane of the Bloch sphere, so the
:math:`δθ` deviation from :math:`+X` becomes a :math:`-δθ` from
:math:`-X`. The next ``sx - sx`` sequence rotates about the axis that
is :math:`+δθ` rotated in the :math:`xy` plane from :math:`+X`, which
takes the deviation from :math:`-X` from :math:`-δθ` to :math:`+3 δθ`.
Then the next ``y`` mirrors this across the :math:`y` axis, taking the
state to :math:`-3 δθ` from :math:`+X`. This pattern continues with
each iteration, with the angular deviation in units of :math:`δθ`
following the sequence 1, 3, 5, 7, 9, etc. from :math:`+X` and
:math:`-X`. The final ``sx`` rotation serves mainly to rotate these
deviations from :math:`+X` and :math:`-X` in the :math:`xy` plane into
deviations out of the :math:`xy` plane, so that they appear as a signal
in the :math:`Z` basis. Because ``sx`` has a :math:`δθ` deviation from
``x``, the final ``sx`` adds an extra :math:`δθ` to the deviations, so
the pattern ends up as 2, 4, 6, 8, etc., meaning that each iteration
adds :math:`2 δθ` to the deviation from the equator of the Bloch sphere
(with the sign alternating due to the ``y`` gates, so the deviations
are really -2, 4, -6, 8, etc.).
For the implementation of the circuits, the experiment uses ``Rz(π/2) -
sx - Rz(-π/2)`` to implement the ``Ry(π/2)`` and ``Rz(π/2) - x -
Rz(-π/2)`` to implement the ``y``. So the experiment makes use of only
``sx``, ``x``, ``Rz(π/2)``, and ``Rz(-π/2)`` gates. For the
experiment's analysis to be valid, it is important that the ``sx`` and
``x`` gates are not replaced (such as by a transpiler pass that
replaces ``x`` with ``sx - sx``), as it is the angle between them which
is being inferred. It is assumed that the angle between ``x`` and
``Rz`` is exactly :math:`π/2`.
# section: analysis_ref
:class:`.ErrorAmplificationAnalysis`
Expand All @@ -66,18 +105,6 @@ def _default_experiment_options(cls) -> Options:
options.repetitions = list(range(15))
return options

@classmethod
def _default_transpile_options(cls) -> Options:
"""Default transpile options.
The basis gates option should not be changed since it will affect the gates and
the pulses that are run on the hardware.
"""
options = super()._default_transpile_options()
options.basis_gates = ["sx", "rz", "y"]
options.inst_map = None
return options

def __init__(self, physical_qubits: Sequence[int], backend: Optional[Backend] = None):
"""Setup a half angle experiment on the given qubit.
Expand Down Expand Up @@ -126,7 +153,9 @@ def circuits(self) -> List[QuantumCircuit]:
for _ in range(repetition):
circuit.sx(0)
circuit.sx(0)
circuit.y(0)
circuit.rz(np.pi / 2, 0)
circuit.x(0)
circuit.rz(-np.pi / 2, 0)

circuit.sx(0)
circuit.measure_all()
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/half-angle-x-600debac368ce2c6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
fixes:
- |
The :class:`.HalfAngle` experiment's circuits were changed so that they use
combinations of ``rz`` and ``x`` instead of the less standard ``y`` gate.
This change allows :class:`~HalfAngle` to be run on IBM backends directly.
Previously, it could only be run through the :class:`~HalfAngleCal`
subclass in combination with a :class:`~Calibrations` instance containing a
custom calibration for the ``y`` gate.
Fixes issue `#1233 <https://github.com/Qiskit-Extensions/qiskit-experiments/issues/1233>`_.
8 changes: 4 additions & 4 deletions test/library/characterization/test_half_angle.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ def test_end_to_end(self):
self.assertTrue(abs(d_theta - error) < tol)

def test_circuits(self):
"""Test that transpiling works and that we can have a y gate with a calibration."""
"""Test that transpiling works."""

qubit = 1

inst_map = InstructionScheduleMap()
for inst in ["sx", "y"]:
for inst in ["sx", "x"]:
inst_map.add(inst, (qubit,), pulse.Schedule(name=inst))

hac = HalfAngle([qubit])
Expand All @@ -64,8 +64,8 @@ def test_circuits(self):
self.assertEqual(circ.count_ops()["sx"], idx * 2 + 2)
self.assertEqual(circ.calibrations["sx"][((qubit,), ())], pulse.Schedule(name="sx"))
if idx > 0:
self.assertEqual(circ.count_ops()["y"], idx)
self.assertEqual(circ.calibrations["y"][((qubit,), ())], pulse.Schedule(name="y"))
self.assertEqual(circ.count_ops()["x"], idx)
self.assertEqual(circ.calibrations["x"][((qubit,), ())], pulse.Schedule(name="x"))

def test_experiment_config(self):
"""Test converting to and from config works"""
Expand Down
31 changes: 0 additions & 31 deletions test/library/characterization/test_t1.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,37 +190,6 @@ def test_t1_parallel_measurement_level_1(self):
self.assertEqual(sub_res.quality, "good")
self.assertAlmostEqual(sub_res.value.n, t1[qb], delta=3)

def test_t1_parallel_different_analysis_options(self):
"""
Test parallel experiments of T1 using a simulator, for the case where
the sub-experiments have different analysis options
"""

t1 = [25, 25]
t2 = [value / 2 for value in t1]

backend = NoisyDelayAerBackend(t1, t2)

delays = list(range(1, 40, 3))

exp0 = T1([0], delays)
exp0.analysis.set_options(p0={"tau": 30})

exp1 = T1([1], delays)
exp1.analysis.set_options(p0={"tau": 1000000})

par_exp = ParallelExperiment([exp0, exp1], flatten_results=False)
res = par_exp.run(backend=backend, seed_simulator=4)
self.assertExperimentDone(res)

sub_res = []
for i in range(2):
sub_res.append(res.child_data(i).analysis_results("T1"))

self.assertEqual(sub_res[0].quality, "good")
self.assertAlmostEqual(sub_res[0].value.n, t1[0], delta=3)
self.assertEqual(sub_res[1].quality, "bad")

def test_t1_analysis(self):
"""
Test T1Analysis
Expand Down

0 comments on commit 0d50b5f

Please sign in to comment.