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

Fully QPY compatible pulse builder for V2 and runtime backend #8949

Merged
merged 20 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
805a5b2
Eliminate use of Schedule in the builder context. Schedule is implici…
nkanazawa1989 Oct 17, 2022
a02bcb5
Support AreaBarrier instruction in QPY
nkanazawa1989 Oct 19, 2022
f2bc607
Update RZX calibration builder to generate ScheduleBlock
nkanazawa1989 Oct 19, 2022
17d2a46
Add release note
nkanazawa1989 Oct 19, 2022
4d0ddf5
Support python3.7
nkanazawa1989 Oct 19, 2022
6a90761
Fix hadamard schedule in the RZX builder. This should not use sequent…
nkanazawa1989 Oct 19, 2022
0ca8efb
Review comments
nkanazawa1989 Oct 19, 2022
de2f7d5
Unified append_block and append_subroutine
nkanazawa1989 Oct 19, 2022
a69aac0
Merge branch 'main' into upgrade/add_schedule_block_like
nkanazawa1989 Oct 19, 2022
1f1db8d
Update instruction name AreaBarrier -> TimeBlockade
nkanazawa1989 Oct 24, 2022
7ab2710
Documentation updates
nkanazawa1989 Oct 24, 2022
15887c8
Remove old name
nkanazawa1989 Oct 24, 2022
37f4cbd
lint fix
nkanazawa1989 Oct 25, 2022
5434f79
Merge branch 'main' of github.com:Qiskit/qiskit-terra into upgrade/ad…
nkanazawa1989 Oct 28, 2022
1504aaf
add release note for bugfix
nkanazawa1989 Oct 28, 2022
bee3fca
Merge branch 'main' into upgrade/add_schedule_block_like
eggerdj Oct 30, 2022
1756e35
review comments
nkanazawa1989 Nov 14, 2022
b17595e
more detailed upgrade notes
nkanazawa1989 Nov 22, 2022
8562d98
add new unittests. rescale_cr_inst method is updated so that it becom…
nkanazawa1989 Nov 22, 2022
41f6d1b
Merge branch 'main' into upgrade/add_schedule_block_like
nkanazawa1989 Nov 23, 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
206 changes: 130 additions & 76 deletions qiskit/pulse/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@
import contextvars
import functools
import itertools
import sys
import uuid
import warnings
from contextlib import contextmanager
Expand Down Expand Up @@ -481,6 +482,12 @@
from qiskit.pulse.schedule import Schedule, ScheduleBlock
from qiskit.pulse.transforms.alignments import AlignmentKind

if sys.version_info >= (3, 8):
from functools import singledispatchmethod # pylint: disable=no-name-in-module
else:
from singledispatchmethod import singledispatchmethod


#: contextvars.ContextVar[BuilderContext]: active builder
BUILDER_CONTEXTVAR = contextvars.ContextVar("backend")

Expand Down Expand Up @@ -585,8 +592,7 @@ def __init__(
if isinstance(block, ScheduleBlock):
root_block = block
elif isinstance(block, Schedule):
root_block = ScheduleBlock()
root_block.append(instructions.Call(subroutine=block))
root_block = self._naive_typecast_schedule(block)
else:
raise exceptions.PulseError(
f"Input `block` type {block.__class__.__name__} is "
Expand Down Expand Up @@ -650,7 +656,7 @@ def get_context(self) -> ScheduleBlock:
"""Get current context.

Notes:
New instruction can be added by `.append_block` or `.append_instruction` method.
New instruction can be added by `.append_subroutine` or `.append_instruction` method.
Use above methods rather than directly accessing to the current context.
"""
return self._context_stack[-1]
Expand Down Expand Up @@ -692,7 +698,7 @@ def compile(self) -> ScheduleBlock:

while len(self._context_stack) > 1:
current = self.pop_context()
self.append_block(current)
self.append_subroutine(current)

return self._context_stack[0]

Expand All @@ -706,7 +712,7 @@ def _compile_lazy_circuit(self):
lazy_circuit = self._lazy_circuit
# reset lazy circuit
self._lazy_circuit = self._new_circuit()
self.call_subroutine(subroutine=self._compile_circuit(lazy_circuit))
self.call_subroutine(self._compile_circuit(lazy_circuit))

def _compile_circuit(self, circ) -> Schedule:
"""Take a QuantumCircuit and output the pulse schedule associated with the circuit."""
Expand All @@ -731,17 +737,6 @@ def append_instruction(self, instruction: instructions.Instruction):
"""
self._context_stack[-1].append(instruction)

@_compile_lazy_circuit_before
def append_block(self, context_block: ScheduleBlock):
Copy link
Member

Choose a reason for hiding this comment

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

Are there any backwards compatibility implications with this removal. The documentation referred to this method previously so was it user facing or is this all internal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"""Add a :class:`ScheduleBlock` to the builder's context schedule.

Args:
context_block: ScheduleBlock to append to the current context block.
"""
# ignore empty context
if len(context_block) > 0:
self._context_stack[-1].append(context_block)

def append_reference(self, name: str, *extra_keys: str):
"""Add external program as a :class:`~qiskit.pulse.instructions.Reference` instruction.

Expand All @@ -752,6 +747,32 @@ def append_reference(self, name: str, *extra_keys: str):
inst = instructions.Reference(name, *extra_keys)
self.append_instruction(inst)

@_compile_lazy_circuit_before
def append_subroutine(self, subroutine: Union[Schedule, ScheduleBlock]):
"""Append a :class:`ScheduleBlock` to the builder's context schedule.

This operation doesn't create a reference. Subroutine is directly
appended to current context schedule.

Args:
subroutine: ScheduleBlock to append to the current context block.

Raises:
PulseError: When subroutine is not Schedule nor ScheduleBlock.
"""
if not isinstance(subroutine, (ScheduleBlock, Schedule)):
raise exceptions.PulseError(
f"'{subroutine.__class__.__name__}' is not valid data format in the builder. "
"'Schedule' and 'ScheduleBlock' can be appended to the builder context."
)

if len(subroutine) == 0:
return
if isinstance(subroutine, Schedule):
subroutine = self._naive_typecast_schedule(subroutine)
self._context_stack[-1].append(subroutine)

@singledispatchmethod
def call_subroutine(
self,
subroutine: Union[circuit.QuantumCircuit, Schedule, ScheduleBlock],
Expand All @@ -778,34 +799,37 @@ def call_subroutine(

Raises:
PulseError:
- When specified parameter is not contained in the subroutine
- When input subroutine is not valid data format.
"""
if isinstance(subroutine, circuit.QuantumCircuit):
self._compile_lazy_circuit()
subroutine = self._compile_circuit(subroutine)

if not isinstance(subroutine, (Schedule, ScheduleBlock)):
raise exceptions.PulseError(
f"Subroutine type {subroutine.__class__.__name__} is "
"not valid data format. Call QuantumCircuit, "
"Schedule, or ScheduleBlock."
)
raise exceptions.PulseError(
f"Subroutine type {subroutine.__class__.__name__} is "
"not valid data format. Call QuantumCircuit, "
"Schedule, or ScheduleBlock."
)

if len(subroutine) == 0:
@call_subroutine.register
def _(
self,
target_block: ScheduleBlock,
name: Optional[str] = None,
value_dict: Optional[Dict[ParameterExpression, ParameterValueType]] = None,
**kw_params: ParameterValueType,
):
if len(target_block) == 0:
return

# Create local parameter assignment
local_assignment = dict()
for param_name, value in kw_params.items():
params = subroutine.get_parameters(param_name)
params = target_block.get_parameters(param_name)
if not params:
raise exceptions.PulseError(
f"Parameter {param_name} is not defined in the target subroutine. "
f'{", ".join(map(str, subroutine.parameters))} can be specified.'
f'{", ".join(map(str, target_block.parameters))} can be specified.'
)
for param in params:
local_assignment[param] = value

if value_dict:
if local_assignment.keys() & value_dict.keys():
warnings.warn(
Expand All @@ -816,22 +840,54 @@ def call_subroutine(
)
local_assignment.update(value_dict)

if isinstance(subroutine, ScheduleBlock):
# If subroutine is schedule block, use reference mechanism.
if local_assignment:
subroutine = subroutine.assign_parameters(local_assignment, inplace=False)
if name is None:
# Add unique string, not to accidentally override existing reference entry.
keys = (subroutine.name, uuid.uuid4().hex)
else:
keys = (name,)
self.append_reference(*keys)
self.get_context().assign_references({keys: subroutine}, inplace=True)
if local_assignment:
target_block = target_block.assign_parameters(local_assignment, inplace=False)

if name is None:
# Add unique string, not to accidentally override existing reference entry.
keys = (target_block.name, uuid.uuid4().hex)
else:
# If subroutine is schedule, use Call instruction.
name = name or subroutine.name
call_instruction = instructions.Call(subroutine, local_assignment, name)
self.append_instruction(call_instruction)
keys = (name,)

self.append_reference(*keys)
self.get_context().assign_references({keys: target_block}, inplace=True)

@call_subroutine.register
def _(
self,
target_schedule: Schedule,
name: Optional[str] = None,
value_dict: Optional[Dict[ParameterExpression, ParameterValueType]] = None,
**kw_params: ParameterValueType,
):
if len(target_schedule) == 0:
return

self.call_subroutine(
self._naive_typecast_schedule(target_schedule),
name=name,
value_dict=value_dict,
**kw_params,
)

@call_subroutine.register
def _(
self,
target_circuit: circuit.QuantumCircuit,
name: Optional[str] = None,
value_dict: Optional[Dict[ParameterExpression, ParameterValueType]] = None,
**kw_params: ParameterValueType,
):
if len(target_circuit) == 0:
return

self._compile_lazy_circuit()
self.call_subroutine(
self._compile_circuit(target_circuit),
name=name,
value_dict=value_dict,
**kw_params,
)

@_requires_backend
def call_gate(self, gate: circuit.Gate, qubits: Tuple[int, ...], lazy: bool = True):
Expand Down Expand Up @@ -870,6 +926,21 @@ def _call_gate(self, gate, qargs):

self._lazy_circuit.append(gate, qargs=qargs)

@staticmethod
def _naive_typecast_schedule(schedule: Schedule):
# Naively convert into ScheduleBlock
from qiskit.pulse.transforms import inline_subroutines, flatten, pad

preprocessed_schedule = inline_subroutines(flatten(schedule))
pad(preprocessed_schedule, inplace=True, pad_with=instructions.TimeBlockade)

# default to left alignment, namely ASAP scheduling
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
target_block = ScheduleBlock(name=schedule.name)
for _, inst in preprocessed_schedule.instructions:
target_block.append(inst, inplace=True)

return target_block


def build(
backend=None,
Expand Down Expand Up @@ -973,21 +1044,9 @@ def append_schedule(schedule: Union[Schedule, ScheduleBlock]):
"""Call a schedule by appending to the active builder's context block.

Args:
schedule: Schedule to append.

Raises:
PulseError: When input `schedule` is invalid data format.
schedule: Schedule or ScheduleBlock to append.
"""
if isinstance(schedule, Schedule):
_active_builder().append_instruction(instructions.Call(subroutine=schedule))
elif isinstance(schedule, ScheduleBlock):
_active_builder().append_block(schedule)
else:
raise exceptions.PulseError(
f"Input program {schedule.__class__.__name__} is not "
"acceptable program format. Input `Schedule` or "
"`ScheduleBlock`."
)
_active_builder().append_subroutine(schedule)


def append_instruction(instruction: instructions.Instruction):
Expand Down Expand Up @@ -1181,7 +1240,7 @@ def align_left() -> ContextManager[None]:
yield
finally:
current = builder.pop_context()
builder.append_block(current)
builder.append_subroutine(current)


@contextmanager
Expand Down Expand Up @@ -1219,7 +1278,7 @@ def align_right() -> AlignmentKind:
yield
finally:
current = builder.pop_context()
builder.append_block(current)
builder.append_subroutine(current)


@contextmanager
Expand Down Expand Up @@ -1257,7 +1316,7 @@ def align_sequential() -> AlignmentKind:
yield
finally:
current = builder.pop_context()
builder.append_block(current)
builder.append_subroutine(current)


@contextmanager
Expand Down Expand Up @@ -1308,7 +1367,7 @@ def align_equispaced(duration: Union[int, ParameterExpression]) -> AlignmentKind
yield
finally:
current = builder.pop_context()
builder.append_block(current)
builder.append_subroutine(current)


@contextmanager
Expand Down Expand Up @@ -1369,7 +1428,7 @@ def udd10_pos(j):
yield
finally:
current = builder.pop_context()
builder.append_block(current)
builder.append_subroutine(current)


@contextmanager
Expand All @@ -1395,7 +1454,7 @@ def general_transforms(alignment_context: AlignmentKind) -> ContextManager[None]
yield
finally:
current = builder.pop_context()
builder.append_block(current)
builder.append_subroutine(current)


@contextmanager
Expand Down Expand Up @@ -1986,16 +2045,8 @@ def call(
the parameters having the same name are all updated together.
If you want to avoid name collision, use ``value_dict`` with :class:`~.Parameter`
objects instead.

Raises:
exceptions.PulseError: If the input ``target`` type is not supported.
"""
if not isinstance(target, (circuit.QuantumCircuit, Schedule, ScheduleBlock)):
raise exceptions.PulseError(f"'{target.__class__.__name__}' is not a valid target object.")

_active_builder().call_subroutine(
subroutine=target, name=name, value_dict=value_dict, **kw_params
)
_active_builder().call_subroutine(target, name, value_dict, **kw_params)


def reference(name: str, *extra_keys: str):
Expand Down Expand Up @@ -2237,7 +2288,10 @@ def measure(

# note this is not a subroutine.
# just a macro to automate combination of stimulus and acquisition.
_active_builder().call_subroutine(measure_sched)
# prepare unique reference name based on qubit and memory slot index.
qubits_repr = "&".join(map(str, qubits))
mslots_repr = "&".join(map(lambda r: str(r.index), registers))
_active_builder().call_subroutine(measure_sched, name=f"measure_{qubits_repr}..{mslots_repr}")

if len(qubits) == 1:
return registers[0]
Expand Down Expand Up @@ -2283,7 +2337,7 @@ def measure_all() -> List[chans.MemorySlot]:

# note this is not a subroutine.
# just a macro to automate combination of stimulus and acquisition.
_active_builder().call_subroutine(measure_sched)
_active_builder().call_subroutine(measure_sched, name="measure_all")

return registers

Expand Down
4 changes: 3 additions & 1 deletion qiskit/pulse/instructions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,21 @@
Reference
Delay
Play
RelativeBarrier
SetFrequency
ShiftFrequency
SetPhase
ShiftPhase
Snapshot
TimeBlockade

These are all instances of the same base class:

.. autoclass:: Instruction
"""
from .acquire import Acquire
from .delay import Delay
from .directives import Directive, RelativeBarrier
from .directives import Directive, RelativeBarrier, TimeBlockade
nkanazawa1989 marked this conversation as resolved.
Show resolved Hide resolved
from .call import Call
from .instruction import Instruction
from .frequency import SetFrequency, ShiftFrequency
Expand Down
Loading