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.
@@ -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
+ [![CI](https://github.com/UmarGit/mlx_quantum/actions/workflows/ci.yml/badge.svg)](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
+ | | ![accuracy](benchmarks/accuracy_vs_qubits.png) | ![error distribution](benchmarks/error_distribution.png) |
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
+ | | ![speedup](benchmarks/speedup_vs_qubits.png) | ![wall time](benchmarks/walltime_vs_qubits.png) |
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
+ ![training overlay](benchmarks/training_overlay.png)
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
+ [![CI](https://github.com/UmarGit/mlx_quantum/actions/workflows/ci.yml/badge.svg)](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
+ | | ![accuracy](benchmarks/accuracy_vs_qubits.png) | ![error distribution](benchmarks/error_distribution.png) |
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
+ | | ![speedup](benchmarks/speedup_vs_qubits.png) | ![wall time](benchmarks/walltime_vs_qubits.png) |
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
+ ![training overlay](benchmarks/training_overlay.png)
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.