qadence 1.1.1__py3-none-any.whl → 1.2.1__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 (64) hide show
  1. qadence/__init__.py +1 -0
  2. qadence/analog/__init__.py +4 -2
  3. qadence/analog/addressing.py +167 -0
  4. qadence/analog/constants.py +59 -0
  5. qadence/analog/device.py +82 -0
  6. qadence/analog/hamiltonian_terms.py +101 -0
  7. qadence/analog/parse_analog.py +120 -0
  8. qadence/backend.py +42 -12
  9. qadence/backends/__init__.py +1 -2
  10. qadence/backends/api.py +27 -9
  11. qadence/backends/braket/backend.py +3 -2
  12. qadence/backends/horqrux/__init__.py +5 -0
  13. qadence/backends/horqrux/backend.py +216 -0
  14. qadence/backends/horqrux/config.py +26 -0
  15. qadence/backends/horqrux/convert_ops.py +273 -0
  16. qadence/backends/jax_utils.py +45 -0
  17. qadence/backends/pulser/__init__.py +0 -1
  18. qadence/backends/pulser/backend.py +31 -15
  19. qadence/backends/pulser/config.py +19 -10
  20. qadence/backends/pulser/devices.py +57 -63
  21. qadence/backends/pulser/pulses.py +70 -12
  22. qadence/backends/pyqtorch/backend.py +4 -4
  23. qadence/backends/pyqtorch/config.py +18 -12
  24. qadence/backends/pyqtorch/convert_ops.py +15 -7
  25. qadence/backends/utils.py +5 -9
  26. qadence/blocks/abstract.py +5 -1
  27. qadence/blocks/analog.py +18 -9
  28. qadence/blocks/block_to_tensor.py +11 -0
  29. qadence/blocks/embedding.py +46 -24
  30. qadence/blocks/primitive.py +81 -9
  31. qadence/blocks/utils.py +20 -1
  32. qadence/circuit.py +3 -9
  33. qadence/constructors/__init__.py +4 -0
  34. qadence/constructors/feature_maps.py +84 -60
  35. qadence/constructors/hamiltonians.py +27 -98
  36. qadence/constructors/rydberg_feature_maps.py +113 -0
  37. qadence/divergences.py +12 -0
  38. qadence/engines/__init__.py +0 -0
  39. qadence/engines/differentiable_backend.py +152 -0
  40. qadence/engines/jax/__init__.py +8 -0
  41. qadence/engines/jax/differentiable_backend.py +73 -0
  42. qadence/engines/jax/differentiable_expectation.py +94 -0
  43. qadence/engines/torch/__init__.py +4 -0
  44. qadence/engines/torch/differentiable_backend.py +85 -0
  45. qadence/extensions.py +21 -9
  46. qadence/finitediff.py +47 -0
  47. qadence/mitigations/readout.py +92 -25
  48. qadence/ml_tools/models.py +10 -3
  49. qadence/models/qnn.py +88 -23
  50. qadence/models/quantum_model.py +13 -2
  51. qadence/operations.py +55 -70
  52. qadence/parameters.py +24 -13
  53. qadence/register.py +91 -43
  54. qadence/transpile/__init__.py +1 -0
  55. qadence/transpile/apply_fn.py +40 -0
  56. qadence/types.py +32 -2
  57. qadence/utils.py +35 -0
  58. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/METADATA +22 -3
  59. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/RECORD +62 -44
  60. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/WHEEL +1 -1
  61. qadence/analog/interaction.py +0 -198
  62. qadence/analog/utils.py +0 -132
  63. /qadence/{backends/pytorch_wrapper.py → engines/torch/differentiable_expectation.py} +0 -0
  64. {qadence-1.1.1.dist-info → qadence-1.2.1.dist-info}/licenses/LICENSE +0 -0
qadence/models/qnn.py CHANGED
@@ -1,13 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections import Counter
3
4
  from typing import Callable
4
5
 
6
+ import sympy
5
7
  from torch import Tensor
6
8
 
7
- from qadence.backend import BackendConfiguration
9
+ from qadence.backend import BackendConfiguration, ConvertedObservable
8
10
  from qadence.blocks.abstract import AbstractBlock
9
11
  from qadence.circuit import QuantumCircuit
10
12
  from qadence.measurements import Measurements
13
+ from qadence.mitigations import Mitigations
11
14
  from qadence.models.quantum_model import QuantumModel
12
15
  from qadence.noise import Noise
13
16
  from qadence.types import BackendName, DiffMode, Endianness
@@ -19,22 +22,25 @@ class QNN(QuantumModel):
19
22
  Examples:
20
23
  ```python exec="on" source="material-block" result="json"
21
24
  import torch
22
- from qadence import QuantumCircuit, QNN
23
- from qadence import hea, feature_map, hamiltonian_factory, Z
25
+ from qadence import QuantumCircuit, QNN, Z
26
+ from qadence import hea, feature_map, hamiltonian_factory, kron
24
27
 
25
28
  # create the circuit
26
29
  n_qubits, depth = 2, 4
27
- fm = feature_map(n_qubits)
30
+ fm = kron(
31
+ feature_map(1, support=(0,), param="x"),
32
+ feature_map(1, support=(1,), param="y")
33
+ )
28
34
  ansatz = hea(n_qubits=n_qubits, depth=depth)
29
35
  circuit = QuantumCircuit(n_qubits, fm, ansatz)
30
- obs_base = hamiltonian_factory(n_qubits, detuning = Z)
36
+ obs_base = hamiltonian_factory(n_qubits, detuning=Z)
31
37
 
32
38
  # the QNN will yield two outputs
33
39
  obs = [2.0 * obs_base, 4.0 * obs_base]
34
40
 
35
41
  # initialize and use the model
36
- qnn = QNN(circuit, obs, diff_mode="ad", backend="pyqtorch")
37
- y = qnn.expectation({"phi": torch.rand(3)})
42
+ qnn = QNN(circuit, obs, inputs=["x", "y"])
43
+ y = qnn(torch.rand(3, 2))
38
44
  print(str(y)) # markdown-exec: hide
39
45
  ```
40
46
  """
@@ -49,6 +55,7 @@ class QNN(QuantumModel):
49
55
  measurement: Measurements | None = None,
50
56
  noise: Noise | None = None,
51
57
  configuration: BackendConfiguration | dict | None = None,
58
+ inputs: list[sympy.Basic | str] | None = None,
52
59
  ):
53
60
  """Initialize the QNN.
54
61
 
@@ -59,6 +66,9 @@ class QNN(QuantumModel):
59
66
  Args:
60
67
  circuit: The quantum circuit to use for the QNN.
61
68
  transform: A transformation applied to the output of the QNN.
69
+ inputs: Tuple that indicates the order of variables of the tensors that are passed
70
+ to the model. Given input tensors `xs = torch.rand(batch_size, input_size:=2)` a QNN
71
+ with `inputs=("t", "x")` will assign `t, x = xs[:,0], xs[:,1]`.
62
72
  backend: The chosen quantum backend.
63
73
  diff_mode: The differentiation engine to use. Choices 'gpsr' or 'ad'.
64
74
  measurement: optional measurement protocol. If None,
@@ -67,7 +77,7 @@ class QNN(QuantumModel):
67
77
  configuration: optional configuration for the backend
68
78
  """
69
79
  super().__init__(
70
- circuit=circuit,
80
+ circuit,
71
81
  observable=observable,
72
82
  backend=backend,
73
83
  diff_mode=diff_mode,
@@ -75,12 +85,33 @@ class QNN(QuantumModel):
75
85
  configuration=configuration,
76
86
  noise=noise,
77
87
  )
78
-
79
88
  if self.out_features is None:
80
89
  raise ValueError("You need to provide at least one observable in the QNN constructor")
81
-
82
90
  self.transform = transform if transform else lambda x: x
83
91
 
92
+ if (inputs is not None) and (len(self.inputs) == len(inputs)):
93
+ self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in inputs] # type: ignore[union-attr]
94
+ elif (inputs is None) and len(self.inputs) <= 1:
95
+ self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in self.inputs] # type: ignore[union-attr]
96
+ else:
97
+ raise ValueError(
98
+ """
99
+ Your QNN has more than one input. Please provide a list of inputs in the order of
100
+ your tensor domain. For example, if you want to pass
101
+ `xs = torch.rand(batch_size, input_size:=3)` to you QNN, where
102
+ ```
103
+ t = x[:,0]
104
+ x = x[:,1]
105
+ y = x[:,2]
106
+ ```
107
+ you have to specify
108
+ ```
109
+ QNN(circuit, observable, inputs=["t", "x", "y"])
110
+ ```
111
+ You can also pass a list of sympy symbols.
112
+ """
113
+ )
114
+
84
115
  def forward(
85
116
  self,
86
117
  values: dict[str, Tensor] | Tensor = None,
@@ -103,7 +134,7 @@ class QNN(QuantumModel):
103
134
  is instead `n_batches x n_observables`
104
135
 
105
136
  Args:
106
- values (dict[str, Tensor] | Tensor): the values of the feature parameters
137
+ values: the values of the feature parameters
107
138
  state: Initial state.
108
139
  measurement: optional measurement protocol. If None,
109
140
  use exact expectation value with a statevector simulator
@@ -114,18 +145,55 @@ class QNN(QuantumModel):
114
145
  Tensor: a tensor with the expectation value of the observables passed
115
146
  in the constructor of the model
116
147
  """
148
+ return self.expectation(
149
+ values, state=state, measurement=measurement, noise=noise, endianness=endianness
150
+ )
151
+
152
+ def run(
153
+ self,
154
+ values: Tensor | dict[str, Tensor] = None,
155
+ state: Tensor | None = None,
156
+ endianness: Endianness = Endianness.BIG,
157
+ ) -> Tensor:
158
+ return super().run(values=self._format_to_dict(values), state=state, endianness=endianness)
159
+
160
+ def sample(
161
+ self,
162
+ values: Tensor | dict[str, Tensor] = {},
163
+ n_shots: int = 1000,
164
+ state: Tensor | None = None,
165
+ noise: Noise | None = None,
166
+ mitigation: Mitigations | None = None,
167
+ endianness: Endianness = Endianness.BIG,
168
+ ) -> list[Counter]:
169
+ return super().sample(
170
+ values=self._format_to_dict(values),
171
+ n_shots=n_shots,
172
+ state=state,
173
+ noise=noise,
174
+ mitigation=mitigation,
175
+ endianness=endianness,
176
+ )
177
+
178
+ def expectation(
179
+ self,
180
+ values: Tensor | dict[str, Tensor] = {},
181
+ observable: list[ConvertedObservable] | ConvertedObservable | None = None,
182
+ state: Tensor | None = None,
183
+ measurement: Measurements | None = None,
184
+ noise: Noise | None = None,
185
+ mitigation: Mitigations | None = None,
186
+ endianness: Endianness = Endianness.BIG,
187
+ ) -> Tensor:
117
188
  if values is None:
118
189
  values = {}
119
- if not isinstance(values, dict):
120
- values = self._format_to_dict(values)
121
190
  if measurement is None:
122
191
  measurement = self._measurement
123
192
  if noise is None:
124
193
  noise = self._noise
125
-
126
194
  return self.transform(
127
- self.expectation(
128
- values=values,
195
+ super().expectation(
196
+ values=self._format_to_dict(values),
129
197
  state=state,
130
198
  measurement=measurement,
131
199
  endianness=endianness,
@@ -139,6 +207,9 @@ class QNN(QuantumModel):
139
207
  The tensor is assumed to have dimensions: n_batches x in_features where in_features
140
208
  corresponds to the number of input features of the QNN
141
209
  """
210
+ # for backwards compat...
211
+ if isinstance(values, dict):
212
+ return values
142
213
 
143
214
  if len(values.size()) == 1:
144
215
  values = values.reshape(-1, 1)
@@ -146,10 +217,4 @@ class QNN(QuantumModel):
146
217
  assert len(values.size()) == 2, msg
147
218
  assert values.size()[1] == self.in_features, msg
148
219
 
149
- names = [p.name for p in self.inputs]
150
- res = {}
151
- for i, name in enumerate(names):
152
- res[name] = values[:, i]
153
- return res
154
-
155
- # TODO: Implement derivatives w.r.t. to inputs
220
+ return {var.name: values[:, self.inputs.index(var)] for var in self.inputs}
@@ -18,11 +18,14 @@ from qadence.backend import (
18
18
  )
19
19
  from qadence.backends.api import backend_factory, config_factory
20
20
  from qadence.blocks.abstract import AbstractBlock
21
+ from qadence.blocks.utils import chain, unique_parameters
21
22
  from qadence.circuit import QuantumCircuit
23
+ from qadence.engines.differentiable_backend import DifferentiableBackend
22
24
  from qadence.logger import get_logger
23
25
  from qadence.measurements import Measurements
24
26
  from qadence.mitigations import Mitigations
25
27
  from qadence.noise import Noise
28
+ from qadence.parameters import Parameter
26
29
  from qadence.types import DiffMode, Endianness
27
30
 
28
31
  logger = get_logger(__name__)
@@ -36,7 +39,7 @@ class QuantumModel(nn.Module):
36
39
  [here](/advanced_tutorials/custom-models.md).
37
40
  """
38
41
 
39
- backend: Backend
42
+ backend: Backend | DifferentiableBackend
40
43
  embedding_fn: Callable
41
44
  _params: nn.ParameterDict
42
45
  _circuit: ConvertedCircuit
@@ -77,7 +80,6 @@ class QuantumModel(nn.Module):
77
80
  f"The circuit should be of type '<class QuantumCircuit>'. Got {type(circuit)}."
78
81
  )
79
82
 
80
- self.inputs = [p for p in circuit.unique_parameters if not p.trainable and not p.is_number]
81
83
  if diff_mode is None:
82
84
  raise ValueError("`diff_mode` cannot be `None` in a `QuantumModel`.")
83
85
 
@@ -90,6 +92,15 @@ class QuantumModel(nn.Module):
90
92
  else:
91
93
  observable = [observable]
92
94
 
95
+ def _is_feature_param(p: Parameter) -> bool:
96
+ return not p.trainable and not p.is_number
97
+
98
+ if observable is None:
99
+ self.inputs = list(filter(_is_feature_param, circuit.unique_parameters))
100
+ else:
101
+ uparams = unique_parameters(chain(circuit.block, *observable))
102
+ self.inputs = list(filter(_is_feature_param, uparams))
103
+
93
104
  conv = self.backend.convert(circuit, observable)
94
105
  self.embedding_fn = conv.embedding_fn
95
106
  self._circuit = conv.circuit
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
- class N(PrimitiveBlock):
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((I(control) - Z(control)) * 0.5, X(target) - I(target))
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(*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], X(target))
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(*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Y(target))
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: Parameter | TNumber | sympy.Expr | str,
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(*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target))
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 = 0.5 * (Z(control) - I(control))
882
- a00p = -0.5 * (Z(control) + I(control))
883
- a11 = 0.5 * (Z(target1) - I(target1))
884
- a22 = -0.5 * (Z(target2) + I(target2))
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
- if isinstance(duration, str):
1178
- duration = Parameter(duration)
1179
- alpha = duration * sympy.sqrt(omega**2 + delta**2) / 1000 # type: ignore [operator]
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,17 +8,19 @@ 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 sympytorch import SymPyModule
12
- from torch import Tensor, rand, tensor
11
+ from sympy.physics.quantum.dagger import Dagger
12
+ from sympytorch import SymPyModule as torchSympyModule
13
+ from torch import Tensor, heaviside, no_grad, rand, tensor
13
14
 
14
15
  from qadence.logger import get_logger
15
- from qadence.types import TNumber
16
+ from qadence.types import DifferentiableExpression, Engine, TNumber
16
17
 
17
18
  # Modules to be automatically added to the qadence namespace
18
19
  __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#",
@@ -188,17 +190,26 @@ def extract_original_param_entry(
188
190
  return param if not param.is_number else evaluate(param)
189
191
 
190
192
 
191
- def torchify(expr: Expr) -> SymPyModule:
192
- """
193
- Arguments:
193
+ def heaviside_func(x: Tensor, _: Any) -> Tensor:
194
+ with no_grad():
195
+ res = heaviside(x, tensor(0.5))
196
+ return res
194
197
 
195
- expr: An expression consisting of Parameters.
196
198
 
197
- Returns:
198
- A torchified, differentiable Expression.
199
- """
200
- extra_funcs = {sympy.core.numbers.ImaginaryUnit: 1.0j}
201
- return SymPyModule(expressions=[sympy.N(expr)], extra_funcs=extra_funcs)
199
+ def torchify(expr: Expr) -> torchSympyModule:
200
+ extra_funcs = {sympy.core.numbers.ImaginaryUnit: 1.0j, sympy.Heaviside: heaviside_func}
201
+ return torchSympyModule(expressions=[sympy.N(expr)], extra_funcs=extra_funcs)
202
+
203
+
204
+ def make_differentiable(expr: Expr, engine: Engine = Engine.TORCH) -> DifferentiableExpression:
205
+ diff_expr: DifferentiableExpression
206
+ if engine == Engine.JAX:
207
+ from qadence.backends.jax_utils import jaxify
208
+
209
+ diff_expr = jaxify(expr)
210
+ else:
211
+ diff_expr = torchify(expr)
212
+ return diff_expr
202
213
 
203
214
 
204
215
  def sympy_to_numeric(expr: Basic) -> TNumber:
@@ -253,7 +264,7 @@ def evaluate(expr: Expr, values: dict = {}, as_torch: bool = False) -> TNumber |
253
264
  else:
254
265
  raise ValueError(f"No value provided for symbol {s.name}")
255
266
  if as_torch:
256
- res_value = torchify(expr)(**{s.name: tensor(v) for s, v in query.items()})
267
+ res_value = make_differentiable(expr)(**{s.name: tensor(v) for s, v in query.items()})
257
268
  else:
258
269
  res = expr.subs(query)
259
270
  res_value = sympy_to_numeric(res)