Concepts

QUASAR (QUantum Assembler for Star-topology ARchitectures) is a hardware-aware compilation workspace for star-topology quantum devices. Its main targets are central-spin platforms, such as nitrogen–vacancy (NV) centers in diamond. There, one central electron spin is coupled to several peripheral nuclear spins.

This page explains the concepts behind the package. The tutorials show how to use them, and the API Reference documents every public class and function.

QUASAR is organized as three independent objects, each in its own subpackage:

  • GateSet (quasar.gatesets) describes a native gate catalogue. It is independent of the two compilers and of any simulator.

  • GateCompiler (quasar.gate_compiler) executes the compilation of a logical circuit to native gates, for a bound gate set.

  • PulseCompiler (quasar.pulse_compiler) executes the lowering of a native circuit to pulses, for a bound gate set, backend and device model.

Conventions

QUASAR follows Qiskit’s qubit order everywhere.

object

order

example on three qubits

Pauli string

the last letter is qubit 0

"IXZ": \(Z\) on qubit 0, \(X\) on qubit 1

basis state

qubit 0 is the lowest bit

initial_state=1 and \(\lvert 001\rangle\) excite qubit 0

matrix of a gate or a circuit

qubit 0 is the right tensor factor

as Qiskit’s Operator

Many textbooks put the control on the left

The formulas of the two-qubit entanglers on this page follow Qiskit’s order. The control, the gate’s first qubit, is the right tensor factor. Many textbooks write the control as the left factor, for example \(\mathrm{CX} = |0\rangle\langle 0| \otimes I + |1\rangle\langle 1| \otimes X\).

Simphony orders its spins the other way

In a Simphony model, spin 0 is the left tensor factor and the highest bit. A state or an operator read from a Simphony simulation is in that order. Reverse it before comparing it with Qiskit, for example with Statevector.reverse_qargs() or QuantumCircuit.reverse_bits(). The benchmarks of PulseCompiler do this themselves.

Gate sets

A GateSet describes the native gate catalogue of a device. It says which single- and two-qubit operations are native, and which qubits they connect. Optionally, it also says how each operation maps to a pulse sequence. Each native operation is a Gate of some GateType.

Native gates

The gate sets build their circuits from these gates.

Single-qubit gates

  • rz — a rotation about \(Z\): \(R_z(\lambda) = e^{-i\lambda Z/2}\).

    • It is a virtual gate: the device realizes it as a frame change.

    • A frame change is no pulse. It takes no time and carries no error.

  • r — a rotation by \(\theta\) about an axis in the \(XY\) plane: \(R(\theta, \phi) = e^{-i\frac{\theta}{2}\left(\cos\phi\,X + \sin\phi\,Y\right)}\).

  • rx — the same rotation about \(X\): \(R_x(\theta) = R(\theta, 0)\).

  • sx — a fixed rotation with no angle: \(SX = \sqrt{X} = e^{i\pi/4} R_x(\pi/2)\).

Two-qubit entanglers

The first qubit is the control, the second the target. The formulas put the control on the right, in Qiskit’s order (Conventions).

  • crot — a controlled rotation, where the control flips the sign of the target’s rotation: \(\mathrm{CROT}(\theta, \phi) = R(\theta, \phi) \otimes |0\rangle\langle 0| + R(-\theta, \phi) \otimes |1\rangle\langle 1|\).

  • crotxpi2 — crot with both angles fixed: \(\mathrm{CROT}(\pi/2, 0)\).

  • cx — the controlled NOT: \(I \otimes |0\rangle\langle 0| + X \otimes |1\rangle\langle 1|\).

  • crx — the controlled \(X\) rotation: \(I \otimes |0\rangle\langle 0| + R_x(\theta) \otimes |1\rangle\langle 1|\).

Rotations of the synthesis

The synthesis searches with two general rotations. Neither is native. The compiler writes each one in the gate set’s single-qubit gates, as Layers and levels shows.

  • U3 — any single-qubit rotation, up to a global phase: \(U3(\theta, \phi, \lambda) = R_z(\phi)\, R_y(\theta)\, R_z(\lambda)\).

  • U2 — a U3 with \(\theta\) fixed at \(\pi/2\): \(U2(\phi, \lambda) = U3(\pi/2, \phi, \lambda)\).

Star topology

QUASAR targets devices where a single central qubit, qubit 0, is directly connected to every outer qubit. The outer qubits are not connected to each other. StarTopologyGateSet captures this connectivity. Its topology holds the directed edges ((0, 1), (0, 2), …, (0, n-1)). Every concrete gate set in QUASAR derives from it.

A topology entry is a placement: the qubits one native entangler acts on, in the order the gate is applied to them. The length of a placement is the entangler’s arity.

Two properties set these devices apart from all-to-all or nearest-neighbor hardware. QUASAR is designed around them:

  • Native gate asymmetry — different physical mechanisms drive the central and the outer qubits: microwaves and radio frequency. Their native operations and costs differ.

  • Pulse-level scheduling — dynamical-decoupling and RF sequences realize the high-fidelity two-qubit interactions. Gates on different edges therefore cannot run in parallel.

The built-in gate sets

QUASAR ships four gate sets:

  • CROTGateSet — rz, r and crot.

    • Pulse-level description: SimphonyBackend.

  • FixedCROTGateSet — rz, sx and crotxpi2. crotxpi2 is the fixed entangler the hardware implements.

    • Pulse-level description: SimphonyBackend (a Simphony digital twin) and XeedQBackend (real hardware).

    • Each rotation of the synthesis may be a U3, which takes two sx, or a U2, which takes one. control_rotation_ansatz, target_rotation_ansatz and closing_rotation_ansatz choose.

  • CNOTGateSet — rz, rx and cx. Most quantum-computing tooling already assumes cx as native.

    • Gate-level only: no pulse backend.

  • CRXGateSet — rz, rx and crx. Its default angle is \(\pi\). At that angle, cx and cz decompose into a single crx exactly.

    • Gate-level only: no pulse backend.

Every built-in gate set takes spam_qubits, the qubits the device resets and reads out together. The default (0,) is the central qubit alone. Targets describes both.

The custom two-qubit rotations are Qiskit gate objects: CROTGate, CROTXGate (free-angle), CROTXPi2Gate (the fixed hardware gate) and CROTYGate.

Gate properties

A gate set can carry what each native gate costs on the device:

  • error — the average gate infidelity on the gate’s own qubits, as randomized benchmarking measures it;

  • duration — the time the gate takes, in one unit for the whole gate set;

  • reserved qubits — the qubits the gate keeps busy while it runs. Two gates can run at once only when their reserved qubits are disjoint.

A value is either uniform, one for every single-qubit gate and one for every entangler, or set per gate. A per-gate value can describe one worse edge, for example. A frame change (rz) costs nothing: no error, no time and no reserved qubit.

Errors and durations have no default. Reserved qubits have one. On CROTGateSet and FixedCROTGateSet, a gate on a nucleus also reserves the electron. Every gate there runs after the one before. On CNOTGateSet and CRXGateSet, a gate reserves its own qubits only.

The error-aware strategies and GateCompiler.estimate_errors read the errors. A search for the shortest qubit mapping reads the durations and the reserved qubits.

Gate-level compilation

GateCompiler takes a gate set and a CompilerConfig. The config selects one of two paths:

Targets

A compile has one of two targets:

  • unitary — GateCompiler.compile(circuit). The compiled circuit acts like the source on every input state.

  • state preparation — GateCompiler.compile_state(state). The compiled circuit only has to turn one start state into the target state.

    • It is usually far shorter than the unitary, so it reaches wider registers.

    • compile(circuit, initial_state=...) asks the same of a circuit run from a known state.

    • Only the two adaptive strategies compile a state.

A circuit can also ask the device to reset qubits and to read out a Pauli string.

Reset

A reset in a circuit sets a qubit to \(|0\rangle\). The device resets its spam_qubits together: a reset of any of them resets all of them. A qubit outside the group can get \(|0\rangle\) by a swap from a reset qubit. The compiled circuit keeps each reset, with a barrier on either side. A custom gate set may have no spam_qubits. Then every qubit resets alone, and a reset stays as it is.

Pauli readout

QUASAR models a population readout. Averaged over many runs, the device resolves the population of each basis state of its spam_qubits. These populations give \(Z\) on each of the spam_qubits, and every product of those. These are the device’s native readouts. A single-shot readout is not modeled.

A Pauli string lists qubit 0 last, as Qiskit does (Conventions). On three qubits, the default spam_qubits=(0,) has one native readout, \(IIZ\). With spam_qubits=(0, 1), there are three: \(IIZ\), \(IZI\) and \(IZZ\).

A circuit can end with a request for any Pauli string, such as \(IXY\). The compiler adds the Clifford rotation that turns the request into a native readout. It synthesizes this rotation together with the circuit, as one unitary. Several commuting Pauli strings can be read from the same readout, at most as many as there are spam_qubits.

The State Preparation and Pauli Readout tutorial shows all of these. The rest of this section describes the synthesis path.

Synthesis

Stages of a synthesis compile

  1. Structure search — grow the circuit and fit it, until the strategy stops.

  2. Compression — remove single layers the circuit does not need (adaptive strategies).

  3. Rotation rounding — set rotation angles near chosen values to these values.

  4. Lowering — write the result in native gates and clean it up.

Layers and levels

  • Layer — a rotation on each qubit of one placement, then the entangler on it. A circuit is built from layers, and its size is reported in layers.

  • Level — one layer on every placement: \(n - 1\) layers on a star of \(n\) qubits.

  • Closing layer — a rotation on every qubit, after the last entangler.

Each rotation is a U3 during the search. On FixedCROTGateSet, a rotation may be a U2 instead. The lowering then writes each rotation in the gate set’s single-qubit gates.

(a) A three-qubit star circuit with two layers, each a U3 on two qubits and an entangler, followed by a U3 on every qubit. (b) U3 written as Rz then R, U3 written as Rz, SX, Rz, SX, Rz, and U2 written as Rz, SX, Rz.

(a) One level on a three-qubit star, then the closing layer. \(E\) is the gate set’s entangler. A wire drawn across a gate does not take part in it. (b) A U3 or U2 rotation in the native single-qubit gates. A dashed box is a frame change.

Each candidate structure is fitted by a numerical optimizer, from several starting points. A fixed seed makes the compile reproducible.

The four synthesis strategies

configuration

grows by

stops when

returns

AdaptiveConfig

one level

the compilation error falls below tolerance_error

the first level that meets it, compressed

TreeSearchConfig

one layer, choosing its placement

the compilation error falls below tolerance_error

the first depth that meets it

ErrorAwareAdaptiveConfig

one level

the overall error stops improving

the level with the lowest overall error, compressed

ErrorAwareTreeSearchConfig

one layer, choosing its placement

the overall error stops improving

the depth with the lowest overall error

  • Adaptive strategies add the same sweep over every placement at each level. They cost one fit per starting point and level.

  • Tree-search strategies try the placement sequences at each depth and keep the best. They can find shorter circuits on an irregular topology, at many fits per depth.

  • A tolerance-driven strategy fails when it cannot meet tolerance_error within its depth bound. An error-aware strategy always returns its best circuit.

Choosing a strategy

goal

use

an exact circuit, fast

AdaptiveConfig

an exact circuit on an irregular topology

TreeSearchConfig

the best circuit for given gate errors

ErrorAwareAdaptiveConfig

the same, choosing each placement

ErrorAwareTreeSearchConfig (much slower)

a routed circuit, without synthesis

QiskitConfig

The Gate Compilers tutorial runs each strategy.

Qubit mapping

The compiled circuit may place logical qubits on other physical qubits. The mapping at its input and the mapping at its output may differ. The layout_method option of a synthesis config chooses how the mappings are found:

value

candidates

syntheses on a star of \(n\) qubits

"trivial" (the default)

logical qubit \(i\) on physical qubit \(i\) at both ends

\(1\)

"full_search"

every (input, output) pair

\((n!)^2\)

"greedy_search"

input first, then output

\(2\,n!\)

"symmetric_search"

every pair, as "full_search", in fewer syntheses

\(n \cdot n!\)

"diagonal_search"

pairs with the same mapping at both ends

\(n\)

  • "diagonal_search" leaves no net permutation to follow afterwards. On a star, it chooses which logical qubit sits on the centre.

  • An error-aware config ranks the candidates by the overall error.

  • AdaptiveConfig and TreeSearchConfig rank them by layout_objective: the entangler count (the default), the overall error, or the duration. The last two need the gate properties.

The compiled circuit records both mappings on .layout. Its gates act on the physical qubits. PulseCompiler turns them into pulses as they are, and issues a LayoutWarning when the layout is not the identity. The Qubit Layout tutorial compares the methods.

Shortening the circuit

Compression

The compress option of AdaptiveConfig and ErrorAwareAdaptiveConfig removes the layers the finished circuit does not need. It removes one layer at a time and refits the rest.

  • AdaptiveConfig keeps a removal while the circuit still meets tolerance_error.

  • ErrorAwareAdaptiveConfig keeps a removal when the overall error falls, even if the compilation error grows.

  • By default, the gate set decides. Every built-in gate set compresses.

Rotation rounding

The round_single_qubit_rotations option of every synthesis config names the angles a rotation may be rounded onto, such as \(0\) or \(\pi/2\). A rotation rounded onto \(0\) disappears.

  • By default, the gate set decides. FixedCROTGateSet rounds onto \(0\) and \(\pi/2\), every other built-in gate set onto \(0\).

  • A rounding is kept while the compilation error stays within tolerance_error. On ErrorAwareAdaptiveConfig, it is kept while the overall error does not rise.

  • () turns the rounding off, for a faster compile.

What a rounded rotation costs:

target

CROTGateSet

FixedCROTGateSet

CNOTGateSet, CRXGateSet

\(0\)

no r

no sx

no rx

\(\pi/2\)

one r

one sx instead of two

one rx

any other angle

one r

two sx

one rx

A target that saves no gate still fixes the rotation angle, for a device that runs some angles better than others.

Lowering to native gates

  • Each rotation is written in the gate set’s single-qubit gates, as panel (b) of the figure shows:

    • rz + r on CROTGateSet;

    • rz + sx on FixedCROTGateSet;

    • rz + rx on CNOTGateSet and CRXGateSet.

  • A rounded \(\pi\) rotation becomes the basis’s own \(X\).

  • Virtual rz phases are merged.

  • Consecutive single-qubit gates on one qubit are merged into one rotation.

  • None of these steps changes the circuit’s action.

Pulse-level compilation

For hardware execution, QUASAR lowers a native circuit to a control pulse sequence via PulseCompiler. A gate set is a gate-level description. Its pulse-level description lives in a pulse backend registered for it.

Pulse backends

Two backends exist:

  • SimphonyBackend — a simulator, effectively a digital twin of the device, driven by a simphony model.

    • Emits microwave, RF and DDRF pulses onto the model.

    • Available only when the simphony package is installed.

  • XeedQBackend — targets real XeedQ hardware. It emits pulse programs from the calibration the device reports.

    • to_xeedq_circuit writes a compiled circuit in the gate names of the XeedQ toolchain.

    • from_xeedq_circuit reads such a circuit back.

Each backend is bound to one device: SimphonyBackend(model) to a model, XeedQBackend.from_cloud_backend to the hardware it reads. PulseCompiler(gateset=..., backend=...) takes the gate set and the backend. PulseCompiler.compile returns the backend’s own pulse representation. A gate set without a registered backend is gate-level only.

A custom backend implements AbstractPulseBackend. register_pulse_backend registers it for a gate-set type.

Native gates as pulses

Each native gate becomes one pulse sequence, chosen by the gate and its qubits. Qubit 0 is the electron, every other qubit a nucleus.

native gate

acts on

pulses

rz

any qubit

none: a frame change

r, sx

the electron

one microwave pulse

r, sx

a nucleus

a DDRF sequence that rotates the nucleus whatever the electron’s state

crot, crotxpi2

the electron and a nucleus

a DDRF sequence that rotates the nucleus conditioned on the electron’s state

  • A DDRF sequence drives the nucleus with radio-frequency pulses between decoupling pulses on the electron.

  • The pulses follow the circuit’s gate order, one gate after another.

Pulse parameters

A backend reads every physical number from one PulseParameters object: frequencies, drive strengths and pulse durations.

  • XeedQBackend.from_cloud_backend reads them from the calibration the device reports.

  • SimphonyBackend(model) takes the frequencies from the model. The durations are choices, with defaults that SimphonyBackend lists.

Benchmarking

SimphonyBackend simulates its pulses, so PulseCompiler can compare them with the ideal gates. It gives the average gate fidelity of one native gate, of every native gate, or of a whole native circuit. One minus a gate’s fidelity is its error, which the gate set can carry (Gate properties). The error-aware strategies then compile for the simulated device. XeedQBackend does not simulate its pulses, so it cannot benchmark.

The Pulse Compilers tutorial runs these steps.