Source code for qiskit.pulse.instruction_schedule_map

# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
A convenient way to track reusable subschedules by name and qubit.

This can be used for scheduling circuits with custom definitions, for instance::

    inst_map = InstructionScheduleMap()
    inst_map.add('new_inst', 0, qubit_0_new_inst_schedule)

    sched = schedule(quantum_circuit, backend, inst_map)

An instance of this class is instantiated by Pulse-enabled backends and populated with defaults
(if available)::

    inst_map = backend.defaults().instruction_schedule_map

"""
import inspect

from collections import defaultdict
from typing import List, Tuple, Iterable, Union, Callable

from .schedule import Schedule, ParameterizedSchedule
from .exceptions import PulseError


[docs]class InstructionScheduleMap(): """Mapping from :py:class:`~qiskit.circuit.QuantumCircuit` :py:class:`qiskit.circuit.Instruction` names and qubits to :py:class:`~qiskit.pulse.Schedule` s. In particular, the mapping is formatted as type:: Dict[str, Dict[Tuple[int], Schedule]] where the first key is the name of a circuit instruction (e.g. ``'u1'``, ``'measure'``), the second key is a tuple of qubit indices, and the final value is a Schedule implementing the requested instruction. These can usually be seen as gate calibrations. """ def __init__(self): """Initialize a circuit instruction to schedule mapper instance.""" # The processed and reformatted circuit instruction definitions self._map = defaultdict(dict) # A backwards mapping from qubit to supported instructions self._qubit_instructions = defaultdict(set) @property def instructions(self) -> List[str]: """Return all instructions which have definitions. By default, these are typically the basis gates along with other instructions such as measure and reset. Returns: The names of all the circuit instructions which have Schedule definitions in this. """ return list(self._map.keys())
[docs] def qubits_with_instruction(self, instruction: str) -> List[Union[int, Tuple[int]]]: """Return a list of the qubits for which the given instruction is defined. Single qubit instructions return a flat list, and multiqubit instructions return a list of ordered tuples. Args: instruction: The name of the circuit instruction. Returns: Qubit indices which have the given instruction defined. This is a list of tuples if the instruction has an arity greater than 1, or a flat list of ints otherwise. Raises: PulseError: If the instruction is not found. """ if instruction not in self._map: return [] return [qubits[0] if len(qubits) == 1 else qubits for qubits in sorted(self._map[instruction].keys())]
[docs] def qubit_instructions(self, qubits: Union[int, Iterable[int]]) -> List[str]: """Return a list of the instruction names that are defined by the backend for the given qubit or qubits. Args: qubits: A qubit index, or a list or tuple of indices. Returns: All the instructions which are defined on the qubits. For 1 qubit, all the 1Q instructions defined. For multiple qubits, all the instructions which apply to that whole set of qubits (e.g. ``qubits=[0, 1]`` may return ``['cx']``). """ if _to_tuple(qubits) in self._qubit_instructions: return list(self._qubit_instructions[_to_tuple(qubits)]) return []
[docs] def has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> bool: """Is the instruction defined for the given qubits? Args: instruction: The instruction for which to look. qubits: The specific qubits for the instruction. Returns: True iff the instruction is defined. """ return instruction in self._map and \ _to_tuple(qubits) in self._map[instruction]
[docs] def assert_has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> None: """Error if the given instruction is not defined. Args: instruction: The instruction for which to look. qubits: The specific qubits for the instruction. Raises: PulseError: If the instruction is not defined on the qubits. """ if not self.has(instruction, _to_tuple(qubits)): if instruction in self._map: raise PulseError("Operation '{inst}' exists, but is only defined for qubits " "{qubits}.".format( inst=instruction, qubits=self.qubits_with_instruction(instruction))) raise PulseError("Operation '{inst}' is not defined for this " "system.".format(inst=instruction))
[docs] def get(self, instruction: str, qubits: Union[int, Iterable[int]], *params: Union[int, float, complex], **kwparams: Union[int, float, complex]) -> Schedule: """Return the defined :py:class:`~qiskit.pulse.Schedule` for the given instruction on the given qubits. Args: instruction: Name of the instruction. qubits: The qubits for the instruction. *params: Command parameters for generating the output schedule. **kwparams: Keyworded command parameters for generating the schedule. Returns: The Schedule defined for the input. """ self.assert_has(instruction, qubits) schedule_generator = self._map[instruction].get(_to_tuple(qubits)) if callable(schedule_generator): return schedule_generator(*params, **kwparams) # otherwise this is just a Schedule return schedule_generator
[docs] def add(self, instruction: str, qubits: Union[int, Iterable[int]], schedule: Union[Schedule, Callable[..., Schedule]]) -> None: """Add a new known instruction for the given qubits and its mapping to a pulse schedule. Args: instruction: The name of the instruction to add. qubits: The qubits which the instruction applies to. schedule: The Schedule that implements the given instruction. Raises: PulseError: If the qubits are provided as an empty iterable. """ qubits = _to_tuple(qubits) if qubits == (): raise PulseError("Cannot add definition {} with no target qubits.".format(instruction)) if not (isinstance(schedule, Schedule) or callable(schedule)): raise PulseError('Supplied schedule must be either a Schedule, or a ' 'callable that outputs a schedule.') self._map[instruction][qubits] = schedule self._qubit_instructions[qubits].add(instruction)
[docs] def remove(self, instruction: str, qubits: Union[int, Iterable[int]]) -> None: """Remove the given instruction from the listing of instructions defined in self. Args: instruction: The name of the instruction to add. qubits: The qubits which the instruction applies to. """ qubits = _to_tuple(qubits) self.assert_has(instruction, qubits) self._map[instruction].pop(qubits) self._qubit_instructions[qubits].remove(instruction) if not self._map[instruction]: self._map.pop(instruction) if not self._qubit_instructions[qubits]: self._qubit_instructions.pop(qubits)
[docs] def pop(self, instruction: str, qubits: Union[int, Iterable[int]], *params: Union[int, float, complex], **kwparams: Union[int, float, complex]) -> Schedule: """Remove and return the defined ``Schedule`` for the given instruction on the given qubits. Args: instruction: Name of the instruction. qubits: The qubits for the instruction. *params: Command parameters for generating the output schedule. **kwparams: Keyworded command parameters for generating the schedule. Returns: The Schedule defined for the input. """ schedule = self.get(instruction, qubits, *params, **kwparams) self.remove(instruction, qubits) return schedule
[docs] def get_parameters(self, instruction: str, qubits: Union[int, Iterable[int]]) -> Tuple[str]: """Return the list of parameters taken by the given instruction on the given qubits. Args: instruction: Name of the instruction. qubits: The qubits for the instruction. Returns: The names of the parameters required by the instruction. """ self.assert_has(instruction, qubits) schedule_generator = self._map[instruction][_to_tuple(qubits)] if isinstance(schedule_generator, ParameterizedSchedule): return schedule_generator.parameters elif callable(schedule_generator): return tuple(inspect.signature(schedule_generator).parameters.keys()) else: return ()
def __str__(self): single_q_insts = "1Q instructions:\n" multi_q_insts = "Multi qubit instructions:\n" for qubits, insts in self._qubit_instructions.items(): if len(qubits) == 1: single_q_insts += " q{qubit}: {insts}\n".format(qubit=qubits[0], insts=insts) else: multi_q_insts += " {qubits}: {insts}\n".format(qubits=qubits, insts=insts) instructions = single_q_insts + multi_q_insts return ("<{name}({insts})>" "".format(name=self.__class__.__name__, insts=instructions))
def _to_tuple(values: Union[int, Iterable[int]]) -> Tuple[int, ...]: """Return the input as a tuple. Args: values: An integer, or iterable of integers. Returns: The input values as a sorted tuple. """ try: return tuple(values) except TypeError: return (values,)