mlx-quantum 0.1.0__py3-none-any.whl
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/__init__.py +38 -0
- mlx_quantum/layer.py +50 -0
- mlx_quantum/py.typed +0 -0
- mlx_quantum/statevector.py +114 -0
- mlx_quantum-0.1.0.dist-info/METADATA +173 -0
- mlx_quantum-0.1.0.dist-info/RECORD +8 -0
- mlx_quantum-0.1.0.dist-info/WHEEL +4 -0
- mlx_quantum-0.1.0.dist-info/licenses/LICENSE +21 -0
mlx_quantum/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from . import statevector
|
|
2
|
+
from .statevector import (
|
|
3
|
+
zero_state,
|
|
4
|
+
apply_1q,
|
|
5
|
+
apply_2q,
|
|
6
|
+
expval_z,
|
|
7
|
+
expval_all_z,
|
|
8
|
+
rx,
|
|
9
|
+
ry,
|
|
10
|
+
rz,
|
|
11
|
+
H,
|
|
12
|
+
X,
|
|
13
|
+
Y,
|
|
14
|
+
Z,
|
|
15
|
+
CX,
|
|
16
|
+
CZ,
|
|
17
|
+
)
|
|
18
|
+
from .layer import QuantumLayer
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"QuantumLayer",
|
|
22
|
+
"statevector",
|
|
23
|
+
"zero_state",
|
|
24
|
+
"apply_1q",
|
|
25
|
+
"apply_2q",
|
|
26
|
+
"expval_z",
|
|
27
|
+
"expval_all_z",
|
|
28
|
+
"rx",
|
|
29
|
+
"ry",
|
|
30
|
+
"rz",
|
|
31
|
+
"H",
|
|
32
|
+
"X",
|
|
33
|
+
"Y",
|
|
34
|
+
"Z",
|
|
35
|
+
"CX",
|
|
36
|
+
"CZ",
|
|
37
|
+
]
|
|
38
|
+
__version__ = "0.1.0"
|
mlx_quantum/layer.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import mlx.core as mx
|
|
3
|
+
import mlx.nn as nn
|
|
4
|
+
|
|
5
|
+
from .statevector import zero_state, apply_1q, apply_2q, expval_all_z, H, ry, CX
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class QuantumLayer(nn.Module):
|
|
9
|
+
"""A trainable quantum layer that behaves like any other ``mlx.nn`` module.
|
|
10
|
+
|
|
11
|
+
Runs a hardware-efficient ansatz natively in MLX, so its weights train by
|
|
12
|
+
ordinary gradient descent on the Metal GPU — no Qiskit, no custom autodiff.
|
|
13
|
+
|
|
14
|
+
H on all qubits
|
|
15
|
+
RY(input[q]) on each qubit q (angle encoding)
|
|
16
|
+
reps x [ RY(weight) on each qubit; CX ring ]
|
|
17
|
+
|
|
18
|
+
Call with ``x`` of shape ``(num_qubits,)`` or ``(batch, num_qubits)``; returns
|
|
19
|
+
<Z> per qubit, shape ``(num_qubits,)`` or ``(batch, num_qubits)``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, num_qubits: int, reps: int = 1, initial_weights: np.ndarray | None = None):
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.num_qubits = num_qubits
|
|
25
|
+
self.reps = reps
|
|
26
|
+
if initial_weights is None:
|
|
27
|
+
initial_weights = np.random.uniform(-1, 1, reps * num_qubits)
|
|
28
|
+
self.weight = mx.array(np.asarray(initial_weights, np.float32))
|
|
29
|
+
|
|
30
|
+
def __call__(self, x: mx.array) -> mx.array:
|
|
31
|
+
was_1d = x.ndim == 1
|
|
32
|
+
if was_1d:
|
|
33
|
+
x = x[None]
|
|
34
|
+
|
|
35
|
+
n = self.num_qubits
|
|
36
|
+
state = zero_state(x.shape[0], n)
|
|
37
|
+
for q in range(n):
|
|
38
|
+
state = apply_1q(state, H, q)
|
|
39
|
+
for q in range(n):
|
|
40
|
+
state = apply_1q(state, ry(x[:, q]), q)
|
|
41
|
+
|
|
42
|
+
w = self.weight.reshape(self.reps, n)
|
|
43
|
+
for r in range(self.reps):
|
|
44
|
+
for q in range(n):
|
|
45
|
+
state = apply_1q(state, ry(w[r, q]), q)
|
|
46
|
+
for q in range(n - 1):
|
|
47
|
+
state = apply_2q(state, CX, q, q + 1)
|
|
48
|
+
|
|
49
|
+
out = expval_all_z(state)
|
|
50
|
+
return out[0] if was_1d else out
|
mlx_quantum/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Batched, differentiable statevector simulation in pure MLX.
|
|
2
|
+
|
|
3
|
+
A statevector is a complex ``mx.array`` of shape ``(batch,) + (2,) * num_qubits``.
|
|
4
|
+
Qubit ordering is little-endian to match Qiskit: qubit 0 is the fastest-varying
|
|
5
|
+
index, so flattening the state reproduces Qiskit's amplitude order exactly. Gates
|
|
6
|
+
are applied as ``einsum`` contractions, so the whole simulation runs on the Metal
|
|
7
|
+
GPU and is differentiable end to end via ``mx.grad`` — no custom vjp.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
import string
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import mlx.core as mx
|
|
15
|
+
|
|
16
|
+
_AXES = string.ascii_lowercase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def zero_state(batch: int, num_qubits: int) -> mx.array:
|
|
20
|
+
"""The |0...0> state, broadcast over a batch."""
|
|
21
|
+
amp = np.zeros((1,) + (2,) * num_qubits, np.complex64)
|
|
22
|
+
amp.flat[0] = 1.0
|
|
23
|
+
return mx.array(np.broadcast_to(amp, (batch,) + (2,) * num_qubits).copy())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _axis(qubit: int, n: int) -> int:
|
|
27
|
+
"""Little-endian: qubit 0 is the last axis (fastest index when flattened)."""
|
|
28
|
+
return n - 1 - qubit
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def apply_1q(state: mx.array, gate: mx.array, qubit: int) -> mx.array:
|
|
32
|
+
"""Apply a single-qubit gate. ``gate`` is ``(2, 2)`` or, per-sample, ``(batch, 2, 2)``."""
|
|
33
|
+
n = state.ndim - 1
|
|
34
|
+
axes = _AXES[:n]
|
|
35
|
+
q = _axis(qubit, n)
|
|
36
|
+
src, tgt = "B" + axes, "B" + axes[:q] + "Z" + axes[q + 1:]
|
|
37
|
+
if gate.ndim == 2:
|
|
38
|
+
return mx.einsum(f"{src},Z{axes[q]}->{tgt}", state, gate)
|
|
39
|
+
return mx.einsum(f"{src},BZ{axes[q]}->{tgt}", state, gate)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def apply_2q(state: mx.array, gate: mx.array, q0: int, q1: int) -> mx.array:
|
|
43
|
+
"""Apply a two-qubit gate given as a ``(2, 2, 2, 2)`` tensor ``[out0, out1, in0, in1]``."""
|
|
44
|
+
n = state.ndim - 1
|
|
45
|
+
axes = _AXES[:n]
|
|
46
|
+
a0, a1 = _axis(q0, n), _axis(q1, n)
|
|
47
|
+
tgt = list("B" + axes)
|
|
48
|
+
tgt[1 + a0], tgt[1 + a1] = "Y", "Z"
|
|
49
|
+
return mx.einsum(f"B{axes},YZ{axes[a0]}{axes[a1]}->{''.join(tgt)}", state, gate)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def expval_z(state: mx.array, qubit: int) -> mx.array:
|
|
53
|
+
"""<Z> on one qubit, shape ``(batch,)``."""
|
|
54
|
+
probs = mx.abs(state) ** 2
|
|
55
|
+
keep = _axis(qubit, state.ndim - 1)
|
|
56
|
+
axes = [1 + j for j in range(state.ndim - 1) if j != keep]
|
|
57
|
+
marg = mx.sum(probs, axis=axes) if axes else probs
|
|
58
|
+
return marg[:, 0] - marg[:, 1]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def expval_all_z(state: mx.array) -> mx.array:
|
|
62
|
+
"""<Z> on every qubit, shape ``(batch, num_qubits)``."""
|
|
63
|
+
return mx.stack([expval_z(state, q) for q in range(state.ndim - 1)], axis=1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _const(matrix) -> mx.array:
|
|
67
|
+
return mx.array(np.array(matrix, np.complex64))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
H = _const([[1, 1], [1, -1]]) / math.sqrt(2)
|
|
71
|
+
X = _const([[0, 1], [1, 0]])
|
|
72
|
+
Y = _const([[0, -1j], [1j, 0]])
|
|
73
|
+
Z = _const([[1, 0], [0, -1]])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _two_qubit(fn) -> mx.array:
|
|
77
|
+
g = np.zeros((2, 2, 2, 2), np.complex64)
|
|
78
|
+
for c in (0, 1):
|
|
79
|
+
for t in (0, 1):
|
|
80
|
+
oc, ot, val = fn(c, t)
|
|
81
|
+
g[oc, ot, c, t] = val
|
|
82
|
+
return mx.array(g)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
CX = _two_qubit(lambda c, t: (c, t ^ c, 1.0))
|
|
86
|
+
CZ = _two_qubit(lambda c, t: (c, t, -1.0 if c and t else 1.0))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def rx(theta: mx.array) -> mx.array:
|
|
90
|
+
c, s = mx.cos(theta / 2), mx.sin(theta / 2)
|
|
91
|
+
return _rot(c, -1j * s, -1j * s, c)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def ry(theta: mx.array) -> mx.array:
|
|
95
|
+
c, s = mx.cos(theta / 2), mx.sin(theta / 2)
|
|
96
|
+
return _rot(c, -s, s, c)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def rz(theta: mx.array) -> mx.array:
|
|
100
|
+
p = _expi(theta / 2)
|
|
101
|
+
zero = mx.zeros_like(theta).astype(mx.complex64)
|
|
102
|
+
return _rot(mx.conj(p), zero, zero, p)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _expi(phase: mx.array) -> mx.array:
|
|
106
|
+
return (mx.cos(phase) + 1j * mx.sin(phase)).astype(mx.complex64)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _rot(a, b, c, d) -> mx.array:
|
|
110
|
+
"""Stack four (scalar or batched) entries into a ``(*shape, 2, 2)`` gate."""
|
|
111
|
+
a, b, c, d = (mx.array(v).astype(mx.complex64) for v in (a, b, c, d))
|
|
112
|
+
row0 = mx.stack([a, b], axis=-1)
|
|
113
|
+
row1 = mx.stack([c, d], axis=-1)
|
|
114
|
+
return mx.stack([row0, row1], axis=-2)
|
|
@@ -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,8 @@
|
|
|
1
|
+
mlx_quantum/__init__.py,sha256=7qjKB4AZ1N3xiUX43BN4iilxspCnPB46KJnJBEDTs6A,479
|
|
2
|
+
mlx_quantum/layer.py,sha256=eSdMsA3vJNDAHTww4AP1yztWPJX7eTS67EA6Q7Fvu9I,1767
|
|
3
|
+
mlx_quantum/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
mlx_quantum/statevector.py,sha256=-4zG5KVPFXku6hO0iofgOP-0ULsjgJoVhpCKsS7PGuw,3743
|
|
5
|
+
mlx_quantum-0.1.0.dist-info/METADATA,sha256=2bcRDqPEtIu9GIEz5Krt069FYxNO43HFeEn5HVIQtiw,6701
|
|
6
|
+
mlx_quantum-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
7
|
+
mlx_quantum-0.1.0.dist-info/licenses/LICENSE,sha256=CthJDHR37kFu4ndMrsrTTwdJaBqDDyB-OyIn4AteLxM,1067
|
|
8
|
+
mlx_quantum-0.1.0.dist-info/RECORD,,
|
|
@@ -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.
|