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

Converting the pulse library from complex amp to amp+angle #9002

Merged
merged 27 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
03129e2
Converting Gaussian SymbolicPulse from complex amp to amp,angle.
TsafrirA Oct 26, 2022
e7a0bf7
removed unnecessary import.
TsafrirA Oct 26, 2022
0310fbd
Completed the changes.
TsafrirA Nov 10, 2022
786c000
Bug fix and test updates.
TsafrirA Nov 13, 2022
a591f66
removed commented line.
TsafrirA Nov 13, 2022
dbe43fb
black correction.
TsafrirA Nov 14, 2022
8a74b70
Tests correction.
TsafrirA Nov 14, 2022
22c61b9
Bump QPY version, and adjust QPY loader.
TsafrirA Nov 20, 2022
04dcd0e
Release Notes.
TsafrirA Nov 21, 2022
4d1ea59
Update qiskit/qobj/converters/pulse_instruction.py
TsafrirA Nov 21, 2022
4412df6
Update releasenotes/notes/Symbolic-Pulses-conversion-to-amp-angle-0c6…
TsafrirA Nov 21, 2022
54bcdf2
Some more corrections.
TsafrirA Nov 21, 2022
fbe5ead
QPY load adjustment.
TsafrirA Nov 23, 2022
1a6db4a
Removed debug print
TsafrirA Nov 24, 2022
d7d4927
Always add "angle" to envelope
TsafrirA Nov 26, 2022
211838a
black
TsafrirA Nov 26, 2022
e07a2c5
Update qiskit/qpy/__init__.py
TsafrirA Nov 28, 2022
8e1db4f
resolve conflict
TsafrirA Nov 28, 2022
c9d128f
Merge branch 'main' into AmpAngle
TsafrirA Nov 28, 2022
ee394ef
Remove outdated test.
TsafrirA Nov 28, 2022
0cc531b
Lint
TsafrirA Nov 28, 2022
048a53b
Release notes style
TsafrirA Nov 28, 2022
b69940b
Removed QPY version bump in favor of using qiskit terra version as an…
TsafrirA Nov 29, 2022
55262a2
bug fix
TsafrirA Nov 30, 2022
fb07443
bug fix
TsafrirA Nov 30, 2022
8c7281c
Merge branch 'main' into AmpAngle
TsafrirA Nov 30, 2022
97f6786
Merge branch 'main' into AmpAngle
mergify[bot] Nov 30, 2022
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
163 changes: 127 additions & 36 deletions qiskit/pulse/library/symbolic_pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,6 @@ def __init__(
if parameters is None:
parameters = {}

# TODO remove this.
# This is due to convention in IBM Quantum backends where "amp" is treated as a
# special parameter that must be defined in the form [real, imaginary].
# this check must be removed because Qiskit pulse should be backend agnostic.
if "amp" in parameters and not isinstance(parameters["amp"], ParameterExpression):
parameters["amp"] = complex(parameters["amp"])

self._pulse_type = pulse_type
self._params = parameters

Expand Down Expand Up @@ -614,42 +607,66 @@ class Gaussian(metaclass=_PulseType):
.. math::

f'(x) &= \exp\Bigl( -\frac12 \frac{{(x - \text{duration}/2)}^2}{\text{sigma}^2} \Bigr)\\
f(x) &= \text{amp} \times \frac{f'(x) - f'(-1)}{1-f'(-1)}, \quad 0 \le x < \text{duration}
f(x) &= \text{A} \times \frac{f'(x) - f'(-1)}{1-f'(-1)}, \quad 0 \le x < \text{duration}

where :math:`f'(x)` is the gaussian waveform without lifting or amplitude scaling.
where :math:`f'(x)` is the gaussian waveform without lifting or amplitude scaling, and
:math:`\text{A} = \text{amp} \times \exp\left(i\times\text{angle}\right)`.
"""

alias = "Gaussian"

def __new__(
cls,
duration: Union[int, ParameterExpression],
amp: Union[complex, ParameterExpression],
amp: Union[complex, float, ParameterExpression],
sigma: Union[float, ParameterExpression],
angle: Optional[Union[float, ParameterExpression]] = None,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> SymbolicPulse:
"""Create new pulse instance.

Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The amplitude of the Gaussian envelope.
amp: The magnitude of the amplitude of the Gaussian envelope.
Complex amp support will be deprecated.
sigma: A measure of how wide or narrow the Gaussian peak is; described mathematically
in the class docstring.
angle: The angle of the complex amplitude of the Gaussian envelope. Default value 0.
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:
SymbolicPulse instance.

Raises:
PulseError: If both complex amp and angle are provided as arguments.
"""
parameters = {"amp": amp, "sigma": sigma}
# This should be removed once complex amp support is deprecated.
if isinstance(amp, complex):
if angle is None:
warnings.warn(
"Complex amp will be deprecated. "
"Use float amp (for the magnitude) and float angle instead.",
PendingDeprecationWarning,
wshanks marked this conversation as resolved.
Show resolved Hide resolved
)
else:
raise PulseError("amp can't be complex when providing angle")

if angle is None:
angle = 0

parameters = {"amp": amp, "sigma": sigma, "angle": angle}

# Prepare symbolic expressions
_t, _duration, _amp, _sigma = sym.symbols("t, duration, amp, sigma")
_t, _duration, _amp, _sigma, _angle = sym.symbols("t, duration, amp, sigma, angle")
_center = _duration / 2

envelope_expr = _amp * _lifted_gaussian(_t, _center, _duration + 1, _sigma)
envelope_expr = (
_amp * sym.exp(sym.I * _angle) * _lifted_gaussian(_t, _center, _duration + 1, _sigma)
)

consts_expr = _sigma > 0
valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0

Expand Down Expand Up @@ -700,20 +717,22 @@ class GaussianSquare(metaclass=_PulseType):
\\biggr)\
& \\text{risefall} + \\text{width} \\le x\
\\end{cases}\\\\
f(x) &= \\text{amp} \\times \\frac{f'(x) - f'(-1)}{1-f'(-1)},\
f(x) &= \\text{A} \\times \\frac{f'(x) - f'(-1)}{1-f'(-1)},\
\\quad 0 \\le x < \\text{duration}

where :math:`f'(x)` is the gaussian square waveform without lifting or amplitude scaling.
where :math:`f'(x)` is the gaussian square waveform without lifting or amplitude scaling, and
:math:`\\text{A} = \\text{amp} \\times \\exp\\left(i\\times\\text{angle}\\right)`.
"""

alias = "GaussianSquare"

def __new__(
cls,
duration: Union[int, ParameterExpression],
amp: Union[complex, ParameterExpression],
amp: Union[complex, float, ParameterExpression],
sigma: Union[float, ParameterExpression],
width: Optional[Union[float, ParameterExpression]] = None,
angle: Optional[Union[float, ParameterExpression]] = None,
risefall_sigma_ratio: Optional[Union[float, ParameterExpression]] = None,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
Expand All @@ -722,10 +741,12 @@ def __new__(

Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The amplitude of the Gaussian and of the square pulse.
amp: The magnitude of the amplitude of the Gaussian and square pulse.
Complex amp support will be deprecated.
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.
angle: The angle of the complex amplitude of the 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
Expand All @@ -736,6 +757,7 @@ def __new__(

Raises:
PulseError: When width and risefall_sigma_ratio are both empty or both non-empty.
PulseError: If both complex amp and angle are provided as arguments.
"""
# Convert risefall_sigma_ratio into width which is defined in OpenPulse spec
if width is None and risefall_sigma_ratio is None:
Expand All @@ -750,10 +772,26 @@ def __new__(
if width is None and risefall_sigma_ratio is not None:
width = duration - 2.0 * risefall_sigma_ratio * sigma

parameters = {"amp": amp, "sigma": sigma, "width": width}
# This should be removed once complex amp support is deprecated.
if isinstance(amp, complex):
if angle is None:
warnings.warn(
"Complex amp will be deprecated. "
"Use float amp (for the magnitude) and float angle instead.",
PendingDeprecationWarning,
)
else:
raise PulseError("amp can't be complex when providing angle")

if angle is None:
angle = 0

parameters = {"amp": amp, "sigma": sigma, "width": width, "angle": angle}

# Prepare symbolic expressions
_t, _duration, _amp, _sigma, _width = sym.symbols("t, duration, amp, sigma, width")
_t, _duration, _amp, _sigma, _width, _angle = sym.symbols(
"t, duration, amp, sigma, width, angle"
)
_center = _duration / 2

_sq_t0 = _center - _width / 2
Expand All @@ -762,9 +800,14 @@ def __new__(
_gaussian_ledge = _lifted_gaussian(_t, _sq_t0, -1, _sigma)
_gaussian_redge = _lifted_gaussian(_t, _sq_t1, _duration + 1, _sigma)

envelope_expr = _amp * sym.Piecewise(
(_gaussian_ledge, _t <= _sq_t0), (_gaussian_redge, _t >= _sq_t1), (1, True)
envelope_expr = (
_amp
* sym.exp(sym.I * _angle)
* sym.Piecewise(
(_gaussian_ledge, _t <= _sq_t0), (_gaussian_redge, _t >= _sq_t1), (1, True)
)
)

consts_expr = sym.And(_sigma > 0, _width >= 0, _duration >= _width)
valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0

Expand Down Expand Up @@ -795,13 +838,14 @@ class Drag(metaclass=_PulseType):
.. math::

g(x) &= \\exp\\Bigl(-\\frac12 \\frac{(x - \\text{duration}/2)^2}{\\text{sigma}^2}\\Bigr)\\\\
g'(x) &= \\text{amp}\\times\\frac{g(x)-g(-1)}{1-g(-1)}\\\\
g'(x) &= \\text{A}\\times\\frac{g(x)-g(-1)}{1-g(-1)}\\\\
f(x) &= g'(x) \\times \\Bigl(1 + 1j \\times \\text{beta} \\times\
\\Bigl(-\\frac{x - \\text{duration}/2}{\\text{sigma}^2}\\Bigr) \\Bigr),
\\quad 0 \\le x < \\text{duration}

where :math:`g(x)` is a standard unlifted Gaussian waveform and
:math:`g'(x)` is the lifted :class:`~qiskit.pulse.library.Gaussian` waveform.
where :math:`g(x)` is a standard unlifted Gaussian waveform, :math:`g'(x)` is the lifted
:class:`~qiskit.pulse.library.Gaussian` waveform, and
:math:`\\text{A} = \\text{amp} \\times \\exp\\left(i\\times\\text{angle}\\right)`.

References:
1. |citation1|_
Expand All @@ -825,37 +869,59 @@ class Drag(metaclass=_PulseType):
def __new__(
cls,
duration: Union[int, ParameterExpression],
amp: Union[complex, ParameterExpression],
amp: Union[complex, float, ParameterExpression],
sigma: Union[float, ParameterExpression],
beta: Union[float, ParameterExpression],
angle: Optional[Union[float, ParameterExpression]] = None,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> SymbolicPulse:
"""Create new pulse instance.

Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The amplitude of the Drag envelope.
amp: The magnitude of the amplitude of the DRAG envelope.
Complex amp support will be deprecated.
sigma: A measure of how wide or narrow the Gaussian peak is; described mathematically
in the class docstring.
beta: The correction amplitude.
angle: The angle of the complex amplitude of the DRAG envelope. Default value 0.
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:
SymbolicPulse instance.

Raises:
PulseError: If both complex amp and angle are provided as arguments.
"""
parameters = {"amp": amp, "sigma": sigma, "beta": beta}
# This should be removed once complex amp support is deprecated.
if isinstance(amp, complex):
if angle is None:
warnings.warn(
"Complex amp will be deprecated. "
"Use float amp (for the magnitude) and float angle instead.",
PendingDeprecationWarning,
)
else:
raise PulseError("amp can't be complex when providing angle")

if angle is None:
angle = 0

parameters = {"amp": amp, "sigma": sigma, "beta": beta, "angle": angle}

# Prepare symbolic expressions
_t, _duration, _amp, _sigma, _beta = sym.symbols("t, duration, amp, sigma, beta")
_t, _duration, _amp, _sigma, _beta, _angle = sym.symbols(
"t, duration, amp, sigma, beta, angle"
)
_center = _duration / 2

_gauss = _lifted_gaussian(_t, _center, _duration + 1, _sigma)
_deriv = -(_t - _center) / (_sigma**2) * _gauss

envelope_expr = _amp * (_gauss + sym.I * _beta * _deriv)
envelope_expr = _amp * sym.exp(sym.I * _angle) * (_gauss + sym.I * _beta * _deriv)

consts_expr = _sigma > 0
valid_amp_conditions_expr = sym.And(sym.Abs(_amp) <= 1.0, sym.Abs(_beta) < _sigma)
Expand All @@ -880,7 +946,7 @@ class Constant(metaclass=_PulseType):

.. math::

f(x) = amp , 0 <= x < duration
f(x) = \\text{amp}\\times\\exp\\left(i\\text{angle}\\right) , 0 <= x < duration
f(x) = 0 , elsewhere
"""

Expand All @@ -889,26 +955,46 @@ class Constant(metaclass=_PulseType):
def __new__(
cls,
duration: Union[int, ParameterExpression],
amp: Union[complex, ParameterExpression],
amp: Union[complex, float, ParameterExpression],
angle: Optional[Union[float, ParameterExpression]] = None,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> SymbolicPulse:
"""Create new pulse instance.

Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The amplitude of the constant square pulse.
amp: The magnitude of the amplitude of the square envelope.
Complex amp support will be deprecated.
angle: The angle of the complex amplitude of the square envelope. Default value 0.
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:
SymbolicPulse instance.

Raises:
PulseError: If both complex amp and angle are provided as arguments.
"""
parameters = {"amp": amp}
# This should be removed once complex amp support is deprecated.
if isinstance(amp, complex):
if angle is None:
warnings.warn(
"Complex amp will be deprecated. "
"Use float amp (for the magnitude) and float angle instead.",
PendingDeprecationWarning,
)
else:
raise PulseError("amp can't be complex when providing angle")

if angle is None:
angle = 0

parameters = {"amp": amp, "angle": angle}

# Prepare symbolic expressions
_t, _amp, _duration = sym.symbols("t, amp, duration")
_t, _amp, _duration, _angle = sym.symbols("t, amp, duration, angle")

# Note this is implemented using Piecewise instead of just returning amp
# directly because otherwise the expression has no t dependence and sympy's
Expand All @@ -917,7 +1003,12 @@ def __new__(
# ParametricPulse.get_waveform().
#
# See: https://github.com/sympy/sympy/issues/5642
envelope_expr = _amp * sym.Piecewise((1, sym.And(_t >= 0, _t <= _duration)), (0, True))
envelope_expr = (
_amp
* sym.exp(sym.I * _angle)
* sym.Piecewise((1, sym.And(_t >= 0, _t <= _duration)), (0, True))
)

valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0

instance = SymbolicPulse(
Expand Down
5 changes: 0 additions & 5 deletions qiskit/pulse/parameter_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,6 @@ def visit_SymbolicPulse(self, node: SymbolicPulse):
pval = node._params[name]
if isinstance(pval, ParameterExpression):
new_val = self._assign_parameter_expression(pval)
if name == "amp" and not isinstance(new_val, ParameterExpression):
# This is due to an odd behavior of IBM Quantum backends.
# When the amplitude is given as a float, then job execution is
# terminated with an error.
new_val = complex(new_val)
node._params[name] = new_val
node.validate_parameters()

Expand Down
17 changes: 13 additions & 4 deletions qiskit/qobj/converters/pulse_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from enum import Enum
from typing import Union
import numpy as np

from qiskit.pulse import channels, instructions, library
from qiskit.pulse.configuration import Kernel, Discriminator
Expand Down Expand Up @@ -425,12 +426,18 @@ def convert_play(self, shift, instruction):
dict: Dictionary of required parameters.
"""
if isinstance(instruction.pulse, (library.ParametricPulse, library.SymbolicPulse)):
params = dict(instruction.pulse.parameters)
# IBM backends expect "amp" to be the complex amplitude
if "amp" in params and "angle" in params:
params["amp"] = complex(params["amp"] * np.exp(1j * params["angle"]))
del params["angle"]

command_dict = {
"name": "parametric_pulse",
"pulse_shape": ParametricPulseShapes.from_instance(instruction.pulse).name,
"t0": shift + instruction.start_time,
"ch": instruction.channel.name,
"parameters": instruction.pulse.parameters,
"parameters": params,
}
else:
command_dict = {
Expand Down Expand Up @@ -723,10 +730,12 @@ def convert_parametric(self, instruction):
)
short_pulse_id = hashlib.md5(base_str.encode("utf-8")).hexdigest()[:4]
pulse_name = f"{instruction.pulse_shape}_{short_pulse_id}"
params = dict(instruction.parameters)
if "amp" in params and isinstance(params["amp"], complex):
params["angle"] = np.angle(params["amp"])
params["amp"] = np.abs(params["amp"])

pulse = ParametricPulseShapes.to_type(instruction.pulse_shape)(
**instruction.parameters, name=pulse_name
)
pulse = ParametricPulseShapes.to_type(instruction.pulse_shape)(**params, name=pulse_name)
return instructions.Play(pulse, channel) << t0

@bind_name("snapshot")
Expand Down
Loading