Skip to Content

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
from unitarylab import Circuit, Register, ClassicalRegister

Circuit, 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 shots is greater than 1, result.state / result.classical_results_map are the results of the last execution, while result.counts aggregates the classical bit-string statistics over all shots (keys are strings, values are occurrence counts).
  • seed independently controls the randomness of measurements within the circuit; it defaults to a fixed 42, so results are reproducible when not explicitly overridden. Pass seed=None when you need truly random sampling.
  • backend_options currently only takes effect when backend='tensornet'; it controls the MPS maximum bond dimension max_bond, the truncation threshold cutoff, and the two-qubit gate routing strategy routing ('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 run

Choosing 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

Last updated on