Skip to content

Getting Started

This page covers installing Exaqt and driving the low-level ExaqtSV state vector directly. To run mimiqcircuits.Circuit objects and collect QCSResults, see Circuit Execution.

Requirements

  • Python 3.10 or later
  • mimiqcircuits 0.26.2 or later and NumPy 1.26 or later — both installed automatically with the wheel

Installation

Exaqt is distributed as mimiq-exaqt and imported as exaqt:

pip install mimiq-exaqt

Pre-built wheels are published for Linux (x86-64), macOS (Apple silicon), and Windows (x86-64). One abi3 wheel per platform covers Python 3.10 and every later version, so no compiler or Rust toolchain is needed. On any other platform, see Other platforms.

Verify the install:

import exaqt

print(exaqt.__version__)
print(exaqt.ExaqtSV.zero(2).amplitudes())   # [1+0j, 0j, 0j, 0j]

Other platforms

Need a platform without a published wheel — Linux aarch64 or Intel macOS, for example? Contact QPerfect.

Reading the documentation offline

The wheel ships this documentation site inside it, so the version you read always matches the version you installed:

exaqt docs                  # open the bundled site in a browser
exaqt docs --print-path     # or print the path to its index.html

exaqt.docs_dir() returns the same directory from Python. The online copy lives at https://docs.qperfect.io/exaqt-python/.

The state vector

ExaqtSV is the dense quantum register. Amplitudes are stored in little-endian order: index i of the amplitude array is the basis state whose bit k is set when qubit k is |1>. Qubit 0 is therefore the least-significant bit.

import numpy as np
from exaqt import ExaqtSV

# Allocate |000> — three qubits, 2**3 = 8 amplitudes.
sv = ExaqtSV.zero(3)
print(sv.num_qubits)            # 3
print(len(sv))                  # 8

# Build a GHZ state: H on qubit 0, then fan out with CX.
sv.apply_h(0)
sv.apply_cx(0, 1)
sv.apply_cx(0, 2)

# Read the full amplitude vector (numpy complex128, length 2**3).
amps = sv.amplitudes()
print(amps[0], amps[7])         # (0.707..+0j) (0.707..+0j) — |000> and |111>

# Probability of a single basis state, without materialising the vector.
print(sv.probability(0))        # 0.5
print(sv.probability(7))        # 0.5

Gates

Common gates have dedicated methods; the target qubit is always the last positional argument, and rotation angles come first.

import math
from exaqt import ExaqtSV

sv = ExaqtSV.zero(2)

# Non-parametric single-qubit gates.
sv.apply_x(0)                   # Pauli-X on qubit 0
sv.apply_h(1)                   # Hadamard on qubit 1

# Parametric single-qubit gates: angle(s) first, target last.
sv.apply_rx(math.pi / 2, 0)     # RX(pi/2) on qubit 0
sv.apply_u(math.pi, 0.0, math.pi, 1)   # U(theta, phi, lambda) on qubit 1

# Two-qubit gates.
sv.apply_cx(0, 1)               # control 0, target 1
sv.apply_rzz(math.pi / 4, 0, 1) # RZZ(pi/4) on qubits 0 and 1

For any unitary not covered by a named method, pass the matrix as a numpy complex128 array:

import numpy as np
from exaqt import ExaqtSV

sv = ExaqtSV.zero(1)

# Hadamard as an explicit 2x2 matrix.
h = np.array([[1, 1], [1, -1]], dtype=np.complex128) / np.sqrt(2)
sv.apply_gate_1q(h, 0)

# A 4x4 array goes to apply_gate_2q(gate, q1, q2).

Expectation values

Compute <psi|O|psi> without mutating the state. Pass 1- or 2-qubit operators as numpy matrices, or a Pauli string spanning any number of qubits.

import numpy as np
from exaqt import ExaqtSV

sv = ExaqtSV.zero(3)
sv.apply_h(0)
sv.apply_cx(0, 1)
sv.apply_cx(0, 2)               # GHZ state

# Pauli-string expectation — no need to build a 2**k x 2**k matrix.
# XXX is a GHZ stabiliser, so its expectation is +1.
print(sv.expectation_pauli("XXX", [0, 1, 2]))   # (1+0j)

# 1-qubit operator expectation from a matrix.
z = np.array([[1, 0], [0, -1]], dtype=np.complex128)
print(sv.expectation_1q(z, 0))                   # (0+0j) — <Z> = 0 on qubit 0

Measurement and sampling

Sampling and measurement draw from a seeded Rng. The same seed always reproduces the same stream.

from exaqt import ExaqtSV, Rng

sv = ExaqtSV.zero(2)
sv.apply_h(0)
sv.apply_cx(0, 1)               # Bell state

rng = Rng(seed=42)

# Draw 1000 shots at once — shape (1000, 2), dtype uint8, qubit 0 first.
shots = sv.sample(rng, nsamples=1000)
print(shots.shape)              # (1000, 2)

# A single destructive measurement of one qubit (collapses the state).
outcome = sv.measure_qubit(0, rng)
print(outcome)                  # 0 or 1

Errors

The wrapper raises subclasses of ExaqtError:

  • GateShapeError — a gate matrix has the wrong shape or layout.
  • QubitIndexError — a qubit index is out of bounds, or duplicated.
  • DegenerateStateError — sampling a state with zero / non-finite norm.

A MemoryError is raised (instead of aborting the process) for state-vector allocations that will not fit — for example ExaqtSV.zero(40).

Next steps