State-vector simulator#

A small batched state-vector simulator for parameterised circuits, in pure NumPy.

Qubit q is bit q of the basis-state index (Qiskit ordering). Gate angles are expressions: a number, W (trainable weight k), X (feature j of each sample, scaled and shifted), XX (a product feature) or a constant.

Circuits are simulated for a whole batch of samples at once, which makes training parameterised circuits fast without a quantum SDK. Gradients are exact and cheap (Circuit.value_and_grad(), adjoint method). Circuit.to_qiskit() produces the same circuit for Aer, IBM Quantum or Amazon Braket (via quantum_mind.circuits.run()).

Examples

>>> import numpy as np
>>> from quantum_mind.quantum import Circuit, W
>>> c = Circuit(2).h(0).cx(0, 1)                   # Bell state
>>> c.probabilities().round(3)
array([[0.5, 0. , 0. , 0.5]])
>>> c = Circuit(1).ry(W(0), 0)                     # one trainable rotation
>>> c.probabilities([np.pi]).round(3)
array([[0., 1.]])

Classes

W

Trainable weight as a gate angle.

X

Input feature as a gate angle.

XX

Product feature used by ZZ feature maps.

Circuit

Parameterised quantum circuit with a batched simulator.

Functions

apply_matrix

Apply a k-qubit matrix to a batch of states.

basis_probs

Born-rule probabilities.

expectation_z

Expectation of Pauli Z on one qubit.

Data

MAX_QUBITS

beyond this a state vector does not fit in typical memory (2^22 x 16 bytes per sample).

class W(k, scale=1.0)[source]#

Bases: object

Trainable weight as a gate angle.

Parameters:
  • k (int) – Index of the weight in the weight vector.

  • scale (float, optional) – Constant factor: the angle is scale * w[k].

class X(j, scale=1.0, shift=0.0)[source]#

Bases: object

Input feature as a gate angle.

Parameters:
  • j (int) – Feature index.

  • scale (float, optional) – Factor.

  • shift (float, optional) – Offset: the angle is scale * x[j] + shift.

class XX(i, j, a=3.141592653589793, scale=1.0)[source]#

Bases: object

Product feature used by ZZ feature maps.

Parameters:
  • i (int) – Feature indices.

  • j (int) – Feature indices.

  • a (float, optional) – Offset; the angle is scale * (a - x[i]) * (a - x[j]) (Havlíček et al., 2019).

  • scale (float, optional) – Factor.

class Circuit(n)[source]#

Bases: object

Parameterised quantum circuit with a batched simulator.

Build the circuit with the gate methods (each returns self, so calls chain) and simulate it with state() or probabilities().

Parameters:

n (int) – Number of qubits, at most MAX_QUBITS.

Variables:

ops (list of tuple) – Gates as (name, qubits, parameter).

Raises:

ValueError – If n exceeds MAX_QUBITS.

h(q)[source]#

Hadamard on qubit q.

x(q)[source]#

Pauli X on qubit q.

y(q)[source]#

Pauli Y on qubit q.

z(q)[source]#

Pauli Z on qubit q.

s(q)[source]#

S gate (phase pi/2) on qubit q.

sdg(q)[source]#

S-dagger gate on qubit q.

cx(c, t)[source]#

CNOT with control c and target t.

cz(a, b)[source]#

Controlled Z on qubits a and b.

swap(a, b)[source]#

Swap qubits a and b.

rx(p, q)[source]#

RX rotation on qubit q; p is a number, W, X or XX.

ry(p, q)[source]#

RY rotation on qubit q; p is a number or an angle expression.

rz(p, q)[source]#

RZ rotation on qubit q; p is a number or an angle expression.

p(p, q)[source]#

Phase gate diag(1, e^{ip}) on qubit q.

cp(p, c, t)[source]#

Controlled phase diag(1, 1, 1, e^{ip}) on qubits c and t (symmetric).

rzz(p, a, b)[source]#

RZZ(p) = exp(-i p Z_a Z_b / 2) on qubits a and b.

unitary(M, qubits, label='U')[source]#

Fixed unitary on some qubits.

Parameters:
  • M (array_like) – (2**k, 2**k) unitary.

  • qubits (sequence of int) – Qubits acted on (the first is the least significant bit of M’s index).

  • label (str, optional) – Label in the Qiskit export.

Returns:

Circuit – self.

diagonal(phases, label='D')[source]#

Diagonal unitary diag(exp(i phases)) on all qubits (for example a cost or oracle layer).

Parameters:
  • phases (array_like) – 2**n phases.

  • label (str, optional) – Label.

Returns:

Circuit – self.

property n_weights#

number of trainable weights (largest W index + 1).

Type:

int

compose(other)[source]#

Append the gates of another circuit.

Parameters:

other (Circuit) – Circuit on the same number of qubits.

Returns:

Circuit – self.

state(w=None, X_=None, init=None)[source]#

Simulate the circuit.

Parameters:
  • w (array_like, optional) – Weights, shape (K,) shared by the batch or (B, K) one per sample. Default zeros.

  • X (array_like, optional) – Input samples, shape (B, n_features).

  • init (array_like, optional) – Initial state (default \(|0\dots0\rangle\)).

Returns:

numpy.ndarray – Final states, shape (B, 2**n).

Raises:

ValueError – If fewer weights than n_weights are given.

value_and_grad(w, X_, loss, chunk=None)[source]#

Loss and its exact gradient with respect to the weights (adjoint method).

The circuit is run forward once, then backwards once with the inverse gates, so the cost is about three simulations whatever the number of weights. Samples are processed in chunks (memory O(chunk * 2**n)). Weights shared by several gates (for example a repeated rotation angle) are handled.

Parameters:
  • w (array_like) – Weights, shape (K,).

  • X (array_like or None) – Input samples, shape (B, n_features).

  • loss (callable) – loss(psi, rows) -> (L, phi): L is the loss summed over the samples rows of the chunk and phi = dL/d(conj psi), with the shape of psi.

  • chunk (int, optional) – Samples per chunk (default: about 2**21 amplitudes).

Returns:

probabilities(w=None, X_=None)[source]#

Born-rule probabilities of the final states.

Parameters:
  • w (array_like, optional) – Weights.

  • X (array_like, optional) – Input samples.

Returns:

numpy.ndarray – Shape (B, 2**n).

to_qiskit(w=None, x=None, measure=True)[source]#

Export to Qiskit with every angle bound.

Parameters:
  • w (array_like, optional) – Weights.

  • x (array_like, optional) – One input sample.

  • measure (bool, optional) – Add a final measurement of every qubit.

Returns:

qiskit.QuantumCircuit – Ready for quantum_mind.circuits.run() on Aer, IBM Quantum or Amazon Braket.

apply_matrix(state, M, qubits, n)[source]#

Apply a k-qubit matrix to a batch of states.

Parameters:
  • state (numpy.ndarray) – Batch of states, shape (B, 2**n).

  • M (numpy.ndarray) – (2**k, 2**k) matrix shared by the batch, or (B, 2**k, 2**k) one per sample. The first qubit in qubits is the least significant bit of M’s index.

  • qubits (sequence of int) – Qubits acted on.

  • n (int) – Total number of qubits.

Returns:

numpy.ndarray – New batch of states, shape (B, 2**n). Diagonal two-qubit gates use a fast phase path.

basis_probs(psi)[source]#

Born-rule probabilities.

Parameters:

psi (numpy.ndarray) – State or batch of states.

Returns:

numpy.ndarray – \(|\psi|^2\).

expectation_z(psi, q, n)[source]#

Expectation of Pauli Z on one qubit.

Parameters:
  • psi (numpy.ndarray) – Batch of states, shape (B, 2**n).

  • q (int) – Qubit.

  • n (int) – Number of qubits.

Returns:

numpy.ndarray – \(\langle Z_q \rangle\) for each state, shape (B,).

MAX_QUBITS#

beyond this a state vector does not fit in typical memory (2^22 x 16 bytes per sample).

Type:

Largest number of qubits