mlx-quantum 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mlx_quantum-0.1.0/.gitignore +18 -0
- mlx_quantum-0.1.0/CHANGELOG.md +41 -0
- mlx_quantum-0.1.0/LICENSE +21 -0
- mlx_quantum-0.1.0/PKG-INFO +173 -0
- mlx_quantum-0.1.0/README.md +154 -0
- mlx_quantum-0.1.0/benchmarks/README.md +37 -0
- mlx_quantum-0.1.0/benchmarks/accuracy_vs_qubits.png +0 -0
- mlx_quantum-0.1.0/benchmarks/error_distribution.png +0 -0
- mlx_quantum-0.1.0/benchmarks/speedup_vs_qubits.png +0 -0
- mlx_quantum-0.1.0/benchmarks/training_overlay.png +0 -0
- mlx_quantum-0.1.0/benchmarks/validate.py +322 -0
- mlx_quantum-0.1.0/benchmarks/walltime_vs_qubits.png +0 -0
- mlx_quantum-0.1.0/examples/benchmark_vs_qiskit.py +103 -0
- mlx_quantum-0.1.0/examples/simple_mlp.py +53 -0
- mlx_quantum-0.1.0/mlx_quantum/__init__.py +38 -0
- mlx_quantum-0.1.0/mlx_quantum/layer.py +50 -0
- mlx_quantum-0.1.0/mlx_quantum/py.typed +0 -0
- mlx_quantum-0.1.0/mlx_quantum/statevector.py +114 -0
- mlx_quantum-0.1.0/pyproject.toml +59 -0
- mlx_quantum-0.1.0/tests/_circuits.py +86 -0
- mlx_quantum-0.1.0/tests/test_convention.py +52 -0
- mlx_quantum-0.1.0/tests/test_layer.py +78 -0
- mlx_quantum-0.1.0/tests/test_properties.py +61 -0
- mlx_quantum-0.1.0/tests/test_statevector.py +34 -0
- mlx_quantum-0.1.0/tests/test_vs_qiskit.py +101 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# macOS
|
|
13
|
+
.DS_Store
|
|
14
|
+
|
|
15
|
+
# Generated example output (transient)
|
|
16
|
+
examples/*.png
|
|
17
|
+
benchmarks/run.log
|
|
18
|
+
.pytest_cache/
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-07-05
|
|
10
|
+
|
|
11
|
+
Initial release: a native, GPU-accelerated, differentiable quantum-machine-learning
|
|
12
|
+
library in pure Apple MLX.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `mlx_quantum.statevector` — batched complex statevector simulation with gates
|
|
16
|
+
applied as `einsum` contractions: `zero_state`, `apply_1q`, `apply_2q`,
|
|
17
|
+
`expval_z`, `expval_all_z`, and gates `H, X, Y, Z, rx, ry, rz, CX, CZ`. Qubit
|
|
18
|
+
ordering is little-endian, matching Qiskit's amplitude layout exactly.
|
|
19
|
+
- `QuantumLayer` — a trainable `mlx.nn.Module` running a hardware-efficient
|
|
20
|
+
ansatz; its weights train by ordinary MLX autodiff, no custom vjp.
|
|
21
|
+
- Examples: `simple_mlp.py` (hybrid classical-quantum MLP) and
|
|
22
|
+
`benchmark_vs_qiskit.py` (accuracy + speed vs Qiskit's `EstimatorQNN`).
|
|
23
|
+
- Validation suite (`benchmarks/validate.py`) producing five scope-labeled graphs:
|
|
24
|
+
accuracy vs qubits, two-baseline speedup (vs `EstimatorQNN` and vs Aer),
|
|
25
|
+
wall-time to the memory cliff, error distribution over a random-circuit sweep,
|
|
26
|
+
and a training-curve overlay.
|
|
27
|
+
- Tests covering statevector/gate correctness, gate unitarity, state-norm
|
|
28
|
+
preservation, the little-endian convention, layer training, a finite-difference
|
|
29
|
+
gradient check, and forward + weight-gradient + input-gradient parity with
|
|
30
|
+
Qiskit across a random gate sweep (shared engine in `tests/_circuits.py`;
|
|
31
|
+
Qiskit-dependent tests auto-skip when Qiskit is absent).
|
|
32
|
+
|
|
33
|
+
### Notes
|
|
34
|
+
- Runtime dependencies are MLX and NumPy only; Qiskit, Aer, and matplotlib are
|
|
35
|
+
optional (`[examples]` extra), used for validation and benchmarking.
|
|
36
|
+
- Forward values and gradients match Qiskit to ~1e-6 (noiseless statevector,
|
|
37
|
+
float32); ~100–400× faster end-to-end than driving `EstimatorQNN` from Python,
|
|
38
|
+
and ~1.7–3× faster than Aer's compiled statevector on the forward pass.
|
|
39
|
+
|
|
40
|
+
[Unreleased]: https://github.com/UmarGit/mlx_quantum/compare/v0.1.0...HEAD
|
|
41
|
+
[0.1.0]: https://github.com/UmarGit/mlx_quantum/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Umar Ahmed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mlx-quantum
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast, differentiable quantum-machine-learning in pure Apple MLX — GPU statevector simulation with native autodiff.
|
|
5
|
+
Project-URL: Repository, https://github.com/UmarGit/mlx_quantum
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: apple-silicon,differentiable,mlx,quantum,quantum-machine-learning
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Requires-Dist: mlx>=0.31.2
|
|
11
|
+
Requires-Dist: numpy>=2.5.1
|
|
12
|
+
Provides-Extra: examples
|
|
13
|
+
Requires-Dist: matplotlib>=3.9; extra == 'examples'
|
|
14
|
+
Requires-Dist: qiskit-aer>=0.17.2; extra == 'examples'
|
|
15
|
+
Requires-Dist: qiskit-algorithms>=0.4.0; extra == 'examples'
|
|
16
|
+
Requires-Dist: qiskit-machine-learning>=0.9.0; extra == 'examples'
|
|
17
|
+
Requires-Dist: qiskit>=2.5.0; extra == 'examples'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# mlx-quantum
|
|
21
|
+
|
|
22
|
+
[](https://github.com/UmarGit/mlx_quantum/actions/workflows/ci.yml)
|
|
23
|
+
|
|
24
|
+
Fast, differentiable quantum machine learning in pure [Apple MLX](https://github.com/ml-explore/mlx).
|
|
25
|
+
|
|
26
|
+
Statevector simulation runs on the Metal GPU and is differentiable end-to-end
|
|
27
|
+
through MLX autodiff — so a quantum layer trains like any other `mlx.nn` module,
|
|
28
|
+
with no custom gradient code. Forward values **and** gradients match Qiskit to
|
|
29
|
+
~1e-6 (float32), and it is **~100–400× faster** end-to-end than driving Qiskit's
|
|
30
|
+
`EstimatorQNN` from Python. See [Validation](#validation) for the evidence.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv add mlx-quantum
|
|
36
|
+
# or
|
|
37
|
+
pip install mlx-quantum
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires Python ≥ 3.13 and Apple Silicon. The library depends only on MLX and
|
|
41
|
+
NumPy; Qiskit is optional and used solely to cross-validate/benchmark.
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
`QuantumLayer` is a trainable `mlx.nn.Module`. Drop it into a model and train:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import mlx.core as mx
|
|
49
|
+
import mlx.nn as nn
|
|
50
|
+
from mlx_quantum import QuantumLayer
|
|
51
|
+
|
|
52
|
+
class HybridMLP(nn.Module):
|
|
53
|
+
def __init__(self):
|
|
54
|
+
super().__init__()
|
|
55
|
+
self.pre = nn.Linear(8, 4)
|
|
56
|
+
self.qnn = QuantumLayer(num_qubits=4, reps=2) # trainable quantum layer
|
|
57
|
+
self.post = nn.Linear(4, 3)
|
|
58
|
+
|
|
59
|
+
def __call__(self, x):
|
|
60
|
+
x = mx.tanh(self.pre(x)) * mx.pi # encode into rotation angles
|
|
61
|
+
return self.post(self.qnn(x))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The quantum weights are ordinary MLX parameters — `nn.value_and_grad` and any
|
|
65
|
+
optimizer update them automatically:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
loss_and_grad = nn.value_and_grad(model, loss_fn)
|
|
69
|
+
loss, grads = loss_and_grad(model, x, y)
|
|
70
|
+
optimizer.update(model, grads)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Building custom circuits
|
|
74
|
+
|
|
75
|
+
`QuantumLayer` runs a hardware-efficient ansatz, but the simulator primitives are
|
|
76
|
+
public — build any circuit as a plain differentiable function:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import mlx.core as mx
|
|
80
|
+
from mlx_quantum import zero_state, apply_1q, apply_2q, expval_all_z, H, ry, CX
|
|
81
|
+
|
|
82
|
+
def circuit(x, weights): # x: (batch, n) angles, weights: (n,)
|
|
83
|
+
n = x.shape[1]
|
|
84
|
+
state = zero_state(x.shape[0], n)
|
|
85
|
+
for q in range(n):
|
|
86
|
+
state = apply_1q(state, H, q)
|
|
87
|
+
for q in range(n):
|
|
88
|
+
state = apply_1q(state, ry(x[:, q]), q) # per-sample encoding
|
|
89
|
+
for q in range(n):
|
|
90
|
+
state = apply_1q(state, ry(weights[q]), q) # trainable
|
|
91
|
+
for q in range(n - 1):
|
|
92
|
+
state = apply_2q(state, CX, q, q + 1)
|
|
93
|
+
return expval_all_z(state) # <Z> per qubit, shape (batch, n)
|
|
94
|
+
|
|
95
|
+
grads = mx.grad(lambda w: mx.sum(circuit(x, w)))(weights) # just works
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Gates provided: `H, X, Y, Z, rx, ry, rz, CX, CZ`. Add your own — a single-qubit
|
|
99
|
+
gate is any `(2, 2)` complex `mx.array`; a two-qubit gate is a `(2, 2, 2, 2)`
|
|
100
|
+
tensor `[out0, out1, in0, in1]`.
|
|
101
|
+
|
|
102
|
+
## How it works
|
|
103
|
+
|
|
104
|
+
A statevector is a complex `mx.array` of shape `(batch,) + (2,) * num_qubits`;
|
|
105
|
+
qubit ordering is little-endian to match Qiskit exactly (qubit 0 is the fastest
|
|
106
|
+
index, so flattening reproduces Qiskit's amplitude order). Gates are
|
|
107
|
+
applied as `einsum` contractions, so the entire simulation is differentiable and
|
|
108
|
+
GPU-resident. Because there is no custom `vjp` and no NumPy round-trip, `mx.grad`
|
|
109
|
+
differentiates the circuit directly — including through complex amplitudes.
|
|
110
|
+
|
|
111
|
+
Two MLX specifics the implementation works around: the initial state is built as a
|
|
112
|
+
constant (not an in-place assignment, which compiles to an unsupported complex
|
|
113
|
+
GPU scatter), and gates are contractions rather than `take`/gather (whose backward
|
|
114
|
+
is also a scatter).
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
uv run python examples/simple_mlp.py # hybrid MLP training
|
|
120
|
+
uv run --extra examples python examples/benchmark_vs_qiskit.py # quick speed + accuracy vs Qiskit
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Validation
|
|
124
|
+
|
|
125
|
+
Two tracks — is it correct, and is the speed claim fair? All measurements are
|
|
126
|
+
**noiseless statevector, float32**. Regenerate with
|
|
127
|
+
`uv run --extra examples python benchmarks/validate.py` (details in
|
|
128
|
+
[`benchmarks/`](benchmarks/)).
|
|
129
|
+
|
|
130
|
+
**Correctness.** Forward values and gradients are compared against Qiskit
|
|
131
|
+
(`Statevector` and `ReverseEstimatorGradient`) over 142 random circuits covering
|
|
132
|
+
every gate (`H, X, Y, Z, rx, ry, rz, CX, CZ`); per-circuit error stays at ~1e-6.
|
|
133
|
+
(The *batch-summed* gradient error on the accuracy plot climbs to ~1e-5 by 8
|
|
134
|
+
qubits — that is float32 accumulation from summing 128 terms into one number,
|
|
135
|
+
still ≥5 significant figures, not a modelling error.) Gates are checked for
|
|
136
|
+
unitarity, the state norm is checked after every layer, and an asymmetric circuit
|
|
137
|
+
pins the little-endian qubit order to Qiskit's.
|
|
138
|
+
|
|
139
|
+
| |  |  |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
|
|
142
|
+
**Performance.** Two honest baselines. End-to-end vs `EstimatorQNN` driven from
|
|
143
|
+
Python (~100–400×), and kernel-level vs Aer's compiled statevector estimator
|
|
144
|
+
(~1.7–3×, forward only) — so the win is not just deleted orchestration. Wall-time
|
|
145
|
+
is shown until MLX hits the memory cliff (~22–26 qubits, single statevector).
|
|
146
|
+
|
|
147
|
+
| |  |  |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
|
|
150
|
+
**Trains identically.** Same circuit, init, data, and optimizer (SGD): the MLX
|
|
151
|
+
layer (`mx.grad`) and the Qiskit QNN (`qnn.backward`) produce the same loss curve
|
|
152
|
+
to ~1e-7 — same training, just faster.
|
|
153
|
+
|
|
154
|
+

|
|
155
|
+
|
|
156
|
+
## Tests
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
uv run pytest
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Covers gate/statevector correctness, gate unitarity and norm preservation, the
|
|
163
|
+
little-endian convention, layer training, a finite-difference gradient check, and
|
|
164
|
+
forward + weight-gradient + input-gradient parity with Qiskit across a random gate
|
|
165
|
+
sweep (Qiskit-dependent tests skip automatically if Qiskit is absent).
|
|
166
|
+
|
|
167
|
+
## Changelog
|
|
168
|
+
|
|
169
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# mlx-quantum
|
|
2
|
+
|
|
3
|
+
[](https://github.com/UmarGit/mlx_quantum/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Fast, differentiable quantum machine learning in pure [Apple MLX](https://github.com/ml-explore/mlx).
|
|
6
|
+
|
|
7
|
+
Statevector simulation runs on the Metal GPU and is differentiable end-to-end
|
|
8
|
+
through MLX autodiff — so a quantum layer trains like any other `mlx.nn` module,
|
|
9
|
+
with no custom gradient code. Forward values **and** gradients match Qiskit to
|
|
10
|
+
~1e-6 (float32), and it is **~100–400× faster** end-to-end than driving Qiskit's
|
|
11
|
+
`EstimatorQNN` from Python. See [Validation](#validation) for the evidence.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv add mlx-quantum
|
|
17
|
+
# or
|
|
18
|
+
pip install mlx-quantum
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requires Python ≥ 3.13 and Apple Silicon. The library depends only on MLX and
|
|
22
|
+
NumPy; Qiskit is optional and used solely to cross-validate/benchmark.
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
`QuantumLayer` is a trainable `mlx.nn.Module`. Drop it into a model and train:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import mlx.core as mx
|
|
30
|
+
import mlx.nn as nn
|
|
31
|
+
from mlx_quantum import QuantumLayer
|
|
32
|
+
|
|
33
|
+
class HybridMLP(nn.Module):
|
|
34
|
+
def __init__(self):
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.pre = nn.Linear(8, 4)
|
|
37
|
+
self.qnn = QuantumLayer(num_qubits=4, reps=2) # trainable quantum layer
|
|
38
|
+
self.post = nn.Linear(4, 3)
|
|
39
|
+
|
|
40
|
+
def __call__(self, x):
|
|
41
|
+
x = mx.tanh(self.pre(x)) * mx.pi # encode into rotation angles
|
|
42
|
+
return self.post(self.qnn(x))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The quantum weights are ordinary MLX parameters — `nn.value_and_grad` and any
|
|
46
|
+
optimizer update them automatically:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
loss_and_grad = nn.value_and_grad(model, loss_fn)
|
|
50
|
+
loss, grads = loss_and_grad(model, x, y)
|
|
51
|
+
optimizer.update(model, grads)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Building custom circuits
|
|
55
|
+
|
|
56
|
+
`QuantumLayer` runs a hardware-efficient ansatz, but the simulator primitives are
|
|
57
|
+
public — build any circuit as a plain differentiable function:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import mlx.core as mx
|
|
61
|
+
from mlx_quantum import zero_state, apply_1q, apply_2q, expval_all_z, H, ry, CX
|
|
62
|
+
|
|
63
|
+
def circuit(x, weights): # x: (batch, n) angles, weights: (n,)
|
|
64
|
+
n = x.shape[1]
|
|
65
|
+
state = zero_state(x.shape[0], n)
|
|
66
|
+
for q in range(n):
|
|
67
|
+
state = apply_1q(state, H, q)
|
|
68
|
+
for q in range(n):
|
|
69
|
+
state = apply_1q(state, ry(x[:, q]), q) # per-sample encoding
|
|
70
|
+
for q in range(n):
|
|
71
|
+
state = apply_1q(state, ry(weights[q]), q) # trainable
|
|
72
|
+
for q in range(n - 1):
|
|
73
|
+
state = apply_2q(state, CX, q, q + 1)
|
|
74
|
+
return expval_all_z(state) # <Z> per qubit, shape (batch, n)
|
|
75
|
+
|
|
76
|
+
grads = mx.grad(lambda w: mx.sum(circuit(x, w)))(weights) # just works
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Gates provided: `H, X, Y, Z, rx, ry, rz, CX, CZ`. Add your own — a single-qubit
|
|
80
|
+
gate is any `(2, 2)` complex `mx.array`; a two-qubit gate is a `(2, 2, 2, 2)`
|
|
81
|
+
tensor `[out0, out1, in0, in1]`.
|
|
82
|
+
|
|
83
|
+
## How it works
|
|
84
|
+
|
|
85
|
+
A statevector is a complex `mx.array` of shape `(batch,) + (2,) * num_qubits`;
|
|
86
|
+
qubit ordering is little-endian to match Qiskit exactly (qubit 0 is the fastest
|
|
87
|
+
index, so flattening reproduces Qiskit's amplitude order). Gates are
|
|
88
|
+
applied as `einsum` contractions, so the entire simulation is differentiable and
|
|
89
|
+
GPU-resident. Because there is no custom `vjp` and no NumPy round-trip, `mx.grad`
|
|
90
|
+
differentiates the circuit directly — including through complex amplitudes.
|
|
91
|
+
|
|
92
|
+
Two MLX specifics the implementation works around: the initial state is built as a
|
|
93
|
+
constant (not an in-place assignment, which compiles to an unsupported complex
|
|
94
|
+
GPU scatter), and gates are contractions rather than `take`/gather (whose backward
|
|
95
|
+
is also a scatter).
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv run python examples/simple_mlp.py # hybrid MLP training
|
|
101
|
+
uv run --extra examples python examples/benchmark_vs_qiskit.py # quick speed + accuracy vs Qiskit
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Validation
|
|
105
|
+
|
|
106
|
+
Two tracks — is it correct, and is the speed claim fair? All measurements are
|
|
107
|
+
**noiseless statevector, float32**. Regenerate with
|
|
108
|
+
`uv run --extra examples python benchmarks/validate.py` (details in
|
|
109
|
+
[`benchmarks/`](benchmarks/)).
|
|
110
|
+
|
|
111
|
+
**Correctness.** Forward values and gradients are compared against Qiskit
|
|
112
|
+
(`Statevector` and `ReverseEstimatorGradient`) over 142 random circuits covering
|
|
113
|
+
every gate (`H, X, Y, Z, rx, ry, rz, CX, CZ`); per-circuit error stays at ~1e-6.
|
|
114
|
+
(The *batch-summed* gradient error on the accuracy plot climbs to ~1e-5 by 8
|
|
115
|
+
qubits — that is float32 accumulation from summing 128 terms into one number,
|
|
116
|
+
still ≥5 significant figures, not a modelling error.) Gates are checked for
|
|
117
|
+
unitarity, the state norm is checked after every layer, and an asymmetric circuit
|
|
118
|
+
pins the little-endian qubit order to Qiskit's.
|
|
119
|
+
|
|
120
|
+
| |  |  |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
|
|
123
|
+
**Performance.** Two honest baselines. End-to-end vs `EstimatorQNN` driven from
|
|
124
|
+
Python (~100–400×), and kernel-level vs Aer's compiled statevector estimator
|
|
125
|
+
(~1.7–3×, forward only) — so the win is not just deleted orchestration. Wall-time
|
|
126
|
+
is shown until MLX hits the memory cliff (~22–26 qubits, single statevector).
|
|
127
|
+
|
|
128
|
+
| |  |  |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
|
|
131
|
+
**Trains identically.** Same circuit, init, data, and optimizer (SGD): the MLX
|
|
132
|
+
layer (`mx.grad`) and the Qiskit QNN (`qnn.backward`) produce the same loss curve
|
|
133
|
+
to ~1e-7 — same training, just faster.
|
|
134
|
+
|
|
135
|
+

|
|
136
|
+
|
|
137
|
+
## Tests
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
uv run pytest
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Covers gate/statevector correctness, gate unitarity and norm preservation, the
|
|
144
|
+
little-endian convention, layer training, a finite-difference gradient check, and
|
|
145
|
+
forward + weight-gradient + input-gradient parity with Qiskit across a random gate
|
|
146
|
+
sweep (Qiskit-dependent tests skip automatically if Qiskit is absent).
|
|
147
|
+
|
|
148
|
+
## Changelog
|
|
149
|
+
|
|
150
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Validation & benchmarks
|
|
2
|
+
|
|
3
|
+
Correctness and performance evidence for `mlx-quantum`. Scope for every
|
|
4
|
+
measurement: **noiseless statevector, float32**.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
uv run --extra examples python benchmarks/validate.py
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Regenerates the five graphs in this directory. Requires the dev/`examples`
|
|
11
|
+
dependencies (Qiskit, qiskit-machine-learning, qiskit-algorithms, qiskit-aer,
|
|
12
|
+
matplotlib).
|
|
13
|
+
|
|
14
|
+
## What each graph shows
|
|
15
|
+
|
|
16
|
+
| File | Question it answers |
|
|
17
|
+
|------|---------------------|
|
|
18
|
+
| `accuracy_vs_qubits.png` | Forward error vs Qiskit stays at ~1e-6. The batch-summed gradient error rises to ~1e-5 by 8 qubits — pure float32 accumulation (16×8 terms summed into one number), still ≥5 significant figures; the per-circuit gradient error stays ~1e-6 (see the distribution). |
|
|
19
|
+
| `speedup_vs_qubits.png` | Two honest baselines: end-to-end vs `EstimatorQNN` (forward+gradient), and kernel-level vs Aer's compiled statevector (forward only). |
|
|
20
|
+
| `walltime_vs_qubits.png` | Absolute wall-time, extended until MLX hits the memory cliff. EstimatorQNN is forward+grad; Aer and MLX are forward only. |
|
|
21
|
+
| `error_distribution.png` | Error histogram over a random-circuit sweep covering every gate (up to 150 draws; parameter-free draws are skipped, leaving n≈142) — the 1e-6 is not cherry-picked. |
|
|
22
|
+
| `training_overlay.png` | Same circuit/init/data/optimizer trains identically via `mx.grad` and via `qnn.backward`. |
|
|
23
|
+
|
|
24
|
+
## References used
|
|
25
|
+
|
|
26
|
+
- **Forward:** `qiskit.quantum_info.Statevector`.
|
|
27
|
+
- **Gradients:** `qiskit_algorithms.gradients.ReverseEstimatorGradient` (adjoint /
|
|
28
|
+
reverse-mode — the efficient, best-case Qiskit gradient, so the speedup claim
|
|
29
|
+
is conservative).
|
|
30
|
+
- **End-to-end baseline:** `qiskit_machine_learning.neural_networks.EstimatorQNN`
|
|
31
|
+
(`forward` + `backward` driven from Python).
|
|
32
|
+
- **Kernel baseline:** `qiskit_aer.primitives.EstimatorV2` (compiled statevector),
|
|
33
|
+
forward only — Aer does not expose a matching gradient, so the kernel line is a
|
|
34
|
+
forward-vs-forward comparison.
|
|
35
|
+
|
|
36
|
+
The gate-level parity engine (`tests/_circuits.py`) is shared with the test suite,
|
|
37
|
+
so the graphs and `pytest` check the same thing.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|