ScheduleBuilder#

class iqm.pulse.builder.ScheduleBuilder(op_table, calibration, chip_topology, channels, component_channels)#

Bases: object

Builds instruction schedules out of quantum circuits or individual quantum operations.

Encapsulates known quantum ops, the calibration information for them, QPU components and their topology, and controller properties.

Parameters:
  • op_table (QuantumOpTable) – definitions of known quantum ops

  • calibration (OpCalibrationDataTree) – calibration data tree for the quantum ops

  • chip_topology (ChipTopology) – Chip topology derived from the CHAD.

  • channels (dict[str, ChannelProperties]) – mapping of controller names to the configurations of their channels

  • component_channels (dict[str, dict[str, str]]) – Mapping from QPU component name to a mapping of ('drive', 'flux', 'readout') to the name of the control channel responsible for that function of the component.

Module: iqm.pulse.builder

Methods

build_playlist

Build a playlist from a number of instruction schedules.

circuit_to_timebox

Convert a quantum circuit to a TimeBox.

get_calibration

Calibration data for the given quantum operation, implementation and locus.

get_control_channels

Control channels that directly affect quantum operations at the given locus.

get_drive_channel

Drive channel for the given QPU component.

get_flux_channel

Flux channel for the given QPU component.

get_implementation

Provide an implementation for a quantum operation at a given locus.

get_implementation_class

Implementation class for the given operation.

get_probe_channel

Probe line channel for the probe line component belongs to.

get_virtual_feedback_channel_for

Get virtual feedback channel for feedback to a given AWG from a given probe line.

get_virtual_feedback_channels

All virtual feedback signal channels for the given QPU component.

has_calibration

Is there calibration data for the given quantum operation, implementation and locus?

inject_calibration

Inject new calibration data, changing self.calibration after the ScheduleBuilder initialisation.

resolve_timebox

Resolve a TimeBox.

timebox_to_schedule

Convert a TimeBox to a finished instruction schedule, ready for execution.

timeboxes_to_front_padded_playlist

Temporary helper function, for converting a sequence of TimeBoxes to a Playlist.

timeboxes_to_playlist

Convert a sequence of TimeBoxes to a Playlist.

validate_calibration

Check that the calibration data matches the known quantum operations.

validate_quantum_circuit

Validate a sequence of circuit operations constituting a quantum circuit.

wait

Utility method for applying Block instructions on every channel of the given locus.

_cache: dict[str, dict[str, dict[tuple[str, ...], GateImplementation]]]#

Cached GateImplementations. The tree has the same structure as OpCalibrationDataTree.

_channel_to_component: dict[str, str]#

self.component_channels mapping inverted cached for scheduling algorithm performance. This mapping is used in the scheduling to determine the components to block based on their associated channels. Only blocking channels are included in this mapping, non-blocking channels (e.g. certain virtual channels) do not block their components, just themselves.

_variable_sample_rates: bool#

the drive and flux channels should always have uniform sampling rates, so this can happen only due to readout channels.

Type:

Whether the builder has variable sampling rates in its channels. NOTE

_channel_types#

Cache the probe and non-probe channel names for the scheduling algorithm performance

_channel_templates: dict[str, ChannelProperties | None]#

Cache representative channel properties for a probe and a non-probe channel for the scheduling algorithm performance.

inject_calibration(partial_calibration)#

Inject new calibration data, changing self.calibration after the ScheduleBuilder initialisation.

Invalidates the gate_implementation cache for the affected operations/implementations/loci. Also invalidates the cache for any factorizable gate implementation, if any of its locus components was affected.

Parameters:

partial_calibration (dict[str, dict[str, dict[tuple[str, ...] | None, dict[str, Any]]]]) – data to be injected. Must have the same structure as self.calibration but does not have to contain all operations/implementations/loci/values. Only the parts of the data that are found will be merged into self.calibration (including any None values). self._cache will be invalidated for the found operations/implementations/loci and only if the new calibration data actually differs from the previous.

Return type:

None

validate_calibration()#

Check that the calibration data matches the known quantum operations.

Raises:

ValueError – there is something wrong with the calibration data

Return type:

None

get_drive_channel(component)#

Drive channel for the given QPU component.

Parameters:

component (str) – name of a QPU component

Returns:

Name of the drive channel for component, if it exists.

Raises:

KeyError – if component does not exist or does not have a drive channel

Return type:

str

get_flux_channel(component)#

Flux channel for the given QPU component.

See get_drive_channel().

Parameters:

component (str) –

Return type:

str

get_probe_channel(component)#

Probe line channel for the probe line component belongs to.

See get_drive_channel().

Parameters:

component (str) –

Return type:

str

get_virtual_feedback_channels(component)#

All virtual feedback signal channels for the given QPU component.

A virtual feedback channel between a source and a destination exists if the station configuration allows it. component can be either the source or the destination of the signal.

Parameters:

component (str) – name of a QPU component

Returns:

Names of the virtual channels.

Return type:

list[str]

get_virtual_feedback_channel_for(awg_name, feedback_qubit)#

Get virtual feedback channel for feedback to a given AWG from a given probe line.

Parameters:
  • awg_name (str) – name of the awg node that receives the feedback bit.

  • feedback_qubit (str) – which qubit’s measurement resulted in the feedback bit

Returns:

The virtual feedback channel name.

Raises:

ValueError – if the given AWG does not support fast feedback from the given probe line.

Return type:

str

_get_channel_for_component(component, operation)#

Control channel name for the given QPU component and operation.

Returns:

name of the channel

Raises:

KeyError – if component does not exist or does not have the operation

Parameters:
  • component (str) –

  • operation (str) –

Return type:

str

has_calibration(op_name, impl_name, locus)#

Is there calibration data for the given quantum operation, implementation and locus?

Parameters:
  • op_name (str) – name of the quantum operation

  • impl_name (str) – name of the implementation

  • locus (tuple[str, ...]) – locus of the operation

Returns:

True iff requested calibration data was found

Return type:

bool

get_calibration(op_name, impl_name, locus)#

Calibration data for the given quantum operation, implementation and locus.

Parameters:
  • op_name (str) – name of the quantum operation

  • impl_name (str) – name of the implementation

  • locus (tuple[str, ...]) – locus of the operation

Returns:

requested calibration data

Raises:

ValueError – requested calibration data was not found

Return type:

dict[str, Any]

get_control_channels(locus)#

Control channels that directly affect quantum operations at the given locus.

Includes the probe, drive and flux channels of the locus QPU components. Does not include e.g. any neighboring coupler channels, these will have to be added separately in the TimeBox resolution phase.

Will only return channels that are known to exist, i.e. are found in ScheduleBuilder.channels.

Parameters:

locus (Iterable[str]) – locus on which the operation acts

Returns:

names of the control channels that directly affect the operation

Return type:

tuple[str, …]

wait(locus, duration, *, rounding=False)#

Utility method for applying Block instructions on every channel of the given locus.

The Block instructions guarantee the locus components to idle for the given duration, and cannot e.g. be replaced with e.g. dynamical decoupling sequences. They are treated the same as any other TimeBox contents:

  1. Blocks on different channels remain aligned in time during scheduling.

  2. The actual waiting time on a particular channel may thus be >= duration, if the other channels have less non-blocking space on either side.

Note

TODO For now, this method can round duration to the nearest value allowed by each channel if requested. This is for the benefit of EXA sweeping over waiting durations. In the future, EXA sweep generation should be responsible for doing the rounding.

Parameters:
  • locus (Iterable[str]) – locus components that should experience the wait

  • duration (float) – how long to wait (in seconds)

  • rounding (bool) – Iff True, for each channel separately, duration will be rounded to the nearest value allowed by the granularity of that channel. The Waits will start simultaneously.

Returns:

box containing Block instructions on every control channel of locus

Return type:

TimeBox

get_implementation(op_name, locus, impl_name=None, *, use_priority_order=False, strict_locus=False, priority_calibration=None)#

Provide an implementation for a quantum operation at a given locus.

Parameters:
  • op_name (str) – name of the quantum operation

  • locus (Iterable[str]) – locus of the operation

  • impl_name (str | None) – name of the implementation (None means the implementation is chosen automatically using the logic described below)

  • strict_locus (bool) – iff False, for non-symmetric implementations of symmetric ops the locus order may be changed if no calibration data is available for the requested locus order

  • use_priority_order (bool) – Only has an effect if impl_name is None. Iff False, QuantumOp.get_default_implementation_for_locus() is used. Otherwise, the first implementation in the priority order that has calibration data for locus is chosen. The priority order is as follows: 1. The locus-specific priority defined in QuantumOp.defaults_for_locus[locus] if any. 2. The global priority order defined in QuantumOp.implementations.

  • priority_calibration (dict[str, Any] | None) – Calibration data from which to load the calibration instead of the common calibration data in calibration. If no calibration is found for the given implementation or priority_calibration is None, the common calibration is used. Any non-empty values found in priority_calibration will be merged to the common calibration. Note: using priority_calibration will prevent saving/loading via the cache.

Returns:

requested implementation

Raises:

ValueError – requested implementation could not be provided

Return type:

GateImplementation

_find_implementation_and_locus(op, impl_name, locus, *, strict_locus=False)#

Find an implementation and locus for the given quantum operation instance compatible with the calibration data.

Parameters:
  • op (QuantumOp) – quantum operation

  • impl_name (str | None) – Name of the implementation. None means use the highest-priority implementation for which we have calibration data.

  • locus (tuple[str, ...]) – locus of the operation

  • strict_locus (bool) – iff False, for non-symmetric implementations of symmetric ops the locus order may be changed to an equivalent one if no calibration data is available for the requested locus order

Returns:

chosen implementation name, locus

Raises:
  • ValueError – requested implementation could not be found

  • ValueError – requested implementation had no calibration data for this locus

  • ValueError – no specific implementation was requested, but no known implementation had calibration data for this locus

Return type:

tuple[str, tuple[str, …]]

_get_implementation(op, impl_name, locus, strict_locus=False, priority_calibration=None)#

Build a factory class for the given quantum operation, implementation and locus.

The GateImplementations are built when they are first requested, and cached for later use.

Parameters:
  • op (QuantumOp) – quantum operation

  • impl_name (str | None) – Name of the implementation. None means use the highest-priority implementation for which we have calibration data.

  • locus (tuple[str, ...]) – locus of the operation

  • strict_locus (bool) – iff False, for non-symmetric implementations of symmetric ops the locus order may be changed if no calibration data is available for the requested locus order

  • priority_calibration (dict[str, Any] | None) – Calibration data from which to load the calibration instead of the common calibration data. Priority calibration should be either a dict of the type OILCalibrationData, i.e. containing the operation name, implementation name, and locus, or just a dict containing the calibration data for the locus implied by the args op, impl_name and locus.

Returns:

requested implementation

Raises:

ValueError – requested implementation could not be provided or had no calibration data for this locus

Return type:

GateImplementation

get_implementation_class(op_name, impl_name=None)#

Implementation class for the given operation.

Parameters:
  • op_name (str) – name of the quantum operation

  • impl_name (str | None) – name of the implementation (None means use the default implementation)

Returns:

requested implementation class

Return type:

type[GateImplementation]

validate_quantum_circuit(operations, *, require_measurements=False)#

Validate a sequence of circuit operations constituting a quantum circuit.

Parameters:
  • operations (Iterable[CircuitOperation]) – quantum circuit to be validated

  • require_measurements (bool) – iff True the circuit must include at least one measurement operation

Raises:

ValueErroroperations do not constitute a valid quantum circuit

Return type:

None

circuit_to_timebox(circuit, *, name='', scheduling_algorithm=SchedulingAlgorithm.HARD_BOUNDARY)#

Convert a quantum circuit to a TimeBox.

Parameters:
Returns:

unresolved TimeBox that implements circuit

Return type:

TimeBox

timeboxes_to_front_padded_playlist(boxes, *, neighborhood=0)#

Temporary helper function, for converting a sequence of TimeBoxes to a Playlist.

Each individual TimeBox in boxes is resolved into a Schedule, and then each schedules is front-padded with Wait instructions on each channel such that the resulting Schedules have equal durations. This is required since for now in Station Control the delay before the final measurement is the same for all the Schedules in a Playlist, and we do not wish to lose coherence waiting for the measurement after each Schedule is done.

TODO Once Station Control can handle measurements better, this method should be removed, and timeboxes_to_playlist() be used instead.

Parameters:
  • boxes (Iterable[TimeBox]) – TimeBoxes to include in the playlist

  • neighborhood (int) – During scheduling, block neighboring channels of the used components this far. By default, blocks only the defined locus components and any other components which have occupied channels.

Returns:

playlist that implements boxes, padded schedule duration in seconds

Return type:

tuple[Playlist, float]

timeboxes_to_playlist(boxes, *, neighborhood=1)#

Convert a sequence of TimeBoxes to a Playlist.

Resolves the boxes, converts them to Schedules, removes unnecessary channels, and then packs the Schedules into a Playlist. Assumes all the TimeBoxes refer to the same QPU and its control channels.

Parameters:
  • boxes (Iterable[TimeBox]) – TimeBoxes to include in the playlist

  • neighborhood (int) – During scheduling, block neighboring channels of the used components this far. The default value ensures that quantum operations work as intended, assuming the station is properly calibrated. Higher values may help defend against crosstalk, at the expense of a longer instruction schedule and thus more decoherence.

Returns:

playlist that implements boxes

Return type:

Playlist

timebox_to_schedule(box, *, neighborhood=1)#

Convert a TimeBox to a finished instruction schedule, ready for execution.

Resolves the box, then converts the durations of the instructions in the schedule to samples at the channel sample_rate.

Parameters:
  • box (TimeBox) – TimeBox to resolve

  • neighborhood (int) – During scheduling, block neighboring channels of the used components this far. The default value ensures that quantum operations work as intended, assuming the station is properly calibrated. Higher values may help defend against crosstalk, at the expense of a longer instruction schedule and thus more decoherence.

Returns:

finished schedule that implements box

Return type:

Schedule

_finish_schedule(schedule)#

Finishes the instruction schedule.

  • filters out zero-duration Blocks and Waits

  • converts all spacer instructions used during scheduling to Waits

  • merges consequent Waits

  • removes channels that only have Waits in them

This should be the final step of the schedule building process, after this the resulting Schedule can no longer be consistently extended with another (since all the spacer instructions are gone).

Parameters:

schedule (Schedule) – schedule to finish

Returns:

finished copy of schedule

Return type:

Schedule

resolve_timebox(box, *, neighborhood, compute_neighborhood_hard_boundary=False)#

Resolve a TimeBox.

Resolves recursively each of the children of the box, and then concatenates the resulting Schedules into a new one using a specific scheduling strategy and algorithm.

The supported algorithms are HARD_BOUNDARY, which treats each composite TimeBox as a solid rectangle (the longest channel within defines the duration) and TETRIS, which packs the schedule as tightly as possible (solid instructions still cannot overlap) regardless of the TimeBox boundaries.

Modifies box so that it becomes atomic, if it isn’t already.

Parameters:
  • box (TimeBox) – TimeBox to resolve

  • neighborhood (int) – During scheduling, block control channels of neighboring QPU components this far from the locus. Values higher than 0 may help defend against crosstalk, at the expense of a longer instruction schedule and thus more decoherence.

  • compute_neighborhood_hard_boundary (bool) – Whether to precompute the neighborhood components while resolving a composite TimeBox in the HARD_BOUNDARY algorithm. Typically one does not want to do this on the top layer composite TimeBox, since it would be unused. The algorithm sets this True on lower layers, where it improves the performance as the neighborhood components are needed in scheduling.

Returns:

instruction schedule that implements box

Return type:

Schedule

_resolve_timebox_hard_boundary(box, neighborhood, compute_neighborhood=False)#

Resolves a TimeBox using the HARD_BOUNDARY algorithm, which treats each composite TimeBox as a solid rectangle (the longest channel within defines the duration).

Parameters:
  • box (TimeBox) –

  • neighborhood (int) –

  • compute_neighborhood (bool) –

Return type:

Schedule

_get_neighborhood_hard_boundary(box, neighborhood)#

Computes and caches the blocking neighborhoods for HARD_BOUNDARY algorithm.

Parameters:
  • box (TimeBox) – Atomic TimeBox whose neighborhood to compute.

  • neighborhood (int) – Return QPU components this far from the locus.

Returns:

QPU components (plus maybe channels?) belonging the the given neighborhood of box.

Return type:

set[str]

_resolve_timebox_tetris(box, *, neighborhood)#

Resolves a TimeBox using the TETRIS algorithm, which packs the schedule as tightly as possible (solid instructions still cannot overlap) regardless of the TimeBox boundaries.

Parameters:
Return type:

Schedule

_block_neighborhood_tetris(schedule, locus, neighborhood)#

Add additional blocked channels to the schedule, preventing their use during the schedule.

In the idealized computational model we assume that in a (correctly calibrated) quantum computer there is no effective interaction between QPU components (in the computational frame and subspace) while a Wait instruction is acting on the flux channel of the coupler connecting those components (i.e., the coupler is idling). Hence a QPU component experiences no effective evolution if Wait instructions are acting on its drive, flux and probe channels, and the flux channels of all its couplers.

Of course, in reality the QPU will experience at least some decoherence during a Wait, and possibly some crosstalk. In some applications, e.g. decoherence experiments, it is essential to Wait for a specific time, since it’s precisely the decoherence that we are interested in.

However, if we are only interested in applying well-defined local quantum operations on specific loci, it is essential to shut down all unwanted dynamics by adding Block instructions on control channels of the unused and neighboring channels. They act like Waits (and are converted into Waits at the end of the scheduling), but are allowed to overlap in time, since we are only interested in blocking those channels for the duration of the quantum operation.

Parameters:
  • schedule (Schedule) – instruction schedule to modify

  • locus (set[str]) – information-carrying QPU components schedule is meant to operate on (does not include couplers)

  • neighborhood (int) – How far should we block neighboring QPU components? Zero means just the locus qubits, one means neighboring couplers, two means their neighboring qubits etc.

Returns:

schedule, with added Block instructions on all the neighbor channels, for the duration of the schedule

Return type:

None

build_playlist(schedules, finish_schedules=True)#

Build a playlist from a number of instruction schedules.

This involves compressing the schedules so that no duplicate information needs to be transferred to Station Control.

All virtual channels are dropped at this point.

Parameters:
  • schedules (Iterable[Schedule]) – finished instruction schedules to include in the playlist

  • finish_schedules (bool) – whether to finalise the schedules before building the playlist. Should be set True unless some process has already finalised them before calling this function.

Returns:

playlist containing the schedules

Raises:

ValueError – if the schedules contain channels with non-uniform sampling rates

Return type:

Playlist

_set_gate_implementation_shortcut(op_name)#

Create shortcut for self.get_implementation(<op_name>, …) as self.<op_name>(…).

If there is a name collision with another attribute in self, the shortcut method won’t be added and a warning is raised.

Parameters:

op_name (str) –

Return type:

None