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

Command/Inst refactor: Pulses and Play #3936

Merged
merged 49 commits into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5ae572a
Deprecate PulseCommands, create new Play Instruction-implementation. …
lcapelluto Feb 20, 2020
3433240
Update import statements in Pulse
lcapelluto Mar 9, 2020
a007339
Merge remote-tracking branch 'upstream/master' into issue-3750-pulses
lcapelluto Mar 11, 2020
2a71ffa
Fill in the implementation of some IP work like __call__
lcapelluto Mar 11, 2020
ba918a6
Merge remote-tracking branch 'upstream/master' into issue-3750-pulses
lcapelluto Mar 13, 2020
9bbc01f
Fill in and update documentation, complete import paths
lcapelluto Mar 13, 2020
c5b0741
fixup cyclic imports
lcapelluto Mar 13, 2020
780e5f8
IP updates to assemble_schedules for supporting Play instruction
lcapelluto Mar 16, 2020
d4418e3
Give all pulses a name arg and update reno note
lcapelluto Mar 16, 2020
66d2a25
Continue pulse_instruction conversion support for Play
lcapelluto Mar 16, 2020
646529c
Add support for Play in assemble schedules, fix style
lcapelluto Mar 16, 2020
4bfe424
Merge remote-tracking branch 'upstream/master' into issue-3750-pulses
lcapelluto Mar 16, 2020
6104e88
Attempt to fix build error
lcapelluto Mar 17, 2020
67ab1b4
Add missing name field to Constant pulse
lcapelluto Mar 17, 2020
36134c2
To support __call__ from Pulse, I need to import Play, which means I …
lcapelluto Mar 17, 2020
b8f73e6
Merge remote-tracking branch 'upstream/master' into issue-3750-pulses
lcapelluto Mar 18, 2020
efbbd17
Fixup bugs introduced in SamplePulse
lcapelluto Mar 18, 2020
33c8da4
Update tests with new API
lcapelluto Mar 18, 2020
8739997
Add tests and fixup missing name passing
lcapelluto Mar 19, 2020
f1ed9ed
Merge remote-tracking branch 'upstream/master' into issue-3750-pulses
lcapelluto Mar 19, 2020
0bed2c2
Fixup tests, remove deprecation warning from assemble execution, add …
lcapelluto Mar 19, 2020
dc1d42d
Fixup docs
lcapelluto Mar 19, 2020
e7facb3
Update functional_pulse import path and Instruction type ref
lcapelluto Mar 19, 2020
f719a86
Merge remote-tracking branch 'upstream/master' into issue-3750-pulses
lcapelluto Mar 19, 2020
cbefe8a
try again with the type hints
lcapelluto Mar 19, 2020
98169f3
Was missing updates to PulseCommand that needed to be migrated with t…
lcapelluto Mar 19, 2020
74ada9b
Was missing docstring type documentation for unresolvable type
lcapelluto Mar 19, 2020
a889e47
Was missing removal of PulseStyle in parametric pulses
lcapelluto Mar 19, 2020
16278c0
The changes from the sphinx warrnings pass hadn't been moved with the…
lcapelluto Mar 19, 2020
14fc973
Fix spacing in drag pulse docstring
lcapelluto Mar 19, 2020
3d9efd8
The docs should finally build now
lcapelluto Mar 20, 2020
d753a45
Update qiskit/pulse/instructions/play.py
lcapelluto Mar 23, 2020
e05c937
Update qiskit/pulse/pulse_lib/sample_pulse.py
lcapelluto Mar 23, 2020
30a517d
Apply suggestions from code review
lcapelluto Mar 23, 2020
c2ecd3d
Documentation improvements
lcapelluto Mar 23, 2020
4fe635d
Merge remote-tracking branch 'upstream/master' into issue-3750-pulses
lcapelluto Mar 23, 2020
8430aec
Test change because linux python35 is failing
lcapelluto Mar 23, 2020
c1c64a3
Update releasenotes/notes/unify-instructions-and-commands-aaa6d8724b8…
lcapelluto Mar 24, 2020
1abc77c
Merge branch 'master' into issue-3750-pulses
lcapelluto Mar 24, 2020
85988f2
Fix plotting Play instruction
lcapelluto Mar 24, 2020
dcc4c90
style, line length
lcapelluto Mar 24, 2020
5d1fb84
Attempt to fix failing test by comparing names directly
lcapelluto Mar 24, 2020
873e5a1
Handle all instruction types in add_instruction
lcapelluto Mar 25, 2020
2c73613
Remove unused import
lcapelluto Mar 25, 2020
e53f42e
Merge branch 'master' into issue-3750-pulses
lcapelluto Mar 25, 2020
e41082a
Update releasenotes/notes/unify-instructions-and-commands-aaa6d8724b8…
lcapelluto Mar 25, 2020
0e0c142
Separate note about Play feature into two notes: Play feature and Pul…
lcapelluto Mar 25, 2020
8de5dfb
Merge branch 'issue-3750-pulses' of github.com:lcapelluto/qiskit-terr…
lcapelluto Mar 25, 2020
c654bd3
Merge branch 'master' into issue-3750-pulses
mergify[bot] Mar 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
369 changes: 6 additions & 363 deletions qiskit/pulse/commands/parametric_pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,370 +37,13 @@ class ParametricPulseShapes(Enum):
...
new_supported_pulse_name = commands.YourPulseCommandClass
"""
from abc import abstractmethod
from typing import Any, Callable, Dict, Optional
import math
import warnings

import numpy as np
from qiskit.pulse.pulse_lib import ParametricPulse, Gaussian, GaussianSquare, Drag, ConstantPulse

from qiskit.pulse.pulse_lib import gaussian, gaussian_square, drag, constant, continuous
from .sample_pulse import SamplePulse
from ..instructions import Instruction
from ..channels import PulseChannel
from ..exceptions import PulseError

from .pulse_command import PulseCommand

# pylint: disable=missing-docstring


class ParametricPulse(PulseCommand):
"""The abstract superclass for parametric pulses."""

@abstractmethod
def __init__(self, duration: int):
"""Create a parametric pulse and validate the input parameters.

Args:
duration: Pulse length in terms of the the sampling period `dt`.
"""
super().__init__(duration=duration)
self.validate_parameters()

@abstractmethod
def get_sample_pulse(self) -> SamplePulse:
"""Return a SamplePulse with samples filled according to the formula that the pulse
represents and the parameter values it contains.
"""
raise NotImplementedError

@abstractmethod
def validate_parameters(self) -> None:
"""
Validate parameters.

Raises:
PulseError: If the parameters passed are not valid.
"""
raise NotImplementedError

@property
@abstractmethod
def parameters(self) -> Dict[str, Any]:
"""Return a dictionary containing the pulse's parameters."""
pass

def to_instruction(self, channel: PulseChannel,
name: Optional[str] = None) -> 'ParametricInstruction':
# pylint: disable=arguments-differ
return ParametricInstruction(self, channel, name=name)

def draw(self, dt: float = 1,
style=None,
filename: Optional[str] = None,
interp_method: Optional[Callable] = None,
scale: float = 1, interactive: bool = False,
scaling: float = None):
"""Plot the pulse.

Args:
dt: Time interval of samples.
style (Optional[PulseStyle]): A style sheet to configure plot appearance
filename: Name required to save pulse image
interp_method: A function for interpolation
scale: Relative visual scaling of waveform amplitudes
interactive: When set true show the circuit in a new window
(this depends on the matplotlib backend being used supporting this)
scaling: Deprecated, see `scale`

Returns:
matplotlib.figure: A matplotlib figure object of the pulse envelope
"""
return self.get_sample_pulse().draw(dt=dt, style=style, filename=filename,
interp_method=interp_method, scale=scale,
interactive=interactive)


class Gaussian(ParametricPulse):
"""
A truncated pulse envelope shaped according to the Gaussian function whose mean is centered at
the center of the pulse (duration / 2):

.. math::

f(x) = amp * exp( -(1/2) * (x - duration/2)^2 / sigma^2) ) , 0 <= x < duration
"""

def __init__(self,
duration: int,
amp: complex,
sigma: float):
"""Initialize the gaussian pulse.

Args:
duration: Pulse length in terms of the the sampling period `dt`.
amp: The amplitude of the Gaussian envelope.
sigma: A measure of how wide or narrow the Gaussian peak is; described mathematically
in the class docstring.
"""
self._amp = complex(amp)
self._sigma = sigma
super().__init__(duration=duration)

@property
def amp(self):
return self._amp

@property
def sigma(self):
return self._sigma

def get_sample_pulse(self) -> SamplePulse:
return gaussian(duration=self.duration, amp=self.amp,
sigma=self.sigma, zero_ends=False)

def validate_parameters(self) -> None:
if abs(self.amp) > 1.:
raise PulseError("The amplitude norm must be <= 1, "
"found: {}".format(abs(self.amp)))
if self.sigma <= 0:
raise PulseError("Sigma must be greater than 0.")

@property
def parameters(self) -> Dict[str, Any]:
return {"duration": self.duration, "amp": self.amp, "sigma": self.sigma}

def __repr__(self):
return '{}(duration={}, amp={}, sigma={})' \
''.format(self.__class__.__name__, self.duration, self.amp, self.sigma)


class GaussianSquare(ParametricPulse):
"""
A square pulse with a Gaussian shaped risefall on either side:

.. math::

risefall = (duration - width) / 2

0 <= x < risefall

f(x) = amp * exp( -(1/2) * (x - risefall/2)^2 / sigma^2) )

risefall <= x < risefall + width

f(x) = amp

risefall + width <= x < duration

f(x) = amp * exp( -(1/2) * (x - (risefall + width)/2)^2 / sigma^2) )
"""

def __init__(self,
duration: int,
amp: complex,
sigma: float,
width: float):
"""Initialize the gaussian square pulse.

Args:
duration: Pulse length in terms of the the sampling period `dt`.
amp: The amplitude of the Gaussian and of the square pulse.
sigma: A measure of how wide or narrow the Gaussian risefall is; see the class
docstring for more details.
width: The duration of the embedded square pulse.
"""
self._amp = complex(amp)
self._sigma = sigma
self._width = width
super().__init__(duration=duration)

@property
def amp(self):
return self._amp

@property
def sigma(self):
return self._sigma

@property
def width(self):
return self._width

def get_sample_pulse(self) -> SamplePulse:
return gaussian_square(duration=self.duration, amp=self.amp,
width=self.width, sigma=self.sigma,
zero_ends=False)

def validate_parameters(self) -> None:
if abs(self.amp) > 1.:
raise PulseError("The amplitude norm must be <= 1, "
"found: {}".format(abs(self.amp)))
if self.sigma <= 0:
raise PulseError("Sigma must be greater than 0.")
if self.width < 0 or self.width >= self.duration:
raise PulseError("The pulse width must be at least 0 and less than its duration.")

@property
def parameters(self) -> Dict[str, Any]:
return {"duration": self.duration, "amp": self.amp, "sigma": self.sigma,
"width": self.width}

def __repr__(self):
return '{}(duration={}, amp={}, sigma={}, width={})' \
''.format(self.__class__.__name__, self.duration, self.amp, self.sigma, self.width)


class Drag(ParametricPulse):
r"""The Derivative Removal by Adiabatic Gate (DRAG) pulse is a standard Gaussian pulse
with an additional Gaussian derivative component. It is designed to reduce the frequency
spectrum of a normal gaussian pulse near the :math:`|1\rangle` - :math:`|2\rangle` transition,
reducing the chance of leakage to the :math:`|2\rangle` state.

.. math::

f(x) = Gaussian + 1j * beta * d/dx [Gaussian]
= Gaussian + 1j * beta * (-(x - duration/2) / sigma^2) [Gaussian]

where 'Gaussian' is:

.. math::

Gaussian(x, amp, sigma) = amp * exp( -(1/2) * (x - duration/2)^2 / sigma^2) )

References:
1. |citation1|_

.. _citation1: https://link.aps.org/doi/10.1103/PhysRevA.83.012308

.. |citation1| replace:: *Gambetta, J. M., Motzoi, F., Merkel, S. T. & Wilhelm, F. K.
Analytic control methods for high-fidelity unitary operations
in a weakly nonlinear oscillator. Phys. Rev. A 83, 012308 (2011).*

2. |citation2|_

.. _citation2: https://link.aps.org/doi/10.1103/PhysRevLett.103.110501

.. |citation2| replace:: *F. Motzoi, J. M. Gambetta, P. Rebentrost, and F. K. Wilhelm
Phys. Rev. Lett. 103, 110501 – Published 8 September 2009.*
"""

def __init__(self,
duration: int,
amp: complex,
sigma: float,
beta: float):
"""Initialize the drag pulse.

Args:
duration: Pulse length in terms of the the sampling period `dt`.
amp: The amplitude of the Drag envelope.
sigma: A measure of how wide or narrow the Gaussian peak is; described mathematically
in the class docstring.
beta: The correction amplitude.
"""
self._amp = complex(amp)
self._sigma = sigma
self._beta = beta
super().__init__(duration=duration)

@property
def amp(self):
return self._amp

@property
def sigma(self):
return self._sigma

@property
def beta(self):
return self._beta

def get_sample_pulse(self) -> SamplePulse:
return drag(duration=self.duration, amp=self.amp, sigma=self.sigma,
beta=self.beta, zero_ends=False)

def validate_parameters(self) -> None:
if abs(self.amp) > 1.:
raise PulseError("The amplitude norm must be <= 1, "
"found: {}".format(abs(self.amp)))
if self.sigma <= 0:
raise PulseError("Sigma must be greater than 0.")
if isinstance(self.beta, complex):
raise PulseError("Beta must be real.")
# Check if beta is too large: the amplitude norm must be <=1 for all points
if self.beta > self.sigma:
# If beta <= sigma, then the maximum amplitude is at duration / 2, which is
# already constrainted by self.amp <= 1

# 1. Find the first maxima associated with the beta * d/dx gaussian term
# This eq is derived from solving for the roots of the norm of the drag function.
# There is a second maxima mirrored around the center of the pulse with the same
# norm as the first, so checking the value at the first x maxima is sufficient.
argmax_x = (self.duration / 2
- (self.sigma / self.beta) * math.sqrt(self.beta ** 2 - self.sigma ** 2))
if argmax_x < 0:
# If the max point is out of range, either end of the pulse will do
argmax_x = 0

# 2. Find the value at that maximum
max_val = continuous.drag(np.array(argmax_x), sigma=self.sigma,
beta=self.beta, amp=self.amp, center=self.duration / 2)
if abs(max_val) > 1.:
raise PulseError("Beta is too large; pulse amplitude norm exceeds 1.")

@property
def parameters(self) -> Dict[str, Any]:
return {"duration": self.duration, "amp": self.amp, "sigma": self.sigma,
"beta": self.beta}

def __repr__(self):
return '{}(duration={}, amp={}, sigma={}, beta={})' \
''.format(self.__class__.__name__, self.duration, self.amp, self.sigma, self.beta)


class ConstantPulse(ParametricPulse):
"""
A simple constant pulse, with an amplitude value and a duration:

.. math::

f(x) = amp , 0 <= x < duration
f(x) = 0 , elsewhere
"""

def __init__(self,
duration: int,
amp: complex):
"""
Initialize the constant-valued pulse.

Args:
duration: Pulse length in terms of the the sampling period `dt`.
amp: The amplitude of the constant square pulse.
"""
self._amp = complex(amp)
super().__init__(duration=duration)

@property
def amp(self):
return self._amp

def get_sample_pulse(self) -> SamplePulse:
return constant(duration=self.duration, amp=self.amp)

def validate_parameters(self) -> None:
if abs(self.amp) > 1.:
raise PulseError("The amplitude norm must be <= 1, "
"found: {}".format(abs(self.amp)))

@property
def parameters(self) -> Dict[str, Any]:
return {"duration": self.duration, "amp": self.amp}

def __repr__(self):
return '{}(duration={}, amp={})'.format(self.__class__.__name__, self.duration, self.amp)


class ParametricInstruction(Instruction):
class ParametricInstruction:
"""Instruction to drive a parametric pulse to an `PulseChannel`."""

def __init__(self):
warnings.warn("TODO", DeprecationWarning)
Loading