pdft 0.2.3__tar.gz → 0.2.4__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.
- pdft-0.2.4/PKG-INFO +202 -0
- pdft-0.2.4/README.md +143 -0
- {pdft-0.2.3 → pdft-0.2.4}/pyproject.toml +25 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/__init__.py +9 -5
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/block/block.py +1 -1
- pdft-0.2.4/src/pdft/coherence.py +203 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/io/serialize.py +1 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/loss.py +0 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/optimizers/loop.py +1 -1
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/profiling.py +1 -1
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/training/batched.py +2 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/viz/circuit.py +2 -1
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/circuit/test_freeze.py +14 -15
- pdft-0.2.4/tests/test_coherence.py +147 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/test_profiling.py +0 -1
- {pdft-0.2.3 → pdft-0.2.4}/tests/training/test_batched.py +18 -17
- {pdft-0.2.3 → pdft-0.2.4}/tests/viz/test_loss.py +7 -2
- pdft-0.2.3/PKG-INFO +0 -100
- pdft-0.2.3/README.md +0 -41
- {pdft-0.2.3 → pdft-0.2.4}/.claude/CLAUDE.md +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/.github/workflows/ci.yml +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/.github/workflows/publish.yml +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/.github/workflows/verify-upstream-pin.yml +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/.gitignore +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/.pre-commit-config.yaml +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/LICENSE +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/Makefile +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/examples/basis_demo.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/examples/mera_demo.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/examples/optimizer_benchmark.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/README.md +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/adam_trajectory_4x4.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/basis_roundtrip.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/basis_roundtrip_hash.txt +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/compression_roundtrip_4x4.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/entangled_qft_4x4.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/ft_mat_roundtrip.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/loss_values.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/manifest.json +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/manifold_project_retract.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/mera_4x4.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/phase_extraction_3x3.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/qft_basis_trained.json +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/qft_code_16x16.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/qft_code_4x4.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/tebd_4x4.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/train_trajectory_4x4.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/goldens/train_trajectory_4x4_long.npz +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/julia/Manifest.toml +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/julia/Project.toml +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/reference/julia/generate_goldens.jl +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/base.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/block/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/__init__.py +4 -4
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/dct4.py +2 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/entangled_qft.py +2 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/freeze.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/mera.py +2 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/qft.py +2 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/real_rich.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/rich.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/bases/circuit/tebd.py +2 -2
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/circuit/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/circuit/builder.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/circuit/cache.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/io/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/io/compression.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/manifolds.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/optimizers/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/optimizers/adam.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/optimizers/core.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/optimizers/gd.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/training/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/training/adam_step.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/training/eval_loop.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/training/result.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/training/schedules.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/training/single.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/viz/__init__.py +1 -1
- {pdft-0.2.3 → pdft-0.2.4}/src/pdft/viz/loss.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/block/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/block/test_block.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/circuit/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/circuit/test_dct4.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/circuit/test_qft.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/circuit/test_real_rich.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/circuit/test_rich.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/conftest.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/test_base.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/test_new_bases.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/bases/test_phase_extraction.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/circuit/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/circuit/test_cache.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/circuit/test_cry_gate.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/circuit/test_h_inverse_leg_swap.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/circuit/test_high_qubit_circuit.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/circuit/test_legacy_builder.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/circuit/test_sorted_gate_program.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/conftest.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/io/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/io/conftest.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/io/test_compression.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/io/test_serialize.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/optimizers/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/optimizers/conftest.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/optimizers/test_adam.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/optimizers/test_gd.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/conftest.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_adam.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_compression.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_long_run.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_loss.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_manifolds.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_new_bases.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_qft.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_scale.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_serialize.py +1 -1
- {pdft-0.2.3 → pdft-0.2.4}/tests/parity/test_training.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/test_basis_seeded_init.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/test_loss.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/test_manifolds.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/test_smoke.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/training/__init__.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/training/conftest.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/training/test_eval_loop.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/training/test_integration.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/training/test_single.py +0 -0
- {pdft-0.2.3 → pdft-0.2.4}/tests/viz/__init__.py +0 -0
pdft-0.2.4/PKG-INFO
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pdft
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Python port of ParametricDFT.jl: learning parametric quantum Fourier transforms via manifold optimization.
|
|
5
|
+
Project-URL: Homepage, https://github.com/zazabap/pdft
|
|
6
|
+
Project-URL: Issues, https://github.com/zazabap/pdft/issues
|
|
7
|
+
Project-URL: Upstream, https://github.com/nzy1997/ParametricDFT.jl
|
|
8
|
+
Author: zazabap
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 zazabap
|
|
12
|
+
|
|
13
|
+
Portions of this project are a Python port of ParametricDFT.jl
|
|
14
|
+
(https://github.com/nzy1997/ParametricDFT.jl), Copyright (c) 2025 nzy1997,
|
|
15
|
+
originally released under the MIT License.
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
|
34
|
+
License-File: LICENSE
|
|
35
|
+
Keywords: dft,fourier,jax,manifold-optimization,quantum,variational
|
|
36
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
37
|
+
Classifier: Intended Audience :: Science/Research
|
|
38
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
39
|
+
Classifier: Programming Language :: Python :: 3
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
44
|
+
Requires-Python: >=3.11
|
|
45
|
+
Requires-Dist: jax>=0.10.0
|
|
46
|
+
Requires-Dist: typing-extensions>=4.5
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: jaxtyping>=0.2; extra == 'dev'
|
|
49
|
+
Requires-Dist: matplotlib>=3.8; extra == 'dev'
|
|
50
|
+
Requires-Dist: numpy>=1.26; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest-cov>=4; extra == 'dev'
|
|
52
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
53
|
+
Requires-Dist: ruff<0.17,>=0.6; extra == 'dev'
|
|
54
|
+
Provides-Extra: gpu
|
|
55
|
+
Requires-Dist: jax[cuda12]>=0.10.0; extra == 'gpu'
|
|
56
|
+
Provides-Extra: plot
|
|
57
|
+
Requires-Dist: matplotlib>=3.8; extra == 'plot'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# pdft
|
|
61
|
+
|
|
62
|
+
[](https://arxiv.org/abs/2608.00053)
|
|
63
|
+
|
|
64
|
+
A Python port of [ParametricDFT.jl](https://github.com/nzy1997/ParametricDFT.jl):
|
|
65
|
+
learning parametric quantum Fourier transforms via manifold optimization. The
|
|
66
|
+
package implements a variational approach that approximates the Discrete
|
|
67
|
+
Fourier Transform (DFT) with parameterized quantum circuits.
|
|
68
|
+
|
|
69
|
+
This is the reference implementation accompanying the paper
|
|
70
|
+
[*Fast Trainable Multilinear Bases for Image Compression*](https://arxiv.org/abs/2608.00053)
|
|
71
|
+
(An, Ni, Zhou, Liu, 2026).
|
|
72
|
+
|
|
73
|
+
> Status: feature-complete port. All bases (QFT, entangled QFT, TEBD, MERA,
|
|
74
|
+
> Rich/RealRich, DCT-IV, blocked), both Riemannian optimizers (GD + Adam),
|
|
75
|
+
> training, JSON/compression I/O, and visualization are implemented, with
|
|
76
|
+
> parity against the Julia reference verified by committed goldens.
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
From PyPI (Python 3.11+):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install "pdft>=0.2.3"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> **Note:** the older `pdft==0.2.2` wheel predates `DCT4Basis` and the
|
|
87
|
+
> `parametrization="u4"` option of `TEBDBasis` / `MERABasis`, so it cannot run
|
|
88
|
+
> the paper's DCT-IV, TEBD-U4, or MERA-U4 configurations. If
|
|
89
|
+
> `pdft.__version__` reports `0.2.2`, upgrade with `pip install -U pdft`.
|
|
90
|
+
|
|
91
|
+
From source:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/zazabap/pdft.git
|
|
95
|
+
cd pdft
|
|
96
|
+
pip install -e ".[dev]"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quick start
|
|
100
|
+
|
|
101
|
+
Train a parametric QFT basis on a target image with Riemannian gradient
|
|
102
|
+
descent:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import jax
|
|
106
|
+
import jax.numpy as jnp
|
|
107
|
+
import pdft
|
|
108
|
+
|
|
109
|
+
target = jax.random.normal(jax.random.PRNGKey(7), (4, 4)).astype(jnp.complex128)
|
|
110
|
+
basis = pdft.QFTBasis(m=2, n=2)
|
|
111
|
+
|
|
112
|
+
result = pdft.train_basis(
|
|
113
|
+
basis,
|
|
114
|
+
target=target,
|
|
115
|
+
loss=pdft.L1Norm(),
|
|
116
|
+
optimizer=pdft.RiemannianGD(lr=0.01),
|
|
117
|
+
steps=50,
|
|
118
|
+
seed=0,
|
|
119
|
+
)
|
|
120
|
+
print(result.loss_history[0], "->", result.loss_history[-1])
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Runnable demos live in [`examples/`](examples/) (each finishes in under
|
|
124
|
+
10 seconds):
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
python examples/basis_demo.py # train a QFTBasis, plot the loss
|
|
128
|
+
python examples/optimizer_benchmark.py # GD vs Adam comparison
|
|
129
|
+
python examples/mera_demo.py # MERA basis training
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Coherence with the pixel basis
|
|
133
|
+
|
|
134
|
+
Compression cares only how few coefficients a basis needs. Anything that
|
|
135
|
+
recovers an image from a *subset of its pixels* — inpainting, completion,
|
|
136
|
+
compressed sensing — is governed by a second quantity, and it is not sparsity:
|
|
137
|
+
|
|
138
|
+
mu(U) = N max_ij |U_ij|^2 in [1, N]
|
|
139
|
+
|
|
140
|
+
`mu = 1` is maximal incoherence with the pixel basis, the best case for
|
|
141
|
+
recovery from pointwise samples; `mu = N` is an atom living on one pixel,
|
|
142
|
+
invisible to any sample set that misses it.
|
|
143
|
+
|
|
144
|
+
Every basis here starts at `mu = 1`, and there is a structural reason it can
|
|
145
|
+
stay there: if the only non-diagonal gates are one Hadamard per wire, then
|
|
146
|
+
`|U_ij| = N^{-1/2}` for **every** parameter value, so `mu = 1` identically and
|
|
147
|
+
`sqrt(N) U` is a complex Hadamard matrix. Training the controlled-phase gates
|
|
148
|
+
arbitrarily hard, on any objective, cannot move it. Training the Hadamard /
|
|
149
|
+
`U(4)` gates can and does — randomising them on a `(3, 3)` `QFTBasis` reaches
|
|
150
|
+
`mu = 24.8` out of 64, and on `RichBasis`, which has no diagonal gates at all,
|
|
151
|
+
`mu = 32.5`.
|
|
152
|
+
|
|
153
|
+
`certify_flat_modulus` answers that before a run rather than measuring it
|
|
154
|
+
after, using the same `frozen_indices` that `train_basis_batched` takes:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from pdft.coherence import certify_flat_modulus, coherence, diagonal_tensor_indices
|
|
158
|
+
|
|
159
|
+
basis = pdft.QFTBasis(m=3, n=3)
|
|
160
|
+
coherence(basis) # 1.0
|
|
161
|
+
|
|
162
|
+
cert = certify_flat_modulus(basis) # nothing frozen
|
|
163
|
+
print(cert.holds, cert.reason) # False: the Hadamards are trainable
|
|
164
|
+
|
|
165
|
+
frozen = cert.offending_indices # exactly what must be held fixed
|
|
166
|
+
assert certify_flat_modulus(basis, frozen_indices=frozen)
|
|
167
|
+
result = pdft.train_basis_batched(basis, frozen_indices=frozen, ...)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Freezing gates is a real trade — it removes the freedom that `RichBasis` and
|
|
171
|
+
`TEBDBasis` add for compression. The point is that the trade is now visible and
|
|
172
|
+
checkable, so it can be made deliberately per task.
|
|
173
|
+
|
|
174
|
+
## Background
|
|
175
|
+
|
|
176
|
+
For the theory, see the paper:
|
|
177
|
+
- [Fast Trainable Multilinear Bases for Image Compression](https://arxiv.org/abs/2608.00053) (arXiv:2608.00053)
|
|
178
|
+
|
|
179
|
+
and the upstream notes:
|
|
180
|
+
- [`note/stepbystep.pdf`](https://github.com/nzy1997/ParametricDFT.jl/blob/main/note/stepbystep.pdf)
|
|
181
|
+
- [`note/main.pdf`](https://github.com/nzy1997/ParametricDFT.jl/blob/main/note/main.pdf)
|
|
182
|
+
|
|
183
|
+
## Citation
|
|
184
|
+
|
|
185
|
+
If you use this package in your research, please cite:
|
|
186
|
+
|
|
187
|
+
```bibtex
|
|
188
|
+
@misc{an2026fast,
|
|
189
|
+
title = {Fast Trainable Multilinear Bases for Image Compression},
|
|
190
|
+
author = {An, Shiwen and Ni, Zhongyi and Zhou, Huanhai and Liu, Jin-Guo},
|
|
191
|
+
year = {2026},
|
|
192
|
+
eprint = {2608.00053},
|
|
193
|
+
archivePrefix = {arXiv},
|
|
194
|
+
primaryClass = {eess.IV},
|
|
195
|
+
url = {https://arxiv.org/abs/2608.00053},
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT. See [LICENSE](LICENSE). This project is a derivative port of
|
|
202
|
+
ParametricDFT.jl (Copyright © 2025 nzy1997, MIT).
|
pdft-0.2.4/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# pdft
|
|
2
|
+
|
|
3
|
+
[](https://arxiv.org/abs/2608.00053)
|
|
4
|
+
|
|
5
|
+
A Python port of [ParametricDFT.jl](https://github.com/nzy1997/ParametricDFT.jl):
|
|
6
|
+
learning parametric quantum Fourier transforms via manifold optimization. The
|
|
7
|
+
package implements a variational approach that approximates the Discrete
|
|
8
|
+
Fourier Transform (DFT) with parameterized quantum circuits.
|
|
9
|
+
|
|
10
|
+
This is the reference implementation accompanying the paper
|
|
11
|
+
[*Fast Trainable Multilinear Bases for Image Compression*](https://arxiv.org/abs/2608.00053)
|
|
12
|
+
(An, Ni, Zhou, Liu, 2026).
|
|
13
|
+
|
|
14
|
+
> Status: feature-complete port. All bases (QFT, entangled QFT, TEBD, MERA,
|
|
15
|
+
> Rich/RealRich, DCT-IV, blocked), both Riemannian optimizers (GD + Adam),
|
|
16
|
+
> training, JSON/compression I/O, and visualization are implemented, with
|
|
17
|
+
> parity against the Julia reference verified by committed goldens.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
From PyPI (Python 3.11+):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install "pdft>=0.2.3"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> **Note:** the older `pdft==0.2.2` wheel predates `DCT4Basis` and the
|
|
28
|
+
> `parametrization="u4"` option of `TEBDBasis` / `MERABasis`, so it cannot run
|
|
29
|
+
> the paper's DCT-IV, TEBD-U4, or MERA-U4 configurations. If
|
|
30
|
+
> `pdft.__version__` reports `0.2.2`, upgrade with `pip install -U pdft`.
|
|
31
|
+
|
|
32
|
+
From source:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/zazabap/pdft.git
|
|
36
|
+
cd pdft
|
|
37
|
+
pip install -e ".[dev]"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
Train a parametric QFT basis on a target image with Riemannian gradient
|
|
43
|
+
descent:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import jax
|
|
47
|
+
import jax.numpy as jnp
|
|
48
|
+
import pdft
|
|
49
|
+
|
|
50
|
+
target = jax.random.normal(jax.random.PRNGKey(7), (4, 4)).astype(jnp.complex128)
|
|
51
|
+
basis = pdft.QFTBasis(m=2, n=2)
|
|
52
|
+
|
|
53
|
+
result = pdft.train_basis(
|
|
54
|
+
basis,
|
|
55
|
+
target=target,
|
|
56
|
+
loss=pdft.L1Norm(),
|
|
57
|
+
optimizer=pdft.RiemannianGD(lr=0.01),
|
|
58
|
+
steps=50,
|
|
59
|
+
seed=0,
|
|
60
|
+
)
|
|
61
|
+
print(result.loss_history[0], "->", result.loss_history[-1])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Runnable demos live in [`examples/`](examples/) (each finishes in under
|
|
65
|
+
10 seconds):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python examples/basis_demo.py # train a QFTBasis, plot the loss
|
|
69
|
+
python examples/optimizer_benchmark.py # GD vs Adam comparison
|
|
70
|
+
python examples/mera_demo.py # MERA basis training
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Coherence with the pixel basis
|
|
74
|
+
|
|
75
|
+
Compression cares only how few coefficients a basis needs. Anything that
|
|
76
|
+
recovers an image from a *subset of its pixels* — inpainting, completion,
|
|
77
|
+
compressed sensing — is governed by a second quantity, and it is not sparsity:
|
|
78
|
+
|
|
79
|
+
mu(U) = N max_ij |U_ij|^2 in [1, N]
|
|
80
|
+
|
|
81
|
+
`mu = 1` is maximal incoherence with the pixel basis, the best case for
|
|
82
|
+
recovery from pointwise samples; `mu = N` is an atom living on one pixel,
|
|
83
|
+
invisible to any sample set that misses it.
|
|
84
|
+
|
|
85
|
+
Every basis here starts at `mu = 1`, and there is a structural reason it can
|
|
86
|
+
stay there: if the only non-diagonal gates are one Hadamard per wire, then
|
|
87
|
+
`|U_ij| = N^{-1/2}` for **every** parameter value, so `mu = 1` identically and
|
|
88
|
+
`sqrt(N) U` is a complex Hadamard matrix. Training the controlled-phase gates
|
|
89
|
+
arbitrarily hard, on any objective, cannot move it. Training the Hadamard /
|
|
90
|
+
`U(4)` gates can and does — randomising them on a `(3, 3)` `QFTBasis` reaches
|
|
91
|
+
`mu = 24.8` out of 64, and on `RichBasis`, which has no diagonal gates at all,
|
|
92
|
+
`mu = 32.5`.
|
|
93
|
+
|
|
94
|
+
`certify_flat_modulus` answers that before a run rather than measuring it
|
|
95
|
+
after, using the same `frozen_indices` that `train_basis_batched` takes:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from pdft.coherence import certify_flat_modulus, coherence, diagonal_tensor_indices
|
|
99
|
+
|
|
100
|
+
basis = pdft.QFTBasis(m=3, n=3)
|
|
101
|
+
coherence(basis) # 1.0
|
|
102
|
+
|
|
103
|
+
cert = certify_flat_modulus(basis) # nothing frozen
|
|
104
|
+
print(cert.holds, cert.reason) # False: the Hadamards are trainable
|
|
105
|
+
|
|
106
|
+
frozen = cert.offending_indices # exactly what must be held fixed
|
|
107
|
+
assert certify_flat_modulus(basis, frozen_indices=frozen)
|
|
108
|
+
result = pdft.train_basis_batched(basis, frozen_indices=frozen, ...)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Freezing gates is a real trade — it removes the freedom that `RichBasis` and
|
|
112
|
+
`TEBDBasis` add for compression. The point is that the trade is now visible and
|
|
113
|
+
checkable, so it can be made deliberately per task.
|
|
114
|
+
|
|
115
|
+
## Background
|
|
116
|
+
|
|
117
|
+
For the theory, see the paper:
|
|
118
|
+
- [Fast Trainable Multilinear Bases for Image Compression](https://arxiv.org/abs/2608.00053) (arXiv:2608.00053)
|
|
119
|
+
|
|
120
|
+
and the upstream notes:
|
|
121
|
+
- [`note/stepbystep.pdf`](https://github.com/nzy1997/ParametricDFT.jl/blob/main/note/stepbystep.pdf)
|
|
122
|
+
- [`note/main.pdf`](https://github.com/nzy1997/ParametricDFT.jl/blob/main/note/main.pdf)
|
|
123
|
+
|
|
124
|
+
## Citation
|
|
125
|
+
|
|
126
|
+
If you use this package in your research, please cite:
|
|
127
|
+
|
|
128
|
+
```bibtex
|
|
129
|
+
@misc{an2026fast,
|
|
130
|
+
title = {Fast Trainable Multilinear Bases for Image Compression},
|
|
131
|
+
author = {An, Shiwen and Ni, Zhongyi and Zhou, Huanhai and Liu, Jin-Guo},
|
|
132
|
+
year = {2026},
|
|
133
|
+
eprint = {2608.00053},
|
|
134
|
+
archivePrefix = {arXiv},
|
|
135
|
+
primaryClass = {eess.IV},
|
|
136
|
+
url = {https://arxiv.org/abs/2608.00053},
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT. See [LICENSE](LICENSE). This project is a derivative port of
|
|
143
|
+
ParametricDFT.jl (Copyright © 2025 nzy1997, MIT).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pdft"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Python port of ParametricDFT.jl: learning parametric quantum Fourier transforms via manifold optimization."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
dev = [
|
|
31
31
|
"pytest>=7",
|
|
32
32
|
"pytest-cov>=4",
|
|
33
|
-
"ruff>=0.
|
|
33
|
+
"ruff>=0.6,<0.17",
|
|
34
34
|
"numpy>=1.26",
|
|
35
35
|
"jaxtyping>=0.2",
|
|
36
36
|
"matplotlib>=3.8",
|
|
@@ -50,6 +50,29 @@ packages = ["src/pdft"]
|
|
|
50
50
|
line-length = 100
|
|
51
51
|
target-version = "py311"
|
|
52
52
|
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
# Declared deliberately, because ruff's *default* rule set widens between
|
|
55
|
+
# releases. With no `select` here and an unpinned `ruff>=0.5` in [dev], lint
|
|
56
|
+
# was a property of the release calendar rather than of the repository: CI
|
|
57
|
+
# went red on `main` with no source change when a newer ruff turned on rules
|
|
58
|
+
# 0.5 did not have (RUF100, I001, C408, B008, ...).
|
|
59
|
+
#
|
|
60
|
+
# This set is pyflakes + import order + pyupgrade + comprehensions: the rules
|
|
61
|
+
# that catch real defects, all of which the codebase already satisfies.
|
|
62
|
+
select = ["E4", "E7", "E9", "F", "I", "UP", "C4"]
|
|
63
|
+
|
|
64
|
+
# The package configures jax x64 mode before importing anything that reads it,
|
|
65
|
+
# which is a module-level import after a statement by construction. This is
|
|
66
|
+
# what the scattered `# noqa: E402` comments were for; ignoring the rule once
|
|
67
|
+
# here is the same intent without directives that later ruff versions then
|
|
68
|
+
# flag as unused.
|
|
69
|
+
ignore = ["E402"]
|
|
70
|
+
|
|
71
|
+
# Not enabled, but each is a reasonable thing to opt into later:
|
|
72
|
+
# "B" -> 24 findings, almost all `zip()` without `strict=`
|
|
73
|
+
# "RUF" -> 40 findings, mostly ambiguous-unicode in maths docstrings where
|
|
74
|
+
# the multiplication sign is intended, so it needs per-site review
|
|
75
|
+
|
|
53
76
|
[tool.pytest.ini_options]
|
|
54
77
|
testpaths = ["tests"]
|
|
55
78
|
addopts = "-ra --strict-markers"
|
|
@@ -17,6 +17,7 @@ Public API: import from subpackages directly, e.g.
|
|
|
17
17
|
from pdft.optimizers import RiemannianGD, RiemannianAdam
|
|
18
18
|
from pdft.loss import L1Norm, MSELoss
|
|
19
19
|
from pdft.io import save_basis, load_basis, compress
|
|
20
|
+
from pdft.coherence import coherence, certify_flat_modulus
|
|
20
21
|
|
|
21
22
|
The names re-exported at the package root below are kept only for the
|
|
22
23
|
small set most commonly used in interactive sessions and notebooks.
|
|
@@ -41,12 +42,12 @@ if not _os.environ.get("PDFT_DISABLE_COMPILE_CACHE"):
|
|
|
41
42
|
except OSError:
|
|
42
43
|
pass
|
|
43
44
|
|
|
44
|
-
__version__ = "0.2.
|
|
45
|
+
__version__ = "0.2.4"
|
|
45
46
|
__upstream_ref__ = "nzy1997/ParametricDFT.jl@a201a27e47df2f0f3ab460f83d49b6e5f5d1e9ef"
|
|
46
47
|
|
|
47
48
|
# Slim public re-export hub: most-used names only. Anything else: import
|
|
48
49
|
# from the relevant subpackage directly.
|
|
49
|
-
from .bases import (
|
|
50
|
+
from .bases import (
|
|
50
51
|
AbstractSparseBasis,
|
|
51
52
|
BlockedBasis,
|
|
52
53
|
DCT4Basis,
|
|
@@ -59,9 +60,10 @@ from .bases import ( # noqa: E402
|
|
|
59
60
|
bases_allclose,
|
|
60
61
|
freeze_as_blocked,
|
|
61
62
|
)
|
|
62
|
-
from .
|
|
63
|
-
from .
|
|
64
|
-
from .
|
|
63
|
+
from .coherence import certify_flat_modulus, coherence
|
|
64
|
+
from .loss import AbstractLoss, L1Norm, MSELoss, loss_function
|
|
65
|
+
from .optimizers import RiemannianAdam, RiemannianGD, optimize
|
|
66
|
+
from .training import TrainingResult, train_basis, train_basis_batched
|
|
65
67
|
|
|
66
68
|
__all__ = [
|
|
67
69
|
"AbstractLoss",
|
|
@@ -82,6 +84,8 @@ __all__ = [
|
|
|
82
84
|
"__upstream_ref__",
|
|
83
85
|
"__version__",
|
|
84
86
|
"bases_allclose",
|
|
87
|
+
"certify_flat_modulus",
|
|
88
|
+
"coherence",
|
|
85
89
|
"freeze_as_blocked",
|
|
86
90
|
"loss_function",
|
|
87
91
|
"optimize",
|
|
@@ -73,7 +73,7 @@ def _make_block_code(
|
|
|
73
73
|
# axes (rows then cols) trailing. The trailing layout matches the inner
|
|
74
74
|
# einsum's expected (2,)^(m_inner+n_inner) shape.
|
|
75
75
|
perm = (
|
|
76
|
-
list(range(
|
|
76
|
+
list(range(block_log_m)) # block row axes
|
|
77
77
|
+ list(range(m_outer, m_outer + block_log_n)) # block col axes
|
|
78
78
|
+ list(range(block_log_m, m_outer)) # within row axes
|
|
79
79
|
+ list(range(m_outer + block_log_n, m_outer + n_outer)) # within col axes
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Mutual coherence of a learned basis with the pixel basis.
|
|
2
|
+
|
|
3
|
+
For compression, the only thing that matters about a basis is how few
|
|
4
|
+
coefficients it needs. For any task that recovers an image from a *subset of
|
|
5
|
+
its pixels* --- inpainting, completion, compressed sensing --- a second
|
|
6
|
+
quantity governs the outcome, and it is not sparsity:
|
|
7
|
+
|
|
8
|
+
mu(U) = N * max_ij |U_ij|^2 in [1, N]
|
|
9
|
+
|
|
10
|
+
with N the operator dimension. mu = 1 is maximal incoherence with the pixel
|
|
11
|
+
basis, the most favourable case for recovery from pointwise samples; mu = N is
|
|
12
|
+
the worst, an atom supported on a single pixel, invisible to any sample set
|
|
13
|
+
that misses it. Sample-complexity bounds for recovery from random pixel
|
|
14
|
+
observations scale linearly in mu (Candes & Plan 2011, Krahmer & Ward 2014).
|
|
15
|
+
|
|
16
|
+
A trained basis can drift in mu while its compression loss improves, so the
|
|
17
|
+
quantity is worth being able to measure. This module measures it --- and, more
|
|
18
|
+
usefully, certifies when it cannot drift at all.
|
|
19
|
+
|
|
20
|
+
The guarantee
|
|
21
|
+
-------------
|
|
22
|
+
Let the circuit act on n wires, and suppose every gate is diagonal in the
|
|
23
|
+
computational basis except for exactly one Hadamard per wire. Then
|
|
24
|
+
|
|
25
|
+
|U_ij| = N^{-1/2} for every i, j and every parameter value,
|
|
26
|
+
|
|
27
|
+
so mu(U) = 1 identically over the whole parameter space, and sqrt(N) U is a
|
|
28
|
+
complex Hadamard matrix (Tadej & Zyczkowski 2006).
|
|
29
|
+
|
|
30
|
+
*Proof.* Track the amplitude vector produced by an input basis vector.
|
|
31
|
+
Diagonal gates multiply amplitudes by unit moduli and never move amplitude
|
|
32
|
+
between the two values of any wire; a permutation permutes. Consider the moment
|
|
33
|
+
the Hadamard on wire q is applied. Every earlier gate is diagonal or a Hadamard
|
|
34
|
+
on a different wire, so none has moved amplitude across wire q, and the vector
|
|
35
|
+
is supported on one value of it: one branch carries a, the other 0. Since
|
|
36
|
+
H (a, 0)^T = (a, a)^T / sqrt(2), the gate replaces each modulus by two equal
|
|
37
|
+
moduli |a|/sqrt(2) and never combines unequal ones. Each wire receives exactly
|
|
38
|
+
one Hadamard, so every amplitude ends at 2^{-n/2}, and the remaining diagonal
|
|
39
|
+
gates and permutations preserve moduli. QED
|
|
40
|
+
|
|
41
|
+
The practical consequence is a classification of this package's bases by which
|
|
42
|
+
gates are left free during training:
|
|
43
|
+
|
|
44
|
+
* free only the controlled-phase gates --- the compact-CP tensors --- and
|
|
45
|
+
mu is pinned at 1 no matter how hard the basis is trained, on any
|
|
46
|
+
objective, with no penalty, constraint or monitoring;
|
|
47
|
+
* free the Hadamard / U(4) gates as well and the guarantee is void. It is
|
|
48
|
+
void in practice, not only in principle: randomising them on an (m, n) =
|
|
49
|
+
(3, 3) QFTBasis reaches mu = 24.8 of a possible 64, and on RichBasis ---
|
|
50
|
+
which has no diagonal gates at all --- mu = 32.5.
|
|
51
|
+
|
|
52
|
+
`certify_flat_modulus` performs that check for a given training configuration,
|
|
53
|
+
taking the same `frozen_indices` that `train_basis_batched` accepts, so the
|
|
54
|
+
question "does this training run preserve incoherence?" can be answered before
|
|
55
|
+
the run rather than measured after it.
|
|
56
|
+
|
|
57
|
+
Discovered while applying this package's basis family to image *completion*,
|
|
58
|
+
where the reversal matters: the transform that compresses an image best is not
|
|
59
|
+
the one that completes it best.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
from __future__ import annotations
|
|
63
|
+
|
|
64
|
+
from dataclasses import dataclass, field
|
|
65
|
+
|
|
66
|
+
import jax
|
|
67
|
+
import jax.numpy as jnp
|
|
68
|
+
|
|
69
|
+
from .circuit.builder import is_compact_cp
|
|
70
|
+
|
|
71
|
+
Array = jax.Array
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"FlatModulusCertificate",
|
|
75
|
+
"certify_flat_modulus",
|
|
76
|
+
"coherence",
|
|
77
|
+
"dense_operator",
|
|
78
|
+
"diagonal_tensor_indices",
|
|
79
|
+
"is_flat_modulus",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def dense_operator(basis) -> Array:
|
|
84
|
+
"""The basis as an explicit matrix acting on `vec(image)`.
|
|
85
|
+
|
|
86
|
+
Column j is the transform of the image that is 1 at pixel j and 0
|
|
87
|
+
elsewhere, so the result is (2^m 2^n) x (2^m 2^n).
|
|
88
|
+
|
|
89
|
+
This is a diagnostic, not a code path: it costs N^2 transforms of an
|
|
90
|
+
N-pixel image and is meant for small m, n. Nothing in training needs it ---
|
|
91
|
+
`certify_flat_modulus` gives the parameter-space guarantee without forming
|
|
92
|
+
it.
|
|
93
|
+
"""
|
|
94
|
+
rows, cols = basis.image_size
|
|
95
|
+
dim = rows * cols
|
|
96
|
+
eye = jnp.eye(dim, dtype=jnp.complex128)
|
|
97
|
+
return jax.vmap(lambda e: basis.forward_transform(e.reshape(rows, cols)).reshape(-1))(eye).T
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def coherence(basis, operator: Array | None = None) -> float:
|
|
101
|
+
"""mu(U) = N max_ij |U_ij|^2, in [1, N]. 1 is maximal incoherence.
|
|
102
|
+
|
|
103
|
+
Pass `operator` to reuse a matrix from `dense_operator`.
|
|
104
|
+
"""
|
|
105
|
+
u = dense_operator(basis) if operator is None else operator
|
|
106
|
+
return float(u.shape[0] * jnp.max(jnp.abs(u) ** 2))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_flat_modulus(basis, operator: Array | None = None, atol: float = 1e-8) -> bool:
|
|
110
|
+
"""True if |U_ij| = N^{-1/2} everywhere, i.e. sqrt(N) U is complex Hadamard."""
|
|
111
|
+
u = dense_operator(basis) if operator is None else operator
|
|
112
|
+
return bool(jnp.allclose(jnp.abs(u), u.shape[0] ** -0.5, atol=atol))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def diagonal_tensor_indices(basis) -> list[int]:
|
|
116
|
+
"""Indices of the tensors that are diagonal in the computational basis.
|
|
117
|
+
|
|
118
|
+
These are the compact controlled-phase gates, `[[1, 1], [1, e^{i phi}]]`,
|
|
119
|
+
whose only freedom is a phase. Freeing exactly these is what preserves
|
|
120
|
+
mu == 1; see the module docstring.
|
|
121
|
+
"""
|
|
122
|
+
return [i for i, t in enumerate(basis.tensors) if is_compact_cp(t)]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class FlatModulusCertificate:
|
|
127
|
+
"""Whether mu == 1 holds over the whole reachable parameter set.
|
|
128
|
+
|
|
129
|
+
`holds` is the answer; `reason` says why in one line. `mu` is the coherence
|
|
130
|
+
at the basis's current parameters, and `offending_indices` names the
|
|
131
|
+
trainable tensors that are not diagonal, which are exactly the ones that
|
|
132
|
+
would have to be frozen for the guarantee to apply.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
holds: bool
|
|
136
|
+
reason: str
|
|
137
|
+
mu: float
|
|
138
|
+
offending_indices: list[int] = field(default_factory=list)
|
|
139
|
+
|
|
140
|
+
def __bool__(self) -> bool:
|
|
141
|
+
return self.holds
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def certify_flat_modulus(
|
|
145
|
+
basis,
|
|
146
|
+
frozen_indices: list[int] | None = None,
|
|
147
|
+
*,
|
|
148
|
+
atol: float = 1e-8,
|
|
149
|
+
) -> FlatModulusCertificate:
|
|
150
|
+
"""Certify that training cannot raise mu above 1.
|
|
151
|
+
|
|
152
|
+
Two conditions, both necessary. The basis must be flat-modulus *now*, and
|
|
153
|
+
every tensor left trainable must be diagonal --- then by the proposition in
|
|
154
|
+
the module docstring, mu == 1 for every value those parameters can take.
|
|
155
|
+
|
|
156
|
+
`frozen_indices` is the same argument `train_basis_batched` accepts, so:
|
|
157
|
+
|
|
158
|
+
cert = certify_flat_modulus(basis, frozen_indices=frozen)
|
|
159
|
+
if not cert:
|
|
160
|
+
raise ValueError(cert.reason)
|
|
161
|
+
result = train_basis_batched(basis, frozen_indices=frozen, ...)
|
|
162
|
+
|
|
163
|
+
Frozen non-diagonal gates are fine: a fixed Hadamard is what the
|
|
164
|
+
proposition assumes. It is *training* them that voids it.
|
|
165
|
+
"""
|
|
166
|
+
operator = dense_operator(basis)
|
|
167
|
+
mu = coherence(basis, operator)
|
|
168
|
+
|
|
169
|
+
if not is_flat_modulus(basis, operator, atol=atol):
|
|
170
|
+
return FlatModulusCertificate(
|
|
171
|
+
holds=False,
|
|
172
|
+
reason=(
|
|
173
|
+
f"basis is not flat-modulus at its current parameters "
|
|
174
|
+
f"(mu = {mu:.6g}); the guarantee applies to circuits with one "
|
|
175
|
+
f"Hadamard per wire and diagonal gates elsewhere"
|
|
176
|
+
),
|
|
177
|
+
mu=mu,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
frozen = set(frozen_indices or [])
|
|
181
|
+
diagonal = set(diagonal_tensor_indices(basis))
|
|
182
|
+
offending = [i for i in range(len(basis.tensors)) if i not in frozen and i not in diagonal]
|
|
183
|
+
|
|
184
|
+
if offending:
|
|
185
|
+
return FlatModulusCertificate(
|
|
186
|
+
holds=False,
|
|
187
|
+
reason=(
|
|
188
|
+
f"mu = {mu:.6g} now, but {len(offending)} trainable tensor(s) "
|
|
189
|
+
f"are not diagonal, so training may raise it; freeze indices "
|
|
190
|
+
f"{offending} to obtain the guarantee"
|
|
191
|
+
),
|
|
192
|
+
mu=mu,
|
|
193
|
+
offending_indices=offending,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return FlatModulusCertificate(
|
|
197
|
+
holds=True,
|
|
198
|
+
reason=(
|
|
199
|
+
f"mu == 1 for every reachable parameter value: all "
|
|
200
|
+
f"{len(basis.tensors) - len(frozen)} trainable tensor(s) are diagonal"
|
|
201
|
+
),
|
|
202
|
+
mu=mu,
|
|
203
|
+
)
|
|
@@ -71,8 +71,7 @@ def format_float_julia_like(x) -> str:
|
|
|
71
71
|
mantissa, exp = s.split("e", 1)
|
|
72
72
|
|
|
73
73
|
# Python always includes sign in exponent (e+20, e-07); Julia drops '+'.
|
|
74
|
-
|
|
75
|
-
exp = exp[1:]
|
|
74
|
+
exp = exp.removeprefix("+")
|
|
76
75
|
|
|
77
76
|
# Strip leading zeros in the exponent magnitude, but keep at least one digit.
|
|
78
77
|
if exp.startswith("-"):
|
|
@@ -19,14 +19,12 @@ Array = jax.Array
|
|
|
19
19
|
class AbstractLoss(Protocol):
|
|
20
20
|
"""Marker protocol for loss types. No behavior; dispatch is functional."""
|
|
21
21
|
|
|
22
|
-
...
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
@dataclass(frozen=True)
|
|
26
25
|
class L1Norm:
|
|
27
26
|
"""L1 norm loss: minimizes `sum(|T(x)|)` to encourage sparsity."""
|
|
28
27
|
|
|
29
|
-
pass
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
@dataclass(frozen=True)
|