qilisdk 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 (47) hide show
  1. qilisdk/__init__.py +47 -0
  2. qilisdk/__init__.pyi +30 -0
  3. qilisdk/_optionals.py +105 -0
  4. qilisdk/analog/__init__.py +17 -0
  5. qilisdk/analog/algorithms.py +111 -0
  6. qilisdk/analog/analog_backend.py +43 -0
  7. qilisdk/analog/analog_result.py +114 -0
  8. qilisdk/analog/exceptions.py +19 -0
  9. qilisdk/analog/hamiltonian.py +706 -0
  10. qilisdk/analog/quantum_objects.py +486 -0
  11. qilisdk/analog/schedule.py +311 -0
  12. qilisdk/common/__init__.py +20 -0
  13. qilisdk/common/algorithm.py +17 -0
  14. qilisdk/common/backend.py +16 -0
  15. qilisdk/common/model.py +16 -0
  16. qilisdk/common/optimizer.py +136 -0
  17. qilisdk/common/optimizer_result.py +110 -0
  18. qilisdk/common/result.py +17 -0
  19. qilisdk/digital/__init__.py +66 -0
  20. qilisdk/digital/ansatz.py +143 -0
  21. qilisdk/digital/circuit.py +106 -0
  22. qilisdk/digital/digital_algorithm.py +20 -0
  23. qilisdk/digital/digital_backend.py +90 -0
  24. qilisdk/digital/digital_result.py +145 -0
  25. qilisdk/digital/exceptions.py +31 -0
  26. qilisdk/digital/gates.py +989 -0
  27. qilisdk/digital/vqe.py +165 -0
  28. qilisdk/extras/__init__.py +13 -0
  29. qilisdk/extras/cuda/__init__.py +18 -0
  30. qilisdk/extras/cuda/cuda_analog_result.py +19 -0
  31. qilisdk/extras/cuda/cuda_backend.py +398 -0
  32. qilisdk/extras/cuda/cuda_digital_result.py +19 -0
  33. qilisdk/extras/qaas/__init__.py +13 -0
  34. qilisdk/extras/qaas/keyring.py +54 -0
  35. qilisdk/extras/qaas/models.py +57 -0
  36. qilisdk/extras/qaas/qaas_backend.py +154 -0
  37. qilisdk/extras/qaas/qaas_digital_result.py +20 -0
  38. qilisdk/extras/qaas/qaas_settings.py +23 -0
  39. qilisdk/py.typed +0 -0
  40. qilisdk/utils/__init__.py +27 -0
  41. qilisdk/utils/openqasm2.py +215 -0
  42. qilisdk/utils/serialization.py +128 -0
  43. qilisdk/yaml.py +71 -0
  44. qilisdk-0.1.0.dist-info/METADATA +237 -0
  45. qilisdk-0.1.0.dist-info/RECORD +47 -0
  46. qilisdk-0.1.0.dist-info/WHEEL +4 -0
  47. qilisdk-0.1.0.dist-info/licenses/LICENCE +201 -0
@@ -0,0 +1,989 @@
1
+ # Copyright 2025 Qilimanjaro Quantum Tech
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from __future__ import annotations
15
+
16
+ from abc import ABC, abstractmethod
17
+ from typing import ClassVar, Generic, TypeVar
18
+
19
+ import numpy as np
20
+ from scipy.linalg import expm
21
+ from typing_extensions import Self
22
+
23
+ from qilisdk.yaml import yaml
24
+
25
+ from .exceptions import (
26
+ GateHasNoMatrixError,
27
+ GateNotParameterizedError,
28
+ InvalidParameterNameError,
29
+ ParametersNotEqualError,
30
+ )
31
+
32
+ TBasicGate = TypeVar("TBasicGate", bound="BasicGate")
33
+
34
+
35
+ class Gate(ABC):
36
+ """
37
+ Represents a quantum gate that can be used in quantum circuits.
38
+ """
39
+
40
+ PARAMETER_NAMES: ClassVar[list[str]] = []
41
+
42
+ @property
43
+ @abstractmethod
44
+ def name(self) -> str:
45
+ """
46
+ Retrieve the name of the gate.
47
+
48
+ Returns:
49
+ str: The name of the gate.
50
+ """
51
+
52
+ @property
53
+ @abstractmethod
54
+ def matrix(self) -> np.ndarray:
55
+ """
56
+ Retrieve the matrix of the gate.
57
+
58
+ Raises:
59
+ GateHasNoMatrixError: If gate has no matrix.
60
+
61
+ Returns:
62
+ np.ndarray: The matrix of the gate.
63
+ """
64
+
65
+ @property
66
+ def control_qubits(self) -> tuple[int, ...]:
67
+ """
68
+ Retrieve the indices of the control qubits.
69
+
70
+ Returns:
71
+ tuple[int, ...]: A tuple containing the indices of the control qubits.
72
+ """
73
+ return ()
74
+
75
+ @property
76
+ def target_qubits(self) -> tuple[int, ...]:
77
+ """
78
+ Retrieve the indices of the target qubits.
79
+
80
+ Returns:
81
+ tuple[int, ...]: A tuple containing the indices of the target qubits.
82
+ """
83
+ return ()
84
+
85
+ @property
86
+ def qubits(self) -> tuple[int, ...]:
87
+ """
88
+ Retrieve all qubits associated with the gate, including both control and target qubits.
89
+
90
+ Returns:
91
+ tuple[int, ...]: A tuple of all qubit indices on which the gate operates.
92
+ """
93
+ return self.control_qubits + self.target_qubits
94
+
95
+ @property
96
+ def nqubits(self) -> int:
97
+ """
98
+ Retrieve the number of qubits the gate acts upon.
99
+
100
+ Returns:
101
+ int: The number of qubits for this gate.
102
+ """
103
+ return len(self.qubits)
104
+
105
+ @property
106
+ def parameters(self) -> dict[str, float]:
107
+ """
108
+ Retrieve a mapping of parameter names to their corresponding values.
109
+
110
+ Returns:
111
+ dict[str, float]: A dictionary mapping each parameter name to its numeric value.
112
+ """
113
+ return {}
114
+
115
+ @property
116
+ def parameter_names(self) -> list[str]:
117
+ """
118
+ Retrieve the symbolic names of the gate's parameters.
119
+
120
+ Returns:
121
+ list[str]: A list containing the names of the parameters.
122
+ """
123
+ return list(self.parameters.keys())
124
+
125
+ @property
126
+ def parameter_values(self) -> list[float]:
127
+ """
128
+ Retrieve the numerical values assigned to the gate's parameters.
129
+
130
+ Returns:
131
+ list[float]: A list containing the parameter values.
132
+ """
133
+ return list(self.parameters.values())
134
+
135
+ @property
136
+ def nparameters(self) -> int:
137
+ """
138
+ Retrieve the number of parameters for the gate.
139
+
140
+ Returns:
141
+ int: The count of parameters needed by the gate.
142
+ """
143
+ return len(self.parameters)
144
+
145
+ @property
146
+ def is_parameterized(self) -> bool:
147
+ """
148
+ Determine whether the gate requires parameters.
149
+
150
+ Returns:
151
+ bool: True if the gate is parameterized; otherwise, False.
152
+ """
153
+ return self.nparameters != 0
154
+
155
+ def set_parameters(self, parameters: dict[str, float]) -> None:
156
+ """
157
+ Set the parameters for the gate using a dictionary mapping names to values.
158
+
159
+ Args:
160
+ parameters (dict[str, float]): A dictionary where keys are parameter names and values are the new parameter values.
161
+
162
+ Raises:
163
+ GateNotParameterizedError: If gate is not parameterized.
164
+ InvalidParameterNameError: If any provided parameter name is not valid for this gate.
165
+ """
166
+ if not self.is_parameterized:
167
+ raise GateNotParameterizedError
168
+
169
+ if any(name not in self.parameters for name in parameters):
170
+ raise InvalidParameterNameError
171
+
172
+ def set_parameter_values(self, values: list[float]) -> None:
173
+ """
174
+ Set the numerical values for the gate's parameters.
175
+
176
+ Args:
177
+ values (list[float]): A list containing new parameter values.
178
+
179
+ Raises:
180
+ GateNotParameterizedError: If gate is not parameterized.
181
+ ParametersNotEqualError: If the number of provided values does not match the expected parameter count.
182
+ """
183
+ if not self.is_parameterized:
184
+ raise GateNotParameterizedError
185
+
186
+ if len(values) != len(self.parameters):
187
+ raise ParametersNotEqualError
188
+
189
+ def __repr__(self) -> str:
190
+ qubits_str = f"({self.qubits[0]})" if self.nqubits == 1 else str(self.qubits)
191
+ return f"{self.name}{qubits_str}"
192
+
193
+
194
+ class BasicGate(Gate):
195
+ """
196
+ Represents a quantum gate that can be used in quantum circuits.
197
+ """
198
+
199
+ def __init__(self, target_qubits: tuple[int, ...], parameters: dict[str, float] = {}) -> None:
200
+ # Check for duplicate integers in target_qubits.
201
+ if len(target_qubits) != len(set(target_qubits)):
202
+ raise ValueError("Duplicate target qubits found.")
203
+
204
+ self._target_qubits: tuple[int, ...] = target_qubits
205
+ self._parameters: dict[str, float] = parameters
206
+ self._matrix: np.ndarray = self._generate_matrix()
207
+
208
+ @property
209
+ def matrix(self) -> np.ndarray:
210
+ return self._matrix
211
+
212
+ @property
213
+ def target_qubits(self) -> tuple[int, ...]:
214
+ return self._target_qubits
215
+
216
+ @property
217
+ def parameters(self) -> dict[str, float]:
218
+ return dict(self._parameters)
219
+
220
+ def set_parameters(self, parameters: dict[str, float]) -> None:
221
+ super().set_parameters(parameters=parameters)
222
+ self._parameters.update(parameters)
223
+ self._matrix = self._generate_matrix()
224
+
225
+ def set_parameter_values(self, values: list[float]) -> None:
226
+ super().set_parameter_values(values=values)
227
+
228
+ for key, value in zip(self.parameters, values):
229
+ self._parameters[key] = value
230
+
231
+ self._matrix = self._generate_matrix()
232
+
233
+ def controlled(self: Self, *control_qubits: int) -> Controlled[Self]:
234
+ """
235
+ Creates a controlled version of this unitary gate.
236
+
237
+ This method returns a new instance of a Controlled gate where the provided qubits serve as the
238
+ control qubits and the current unitary gate is used as the target. The resulting gate operates
239
+ on both the control and target qubits.
240
+
241
+ Args:
242
+ *control_qubits (int): One or more integer indices specifying the control qubits.
243
+
244
+ Returns:
245
+ Controlled: A new Controlled gate instance that wraps this unitary gate with the specified control qubits.
246
+ """
247
+ return Controlled(*control_qubits, basic_gate=self)
248
+
249
+ def adjoint(self: Self) -> Adjoint[Self]:
250
+ """
251
+ Returns the adjoint (conjugate transpose) of this unitary gate.
252
+
253
+ This method constructs and returns a new gate whose matrix is the conjugate transpose of the current
254
+ gate's matrix. The adjoint (or Hermitian conjugate) is commonly used to reverse the effect of a unitary operation.
255
+
256
+ Returns:
257
+ Adjoint: A new Adjoint gate instance representing the adjoint of this gate.
258
+ """
259
+ return Adjoint(basic_gate=self)
260
+
261
+ def exponential(self: Self) -> Exponential[Self]:
262
+ """
263
+ Returns the exponential of this unitary gate.
264
+
265
+ This method computes the matrix exponential of the current gate's matrix, resulting in a new gate.
266
+ The matrix exponential is useful for representing continuous time evolution in quantum systems and
267
+ appears in various quantum mechanics and quantum computing applications.
268
+
269
+ Returns:
270
+ Exponential: A new Exponential gate instance whose matrix is the matrix exponential of the current gate's matrix.
271
+ """
272
+ return Exponential(basic_gate=self)
273
+
274
+ @abstractmethod
275
+ def _generate_matrix(self) -> np.ndarray: ...
276
+
277
+
278
+ @yaml.register_class
279
+ class Modified(Gate, Generic[TBasicGate]):
280
+ def __init__(self, basic_gate: TBasicGate) -> None:
281
+ self._basic_gate: TBasicGate = basic_gate
282
+ self._matrix: np.ndarray
283
+
284
+ @property
285
+ def basic_gate(self) -> TBasicGate:
286
+ return self._basic_gate
287
+
288
+ @property
289
+ def matrix(self) -> np.ndarray:
290
+ return self._matrix
291
+
292
+ @property
293
+ def target_qubits(self) -> tuple[int, ...]:
294
+ return self._basic_gate.target_qubits
295
+
296
+ @property
297
+ def parameters(self) -> dict[str, float]:
298
+ return self._basic_gate.parameters
299
+
300
+ @property
301
+ def parameter_names(self) -> list[str]:
302
+ return self._basic_gate.parameter_names
303
+
304
+ @property
305
+ def parameter_values(self) -> list[float]:
306
+ return self._basic_gate.parameter_values
307
+
308
+ @property
309
+ def nparameters(self) -> int:
310
+ return self._basic_gate.nparameters
311
+
312
+ @property
313
+ def is_parameterized(self) -> bool:
314
+ return self._basic_gate.is_parameterized
315
+
316
+ def set_parameters(self, parameters: dict[str, float]) -> None:
317
+ self._basic_gate.set_parameters(parameters=parameters)
318
+ self._matrix = self._generate_matrix()
319
+
320
+ def set_parameter_values(self, values: list[float]) -> None:
321
+ self._basic_gate.set_parameter_values(values=values)
322
+ self._matrix = self._generate_matrix()
323
+
324
+ @abstractmethod
325
+ def _generate_matrix(self) -> np.ndarray: ...
326
+
327
+ def is_modified_from(self, gate_type: type[TBasicGate]) -> bool:
328
+ return isinstance(self.basic_gate, gate_type)
329
+
330
+
331
+ @yaml.register_class
332
+ class Controlled(Modified[TBasicGate]):
333
+ def __init__(self, *control_qubits: int, basic_gate: TBasicGate) -> None:
334
+ super().__init__(basic_gate=basic_gate)
335
+
336
+ # Check for duplicate integers in control_qubits.
337
+ if len(control_qubits) != len(set(control_qubits)):
338
+ raise ValueError("Duplicate control qubits found.")
339
+
340
+ # Check if any integer in control_qubits is also in unitary_gate.target_qubits.
341
+ if set(control_qubits) & set(basic_gate.target_qubits):
342
+ raise ValueError("Some control qubits are the same as unitary gate's target qubits.")
343
+
344
+ self._control_qubits = control_qubits
345
+ self._matrix = self._generate_matrix()
346
+
347
+ def _generate_matrix(self) -> np.ndarray:
348
+ I_full = np.eye(1 << self.nqubits, dtype=complex)
349
+ # Construct projector P_control = |1...1><1...1| on the n control qubits.
350
+ P = np.array([[0, 0], [0, 1]], dtype=complex)
351
+ for _ in range(len(self.control_qubits) - 1):
352
+ P = np.kron(P, np.array([[0, 0], [0, 1]], dtype=complex))
353
+ # Extend the projector to the full space (control qubits ⊗ target qubit). It acts as P_control ⊗ I_target.
354
+ I_target = np.eye(1 << len(self.target_qubits), dtype=complex)
355
+ # The controlled gate is then:
356
+ controlled = I_full + np.kron(P, self.basic_gate.matrix - I_target)
357
+ return controlled
358
+
359
+ @property
360
+ def control_qubits(self) -> tuple[int, ...]:
361
+ return self._control_qubits
362
+
363
+ @property
364
+ def name(self) -> str:
365
+ return "C" * len(self.control_qubits) + self.basic_gate.name
366
+
367
+
368
+ @yaml.register_class
369
+ class Adjoint(Modified[TBasicGate]):
370
+ """
371
+ Represents the adjoint (conjugate transpose) of a unitary gate.
372
+ """
373
+
374
+ def __init__(self, basic_gate: TBasicGate) -> None:
375
+ super().__init__(basic_gate=basic_gate)
376
+ self._matrix = self._generate_matrix()
377
+
378
+ def _generate_matrix(self) -> np.ndarray:
379
+ return self.basic_gate.matrix.conj().T
380
+
381
+ @property
382
+ def name(self) -> str:
383
+ """
384
+ Get the name of the adjoint gate.
385
+
386
+ The name is constructed by appending an adjoint symbol (†) to the name of the underlying gate.
387
+ For example, if the underlying gate's name is "X", this property returns "X†".
388
+
389
+ Returns:
390
+ str: The name of the adjoint gate.
391
+ """
392
+ return self.basic_gate.name + "†"
393
+
394
+
395
+ @yaml.register_class
396
+ class Exponential(Modified[TBasicGate]):
397
+ """
398
+ Represents the exponential of a unitary gate.
399
+ The matrix of this gate is computed as the matrix exponential (e^(gate.matrix))
400
+ of the underlying gate's matrix.
401
+ """
402
+
403
+ def __init__(self, basic_gate: TBasicGate) -> None:
404
+ super().__init__(basic_gate=basic_gate)
405
+ self._matrix = self._generate_matrix()
406
+
407
+ def _generate_matrix(self) -> np.ndarray:
408
+ return expm(self.basic_gate.matrix)
409
+
410
+ @property
411
+ def name(self) -> str:
412
+ """
413
+ Get the name of the exponential gate.
414
+
415
+ The name is constructed by prepending the underlying gate's name within "e^".
416
+ For example, if the underlying gate's name is "X", the exponential gate's name is "e^X".
417
+
418
+ Returns:
419
+ str: The generated name of the exponential gate.
420
+ """
421
+ return f"e^{self.basic_gate.name}"
422
+
423
+
424
+ @yaml.register_class
425
+ class M(Gate):
426
+ """
427
+ Measurement operation on a qubit. The measurement is performed in the computational basis and the
428
+ result is stored in a classical bit with the same label as the measured qubit.
429
+ """
430
+
431
+ def __init__(self, *qubits: int) -> None:
432
+ """
433
+ Initialize a measurement operation.
434
+
435
+ Args:
436
+ qubit (int): The qubit index to be measured.
437
+ """
438
+ self._target_qubits = qubits
439
+
440
+ @property
441
+ def name(self) -> str:
442
+ return "M"
443
+
444
+ @property
445
+ def matrix(self) -> np.ndarray:
446
+ raise GateHasNoMatrixError
447
+
448
+ @property
449
+ def target_qubits(self) -> tuple[int, ...]:
450
+ return self._target_qubits
451
+
452
+
453
+ @yaml.register_class
454
+ class X(BasicGate):
455
+ """
456
+ The Pauli-X gate.
457
+
458
+ The associated matrix is:
459
+ [[0, 1],
460
+ [1, 0]]
461
+
462
+ This is a pi radians rotation around the X-axis in the Bloch sphere.
463
+ """
464
+
465
+ def __init__(self, qubit: int) -> None:
466
+ """
467
+ Initialize a Pauli-X gate.
468
+
469
+ Args:
470
+ qubit (int): The target qubit index for the X gate.
471
+ """
472
+ super().__init__(target_qubits=(qubit,))
473
+
474
+ @property
475
+ def name(self) -> str:
476
+ return "X"
477
+
478
+ def _generate_matrix(self) -> np.ndarray: # noqa: PLR6301
479
+ return np.array([[0, 1], [1, 0]], dtype=complex)
480
+
481
+
482
+ @yaml.register_class
483
+ class Y(BasicGate):
484
+ """
485
+ The Pauli-Y gate.
486
+
487
+ The associated matrix is:
488
+
489
+ .. code-block:: text
490
+ [[0, -i],
491
+ [i, 0]]
492
+
493
+ This is a pi radians rotation around the Y-axis in the Bloch sphere.
494
+ """
495
+
496
+ def __init__(self, qubit: int) -> None:
497
+ """
498
+ Initialize a Pauli-Y gate.
499
+
500
+ Args:
501
+ qubit (int): The target qubit index for the Y gate.
502
+ """
503
+ super().__init__(target_qubits=(qubit,))
504
+
505
+ @property
506
+ def name(self) -> str:
507
+ return "Y"
508
+
509
+ def _generate_matrix(self) -> np.ndarray: # noqa: PLR6301
510
+ return np.array([[0, -1j], [1j, 0]], dtype=complex)
511
+
512
+
513
+ @yaml.register_class
514
+ class Z(BasicGate):
515
+ """
516
+ The Pauli-Z gate.
517
+
518
+ The associated matrix is:
519
+
520
+ .. code-block:: text
521
+ [[1, 0],
522
+ [0, -1]]
523
+
524
+ This is a pi radians rotation around the Z-axis in the Bloch sphere.
525
+ """
526
+
527
+ def __init__(self, qubit: int) -> None:
528
+ """
529
+ Initialize a Pauli-Z gate.
530
+
531
+ Args:
532
+ qubit (int): The target qubit index for the Z gate.
533
+ """
534
+ super().__init__(target_qubits=(qubit,))
535
+
536
+ @property
537
+ def name(self) -> str:
538
+ return "Z"
539
+
540
+ def _generate_matrix(self) -> np.ndarray: # noqa: PLR6301
541
+ return np.array([[1, 0], [0, -1]], dtype=complex)
542
+
543
+
544
+ @yaml.register_class
545
+ class H(BasicGate):
546
+ """
547
+ The Hadamard gate.
548
+
549
+ The associated matrix is:
550
+
551
+ .. code-block:: text
552
+ 1/sqrt(2) * [[1, 1],
553
+ [1, -1]]
554
+
555
+ This is a pi radians rotation around the (X+Z)-axis in the Bloch sphere.
556
+ """
557
+
558
+ def __init__(self, qubit: int) -> None:
559
+ """
560
+ Initialize a Hadamard gate.
561
+
562
+ Args:
563
+ qubit (int): The target qubit index for the Hadamard gate.
564
+ """
565
+ super().__init__(target_qubits=(qubit,))
566
+
567
+ @property
568
+ def name(self) -> str:
569
+ return "H"
570
+
571
+ def _generate_matrix(self) -> np.ndarray: # noqa: PLR6301
572
+ return (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex)
573
+
574
+
575
+ @yaml.register_class
576
+ class S(BasicGate):
577
+ """
578
+ Represents the S gate, which induces a pi/2 phase.
579
+
580
+ The associated matrix is:
581
+
582
+ .. code-block:: text
583
+ [[1, 0],
584
+ [0, i]]
585
+
586
+ This gate is also known as the square root of Z gate: ``S**2=Z``, or equivalently it is a pi/2 radians rotation around the Z-axis in the Bloch sphere.
587
+ """
588
+
589
+ def __init__(self, qubit: int) -> None:
590
+ """
591
+ Initialize an S gate.
592
+
593
+ Args:
594
+ qubit (int): The target qubit index for the S gate.
595
+ """
596
+ super().__init__(target_qubits=(qubit,))
597
+
598
+ @property
599
+ def name(self) -> str:
600
+ return "S"
601
+
602
+ def _generate_matrix(self) -> np.ndarray: # noqa: PLR6301
603
+ return np.array([[1, 0], [0, 1j]], dtype=complex)
604
+
605
+
606
+ @yaml.register_class
607
+ class T(BasicGate):
608
+ """
609
+ Represents the T gate, which induces a pi/4 phase.
610
+
611
+ The associated matrix is:
612
+
613
+ .. code-block:: text
614
+ [[1, 0],
615
+ [0, exp(i*pi/4)]]
616
+
617
+ This gate is also known as the fourth-root of Z gate: ``T**4=Z``, or equivalently it is a pi/4 radians rotation around the Z-axis in the Bloch sphere.
618
+ """
619
+
620
+ def __init__(self, qubit: int) -> None:
621
+ """
622
+ Initialize a T gate.
623
+
624
+ Args:
625
+ qubit (int): The target qubit index for the T gate.
626
+ """
627
+ super().__init__(target_qubits=(qubit,))
628
+
629
+ @property
630
+ def name(self) -> str:
631
+ return "T"
632
+
633
+ def _generate_matrix(self) -> np.ndarray: # noqa: PLR6301
634
+ return np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex)
635
+
636
+
637
+ @yaml.register_class
638
+ class RX(BasicGate):
639
+ """
640
+ Represents a `theta` angle rotation around the X-axis (polar) in the Bloch sphere.
641
+
642
+ The associated matrix is:
643
+
644
+ .. code-block:: text
645
+ [[cos(theta/2), -i*sin(theta/2)],
646
+ [-i*sin(theta/2), cos(theta/2)]]
647
+
648
+ This is an exponential of the Pauli-X operator:
649
+ ``RX(theta) = exp(-i*theta*X/2)``
650
+ """
651
+
652
+ PARAMETER_NAMES: ClassVar[list[str]] = ["theta"]
653
+
654
+ def __init__(self, qubit: int, *, theta: float) -> None:
655
+ """
656
+ Initialize an RX gate.
657
+
658
+ Args:
659
+ qubit (int): The target qubit index for the rotation.
660
+ theta (float): The rotation angle (polar) in radians.
661
+ """
662
+ super().__init__(target_qubits=(qubit,), parameters={"theta": theta})
663
+
664
+ @property
665
+ def name(self) -> str:
666
+ return "RX"
667
+
668
+ @property
669
+ def theta(self) -> float:
670
+ return self.parameters["theta"]
671
+
672
+ def _generate_matrix(self) -> np.ndarray:
673
+ theta = self.parameters["theta"]
674
+ cos_half = np.cos(theta / 2)
675
+ sin_half = np.sin(theta / 2)
676
+ return np.array([[cos_half, -1j * sin_half], [-1j * sin_half, cos_half]], dtype=complex)
677
+
678
+
679
+ @yaml.register_class
680
+ class RY(BasicGate):
681
+ """
682
+ Represents a `theta` angle rotation around the Y-axis (polar) in the Bloch sphere.
683
+
684
+ The associated matrix is:
685
+
686
+ .. code-block:: text
687
+ [[cos(theta/2), -sin(theta/2)],
688
+ [sin(theta/2), cos(theta/2)]]
689
+
690
+ This is an exponential of the Pauli-Y operator:
691
+ ``RY(theta) = exp(-i*theta*Y/2)``
692
+ """
693
+
694
+ PARAMETER_NAMES: ClassVar[list[str]] = ["theta"]
695
+
696
+ def __init__(self, qubit: int, *, theta: float) -> None:
697
+ """
698
+ Initialize an RY gate.
699
+
700
+ Args:
701
+ qubit (int): The target qubit index for the rotation.
702
+ theta (float): The rotation angle (polar) in radians.
703
+ """
704
+ super().__init__(target_qubits=(qubit,), parameters={"theta": theta})
705
+
706
+ @property
707
+ def name(self) -> str:
708
+ return "RY"
709
+
710
+ @property
711
+ def theta(self) -> float:
712
+ return self.parameters["theta"]
713
+
714
+ def _generate_matrix(self) -> np.ndarray:
715
+ theta = self.parameters["theta"]
716
+ cos_half = np.cos(theta / 2)
717
+ sin_half = np.sin(theta / 2)
718
+ return np.array([[cos_half, -sin_half], [sin_half, cos_half]], dtype=complex)
719
+
720
+
721
+ @yaml.register_class
722
+ class RZ(BasicGate):
723
+ """
724
+ Represents a `phi` angle rotation around the Z-axis (azimuthal) in the Bloch sphere.
725
+
726
+ The associated matrix is:
727
+
728
+ .. code-block:: text
729
+ [[exp(-i*phi/2), 0],
730
+ [0, exp(i*phi/2)]]
731
+
732
+ This is an exponential of the Pauli-Z operator:
733
+ ``RZ(phi) = exp(-i*phi*Z/2)``
734
+
735
+ Which is equivalent to the U1 gate plus a global phase:
736
+ ``RZ(phi) = exp(-i*phi/2)U1(phi)``
737
+
738
+ Other unitaries you can get from this one are:
739
+ - ``RZ(phi=pi) = exp(-i*pi/2) Z = -i Z``
740
+ - ``RZ(phi=pi/2) = exp(-i*pi/4) S``
741
+ - ``RZ(phi=pi/4) = exp(-i*pi/8) T``
742
+ """
743
+
744
+ PARAMETER_NAMES: ClassVar[list[str]] = ["phi"]
745
+
746
+ def __init__(self, qubit: int, *, phi: float) -> None:
747
+ """
748
+ Initialize an RZ gate.
749
+
750
+ Args:
751
+ qubit (int): The target qubit index for the rotation.
752
+ phi (float): The rotation angle (azimuthal) in radians.
753
+ """
754
+ super().__init__(target_qubits=(qubit,), parameters={"phi": phi})
755
+
756
+ @property
757
+ def name(self) -> str:
758
+ return "RZ"
759
+
760
+ @property
761
+ def theta(self) -> float:
762
+ return self.parameters["theta"]
763
+
764
+ def _generate_matrix(self) -> np.ndarray:
765
+ phi = self.parameters["phi"]
766
+ cos_half = np.cos(phi / 2)
767
+ sin_half = np.sin(phi / 2)
768
+ return np.array([[cos_half, -sin_half], [sin_half, cos_half]], dtype=complex)
769
+
770
+
771
+ @yaml.register_class
772
+ class U1(BasicGate):
773
+ """
774
+ Represents the U1 gate defined by an azimuthal angle `phi`.
775
+
776
+ The associated matrix is:
777
+
778
+ .. code-block:: text
779
+ [[1, 0],
780
+ [0, exp(i*phi)]]
781
+
782
+ Which is equivalent to the RZ gate plus a global phase:
783
+ ``U1(phi) = exp(i*phi/2)RZ(phi)``
784
+
785
+ Other unitaries you can get from this one are:
786
+ - ``U1(phi=np.pi) = Z``
787
+ - ``U1(phi=np.pi/2) = S``
788
+ - ``U1(phi=np.pi/4) = T``
789
+ """
790
+
791
+ PARAMETER_NAMES: ClassVar[list[str]] = ["phi"]
792
+
793
+ def __init__(self, qubit: int, *, phi: float) -> None:
794
+ """
795
+ Initialize a U1 gate.
796
+
797
+ Args:
798
+ qubit (int): The target qubit index for the U1 gate.
799
+ phi (float): The phase to add, or equivalently the rotation angle (azimuthal) in radians.
800
+ """
801
+ super().__init__(target_qubits=(qubit,), parameters={"phi": phi})
802
+
803
+ @property
804
+ def name(self) -> str:
805
+ return "U1"
806
+
807
+ @property
808
+ def phi(self) -> float:
809
+ return self.parameters["phi"]
810
+
811
+ def _generate_matrix(self) -> np.ndarray:
812
+ phi = self.parameters["phi"]
813
+ return np.array([[1, 0], [0, np.exp(1j * phi)]], dtype=complex)
814
+
815
+
816
+ @yaml.register_class
817
+ class U2(BasicGate):
818
+ """
819
+ Represents the U2 gate defined by the angles `phi` and `gamma`.
820
+
821
+ The associated matrix is:
822
+
823
+ .. code-block:: text
824
+ 1/sqrt(2)*[[1, -exp(i*gamma)],
825
+ [exp(i*phi), exp(i*(phi+gamma))]]
826
+
827
+ Which is equivalent to two azimuthal rotations of `phi` and `gamma`, with a pi/2 polar rotation in between:
828
+ ``U2(phi, gamma) = exp(i*(phi+gamma)/2) RZ(phi) RY(pi/2) RZ(gamma)``
829
+
830
+ This is the same matrix of `qiskit` and `pennylane`, differing from `qibo` implementation on a global phase:
831
+ ``U2(phi, gamma) = U2_qiskit/pennylane(phi, gamma) = exp(i*(phi+gamma)/2) U2_qibo(phi, gamma)``
832
+
833
+ Other unitaries you can get from this one are:
834
+ - ``U2(phi=0, gamma=np.pi) = H``
835
+ - ``U2(phi=0, gamma=0) = RY(theta=pi/2)``
836
+ - ``U2(phi=-pi/2, gamma=pi/2) = RX(theta=pi/2)``
837
+ """
838
+
839
+ PARAMETER_NAMES: ClassVar[list[str]] = ["phi", "gamma"]
840
+
841
+ def __init__(self, qubit: int, *, phi: float, gamma: float) -> None:
842
+ """
843
+ Initialize a U2 gate.
844
+
845
+ Args:
846
+ qubit (int): The target qubit index for the U2 gate.
847
+ phi (float): The first phase parameter, or equivalently the first rotation angle (azimuthal) in radians.
848
+ gamma (float): The second phase parameter, or equivalently the second rotation angle (azimuthal) in radians..
849
+ """
850
+ super().__init__(target_qubits=(qubit,), parameters={"phi": phi, "gamma": gamma})
851
+
852
+ @property
853
+ def name(self) -> str:
854
+ return "U2"
855
+
856
+ @property
857
+ def phi(self) -> float:
858
+ return self.parameters["phi"]
859
+
860
+ @property
861
+ def gamma(self) -> float:
862
+ return self.parameters["gamma"]
863
+
864
+ def _generate_matrix(self) -> np.ndarray:
865
+ phi = self.parameters["phi"]
866
+ gamma = self.parameters["gamma"]
867
+ return (1 / np.sqrt(2)) * np.array(
868
+ [
869
+ [1, -np.exp(1j * gamma)],
870
+ [np.exp(1j * phi), np.exp(1j * (phi + gamma))],
871
+ ],
872
+ dtype=complex,
873
+ )
874
+
875
+
876
+ @yaml.register_class
877
+ class U3(BasicGate):
878
+ """
879
+ Represents the U3 gate defined by the angles `theta`, `phi` and `gamma`.
880
+
881
+ The associated matrix is:
882
+
883
+ .. code-block:: text
884
+ [[cos(theta/2), -exp(i*gamma/2*sin(theta/2))],
885
+ [exp(i*phi/2)*sin(theta/2), exp(i*(phi+gamma))*cos(theta/2)]]
886
+
887
+ Which is equivalent to two azimuthal rotations of `phi` and `gamma`, with a 'theta' polar rotation in between:
888
+ ``U3(theta, phi, gamma) = exp(i*(phi+gamma)/2) RZ(phi) RY(theta) RZ(gamma)``
889
+
890
+ This is the same matrix of `qiskit` and `pennylane`, differing from `QASM` and `qibo` implementation on a global phase:
891
+ ``U3(theta, phi, gamma) = U3_qiskit/pennylane(theta, phi, gamma) = exp(-i*(phi+gamma)/2) U3_QASM/qibo(theta, phi, gamma)``
892
+
893
+ Other unitaries you can get from this one are:
894
+ - ``U3(theta=pi/2, phi, gamma) = U2(phi, gamma)``
895
+ - ``U3(theta=0, phi=0, gamma) = U1(gamma)`` and ``U3(theta=0, phi, gamma=0) = U1(phi)``
896
+ - ``U3(theta, phi=0, gamma=0) = RY(theta)``
897
+ - ``U3(theta, phi=-pi/2, gamma=pi/2) = RX(theta)``
898
+ """
899
+
900
+ PARAMETER_NAMES: ClassVar[list[str]] = ["theta", "phi", "gamma"]
901
+
902
+ def __init__(self, qubit: int, *, theta: float, phi: float, gamma: float) -> None:
903
+ """
904
+ Initialize a U3 gate.
905
+
906
+ Args:
907
+ qubit (int): The target qubit index for the U3 gate.
908
+ theta (float): The rotation angle (polar), in between both phase rotations (azimuthal).
909
+ phi (float): The first phase parameter, or equivalently the first rotation angle (azimuthal) in radians.
910
+ gamma (float): The second phase parameter, or equivalently the second rotation angle (azimuthal) in radians.
911
+ """
912
+ super().__init__(target_qubits=(qubit,), parameters={"theta": theta, "phi": phi, "gamma": gamma})
913
+
914
+ @property
915
+ def name(self) -> str:
916
+ return "U3"
917
+
918
+ @property
919
+ def theta(self) -> float:
920
+ return self.parameters["theta"]
921
+
922
+ @property
923
+ def phi(self) -> float:
924
+ return self.parameters["phi"]
925
+
926
+ @property
927
+ def gamma(self) -> float:
928
+ return self.parameters["gamma"]
929
+
930
+ def _generate_matrix(self) -> np.ndarray:
931
+ theta = self.parameters["theta"]
932
+ phi = self.parameters["phi"]
933
+ gamma = self.parameters["gamma"]
934
+ return np.array(
935
+ [
936
+ [np.cos(theta / 2), -np.exp(1j * gamma) * np.sin(theta / 2)],
937
+ [np.exp(1j * phi) * np.sin(theta / 2), np.exp(1j * (phi + gamma)) * np.cos(theta / 2)],
938
+ ],
939
+ dtype=complex,
940
+ )
941
+
942
+
943
+ @yaml.register_class
944
+ class CNOT(Controlled[X]):
945
+ """
946
+ Represents the CNOT gate.
947
+
948
+ The associated matrix is:
949
+
950
+ .. code-block:: text
951
+ [[1, 0, 0, 0],
952
+ [0, 1, 0, 0],
953
+ [0, 0, 0, 1],
954
+ [0, 0, 1, 0]]
955
+
956
+ Which is equivalent to the CZ gate surrounded by two H's gates on the target qubit:
957
+ ``CNOT(control, target) = H(target) CZ(control, target) H(target)``
958
+ """
959
+
960
+ def __init__(self, control: int, target: int) -> None:
961
+ super().__init__(control, basic_gate=X(target))
962
+
963
+ @property
964
+ def name(self) -> str:
965
+ return "CNOT"
966
+
967
+
968
+ @yaml.register_class
969
+ class CZ(Controlled[Z]):
970
+ """
971
+ Represents the CZ gate.
972
+
973
+ The associated matrix is:
974
+
975
+ .. code-block:: text
976
+ [[1, 0, 0, 0],
977
+ [0, 1, 0, 0],
978
+ [0, 0, 1, 0],
979
+ [0, 0, 0, -1]]
980
+
981
+ This gate is totally symmetric respect control and target, meaning that both are control and target in reality:
982
+ ``CZ(control, target) = CZ(target, control)``
983
+
984
+ It is also equivalent to the CNOT gate surrounded by two H's gates on the target qubit:
985
+ ``CZ(control, target) = H(target) CNOT(control, target) H(target)``
986
+ """
987
+
988
+ def __init__(self, control: int, target: int) -> None:
989
+ super().__init__(control, basic_gate=Z(target))