pdft 0.2.2__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.2 → pdft-0.2.4}/.claude/CLAUDE.md +2 -2
- pdft-0.2.4/PKG-INFO +202 -0
- pdft-0.2.4/README.md +143 -0
- {pdft-0.2.2 → pdft-0.2.4}/pyproject.toml +25 -2
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/__init__.py +13 -5
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/bases/__init__.py +18 -4
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/bases/base.py +124 -4
- pdft-0.2.4/src/pdft/bases/block/__init__.py +15 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/bases/block/block.py +1 -1
- pdft-0.2.4/src/pdft/bases/circuit/__init__.py +30 -0
- pdft-0.2.4/src/pdft/bases/circuit/dct4.py +189 -0
- pdft-0.2.4/src/pdft/bases/circuit/freeze.py +116 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/bases/circuit/mera.py +49 -16
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/bases/circuit/tebd.py +54 -39
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/circuit/__init__.py +2 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/circuit/builder.py +56 -22
- pdft-0.2.4/src/pdft/coherence.py +203 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/io/serialize.py +1 -2
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/loss.py +0 -2
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/manifolds.py +13 -22
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/optimizers/loop.py +1 -1
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/profiling.py +1 -1
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/training/batched.py +2 -2
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/viz/circuit.py +2 -1
- pdft-0.2.4/tests/bases/circuit/test_dct4.py +186 -0
- pdft-0.2.4/tests/bases/circuit/test_freeze.py +169 -0
- pdft-0.2.4/tests/circuit/test_cry_gate.py +38 -0
- pdft-0.2.4/tests/circuit/test_sorted_gate_program.py +26 -0
- pdft-0.2.4/tests/test_coherence.py +147 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/test_manifolds.py +22 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/test_profiling.py +0 -1
- {pdft-0.2.2 → pdft-0.2.4}/tests/training/test_batched.py +26 -32
- {pdft-0.2.2 → pdft-0.2.4}/tests/viz/test_loss.py +7 -2
- pdft-0.2.2/PKG-INFO +0 -100
- pdft-0.2.2/README.md +0 -41
- pdft-0.2.2/src/pdft/bases/block/__init__.py +0 -12
- pdft-0.2.2/src/pdft/bases/circuit/__init__.py +0 -15
- {pdft-0.2.2 → pdft-0.2.4}/.github/workflows/ci.yml +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/.github/workflows/publish.yml +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/.github/workflows/verify-upstream-pin.yml +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/.gitignore +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/.pre-commit-config.yaml +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/LICENSE +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/Makefile +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/examples/basis_demo.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/examples/mera_demo.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/examples/optimizer_benchmark.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/README.md +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/adam_trajectory_4x4.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/basis_roundtrip.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/basis_roundtrip_hash.txt +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/compression_roundtrip_4x4.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/entangled_qft_4x4.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/ft_mat_roundtrip.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/loss_values.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/manifest.json +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/manifold_project_retract.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/mera_4x4.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/phase_extraction_3x3.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/qft_basis_trained.json +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/qft_code_16x16.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/qft_code_4x4.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/tebd_4x4.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/train_trajectory_4x4.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/goldens/train_trajectory_4x4_long.npz +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/julia/Manifest.toml +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/julia/Project.toml +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/reference/julia/generate_goldens.jl +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/bases/circuit/entangled_qft.py +2 -2
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/bases/circuit/qft.py +2 -2
- {pdft-0.2.2/src/pdft/bases/block → pdft-0.2.4/src/pdft/bases/circuit}/real_rich.py +0 -0
- {pdft-0.2.2/src/pdft/bases/block → pdft-0.2.4/src/pdft/bases/circuit}/rich.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/circuit/cache.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/io/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/io/compression.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/optimizers/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/optimizers/adam.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/optimizers/core.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/optimizers/gd.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/training/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/training/adam_step.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/training/eval_loop.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/training/result.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/training/schedules.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/training/single.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/viz/__init__.py +1 -1
- {pdft-0.2.2 → pdft-0.2.4}/src/pdft/viz/loss.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/block/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/block/test_block.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/circuit/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/circuit/test_qft.py +0 -0
- {pdft-0.2.2/tests/bases/block → pdft-0.2.4/tests/bases/circuit}/test_real_rich.py +0 -0
- {pdft-0.2.2/tests/bases/block → pdft-0.2.4/tests/bases/circuit}/test_rich.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/conftest.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/test_base.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/test_new_bases.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/bases/test_phase_extraction.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/circuit/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/circuit/test_cache.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/circuit/test_h_inverse_leg_swap.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/circuit/test_high_qubit_circuit.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/circuit/test_legacy_builder.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/conftest.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/io/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/io/conftest.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/io/test_compression.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/io/test_serialize.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/optimizers/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/optimizers/conftest.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/optimizers/test_adam.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/optimizers/test_gd.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/conftest.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_adam.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_compression.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_long_run.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_loss.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_manifolds.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_new_bases.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_qft.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_scale.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_serialize.py +1 -1
- {pdft-0.2.2 → pdft-0.2.4}/tests/parity/test_training.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/test_basis_seeded_init.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/test_loss.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/test_smoke.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/training/__init__.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/training/conftest.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/training/test_eval_loop.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/training/test_integration.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/training/test_single.py +0 -0
- {pdft-0.2.2 → pdft-0.2.4}/tests/viz/__init__.py +0 -0
|
@@ -80,8 +80,8 @@ src/pdft/
|
|
|
80
80
|
├── profiling.py Cross-cutting profiling helpers
|
|
81
81
|
├── bases/
|
|
82
82
|
│ ├── base.py AbstractSparseBasis + bases_allclose + 4 basis dataclasses
|
|
83
|
-
│ ├── circuit/ QFT, EntangledQFT, TEBD, MERA
|
|
84
|
-
│ └── block/
|
|
83
|
+
│ ├── circuit/ QFT, EntangledQFT, TEBD, MERA, Rich, RealRich + freeze_as_blocked
|
|
84
|
+
│ └── block/ BlockedBasis (Rich/RealRich re-exported from circuit for back-compat)
|
|
85
85
|
├── circuit/ Einsum builder (builder.py) + JIT closure cache (cache.py)
|
|
86
86
|
├── optimizers/ core, gd (RiemannianGD + Armijo), adam (RiemannianAdam), loop
|
|
87
87
|
├── training/ schedules, single (train_basis), batched, adam_step, eval_loop
|
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,14 +42,15 @@ 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,
|
|
53
|
+
DCT4Basis,
|
|
52
54
|
EntangledQFTBasis,
|
|
53
55
|
MERABasis,
|
|
54
56
|
QFTBasis,
|
|
@@ -56,15 +58,18 @@ from .bases import ( # noqa: E402
|
|
|
56
58
|
RichBasis,
|
|
57
59
|
TEBDBasis,
|
|
58
60
|
bases_allclose,
|
|
61
|
+
freeze_as_blocked,
|
|
59
62
|
)
|
|
60
|
-
from .
|
|
61
|
-
from .
|
|
62
|
-
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
|
|
63
67
|
|
|
64
68
|
__all__ = [
|
|
65
69
|
"AbstractLoss",
|
|
66
70
|
"AbstractSparseBasis",
|
|
67
71
|
"BlockedBasis",
|
|
72
|
+
"DCT4Basis",
|
|
68
73
|
"EntangledQFTBasis",
|
|
69
74
|
"L1Norm",
|
|
70
75
|
"MERABasis",
|
|
@@ -79,6 +84,9 @@ __all__ = [
|
|
|
79
84
|
"__upstream_ref__",
|
|
80
85
|
"__version__",
|
|
81
86
|
"bases_allclose",
|
|
87
|
+
"certify_flat_modulus",
|
|
88
|
+
"coherence",
|
|
89
|
+
"freeze_as_blocked",
|
|
82
90
|
"loss_function",
|
|
83
91
|
"optimize",
|
|
84
92
|
"train_basis",
|
|
@@ -3,26 +3,39 @@
|
|
|
3
3
|
Two families:
|
|
4
4
|
- bases.circuit — full circuit topologies (QFT, EntangledQFT, TEBD, MERA),
|
|
5
5
|
comparable to FFT/DCT.
|
|
6
|
-
- bases.block —
|
|
7
|
-
|
|
6
|
+
- bases.block — BlockedBasis over arbitrary block partitions (Rich/RealRich
|
|
7
|
+
are re-exported here for back-compat; they live in circuit).
|
|
8
8
|
|
|
9
9
|
The abstract base class and bases_allclose helper live in bases.base.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from .base import (
|
|
13
13
|
AbstractSparseBasis,
|
|
14
|
+
DCT4Basis,
|
|
14
15
|
EntangledQFTBasis,
|
|
15
16
|
MERABasis,
|
|
16
17
|
QFTBasis,
|
|
17
18
|
TEBDBasis,
|
|
18
19
|
bases_allclose,
|
|
19
20
|
)
|
|
20
|
-
from .block import BlockedBasis
|
|
21
|
-
from .circuit import
|
|
21
|
+
from .block import BlockedBasis
|
|
22
|
+
from .circuit import (
|
|
23
|
+
RealRichBasis,
|
|
24
|
+
RichBasis,
|
|
25
|
+
entangled_qft_code,
|
|
26
|
+
fit_to_dct,
|
|
27
|
+
freeze_as_blocked,
|
|
28
|
+
ft_mat,
|
|
29
|
+
ift_mat,
|
|
30
|
+
mera_code,
|
|
31
|
+
qft_code,
|
|
32
|
+
tebd_code,
|
|
33
|
+
)
|
|
22
34
|
|
|
23
35
|
__all__ = [
|
|
24
36
|
"AbstractSparseBasis",
|
|
25
37
|
"BlockedBasis",
|
|
38
|
+
"DCT4Basis",
|
|
26
39
|
"EntangledQFTBasis",
|
|
27
40
|
"MERABasis",
|
|
28
41
|
"QFTBasis",
|
|
@@ -32,6 +45,7 @@ __all__ = [
|
|
|
32
45
|
"bases_allclose",
|
|
33
46
|
"entangled_qft_code",
|
|
34
47
|
"fit_to_dct",
|
|
48
|
+
"freeze_as_blocked",
|
|
35
49
|
"ft_mat",
|
|
36
50
|
"ift_mat",
|
|
37
51
|
"mera_code",
|
|
@@ -291,11 +291,15 @@ class TEBDBasis:
|
|
|
291
291
|
code: object | None = None,
|
|
292
292
|
inv_code: object | None = None,
|
|
293
293
|
seed: int | None = None,
|
|
294
|
+
parametrization: str = "cp",
|
|
294
295
|
):
|
|
295
296
|
"""When `seed` is provided AND `phases` is None, draws phases from
|
|
296
297
|
`np.random.default_rng(seed).normal(0, 0.1, n_row_gates + n_col_gates)`
|
|
297
298
|
— Julia's `randn(n_gates) * 0.1` convention from
|
|
298
299
|
`ParametricDFT.jl/src/training.jl::_init_circuit`.
|
|
300
|
+
|
|
301
|
+
`parametrization` is "cp" (diagonal ring gates on U(1)^4) or "u4"
|
|
302
|
+
(dense two-qubit ring gates on U(4), the canonical TEBD gate).
|
|
299
303
|
"""
|
|
300
304
|
import numpy as np
|
|
301
305
|
|
|
@@ -307,8 +311,12 @@ class TEBDBasis:
|
|
|
307
311
|
self.n = n
|
|
308
312
|
if phases is None and seed is not None:
|
|
309
313
|
phases = list(np.random.default_rng(seed).normal(0.0, 0.1, m + n))
|
|
310
|
-
_code, init_tensors, self.n_row_gates, self.n_col_gates = tebd_code(
|
|
311
|
-
|
|
314
|
+
_code, init_tensors, self.n_row_gates, self.n_col_gates = tebd_code(
|
|
315
|
+
m, n, phases=phases, parametrization=parametrization
|
|
316
|
+
)
|
|
317
|
+
_inv_code, _, _, _ = tebd_code(
|
|
318
|
+
m, n, phases=phases, inverse=True, parametrization=parametrization
|
|
319
|
+
)
|
|
312
320
|
self.tensors = list(tensors) if tensors is not None else init_tensors
|
|
313
321
|
self.code = code if code is not None else _code
|
|
314
322
|
self.inv_code = inv_code if inv_code is not None else _inv_code
|
|
@@ -394,11 +402,15 @@ class MERABasis:
|
|
|
394
402
|
code: object | None = None,
|
|
395
403
|
inv_code: object | None = None,
|
|
396
404
|
seed: int | None = None,
|
|
405
|
+
parametrization: str = "cp",
|
|
397
406
|
):
|
|
398
407
|
"""When `seed` is provided AND `phases` is None, draws phases from
|
|
399
408
|
`np.random.default_rng(seed).normal(0, 0.1, n_gates)` — Julia's
|
|
400
409
|
`randn(n_gates) * 0.1` convention from
|
|
401
410
|
`ParametricDFT.jl/src/training.jl::_init_circuit`.
|
|
411
|
+
|
|
412
|
+
`parametrization` is "cp" (diagonal disentanglers/isometries on
|
|
413
|
+
U(1)^4) or "u4" (dense two-qubit gates on U(4), the canonical form).
|
|
402
414
|
"""
|
|
403
415
|
import numpy as np
|
|
404
416
|
|
|
@@ -412,8 +424,12 @@ class MERABasis:
|
|
|
412
424
|
n_row = _n_mera_gates(m) if m >= 2 else 0
|
|
413
425
|
n_col = _n_mera_gates(n) if n >= 2 else 0
|
|
414
426
|
phases = list(np.random.default_rng(seed).normal(0.0, 0.1, n_row + n_col))
|
|
415
|
-
_code, init_tensors, self.n_row_gates, self.n_col_gates = mera_code(
|
|
416
|
-
|
|
427
|
+
_code, init_tensors, self.n_row_gates, self.n_col_gates = mera_code(
|
|
428
|
+
m, n, phases=phases, parametrization=parametrization
|
|
429
|
+
)
|
|
430
|
+
_inv_code, _, _, _ = mera_code(
|
|
431
|
+
m, n, phases=phases, inverse=True, parametrization=parametrization
|
|
432
|
+
)
|
|
417
433
|
self.tensors = list(tensors) if tensors is not None else init_tensors
|
|
418
434
|
self.code = code if code is not None else _code
|
|
419
435
|
self.inv_code = inv_code if inv_code is not None else _inv_code
|
|
@@ -468,3 +484,107 @@ def _mera_unflatten(aux, leaves) -> MERABasis:
|
|
|
468
484
|
|
|
469
485
|
|
|
470
486
|
tree_util.register_pytree_node(MERABasis, _mera_flatten, _mera_unflatten)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# ---------------------------------------------------------------------------
|
|
490
|
+
# DCT4Basis
|
|
491
|
+
# ---------------------------------------------------------------------------
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@dataclass
|
|
495
|
+
class DCT4Basis:
|
|
496
|
+
"""DCT-IV tensor-network basis: the real-orthogonal, ancilla-free analogue
|
|
497
|
+
of :class:`QFTBasis`.
|
|
498
|
+
|
|
499
|
+
Mirrors QFTBasis (one tensor list; `inverse_transform` applies
|
|
500
|
+
`conj(tensors)` through `inv_code`). The gates are emitted by
|
|
501
|
+
:func:`pdft.bases.circuit.dct4.dct4_code`; at initialization the forward
|
|
502
|
+
operator is the bit-reversed orthonormal DCT-IV per dimension, and — since
|
|
503
|
+
DCT-IV is self-inverse — the basis reconstructs exactly. ``tensors`` holds
|
|
504
|
+
every gate — the affine ``R_y`` rotation layer, the branch Hadamards, the
|
|
505
|
+
mirror-``Q`` CNOT permutations and the ``Delta`` sign — each a learnable
|
|
506
|
+
leaf on its auto-selected Riemannian manifold (O(2) / O(4) / phase). The
|
|
507
|
+
gate tensors are real-valued (stored complex128, zero imaginary), so the
|
|
508
|
+
unitary manifold trains the real-orthogonal subset under a real objective:
|
|
509
|
+
exact DCT-IV at init, then relaxed, just as QFT relaxes within U.
|
|
510
|
+
|
|
511
|
+
Pytree contract:
|
|
512
|
+
leaves = tensors
|
|
513
|
+
aux data = (m, n, len(tensors), code, inv_code)
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
m: int
|
|
517
|
+
n: int
|
|
518
|
+
tensors: list[Array]
|
|
519
|
+
code: object = field(compare=False, repr=False)
|
|
520
|
+
inv_code: object = field(compare=False, repr=False)
|
|
521
|
+
|
|
522
|
+
def __init__(
|
|
523
|
+
self,
|
|
524
|
+
m: int,
|
|
525
|
+
n: int,
|
|
526
|
+
tensors: Sequence[Array] | None = None,
|
|
527
|
+
code: object | None = None,
|
|
528
|
+
inv_code: object | None = None,
|
|
529
|
+
parametrization: str = "o4",
|
|
530
|
+
):
|
|
531
|
+
if m < 1 or n < 1:
|
|
532
|
+
raise ValueError(f"m and n must be >= 1, got m={m}, n={n}")
|
|
533
|
+
from .circuit.dct4 import dct4_code
|
|
534
|
+
|
|
535
|
+
self.m = m
|
|
536
|
+
self.n = n
|
|
537
|
+
_code, init_tensors = dct4_code(m, n, parametrization=parametrization)
|
|
538
|
+
_inv_code, _ = dct4_code(m, n, inverse=True, parametrization=parametrization)
|
|
539
|
+
self.tensors = list(tensors) if tensors is not None else init_tensors
|
|
540
|
+
self.code = code if code is not None else _code
|
|
541
|
+
self.inv_code = inv_code if inv_code is not None else _inv_code
|
|
542
|
+
|
|
543
|
+
@property
|
|
544
|
+
def inv_tensors(self) -> list[Array]:
|
|
545
|
+
return self.tensors
|
|
546
|
+
|
|
547
|
+
@property
|
|
548
|
+
def image_size(self) -> tuple[int, int]:
|
|
549
|
+
return (2**self.m, 2**self.n)
|
|
550
|
+
|
|
551
|
+
@property
|
|
552
|
+
def num_parameters(self) -> int:
|
|
553
|
+
return sum(int(t.size) for t in self.tensors)
|
|
554
|
+
|
|
555
|
+
def forward_transform(self, pic: Array) -> Array:
|
|
556
|
+
from .circuit.dct4 import dct4_ft_mat
|
|
557
|
+
|
|
558
|
+
return dct4_ft_mat(self.tensors, self.code, self.m, self.n, pic)
|
|
559
|
+
|
|
560
|
+
def inverse_transform(self, pic: Array) -> Array:
|
|
561
|
+
from .circuit.dct4 import dct4_ift_mat
|
|
562
|
+
|
|
563
|
+
return dct4_ift_mat(
|
|
564
|
+
[jnp.conj(t) for t in self.tensors],
|
|
565
|
+
self.inv_code,
|
|
566
|
+
self.m,
|
|
567
|
+
self.n,
|
|
568
|
+
pic,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _dct4basis_flatten(b: DCT4Basis):
|
|
573
|
+
leaves = tuple(b.tensors)
|
|
574
|
+
aux = (b.m, b.n, len(b.tensors), b.code, b.inv_code)
|
|
575
|
+
return leaves, aux
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _dct4basis_unflatten(aux, leaves) -> DCT4Basis:
|
|
579
|
+
m, n, n_fwd, code, inv_code = aux
|
|
580
|
+
assert len(leaves) == n_fwd
|
|
581
|
+
out = DCT4Basis.__new__(DCT4Basis)
|
|
582
|
+
out.m = m
|
|
583
|
+
out.n = n
|
|
584
|
+
out.tensors = list(leaves)
|
|
585
|
+
out.code = code
|
|
586
|
+
out.inv_code = inv_code
|
|
587
|
+
return out
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
tree_util.register_pytree_node(DCT4Basis, _dct4basis_flatten, _dct4basis_unflatten)
|