qat.backend.passes.analysis module

class BindingPass

Bases: AnalysisPass

Builds binding of variables, instructions, and a view of variables from/to scopes.

Variables are implicitly declared in sweep instructions and are ultimately read from quantum instructions. Thus, every iteration variable is associated to all the scopes it is declared in.

Values of the iteration variable are abstract and don’t mean anything. In this pass, we only extract the bound and associate it to the name variable. Further analysis is required on read sites to make sure their usage is consistent and meaningful.

static extract_iter_bound(value)

Given a sequence of numbers (typically having been generated from np.linspace()), figure out if the numbers are linearly/evenly spaced, in which case returns an IterBound instance holding the start, step, end, and count of the numbers in the array, or else fail.

In the future, we might be interested in relaxing this condition and return “interpolated” evenly spaced approximation of the input sequence.

run(ir, res_mgr, *args, **kwargs)
Parameters:
class BindingResult(scoping_results=<factory>, rw_results=<factory>, iter_bound_results=<factory>)

Bases: ResultInfoMixin

iter_bound_results: Dict[PulseChannel, Dict[str, IterBound]]
rw_results: Dict[PulseChannel, ReadWriteResult]
scoping_results: Dict[PulseChannel, ScopingResult]
class CFGPass

Bases: AnalysisPass

run(ir, res_mgr, *args, **kwargs)
Parameters:
class CFGResult(cfg=<factory>)

Bases: ResultInfoMixin

cfg: ControlFlowGraph
class IntermediateFrequencyAnalysis(model)

Bases: AnalysisPass

Adapted from qat.purr.backends.live.LiveDeviceEngine.build_baseband_frequencies().

Retrieves intermediate frequencies for all physical channels if they exist, and validates that pulse channels that share the same physical channel cannot have differing fixed frequencies. This pass should always follow a TriagePass, as information of pulse channels are needed.

Instantiate the pass with a hardware model.

Parameters:

model (QuantumHardwareModel) – The hardware model.

run(ir, res_mgr, *args, **kwargs)
Parameters:
  • ir (PartitionedIR) – The list of instructions stored in an InstructionBuilder.

  • res_mgr (ResultManager) – The result manager to store the analysis results.

Return type:

PartitionedIR

class IntermediateFrequencyResult(frequencies)

Bases: ResultInfoMixin

frequencies: Dict[PhysicalChannel, float]
class IterBound(start=None, step=None, end=None, count=None)

Bases: object

count: int = None
end: Union[int, float, complex] = None
start: Union[int, float, complex] = None
step: Union[int, float, complex] = None
class LifetimePass

Bases: AnalysisPass

The end goal of this pass is to facilitate sequencer allocation on the control hardware. Much like classical register allocation techniques, this pass is intended to perform “channel liveness” analysis.

A logical channel is alive at some point P1 in the builder (analogically to a classical program) if it is targeted by some quantum operation at some point P2 > P1 in the future relative to P1.

Example:

P1: |– builder.pulse(pulse_channel1)
P2: | builder.pulse(pulse_channel2) –|
… |
P3: |– builder.pulse(pulse_channel1) |

… |

P4: builder.pulse(pulse_channel2) –|

pulse_channel1 is alive at P1 and P2, pulse_channel2 is alive at P2 and P3. Notice how the lifetimes of the channels overlap and “interfere”.

Knowledge of channel/target liveness (with full awareness of control flow) is invaluable for understanding physical allocation requirements on the control stack. This is achieved via an interference graph which allows allocation to be represented as a graph coloring.

With this in mind, this pass spits out a colored interference graph that will be used by the code generator.

run(ir, res_mgr, *args, **kwargs)
class PulseChannelTimeline(samples=<factory>, start_positions=<factory>, end_positions=<factory>)

Bases: object

Timeline analysis for instructions on a pulse channel.

Imagine the timeline for a pulse channel, with an instruction that occurs over samples 3-7, i.e.,

samples: 0 1 2 [3 4 5 6 7] 8 9 10.

The start_position would be 3, the end_position 7, and the number of samples 5.

Parameters:
  • samples (np.ndarray[int]) – The number of samples each instruction takes.

  • start_positions (np.ndarray[int]) – The sample when the instruction begins.

  • end_positions (np.ndarray[int]) – The sample when the instruction ends.

end_positions: ndarray[int]
samples: ndarray[int]
start_positions: ndarray[int]
class ReadWriteResult(reads=<factory>, writes=<factory>)

Bases: object

reads: Dict[str, List[Instruction]]
writes: Dict[str, List[Instruction]]
class ScopingResult(scope2symbols=<factory>, symbol2scopes=<factory>)

Bases: object

scope2symbols: Dict[Tuple[Instruction, Optional[Instruction]], Set[str]]
symbol2scopes: Dict[str, List[Tuple[Instruction, Optional[Instruction]]]]
class TILegalisationPass

Bases: AnalysisPass

An instruction is legal if it has a direct equivalent in the programming model implemented by the control stack. The notion of “legal” is highly determined by the hardware features of the control stack as well as its programming model. Control stacks such as Qblox have a direct ISA-level representation for basic RF instructions such as frequency and phase manipulation, arithmetic instructions such as add, and branching instructions such as jump.

This pass performs target-independent legalisation. The goal here is to understand how variables are used and legalise their bounds. Furthermore, analysis in this pass is fundamentally based on QAT semantics and must be kept target-agnostic so that it can be reused among backends.

Particularly in QAT: #. A sweep instruction is illegal because it specifies unclear iteration semantics. #. Device updates/assigns in general are illegal because they are bound to a sweep

instruction via a variable. In fact, a variable (implicitly defined by a Sweep instruction) remains obscure until a “read” (usually on the instruction builder or on the hardware model) (typically from a DeviceUpdate instruction) is encountered where its intent becomes clear. We say that a DeviceUpdate carries meaning for the variable and materialises its intention.

static decompose_freq(frequency, target)
run(ir, res_mgr, *args, **kwargs)
Parameters:
static transform_amp(amp, scale_factor, ignore_scale, target)
class TimelineAnalysis

Bases: AnalysisPass

Analyses the timeline of each pulse channel.

Takes the instruction list for each pulse channel retrieved from the the partitioned results, and calculates the timeline in units of samples (each sample takes time sample_time). It calculates the duration of each instruction in units of samples, and the start and end times of each instruction in units of samples.

Warning

The pass will assume that the durations of instructions are sanitised to the granularity of the channels. If instructions that do not meet the criteria are provided, it might produce incorrect timelines. This can be enforced used the InstructionGranularitySanitisation pass.

static durations_as_samples(channel, durations)

Converts a list of durations into a number of samples.

run(ir, res_mgr, *args, **kwargs)
Parameters:
  • ir (PartitionedIR) – The list of instructions stored in an InstructionBuilder.

  • res_mgr (ResultManager) – The result manager to store the analysis results.

Return type:

PartitionedIR

class TimelineAnalysisResult(target_map=<factory>, total_duration=0.0)

Bases: ResultInfoMixin

Stores the timeline analysis for all pulse channels.

Parameters:

target_map (dict[PulseChannel, PulseChannelTimeline]) – The dictionary containing the timeline analysis for all pulse channels.

target_map: dict[PulseChannel, PulseChannelTimeline]
total_duration: float = 0.0
class TriagePass

Bases: AnalysisPass

Builds a view of instructions per quantum target AOT.

Builds selections of instructions useful for subsequent analysis/transform passes, for code generation, and post-playback steps.

This is equivalent to the QatFile and simplifies the duration timeline creation in the legacy code.

run(ir, res_mgr, *args, **kwargs)
Parameters:
class TriageResult(sweeps=<factory>, returns=<factory>, assigns=<factory>, target_map=<factory>, acquire_map=<factory>, pp_map=<factory>, rp_map=<factory>)

Bases: ResultInfoMixin

acquire_map: Dict[PulseChannel, List[Acquire]]
assigns: List[Assign]
pp_map: Dict[str, List[PostProcessing]]
returns: List[Return]
rp_map: Dict[str, ResultsProcessing]
sweeps: List[Sweep]
target_map: Dict[PulseChannel, List[Instruction]]