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

QPY schedule serialization #7300

Merged
merged 42 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f32989c
Schedule serialization wip
nkanazawa1989 Nov 21, 2021
59cb2c4
pulse serialization
nkanazawa1989 Nov 24, 2021
dab2484
move mapping io to separate file
nkanazawa1989 Nov 24, 2021
81746b0
move dump numbers to parameter_values
nkanazawa1989 Nov 24, 2021
3b01c81
add alignment context serialization
nkanazawa1989 Nov 24, 2021
354e075
block serialization
nkanazawa1989 Nov 24, 2021
04cbf6d
add interface
nkanazawa1989 Nov 25, 2021
2033738
bug fix
nkanazawa1989 Nov 25, 2021
242b0f3
black / lint
nkanazawa1989 Nov 25, 2021
8ba02f7
documentation
nkanazawa1989 Nov 25, 2021
4b585d8
add serialization for kernel and discriminator
nkanazawa1989 Nov 30, 2021
21bfe1b
migrate circuit qpy to qpy module
nkanazawa1989 Nov 30, 2021
20fd393
keep import path
nkanazawa1989 Nov 30, 2021
64965b7
reno
nkanazawa1989 Nov 30, 2021
9836cc6
lint
nkanazawa1989 Nov 30, 2021
b8ee92b
Merge branch 'main' into feature/schedule-serialization
nkanazawa1989 Nov 30, 2021
82520a8
fix import path
nkanazawa1989 Nov 30, 2021
3eae2d8
Merge branch 'main' of github.com:Qiskit/qiskit-terra into feature/sc…
nkanazawa1989 Jun 16, 2022
49f96f8
update schedule qpy with cleanup. alignment classes and acquire instr…
nkanazawa1989 Jun 17, 2022
838c80a
move type key enum to type_keys module
nkanazawa1989 Jun 17, 2022
6c841e3
add mapping writer (for qpy values) and cleanup sequence serializer t…
nkanazawa1989 Jun 17, 2022
7c548b1
compress symbolic pulse expressions with zlib
nkanazawa1989 Jun 17, 2022
25ba1d9
remove change to existing circuit qpy tests
nkanazawa1989 Jun 17, 2022
f49b2e6
cleanup for type key
nkanazawa1989 Jun 17, 2022
3cbe5ac
Merge branch 'main' of github.com:Qiskit/qiskit-terra into feature/sc…
nkanazawa1989 Jun 20, 2022
cd49a4b
add unittest and fix some bug
nkanazawa1989 Jun 20, 2022
f2e8720
update documentation
nkanazawa1989 Jun 20, 2022
5c45c11
add seek(0) to data_from_binary helper function
nkanazawa1989 Jun 20, 2022
48c70fe
remove subclasses and hard coded is_sequential in every subclass
nkanazawa1989 Jun 21, 2022
8d7f898
documentation update and type fix
nkanazawa1989 Jun 21, 2022
a90a7d5
move test to own module
nkanazawa1989 Jun 21, 2022
9e9337f
update typehint
nkanazawa1989 Jun 21, 2022
f2d32eb
add backward compatibility test
nkanazawa1989 Jun 22, 2022
6df43aa
Merge branch 'main' of github.com:Qiskit/qiskit-terra into feature/sc…
nkanazawa1989 Jun 22, 2022
a148052
fix merge and cleanup
nkanazawa1989 Jun 22, 2022
7c9f829
add circuit calibrations serialization
nkanazawa1989 Jun 22, 2022
8aee1d5
fix qpy_compat test
nkanazawa1989 Jun 22, 2022
8d666ab
remove compatibility test for parameterized program
nkanazawa1989 Jun 22, 2022
ced2f68
Merge branch 'main' of github.com:Qiskit/qiskit-terra into feature/sc…
nkanazawa1989 Jun 22, 2022
39b34b1
update reno for calibrations
nkanazawa1989 Jun 22, 2022
fe46dad
fix data size
nkanazawa1989 Jun 22, 2022
b9c4b54
Merge branch 'main' into feature/schedule-serialization
mergify[bot] Jun 22, 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
12 changes: 6 additions & 6 deletions qiskit/pulse/instructions/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ def __init__(
if not (mem_slot or reg_slot):
raise PulseError("Neither MemorySlots nor RegisterSlots were supplied.")

self._kernel = kernel
self._discriminator = discriminator

super().__init__(operands=(duration, channel, mem_slot, reg_slot), name=name)
super().__init__(
operands=(duration, channel, mem_slot, reg_slot, kernel, discriminator),
name=name,
)

@property
def channel(self) -> AcquireChannel:
Expand All @@ -100,12 +100,12 @@ def duration(self) -> Union[int, ParameterExpression]:
@property
def kernel(self) -> Kernel:
"""Return kernel settings."""
return self._kernel
return self._operands[4]

@property
def discriminator(self) -> Discriminator:
"""Return discrimination settings."""
return self._discriminator
return self._operands[5]

@property
def acquire(self) -> AcquireChannel:
Expand Down
10 changes: 8 additions & 2 deletions qiskit/pulse/library/symbolic_pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ def _lifted_gaussian(
Returns:
Symbolic equation.
"""
gauss = sym.exp(-(((t - center) / sigma) ** 2) / 2)
offset = sym.exp(-(((t_zero - center) / sigma) ** 2) / 2)
# Sympy automatically does expand.
# This causes expression inconsistency after qpy round-trip serializing through sympy.
# See issue for details: https://github.com/symengine/symengine.py/issues/409
t_shifted = (t - center).expand()
t_offset = (t_zero - center).expand()

gauss = sym.exp(-((t_shifted / sigma) ** 2) / 2)
offset = sym.exp(-((t_offset / sigma) ** 2) / 2)

return (gauss - offset) / (1 - offset)

Expand Down
116 changes: 88 additions & 28 deletions qiskit/pulse/transforms/alignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,22 @@
"""A collection of passes to reallocate the timeslots of instructions according to context."""

import abc
from typing import Callable, Dict, Any, Union
from typing import Callable, Dict, Any, Union, Tuple

import numpy as np

from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType
from qiskit.pulse.exceptions import PulseError
from qiskit.pulse.schedule import Schedule, ScheduleComponent
from qiskit.pulse.utils import instruction_duration_validation
from qiskit.pulse.utils import instruction_duration_validation, deprecated_functionality


class AlignmentKind(abc.ABC):
"""An abstract class for schedule alignment."""

is_sequential = None
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for review; attribute is removed because user can override this, then we should have this in QPY but it's unnecessary complexity. This is designed to be static value, so it's now replaced by subclass which user cannot override.


def __init__(self):
def __init__(self, context_params: Tuple[ParameterValueType, ...]):
"""Create new context."""
self._context_params = tuple()
self._context_params = tuple(context_params)

@abc.abstractmethod
def align(self, schedule: Schedule) -> Schedule:
Expand All @@ -46,20 +44,52 @@ def align(self, schedule: Schedule) -> Schedule:
"""
pass

@deprecated_functionality
def to_dict(self) -> Dict[str, Any]:
"""Returns dictionary to represent this alignment."""
return {"alignment": self.__class__.__name__}

@property
@abc.abstractmethod
def is_sequential(self) -> bool:
"""Return ``True`` if this is sequential alignment context.

This information is used to evaluate DAG equivalency of two :class:`.ScheduleBlock`s.
When the context has two pulses in different channels,
a sequential context subtype intends to return following scheduling outcome.

.. parsed-literal::

┌────────┐
D0: ┤ pulse1 ├────────────
└────────┘ ┌────────┐
D1: ────────────┤ pulse2 ├
└────────┘

On the other hand, parallel context with ``is_sequential=False`` returns

.. parsed-literal::

┌────────┐
D0: ┤ pulse1 ├
├────────┤
D1: ┤ pulse2 ├
└────────┘

All subclasses must implement this method according to scheduling strategy.
"""
pass

def __eq__(self, other):
"""Check equality of two transforms."""
return isinstance(other, type(self)) and self.to_dict() == other.to_dict()
if type(self) is not type(other):
return False
if self._context_params != other._context_params:
return False
return True

def __repr__(self):
name = self.__class__.__name__
opts = self.to_dict()
opts.pop("alignment")
opts_str = ", ".join(f"{key}={val}" for key, val in opts.items())
return f"{name}({opts_str})"
return f"{self.__class__.__name__}({', '.join(self._context_params)})"


class AlignLeft(AlignmentKind):
Expand All @@ -68,7 +98,13 @@ class AlignLeft(AlignmentKind):
Instructions are placed at earliest available timeslots.
"""

is_sequential = False
def __init__(self):
"""Create new left-justified context."""
super().__init__(context_params=())

@property
def is_sequential(self) -> bool:
return False

def align(self, schedule: Schedule) -> Schedule:
"""Reallocate instructions according to the policy.
Expand Down Expand Up @@ -129,7 +165,13 @@ class AlignRight(AlignmentKind):
Instructions are placed at latest available timeslots.
"""

is_sequential = False
def __init__(self):
"""Create new right-justified context."""
super().__init__(context_params=())

@property
def is_sequential(self) -> bool:
return False

def align(self, schedule: Schedule) -> Schedule:
"""Reallocate instructions according to the policy.
Expand Down Expand Up @@ -192,7 +234,13 @@ class AlignSequential(AlignmentKind):
No buffer time is inserted in between instructions.
"""

is_sequential = True
def __init__(self):
"""Create new sequential context."""
super().__init__(context_params=())

@property
def is_sequential(self) -> bool:
return True

def align(self, schedule: Schedule) -> Schedule:
"""Reallocate instructions according to the policy.
Expand Down Expand Up @@ -220,8 +268,6 @@ class AlignEquispaced(AlignmentKind):
This alignment is convenient to create dynamical decoupling sequences such as PDD.
"""

is_sequential = True

def __init__(self, duration: Union[int, ParameterExpression]):
"""Create new equispaced context.

Expand All @@ -231,9 +277,11 @@ def __init__(self, duration: Union[int, ParameterExpression]):
no alignment is performed and the input schedule is just returned.
This duration can be parametrized.
"""
super().__init__()
super().__init__(context_params=(duration,))

self._context_params = (duration,)
@property
def is_sequential(self) -> bool:
return True

@property
def duration(self):
Expand Down Expand Up @@ -281,6 +329,7 @@ def align(self, schedule: Schedule) -> Schedule:

return aligned

@deprecated_functionality
def to_dict(self) -> Dict[str, Any]:
"""Returns dictionary to represent this alignment."""
return {"alignment": self.__class__.__name__, "duration": self.duration}
Expand All @@ -301,9 +350,13 @@ class AlignFunc(AlignmentKind):

def udd10_pos(j):
return np.sin(np.pi*j/(2*10 + 2))**2
"""

is_sequential = True
.. note::

This context cannot be QPY serialized because of the callable. If you use this context,
your program cannot be saved in QPY format.

"""

def __init__(self, duration: Union[int, ParameterExpression], func: Callable):
"""Create new equispaced context.
Expand All @@ -317,16 +370,22 @@ def __init__(self, duration: Union[int, ParameterExpression], func: Callable):
fractional coordinate of of that sub-schedule. The returned value should be
defined within [0, 1]. The pulse index starts from 1.
"""
super().__init__()
super().__init__(context_params=(duration, func))

self._context_params = (duration,)
self._func = func
@property
def is_sequential(self) -> bool:
return True

@property
def duration(self):
"""Return context duration."""
return self._context_params[0]

@property
def func(self):
"""Return context alignment function."""
return self._context_params[1]

def align(self, schedule: Schedule) -> Schedule:
"""Reallocate instructions according to the policy.

Expand All @@ -346,21 +405,22 @@ def align(self, schedule: Schedule) -> Schedule:

aligned = Schedule.initialize_from(schedule)
for ind, (_, child) in enumerate(schedule.children):
_t_center = self.duration * self._func(ind + 1)
_t_center = self.duration * self.func(ind + 1)
_t0 = int(_t_center - 0.5 * child.duration)
if _t0 < 0 or _t0 > self.duration:
PulseError("Invalid schedule position t=%d is specified at index=%d" % (_t0, ind))
aligned.insert(_t0, child, inplace=True)

return aligned

@deprecated_functionality
def to_dict(self) -> Dict[str, Any]:
"""Returns dictionary to represent this alignment.

.. note:: ``func`` is not presented in this dictionary. Just name.
"""
return {
"alignment": self.__class__.__name__,
"duration": self._context_params[0],
"func": self._func.__name__,
"duration": self.duration,
"func": self.func.__name__,
}
3 changes: 3 additions & 0 deletions qiskit/pulse/transforms/dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def block_to_dag(block: ScheduleBlock) -> rx.PyDAG:

Returns:
Instructions in DAG representation.

Raises:
PulseError: When the context is invalid subclass.
"""
if block.alignment_context.is_sequential:
return _sequential_allocation(block)
Expand Down
Loading