qudenoise 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.
- qudenoise-0.1.0/LICENSE +21 -0
- qudenoise-0.1.0/MANIFEST.in +4 -0
- qudenoise-0.1.0/PKG-INFO +123 -0
- qudenoise-0.1.0/README.md +103 -0
- qudenoise-0.1.0/docs/design_notes.md +27 -0
- qudenoise-0.1.0/examples/ghz_state.py +15 -0
- qudenoise-0.1.0/examples/noisy_bell_pair.py +9 -0
- qudenoise-0.1.0/examples/qae_config.yaml +14 -0
- qudenoise-0.1.0/examples/train_qae_denoiser.py +34 -0
- qudenoise-0.1.0/pyproject.toml +49 -0
- qudenoise-0.1.0/setup.cfg +4 -0
- qudenoise-0.1.0/src/qudenoise/__init__.py +43 -0
- qudenoise-0.1.0/src/qudenoise/backend.py +256 -0
- qudenoise-0.1.0/src/qudenoise/circuit.py +362 -0
- qudenoise-0.1.0/src/qudenoise/cli.py +241 -0
- qudenoise-0.1.0/src/qudenoise/gates.py +191 -0
- qudenoise-0.1.0/src/qudenoise/mps.py +451 -0
- qudenoise-0.1.0/src/qudenoise/noise.py +312 -0
- qudenoise-0.1.0/src/qudenoise/observables.py +147 -0
- qudenoise-0.1.0/src/qudenoise/py.typed +0 -0
- qudenoise-0.1.0/src/qudenoise/qae.py +464 -0
- qudenoise-0.1.0/src/qudenoise/reference.py +107 -0
- qudenoise-0.1.0/src/qudenoise/simulator.py +344 -0
- qudenoise-0.1.0/src/qudenoise/utils.py +108 -0
- qudenoise-0.1.0/src/qudenoise.egg-info/PKG-INFO +123 -0
- qudenoise-0.1.0/src/qudenoise.egg-info/SOURCES.txt +35 -0
- qudenoise-0.1.0/src/qudenoise.egg-info/dependency_links.txt +1 -0
- qudenoise-0.1.0/src/qudenoise.egg-info/entry_points.txt +2 -0
- qudenoise-0.1.0/src/qudenoise.egg-info/requires.txt +15 -0
- qudenoise-0.1.0/src/qudenoise.egg-info/top_level.txt +1 -0
- qudenoise-0.1.0/tests/conftest.py +35 -0
- qudenoise-0.1.0/tests/test_backend.py +180 -0
- qudenoise-0.1.0/tests/test_cli.py +176 -0
- qudenoise-0.1.0/tests/test_gates.py +107 -0
- qudenoise-0.1.0/tests/test_mps_vs_dense.py +397 -0
- qudenoise-0.1.0/tests/test_noise_channels.py +375 -0
- qudenoise-0.1.0/tests/test_qae_training.py +263 -0
qudenoise-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QuDenoise contributors
|
|
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.
|
qudenoise-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qudenoise
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pure-Python MPS quantum circuit simulator with Kraus-channel noise trajectories and a quantum-autoencoder denoiser
|
|
5
|
+
Author: QuDenoise contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: quantum,simulator,matrix product state,tensor network,noise,autoencoder
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Provides-Extra: autodiff
|
|
16
|
+
Provides-Extra: gpu
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Provides-Extra: viz
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
|
|
21
|
+
# QuDenoise
|
|
22
|
+
|
|
23
|
+
A from-scratch, pure-Python **Matrix Product State (MPS) quantum circuit simulator** with
|
|
24
|
+
**Kraus-channel noise (quantum trajectories)** and a **quantum-autoencoder (QAE) denoiser**.
|
|
25
|
+
Runs on NumPy everywhere; optionally runs every tensor operation on a GPU through CuPy.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install qudenoise # NumPy/SciPy only (CPU)
|
|
31
|
+
pip install "qudenoise[viz]" # + matplotlib for plotting examples
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### GPU (optional)
|
|
35
|
+
|
|
36
|
+
CuPy publishes one wheel per CUDA version, so QuDenoise does **not** pin a CuPy variant. Install the one
|
|
37
|
+
matching your toolkit yourself, e.g.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install cupy-cuda12x # CUDA 12.x
|
|
41
|
+
pip install cupy-cuda11x # CUDA 11.x
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
(`pip install "qudenoise[gpu]"` pulls the generic `cupy` source package, which needs a local CUDA toolchain.)
|
|
45
|
+
CuPy is imported lazily: without it QuDenoise imports and runs with no errors or warnings. With
|
|
46
|
+
`device="auto"` (default) the GPU is used when available and this is logged once
|
|
47
|
+
(`QuDenoise: running on GPU (CuPy)` / `running on CPU (NumPy)`); force a backend with
|
|
48
|
+
`Simulator(device="cupy")` or `device="numpy"`.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from qudenoise import Circuit, Simulator
|
|
54
|
+
|
|
55
|
+
c = Circuit(20).h(0)
|
|
56
|
+
for i in range(19):
|
|
57
|
+
c.cnot(i, i + 1)
|
|
58
|
+
|
|
59
|
+
sim = Simulator(bond_dim=8) # chi cap; also truncation_threshold=...
|
|
60
|
+
state = sim.run(c) # -> MPS
|
|
61
|
+
print(state.bond_dimensions(), state.fidelity_estimate)
|
|
62
|
+
samples, report = sim.sample(c, shots=1000, seed=1)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Non-adjacent two-qubit gates are routed with SWAP chains automatically; every insertion is logged
|
|
66
|
+
(logger `qudenoise`, INFO) and recorded in `sim.last_report.routing_log`.
|
|
67
|
+
|
|
68
|
+
### Noise
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
c = Circuit(2).h(0).cnot(0, 1).depolarizing(0, 0.05).amplitude_damping(1, 0.1)
|
|
72
|
+
res = Simulator().run_observable(c, lambda m: observables.expectation(m, {0: gates.Z(), 1: gates.Z()}).real,
|
|
73
|
+
n_trajectories=2000, seed=0)
|
|
74
|
+
print(res.mean, "+/-", res.sem)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Channels: `depolarizing` (1q/2q), `amplitude_damping`, `phase_damping`, `bit_flip`, `phase_flip`, or any
|
|
78
|
+
`KrausChannel`. Trajectories are seeded per index (`spawn_seeds`), so results are identical for any worker
|
|
79
|
+
count. CPU runs with 8+ trajectories use a process pool; GPU runs are sequential in-process.
|
|
80
|
+
|
|
81
|
+
### Quantum autoencoder
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from qudenoise import QAE
|
|
85
|
+
qae = QAE(n_qubits=4, n_latent_qubits=2, ansatz_depth=2, seed=1)
|
|
86
|
+
qae.fit(training_states, epochs=60, lr=0.1) # MPS / Circuit / dense vectors
|
|
87
|
+
clean_estimate = qae.denoise(noisy_state)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Cost = 1 - mean probability of the trash qubits being `|0..0>`, computed directly from the MPS. Gradients
|
|
91
|
+
use the parameter-shift rule through `qudenoise.qae.compute_gradient`; pass `gradient_fn=` to plug in a JAX
|
|
92
|
+
(`[autodiff]`) backend without changing the API.
|
|
93
|
+
|
|
94
|
+
### CLI
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
qudenoise run circuit.json --qubits 20 --bond-dim 32 --shots 1000 [--device auto|numpy|cupy] [-o out.json]
|
|
98
|
+
qudenoise train-qae config.yaml
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Circuit JSON: `{"n_qubits": N, "ops": [{"gate": "h", "qubits": [0]}, {"gate": "rz", "qubits": [1], "params": [0.3]},
|
|
102
|
+
{"noise": "depolarizing", "qubits": [0], "param": 0.01}]}`. A `.py` file defining `circuit` or
|
|
103
|
+
`build_circuit(n)` also works (executed as ordinary Python - only run files you trust). See
|
|
104
|
+
`examples/qae_config.yaml`.
|
|
105
|
+
|
|
106
|
+
## Conventions and limits
|
|
107
|
+
|
|
108
|
+
* Big-endian qubit order (`|q0 q1 ...>`); rotations are `exp(-i theta P / 2)`.
|
|
109
|
+
* `MPS.to_dense()` refuses above 24 qubits unless forced. `qudenoise.reference` (dense/density-matrix
|
|
110
|
+
simulators) exists to validate the MPS code in tests; it is not a supported product feature.
|
|
111
|
+
* With truncation, `state.truncation_error`, `state.fidelity_estimate` (product of `1 - eps_k`) and
|
|
112
|
+
`state.fidelity_lower_bound` track accuracy loss.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install -e ".[test]" && pytest
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Tests run on NumPy and, when CuPy + a GPU are present, are repeated on CuPy (backend-parity check).
|
|
121
|
+
See `docs/design_notes.md`.
|
|
122
|
+
|
|
123
|
+
MIT licensed.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# QuDenoise
|
|
2
|
+
|
|
3
|
+
A from-scratch, pure-Python **Matrix Product State (MPS) quantum circuit simulator** with
|
|
4
|
+
**Kraus-channel noise (quantum trajectories)** and a **quantum-autoencoder (QAE) denoiser**.
|
|
5
|
+
Runs on NumPy everywhere; optionally runs every tensor operation on a GPU through CuPy.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install qudenoise # NumPy/SciPy only (CPU)
|
|
11
|
+
pip install "qudenoise[viz]" # + matplotlib for plotting examples
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### GPU (optional)
|
|
15
|
+
|
|
16
|
+
CuPy publishes one wheel per CUDA version, so QuDenoise does **not** pin a CuPy variant. Install the one
|
|
17
|
+
matching your toolkit yourself, e.g.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install cupy-cuda12x # CUDA 12.x
|
|
21
|
+
pip install cupy-cuda11x # CUDA 11.x
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
(`pip install "qudenoise[gpu]"` pulls the generic `cupy` source package, which needs a local CUDA toolchain.)
|
|
25
|
+
CuPy is imported lazily: without it QuDenoise imports and runs with no errors or warnings. With
|
|
26
|
+
`device="auto"` (default) the GPU is used when available and this is logged once
|
|
27
|
+
(`QuDenoise: running on GPU (CuPy)` / `running on CPU (NumPy)`); force a backend with
|
|
28
|
+
`Simulator(device="cupy")` or `device="numpy"`.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from qudenoise import Circuit, Simulator
|
|
34
|
+
|
|
35
|
+
c = Circuit(20).h(0)
|
|
36
|
+
for i in range(19):
|
|
37
|
+
c.cnot(i, i + 1)
|
|
38
|
+
|
|
39
|
+
sim = Simulator(bond_dim=8) # chi cap; also truncation_threshold=...
|
|
40
|
+
state = sim.run(c) # -> MPS
|
|
41
|
+
print(state.bond_dimensions(), state.fidelity_estimate)
|
|
42
|
+
samples, report = sim.sample(c, shots=1000, seed=1)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Non-adjacent two-qubit gates are routed with SWAP chains automatically; every insertion is logged
|
|
46
|
+
(logger `qudenoise`, INFO) and recorded in `sim.last_report.routing_log`.
|
|
47
|
+
|
|
48
|
+
### Noise
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
c = Circuit(2).h(0).cnot(0, 1).depolarizing(0, 0.05).amplitude_damping(1, 0.1)
|
|
52
|
+
res = Simulator().run_observable(c, lambda m: observables.expectation(m, {0: gates.Z(), 1: gates.Z()}).real,
|
|
53
|
+
n_trajectories=2000, seed=0)
|
|
54
|
+
print(res.mean, "+/-", res.sem)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Channels: `depolarizing` (1q/2q), `amplitude_damping`, `phase_damping`, `bit_flip`, `phase_flip`, or any
|
|
58
|
+
`KrausChannel`. Trajectories are seeded per index (`spawn_seeds`), so results are identical for any worker
|
|
59
|
+
count. CPU runs with 8+ trajectories use a process pool; GPU runs are sequential in-process.
|
|
60
|
+
|
|
61
|
+
### Quantum autoencoder
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from qudenoise import QAE
|
|
65
|
+
qae = QAE(n_qubits=4, n_latent_qubits=2, ansatz_depth=2, seed=1)
|
|
66
|
+
qae.fit(training_states, epochs=60, lr=0.1) # MPS / Circuit / dense vectors
|
|
67
|
+
clean_estimate = qae.denoise(noisy_state)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Cost = 1 - mean probability of the trash qubits being `|0..0>`, computed directly from the MPS. Gradients
|
|
71
|
+
use the parameter-shift rule through `qudenoise.qae.compute_gradient`; pass `gradient_fn=` to plug in a JAX
|
|
72
|
+
(`[autodiff]`) backend without changing the API.
|
|
73
|
+
|
|
74
|
+
### CLI
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
qudenoise run circuit.json --qubits 20 --bond-dim 32 --shots 1000 [--device auto|numpy|cupy] [-o out.json]
|
|
78
|
+
qudenoise train-qae config.yaml
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Circuit JSON: `{"n_qubits": N, "ops": [{"gate": "h", "qubits": [0]}, {"gate": "rz", "qubits": [1], "params": [0.3]},
|
|
82
|
+
{"noise": "depolarizing", "qubits": [0], "param": 0.01}]}`. A `.py` file defining `circuit` or
|
|
83
|
+
`build_circuit(n)` also works (executed as ordinary Python - only run files you trust). See
|
|
84
|
+
`examples/qae_config.yaml`.
|
|
85
|
+
|
|
86
|
+
## Conventions and limits
|
|
87
|
+
|
|
88
|
+
* Big-endian qubit order (`|q0 q1 ...>`); rotations are `exp(-i theta P / 2)`.
|
|
89
|
+
* `MPS.to_dense()` refuses above 24 qubits unless forced. `qudenoise.reference` (dense/density-matrix
|
|
90
|
+
simulators) exists to validate the MPS code in tests; it is not a supported product feature.
|
|
91
|
+
* With truncation, `state.truncation_error`, `state.fidelity_estimate` (product of `1 - eps_k`) and
|
|
92
|
+
`state.fidelity_lower_bound` track accuracy loss.
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install -e ".[test]" && pytest
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Tests run on NumPy and, when CuPy + a GPU are present, are repeated on CuPy (backend-parity check).
|
|
101
|
+
See `docs/design_notes.md`.
|
|
102
|
+
|
|
103
|
+
MIT licensed.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Design notes
|
|
2
|
+
|
|
3
|
+
**Backend.** `backend.py` is the only place (besides `reference.py`) that imports NumPy directly. All modules
|
|
4
|
+
call `get_backend()` at use time, never cache arrays across backends, and take RNGs explicitly
|
|
5
|
+
(`get_rng(seed)`, `spawn_seeds`). CuPy is imported lazily; missing CuPy never warns.
|
|
6
|
+
|
|
7
|
+
**MPS.** Site tensors `(D_left, 2, D_right)`, complex128, tracked orthogonality centre. Two-qubit gates act on
|
|
8
|
+
adjacent sites via two-site tensor + SVD (`utils.svd_truncate`, fixed chi and/or discarded-weight threshold).
|
|
9
|
+
Each truncation records `eps_k` (discarded weight); `fidelity_estimate = prod(1 - eps_k)` and the rigorous
|
|
10
|
+
`1 - (sum sqrt(eps_k))^2` bound are exposed.
|
|
11
|
+
|
|
12
|
+
**Routing.** Non-adjacent 2q gates are compiled into SWAP chains (out and back) and each insertion is logged,
|
|
13
|
+
never silent.
|
|
14
|
+
|
|
15
|
+
**Noise.** Quantum trajectories: per channel, branch probabilities come from the local norm after each Kraus
|
|
16
|
+
operator; Pauli-type channels (scaled unitaries) take a state-independent fast path. Trajectory `i` uses seed
|
|
17
|
+
`spawn_seeds(seed, n)[i]`, so parallel and serial runs agree exactly. GPU runs are sequential (CUDA contexts
|
|
18
|
+
do not survive fork the way NumPy workers do).
|
|
19
|
+
|
|
20
|
+
**QAE.** Ansatz of RY/RZ layers plus generic `RXX*RYY*RZZ` entanglers on a brick pattern (adjacent only).
|
|
21
|
+
Every parameter feeds one `exp(-i t P/2)` gate, so the two-term parameter-shift rule is exact without
|
|
22
|
+
truncation. Gradients live behind `qae.compute_gradient` / `gradient_fn`. Latent = leading qubits, trash =
|
|
23
|
+
trailing qubits so projection and extension are cheap MPS boundary operations.
|
|
24
|
+
|
|
25
|
+
**Known limitations.** Parameter-shift cost scales as 2 x n_params x n_states simulations per epoch (pure
|
|
26
|
+
Python); the QAE targets small registers. Trajectories give unbiased averages but need many samples for
|
|
27
|
+
small effects. Learning may hit local minima on entangled families; use `restarts=`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""60-qubit GHZ state with bond dimension 2 - far beyond dense simulation."""
|
|
2
|
+
from qudenoise import Circuit, Simulator
|
|
3
|
+
|
|
4
|
+
n = 60
|
|
5
|
+
c = Circuit(n).h(0)
|
|
6
|
+
for i in range(n - 1):
|
|
7
|
+
c.cnot(i, i + 1)
|
|
8
|
+
|
|
9
|
+
sim = Simulator(bond_dim=4)
|
|
10
|
+
state = sim.run(c)
|
|
11
|
+
samples, _ = sim.sample(c, shots=8, seed=1)
|
|
12
|
+
print("max bond dimension:", state.max_bond_dimension)
|
|
13
|
+
print("samples (all-equal rows expected):")
|
|
14
|
+
for row in samples:
|
|
15
|
+
print("".join(str(int(b)) for b in row))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Depolarizing noise on a Bell pair: trajectory average vs exact density matrix."""
|
|
2
|
+
from qudenoise import Circuit, Simulator, gates, observables
|
|
3
|
+
|
|
4
|
+
p = 0.1
|
|
5
|
+
c = Circuit(2).h(0).cnot(0, 1).depolarizing(0, p).depolarizing(1, p)
|
|
6
|
+
zz = lambda m: observables.expectation(m, {0: gates.Z(), 1: gates.Z()}).real
|
|
7
|
+
res = Simulator(device="numpy").run_observable(c, zz, n_trajectories=2000, seed=0)
|
|
8
|
+
print(f"<ZZ> trajectories: {res.mean:.4f} +/- {res.sem:.4f}")
|
|
9
|
+
print(f"<ZZ> exact : {(1 - 4 * p / 3) ** 2:.4f}") # each qubit's Z shrinks by 1 - 4p/3
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# qudenoise train-qae examples/qae_config.yaml
|
|
2
|
+
n_qubits: 3
|
|
3
|
+
n_latent_qubits: 2
|
|
4
|
+
ansatz_depth: 1
|
|
5
|
+
seed: 1
|
|
6
|
+
output: qae_model.json
|
|
7
|
+
fit: {epochs: 40, lr: 0.2, seed: 2}
|
|
8
|
+
training_states:
|
|
9
|
+
- {n_qubits: 3, ops: [{gate: ry, qubits: [0], params: [0.3]}, {gate: ry, qubits: [1], params: [1.1]}, {gate: ry, qubits: [2], params: [0.9]}]}
|
|
10
|
+
- {n_qubits: 3, ops: [{gate: ry, qubits: [0], params: [1.5]}, {gate: ry, qubits: [1], params: [0.4]}, {gate: ry, qubits: [2], params: [0.9]}]}
|
|
11
|
+
- {n_qubits: 3, ops: [{gate: ry, qubits: [0], params: [2.2]}, {gate: ry, qubits: [1], params: [2.0]}, {gate: ry, qubits: [2], params: [0.9]}]}
|
|
12
|
+
test_pairs:
|
|
13
|
+
- clean: {n_qubits: 3, ops: [{gate: ry, qubits: [0], params: [0.8]}, {gate: ry, qubits: [1], params: [1.7]}, {gate: ry, qubits: [2], params: [0.9]}]}
|
|
14
|
+
noisy: {n_qubits: 3, ops: [{gate: ry, qubits: [0], params: [0.8]}, {gate: ry, qubits: [1], params: [1.7]}, {gate: ry, qubits: [2], params: [0.9]}, {gate: rx, qubits: [2], params: [0.5]}]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Train a QAE on a family of product states and use it to denoise perturbed states."""
|
|
2
|
+
import math
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from qudenoise import Circuit, MPS, QAE
|
|
6
|
+
from qudenoise.backend import get_rng
|
|
7
|
+
from qudenoise.simulator import execute_ops
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def family(seed, n=4, k=2):
|
|
11
|
+
r = random.Random(seed)
|
|
12
|
+
c = Circuit(n)
|
|
13
|
+
for q in range(k):
|
|
14
|
+
c.ry(q, r.uniform(0, math.pi)).rz(q, r.uniform(0, 2 * math.pi))
|
|
15
|
+
for q in range(k, n):
|
|
16
|
+
c.ry(q, 0.9).rz(q, 0.4)
|
|
17
|
+
return c
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def state(c):
|
|
21
|
+
return execute_ops(c.compile(), MPS(c.n_qubits, device="numpy"), get_rng(0))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
qae = QAE(4, 2, ansatz_depth=2, seed=1)
|
|
25
|
+
res = qae.fit([family(s) for s in range(6)], epochs=60, lr=0.1, seed=3)
|
|
26
|
+
print(f"trash cost: {res.initial_cost:.3f} -> {res.final_cost:.5f}")
|
|
27
|
+
|
|
28
|
+
for s in range(3):
|
|
29
|
+
clean = family(100 + s)
|
|
30
|
+
noisy = clean.copy()
|
|
31
|
+
for q in range(4):
|
|
32
|
+
noisy.rx(q, 0.25 * (-1) ** q)
|
|
33
|
+
r = qae.denoise_fidelity(state(noisy), state(clean))
|
|
34
|
+
print(f"fidelity {r['before']:.4f} -> {r['after']:.4f}")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qudenoise"
|
|
7
|
+
version = "0.1.0" # keep in sync with src/qudenoise/__init__.py::__version__
|
|
8
|
+
description = "Pure-Python MPS quantum circuit simulator with Kraus-channel noise trajectories and a quantum-autoencoder denoiser"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "QuDenoise contributors" }]
|
|
13
|
+
keywords = ["quantum", "simulator", "matrix product state", "tensor network", "noise", "autoencoder"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"numpy>=1.22",
|
|
23
|
+
"scipy>=1.8",
|
|
24
|
+
"PyYAML>=5.4", # train-qae config files
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
# CuPy ships one wheel per CUDA version (cupy-cuda12x, cupy-cuda11x, ...), so `[gpu]` pulls the
|
|
29
|
+
# generic source package name only as a convenience; on most machines install the matching wheel
|
|
30
|
+
# yourself (see README) instead of using this extra.
|
|
31
|
+
gpu = ["cupy"]
|
|
32
|
+
autodiff = ["jax"]
|
|
33
|
+
viz = ["matplotlib>=3.5"]
|
|
34
|
+
test = ["pytest>=7"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
qudenoise = "qudenoise.cli:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools]
|
|
40
|
+
package-dir = { "" = "src" }
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
where = ["src"]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.package-data]
|
|
46
|
+
qudenoise = ["py.typed"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""QuDenoise: pure-Python MPS quantum circuit simulator with noise and a QAE denoiser."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
__version__ = "0.1.0"
|
|
5
|
+
|
|
6
|
+
from . import backend, gates, noise, observables
|
|
7
|
+
from .backend import (
|
|
8
|
+
get_backend,
|
|
9
|
+
get_rng,
|
|
10
|
+
gpu_available,
|
|
11
|
+
set_backend,
|
|
12
|
+
to_backend,
|
|
13
|
+
to_numpy,
|
|
14
|
+
use_backend,
|
|
15
|
+
)
|
|
16
|
+
from .circuit import Circuit, Op
|
|
17
|
+
from .mps import MPS
|
|
18
|
+
from .noise import KrausChannel
|
|
19
|
+
from .qae import QAE
|
|
20
|
+
from .simulator import ObservableResult, SimulationReport, Simulator
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"__version__",
|
|
24
|
+
"backend",
|
|
25
|
+
"gates",
|
|
26
|
+
"noise",
|
|
27
|
+
"observables",
|
|
28
|
+
"get_backend",
|
|
29
|
+
"get_rng",
|
|
30
|
+
"gpu_available",
|
|
31
|
+
"set_backend",
|
|
32
|
+
"to_backend",
|
|
33
|
+
"to_numpy",
|
|
34
|
+
"use_backend",
|
|
35
|
+
"Circuit",
|
|
36
|
+
"Op",
|
|
37
|
+
"MPS",
|
|
38
|
+
"KrausChannel",
|
|
39
|
+
"QAE",
|
|
40
|
+
"Simulator",
|
|
41
|
+
"SimulationReport",
|
|
42
|
+
"ObservableResult",
|
|
43
|
+
]
|