qmlkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. qmlkit/__init__.py +495 -0
  2. qmlkit/_aliases.py +135 -0
  3. qmlkit/algorithms/__init__.py +82 -0
  4. qmlkit/algorithms/adapt.py +297 -0
  5. qmlkit/algorithms/autoencoder.py +206 -0
  6. qmlkit/algorithms/chemistry.py +222 -0
  7. qmlkit/algorithms/clustering.py +149 -0
  8. qmlkit/algorithms/hamiltonians.py +143 -0
  9. qmlkit/algorithms/molecule.py +442 -0
  10. qmlkit/algorithms/qaoa.py +208 -0
  11. qmlkit/algorithms/rl.py +198 -0
  12. qmlkit/algorithms/vqe.py +198 -0
  13. qmlkit/ansatz/__init__.py +68 -0
  14. qmlkit/ansatz/blocks.py +348 -0
  15. qmlkit/ansatz/library.py +570 -0
  16. qmlkit/ansatz/reupload.py +168 -0
  17. qmlkit/baselines.py +604 -0
  18. qmlkit/budget.py +234 -0
  19. qmlkit/core/__init__.py +1 -0
  20. qmlkit/core/backends/__init__.py +22 -0
  21. qmlkit/core/backends/_sampling.py +43 -0
  22. qmlkit/core/backends/base.py +256 -0
  23. qmlkit/core/backends/cirq_backend.py +110 -0
  24. qmlkit/core/backends/cirq_density_backend.py +71 -0
  25. qmlkit/core/backends/noisy.py +86 -0
  26. qmlkit/core/backends/numpy_backend.py +276 -0
  27. qmlkit/core/backends/qiskit_aer_backend.py +79 -0
  28. qmlkit/core/backends/qiskit_backend.py +104 -0
  29. qmlkit/core/backends/registry.py +210 -0
  30. qmlkit/core/backends/spinqit_backend.py +233 -0
  31. qmlkit/core/backends/torch_backend.py +185 -0
  32. qmlkit/core/builder.py +189 -0
  33. qmlkit/core/execute.py +193 -0
  34. qmlkit/core/gates.py +243 -0
  35. qmlkit/core/ir.py +320 -0
  36. qmlkit/core/observables.py +269 -0
  37. qmlkit/datasets.py +178 -0
  38. qmlkit/diagnostics.py +719 -0
  39. qmlkit/draw.py +177 -0
  40. qmlkit/encoding/__init__.py +63 -0
  41. qmlkit/encoding/amplitude.py +178 -0
  42. qmlkit/encoding/angle.py +61 -0
  43. qmlkit/encoding/feature_maps.py +353 -0
  44. qmlkit/encoding/hamiltonian.py +206 -0
  45. qmlkit/encoding/pipeline.py +198 -0
  46. qmlkit/encoding/scaling.py +139 -0
  47. qmlkit/evaluate.py +686 -0
  48. qmlkit/fourier.py +124 -0
  49. qmlkit/generative.py +406 -0
  50. qmlkit/gradients/__init__.py +61 -0
  51. qmlkit/gradients/adjoint.py +138 -0
  52. qmlkit/gradients/batch.py +275 -0
  53. qmlkit/gradients/dispatch.py +247 -0
  54. qmlkit/gradients/hadamard.py +108 -0
  55. qmlkit/gradients/parameter_shift.py +142 -0
  56. qmlkit/gradients/rules.py +151 -0
  57. qmlkit/gradients/spsa.py +134 -0
  58. qmlkit/imbalance.py +335 -0
  59. qmlkit/info.py +153 -0
  60. qmlkit/interop.py +778 -0
  61. qmlkit/kernels/__init__.py +69 -0
  62. qmlkit/kernels/estimators.py +206 -0
  63. qmlkit/kernels/matrix.py +439 -0
  64. qmlkit/kernels/models.py +315 -0
  65. qmlkit/metrics.py +394 -0
  66. qmlkit/nn/__init__.py +18 -0
  67. qmlkit/nn/advanced.py +254 -0
  68. qmlkit/nn/layer.py +343 -0
  69. qmlkit/nn/losses.py +124 -0
  70. qmlkit/nn/models.py +245 -0
  71. qmlkit/optim.py +306 -0
  72. qmlkit/provenance.py +271 -0
  73. qmlkit/py.typed +0 -0
  74. qmlkit/search.py +561 -0
  75. qmlkit/shadows.py +117 -0
  76. qmlkit/utils/__init__.py +19 -0
  77. qmlkit/utils/errors.py +130 -0
  78. qmlkit/utils/shots.py +55 -0
  79. qmlkit-0.1.0.dist-info/METADATA +745 -0
  80. qmlkit-0.1.0.dist-info/RECORD +83 -0
  81. qmlkit-0.1.0.dist-info/WHEEL +4 -0
  82. qmlkit-0.1.0.dist-info/licenses/LICENSE +202 -0
  83. qmlkit-0.1.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,168 @@
1
+ r"""Data re-uploading, as a pattern rather than a fixed structure.
2
+
3
+ Re-uploading is *any* interleaving of an encoding with a trainable block. The
4
+ encoding can be any feature map, the trainable block any ansatz block, and the
5
+ order, depth and sharing are all free. Treating it as one hardcoded class is a
6
+ category error — so it is not one here.
7
+
8
+ :func:`reupload` is a convenience over that freedom, not a replacement for it:
9
+
10
+ reupload(fmap, n_layers=3) # S W S W S W
11
+ reupload(fmap, n_layers=3, order="WS") # W S W S W S
12
+ reupload(fmap, n_layers=3, block=RotationLayer("ry") + EntanglerLayer("cz", "ring"))
13
+ reupload(fmap, n_layers=3, share_weights=True) # one tied block, reused
14
+
15
+ Anything it cannot express, compose directly — that is the same vocabulary:
16
+
17
+ Ansatz(n, EncodingLayer(zz) + RotationLayer("ry") + EncodingLayer(angle),
18
+ n_inputs=...)
19
+
20
+ **Frequencies.** ``L`` uploads reach frequencies ``0..L`` only when the trainable
21
+ block does not commute with the encoding rotation; if it does, the uploads merge
22
+ into a single rotation. :func:`reupload` checks this and warns.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import warnings
28
+ from collections.abc import Sequence
29
+ from typing import Any
30
+
31
+ import numpy.typing as npt
32
+ from numpy.typing import ArrayLike
33
+
34
+ from qmlkit.ansatz.blocks import (
35
+ Block,
36
+ EncodingLayer,
37
+ EntanglerLayer,
38
+ RotationLayer,
39
+ repeat,
40
+ share,
41
+ )
42
+ from qmlkit.ansatz.library import Ansatz
43
+ from qmlkit.utils.errors import unknown
44
+
45
+ __all__ = ["reupload", "ReuploadModel"]
46
+
47
+
48
+ def _commutes_with_encoding(block: Block, feature_map: object) -> bool:
49
+ """True if every trainable rotation shares the encoding's generator.
50
+
51
+ ``Ry(x) Ry(t1) Ry(x) Ry(t2) = Ry(2x + t1 + t2)`` — the uploads collapse into one
52
+ rotation, the model reaches a single frequency, and the weights become a phase.
53
+ """
54
+ encoding = getattr(feature_map, "rotation", None)
55
+ if encoding is None:
56
+ return False # a multi-gate map (ZZ, Pauli) never fully commutes
57
+ gates: set[str] = set()
58
+
59
+ def walk(b: Block) -> None:
60
+ if isinstance(b, RotationLayer):
61
+ gates.update(b.gates)
62
+ for attr in ("blocks", "block"):
63
+ child = getattr(b, attr, None)
64
+ if isinstance(child, Block):
65
+ walk(child)
66
+ elif isinstance(child, tuple):
67
+ for c in child:
68
+ walk(c)
69
+
70
+ walk(block)
71
+ return bool(gates) and gates <= {encoding}
72
+
73
+
74
+ def reupload(
75
+ feature_map: object,
76
+ n_layers: int = 3,
77
+ block: Block | None = None,
78
+ order: str = "SW",
79
+ entangler: str | None = "cx",
80
+ pattern: str = "chain",
81
+ rotations: Sequence[str] = ("rz", "ry", "rz"),
82
+ share_weights: bool = False,
83
+ name: str = "reupload",
84
+ ) -> Ansatz:
85
+ """Build a re-uploading ansatz from any feature map and any trainable block.
86
+
87
+ Parameters
88
+ ----------
89
+ feature_map
90
+ Any :class:`~qmlkit.encoding.feature_maps.FeatureMap`.
91
+ block
92
+ The trainable block. Defaults to a rotation layer plus an entangler.
93
+ order
94
+ ``"SW"`` encodes then varies; ``"WS"`` varies then encodes. The difference
95
+ is real: ``"WS"`` lets the model transform the state before the first
96
+ upload, ``"SW"`` does not.
97
+ share_weights
98
+ Tie one trainable block across every layer — far fewer parameters, and the
99
+ gradient sums over occurrences.
100
+ """
101
+ if n_layers < 1:
102
+ raise ValueError("n_layers must be at least 1")
103
+ if order not in ("SW", "WS"):
104
+ raise unknown(
105
+ "order",
106
+ order,
107
+ ("SW", "WS"),
108
+ hint="'SW' encodes then varies; 'WS' varies before the first upload.",
109
+ )
110
+
111
+ n_qubits = int(feature_map.n_qubits) # type: ignore[attr-defined]
112
+ n_inputs = int(feature_map.n_angles) # type: ignore[attr-defined]
113
+
114
+ if block is None:
115
+ block = RotationLayer(rotations)
116
+ if entangler and n_qubits > 1:
117
+ block = block + EntanglerLayer(entangler, pattern)
118
+
119
+ if _commutes_with_encoding(block, feature_map):
120
+ warnings.warn(
121
+ f"the trainable block only uses {rotations}, which commutes with the "
122
+ f"encoding rotation: the uploads collapse into a single rotation, so the "
123
+ f"model reaches one frequency instead of 0..{n_layers} and its weights do "
124
+ 'nothing beyond a phase. Use a non-commuting block such as ("rz", "ry", "rz").',
125
+ UserWarning,
126
+ stacklevel=2,
127
+ )
128
+
129
+ encoding = EncodingLayer(feature_map)
130
+ layer = (encoding + block) if order == "SW" else (block + encoding)
131
+ body = share(n_layers, layer) if share_weights else repeat(n_layers, layer)
132
+ return ReuploadModel(n_qubits, body, name, n_inputs, feature_map, n_layers)
133
+
134
+
135
+ class ReuploadModel(Ansatz):
136
+ """An :class:`Ansatz` that also knows its encoding and upload count."""
137
+
138
+ def __init__(
139
+ self,
140
+ n_qubits: int,
141
+ block: Block,
142
+ name: str,
143
+ n_inputs: int,
144
+ feature_map: object,
145
+ n_uploads: int,
146
+ ) -> None:
147
+ super().__init__(n_qubits, block, name, n_inputs)
148
+ self.feature_map = feature_map
149
+ self.n_uploads = n_uploads
150
+
151
+ @property
152
+ def n_frequencies(self) -> int:
153
+ """Reachable frequencies ``0..L``, so ``L + 1`` of them."""
154
+ return self.n_uploads + 1
155
+
156
+ def angles(self, x: ArrayLike) -> npt.NDArray[Any]:
157
+ """The encoding angles for ``x`` — the first ``n_inputs`` parameters."""
158
+ return self.feature_map.angles(x) # type: ignore[attr-defined]
159
+
160
+ def angle_jacobian(self, x: ArrayLike) -> npt.NDArray[Any]:
161
+ """``d(angle)/d(feature)``, for the chain rule down to the data."""
162
+ return self.feature_map.angle_jacobian(x) # type: ignore[attr-defined]
163
+
164
+ def __repr__(self) -> str:
165
+ return (
166
+ f"ReuploadModel({self.name!r}, n_qubits={self.n_qubits}, "
167
+ f"uploads={self.n_uploads}, inputs={self.n_inputs}, weights={self.n_weights})"
168
+ )