End-to-End Tutorial
QUASAR (QUantum Assembler for Star-topology ARchitectures) is a hardware-aware compiler for star-topology quantum devices. Its main target is the NV center: one central electron spin, coupled to several nuclear spins around it.
This notebook runs the whole pipeline once:
pick a native gate set;
compile a circuit onto it at the gate level, with two strategies;
lower the native circuit to hardware pulses, and check them in simulation.
The other tutorials go deeper into each step.
Setup
This notebook needs quasar, simphony for the pulse-level section, and the
plotting extra for the circuit diagrams:
pip install --find-links=wheels/ ".[simphony-backend,plot]"
%matplotlib inline
import quasar
from qiskit.circuit.random import random_circuit
1. Gate set
A GateSet describes the native gates of a device and its star connectivity. We use the
default CROTGateSet. Its native gates are the single-qubit rotations rz and r, and the
two-qubit controlled rotation crot on every star edge (0, target).
gateset = quasar.CROTGateSet(n_qubits=3)
gateset
CROTGateSet(name='crot', n_qubits=3)
The circuit
The running example is a random 3-qubit circuit with a fixed seed. Every compile below starts from it.
qc = random_circuit(num_qubits=3, depth=4, seed=4)
qc.draw(output="mpl");
2. Gate-level compiling
A GateCompiler binds a gate set and a configuration. The configuration picks the
compilation path. We compare two paths:
Qiskit’s transpiler is the baseline. It starts from the gates of the circuit and rewrites them into native gates.
Adaptive synthesis starts from the unitary of the circuit. It searches for a native circuit with the same unitary, whatever gates the original circuit uses.
Baseline: Qiskit routing
QiskitConfig routes and translates the circuit with Qiskit’s transpiler. Then it lowers
the result to native rotations.
config_qiskit = quasar.QiskitConfig(optimization_level=3, seed=1)
compiler_qiskit = quasar.GateCompiler(gateset, config=config_qiskit)
qc_qiskit = compiler_qiskit.compile(qc)
dict(qc_qiskit.count_ops())
{'rz': 43, 'r': 43, 'crot': 20}
The compiled circuit holds only the native gates rz, r and crot:
qc_qiskit.draw(output="mpl");
GateCompiler.compilation_error compares the compiled circuit with the original. It is one
minus the average gate fidelity, so 0 is a perfect match. It takes into account any qubit
relabeling the compiler chose:
compiler_qiskit.compilation_error(qc, qc_qiskit)
7.771561172376096e-16
Adaptive synthesis
The adaptive strategy adds entanglers until the compilation error falls below
tolerance_error. A small tolerance (here 1e-8) makes the compilation numerically
exact.
config_adaptive = quasar.AdaptiveConfig(tolerance_error=1e-8, seed=1)
compiler_adaptive = quasar.GateCompiler(gateset, config=config_adaptive)
qc_adaptive = compiler_adaptive.compile(qc)
dict(qc_adaptive.count_ops())
{'rz': 13, 'r': 13, 'crot': 5}
The synthesized circuit:
qc_adaptive.draw(output="mpl");
The tolerance is small, so the compilation error is close to 0:
compiler_adaptive.compilation_error(qc, qc_adaptive)
5.705247385634493e-11
Synthesis usually needs fewer native two-qubit gates than the Qiskit baseline. Compare
the crot counts:
{"qiskit": qc_qiskit.count_ops().get("crot", 0), "adaptive": qc_adaptive.count_ops().get("crot", 0)}
{'qiskit': 20, 'adaptive': 5}
QUASAR has five configurations: the Qiskit path and four synthesis strategies. This notebook uses two of them. In short:
Tree search also works to a
tolerance_error. It tries more placement orders per depth, so it can find a shorter circuit, but it takes longer.The error-aware strategies, one for adaptive synthesis and one for tree search, pick the depth with the smallest overall error. The overall error combines the compilation error with the error the gates of the device add.
On any synthesis strategy, a looser
tolerance_errortrades accuracy for fewer gates.
The Gate Compilers tutorial compares them. A layout search can lower the count further (see the Qubit Layout tutorial).
3. Pulse-level compiling
A native circuit can be lowered further, to hardware control pulses. This works for any
gate set with a registered pulse backend. CROTGateSet lowers to pulses on a Simphony
device model, which simulates them. FixedCROTGateSet lowers to a Simphony model too, and
also to XeedQ hardware (see the Pulse Compilers tutorial).
The device model
We use a default NV-center model with three spins: the electron spin of the NV center, its
nitrogen-14 nuclear spin, and one nearby carbon-13 nuclear spin. They form a 3-qubit
register, and the electron is the central qubit 0. The model also holds the driving
fields that pulse compilation needs.
import simphony
model = simphony.default_nv_model(
nitrogen_isotope=14,
static_field_strength=0.05,
carbon_atom_indices=[(2, -2, -1, 1)],
)
model
Model(spin_names=['e', 'N', 'C'], num_static_fields=1, driving_field_names=['MW_x', 'RF_x'], dimension=18)
A PulseCompiler binds the gate set to a pulse backend, here a SimphonyBackend on the
model. It lowers a native circuit to pulses. We pulse-compile the synthesized circuit from
above:
backend = quasar.SimphonyBackend(model=model)
pulse_compiler = quasar.PulseCompiler(gateset=gateset, backend=backend)
pulse_compiler.compile(qc_adaptive)
PulseList(n_pulses=114)
The model now holds the pulses of this circuit. Their microwave and RF envelopes:
model.plot_driving_fields(function="complex_envelope")
Finally, benchmark_circuit simulates the whole pulse sequence and compares it with the
ideal circuit. It returns the average gate fidelity of the pulses:
pulse_compiler.benchmark_circuit(qc_adaptive)
0.9586046567475894
The fidelity is about 96 %. Each native gate has a small error on this device model, and over the whole circuit the errors add up. The Pulse Compilers tutorial measures the error of each gate, and lets the gate-level compiler take it into account.
Where to go next
Gate Sets tutorial — the native gate sets, and the gate errors and durations they carry.
Gate Compilers tutorial — every compilation strategy, and which one to choose.
Pulse Compilers tutorial — pulse-level compiling and benchmarking, including the hardware-only
XeedQBackend.Qubit Layout tutorial — letting the compiler choose which qubit sits where.
State Preparation and Pauli Readout tutorial — compiling a state preparation, a readout and a reset.
The API reference for the full surface.