mirror of https://github.com/Qiskit/qiskit.git
add gaussian square echo pulse (#9370)
* add gaussian square echo pulse * address some PR comments * fixed parameters of symbolicpulse * adding math description and reference * change variable names * fix lint * remove width_echo parameter * fix lint * add release notes * fix conflicts * address some PR comments * fixed parameters of symbolicpulse * adding math description and reference * change variable names * fix lint * remove width_echo parameter * fix lint * add release notes * Update first paragraph Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com> * Update amp param to float Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com> * Update angle def param to float Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com> * Update amp constrain Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com> * drafting tests * update docstring * finish up test * fix missing pulses from init * addding Cos * missing sin * fixed tests * black formatting * break string * fixing strings * reformatting * fix lint * fixing last things * fix tests * fix black * fix another test * fix amp validation * fixing docstring * Update qiskit/pulse/library/symbolic_pulses.py Co-authored-by: Will Shanks <wshaos@posteo.net> * rename to gaussian_square_echo * fix lint * Update qiskit/qobj/converters/pulse_instruction.py Co-authored-by: Will Shanks <wshaos@posteo.net> * Update releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml Co-authored-by: Will Shanks <wshaos@posteo.net> --------- Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com> Co-authored-by: Will Shanks <wshaos@posteo.net>
This commit is contained in:
parent
c3b0afaab2
commit
bedecbdce5
|
@ -142,6 +142,7 @@ from qiskit.pulse.library import (
|
|||
Gaussian,
|
||||
GaussianSquare,
|
||||
GaussianSquareDrag,
|
||||
gaussian_square_echo,
|
||||
Sin,
|
||||
Cos,
|
||||
Sawtooth,
|
||||
|
|
|
@ -88,6 +88,7 @@ Parametric Pulse Representation
|
|||
Gaussian
|
||||
GaussianSquare
|
||||
GaussianSquareDrag
|
||||
gaussian_square_echo
|
||||
Sin
|
||||
Cos
|
||||
Sawtooth
|
||||
|
@ -117,6 +118,7 @@ from .symbolic_pulses import (
|
|||
Gaussian,
|
||||
GaussianSquare,
|
||||
GaussianSquareDrag,
|
||||
gaussian_square_echo,
|
||||
Drag,
|
||||
Constant,
|
||||
Sin,
|
||||
|
|
|
@ -1068,6 +1068,207 @@ def GaussianSquareDrag(
|
|||
return instance
|
||||
|
||||
|
||||
def gaussian_square_echo(
|
||||
duration: Union[int, ParameterExpression],
|
||||
amp: Union[float, ParameterExpression],
|
||||
sigma: Union[float, ParameterExpression],
|
||||
width: Optional[Union[float, ParameterExpression]] = None,
|
||||
angle: Optional[Union[float, ParameterExpression]] = 0.0,
|
||||
active_amp: Optional[Union[float, ParameterExpression]] = 0.0,
|
||||
active_angle: Optional[Union[float, ParameterExpression]] = 0.0,
|
||||
risefall_sigma_ratio: Optional[Union[float, ParameterExpression]] = None,
|
||||
name: Optional[str] = None,
|
||||
limit_amplitude: Optional[bool] = None,
|
||||
) -> SymbolicPulse:
|
||||
"""An echoed Gaussian square pulse with an active tone overlaid on it.
|
||||
|
||||
The Gaussian Square Echo pulse is composed of three pulses. First, a Gaussian Square pulse
|
||||
:math:`f_{echo}(x)` with amplitude ``amp`` and phase ``angle`` playing for half duration,
|
||||
followed by a second Gaussian Square pulse :math:`-f_{echo}(x)` with opposite amplitude
|
||||
and same phase playing for the rest of the duration. Third a Gaussian Square pulse
|
||||
:math:`f_{active}(x)` with amplitude ``active_amp`` and phase ``active_angle``
|
||||
playing for the entire duration. The Gaussian Square Echo pulse :math:`g_e()`
|
||||
can be written as:
|
||||
|
||||
.. math::
|
||||
|
||||
g_e(x) &= \\begin{cases}\
|
||||
f_{\\text{active}} + f_{\\text{echo}}(x)\
|
||||
& x < \\frac{\\text{duration}}{2}\\\\
|
||||
f_{\\text{active}} - f_{\\text{echo}}(x)\
|
||||
& \\frac{\\text{duration}}{2} < x\
|
||||
\\end{cases}\\\\
|
||||
|
||||
One case where this pulse can be used is when implementing a direct CNOT gate with
|
||||
a cross-resonance superconducting qubit architecture. When applying this pulse to
|
||||
the target qubit, the active portion can be used to cancel IX terms from the
|
||||
cross-resonance drive while the echo portion can reduce the impact of a static ZZ coupling.
|
||||
|
||||
Exactly one of the ``risefall_sigma_ratio`` and ``width`` parameters has to be specified.
|
||||
|
||||
If ``risefall_sigma_ratio`` is not ``None`` and ``width`` is ``None``:
|
||||
|
||||
.. math::
|
||||
|
||||
\\text{risefall} &= \\text{risefall_sigma_ratio} \\times \\text{sigma}\\\\
|
||||
\\text{width} &= \\text{duration} - 2 \\times \\text{risefall}
|
||||
|
||||
If ``width`` is not None and ``risefall_sigma_ratio`` is None:
|
||||
|
||||
.. math:: \\text{risefall} = \\frac{\\text{duration} - \\text{width}}{2}
|
||||
|
||||
References:
|
||||
1. |citation1|_
|
||||
|
||||
.. _citation1: https://iopscience.iop.org/article/10.1088/2058-9565/abe519
|
||||
|
||||
.. |citation1| replace:: *Jurcevic, P., Javadi-Abhari, A., Bishop, L. S.,
|
||||
Lauer, I., Bogorin, D. F., Brink, M., Capelluto, L., G{\"u}nl{\"u}k, O.,
|
||||
Itoko, T., Kanazawa, N. & others
|
||||
Demonstration of quantum volume 64 on a superconducting quantum
|
||||
computing system. (Section V)*
|
||||
Args:
|
||||
duration: Pulse length in terms of the sampling period `dt`.
|
||||
amp: The amplitude of the rise and fall and of the echoed pulse.
|
||||
sigma: A measure of how wide or narrow the risefall is; see the class
|
||||
docstring for more details.
|
||||
width: The duration of the embedded square pulse.
|
||||
angle: The angle in radians of the complex phase factor uniformly
|
||||
scaling the echoed pulse. Default value 0.
|
||||
active_amp: The amplitude of the active pulse.
|
||||
active_angle: The angle in radian of the complex phase factor uniformly
|
||||
scaling the active pulse. Default value 0.
|
||||
risefall_sigma_ratio: The ratio of each risefall duration to sigma.
|
||||
name: Display name for this pulse envelope.
|
||||
limit_amplitude: If ``True``, then limit the amplitude of the
|
||||
waveform to 1. The default is ``True`` and the amplitude is constrained to 1.
|
||||
Returns:
|
||||
ScalableSymbolicPulse instance.
|
||||
Raises:
|
||||
PulseError: When width and risefall_sigma_ratio are both empty or both non-empty.
|
||||
"""
|
||||
# Convert risefall_sigma_ratio into width which is defined in OpenPulse spec
|
||||
if width is None and risefall_sigma_ratio is None:
|
||||
raise PulseError(
|
||||
"Either the pulse width or the risefall_sigma_ratio parameter must be specified."
|
||||
)
|
||||
if width is not None and risefall_sigma_ratio is not None:
|
||||
raise PulseError(
|
||||
"Either the pulse width or the risefall_sigma_ratio parameter can be specified"
|
||||
" but not both."
|
||||
)
|
||||
|
||||
if width is None and risefall_sigma_ratio is not None:
|
||||
width = duration - 2.0 * risefall_sigma_ratio * sigma
|
||||
|
||||
parameters = {
|
||||
"amp": amp,
|
||||
"angle": angle,
|
||||
"sigma": sigma,
|
||||
"width": width,
|
||||
"active_amp": active_amp,
|
||||
"active_angle": active_angle,
|
||||
}
|
||||
|
||||
# Prepare symbolic expressions
|
||||
(
|
||||
_t,
|
||||
_duration,
|
||||
_amp,
|
||||
_sigma,
|
||||
_active_amp,
|
||||
_width,
|
||||
_angle,
|
||||
_active_angle,
|
||||
) = sym.symbols("t, duration, amp, sigma, active_amp, width, angle, active_angle")
|
||||
|
||||
# gaussian square echo for rotary tone
|
||||
_center = _duration / 4
|
||||
|
||||
_width_echo = (_duration - 2 * (_duration - _width)) / 2
|
||||
|
||||
_sq_t0 = _center - _width_echo / 2
|
||||
_sq_t1 = _center + _width_echo / 2
|
||||
|
||||
_gaussian_ledge = _lifted_gaussian(_t, _sq_t0, -1, _sigma)
|
||||
_gaussian_redge = _lifted_gaussian(_t, _sq_t1, _duration / 2 + 1, _sigma)
|
||||
|
||||
envelope_expr_p = (
|
||||
_amp
|
||||
* sym.exp(sym.I * _angle)
|
||||
* sym.Piecewise(
|
||||
(_gaussian_ledge, _t <= _sq_t0),
|
||||
(_gaussian_redge, _t >= _sq_t1),
|
||||
(1, True),
|
||||
)
|
||||
)
|
||||
|
||||
_center_echo = _duration / 2 + _duration / 4
|
||||
|
||||
_sq_t0_echo = _center_echo - _width_echo / 2
|
||||
_sq_t1_echo = _center_echo + _width_echo / 2
|
||||
|
||||
_gaussian_ledge_echo = _lifted_gaussian(_t, _sq_t0_echo, _duration / 2 - 1, _sigma)
|
||||
_gaussian_redge_echo = _lifted_gaussian(_t, _sq_t1_echo, _duration + 1, _sigma)
|
||||
|
||||
envelope_expr_echo = (
|
||||
-1
|
||||
* _amp
|
||||
* sym.exp(sym.I * _angle)
|
||||
* sym.Piecewise(
|
||||
(_gaussian_ledge_echo, _t <= _sq_t0_echo),
|
||||
(_gaussian_redge_echo, _t >= _sq_t1_echo),
|
||||
(1, True),
|
||||
)
|
||||
)
|
||||
|
||||
envelope_expr = sym.Piecewise(
|
||||
(envelope_expr_p, _t <= _duration / 2), (envelope_expr_echo, _t >= _duration / 2), (0, True)
|
||||
)
|
||||
|
||||
# gaussian square for active cancellation tone
|
||||
_center_active = _duration / 2
|
||||
|
||||
_sq_t0_active = _center_active - _width / 2
|
||||
_sq_t1_active = _center_active + _width / 2
|
||||
|
||||
_gaussian_ledge_active = _lifted_gaussian(_t, _sq_t0_active, -1, _sigma)
|
||||
_gaussian_redge_active = _lifted_gaussian(_t, _sq_t1_active, _duration + 1, _sigma)
|
||||
|
||||
envelope_expr_active = (
|
||||
_active_amp
|
||||
* sym.exp(sym.I * _active_angle)
|
||||
* sym.Piecewise(
|
||||
(_gaussian_ledge_active, _t <= _sq_t0_active),
|
||||
(_gaussian_redge_active, _t >= _sq_t1_active),
|
||||
(1, True),
|
||||
)
|
||||
)
|
||||
|
||||
envelop_expr_total = envelope_expr + envelope_expr_active
|
||||
|
||||
consts_expr = sym.And(
|
||||
_sigma > 0, _width >= 0, _duration >= _width, _duration / 2 >= _width_echo
|
||||
)
|
||||
|
||||
# Check validity of amplitudes
|
||||
valid_amp_conditions_expr = sym.And(sym.Abs(_amp) + sym.Abs(_active_amp) <= 1.0)
|
||||
|
||||
instance = SymbolicPulse(
|
||||
pulse_type="gaussian_square_echo",
|
||||
duration=duration,
|
||||
parameters=parameters,
|
||||
name=name,
|
||||
limit_amplitude=limit_amplitude,
|
||||
envelope=envelop_expr_total,
|
||||
constraints=consts_expr,
|
||||
valid_amp_conditions=valid_amp_conditions_expr,
|
||||
)
|
||||
instance.validate_parameters()
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class Drag(metaclass=_PulseType):
|
||||
"""The Derivative Removal by Adiabatic Gate (DRAG) pulse is a standard Gaussian pulse
|
||||
with an additional Gaussian derivative component and lifting applied.
|
||||
|
|
|
@ -43,6 +43,7 @@ class ParametricPulseShapes(Enum):
|
|||
gaussian = "Gaussian"
|
||||
gaussian_square = "GaussianSquare"
|
||||
gaussian_square_drag = "GaussianSquareDrag"
|
||||
gaussian_square_echo = "gaussian_square_echo"
|
||||
drag = "Drag"
|
||||
constant = "Constant"
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Add new :meth:`~qiskit.pulse.gaussian_square_echo` pulse shape. This pulse
|
||||
is composed by three :class:`~qiskit.pulse.GaussianSquare` pulses. The
|
||||
first two are echo pulses with duration half of the total duration and
|
||||
implement rotary tones. The third pulse is a cancellation tone that lasts
|
||||
the full duration of the pulse and implements correcting single qubit
|
||||
rotations.
|
|
@ -24,6 +24,7 @@ from qiskit.pulse.library import (
|
|||
Gaussian,
|
||||
GaussianSquare,
|
||||
GaussianSquareDrag,
|
||||
gaussian_square_echo,
|
||||
Drag,
|
||||
Sin,
|
||||
Cos,
|
||||
|
@ -288,6 +289,65 @@ class TestParametricPulses(QiskitTestCase):
|
|||
with self.assertRaises(PulseError):
|
||||
GaussianSquareDrag(duration=50, width=0, sigma=4, amp=0.8, beta=-20)
|
||||
|
||||
def test_gaussian_square_echo_pulse(self):
|
||||
"""Test that gaussian_square_echo sample pulse matches expectations.
|
||||
|
||||
Test that the real part of the envelop matches GaussianSquare with
|
||||
given amplitude and phase active for half duration with another
|
||||
GaussianSquare active for the other half duration with opposite
|
||||
amplitude and a GaussianSquare active on the entire duration with
|
||||
its own amplitude and phase
|
||||
"""
|
||||
risefall = 32
|
||||
sigma = 4
|
||||
amp = 0.5
|
||||
width = 100
|
||||
duration = width + 2 * risefall
|
||||
active_amp = 0.1
|
||||
width_echo = (duration - 2 * (duration - width)) / 2
|
||||
|
||||
gse = gaussian_square_echo(
|
||||
duration=duration, sigma=sigma, amp=amp, width=width, active_amp=active_amp
|
||||
)
|
||||
gse_samples = gse.get_waveform().samples
|
||||
|
||||
gs_echo_pulse_pos = GaussianSquare(
|
||||
duration=duration / 2, sigma=sigma, amp=amp, width=width_echo
|
||||
)
|
||||
gs_echo_pulse_neg = GaussianSquare(
|
||||
duration=duration / 2, sigma=sigma, amp=-amp, width=width_echo
|
||||
)
|
||||
gs_active_pulse = GaussianSquare(
|
||||
duration=duration, sigma=sigma, amp=active_amp, width=width
|
||||
)
|
||||
gs_echo_pulse_pos_samples = np.array(
|
||||
gs_echo_pulse_pos.get_waveform().samples.tolist() + [0] * int(duration / 2)
|
||||
)
|
||||
gs_echo_pulse_neg_samples = np.array(
|
||||
[0] * int(duration / 2) + gs_echo_pulse_neg.get_waveform().samples.tolist()
|
||||
)
|
||||
gs_active_pulse_samples = gs_active_pulse.get_waveform().samples
|
||||
|
||||
np.testing.assert_almost_equal(
|
||||
gse_samples,
|
||||
gs_echo_pulse_pos_samples + gs_echo_pulse_neg_samples + gs_active_pulse_samples,
|
||||
)
|
||||
|
||||
def test_gaussian_square_echo_active_amp_validation(self):
|
||||
"""Test gaussian square echo active amp parameter validation."""
|
||||
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=0.2)
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=0.4)
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.5, active_amp=0.3)
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=-0.1, active_amp=0.2)
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=-0.2)
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=0.6)
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=-0.5, angle=1.5, active_amp=0.25)
|
||||
with self.assertRaises(PulseError):
|
||||
gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=1.1)
|
||||
with self.assertRaises(PulseError):
|
||||
gaussian_square_echo(duration=50, width=0, sigma=4, amp=-0.8, active_amp=-0.3)
|
||||
|
||||
def test_drag_pulse(self):
|
||||
"""Test that the Drag sample pulse matches the pulse library."""
|
||||
drag = Drag(duration=25, sigma=4, amp=0.5j, beta=1)
|
||||
|
@ -451,6 +511,22 @@ class TestParametricPulses(QiskitTestCase):
|
|||
repr(gsd),
|
||||
"GaussianSquareDrag(duration=20, sigma=30, width=14.0, beta=1, amp=1.0, angle=0.0)",
|
||||
)
|
||||
gse = gaussian_square_echo(duration=20, sigma=30, amp=1.0, width=3)
|
||||
self.assertEqual(
|
||||
repr(gse),
|
||||
(
|
||||
"gaussian_square_echo(duration=20, amp=1.0, angle=0.0, sigma=30, width=3,"
|
||||
" active_amp=0.0, active_angle=0.0)"
|
||||
),
|
||||
)
|
||||
gse = gaussian_square_echo(duration=20, sigma=30, amp=1.0, risefall_sigma_ratio=0.1)
|
||||
self.assertEqual(
|
||||
repr(gse),
|
||||
(
|
||||
"gaussian_square_echo(duration=20, amp=1.0, angle=0.0, sigma=30, width=14.0,"
|
||||
" active_amp=0.0, active_angle=0.0)"
|
||||
),
|
||||
)
|
||||
drag = Drag(duration=5, amp=0.5, sigma=7, beta=1)
|
||||
self.assertEqual(repr(drag), "Drag(duration=5, sigma=7, beta=1, amp=0.5, angle=0)")
|
||||
const = Constant(duration=150, amp=0.1, angle=0.3)
|
||||
|
@ -476,6 +552,17 @@ class TestParametricPulses(QiskitTestCase):
|
|||
with self.assertRaises(PulseError):
|
||||
GaussianSquareDrag(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=10, beta=1)
|
||||
|
||||
with self.assertRaises(PulseError):
|
||||
gaussian_square_echo(
|
||||
duration=150,
|
||||
amp=0.2,
|
||||
sigma=8,
|
||||
)
|
||||
with self.assertRaises(PulseError):
|
||||
gaussian_square_echo(duration=150, amp=0.2, sigma=8, width=160)
|
||||
with self.assertRaises(PulseError):
|
||||
gaussian_square_echo(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=10)
|
||||
|
||||
with self.assertRaises(PulseError):
|
||||
Constant(duration=150, amp=0.9 + 0.8j)
|
||||
with self.assertRaises(PulseError):
|
||||
|
@ -536,6 +623,25 @@ class TestParametricPulses(QiskitTestCase):
|
|||
)
|
||||
self.assertGreater(np.abs(waveform.amp), 1.0)
|
||||
|
||||
def test_gaussian_square_echo_limit_amplitude(self):
|
||||
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
|
||||
with self.assertRaises(PulseError):
|
||||
gaussian_square_echo(duration=1000, sigma=4.0, amp=1.01, width=100)
|
||||
|
||||
with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False):
|
||||
waveform = gaussian_square_echo(duration=100, sigma=1.0, amp=1.1, width=10)
|
||||
self.assertGreater(np.abs(waveform.amp), 1.0)
|
||||
|
||||
def test_gaussian_square_echo_limit_amplitude_per_instance(self):
|
||||
"""Test that the check for amplitude per instance."""
|
||||
with self.assertRaises(PulseError):
|
||||
gaussian_square_echo(duration=1000, sigma=4.0, amp=1.01, width=100)
|
||||
|
||||
waveform = gaussian_square_echo(
|
||||
duration=1000, sigma=4.0, amp=1.01, width=100, limit_amplitude=False
|
||||
)
|
||||
self.assertGreater(np.abs(waveform.amp), 1.0)
|
||||
|
||||
def test_drag_limit_amplitude(self):
|
||||
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
|
||||
with self.assertRaises(PulseError):
|
||||
|
|
Loading…
Reference in New Issue