Compilation

class trueq.compilation.Direction

Defines the two directions that Patterns may be applied to trueq.Circuit

See descrption of trueq.compilation.base.Pattern for more details

class trueq.compilation.Justify(direction=<Direction.FORWARD: 1>, **_)

Pattern that takes two Cycles, and moves gates preferentially to one side.

This method is inherently limited, because it only deals with 2 Cycles at a time, it can encounter “traffic jams”, IE: If you have two Cycles on qubit 0, followed by a series of empty cycles, the first Cycles Gate cannot move because of the second Cycle, but then the second cycle’s Gate will iteratively progress forward through the empty Cycles. This can be combated by repeating the Justify several times.

This class skips immutable cycles.

Returns 2 Cycles.

Parameters

direction (trueq.compilation.base.Direction) – Specifies the direction to preferentially move gates, either trueq.compilation.base.Direction.FORWARD or BACK

property direction

This must return the direction that the pattern will be applied.

Direction must be one of the two enums of trueq.compilation.base.Direction

This requires some discussion, the way that the Compiler functions is that it incrementally goes through a circuit, and passes a set of Cycles to a Pattern. There are two ways that it can increment through the Circuit, Forward or Backward.

apply_cycles(cycles)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

class trueq.compilation.Merge(n_labels=1, merge_immutable=False, **_)

Pattern that takes two cycles, finds compatabile labels and gates, then merges them to a single gate as much as is possible.

Gates are automatically split into the smallest number of individual gates possible. For example, if the merging results in a two qubit gate that turns out to be the kronecker product of two single qubit gates, then it will automatically be broken apart into the two single qubit gates.

This class skips immutable cycles unless specified.

Returns 2 Cycles.

Params n_labels

How many labels to merge at any given time, if only single qubit reductions are desired, then n_labels=1, two qubit reductions would be n_labels=2 etc. This defaults to single qubit reductions.

Parameters

merge_immutable (bool) – If set to True, immutable cycles will be merged, if set to False, then immutable cycles are skipped.

apply_cycles(cycles)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

class trueq.compilation.RemoveId(skip_immutable=True, **_)

Pattern that removes identity gates from 1 trueq.Cycle

This is a good class to use as an example for more complex classes.

Returns a list containing 1 Cycle.

Params skip_immutable

Determines if identity gates should be removed from immutable cycles. If True, immutable cycles are not altered.

apply_cycles(cycles)

Removes all identity gates in a cycle if the cycle is mutable or skip_immutable is False.

class trueq.compilation.Relabel(permutation, **_)

Pattern which relabels all the labels and keys in a circuit.

This searches through and relabels/reorders entries in the following key entries:

  • analyze_decays

  • compiled_pauli

  • cycle

  • measurement_basis

  • targeted_errors

  • twirl

Note that this does not function when the circuit has results present.

# Swapping the labels on qubit 0 and 1, 2 stays
permutation = {0: 1, 1: 0, 2: 2}

pat = Relabel(permutation)
new_circ = pat.apply_cycles(old_circ)
Parameters

permutation (dict) – A dictionary where the keys are the current label and the values are the new labels.

apply_cycles(circuit)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

class trueq.compilation.CycleSandwich(target, before=None, after=None, ignore_imm=True, ignore_id=True)

Pattern that searches circuits for a specific cycle and when found, sandwiches it between two other (optional) cycles.

target = tq.Cycle({(0, 1): tq.Gate.CNOT})
before = tq.Cycle(0: tq.Gate.from_generators("Z", 4))
after = tq.Cycle(1: tq.Gate.from_generators("X", -8.2))
pattern = CycleSandwich(target, before=before, after=after)
new_circ = pattern.apply_cycles(old_circuit)

Note

This pattern makes deep copies of before and after before returning them.

Parameters
  • target (Cycle) – Which cycle to match on.

  • before (Cycle) – A cycle to place immediately before every occurrence of the provided target.

  • after (Cycle) – A cycle to place immediately after every occurrence of the provided target.

  • ignore_imm (bool) – Whether to apply this pattern when the target and a cycle have differing values of trueq.Cycle.immutable. Default is True.

  • ignore_id (bool) – Whether to treat all identity gates as though they are not present when comparing cycles. Default is True.

apply_cycles(cycles)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

class trueq.compilation.InvolvingRestrictions(config=None)

A Pattern which ensures that any trueq.NativeGate that is defined from a config obeys the involving restrictions of its Factory.

This is done through a greedy algorithm, with no guarantee on optimal number of final cycles.

Returns a list of cycles, whose length will not exceed the number of operations inside the original cycle.

Cycles retain their immutable flag, and immutable cycles will be broken into pieces as neccessary.

import trueq as tq
import trueq.compilation as tqc
# config is a tq.Config which has arbitrary involving restrictions on any gate
cycle = tq.Cycle({(0, 1): tq.Gate.cx, (2, 3): tq.Gate.cx, 7: tq.Gate.x})
patterns = [tqc.Native2Q(config), tqc.InvolvingRestrictions(config)]
comp = tq.compilation.Compiler(patterns)
comp.compile([cycle])
apply_cycles(cycles)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

class trueq.compilation.Compiler(patterns, repeat=1)

Substitutes cycles in a Circuit using known substitution Patterns.

In classical computing this would formally be called a Peephole Optimizer.

This stores a list of trueq.compilation.base.Pattern objects which are rules for how to replace Gates in sets of cycles.

These Pattern objects take a set of cycles at a time, and return an altered set of cycles. A trivial example is the RemoveId() pattern, which accepts 1 cycle at a time, and removes any identity Gates from the cycle, before returning it.

Each Pattern object requires a certain number of cycles at a time; in the example above, only 1 cycle is passed. In general this is not the case, for example to simplify single qubit operations. In this case the minimum number of cycles needed is 2. The Pattern looks for single qubit gates in both of the cycles, and computes the single equivalent Gate, which is put back instead of the two original Gates.

In traversing a Circuit, the compiler can either iterate forward, or backward. This traversal direction is specified by the Patterns themselves, as it may be useful to have certain patterns apply themselves backwards while others are strictly forward.

To build a Compiler which knows all possible simplification rules would result in an overly complex and rigid tool, which would would be difficult to generalize for all hardware implementations. By making the compiler itself very small, and allowing custom rulesets (Patterns in this case), very complex compilation instructions can be expressed in simple and readable fashion.

An example of a set of patterns which converts a Circuit into hardware aware Gates:

Compiler([Justify(),

Native2Q(config=config), Merge(), RemoveId(), Native1Q(config=config), Justify() ])

This set of patterns performs these operations:

Justify - move gates preferentially to one side of the circuit, for example,

A circuit containing an X90 Gate at the beginning, but then 10 empty cycles. Justify(Direction.FORWARD) would move the X90 gate all the way to the other end of the circuit.

Native2Q - Convert all 2 qubit operations found in the circuit into NativeGate’s

which can be run on a hardware as specified by the config object. This Pattern does NOT decompose the single qubit gates, so in the process it adds single qubit Gate objects to some Cycles.

Merge - This simplifies any neighboring single qubit operations and reduces

them to a single Gate.

RemoveId - Since the simplify step may have introduced single qubit gate which

are the identity gate, this Pattern removes all Id Gates.

Native1Q - This converts all 1 qubit operations into NativeGates which can be

run on hardware as specified by the config object.

Justify - Just for good measure, make sure everything is moved as far forward

in the Circuit as Possible.

Parameters
  • patterns (list) – A list of trueq.compilation.base.Pattern, see above.

  • repeat (int) – The number of times to repeat the pattern list, may be helpful in certain instances.

compile(circ)

Apply all of the patterns in the compiler in order to a given trueq.Circuit or trueq.CircuitCollection

Parameters

circ (trueq.Circuit | trueq.CircuitCollection) – A circuit that the Pattern list should be applied to.

property patterns

A list of all Patterns applied by this compiler.

Return type

list

trueq.compilation.count_streaks(circuit)

Iterates through a circuit, finding all multi-qudit gates and counting the number of times that there are repeated operations on the same pair of qudits.

Repeated operations on a pair of qudits can often be merged and do not require extra swaps to map a circuit onto a specific chip topology. Therefore we want to count “streaks”, where a “streak” on a pair of qudits is a series of cycles in a circuit where the qudit pair of interest do not interact with any other qudits.

For example: Given a circuit of 6 cnot gates, where there are 2 cnots in a row on (0, 1) followed by a cnot on (1, 2), then 3 more cnots in a row on (0, 1).

On labels (0, 1) there is 1 streak of 2 in a row and 1 of 3 in a row. On labels (1, 2) there is 1 streak of length 1.

circ = tq.Circuit({(0, 1): tq.Gate.cx})
circ.add_cycle({(0, 1): tq.Gate.cx})
circ.add_cycle({(1, 2): tq.Gate.cx})
circ.add_cycle({(0, 1): tq.Gate.cx})
circ.add_cycle({(1, 0): tq.Gate.cx})
circ.add_cycle({(0, 1): tq.Gate.cx})

count_streaks(circ)
# {(0, 1): {2: 1, 3: 1}, (1, 2): {1: 1}}

This function returns a nested dictionary whose keys are pairs of qudits that have a multi-qudit gate acting on them, and the values are the numbers of streaks of each length. In the above example, (0, 1) has a value of `{2: 1, 3: 1}`, meaning it found 1 streak of length 2 and 1 streak of length 3.

Note that single qudit operations will not break a streak, and operations may happen in parallel and those will be counted as independent streaks.

The keys of the dictionary can be used to define the connectivity graph of the circuit itself, and are useful for validation of matching circuit topology to chip topology.

Parameters

circuit (trueq.Circuit) – The circuit of which to calculate the topology and streaks.

Return type

dict

class trueq.compilation.Native1Q(config)

Pattern which expands arbitrary single qubit gates into the decomposition mode provided in a given Config object.

Basics of operation:

1. When asked to decompose a qubit gate on a given label, looks through the config object to find GateFactory objects that apply to that qubit. Checks if these factories are sufficient to build arbitrary single qubit gates according to the mode of the config. This list of factories is stashed against the label.

2. Next, these factories are combined with QubitMode to decompose into 3 or 5 cycles of NativeGates.

This class does NOT skip immutable cycles.

Returns 3 or 5 cycles.

Parameters

config (trueq.Config) – Config object which defines available gates on the system

apply_cycles(cycles)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

class trueq.compilation.Native2Q(config, max_depth=3, rounding=5, tol=1e-06)

Series of numerical SU(4) decomposition methods.

Decomposes a target gate into a series of SU(2) gates between non-parameterized SU(4) gates found in the config. This uses the trueq.math.decomposition.decompose_su4() method found below.

This class does NOT skip immutable cycles.

Returns 1 to 2 * max_depth + 1 Cycles.

Parameters
  • config (trueq.Config) – Config object which defines available gates on the system

  • max_depth (int) – The maximum number of SU(4) gates to use.

  • rounding (int) – How much rounding should be performed on the KAK fitting, this is how many decimal places to round to in terms of degrees, IE: 0 means nearest whole degree, 1 means nearest 0.1 degree etc.

apply_cycles(cycles)

Decomposes all SU(4) gates in a given cycle into gates defined by the config.

Parameters

cycles (list) – A list containing 1 trueq.Cycles

Raises

tq.math.DecompError – If there is a gate in the cycle which is not decomposable using the config.

class trueq.compilation.PhaseTrack(config, virtual=None)

This pattern tracks phase accumulation on each qudit throughout a circuit, and compiles this phase information into parametric gates.

For example, if a device tunes up two gate pulses, \(X90\) and \(XX90\) (the maximally entangling Molmer-Sorensen gate), and implements single-qubit Z-rotations virtually, then this pattern will accumulate phases on each qubit based on \(Z(\theta)\) gates it finds, and respectively replace \(X90\) and \(XX90\) gates with parameterized \(X90(\phi)\) gates (i.e. 90 degree nutations about a vector in the X-Y plane) and parameterized \(XX90(\phi_1, \phi_2)\) gates (i.e. the \(XX90\) gate which has been individually phase updated on each qubit). Therefore, pulse sequences can be programmed directly by looping through cycles in a circuit, choosing the pulse shape based on the gate names, and choosing pulse phases based on the parameters of the gates.

See Phase Tracking with the Compiler for detailed usage examples.

Note

This pattern may not output a circuit that implements the same unitary as the input because it may be off by z-rotations (as in the example above) prior to measurement. It will, however, produce the same bitstring statistics because a z-rotation prior to a measurement along the z-axis will not affect bitstring populations.

Parameters
  • config (Config) – The config object that contains all the gate factories of interest.

  • virtual (None | GateFactory) – The factory of the virtual gate. By default, the config will be searched for a single-qubit Z-rotation, which will be defined as the virtual gate.

apply(cycles)

This applies the Pattern to a collection of Cycles.

This accepts a trueq.Circuit, list, or dict

Parameters

cycles (trueq.Circuit, list, dict) – A set of Cycles (n_input_cycles or more)

apply_cycles(cycles)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

class trueq.compilation.DecomposeRRZ(config=None)

Decomposes single qubit gates into 3 gates \(R(\theta) R(\phi) Z(\gamma)\), where the R gates are defined by \(Z(\theta) X(90) Z(-\theta)\).

This does not build into trueq.NativeGates, it is a direct decomposition from trueq.Gates into trueq.Gates. All other operations are left unchanged in the final cycle which is returned.

This class does not skip immutable cycles.

It accepts 1 cycle and will return either 1 or 3 cycles.

apply_cycles(cycles)

This must accept n_input_cycles number of Cycles and return a list of Cycles.

This function must be over-written in the subclass, and should be where the substitutions should be calculated and performed.

Return type

list(Cycles)

trueq.compilation.compile(config, circuit, patterns=None)

Compile from arbitrary True-Qᵀᴹ representation of circuits, into a representation which is compatable with a given config.

By default, this performs a standard set of patterns, and can be used as a template for more advanced compiler definitions. See trueq.compilation.DEFAULT_PATTERNS for a list of the default patterns which are applied.

Parameters
  • config (trueq.Config) – A trueq.Config which has 2-qubit gates defined.

  • circuit (trueq.Circuit) – A circuit which will be compiled into the config compatable format.

  • patterns (tuple) – A tuple of trueq.compilation.Pattern to be applied in order. These must be uninstantiated classes which must accept a config as a keyword argument, see trueq.compilation.DEFAULT_PATTERNS for an example. If None is provided, then the default list is used.

trueq.compilation.get_transpiler(config, patterns=None)

Get a trueq.Compiler which can transpile from arbitrary True-Qᵀᴹ representation of circuits, into a representation which is compatable with a given config.

By default, this performs a standard set of patterns, and can be used as a template for more advanced compiler definitions. See trueq.compilation.DEFAULT_PATTERNS for a list of the default patterns which are applied.

Parameters
  • config (trueq.Config) – A trueq.Config which has 2-qubit gates defined.

  • patterns (tuple) – A tuple of trueq.compilation.Pattern to be applied in order. These must be uninstantiated classes which must accept a config as a keyword argument, see trueq.compilation.DEFAULT_PATTERNS for an example. If None is provided, then the default list is used.