pdft 0.2.1__tar.gz → 0.2.3__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.1 → pdft-0.2.3}/.claude/CLAUDE.md +2 -2
- {pdft-0.2.1 → pdft-0.2.3}/PKG-INFO +1 -1
- {pdft-0.2.1 → pdft-0.2.3}/pyproject.toml +1 -1
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/__init__.py +5 -1
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/bases/__init__.py +18 -4
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/bases/base.py +124 -4
- pdft-0.2.3/src/pdft/bases/block/__init__.py +15 -0
- pdft-0.2.3/src/pdft/bases/circuit/__init__.py +30 -0
- pdft-0.2.3/src/pdft/bases/circuit/dct4.py +189 -0
- pdft-0.2.3/src/pdft/bases/circuit/freeze.py +116 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/bases/circuit/mera.py +47 -14
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/bases/circuit/tebd.py +52 -37
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/circuit/__init__.py +2 -0
- pdft-0.2.3/src/pdft/circuit/builder.py +411 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/loss.py +7 -1
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/manifolds.py +13 -22
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/optimizers/core.py +15 -2
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/optimizers/loop.py +2 -1
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/training/adam_step.py +30 -3
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/training/batched.py +61 -0
- pdft-0.2.3/tests/bases/circuit/test_dct4.py +186 -0
- pdft-0.2.3/tests/bases/circuit/test_freeze.py +170 -0
- pdft-0.2.3/tests/circuit/test_cry_gate.py +38 -0
- pdft-0.2.3/tests/circuit/test_h_inverse_leg_swap.py +142 -0
- pdft-0.2.3/tests/circuit/test_high_qubit_circuit.py +62 -0
- pdft-0.2.3/tests/circuit/test_legacy_builder.py +146 -0
- pdft-0.2.3/tests/circuit/test_sorted_gate_program.py +26 -0
- pdft-0.2.3/tests/test_loss.py +93 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/test_manifolds.py +22 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/training/test_batched.py +226 -0
- pdft-0.2.1/src/pdft/bases/block/__init__.py +0 -12
- pdft-0.2.1/src/pdft/bases/circuit/__init__.py +0 -15
- pdft-0.2.1/src/pdft/circuit/builder.py +0 -242
- pdft-0.2.1/tests/test_loss.py +0 -45
- {pdft-0.2.1 → pdft-0.2.3}/.github/workflows/ci.yml +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/.github/workflows/publish.yml +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/.github/workflows/verify-upstream-pin.yml +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/.gitignore +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/.pre-commit-config.yaml +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/LICENSE +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/Makefile +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/README.md +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/examples/basis_demo.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/examples/mera_demo.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/examples/optimizer_benchmark.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/README.md +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/adam_trajectory_4x4.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/basis_roundtrip.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/basis_roundtrip_hash.txt +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/compression_roundtrip_4x4.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/entangled_qft_4x4.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/ft_mat_roundtrip.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/loss_values.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/manifest.json +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/manifold_project_retract.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/mera_4x4.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/phase_extraction_3x3.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/qft_basis_trained.json +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/qft_code_16x16.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/qft_code_4x4.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/tebd_4x4.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/train_trajectory_4x4.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/goldens/train_trajectory_4x4_long.npz +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/julia/Manifest.toml +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/julia/Project.toml +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/reference/julia/generate_goldens.jl +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/bases/block/block.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/bases/circuit/entangled_qft.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/bases/circuit/qft.py +0 -0
- {pdft-0.2.1/src/pdft/bases/block → pdft-0.2.3/src/pdft/bases/circuit}/real_rich.py +0 -0
- {pdft-0.2.1/src/pdft/bases/block → pdft-0.2.3/src/pdft/bases/circuit}/rich.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/circuit/cache.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/io/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/io/compression.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/io/serialize.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/optimizers/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/optimizers/adam.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/optimizers/gd.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/profiling.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/training/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/training/eval_loop.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/training/result.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/training/schedules.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/training/single.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/viz/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/viz/circuit.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/src/pdft/viz/loss.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/block/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/block/test_block.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/circuit/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/circuit/test_qft.py +0 -0
- {pdft-0.2.1/tests/bases/block → pdft-0.2.3/tests/bases/circuit}/test_real_rich.py +0 -0
- {pdft-0.2.1/tests/bases/block → pdft-0.2.3/tests/bases/circuit}/test_rich.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/conftest.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/test_base.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/test_new_bases.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/bases/test_phase_extraction.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/circuit/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/circuit/test_cache.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/conftest.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/io/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/io/conftest.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/io/test_compression.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/io/test_serialize.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/optimizers/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/optimizers/conftest.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/optimizers/test_adam.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/optimizers/test_gd.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/conftest.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_adam.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_compression.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_long_run.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_loss.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_manifolds.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_new_bases.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_qft.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_scale.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_serialize.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/parity/test_training.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/test_basis_seeded_init.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/test_profiling.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/test_smoke.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/training/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/training/conftest.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/training/test_eval_loop.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/training/test_integration.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/training/test_single.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/viz/__init__.py +0 -0
- {pdft-0.2.1 → pdft-0.2.3}/tests/viz/test_loss.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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdft
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Python port of ParametricDFT.jl: learning parametric quantum Fourier transforms via manifold optimization.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zazabap/pdft
|
|
6
6
|
Project-URL: Issues, https://github.com/zazabap/pdft/issues
|
|
@@ -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.3"
|
|
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"
|
|
@@ -41,7 +41,7 @@ if not _os.environ.get("PDFT_DISABLE_COMPILE_CACHE"):
|
|
|
41
41
|
except OSError:
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
|
-
__version__ = "0.2.
|
|
44
|
+
__version__ = "0.2.3"
|
|
45
45
|
__upstream_ref__ = "nzy1997/ParametricDFT.jl@a201a27e47df2f0f3ab460f83d49b6e5f5d1e9ef"
|
|
46
46
|
|
|
47
47
|
# Slim public re-export hub: most-used names only. Anything else: import
|
|
@@ -49,6 +49,7 @@ __upstream_ref__ = "nzy1997/ParametricDFT.jl@a201a27e47df2f0f3ab460f83d49b6e5f5d
|
|
|
49
49
|
from .bases import ( # noqa: E402
|
|
50
50
|
AbstractSparseBasis,
|
|
51
51
|
BlockedBasis,
|
|
52
|
+
DCT4Basis,
|
|
52
53
|
EntangledQFTBasis,
|
|
53
54
|
MERABasis,
|
|
54
55
|
QFTBasis,
|
|
@@ -56,6 +57,7 @@ from .bases import ( # noqa: E402
|
|
|
56
57
|
RichBasis,
|
|
57
58
|
TEBDBasis,
|
|
58
59
|
bases_allclose,
|
|
60
|
+
freeze_as_blocked,
|
|
59
61
|
)
|
|
60
62
|
from .loss import AbstractLoss, L1Norm, MSELoss, loss_function # noqa: E402
|
|
61
63
|
from .optimizers import RiemannianAdam, RiemannianGD, optimize # noqa: E402
|
|
@@ -65,6 +67,7 @@ __all__ = [
|
|
|
65
67
|
"AbstractLoss",
|
|
66
68
|
"AbstractSparseBasis",
|
|
67
69
|
"BlockedBasis",
|
|
70
|
+
"DCT4Basis",
|
|
68
71
|
"EntangledQFTBasis",
|
|
69
72
|
"L1Norm",
|
|
70
73
|
"MERABasis",
|
|
@@ -79,6 +82,7 @@ __all__ = [
|
|
|
79
82
|
"__upstream_ref__",
|
|
80
83
|
"__version__",
|
|
81
84
|
"bases_allclose",
|
|
85
|
+
"freeze_as_blocked",
|
|
82
86
|
"loss_function",
|
|
83
87
|
"optimize",
|
|
84
88
|
"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)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Block-structured bases: BlockedBasis.
|
|
2
|
+
|
|
3
|
+
RichBasis/RealRichBasis/fit_to_dct now live in bases.circuit (they are full
|
|
4
|
+
circuits); re-exported here for back-compat.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ..circuit import RealRichBasis, RichBasis, fit_to_dct
|
|
8
|
+
from .block import BlockedBasis
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BlockedBasis",
|
|
12
|
+
"RealRichBasis",
|
|
13
|
+
"RichBasis",
|
|
14
|
+
"fit_to_dct",
|
|
15
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Circuit-topology bases: QFT, DCT-IV, EntangledQFT, TEBD, MERA, Rich, RealRich.
|
|
2
|
+
|
|
3
|
+
Rich/RealRich are H + U(4) circuits on the QFT topology — full-image
|
|
4
|
+
transforms usable standalone or as a BlockedBasis inner.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .qft import ft_mat, ift_mat, qft_code
|
|
8
|
+
from .dct4 import dct4_code, dct4_ft_mat, dct4_ift_mat
|
|
9
|
+
from .entangled_qft import entangled_qft_code
|
|
10
|
+
from .mera import mera_code
|
|
11
|
+
from .tebd import tebd_code
|
|
12
|
+
from .rich import RichBasis, fit_to_dct
|
|
13
|
+
from .real_rich import RealRichBasis
|
|
14
|
+
from .freeze import freeze_as_blocked
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"RealRichBasis",
|
|
18
|
+
"RichBasis",
|
|
19
|
+
"dct4_code",
|
|
20
|
+
"dct4_ft_mat",
|
|
21
|
+
"dct4_ift_mat",
|
|
22
|
+
"entangled_qft_code",
|
|
23
|
+
"fit_to_dct",
|
|
24
|
+
"freeze_as_blocked",
|
|
25
|
+
"ft_mat",
|
|
26
|
+
"ift_mat",
|
|
27
|
+
"mera_code",
|
|
28
|
+
"qft_code",
|
|
29
|
+
"tebd_code",
|
|
30
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""DCT-IV circuit as a hand-rolled tensor network.
|
|
2
|
+
|
|
3
|
+
Real-orthogonal analogue of the QFT (cf. :mod:`pdft.bases.circuit.qft`). The
|
|
4
|
+
DCT-IV is the self-dual "bulk" cosine transform: real, orthogonal, and its
|
|
5
|
+
own inverse. Unlike the DCT-II it is **ancilla-free** and uses only 1- and
|
|
6
|
+
2-qubit gates, so it drops straight into the H + 2-qubit-gate tensor-network
|
|
7
|
+
builder with no multi-controlled scaffolding.
|
|
8
|
+
|
|
9
|
+
The 1D recursion is *uniform* — both halves run the same child, so DCT-IV
|
|
10
|
+
unrolls into n qubit-local stages with no multi-controls (the real-orthogonal
|
|
11
|
+
analogue of the QFT butterfly). For N = 2M, with top qubit b splitting the
|
|
12
|
+
register (cf. ``all_real_dct_one_ancilla.tex`` §3.2):
|
|
13
|
+
|
|
14
|
+
C^IV_{2M} = P_{2M} (H (x) I_M) (I_M (+) D_M) (I_2 (x) C^IV_M)
|
|
15
|
+
(I_M (+) R_M) T_{2M} Q_{2M}
|
|
16
|
+
|
|
17
|
+
Per level k on the within-block qubits (b = top qubit), in emission order
|
|
18
|
+
(= the operator product above read right-to-left):
|
|
19
|
+
|
|
20
|
+
* Q pair layout : CX(control=b, target=q) for each q below b
|
|
21
|
+
* T rotation : R_y(pi / 2N) on b, then CR_y(pi 2^p / N) on b controlled
|
|
22
|
+
by each lower bit — the affine phase gradient (one base
|
|
23
|
+
rotation + one controlled rotation per bit, never a 2^m
|
|
24
|
+
multiplexer)
|
|
25
|
+
* R reverse : the mirror-Q permutation repeated (odd-branch reversal)
|
|
26
|
+
* recurse : C^IV_M on the lower register, uncontrolled (I_2 (x) C^IV_M)
|
|
27
|
+
* D sign Delta : CZ(control=b, target=next qubit)
|
|
28
|
+
* H merge : H on b
|
|
29
|
+
|
|
30
|
+
The interleave P_{2M} is deferred: per-level interleaves compose into a single
|
|
31
|
+
bit reversal, realised as the bit-reversed output convention (matching
|
|
32
|
+
:func:`qft_code`), so no explicit P gate is emitted.
|
|
33
|
+
|
|
34
|
+
At init the forward operator equals the bit-reversed orthonormal DCT-IV per
|
|
35
|
+
dimension. Every gate is a learnable leaf on its auto-selected Riemannian
|
|
36
|
+
manifold — R_y and branch-H on O(2), CR_y and mirror-Q on O(4), the Delta
|
|
37
|
+
sign on the phase manifold. Like QFT/RealRich, real init plus a real objective
|
|
38
|
+
keep the operator real-orthogonal as it relaxes from the exact transform (cf.
|
|
39
|
+
``all_real_dct_zero_ancilla.tex``).
|
|
40
|
+
|
|
41
|
+
2D DCT-IV = (m-qubit DCT-IV on row qubits) tensor (n-qubit DCT-IV on col
|
|
42
|
+
qubits); no entanglement between blocks.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
from collections.abc import Callable
|
|
48
|
+
|
|
49
|
+
import jax
|
|
50
|
+
import jax.numpy as jnp
|
|
51
|
+
import numpy as np
|
|
52
|
+
|
|
53
|
+
from ...circuit.builder import (
|
|
54
|
+
HADAMARD,
|
|
55
|
+
Gate,
|
|
56
|
+
apply_circuit,
|
|
57
|
+
compile_circuit,
|
|
58
|
+
controlled_phase_diag,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
Array = jax.Array
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"dct4_code",
|
|
66
|
+
"dct4_ft_mat",
|
|
67
|
+
"dct4_ift_mat",
|
|
68
|
+
"_dct4_gates_1d",
|
|
69
|
+
"_cry",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _ry(theta: float) -> Array:
|
|
74
|
+
"""Single-qubit real R_y(theta) as a (2, 2) complex tensor `T[out, in]`."""
|
|
75
|
+
c, s = np.cos(theta / 2.0), np.sin(theta / 2.0)
|
|
76
|
+
return jnp.asarray(np.array([[c, -s], [s, c]]), dtype=jnp.complex128)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _cnot_u4() -> Array:
|
|
80
|
+
"""CNOT as a (2, 2, 2, 2) tensor `T[out_ctrl, out_tgt, in_ctrl, in_tgt]`."""
|
|
81
|
+
M = np.zeros((2, 2, 2, 2))
|
|
82
|
+
for ic in (0, 1):
|
|
83
|
+
for it in (0, 1):
|
|
84
|
+
M[ic, it ^ ic, ic, it] = 1.0
|
|
85
|
+
return jnp.asarray(M, dtype=jnp.complex128)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _cry_u4(theta: float) -> Array:
|
|
89
|
+
"""Controlled-R_y(theta) as a (2, 2, 2, 2) tensor (control fires on 1)."""
|
|
90
|
+
c, s = np.cos(theta / 2.0), np.sin(theta / 2.0)
|
|
91
|
+
R = np.array([[c, -s], [s, c]])
|
|
92
|
+
M = np.zeros((2, 2, 2, 2))
|
|
93
|
+
for it in (0, 1):
|
|
94
|
+
M[0, it, 0, it] = 1.0 # control = 0 -> identity on target
|
|
95
|
+
for ot in (0, 1):
|
|
96
|
+
for it in (0, 1):
|
|
97
|
+
M[1, ot, 1, it] = R[ot, it] # control = 1 -> R_y on target
|
|
98
|
+
return jnp.asarray(M, dtype=jnp.complex128)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _cry(theta: float) -> Array:
|
|
102
|
+
"""Controlled-R_y trainable leaf: just the (2, 2) R_y block.
|
|
103
|
+
|
|
104
|
+
Applied with control structure by the builder's ``CRY`` kind (identity on
|
|
105
|
+
control = 0, this block on control = 1), so the leaf trains on O(2) — the
|
|
106
|
+
structured, dense-O(4)-free parametrization of the twiddle.
|
|
107
|
+
"""
|
|
108
|
+
return _ry(theta)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _dct4_gates_1d(n_qubits: int, offset: int, parametrization: str = "o4") -> list[Gate]:
|
|
112
|
+
"""Emit the 1D DCT-IV gate sequence on qubits (offset+1, ..., offset+n_qubits).
|
|
113
|
+
|
|
114
|
+
Within-block qubit ``q`` (0 = top/most significant) maps to builder qubit
|
|
115
|
+
``offset + n_qubits - q`` (Yao little-endian), which reproduces the
|
|
116
|
+
bit-reversed orthonormal DCT-IV exactly.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def Q(q: int) -> int:
|
|
120
|
+
return offset + n_qubits - q
|
|
121
|
+
|
|
122
|
+
gates: list[Gate] = []
|
|
123
|
+
|
|
124
|
+
def level(k: int) -> None:
|
|
125
|
+
if k == n_qubits:
|
|
126
|
+
return
|
|
127
|
+
size = 2 ** (n_qubits - k)
|
|
128
|
+
b = k
|
|
129
|
+
lower = list(range(k + 1, n_qubits))
|
|
130
|
+
# Q: pair-layout permutation — controlled bit-complement of the lower
|
|
131
|
+
# register (exact CNOT at init; a learnable leaf like every gate)
|
|
132
|
+
for q in lower:
|
|
133
|
+
gates.append(Gate(kind="U4", qubits=(Q(b), Q(q)), tensor=_cnot_u4(), phase=0.0))
|
|
134
|
+
# T: affine R_y phase-gradient (one base R_y on b, one CR_y per lower bit)
|
|
135
|
+
gates.append(Gate(kind="H", qubits=(Q(b),), tensor=_ry(np.pi / (2 * size)), phase=0.0))
|
|
136
|
+
for p in range(n_qubits - 1 - k):
|
|
137
|
+
theta = np.pi * (2**p) / size
|
|
138
|
+
if parametrization == "controlled":
|
|
139
|
+
gates.append(Gate(kind="CRY", qubits=(Q(n_qubits - 1 - p), Q(b)),
|
|
140
|
+
tensor=_cry(theta), phase=0.0))
|
|
141
|
+
else:
|
|
142
|
+
gates.append(Gate(kind="U4", qubits=(Q(n_qubits - 1 - p), Q(b)),
|
|
143
|
+
tensor=_cry_u4(theta), phase=0.0))
|
|
144
|
+
# R: odd-branch reversal — the mirror-Q permutation repeated
|
|
145
|
+
for q in lower:
|
|
146
|
+
gates.append(Gate(kind="U4", qubits=(Q(b), Q(q)), tensor=_cnot_u4(), phase=0.0))
|
|
147
|
+
# recurse on the lower register (uncontrolled)
|
|
148
|
+
level(k + 1)
|
|
149
|
+
# D: Delta sign + H: branch Hadamard merge
|
|
150
|
+
if lower:
|
|
151
|
+
gates.append(
|
|
152
|
+
Gate(kind="CP", qubits=(Q(b), Q(k + 1)), tensor=controlled_phase_diag(float(np.pi)), phase=float(np.pi))
|
|
153
|
+
)
|
|
154
|
+
gates.append(Gate(kind="H", qubits=(Q(b),), tensor=HADAMARD, phase=0.0))
|
|
155
|
+
|
|
156
|
+
level(0)
|
|
157
|
+
return gates
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def dct4_code(
|
|
161
|
+
m: int, n: int, *, inverse: bool = False, parametrization: str = "o4"
|
|
162
|
+
) -> tuple[Callable[..., Array], list[Array]]:
|
|
163
|
+
"""Return `(einsum_fn, initial_tensors)` for 2D DCT-IV on (2^m, 2^n) images.
|
|
164
|
+
|
|
165
|
+
``parametrization`` selects how the affine twiddle is stored: ``"o4"``
|
|
166
|
+
(default) emits a dense ``(2, 2, 2, 2)`` controlled-R_y trained on O(4);
|
|
167
|
+
``"controlled"`` emits a single-angle ``CRY`` gate whose trainable leaf is
|
|
168
|
+
a ``(2, 2)`` block on O(2) (the mirror-Q CNOTs stay dense U4).
|
|
169
|
+
"""
|
|
170
|
+
if m < 1 or n < 1:
|
|
171
|
+
raise ValueError(f"m and n must be >= 1, got m={m}, n={n}")
|
|
172
|
+
if parametrization not in ("o4", "controlled"):
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"parametrization must be 'o4' or 'controlled', got {parametrization!r}"
|
|
175
|
+
)
|
|
176
|
+
gates = _dct4_gates_1d(m, offset=0, parametrization=parametrization) + _dct4_gates_1d(
|
|
177
|
+
n, offset=m, parametrization=parametrization
|
|
178
|
+
)
|
|
179
|
+
return compile_circuit(gates, m, n, inverse=inverse)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def dct4_ft_mat(tensors: list[Array], code: Callable, m: int, n: int, pic: Array) -> Array:
|
|
183
|
+
"""Apply 2D DCT-IV circuit to a (2^m, 2^n) image."""
|
|
184
|
+
return apply_circuit(tensors, code, m, n, pic)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def dct4_ift_mat(tensors: list[Array], code: Callable, m: int, n: int, pic: Array) -> Array:
|
|
188
|
+
"""Apply 2D inverse DCT-IV circuit. Caller must have conjugated the tensors."""
|
|
189
|
+
return apply_circuit(tensors, code, m, n, pic)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""freeze_as_blocked: reduce a full circuit to its blocked equivalent.
|
|
2
|
+
|
|
3
|
+
Resets the outer (block-index) gates of a QFT/Rich/RealRich circuit to
|
|
4
|
+
identity and returns the frozen tensor indices, so that
|
|
5
|
+
|
|
6
|
+
train_basis_batched(frozen_basis, frozen_indices=frozen, ...)
|
|
7
|
+
|
|
8
|
+
reproduces BlockedBasis(inner, block_log_m, block_log_n) training dynamics.
|
|
9
|
+
See docs/superpowers/specs/2026-05-24-circuit-rich-frozen-blocked-design.md.
|
|
10
|
+
|
|
11
|
+
Gradient-norm clipping note: frozen slots have their Euclidean gradient zeroed
|
|
12
|
+
before manifold projection, so their Riemannian gradient is zero and they
|
|
13
|
+
contribute zero to the global grad-norm. The clip factor therefore matches
|
|
14
|
+
BlockedBasis exactly — parity holds with max_grad_norm set (see
|
|
15
|
+
test_frozen_qft_outer_gates_matches_blocked_qft_training at max_grad_norm=1.0).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import jax
|
|
23
|
+
import jax.numpy as jnp
|
|
24
|
+
|
|
25
|
+
from ...circuit.builder import controlled_phase_diag, sorted_gate_program
|
|
26
|
+
|
|
27
|
+
Array = jax.Array
|
|
28
|
+
|
|
29
|
+
__all__ = ["freeze_as_blocked"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _identity_for_kind(kind: str) -> Array:
|
|
33
|
+
"""Identity-acting tensor for a gate kind, as complex128."""
|
|
34
|
+
if kind == "H":
|
|
35
|
+
return jnp.eye(2, dtype=jnp.complex128)
|
|
36
|
+
if kind == "U4":
|
|
37
|
+
return jnp.eye(4, dtype=jnp.complex128).reshape(2, 2, 2, 2)
|
|
38
|
+
if kind == "CP":
|
|
39
|
+
return controlled_phase_diag(0.0) # diag(1,1,1,1) in compact 2x2 form
|
|
40
|
+
raise AssertionError(f"unknown gate kind: {kind}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def freeze_as_blocked(basis: Any, block_log_m: int, block_log_n: int) -> tuple[Any, list[int]]:
|
|
44
|
+
"""Return ``(basis_copy_with_identity_outer_gates, frozen_indices)``.
|
|
45
|
+
|
|
46
|
+
Training the returned basis with ``frozen_indices`` reproduces
|
|
47
|
+
``BlockedBasis(inner, block_log_m, block_log_n)`` training dynamics, where
|
|
48
|
+
``inner`` is the same basis type at ``(m - block_log_m, n - block_log_n)``.
|
|
49
|
+
The outer (block-index) gates are reset to identity; inner gates keep the
|
|
50
|
+
input basis's tensor values.
|
|
51
|
+
|
|
52
|
+
Supports ``QFTBasis``, ``RichBasis``, ``RealRichBasis``.
|
|
53
|
+
|
|
54
|
+
``block_log_m == block_log_n == 0`` is a valid no-op: no qubits are
|
|
55
|
+
block-index qubits, so nothing is frozen and ``frozen_indices`` is empty
|
|
56
|
+
(the returned basis is a tensor-copy of the input).
|
|
57
|
+
"""
|
|
58
|
+
# Lazy imports avoid a real bases.base <-> bases.circuit import cycle.
|
|
59
|
+
# base.py runs `from .circuit.qft import qft_code` at module load; importing
|
|
60
|
+
# that submodule first executes bases/circuit/__init__.py, which imports
|
|
61
|
+
# THIS module. A module-top `from ..base import QFTBasis` would then touch
|
|
62
|
+
# base.py while it is still partially initialised (before QFTBasis exists)
|
|
63
|
+
# and raise ImportError. Verified: hoisting these to the top breaks
|
|
64
|
+
# `import pdft`. Keep them inside the function.
|
|
65
|
+
from ..base import QFTBasis
|
|
66
|
+
from .qft import _qft_gates_1d
|
|
67
|
+
from .real_rich import RealRichBasis, _real_rich_qft_gates_1d
|
|
68
|
+
from .rich import RichBasis, _rich_qft_gates_1d
|
|
69
|
+
|
|
70
|
+
gate_builders = {
|
|
71
|
+
QFTBasis: _qft_gates_1d,
|
|
72
|
+
RichBasis: _rich_qft_gates_1d,
|
|
73
|
+
RealRichBasis: _real_rich_qft_gates_1d,
|
|
74
|
+
}
|
|
75
|
+
btype = type(basis)
|
|
76
|
+
if btype not in gate_builders:
|
|
77
|
+
raise TypeError(
|
|
78
|
+
"freeze_as_blocked supports QFTBasis, RichBasis, RealRichBasis; "
|
|
79
|
+
f"got {btype.__name__}"
|
|
80
|
+
)
|
|
81
|
+
if block_log_m < 0 or block_log_n < 0:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"block_log_m and block_log_n must be >= 0; got "
|
|
84
|
+
f"block_log_m={block_log_m}, block_log_n={block_log_n}"
|
|
85
|
+
)
|
|
86
|
+
m, n = basis.m, basis.n
|
|
87
|
+
if m - block_log_m < 1 or n - block_log_n < 1:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"block partition leaves no inner qubits: m={m}, n={n}, "
|
|
90
|
+
f"block_log_m={block_log_m}, block_log_n={block_log_n} "
|
|
91
|
+
"(need m - block_log_m >= 1 and n - block_log_n >= 1)"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Block-index qubits: higher-numbered qubits per dim (Yao little-endian),
|
|
95
|
+
# matching BlockedBasis.
|
|
96
|
+
block_qubits = set(range(m - block_log_m + 1, m + 1)) | set(
|
|
97
|
+
range(m + n - block_log_n + 1, m + n + 1)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
builder = gate_builders[btype]
|
|
101
|
+
gates = builder(m, offset=0) + builder(n, offset=m)
|
|
102
|
+
program = sorted_gate_program(gates)
|
|
103
|
+
if len(program) != len(basis.tensors):
|
|
104
|
+
raise AssertionError(
|
|
105
|
+
f"gate program length {len(program)} != tensor count {len(basis.tensors)}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
new_tensors = [jnp.array(t, copy=True) for t in basis.tensors]
|
|
109
|
+
frozen_indices: list[int] = []
|
|
110
|
+
for i, (kind, qubits) in enumerate(program):
|
|
111
|
+
if set(qubits) & block_qubits:
|
|
112
|
+
new_tensors[i] = _identity_for_kind(kind).astype(new_tensors[i].dtype)
|
|
113
|
+
frozen_indices.append(i)
|
|
114
|
+
|
|
115
|
+
new_basis = btype(m=m, n=n, tensors=new_tensors, code=basis.code, inv_code=basis.inv_code)
|
|
116
|
+
return new_basis, frozen_indices
|