Quick Start
Overview
This chapter will guide you through getting started with the UnitaryLab simulator in under 5 minutes. By the end, you will be able to:
- Create a quantum circuit using
Circuit - Add basic quantum gates
- Run a simulation and read the statevector and probability distribution
- Add measurements and read classical bit results
Recommended Import
from unitarylab import Circuit, Register, ClassicalRegisterCircuit, Register, and ClassicalRegister are the three names exported directly from the top level of the unitarylab package (equivalently: from unitarylab.core import Circuit, Register, ClassicalRegister), and they are the high-level interfaces most commonly used by users — the vast majority of operations are performed through them. Low-level components such as GateSequence and CircuitExecutor do not need to be imported directly for typical usage.
Minimal Example: Bell State
The Bell state is a canonical example of quantum entanglement, constructed with a Hadamard gate and a CNOT gate.
from unitarylab import Circuit
# 1. Create a 2-qubit circuit
qc = Circuit(2)
# 2. Apply a Hadamard gate to qubit 0
qc.h(0)
# 3. Add a CNOT gate (control qubit 0, target qubit 1)
qc.cx(0, 1)
# 4. Run the simulation
result = qc.execute()
# 5. Inspect the statevector
print(result.state)Expected output:
[0.70710678+0.j 0. +0.j 0. +0.j 0.70710678+0.j]Viewing the Probability Distribution
probs = result.probabilities
print(probs)Expected output:
{'00': 0.4999999999999999, '11': 0.4999999999999999}probabilities returns a dictionary whose keys are binary strings of the computational basis states (little-endian, with the lowest bit corresponding to qubit 0) and whose values are the corresponding measurement probabilities. For the Bell state, |00⟩ and |11⟩ each account for approximately 50%.
Adding Measurements
Constructing Circuit(n) with an integer automatically creates a default quantum register Register('q', n) and a default classical register ClassicalRegister('c', n), so you can call measure() directly without explicitly creating a classical register:
from unitarylab import Circuit
qc = Circuit(2) # A 2-qubit register named 'q' and a 2-bit classical register named 'c' have already been created automatically
qc.h(0)
qc.cx(0, 1)
# Store the measurement results of qubits 0 and 1 into classical bits 0 and 1
qc.measure([0, 1], [0, 1])
result = qc.execute()
print(result.classical_results_map)You only need to explicitly construct Register/ClassicalRegister and pass them into Circuit when you need custom register naming, multiple registers, or a custom qubit/classical-bit layout:
from unitarylab import Circuit, Register, ClassicalRegister
qr = Register('q', 2)
cr = ClassicalRegister('c', 2)
qc = Circuit(qr, cr)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
result = qc.execute()
print(result.classical_results_map)Expected output (deterministic with the default seed=42):
{0: 1, 1: 1}classical_results_map is a mapping from classical bit index to measurement value (0 or 1). For the Bell state, the measurement result always has the two bits equal (00 or 11), demonstrating quantum entanglement. If you instead use qc.execute(seed=None) or a different seed, the result will vary randomly between {0: 0, 1: 0} and {0: 1, 1: 1} on each run.
Execution Parameters: shots, seed, backend_options
The full set of parameters for execute() is:
result = qc.execute(
initial_state=None, # Initial state; defaults to starting from |0...0⟩
backend='torch', # 'torch' (default) / 'numpy' / 'cpp' / 'tensornet'
device='cpu', # 'cpu' or 'gpu' (gpu is only supported when backend='torch')
dtype=np.complex128, # Complex-number precision, default complex128; automatically normalized per backend (e.g. integer/float types are promoted to the corresponding complex type)
shots=1, # Number of independent repeated executions
seed=42, # Random seed for measurement; None means the seed is not fixed
backend_options=None, # Backend-specific options (the tensornet backend accepts max_bond/cutoff/routing)
)- When
shotsis greater than 1,result.state/result.classical_results_mapare the results of the last execution, whileresult.countsaggregates the classical bit-string statistics over all shots (keys are strings, values are occurrence counts). seedindependently controls the randomness of measurements within the circuit; it defaults to a fixed42, so results are reproducible when not explicitly overridden. Passseed=Nonewhen you need truly random sampling.backend_optionscurrently only takes effect whenbackend='tensornet'; it controls the MPS maximum bond dimensionmax_bond, the truncation thresholdcutoff, and the two-qubit gate routing strategyrouting('auto'/'swap'/'mpo').
# Sample multiple times and tally the occurrence counts of classical bit strings
qc = Circuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
result = qc.execute(shots=1000, seed=None)
print(result.counts)
# {'00': 498, '11': 502} # exact values vary on each runChoosing an Execution Backend
execute() supports backend and device parameters to accommodate different computing environments:
# Use the PyTorch backend (default), running on CPU
result = qc.execute(backend='torch', device='cpu')
# Use the NumPy backend
result = qc.execute(backend='numpy', device='cpu')
# Use the compiled C++ backend (requires the compiled extension to be available)
result = qc.execute(backend='cpp', device='cpu')
# Use the tensor-network (MPS) backend, suitable for circuits with many qubits and low entanglement
result = qc.execute(backend='tensornet', device='cpu')
# If CUDA or Apple MPS is available, you can switch to GPU (only supported by the torch backend)
import torch
gpu_available = torch.cuda.is_available() or torch.backends.mps.is_available()
if gpu_available:
# Apple MPS does not support complex128, so complex64 is used explicitly for consistency
result = qc.execute(backend='torch', device='gpu', dtype=np.complex64)
else:
print('No GPU is available in the current environment, skipping the GPU example.')In general you can just call qc.execute() without specifying any parameters (defaulting to torch + cpu); when the qubit count is large but circuit entanglement is limited, you can try the tensornet backend; when a GPU is available, you can use device='gpu' for acceleration.
Drawing the Circuit
qc.draw()draw() pops up a Matplotlib circuit diagram. To save it to a file:
qc.draw(filename='bell.png', title='Bell State')Next Steps
- Learn the full set of gate operations and circuit transformation methods for
Circuit→ Core Circuit Interface - See all API module entry points → API Usage Overview