qadence 1.1.1__py3-none-any.whl → 1.2.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.
- qadence/analog/__init__.py +4 -2
- qadence/analog/addressing.py +167 -0
- qadence/analog/constants.py +59 -0
- qadence/analog/device.py +82 -0
- qadence/analog/hamiltonian_terms.py +101 -0
- qadence/analog/parse_analog.py +120 -0
- qadence/backend.py +27 -1
- qadence/backends/braket/backend.py +1 -1
- qadence/backends/pulser/__init__.py +0 -1
- qadence/backends/pulser/backend.py +30 -15
- qadence/backends/pulser/config.py +19 -10
- qadence/backends/pulser/devices.py +57 -63
- qadence/backends/pulser/pulses.py +70 -12
- qadence/backends/pyqtorch/backend.py +2 -3
- qadence/backends/pyqtorch/config.py +18 -12
- qadence/backends/pyqtorch/convert_ops.py +12 -4
- qadence/backends/pytorch_wrapper.py +2 -1
- qadence/backends/utils.py +1 -10
- qadence/blocks/abstract.py +5 -1
- qadence/blocks/analog.py +18 -9
- qadence/blocks/block_to_tensor.py +11 -0
- qadence/blocks/primitive.py +81 -9
- qadence/constructors/__init__.py +4 -0
- qadence/constructors/feature_maps.py +84 -60
- qadence/constructors/hamiltonians.py +27 -98
- qadence/constructors/rydberg_feature_maps.py +113 -0
- qadence/divergences.py +12 -0
- qadence/extensions.py +1 -6
- qadence/finitediff.py +47 -0
- qadence/mitigations/readout.py +92 -25
- qadence/models/qnn.py +88 -23
- qadence/operations.py +55 -70
- qadence/parameters.py +10 -2
- qadence/register.py +91 -43
- qadence/transpile/__init__.py +1 -0
- qadence/transpile/apply_fn.py +40 -0
- qadence/types.py +19 -1
- qadence/utils.py +35 -0
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/METADATA +2 -2
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/RECORD +42 -36
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/WHEEL +1 -1
- qadence/analog/interaction.py +0 -198
- qadence/analog/utils.py +0 -132
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/licenses/LICENSE +0 -0
qadence/operations.py
CHANGED
@@ -31,6 +31,7 @@ from qadence.blocks.analog import (
|
|
31
31
|
WaitBlock,
|
32
32
|
)
|
33
33
|
from qadence.blocks.block_to_tensor import block_to_tensor
|
34
|
+
from qadence.blocks.primitive import ProjectorBlock
|
34
35
|
from qadence.blocks.utils import (
|
35
36
|
add, # noqa
|
36
37
|
block_is_commuting_hamiltonian,
|
@@ -117,9 +118,6 @@ class X(PrimitiveBlock):
|
|
117
118
|
def eigenvalues(self) -> Tensor:
|
118
119
|
return tensor([-1, 1], dtype=cdouble)
|
119
120
|
|
120
|
-
def dagger(self) -> X:
|
121
|
-
return self
|
122
|
-
|
123
121
|
|
124
122
|
class Y(PrimitiveBlock):
|
125
123
|
"""The Y gate."""
|
@@ -141,9 +139,6 @@ class Y(PrimitiveBlock):
|
|
141
139
|
def eigenvalues(self) -> Tensor:
|
142
140
|
return tensor([-1, 1], dtype=cdouble)
|
143
141
|
|
144
|
-
def dagger(self) -> Y:
|
145
|
-
return self
|
146
|
-
|
147
142
|
|
148
143
|
class Z(PrimitiveBlock):
|
149
144
|
"""The Z gate."""
|
@@ -165,17 +160,36 @@ class Z(PrimitiveBlock):
|
|
165
160
|
def eigenvalues(self) -> Tensor:
|
166
161
|
return tensor([-1, 1], dtype=cdouble)
|
167
162
|
|
168
|
-
def dagger(self) -> Z:
|
169
|
-
return self
|
170
163
|
|
164
|
+
class Projector(ProjectorBlock):
|
165
|
+
"""The projector operator."""
|
166
|
+
|
167
|
+
name = OpName.PROJ
|
168
|
+
|
169
|
+
def __init__(
|
170
|
+
self,
|
171
|
+
ket: str,
|
172
|
+
bra: str,
|
173
|
+
qubit_support: int | tuple[int, ...],
|
174
|
+
):
|
175
|
+
super().__init__(ket=ket, bra=bra, qubit_support=qubit_support)
|
176
|
+
|
177
|
+
@property
|
178
|
+
def generator(self) -> None:
|
179
|
+
raise ValueError("Property `generator` not available for non-unitary operator.")
|
180
|
+
|
181
|
+
@property
|
182
|
+
def eigenvalues_generator(self) -> None:
|
183
|
+
raise ValueError("Property `eigenvalues_generator` not available for non-unitary operator.")
|
171
184
|
|
172
|
-
|
185
|
+
|
186
|
+
class N(Projector):
|
173
187
|
"""The N = (1/2)(I-Z) operator."""
|
174
188
|
|
175
189
|
name = OpName.N
|
176
190
|
|
177
|
-
def __init__(self, target: int):
|
178
|
-
super().__init__((target,))
|
191
|
+
def __init__(self, target: int, state: str = "1"):
|
192
|
+
super().__init__(ket=state, bra=state, qubit_support=(target,))
|
179
193
|
|
180
194
|
@property
|
181
195
|
def generator(self) -> None:
|
@@ -189,9 +203,6 @@ class N(PrimitiveBlock):
|
|
189
203
|
def eigenvalues(self) -> Tensor:
|
190
204
|
return tensor([0, 1], dtype=cdouble)
|
191
205
|
|
192
|
-
def dagger(self) -> N:
|
193
|
-
return self
|
194
|
-
|
195
206
|
|
196
207
|
class S(PrimitiveBlock):
|
197
208
|
"""The S / Phase gate."""
|
@@ -296,9 +307,6 @@ class I(PrimitiveBlock):
|
|
296
307
|
def __ascii__(self, console: Console) -> Padding:
|
297
308
|
return Padding("──────", (1, 1, 1, 1))
|
298
309
|
|
299
|
-
def dagger(self) -> I:
|
300
|
-
return I(*self.qubit_support)
|
301
|
-
|
302
310
|
|
303
311
|
TPauliBlock = Union[X, Y, Z, I, N]
|
304
312
|
|
@@ -320,9 +328,6 @@ class H(PrimitiveBlock):
|
|
320
328
|
def eigenvalues(self) -> Tensor:
|
321
329
|
return torch.tensor([-1, 1], dtype=cdouble)
|
322
330
|
|
323
|
-
def dagger(self) -> H:
|
324
|
-
return H(*self.qubit_support)
|
325
|
-
|
326
331
|
|
327
332
|
class Zero(PrimitiveBlock):
|
328
333
|
name = OpName.ZERO
|
@@ -363,9 +368,6 @@ class Zero(PrimitiveBlock):
|
|
363
368
|
def __pow__(self, power: int) -> AbstractBlock:
|
364
369
|
return self
|
365
370
|
|
366
|
-
def dagger(self) -> Zero:
|
367
|
-
return Zero()
|
368
|
-
|
369
371
|
|
370
372
|
class RX(ParametricBlock):
|
371
373
|
"""The Rx gate."""
|
@@ -668,7 +670,7 @@ class CNOT(ControlBlock):
|
|
668
670
|
name = OpName.CNOT
|
669
671
|
|
670
672
|
def __init__(self, control: int, target: int) -> None:
|
671
|
-
self.generator = kron((
|
673
|
+
self.generator = kron(N(control), X(target) - I(target))
|
672
674
|
super().__init__((control,), X(target))
|
673
675
|
|
674
676
|
@property
|
@@ -691,17 +693,12 @@ class CNOT(ControlBlock):
|
|
691
693
|
tree.add(self._block_title)
|
692
694
|
return tree
|
693
695
|
|
694
|
-
def dagger(self) -> CNOT:
|
695
|
-
return CNOT(*self.qubit_support)
|
696
|
-
|
697
696
|
|
698
697
|
class MCZ(ControlBlock):
|
699
698
|
name = OpName.MCZ
|
700
699
|
|
701
700
|
def __init__(self, control: tuple[int, ...], target: int) -> None:
|
702
|
-
self.generator = kron(
|
703
|
-
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target) - I(target)
|
704
|
-
)
|
701
|
+
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
|
705
702
|
super().__init__(control, Z(target))
|
706
703
|
|
707
704
|
@property
|
@@ -724,9 +721,6 @@ class MCZ(ControlBlock):
|
|
724
721
|
tree.add(self._block_title)
|
725
722
|
return tree
|
726
723
|
|
727
|
-
def dagger(self) -> MCZ:
|
728
|
-
return MCZ(self.qubit_support[:-1], self.qubit_support[-1])
|
729
|
-
|
730
724
|
|
731
725
|
class CZ(MCZ):
|
732
726
|
"""The CZ gate."""
|
@@ -736,9 +730,6 @@ class CZ(MCZ):
|
|
736
730
|
def __init__(self, control: int, target: int) -> None:
|
737
731
|
super().__init__((control,), target)
|
738
732
|
|
739
|
-
def dagger(self) -> CZ:
|
740
|
-
return CZ(self.qubit_support[-2], self.qubit_support[-1])
|
741
|
-
|
742
733
|
|
743
734
|
class MCRX(ParametricControlBlock):
|
744
735
|
name = OpName.MCRX
|
@@ -749,7 +740,7 @@ class MCRX(ParametricControlBlock):
|
|
749
740
|
target: int,
|
750
741
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
751
742
|
) -> None:
|
752
|
-
self.generator = kron(*[(
|
743
|
+
self.generator = kron(*[N(qubit) for qubit in control], X(target))
|
753
744
|
super().__init__(control, RX(target, parameter))
|
754
745
|
|
755
746
|
@classmethod
|
@@ -792,7 +783,7 @@ class MCRY(ParametricControlBlock):
|
|
792
783
|
target: int,
|
793
784
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
794
785
|
) -> None:
|
795
|
-
self.generator = kron(*[(
|
786
|
+
self.generator = kron(*[N(qubit) for qubit in control], Y(target))
|
796
787
|
super().__init__(control, RY(target, parameter))
|
797
788
|
|
798
789
|
@classmethod
|
@@ -821,7 +812,7 @@ class CRY(MCRY):
|
|
821
812
|
self,
|
822
813
|
control: int,
|
823
814
|
target: int,
|
824
|
-
parameter:
|
815
|
+
parameter: TParameter,
|
825
816
|
):
|
826
817
|
super().__init__((control,), target, parameter)
|
827
818
|
|
@@ -835,7 +826,7 @@ class MCRZ(ParametricControlBlock):
|
|
835
826
|
target: int,
|
836
827
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
837
828
|
) -> None:
|
838
|
-
self.generator = kron(*[(
|
829
|
+
self.generator = kron(*[N(qubit) for qubit in control], Z(target))
|
839
830
|
super().__init__(control, RZ(target, parameter))
|
840
831
|
|
841
832
|
@classmethod
|
@@ -878,10 +869,10 @@ class CSWAP(ControlBlock):
|
|
878
869
|
if isinstance(control, tuple):
|
879
870
|
control = control[0]
|
880
871
|
|
881
|
-
a00m =
|
882
|
-
a00p = -
|
883
|
-
a11 =
|
884
|
-
a22 = -
|
872
|
+
a00m = -N(target=control)
|
873
|
+
a00p = -N(target=control, state="0")
|
874
|
+
a11 = -N(target=target1)
|
875
|
+
a22 = -N(target=target2, state="0")
|
885
876
|
a12 = 0.5 * (chain(X(target1), Z(target1)) + X(target1))
|
886
877
|
a21 = 0.5 * (chain(Z(target2), X(target2)) + X(target2))
|
887
878
|
no_effect = kron(a00m, I(target1), I(target2))
|
@@ -906,9 +897,6 @@ class CSWAP(ControlBlock):
|
|
906
897
|
def nqubits(self) -> int:
|
907
898
|
return 3
|
908
899
|
|
909
|
-
def dagger(self) -> CSWAP:
|
910
|
-
return CSWAP(*self.qubit_support)
|
911
|
-
|
912
900
|
|
913
901
|
class T(PrimitiveBlock):
|
914
902
|
"""The T gate."""
|
@@ -994,9 +982,6 @@ class SWAP(PrimitiveBlock):
|
|
994
982
|
s = f"{self.name}({c}, {t})"
|
995
983
|
return s if self.tag is None else (s + rf" \[tag: {self.tag}]")
|
996
984
|
|
997
|
-
def dagger(self) -> SWAP:
|
998
|
-
return SWAP(*self.qubit_support)
|
999
|
-
|
1000
985
|
|
1001
986
|
class AnalogSWAP(HamEvo):
|
1002
987
|
"""
|
@@ -1031,9 +1016,7 @@ class MCPHASE(ParametricControlBlock):
|
|
1031
1016
|
target: int,
|
1032
1017
|
parameter: Parameter | TNumber | sympy.Expr | str,
|
1033
1018
|
) -> None:
|
1034
|
-
self.generator = kron(
|
1035
|
-
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target) - I(target)
|
1036
|
-
)
|
1019
|
+
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
|
1037
1020
|
super().__init__(control, PHASE(target, parameter))
|
1038
1021
|
|
1039
1022
|
@classmethod
|
@@ -1082,14 +1065,9 @@ class Toffoli(ControlBlock):
|
|
1082
1065
|
name = OpName.TOFFOLI
|
1083
1066
|
|
1084
1067
|
def __init__(self, control: tuple[int, ...], target: int) -> None:
|
1085
|
-
self.generator = kron(
|
1086
|
-
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], X(target) - I(target)
|
1087
|
-
)
|
1068
|
+
self.generator = kron(*[N(qubit) for qubit in control], X(target) - I(target))
|
1088
1069
|
super().__init__(control, X(target))
|
1089
1070
|
|
1090
|
-
def dagger(self) -> Toffoli:
|
1091
|
-
return Toffoli(self.qubit_support[:-1], self.qubit_support[-1])
|
1092
|
-
|
1093
1071
|
@property
|
1094
1072
|
def n_qubits(self) -> int:
|
1095
1073
|
return len(self.qubit_support)
|
@@ -1129,6 +1107,7 @@ def _cast(T: Any, val: Any) -> Any:
|
|
1129
1107
|
def wait(
|
1130
1108
|
duration: TNumber | sympy.Basic,
|
1131
1109
|
qubit_support: str | QubitSupport | tuple = "global",
|
1110
|
+
add_pattern: bool = True,
|
1132
1111
|
) -> WaitBlock:
|
1133
1112
|
"""Constructs a [`WaitBlock`][qadence.blocks.analog.WaitBlock].
|
1134
1113
|
|
@@ -1142,7 +1121,7 @@ def wait(
|
|
1142
1121
|
"""
|
1143
1122
|
q = _cast(QubitSupport, qubit_support)
|
1144
1123
|
ps = ParamMap(duration=duration)
|
1145
|
-
return WaitBlock(parameters=ps, qubit_support=q)
|
1124
|
+
return WaitBlock(parameters=ps, qubit_support=q, add_pattern=add_pattern)
|
1146
1125
|
|
1147
1126
|
|
1148
1127
|
def entangle(
|
@@ -1160,6 +1139,7 @@ def AnalogRot(
|
|
1160
1139
|
delta: float | str | Parameter = 0,
|
1161
1140
|
phase: float | str | Parameter = 0,
|
1162
1141
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1142
|
+
add_pattern: bool = True,
|
1163
1143
|
) -> ConstantAnalogRotation:
|
1164
1144
|
"""General analog rotation operation.
|
1165
1145
|
|
@@ -1174,18 +1154,20 @@ def AnalogRot(
|
|
1174
1154
|
ConstantAnalogRotation
|
1175
1155
|
"""
|
1176
1156
|
q = _cast(QubitSupport, qubit_support)
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1157
|
+
duration = Parameter(duration)
|
1158
|
+
omega = Parameter(omega)
|
1159
|
+
delta = Parameter(delta)
|
1160
|
+
phase = Parameter(phase)
|
1161
|
+
alpha = duration * sympy.sqrt(omega**2 + delta**2) / 1000
|
1181
1162
|
ps = ParamMap(alpha=alpha, duration=duration, omega=omega, delta=delta, phase=phase)
|
1182
|
-
return ConstantAnalogRotation(parameters=ps, qubit_support=q)
|
1163
|
+
return ConstantAnalogRotation(parameters=ps, qubit_support=q, add_pattern=add_pattern)
|
1183
1164
|
|
1184
1165
|
|
1185
1166
|
def _analog_rot(
|
1186
1167
|
angle: float | str | Parameter,
|
1187
1168
|
qubit_support: str | QubitSupport | Tuple,
|
1188
1169
|
phase: float,
|
1170
|
+
add_pattern: bool = True,
|
1189
1171
|
) -> ConstantAnalogRotation:
|
1190
1172
|
q = _cast(QubitSupport, qubit_support)
|
1191
1173
|
# assuming some arbitrary omega = π rad/μs
|
@@ -1200,12 +1182,13 @@ def _analog_rot(
|
|
1200
1182
|
# and compute omega like this:
|
1201
1183
|
# omega = alpha / duration * 1000
|
1202
1184
|
ps = ParamMap(alpha=alpha, duration=duration, omega=omega, delta=0, phase=phase)
|
1203
|
-
return ConstantAnalogRotation(parameters=ps, qubit_support=q)
|
1185
|
+
return ConstantAnalogRotation(parameters=ps, qubit_support=q, add_pattern=add_pattern)
|
1204
1186
|
|
1205
1187
|
|
1206
1188
|
def AnalogRX(
|
1207
1189
|
angle: float | str | Parameter,
|
1208
1190
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1191
|
+
add_pattern: bool = True,
|
1209
1192
|
) -> ConstantAnalogRotation:
|
1210
1193
|
"""Analog X rotation.
|
1211
1194
|
|
@@ -1223,12 +1206,13 @@ def AnalogRX(
|
|
1223
1206
|
Returns:
|
1224
1207
|
ConstantAnalogRotation
|
1225
1208
|
"""
|
1226
|
-
return _analog_rot(angle, qubit_support, phase=0)
|
1209
|
+
return _analog_rot(angle, qubit_support, phase=0, add_pattern=add_pattern)
|
1227
1210
|
|
1228
1211
|
|
1229
1212
|
def AnalogRY(
|
1230
1213
|
angle: float | str | Parameter,
|
1231
1214
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1215
|
+
add_pattern: bool = True,
|
1232
1216
|
) -> ConstantAnalogRotation:
|
1233
1217
|
"""Analog Y rotation.
|
1234
1218
|
|
@@ -1245,12 +1229,13 @@ def AnalogRY(
|
|
1245
1229
|
Returns:
|
1246
1230
|
ConstantAnalogRotation
|
1247
1231
|
"""
|
1248
|
-
return _analog_rot(angle, qubit_support, phase=-np.pi / 2)
|
1232
|
+
return _analog_rot(angle, qubit_support, phase=-np.pi / 2, add_pattern=add_pattern)
|
1249
1233
|
|
1250
1234
|
|
1251
1235
|
def AnalogRZ(
|
1252
1236
|
angle: float | str | Parameter,
|
1253
1237
|
qubit_support: str | QubitSupport | Tuple = "global",
|
1238
|
+
add_pattern: bool = True,
|
1254
1239
|
) -> ConstantAnalogRotation:
|
1255
1240
|
"""Analog Z rotation. Shorthand for [`AnalogRot`][qadence.operations.AnalogRot]:
|
1256
1241
|
```
|
@@ -1263,7 +1248,7 @@ def AnalogRZ(
|
|
1263
1248
|
delta = np.pi
|
1264
1249
|
duration = alpha / delta * 1000
|
1265
1250
|
ps = ParamMap(alpha=alpha, duration=duration, omega=0, delta=delta, phase=0.0)
|
1266
|
-
return ConstantAnalogRotation(qubit_support=q, parameters=ps)
|
1251
|
+
return ConstantAnalogRotation(qubit_support=q, parameters=ps, add_pattern=add_pattern)
|
1267
1252
|
|
1268
1253
|
|
1269
1254
|
# gate sets
|
@@ -1288,4 +1273,4 @@ analog_gateset = [
|
|
1288
1273
|
entangle,
|
1289
1274
|
wait,
|
1290
1275
|
]
|
1291
|
-
non_unitary_gateset = [Zero, N]
|
1276
|
+
non_unitary_gateset = [Zero, N, Projector]
|
qadence/parameters.py
CHANGED
@@ -8,8 +8,9 @@ import numpy as np
|
|
8
8
|
import sympy
|
9
9
|
from sympy import *
|
10
10
|
from sympy import Array, Basic, Expr, Symbol, sympify
|
11
|
+
from sympy.physics.quantum.dagger import Dagger
|
11
12
|
from sympytorch import SymPyModule
|
12
|
-
from torch import Tensor, rand, tensor
|
13
|
+
from torch import Tensor, heaviside, no_grad, rand, tensor
|
13
14
|
|
14
15
|
from qadence.logger import get_logger
|
15
16
|
from qadence.types import TNumber
|
@@ -19,6 +20,7 @@ __all__ = ["FeatureParameter", "Parameter", "VariationalParameter"]
|
|
19
20
|
|
20
21
|
logger = get_logger(__file__)
|
21
22
|
|
23
|
+
dagger_expression = Dagger
|
22
24
|
|
23
25
|
ParameterJSONSchema = {
|
24
26
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
@@ -197,7 +199,13 @@ def torchify(expr: Expr) -> SymPyModule:
|
|
197
199
|
Returns:
|
198
200
|
A torchified, differentiable Expression.
|
199
201
|
"""
|
200
|
-
|
202
|
+
|
203
|
+
def heaviside_func(x: Tensor, _: Any) -> Tensor:
|
204
|
+
with no_grad():
|
205
|
+
res = heaviside(x, tensor(0.5))
|
206
|
+
return res
|
207
|
+
|
208
|
+
extra_funcs = {sympy.core.numbers.ImaginaryUnit: 1.0j, sympy.Heaviside: heaviside_func}
|
201
209
|
return SymPyModule(expressions=[sympy.N(expr)], extra_funcs=extra_funcs)
|
202
210
|
|
203
211
|
|
qadence/register.py
CHANGED
@@ -11,12 +11,16 @@ import numpy as np
|
|
11
11
|
from deepdiff import DeepDiff
|
12
12
|
from networkx.classes.reportviews import EdgeView, NodeView
|
13
13
|
|
14
|
+
from qadence.analog import IdealDevice, RydbergDevice
|
14
15
|
from qadence.types import LatticeTopology
|
15
16
|
|
16
17
|
# Modules to be automatically added to the qadence namespace
|
17
18
|
__all__ = ["Register"]
|
18
19
|
|
19
20
|
|
21
|
+
DEFAULT_DEVICE = IdealDevice()
|
22
|
+
|
23
|
+
|
20
24
|
def _scale_node_positions(graph: nx.Graph, min_distance: float, spacing: float) -> None:
|
21
25
|
scaled_nodes = {}
|
22
26
|
scale_factor = spacing / min_distance
|
@@ -27,8 +31,14 @@ def _scale_node_positions(graph: nx.Graph, min_distance: float, spacing: float)
|
|
27
31
|
|
28
32
|
|
29
33
|
class Register:
|
30
|
-
def __init__(
|
31
|
-
|
34
|
+
def __init__(
|
35
|
+
self,
|
36
|
+
support: nx.Graph | int,
|
37
|
+
spacing: float | None = 1.0,
|
38
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
39
|
+
):
|
40
|
+
"""
|
41
|
+
A 2D register of qubits which includes their coordinates.
|
32
42
|
|
33
43
|
It is needed for e.g. analog computing.
|
34
44
|
The coordinates are ignored in backends that don't need them. The easiest
|
@@ -52,21 +62,16 @@ class Register:
|
|
52
62
|
reg.draw()
|
53
63
|
```
|
54
64
|
"""
|
55
|
-
|
65
|
+
if device_specs is not None and not isinstance(device_specs, RydbergDevice):
|
66
|
+
raise ValueError("Device specs are not valid. Please pass a `RydbergDevice` instance.")
|
56
67
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
self.complete_graph = nx.Graph()
|
61
|
-
self.complete_graph.add_nodes_from(support)
|
62
|
-
self.complete_graph.add_edges_from(all_edges)
|
68
|
+
self.device_specs = device_specs
|
69
|
+
|
70
|
+
self.graph = support if isinstance(support, nx.Graph) else alltoall_graph(support)
|
63
71
|
|
64
72
|
if spacing is not None and self.min_distance != 0.0:
|
65
73
|
_scale_node_positions(self.graph, self.min_distance, spacing)
|
66
74
|
|
67
|
-
pos_values = nx.get_node_attributes(self.graph, "pos")
|
68
|
-
nx.set_node_attributes(self.complete_graph, pos_values, "pos")
|
69
|
-
|
70
75
|
@property
|
71
76
|
def n_qubits(self) -> int:
|
72
77
|
return len(self.graph)
|
@@ -77,27 +82,43 @@ class Register:
|
|
77
82
|
coords: list[tuple],
|
78
83
|
lattice: LatticeTopology | str = LatticeTopology.ARBITRARY,
|
79
84
|
spacing: float | None = None,
|
85
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
80
86
|
) -> Register:
|
81
87
|
graph = nx.Graph()
|
82
88
|
for i, pos in enumerate(coords):
|
83
89
|
graph.add_node(i, pos=pos)
|
84
|
-
return cls(graph, spacing)
|
90
|
+
return cls(graph, spacing, device_specs)
|
85
91
|
|
86
92
|
@classmethod
|
87
|
-
def line(
|
88
|
-
|
93
|
+
def line(
|
94
|
+
cls,
|
95
|
+
n_qubits: int,
|
96
|
+
spacing: float = 1.0,
|
97
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
98
|
+
) -> Register:
|
99
|
+
return cls(line_graph(n_qubits), spacing, device_specs)
|
89
100
|
|
90
101
|
@classmethod
|
91
|
-
def circle(
|
102
|
+
def circle(
|
103
|
+
cls,
|
104
|
+
n_qubits: int,
|
105
|
+
spacing: float = 1.0,
|
106
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
107
|
+
) -> Register:
|
92
108
|
graph = nx.grid_2d_graph(n_qubits, 1, periodic=True)
|
93
109
|
graph = nx.relabel_nodes(graph, {(i, 0): i for i in range(n_qubits)})
|
94
110
|
coords = nx.circular_layout(graph)
|
95
111
|
values = {i: {"pos": pos} for i, pos in coords.items()}
|
96
112
|
nx.set_node_attributes(graph, values)
|
97
|
-
return cls(graph, spacing)
|
113
|
+
return cls(graph, spacing, device_specs)
|
98
114
|
|
99
115
|
@classmethod
|
100
|
-
def square(
|
116
|
+
def square(
|
117
|
+
cls,
|
118
|
+
qubits_side: int,
|
119
|
+
spacing: float = 1.0,
|
120
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
121
|
+
) -> Register:
|
101
122
|
n_points = 4 * (qubits_side - 1)
|
102
123
|
|
103
124
|
def gen_points() -> np.ndarray:
|
@@ -122,35 +143,52 @@ class Register:
|
|
122
143
|
graph = nx.relabel_nodes(graph, {(i, 0): i for i in range(n_points)})
|
123
144
|
values = {i: {"pos": point} for i, point in zip(graph.nodes, gen_points())}
|
124
145
|
nx.set_node_attributes(graph, values)
|
125
|
-
return cls(graph, spacing)
|
146
|
+
return cls(graph, spacing, device_specs)
|
126
147
|
|
127
148
|
@classmethod
|
128
|
-
def all_to_all(
|
129
|
-
|
149
|
+
def all_to_all(
|
150
|
+
cls,
|
151
|
+
n_qubits: int,
|
152
|
+
spacing: float = 1.0,
|
153
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
154
|
+
) -> Register:
|
155
|
+
return cls(alltoall_graph(n_qubits), spacing, device_specs)
|
130
156
|
|
131
157
|
@classmethod
|
132
158
|
def rectangular_lattice(
|
133
|
-
cls,
|
159
|
+
cls,
|
160
|
+
qubits_row: int,
|
161
|
+
qubits_col: int,
|
162
|
+
spacing: float = 1.0,
|
163
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
134
164
|
) -> Register:
|
135
165
|
graph = nx.grid_2d_graph(qubits_col, qubits_row)
|
136
166
|
values = {i: {"pos": node} for (i, node) in enumerate(graph.nodes)}
|
137
167
|
graph = nx.relabel_nodes(graph, {(i, j): k for k, (i, j) in enumerate(graph.nodes)})
|
138
168
|
nx.set_node_attributes(graph, values)
|
139
|
-
return cls(graph, spacing)
|
169
|
+
return cls(graph, spacing, device_specs)
|
140
170
|
|
141
171
|
@classmethod
|
142
172
|
def triangular_lattice(
|
143
|
-
cls,
|
173
|
+
cls,
|
174
|
+
n_cells_row: int,
|
175
|
+
n_cells_col: int,
|
176
|
+
spacing: float = 1.0,
|
177
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
144
178
|
) -> Register:
|
145
|
-
return cls(triangular_lattice_graph(n_cells_row, n_cells_col), spacing)
|
179
|
+
return cls(triangular_lattice_graph(n_cells_row, n_cells_col), spacing, device_specs)
|
146
180
|
|
147
181
|
@classmethod
|
148
182
|
def honeycomb_lattice(
|
149
|
-
cls,
|
183
|
+
cls,
|
184
|
+
n_cells_row: int,
|
185
|
+
n_cells_col: int,
|
186
|
+
spacing: float = 1.0,
|
187
|
+
device_specs: RydbergDevice = DEFAULT_DEVICE,
|
150
188
|
) -> Register:
|
151
189
|
graph = nx.hexagonal_lattice_graph(n_cells_row, n_cells_col)
|
152
190
|
graph = nx.relabel_nodes(graph, {(i, j): k for k, (i, j) in enumerate(graph.nodes)})
|
153
|
-
return cls(graph, spacing)
|
191
|
+
return cls(graph, spacing, device_specs)
|
154
192
|
|
155
193
|
@classmethod
|
156
194
|
def lattice(cls, topology: LatticeTopology | str, *args: Any, **kwargs: Any) -> Register:
|
@@ -166,25 +204,29 @@ class Register:
|
|
166
204
|
return self.graph.nodes[item]
|
167
205
|
|
168
206
|
@property
|
169
|
-
def
|
170
|
-
return
|
171
|
-
|
172
|
-
@property
|
173
|
-
def coords(self) -> dict:
|
174
|
-
return {i: tuple(node.get("pos", ())) for i, node in self.graph.nodes.items()}
|
207
|
+
def nodes(self) -> NodeView:
|
208
|
+
return self.graph.nodes
|
175
209
|
|
176
210
|
@property
|
177
211
|
def edges(self) -> EdgeView:
|
178
212
|
return self.graph.edges
|
179
213
|
|
180
214
|
@property
|
181
|
-
def
|
182
|
-
return self.
|
215
|
+
def support(self) -> set:
|
216
|
+
return set(self.nodes)
|
217
|
+
|
218
|
+
@property
|
219
|
+
def coords(self) -> dict:
|
220
|
+
return {i: tuple(node.get("pos", ())) for i, node in self.nodes.items()}
|
221
|
+
|
222
|
+
@property
|
223
|
+
def all_node_pairs(self) -> EdgeView:
|
224
|
+
return list(filter(lambda x: x[0] < x[1], product(self.support, self.support)))
|
183
225
|
|
184
226
|
@property
|
185
227
|
def distances(self) -> dict:
|
186
228
|
coords = self.coords
|
187
|
-
return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in self.
|
229
|
+
return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in self.all_node_pairs}
|
188
230
|
|
189
231
|
@property
|
190
232
|
def edge_distances(self) -> dict:
|
@@ -197,21 +239,27 @@ class Register:
|
|
197
239
|
value: float = min(self.distances.values()) if len(distances) > 0 else 0.0
|
198
240
|
return value
|
199
241
|
|
200
|
-
@property
|
201
|
-
def nodes(self) -> NodeView:
|
202
|
-
return self.graph.nodes
|
203
|
-
|
204
242
|
def rescale_coords(self, scaling: float) -> Register:
|
205
243
|
g = deepcopy(self.graph)
|
206
244
|
_scale_node_positions(g, min_distance=1.0, spacing=scaling)
|
207
|
-
return Register(g, spacing=None)
|
245
|
+
return Register(g, spacing=None, device_specs=self.device_specs)
|
208
246
|
|
209
247
|
def _to_dict(self) -> dict:
|
210
|
-
return {
|
248
|
+
return {
|
249
|
+
"graph": nx.node_link_data(self.graph),
|
250
|
+
"device_specs": self.device_specs._to_dict(),
|
251
|
+
}
|
211
252
|
|
212
253
|
@classmethod
|
213
254
|
def _from_dict(cls, d: dict) -> Register:
|
214
|
-
|
255
|
+
device_dict = d.get("device_specs", None)
|
256
|
+
if device_dict is None:
|
257
|
+
device_dict = DEFAULT_DEVICE._to_dict()
|
258
|
+
|
259
|
+
return cls(
|
260
|
+
support=nx.node_link_graph(d["graph"]),
|
261
|
+
device_specs=RydbergDevice._from_dict(device_dict),
|
262
|
+
)
|
215
263
|
|
216
264
|
def __eq__(self, other: object) -> bool:
|
217
265
|
if not isinstance(other, Register):
|
qadence/transpile/__init__.py
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any, Callable
|
4
|
+
|
5
|
+
from qadence.blocks import AbstractBlock, CompositeBlock, add, chain, kron
|
6
|
+
from qadence.blocks.analog import AnalogChain
|
7
|
+
|
8
|
+
COMPOSE_FN_DICT = {
|
9
|
+
"ChainBlock": chain,
|
10
|
+
"AnalogChain": chain,
|
11
|
+
"KronBlock": kron,
|
12
|
+
"AnalogKron": kron,
|
13
|
+
"AddBlock": add,
|
14
|
+
}
|
15
|
+
|
16
|
+
|
17
|
+
def apply_fn_to_blocks(
|
18
|
+
input_block: AbstractBlock, block_fn: Callable, *args: Any, **kwargs: Any
|
19
|
+
) -> AbstractBlock:
|
20
|
+
"""
|
21
|
+
Recurses through the block tree and applies a given function to all the leaf blocks.
|
22
|
+
|
23
|
+
Arguments:
|
24
|
+
input_block: tree of blocks on which to apply the recurse.
|
25
|
+
block_fn: callable function to apply to each leaf block.
|
26
|
+
args: any positional arguments to pass to the leaf function.
|
27
|
+
kwargs: any keyword arguments to pass to the leaf function.
|
28
|
+
"""
|
29
|
+
|
30
|
+
if isinstance(input_block, (CompositeBlock, AnalogChain)):
|
31
|
+
parsed_blocks = [
|
32
|
+
apply_fn_to_blocks(block, block_fn, *args, **kwargs) for block in input_block.blocks
|
33
|
+
]
|
34
|
+
compose_fn = COMPOSE_FN_DICT[type(input_block).__name__]
|
35
|
+
output_block = compose_fn(*parsed_blocks) # type: ignore [arg-type]
|
36
|
+
else:
|
37
|
+
# AnalogKrons are considered as a leaf block
|
38
|
+
output_block = block_fn(input_block, *args, **kwargs)
|
39
|
+
|
40
|
+
return output_block
|