Coverage for qml_essentials/ansaetze.py: 96%
908 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-10-02 13:10 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-10-02 13:10 +0000
1from abc import ABC, abstractmethod
2from typing import Any, Optional, List, Union, Dict
3import numbers
4import pennylane.numpy as np
5import pennylane as qml
6import jax
7from jax import numpy as jnp
8import itertools
9from contextlib import contextmanager
10import logging
12jax.config.update("jax_enable_x64", True)
13log = logging.getLogger(__name__)
16class Circuit(ABC):
17 def __init__(self):
18 pass
20 @abstractmethod
21 def n_params_per_layer(n_qubits: int) -> int:
22 raise NotImplementedError("n_params_per_layer method is not implemented")
24 def n_pulse_params_per_layer(n_qubits: int) -> int:
25 """
26 Return the number of pulse parameters per layer.
28 Subclasses that do not use pulse-level simulation do not need to override this.
29 If called and not overridden, this will raise NotImplementedError.
30 """
31 raise NotImplementedError("n_pulse_params_per_layer method is not implemented")
33 @abstractmethod
34 def get_control_indices(self, n_qubits: int) -> List[int]:
35 """
36 Returns the indices for the controlled rotation gates for one layer.
37 Indices should slice the list of all parameters for one layer as follows:
38 [indices[0]:indices[1]:indices[2]]
40 Parameters
41 ----------
42 n_qubits : int
43 Number of qubits in the circuit
45 Returns
46 -------
47 Optional[np.ndarray]
48 List of all controlled indices, or None if the circuit does not
49 contain controlled rotation gates.
50 """
51 raise NotImplementedError("get_control_indices method is not implemented")
53 def get_control_angles(self, w: np.ndarray, n_qubits: int) -> Optional[np.ndarray]:
54 """
55 Returns the angles for the controlled rotation gates from the list of
56 all parameters for one layer.
58 Parameters
59 ----------
60 w : np.ndarray
61 List of parameters for one layer
62 n_qubits : int
63 Number of qubits in the circuit
65 Returns
66 -------
67 Optional[np.ndarray]
68 List of all controlled parameters, or None if the circuit does not
69 contain controlled rotation gates.
70 """
71 indices = self.get_control_indices(n_qubits)
72 if indices is None:
73 return np.array([])
75 return w[indices[0] : indices[1] : indices[2]]
77 def _build(self, w: np.ndarray, n_qubits: int, **kwargs):
78 """
79 Builds one layer of the circuit using either unitary or pulse-level parameters.
81 Parameters
82 ----------
83 w : np.ndarray
84 Array of parameters for the current layer.
85 n_qubits : int
86 Number of qubits in the circuit.
87 **kwargs
88 Additional keyword arguments. Supports:
89 - gate_mode : str, optional
90 "unitary" (default) or "pulse" to use pulse-level simulation.
91 - pulse_params : jnp.ndarray, optional
92 Array of pulse parameters to use if gate_mode="pulse".
93 - noise_params : dict, optional
94 Dictionary of noise parameters.
96 Raises
97 ------
98 ValueError
99 If the number of provided pulse parameters does not match the expected
100 number per layer.
101 """
102 gate_mode = kwargs.get("gate_mode", "unitary")
104 if gate_mode == "pulse" and "pulse_params" in kwargs:
105 pulse_params_per_layer = self.n_pulse_params_per_layer(n_qubits)
107 if len(kwargs["pulse_params"]) != pulse_params_per_layer:
108 raise ValueError(
109 f"Pulse params length {len(kwargs['pulse_params'])} "
110 f"does not match expected {pulse_params_per_layer} "
111 f"for {n_qubits} qubits"
112 )
114 with Gates.pulse_manager_context(kwargs["pulse_params"]):
115 return self.build(w, n_qubits, **kwargs)
116 else:
117 return self.build(w, n_qubits, **kwargs)
119 @abstractmethod
120 def build(self, n_qubits: int, n_layers: int):
121 raise NotImplementedError("build method is not implemented")
123 def __call__(self, *args: Any, **kwds: Any) -> Any:
124 self._build(*args, **kwds)
127class UnitaryGates:
128 rng = np.random.default_rng()
129 batch_gate_error = True
131 @staticmethod
132 def init_rng(seed: int):
133 """
134 Initializes the random number generator with the given seed.
136 Parameters
137 ----------
138 seed : int
139 The seed for the random number generator.
140 """
141 UnitaryGates.rng = np.random.default_rng(seed)
143 @staticmethod
144 def NQubitDepolarizingChannel(p, wires):
145 """
146 Generates the Kraus operators for an n-qubit depolarizing channel.
148 The n-qubit depolarizing channel is defined as:
149 E(rho) = sqrt(1 - p * (4^n - 1) / 4^n) * rho
150 + sqrt(p / 4^n) * ∑_{P ≠ I^{⊗n}} P rho P†
151 where the sum is over all non-identity n-qubit Pauli operators
152 (i.e., tensor products of {I, X, Y, Z} excluding the identity operator I^{⊗n}).
153 Each Pauli error operator is weighted equally by p / 4^n.
155 This operator-sum (Kraus) representation models uniform depolarizing noise
156 acting on n qubits simultaneously. It is useful for simulating realistic
157 multi-qubit noise affecting entangling gates in noisy quantum circuits.
159 Parameters
160 ----------
161 p : float
162 The total probability of an n-qubit depolarizing error occurring.
163 Must satisfy 0 ≤ p ≤ 1.
165 wires : Sequence[int]
166 The list of qubit indices (wires) on which the channel acts.
167 Must contain at least 2 qubits.
169 Returns
170 -------
171 qml.QubitChannel
172 A PennyLane QubitChannel constructed from the Kraus operators representing
173 the n-qubit depolarizing noise channel acting on the specified wires.
174 """
176 def n_qubit_depolarizing_kraus(p: float, n: int) -> List[np.ndarray]:
177 if not (0.0 <= p <= 1.0):
178 raise ValueError(f"Probability p must be between 0 and 1, got {p}")
179 if n < 2:
180 raise ValueError(f"Number of qubits must be >= 2, got {n}")
182 Id = np.eye(2)
183 X = qml.matrix(qml.PauliX(0))
184 Y = qml.matrix(qml.PauliY(0))
185 Z = qml.matrix(qml.PauliZ(0))
186 paulis = [Id, X, Y, Z]
188 dim = 2**n
189 all_ops = []
191 # Generate all n-qubit Pauli tensor products:
192 for indices in itertools.product(range(4), repeat=n):
193 P = np.eye(1)
194 for idx in indices:
195 P = np.kron(P, paulis[idx])
196 all_ops.append(P)
198 # Identity operator corresponds to all zeros indices (Id^n)
199 K0 = np.sqrt(1 - p * (4**n - 1) / (4**n)) * np.eye(dim)
201 kraus_ops = []
202 for i, P in enumerate(all_ops):
203 if i == 0:
204 # Skip the identity, already handled as K0
205 continue
206 kraus_ops.append(np.sqrt(p / (4**n)) * P)
208 return [K0] + kraus_ops
210 return qml.QubitChannel(n_qubit_depolarizing_kraus(p, len(wires)), wires=wires)
212 @staticmethod
213 def Noise(
214 wires: Union[int, List[int]], noise_params: Optional[Dict[str, float]] = None
215 ) -> None:
216 """
217 Applies noise to the given wires.
219 Parameters
220 ----------
221 wires : Union[int, List[int]]
222 The wire(s) to apply the noise to.
223 noise_params : Optional[Dict[str, float]]
224 A dictionary of noise parameters. The following noise gates are
225 supported:
226 -BitFlip: Applies a bit flip error to the given wires.
227 -PhaseFlip: Applies a phase flip error to the given wires.
228 -Depolarizing: Applies a depolarizing channel error to the
229 given wires.
230 -MultiQubitDepolarizing: Applies a two-qubit depolarizing channel
231 error to the given wires.
233 All parameters are optional and default to 0.0 if not provided.
234 """
235 if noise_params is not None:
236 if isinstance(wires, int):
237 wires = [wires] # single qubit gate
239 # noise on single qubits
240 for wire in wires:
241 bf = noise_params.get("BitFlip", 0.0)
242 if bf > 0:
243 qml.BitFlip(bf, wires=wire)
245 pf = noise_params.get("PhaseFlip", 0.0)
246 if pf > 0:
247 qml.PhaseFlip(pf, wires=wire)
249 dp = noise_params.get("Depolarizing", 0.0)
250 if dp > 0:
251 qml.DepolarizingChannel(dp, wires=wire)
253 # noise on two-qubits
254 if len(wires) > 1:
255 p = noise_params.get("MultiQubitDepolarizing", 0.0)
256 if p > 0:
257 UnitaryGates.NQubitDepolarizingChannel(p, wires)
259 @staticmethod
260 def GateError(
261 w: float, noise_params: Optional[Dict[str, float]] = None
262 ) -> np.ndarray:
263 """
264 Applies a gate error to the given rotation angle(s).
266 Parameters
267 ----------
268 w : Union[float, np.ndarray, List[float]]
269 The rotation angle in radians.
270 noise_params : Optional[Dict[str, float]]
271 A dictionary of noise parameters. The following noise gates are
272 supported:
273 -GateError: Applies a normal distribution error to the rotation
274 angle. The standard deviation of the noise is specified by
275 the "GateError" key in the dictionary.
277 All parameters are optional and default to 0.0 if not provided.
279 Returns
280 -------
281 float
282 The modified rotation angle after applying the gate error.
283 """
284 if noise_params is not None and noise_params.get("GateError", None) is not None:
285 w += UnitaryGates.rng.normal(
286 0,
287 noise_params["GateError"],
288 (
289 w.shape
290 if isinstance(w, np.ndarray) and UnitaryGates.batch_gate_error
291 else None
292 ),
293 )
294 return w
296 @staticmethod
297 def Rot(phi, theta, omega, wires, noise_params=None):
298 """
299 Applies a rotation gate to the given wires and adds `Noise`.
301 Parameters
302 ----------
303 phi : Union[float, np.ndarray, List[float]]
304 The first rotation angle in radians.
305 theta : Union[float, np.ndarray, List[float]]
306 The second rotation angle in radians.
307 omega : Union[float, np.ndarray, List[float]]
308 The third rotation angle in radians.
309 wires : Union[int, List[int]]
310 The wire(s) to apply the rotation gate to.
311 noise_params : Optional[Dict[str, float]]
312 A dictionary of noise parameters. The following noise gates are
313 supported:
314 -BitFlip: Applies a bit flip error to the given wires.
315 -PhaseFlip: Applies a phase flip error to the given wires.
316 -Depolarizing: Applies a depolarizing channel error to the
317 given wires.
319 All parameters are optional and default to 0.0 if not provided.
320 """
321 if noise_params is not None and "GateError" in noise_params:
322 phi = UnitaryGates.GateError(phi, noise_params)
323 theta = UnitaryGates.GateError(theta, noise_params)
324 omega = UnitaryGates.GateError(omega, noise_params)
325 qml.Rot(phi, theta, omega, wires=wires)
326 UnitaryGates.Noise(wires, noise_params)
328 @staticmethod
329 def RX(w, wires, noise_params=None):
330 """
331 Applies a rotation around the X axis to the given wires and adds `Noise`
333 Parameters
334 ----------
335 w : Union[float, np.ndarray, List[float]]
336 The rotation angle in radians.
337 wires : Union[int, List[int]]
338 The wire(s) to apply the rotation gate to.
339 noise_params : Optional[Dict[str, float]]
340 A dictionary of noise parameters. The following noise gates are
341 supported:
342 -BitFlip: Applies a bit flip error to the given wires.
343 -PhaseFlip: Applies a phase flip error to the given wires.
344 -Depolarizing: Applies a depolarizing channel error to the
345 given wires.
347 All parameters are optional and default to 0.0 if not provided.
348 """
349 w = UnitaryGates.GateError(w, noise_params)
350 qml.RX(w, wires=wires)
351 UnitaryGates.Noise(wires, noise_params)
353 @staticmethod
354 def RY(w, wires, noise_params=None):
355 """
356 Applies a rotation around the Y axis to the given wires and adds `Noise`
358 Parameters
359 ----------
360 w : Union[float, np.ndarray, List[float]]
361 The rotation angle in radians.
362 wires : Union[int, List[int]]
363 The wire(s) to apply the rotation gate to.
364 noise_params : Optional[Dict[str, float]]
365 A dictionary of noise parameters. The following noise gates are
366 supported:
367 -BitFlip: Applies a bit flip error to the given wires.
368 -PhaseFlip: Applies a phase flip error to the given wires.
369 -Depolarizing: Applies a depolarizing channel error to the
370 given wires.
372 All parameters are optional and default to 0.0 if not provided.
373 """
374 w = UnitaryGates.GateError(w, noise_params)
375 qml.RY(w, wires=wires)
376 UnitaryGates.Noise(wires, noise_params)
378 @staticmethod
379 def RZ(w, wires, noise_params=None):
380 """
381 Applies a rotation around the Z axis to the given wires and adds `Noise`
383 Parameters
384 ----------
385 w : Union[float, np.ndarray, List[float]]
386 The rotation angle in radians.
387 wires : Union[int, List[int]]
388 The wire(s) to apply the rotation gate to.
389 noise_params : Optional[Dict[str, float]]
390 A dictionary of noise parameters. The following noise gates are
391 supported:
392 -BitFlip: Applies a bit flip error to the given wires.
393 -PhaseFlip: Applies a phase flip error to the given wires.
394 -Depolarizing: Applies a depolarizing channel error to the
395 given wires.
397 All parameters are optional and default to 0.0 if not provided.
398 """
399 w = UnitaryGates.GateError(w, noise_params)
400 qml.RZ(w, wires=wires)
401 UnitaryGates.Noise(wires, noise_params)
403 @staticmethod
404 def CRX(w, wires, noise_params=None):
405 """
406 Applies a controlled rotation around the X axis to the given wires
407 and adds `Noise`
409 Parameters
410 ----------
411 w : Union[float, np.ndarray, List[float]]
412 The rotation angle in radians.
413 wires : Union[int, List[int]]
414 The wire(s) to apply the controlled rotation gate to.
415 noise_params : Optional[Dict[str, float]]
416 A dictionary of noise parameters. The following noise gates are
417 supported:
418 -BitFlip: Applies a bit flip error to the given wires.
419 -PhaseFlip: Applies a phase flip error to the given wires.
420 -Depolarizing: Applies a depolarizing channel error to the
421 given wires.
423 All parameters are optional and default to 0.0 if not provided.
424 """
425 w = UnitaryGates.GateError(w, noise_params)
426 qml.CRX(w, wires=wires)
427 UnitaryGates.Noise(wires, noise_params)
429 @staticmethod
430 def CRY(w, wires, noise_params=None):
431 """
432 Applies a controlled rotation around the Y axis to the given wires
433 and adds `Noise`
435 Parameters
436 ----------
437 w : Union[float, np.ndarray, List[float]]
438 The rotation angle in radians.
439 wires : Union[int, List[int]]
440 The wire(s) to apply the controlled rotation gate to.
441 noise_params : Optional[Dict[str, float]]
442 A dictionary of noise parameters. The following noise gates are
443 supported:
444 -BitFlip: Applies a bit flip error to the given wires.
445 -PhaseFlip: Applies a phase flip error to the given wires.
446 -Depolarizing: Applies a depolarizing channel error to the
447 given wires.
449 All parameters are optional and default to 0.0 if not provided.
450 """
451 w = UnitaryGates.GateError(w, noise_params)
452 qml.CRY(w, wires=wires)
453 UnitaryGates.Noise(wires, noise_params)
455 @staticmethod
456 def CRZ(w, wires, noise_params=None):
457 """
458 Applies a controlled rotation around the Z axis to the given wires
459 and adds `Noise`
461 Parameters
462 ----------
463 w : Union[float, np.ndarray, List[float]]
464 The rotation angle in radians.
465 wires : Union[int, List[int]]
466 The wire(s) to apply the controlled rotation gate to.
467 noise_params : Optional[Dict[str, float]]
468 A dictionary of noise parameters. The following noise gates are
469 supported:
470 -BitFlip: Applies a bit flip error to the given wires.
471 -PhaseFlip: Applies a phase flip error to the given wires.
472 -Depolarizing: Applies a depolarizing channel error to the
473 given wires.
475 All parameters are optional and default to 0.0 if not provided.
476 """
477 w = UnitaryGates.GateError(w, noise_params)
478 qml.CRZ(w, wires=wires)
479 UnitaryGates.Noise(wires, noise_params)
481 @staticmethod
482 def CX(wires, noise_params=None):
483 """
484 Applies a controlled NOT gate to the given wires and adds `Noise`
486 Parameters
487 ----------
488 wires : Union[int, List[int]]
489 The wire(s) to apply the controlled NOT gate to.
490 noise_params : Optional[Dict[str, float]]
491 A dictionary of noise parameters. The following noise gates are
492 supported:
493 -BitFlip: Applies a bit flip error to the given wires.
494 -PhaseFlip: Applies a phase flip error to the given wires.
495 -Depolarizing: Applies a depolarizing channel error to the
496 given wires.
498 All parameters are optional and default to 0.0 if not provided.
499 """
500 qml.CNOT(wires=wires)
501 UnitaryGates.Noise(wires, noise_params)
503 @staticmethod
504 def CY(wires, noise_params=None):
505 """
506 Applies a controlled Y gate to the given wires and adds `Noise`
508 Parameters
509 ----------
510 wires : Union[int, List[int]]
511 The wire(s) to apply the controlled Y gate to.
512 noise_params : Optional[Dict[str, float]]
513 A dictionary of noise parameters. The following noise gates are
514 supported:
515 -BitFlip: Applies a bit flip error to the given wires.
516 -PhaseFlip: Applies a phase flip error to the given wires.
517 -Depolarizing: Applies a depolarizing channel error to the
518 given wires.
520 All parameters are optional and default to 0.0 if not provided.
521 """
522 qml.CY(wires=wires)
523 UnitaryGates.Noise(wires, noise_params)
525 @staticmethod
526 def CZ(wires, noise_params=None):
527 """
528 Applies a controlled Z gate to the given wires and adds `Noise`
530 Parameters
531 ----------
532 wires : Union[int, List[int]]
533 The wire(s) to apply the controlled Z gate to.
534 noise_params : Optional[Dict[str, float]]
535 A dictionary of noise parameters. The following noise gates are
536 supported:
537 -BitFlip: Applies a bit flip error to the given wires.
538 -PhaseFlip: Applies a phase flip error to the given wires.
539 -Depolarizing: Applies a depolarizing channel error to the
540 given wires.
542 All parameters are optional and default to 0.0 if not provided.
543 """
544 qml.CZ(wires=wires)
545 UnitaryGates.Noise(wires, noise_params)
547 @staticmethod
548 def H(wires, noise_params=None):
549 """
550 Applies a Hadamard gate to the given wires and adds `Noise`
552 Parameters
553 ----------
554 wires : Union[int, List[int]]
555 The wire(s) to apply the Hadamard gate to.
556 noise_params : Optional[Dict[str, float]]
557 A dictionary of noise parameters. The following noise gates are
558 supported:
559 -BitFlip: Applies a bit flip error to the given wires.
560 -PhaseFlip: Applies a phase flip error to the given wires.
561 -Depolarizing: Applies a depolarizing channel error to the
562 given wires.
564 All parameters are optional and default to 0.0 if not provided.
565 """
566 qml.Hadamard(wires=wires)
567 UnitaryGates.Noise(wires, noise_params)
570class PulseInformation:
571 """
572 Stores pulse parameter counts and optimized pulse parameters for quantum gates.
573 """
575 PULSE_PARAM_COUNTS: Dict[str, int] = {"RX": 3, "RY": 3, "RZ": 1, "CZ": 1, "H": 3}
576 PULSE_PARAM_COUNTS["Rot"] = 2 * PULSE_PARAM_COUNTS["RZ"] + PULSE_PARAM_COUNTS["RY"]
577 PULSE_PARAM_COUNTS["CX"] = 2 * PULSE_PARAM_COUNTS["H"] + PULSE_PARAM_COUNTS["CZ"]
578 PULSE_PARAM_COUNTS["CY"] = 2 * PULSE_PARAM_COUNTS["RZ"] + PULSE_PARAM_COUNTS["CX"]
579 PULSE_PARAM_COUNTS["CRZ"] = 2 * PULSE_PARAM_COUNTS["RZ"] + PULSE_PARAM_COUNTS["CZ"]
580 PULSE_PARAM_COUNTS["CRY"] = 2 * PULSE_PARAM_COUNTS["RX"] + PULSE_PARAM_COUNTS["CRZ"]
581 PULSE_PARAM_COUNTS["CRX"] = 2 * PULSE_PARAM_COUNTS["H"] + PULSE_PARAM_COUNTS["CRZ"]
583 OPTIMIZED_PULSES: Dict[str, Optional[jnp.ndarray]] = {
584 "Rot": jnp.array(
585 [0.5, 7.857992399021039, 21.57270102638842, 0.9000668764608991, 0.5]
586 ),
587 "RX": jnp.array([15.70989327341467, 29.5230665326707, 0.7499810441330634]),
588 "RY": jnp.array([7.8787724942614235, 22.001319411513432, 1.098524473819202]),
589 "RZ": jnp.array([0.5]),
590 "CRX": jnp.array(
591 [
592 9.345887537573672,
593 12.785220434787014,
594 0.7109351566377278,
595 0.5,
596 15.102609209445896,
597 0.5,
598 2.9162064326095,
599 0.019005851299126367,
600 10.000000000000078,
601 ]
602 ),
603 "CRY": jnp.array(
604 [
605 19.113133239181412,
606 23.385853735839447,
607 1.2499994641504941,
608 0.5,
609 1.0796514845999126,
610 0.5,
611 12.313295392726795,
612 17.310360723575805,
613 0.8499715424933506,
614 ]
615 ),
616 "CRZ": jnp.array([0.5, 1.7037270017441872, 0.5]),
617 "CX": jnp.array(
618 [
619 7.951920934692106,
620 21.655479574101687,
621 0.8929524493211076,
622 0.9548359253748596,
623 7.94488020182026,
624 21.61729834699293,
625 0.9067943033364354,
626 ]
627 ),
628 "CY": jnp.array(
629 [
630 0.5,
631 13.679990291069169,
632 6.86497650976022,
633 1.0547555119435108,
634 14.96056469588421,
635 13.040583781891456,
636 0.33844677502596704,
637 0.8709563476069772,
638 0.5,
639 ]
640 ),
641 "CZ": jnp.array([0.962596375687258]),
642 "H": jnp.array([7.857992398977854, 21.572701026008765, 0.9000668764548863]),
643 }
645 @staticmethod
646 def num_params(gate: str) -> int:
647 """Return the number of pulse parameters for a given gate."""
648 if gate not in PulseInformation.PULSE_PARAM_COUNTS:
649 raise ValueError(f"Unknown gate '{gate}'")
650 return PulseInformation.PULSE_PARAM_COUNTS[gate]
652 @staticmethod
653 def optimized_params(gate: str) -> Optional[jnp.ndarray]:
654 """Return the optimized pulse parameters for a given gate."""
655 if gate not in PulseInformation.OPTIMIZED_PULSES:
656 raise ValueError(f"Unknown gate '{gate}'")
657 return PulseInformation.OPTIMIZED_PULSES[gate]
660class PulseGates:
661 # NOTE: Implementation of S, RX, RY, RZ, CZ, CNOT/CX and H pulse level
662 # gates closely follow https://doi.org/10.5445/IR/1000184129
663 # TODO: Mention deviations from the above?
664 omega_q = 10 * jnp.pi
665 omega_c = 10 * jnp.pi
667 H_static = jnp.array(
668 [[jnp.exp(1j * omega_q / 2), 0], [0, jnp.exp(-1j * omega_q / 2)]]
669 )
671 Id = jnp.eye(2, dtype=jnp.complex64)
672 X = jnp.array([[0, 1], [1, 0]])
673 Y = jnp.array([[0, -1j], [1j, 0]])
674 Z = jnp.array([[1, 0], [0, -1]])
676 @staticmethod
677 def S(p, t, phi_c):
678 """
679 Generates a shaped pulse envelope modulated by a carrier.
681 The pulse is a Gaussian envelope multiplied by a cosine carrier, commonly
682 used in implementing rotation gates (e.g., RX, RY).
684 Parameters
685 ----------
686 p : sequence of float
687 Pulse parameters `[A, sigma]`:
688 - A : float, amplitude of the Gaussian
689 - sigma : float, width of the Gaussian
690 t : float or sequence of float
691 Time or time interval over which the pulse is applied. If a sequence,
692 `t_c` is taken as the midpoint `(t[0] + t[1]) / 2`.
693 phi_c : float
694 Phase of the carrier cosine.
696 Returns
697 -------
698 jnp.ndarray
699 The shaped pulse at each time step `t`.
700 """
701 A, sigma = p
702 t_c = (t[0] + t[1]) / 2 if isinstance(t, (list, tuple)) else t / 2
704 f = A * jnp.exp(-0.5 * ((t - t_c) / sigma) ** 2)
705 x = jnp.cos(PulseGates.omega_c * t + phi_c)
707 return f * x
709 @staticmethod
710 def Rot(phi, theta, omega, wires, pulse_params=None):
711 """
712 Applies a general single-qubit rotation using a decomposition.
714 Decomposition:
715 Rot(phi, theta, omega) = RZ(phi) · RY(theta) · RZ(omega)
717 Parameters
718 ----------
719 phi : float
720 The first rotation angle.
721 theta : float
722 The second rotation angle.
723 omega : float
724 The third rotation angle.
725 wires : List[int]
726 The wire(s) to apply the rotation to.
727 pulse_params : np.ndarray, optional
728 Pulse parameters for the composing gates. Defaults
729 to optimized parameters if None.
730 """
731 n_RZ = PulseInformation.num_params("RZ")
732 n_RY = PulseInformation.num_params("RY")
734 idx1 = n_RZ
735 idx2 = idx1 + n_RY
736 idx3 = idx2 + n_RZ
738 if pulse_params is None:
739 opt = PulseInformation.optimized_params("Rot")
740 params_RZ_1 = opt[:idx1]
741 params_RY = opt[idx1:idx2]
742 params_RZ_2 = opt[idx2:idx3]
743 else:
744 params_RZ_1 = pulse_params[:idx1]
745 params_RY = pulse_params[idx1:idx2]
746 params_RZ_2 = pulse_params[idx2:idx3]
748 PulseGates.RZ(phi, wires=wires, pulse_params=params_RZ_1)
749 PulseGates.RY(theta, wires=wires, pulse_params=params_RY)
750 PulseGates.RZ(omega, wires=wires, pulse_params=params_RZ_2)
752 @staticmethod
753 def RX(w, wires, pulse_params=None):
754 """
755 Applies a rotation around the X axis pulse to the given wires.
757 Parameters
758 ----------
759 w : float
760 The rotation angle in radians.
761 wires : Union[int, List[int]]
762 The wire(s) to apply the rotation to.
763 pulse_params : np.ndarray, optional
764 Array containing pulse parameters `A`, `sigma` and time `t` for the
765 Gaussian envelope. Defaults to optimized parameters and time.
766 """
767 n_RX = PulseInformation.num_params("RX")
768 idx = n_RX - 1
769 if pulse_params is None:
770 opt = PulseInformation.optimized_params("RX")
771 pulse_params, t = opt[:idx], opt[idx]
772 else:
773 pulse_params, t = pulse_params[:idx], pulse_params[idx]
775 def Sx(p, t):
776 return PulseGates.S(p, t, phi_c=jnp.pi) * w
778 _H = PulseGates.H_static.conj().T @ PulseGates.X @ PulseGates.H_static
779 _H = qml.Hermitian(_H, wires=wires)
780 H_eff = Sx * _H
782 return qml.evolve(H_eff)([pulse_params], t)
784 @staticmethod
785 def RY(w, wires, pulse_params=None):
786 """
787 Applies a rotation around the Y axis pulse to the given wires.
789 Parameters
790 ----------
791 w : float
792 The rotation angle in radians.
793 wires : Union[int, List[int]]
794 The wire(s) to apply the rotation to.
795 pulse_params : np.ndarray, optional
796 Array containing pulse parameters `A`, `sigma` and time `t` for the
797 Gaussian envelope. Defaults to optimized parameters and time.
798 """
799 n_RY = PulseInformation.num_params("RY")
800 idx = n_RY - 1
801 if pulse_params is None:
802 opt = PulseInformation.optimized_params("RY")
803 pulse_params, t = opt[:idx], opt[idx]
804 else:
805 pulse_params, t = pulse_params[:idx], pulse_params[idx]
807 def Sy(p, t):
808 return PulseGates.S(p, t, phi_c=-jnp.pi / 2) * w
810 _H = PulseGates.H_static.conj().T @ PulseGates.Y @ PulseGates.H_static
811 _H = qml.Hermitian(_H, wires=wires)
812 H_eff = Sy * _H
814 return qml.evolve(H_eff)([pulse_params], t)
816 @staticmethod
817 def RZ(w, wires, pulse_params=None):
818 """
819 Applies a rotation around the Z axis to the given wires.
821 Parameters
822 ----------
823 w : float
824 The rotation angle in radians.
825 wires : Union[int, List[int]]
826 The wire(s) to apply the rotation to.
827 pulse_params : float, optional
828 Duration of the pulse. Rotation angle = w * 2 * t.
829 Defaults to 0.5 if None.
830 """
831 idx = PulseInformation.num_params("RZ") - 1
832 if pulse_params is None:
833 t = PulseInformation.optimized_params("RZ")[idx]
834 elif isinstance(pulse_params, (float, int)):
835 t = pulse_params
836 else:
837 t = pulse_params[idx]
839 _H = qml.Hermitian(PulseGates.Z, wires=wires)
841 # TODO: Put comment why p, t has no effect here
842 def Sz(p, t):
843 return w
845 H_eff = Sz * _H
847 return qml.evolve(H_eff)([0], t)
849 @staticmethod
850 def CRX(w, wires, pulse_params=None):
851 """
852 Applies a controlled-RX(w) gate using a decomposition.
854 Decomposition:
855 CRX(w) = H_t · CRZ(w) · H_t
857 Parameters
858 ----------
859 w : float
860 Rotation angle.
861 wires : List[int]
862 The control and target wires.
863 pulse_params : np.ndarray
864 Pulse parameters for the composing gates. Defaults
865 to optimized parameters if None.
866 """
867 n_H = PulseInformation.num_params("H")
868 n_CRZ = PulseInformation.num_params("CRZ")
870 idx1 = n_H
871 idx2 = idx1 + n_CRZ
872 idx3 = idx2 + n_H
874 if pulse_params is None:
875 opt = PulseInformation.optimized_params("CRX")
876 params_H_1 = opt[:idx1]
877 params_CRZ = opt[idx1:idx2]
878 params_H_2 = opt[idx2:idx3]
879 else:
880 params_H_1 = pulse_params[:idx1]
881 params_CRZ = pulse_params[idx1:idx2]
882 params_H_2 = pulse_params[idx2:idx3]
884 target = wires[1]
886 PulseGates.H(wires=target, pulse_params=params_H_1)
887 PulseGates.CRZ(w, wires, pulse_params=params_CRZ)
888 PulseGates.H(wires=target, pulse_params=params_H_2)
890 return
892 @staticmethod
893 def CRY(w, wires, pulse_params=None):
894 """
895 Applies a controlled-RY(w) gate using a decomposition.
897 Decomposition:
898 CRY(w) = RX(-π/2)_t · CRZ(w) · RX(π/2)_t
900 Parameters
901 ----------
902 w : float
903 Rotation angle.
904 wires : List[int]
905 The control and target wires.
906 pulse_params : np.ndarray
907 Pulse parameters for the composing gates. Defaults
908 to optimized parameters if None.
909 """
910 n_RX = PulseInformation.num_params("RX")
911 n_CRZ = PulseInformation.num_params("CRZ")
913 idx1 = n_RX
914 idx2 = idx1 + n_CRZ
915 idx3 = idx2 + n_RX
917 if pulse_params is None:
918 opt = PulseInformation.optimized_params("CRY")
919 params_RX_1 = opt[:idx1]
920 params_CRZ = opt[idx1:idx2]
921 params_RX_2 = opt[idx2:idx3]
922 else:
923 params_RX_1 = pulse_params[:idx1]
924 params_CRZ = pulse_params[idx1:idx2]
925 params_RX_2 = pulse_params[idx2:idx3]
927 target = wires[1]
929 PulseGates.RX(-np.pi / 2, wires=target, pulse_params=params_RX_1)
930 PulseGates.CRZ(w, wires=wires, pulse_params=params_CRZ)
931 PulseGates.RX(np.pi / 2, wires=target, pulse_params=params_RX_2)
933 return
935 @staticmethod
936 def CRZ(w, wires, pulse_params=None):
937 """
938 Applies a controlled-RZ(w) gate using a decomposition.
940 Decomposition:
941 CRZ(w) = RZ(w/2)_t · CZ · RZ(-w/2)_t
943 Parameters
944 ----------
945 w : float
946 Rotation angle.
947 wires : List[int]
948 The control and target wires.
949 pulse_params : np.ndarray
950 Pulse parameters for the composing gates. Defaults
951 to optimized parameters if None.
952 """
953 n_RZ = PulseInformation.num_params("RZ")
954 n_CZ = PulseInformation.num_params("CZ")
956 idx1 = n_RZ
957 idx2 = idx1 + n_CZ
958 idx3 = idx2 + n_RZ
960 if pulse_params is None:
961 opt = PulseInformation.optimized_params("CRZ")
962 params_RZ_1 = opt[:idx1]
963 params_CZ = opt[idx1:idx2]
964 params_RZ_2 = opt[idx2:idx3]
965 else:
966 params_RZ_1 = pulse_params[:idx1]
967 params_CZ = pulse_params[idx1:idx2]
968 params_RZ_2 = pulse_params[idx2:idx3]
970 target = wires[1]
972 PulseGates.RZ(w / 2, wires=target, pulse_params=params_RZ_1)
973 PulseGates.CZ(wires=wires, pulse_params=params_CZ)
974 PulseGates.RZ(-w / 2, wires=target, pulse_params=params_RZ_2)
976 return
978 @staticmethod
979 def CX(wires, pulse_params=None):
980 """
981 Applies a CNOT gate using a decomposition.
983 Decomposition:
984 CNOT = H_t · CZ · H_t
986 Parameters
987 ----------
988 wires : List[int]
989 The control and target wires for the CNOT gate.
990 pulse_params : np.ndarray, optional
991 Pulse parameters for the composing gates. Defaults
992 to optimized parameters if None.
993 """
994 n_H = PulseInformation.num_params("H")
995 n_CZ = PulseInformation.num_params("CZ")
997 idx1 = n_H
998 idx2 = idx1 + n_CZ
999 idx3 = idx2 + n_H
1001 if pulse_params is None:
1002 opt = PulseInformation.optimized_params("CX")
1003 params_H_1 = opt[:idx1]
1004 t_CZ = opt[idx1:idx2]
1005 params_H_2 = opt[idx2:idx3]
1007 else:
1008 params_H_1 = pulse_params[:idx1]
1009 t_CZ = pulse_params[idx1:idx2]
1010 params_H_2 = pulse_params[idx2:idx3]
1012 target = wires[1]
1014 PulseGates.H(wires=target, pulse_params=params_H_1)
1015 PulseGates.CZ(wires=wires, pulse_params=t_CZ)
1016 PulseGates.H(wires=target, pulse_params=params_H_2)
1018 return
1020 @staticmethod
1021 def CY(wires, pulse_params=None):
1022 """
1023 Applies a controlled-Y gate using a decomposition.
1025 Decomposition:
1026 CY = RZ(-π/2)_t · CX · RZ(π/2)_t
1028 Parameters
1029 ----------
1030 wires : List[int]
1031 The control and target wires.
1032 pulse_params : np.ndarray
1033 Pulse parameters for the composing gates. Defaults
1034 to optimized parameters if None.
1035 """
1036 n_RZ = PulseInformation.num_params("RZ")
1037 n_CX = PulseInformation.num_params("CX")
1039 idx1 = n_RZ
1040 idx2 = idx1 + n_CX
1041 idx3 = idx2 + n_RZ
1043 if pulse_params is None:
1044 opt = PulseInformation.optimized_params("CY")
1045 params_RZ_1 = opt[:idx1]
1046 params_CX = opt[idx1:idx2]
1047 params_RZ_2 = opt[idx2:idx3]
1048 else:
1049 params_RZ_1 = pulse_params[:idx1]
1050 params_CX = pulse_params[idx1:idx2]
1051 params_RZ_2 = pulse_params[idx2:idx3]
1053 target = wires[1]
1055 PulseGates.RZ(-np.pi / 2, wires=target, pulse_params=params_RZ_1)
1056 PulseGates.CX(wires=wires, pulse_params=params_CX)
1057 PulseGates.RZ(np.pi / 2, wires=target, pulse_params=params_RZ_2)
1059 return
1061 @staticmethod
1062 def CZ(wires, pulse_params=None):
1063 """
1064 Applies a controlled Z gate to the given wires.
1066 Parameters
1067 ----------
1068 wires : List[int]
1069 The wire(s) to apply the controlled Z gate to.
1070 pulse_params : float, optional
1071 Time or time interval for the evolution.
1072 Defaults to optimized time if None.
1073 """
1074 idx = PulseInformation.num_params("CZ") - 1
1075 if pulse_params is None:
1076 t = PulseInformation.optimized_params("CZ")[idx]
1077 elif isinstance(pulse_params, (float, int)):
1078 t = pulse_params
1079 else:
1080 t = pulse_params[idx]
1082 I_I = jnp.kron(PulseGates.Id, PulseGates.Id)
1083 Z_I = jnp.kron(PulseGates.Z, PulseGates.Id)
1084 I_Z = jnp.kron(PulseGates.Id, PulseGates.Z)
1085 Z_Z = jnp.kron(PulseGates.Z, PulseGates.Z)
1087 # TODO: explain why p, t not in signal
1088 def Scz(p, t):
1089 return jnp.pi
1091 _H = (jnp.pi / 4) * (I_I - Z_I - I_Z + Z_Z)
1092 _H = qml.Hermitian(_H, wires=wires)
1093 H_eff = Scz * _H
1095 return qml.evolve(H_eff)([0], t)
1097 @staticmethod
1098 def H(wires, pulse_params=None):
1099 """
1100 Applies Hadamard gate to the given wires.
1102 Parameters
1103 ----------
1104 wires : Union[int, List[int]]
1105 The wire(s) to apply the Hadamard gate to.
1106 pulse_params : np.ndarray, optional
1107 Pulse parameters for the composing gates. Defaults
1108 to optimized parameters and time.
1109 """
1110 if pulse_params is None:
1111 pulse_params = PulseInformation.optimized_params("H")
1112 else:
1113 pulse_params = pulse_params
1115 # qml.GlobalPhase(-jnp.pi / 2) # this could act as substitute to Sc
1116 # TODO: Explain why p, t not in signal
1117 def Sc(p, t):
1118 return -1.0
1120 _H = jnp.pi / 2 * jnp.eye(2, dtype=jnp.complex64)
1121 _H = qml.Hermitian(_H, wires=wires)
1122 H_corr = Sc * _H
1124 qml.evolve(H_corr)([0], 1)
1126 PulseGates.RZ(jnp.pi, wires=wires)
1127 PulseGates.RY(jnp.pi / 2, wires=wires, pulse_params=pulse_params)
1129 return
1132# Meta class to avoid instantiating the Gates class
1133class GatesMeta(type):
1134 def __getattr__(cls, gate_name):
1135 def handler(*args, **kwargs):
1136 return Gates._inner_getattr(gate_name, *args, **kwargs)
1138 return handler
1141class Gates(metaclass=GatesMeta):
1142 """
1143 Dynamic accessor for quantum gates.
1145 Routes calls like `Gates.RX(...)` to either `UnitaryGates` or `PulseGates`
1146 depending on the `gate_mode` keyword (defaults to 'unitary').
1148 During circuit building, the pulse manager can be activated via
1149 `pulse_manager_context`, which slices the global model pulse parameters
1150 and passes them to each gate. Model pulse parameters act as element-wise
1151 scalers on the gate's optimized pulse parameters.
1153 Parameters
1154 ----------
1155 gate_mode : str, optional
1156 Determines the backend. 'unitary' for UnitaryGates, 'pulse' for PulseGates.
1157 Defaults to 'unitary'.
1159 Examples
1160 --------
1161 >>> Gates.RX(w, wires)
1162 >>> Gates.RX(w, wires, gate_mode="unitary")
1163 >>> Gates.RX(w, wires, gate_mode="pulse")
1164 >>> Gates.RX(w, wires, pulse_params, gate_mode="pulse")
1165 """
1167 def __getattr__(self, gate_name):
1168 def handler(**kwargs):
1169 return self._inner_getattr(gate_name, **kwargs)
1171 return handler
1173 @staticmethod
1174 def _inner_getattr(gate_name, *args, **kwargs):
1175 gate_mode = kwargs.pop("gate_mode", "unitary")
1177 # Backend selection and kwargs filtering
1178 allowed_args = ["w", "wires", "phi", "theta", "omega"]
1179 if gate_mode == "unitary":
1180 gate_backend = UnitaryGates
1181 allowed_args += ["noise_params"]
1182 elif gate_mode == "pulse":
1183 gate_backend = PulseGates
1184 allowed_args += ["pulse_params"]
1185 else:
1186 raise ValueError(
1187 f"Unknown gate mode: {gate_mode}. Use 'unitary' or 'pulse'."
1188 )
1190 kwargs = {k: v for k, v in kwargs.items() if k in allowed_args}
1191 pulse_params = kwargs.get("pulse_params")
1192 pulse_mgr = getattr(Gates, "_pulse_mgr", None)
1194 # Type check on pulse parameters
1195 if pulse_params is not None:
1196 # flatten pulse parameters
1197 if isinstance(pulse_params, (list, tuple)):
1198 flat_params = pulse_params
1200 elif isinstance(pulse_params, jax.core.Tracer):
1201 flat_params = jnp.ravel(pulse_params)
1203 elif isinstance(pulse_params, (np.ndarray, jnp.ndarray)):
1204 flat_params = pulse_params.flatten().tolist()
1206 else:
1207 raise TypeError(f"Unsupported pulse_params type: {type(pulse_params)}")
1209 # checks elements in flat parameters are real numbers or jax Tracer
1210 if not all(
1211 isinstance(x, (numbers.Real, jax.core.Tracer)) for x in flat_params
1212 ):
1213 raise TypeError(
1214 "All elements in pulse_params must be int or float, "
1215 f"got {pulse_params}, type {type(pulse_params)}. "
1216 )
1218 # Len check on pulse parameters
1219 if pulse_params is not None and not isinstance(pulse_mgr, PulseParamManager):
1220 n_params = PulseInformation.num_params(gate_name)
1221 if len(flat_params) != n_params:
1222 raise ValueError(
1223 f"Gate '{gate_name}' expects {n_params} pulse parameters, "
1224 f"got {len(flat_params)}"
1225 )
1227 # Pulse slicing + scaling
1228 if gate_mode == "pulse" and isinstance(pulse_mgr, PulseParamManager):
1229 n_params = PulseInformation.num_params(gate_name)
1230 scalers = pulse_mgr.get(n_params)
1231 base = PulseInformation.optimized_params(gate_name)
1232 kwargs["pulse_params"] = scalers * base # element-wise scaling
1234 # Call the selected gate backend
1235 gate = getattr(gate_backend, gate_name, None)
1236 if gate is None:
1237 raise AttributeError(
1238 f"'{gate_backend.__class__.__name__}' object "
1239 f"has no attribute '{gate_name}'"
1240 )
1242 return gate(*args, **kwargs)
1244 @staticmethod
1245 @contextmanager
1246 def pulse_manager_context(pulse_params: np.ndarray):
1247 """Temporarily set the global pulse manager for circuit building."""
1248 Gates._pulse_mgr = PulseParamManager(pulse_params)
1249 try:
1250 yield
1251 finally:
1252 Gates._pulse_mgr = None
1255class PulseParamManager:
1256 def __init__(self, pulse_params: np.ndarray):
1257 self.pulse_params = pulse_params
1258 self.idx = 0
1260 def get(self, n: int):
1261 """Return the next n parameters and advance the cursor."""
1262 if self.idx + n > len(self.pulse_params):
1263 raise ValueError("Not enough pulse parameters left for this gate")
1264 params = self.pulse_params[self.idx : self.idx + n]
1265 self.idx += n
1266 return params
1269class Ansaetze:
1270 def get_available():
1271 return [
1272 Ansaetze.No_Ansatz,
1273 Ansaetze.Circuit_1,
1274 Ansaetze.Circuit_2,
1275 Ansaetze.Circuit_3,
1276 Ansaetze.Circuit_4,
1277 Ansaetze.Circuit_6,
1278 Ansaetze.Circuit_9,
1279 Ansaetze.Circuit_10,
1280 Ansaetze.Circuit_15,
1281 Ansaetze.Circuit_16,
1282 Ansaetze.Circuit_17,
1283 Ansaetze.Circuit_18,
1284 Ansaetze.Circuit_19,
1285 Ansaetze.No_Entangling,
1286 Ansaetze.Strongly_Entangling,
1287 Ansaetze.Hardware_Efficient,
1288 Ansaetze.GHZ,
1289 ]
1291 class No_Ansatz(Circuit):
1292 @staticmethod
1293 def n_params_per_layer(n_qubits: int) -> int:
1294 return 0
1296 @staticmethod
1297 def n_pulse_params_per_layer(n_qubits: int) -> int:
1298 return 0
1300 @staticmethod
1301 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1302 return None
1304 @staticmethod
1305 def build(w: np.ndarray, n_qubits: int, **kwargs):
1306 pass
1308 class GHZ(Circuit):
1309 @staticmethod
1310 def n_params_per_layer(n_qubits: int) -> int:
1311 return 0
1313 @staticmethod
1314 def n_pulse_params_per_layer(n_qubits: int) -> int:
1315 """
1316 Returns the number of pulse parameters per layer for the GHZ circuit.
1318 Parameters
1319 ----------
1320 n_qubits : int
1321 Number of qubits in the circuit.
1323 Returns
1324 -------
1325 int
1326 Total number of pulse parameters required for one layer of the circuit.
1327 """
1328 n_params = PulseInformation.num_params("H")
1329 n_params += (n_qubits - 1) * PulseInformation.num_params("CX")
1331 return n_params
1333 @staticmethod
1334 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1335 return None
1337 @staticmethod
1338 def build(w: np.ndarray, n_qubits: int, **kwargs):
1339 Gates.H(0, **kwargs)
1341 for q in range(n_qubits - 1):
1342 Gates.CX([q, q + 1], **kwargs)
1344 class Hardware_Efficient(Circuit):
1345 @staticmethod
1346 def n_params_per_layer(n_qubits: int) -> int:
1347 """
1348 Returns the number of parameters per layer for the
1349 Hardware Efficient Ansatz.
1351 The number of parameters is 3 times the number of qubits when there
1352 is more than one qubit, as each qubit contributes 3 parameters.
1353 If the number of qubits is less than 2, a warning is logged since
1354 no entanglement is possible, and a fixed number of 2 parameters is used.
1356 Parameters
1357 ----------
1358 n_qubits : int
1359 Number of qubits in the circuit.
1361 Returns
1362 -------
1363 int
1364 Number of parameters required for one layer of the circuit.
1365 """
1366 if n_qubits < 2:
1367 log.warning("Number of Qubits < 2, no entanglement available")
1368 return n_qubits * 3
1370 @staticmethod
1371 def n_pulse_params_per_layer(n_qubits: int) -> int:
1372 """
1373 Returns the number of pulse parameters per layer for the
1374 Hardware Efficient Ansatz.
1376 This counts all parameters needed if the circuit is used at the
1377 pulse level. It includes contributions from single-qubit rotations
1378 (`RY` and `RZ`) and multi-qubit gates (`CX`) if more than one qubit
1379 is present.
1381 Parameters
1382 ----------
1383 n_qubits : int
1384 Number of qubits in the circuit
1386 Returns
1387 -------
1388 int
1389 Number of pulse parameters required for one layer of the circuit.
1390 """
1391 n_params = 2 * PulseInformation.num_params("RY")
1392 n_params += PulseInformation.num_params("RZ")
1393 n_params *= n_qubits
1395 n_CX = (n_qubits // 2) + ((n_qubits - 1) // 2)
1396 n_CX += 1 if n_qubits > 2 else 0
1397 n_params += n_CX * PulseInformation.num_params("CX")
1399 return n_params
1401 @staticmethod
1402 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1403 """
1404 No controlled rotation gates available. Always None.
1406 Parameters
1407 ----------
1408 n_qubits : int
1409 Number of qubits in the circuit
1411 Returns
1412 -------
1413 Optional[np.ndarray]
1414 List of all controlled indices, or None if the circuit does not
1415 contain controlled rotation gates.
1416 """
1417 return None
1419 @staticmethod
1420 def build(w: np.ndarray, n_qubits: int, **kwargs):
1421 """
1422 Creates a Hardware-Efficient ansatz, as proposed in
1423 https://arxiv.org/pdf/2309.03279
1425 Parameters
1426 ----------
1427 w : np.ndarray
1428 Weight vector of size n_qubits*3
1429 n_qubits : int
1430 Number of qubits
1431 noise_params : Optional[Dict[str, float]], optional
1432 Dictionary of noise parameters to apply to the gates
1433 """
1434 w_idx = 0
1435 for q in range(n_qubits):
1436 Gates.RY(w[w_idx], wires=q, **kwargs)
1437 w_idx += 1
1438 Gates.RZ(w[w_idx], wires=q, **kwargs)
1439 w_idx += 1
1440 Gates.RY(w[w_idx], wires=q, **kwargs)
1441 w_idx += 1
1443 if n_qubits > 1:
1444 for q in range(n_qubits // 2):
1445 Gates.CX(wires=[(2 * q), (2 * q + 1)], **kwargs)
1446 for q in range((n_qubits - 1) // 2):
1447 Gates.CX(wires=[(2 * q + 1), (2 * q + 2)], **kwargs)
1448 if n_qubits > 2:
1449 Gates.CX(wires=[(n_qubits - 1), 0], **kwargs)
1451 class Circuit_19(Circuit):
1452 @staticmethod
1453 def n_params_per_layer(n_qubits: int) -> int:
1454 """
1455 Returns the number of parameters per layer for Circuit_19.
1457 The number of parameters is 3 times the number of qubits when there
1458 is more than one qubit, as each qubit contributes 3 parameters.
1459 If the number of qubits is less than 2, a warning is logged since
1460 no entanglement is possible, and a fixed number of 2 parameters is used.
1462 Parameters
1463 ----------
1464 n_qubits : int
1465 Number of qubits in the circuit
1467 Returns
1468 -------
1469 int
1470 Number of parameters required for one layer of the circuit
1471 """
1472 if n_qubits > 1:
1473 return n_qubits * 3
1474 else:
1475 log.warning("Number of Qubits < 2, no entanglement available")
1476 return 2
1478 @staticmethod
1479 def n_pulse_params_per_layer(n_qubits: int) -> int:
1480 """
1481 Returns the number of pulse parameters per layer for Circuit_19.
1483 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
1484 qubits, and controlled rotations (`CRX`) on each qubit if more than one
1485 qubit is present.
1487 Parameters
1488 ----------
1489 n_qubits : int
1490 Number of qubits in the circuit
1492 Returns
1493 -------
1494 int
1495 Number of pulse parameters required for one layer of the circuit.
1496 """
1497 n_params = PulseInformation.num_params("RX")
1498 n_params += PulseInformation.num_params("RZ")
1499 n_params *= n_qubits
1501 if n_qubits > 1:
1502 n_params += PulseInformation.num_params("CRX") * n_qubits
1504 return n_params
1506 @staticmethod
1507 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1508 """
1509 Returns the indices for the controlled rotation gates for one layer.
1510 Indices should slice the list of all parameters for one layer as follows:
1511 [indices[0]:indices[1]:indices[2]]
1513 Parameters
1514 ----------
1515 n_qubits : int
1516 Number of qubits in the circuit
1518 Returns
1519 -------
1520 Optional[np.ndarray]
1521 List of all controlled indices, or None if the circuit does not
1522 contain controlled rotation gates.
1523 """
1524 if n_qubits > 1:
1525 return [-n_qubits, None, None]
1526 else:
1527 return None
1529 @staticmethod
1530 def build(w: np.ndarray, n_qubits: int, **kwargs):
1531 """
1532 Creates a Circuit19 ansatz.
1534 Length of flattened vector must be n_qubits*3
1535 because for >1 qubits there are three gates
1537 Parameters
1538 ----------
1539 w : np.ndarray
1540 Weight vector of size n_qubits*3
1541 n_qubits : int
1542 Number of qubits
1543 noise_params : Optional[Dict[str, float]], optional
1544 Dictionary of noise parameters to apply to the gates
1545 """
1546 w_idx = 0
1547 for q in range(n_qubits):
1548 Gates.RX(w[w_idx], wires=q, **kwargs)
1549 w_idx += 1
1550 Gates.RZ(w[w_idx], wires=q, **kwargs)
1551 w_idx += 1
1553 if n_qubits > 1:
1554 for q in range(n_qubits):
1555 Gates.CRX(
1556 w[w_idx],
1557 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
1558 **kwargs,
1559 )
1560 w_idx += 1
1562 class Circuit_18(Circuit):
1563 @staticmethod
1564 def n_params_per_layer(n_qubits: int) -> int:
1565 """
1566 Returns the number of parameters per layer for Circuit_18.
1568 The number of parameters is 3 times the number of qubits when there
1569 is more than one qubit, as each qubit contributes 3 parameters.
1570 If the number of qubits is less than 2, a warning is logged since
1571 no entanglement is possible, and a fixed number of 2 parameters is used.
1573 Parameters
1574 ----------
1575 n_qubits : int
1576 Number of qubits in the circuit
1578 Returns
1579 -------
1580 int
1581 Number of parameters required for one layer of the circuit
1582 """
1583 if n_qubits > 1:
1584 return n_qubits * 3
1585 else:
1586 log.warning("Number of Qubits < 2, no entanglement available")
1587 return 2
1589 @staticmethod
1590 def n_pulse_params_per_layer(n_qubits: int) -> int:
1591 """
1592 Returns the number of pulse parameters per layer for Circuit_18.
1594 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
1595 qubits, and controlled rotations (`CRZ`) on each qubit if more than one
1596 qubit is present.
1598 Parameters
1599 ----------
1600 n_qubits : int
1601 Number of qubits in the circuit
1603 Returns
1604 -------
1605 int
1606 Number of pulse parameters required for one layer of the circuit.
1607 """
1608 n_params = PulseInformation.num_params("RX")
1609 n_params += PulseInformation.num_params("RZ")
1610 n_params *= n_qubits
1612 if n_qubits > 1:
1613 n_params += PulseInformation.num_params("CRZ") * n_qubits
1615 return n_params
1617 @staticmethod
1618 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1619 """
1620 Returns the indices for the controlled rotation gates for one layer.
1621 Indices should slice the list of all parameters for one layer as follows:
1622 [indices[0]:indices[1]:indices[2]]
1624 Parameters
1625 ----------
1626 n_qubits : int
1627 Number of qubits in the circuit
1629 Returns
1630 -------
1631 Optional[np.ndarray]
1632 List of all controlled indices, or None if the circuit does not
1633 contain controlled rotation gates.
1634 """
1635 if n_qubits > 1:
1636 return [-n_qubits, None, None]
1637 else:
1638 return None
1640 @staticmethod
1641 def build(w: np.ndarray, n_qubits: int, **kwargs):
1642 """
1643 Creates a Circuit18 ansatz.
1645 Length of flattened vector must be n_qubits*3
1647 Parameters
1648 ----------
1649 w : np.ndarray
1650 Weight vector of size n_qubits*3
1651 n_qubits : int
1652 Number of qubits
1653 noise_params : Optional[Dict[str, float]], optional
1654 Dictionary of noise parameters to apply to the gates
1655 """
1656 w_idx = 0
1657 for q in range(n_qubits):
1658 Gates.RX(w[w_idx], wires=q, **kwargs)
1659 w_idx += 1
1660 Gates.RZ(w[w_idx], wires=q, **kwargs)
1661 w_idx += 1
1663 if n_qubits > 1:
1664 for q in range(n_qubits):
1665 Gates.CRZ(
1666 w[w_idx],
1667 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
1668 **kwargs,
1669 )
1670 w_idx += 1
1672 class Circuit_15(Circuit):
1673 @staticmethod
1674 def n_params_per_layer(n_qubits: int) -> int:
1675 """
1676 Returns the number of parameters per layer for Circuit_15.
1678 The number of parameters is 2 times the number of qubits.
1679 A warning is logged if the number of qubits is less than 2.
1681 Parameters
1682 ----------
1683 n_qubits : int
1684 Number of qubits in the circuit
1686 Returns
1687 -------
1688 int
1689 Number of parameters required for one layer of the circuit
1690 """
1691 if n_qubits > 1:
1692 return n_qubits * 2
1693 else:
1694 log.warning("Number of Qubits < 2, no entanglement available")
1695 return 2
1697 @staticmethod
1698 def n_pulse_params_per_layer(n_qubits: int) -> int:
1699 """
1700 Returns the number of pulse parameters per layer for Circuit_15.
1702 This includes contributions from single-qubit rotations (`RY`) on all
1703 qubits, and controlled rotations (`CX`) on each qubit if more than one
1704 qubit is present.
1706 Parameters
1707 ----------
1708 n_qubits : int
1709 Number of qubits in the circuit
1711 Returns
1712 -------
1713 int
1714 Number of pulse parameters required for one layer of the circuit.
1715 """
1716 n_params = 2 * PulseInformation.num_params("RY")
1717 n_params *= n_qubits
1719 if n_qubits > 1:
1720 n_params += PulseInformation.num_params("CX") * n_qubits
1722 return n_params
1724 @staticmethod
1725 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1726 """
1727 No controlled rotation gates available. Always None.
1729 Parameters
1730 ----------
1731 n_qubits : int
1732 Number of qubits in the circuit
1734 Returns
1735 -------
1736 Optional[np.ndarray]
1737 List of all controlled indices, or None if the circuit does not
1738 contain controlled rotation gates.
1739 """
1740 return None
1742 @staticmethod
1743 def build(w: np.ndarray, n_qubits: int, **kwargs):
1744 """
1745 Creates a Circuit15 ansatz.
1747 Length of flattened vector must be n_qubits*2
1748 because for >1 qubits there are three gates
1750 Parameters
1751 ----------
1752 w : np.ndarray
1753 Weight vector of size n_qubits*2
1754 n_qubits : int
1755 Number of qubits
1756 noise_params : Optional[Dict[str, float]], optional
1757 Dictionary of noise parameters to apply to the gates
1758 """
1759 w_idx = 0
1760 for q in range(n_qubits):
1761 Gates.RY(w[w_idx], wires=q, **kwargs)
1762 w_idx += 1
1764 if n_qubits > 1:
1765 for q in range(n_qubits):
1766 Gates.CX(
1767 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
1768 **kwargs,
1769 )
1771 for q in range(n_qubits):
1772 Gates.RY(w[w_idx], wires=q, **kwargs)
1773 w_idx += 1
1775 if n_qubits > 1:
1776 for q in range(n_qubits):
1777 Gates.CX(
1778 wires=[(q - 1) % n_qubits, (q - 2) % n_qubits],
1779 **kwargs,
1780 )
1782 class Circuit_9(Circuit):
1783 @staticmethod
1784 def n_params_per_layer(n_qubits: int) -> int:
1785 """
1786 Returns the number of parameters per layer for Circuit_9.
1788 The number of parameters is equal to the number of qubits.
1790 Parameters
1791 ----------
1792 n_qubits : int
1793 Number of qubits in the circuit
1795 Returns
1796 -------
1797 int
1798 Number of parameters required for one layer of the circuit
1799 """
1800 return n_qubits
1802 @staticmethod
1803 def n_pulse_params_per_layer(n_qubits: int) -> int:
1804 """
1805 Returns the number of pulse parameters per layer for Circuit_9.
1807 This includes contributions from single-qubit rotations (`H`, `RX`) on all
1808 qubits, and controlled rotations (`CZ`) on each qubit except one if more
1809 than one qubit is present.
1811 Parameters
1812 ----------
1813 n_qubits : int
1814 Number of qubits in the circuit
1816 Returns
1817 -------
1818 int
1819 Number of pulse parameters required for one layer of the circuit.
1820 """
1821 n_params = PulseInformation.num_params("H")
1822 n_params += PulseInformation.num_params("RX")
1823 n_params *= n_qubits
1825 n_params += (n_qubits - 1) * PulseInformation.num_params("CZ")
1827 return n_params
1829 @staticmethod
1830 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1831 """
1832 No controlled rotation gates available. Always None.
1834 Parameters
1835 ----------
1836 n_qubits : int
1837 Number of qubits in the circuit
1839 Returns
1840 -------
1841 Optional[np.ndarray]
1842 List of all controlled indices, or None if the circuit does not
1843 contain controlled rotation gates.
1844 """
1845 return None
1847 @staticmethod
1848 def build(w: np.ndarray, n_qubits: int, **kwargs):
1849 """
1850 Creates a Circuit9 ansatz.
1852 Length of flattened vector must be n_qubits
1854 Parameters
1855 ----------
1856 w : np.ndarray
1857 Weight vector of size n_qubits
1858 n_qubits : int
1859 Number of qubits
1860 noise_params : Optional[Dict[str, float]], optional
1861 Dictionary of noise parameters to apply to the gates
1862 """
1863 w_idx = 0
1864 for q in range(n_qubits):
1865 Gates.H(wires=q, **kwargs)
1867 for q in range(n_qubits - 1):
1868 Gates.CZ(
1869 wires=[n_qubits - q - 2, n_qubits - q - 1],
1870 **kwargs,
1871 )
1873 for q in range(n_qubits):
1874 Gates.RX(w[w_idx], wires=q, **kwargs)
1875 w_idx += 1
1877 class Circuit_6(Circuit):
1878 @staticmethod
1879 def n_params_per_layer(n_qubits: int) -> int:
1880 """
1881 Returns the number of parameters per layer for Circuit_6.
1883 The total number of parameters is n_qubits*3+n_qubits**2, which is
1884 the number of rotations n_qubits*3 plus the number of entangling gates
1885 n_qubits**2.
1887 If n_qubits is 1, the number of parameters is 4, and a warning is logged
1888 since no entanglement is possible.
1890 Parameters
1891 ----------
1892 n_qubits : int
1893 Number of qubits
1895 Returns
1896 -------
1897 int
1898 Number of parameters per layer
1899 """
1900 if n_qubits > 1:
1901 return n_qubits * 3 + n_qubits**2
1902 else:
1903 log.warning("Number of Qubits < 2, no entanglement available")
1904 return 4
1906 @staticmethod
1907 def n_pulse_params_per_layer(n_qubits: int) -> int:
1908 """
1909 Returns the number of pulse parameters per layer for Circuit_6.
1911 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
1912 qubits, and controlled rotations (`CRX`) on each qubit twice except repeats
1913 if more than one qubit is present.
1915 Parameters
1916 ----------
1917 n_qubits : int
1918 Number of qubits in the circuit
1920 Returns
1921 -------
1922 int
1923 Number of pulse parameters required for one layer of the circuit.
1924 """
1925 n_params = 2 * PulseInformation.num_params("RX")
1926 n_params += 2 * PulseInformation.num_params("RZ")
1927 n_params *= n_qubits
1929 n_CRX = n_qubits * (n_qubits - 1)
1930 n_params += n_CRX * PulseInformation.num_params("CRX")
1932 return 0
1934 @staticmethod
1935 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
1936 """
1937 Returns the indices for the controlled rotation gates for one layer.
1938 Indices should slice the list of all parameters for one layer as follows:
1939 [indices[0]:indices[1]:indices[2]]
1941 Parameters
1942 ----------
1943 n_qubits : int
1944 Number of qubits in the circuit
1946 Returns
1947 -------
1948 Optional[np.ndarray]
1949 List of all controlled indices, or None if the circuit does not
1950 contain controlled rotation gates.
1951 """
1952 # TODO: implement
1953 return None
1955 @staticmethod
1956 def build(w: np.ndarray, n_qubits: int, **kwargs):
1957 """
1958 Creates a Circuit6 ansatz.
1960 Length of flattened vector must be
1961 n_qubits*4+n_qubits*(n_qubits-1) =
1962 n_qubits*3+n_qubits**2
1964 Parameters
1965 ----------
1966 w : np.ndarray
1967 Weight vector of size
1968 n_layers*(n_qubits*3+n_qubits**2)
1969 n_qubits : int
1970 Number of qubits
1971 noise_params : Optional[Dict[str, float]], optional
1972 Dictionary of noise parameters to apply to the gates
1973 """
1974 w_idx = 0
1975 for q in range(n_qubits):
1976 Gates.RX(w[w_idx], wires=q, **kwargs)
1977 w_idx += 1
1978 Gates.RZ(w[w_idx], wires=q, **kwargs)
1979 w_idx += 1
1981 if n_qubits > 1:
1982 for ql in range(n_qubits):
1983 for q in range(n_qubits):
1984 if q == ql:
1985 continue
1986 Gates.CRX(
1987 w[w_idx],
1988 wires=[n_qubits - ql - 1, (n_qubits - q - 1) % n_qubits],
1989 **kwargs,
1990 )
1991 w_idx += 1
1993 for q in range(n_qubits):
1994 Gates.RX(w[w_idx], wires=q, **kwargs)
1995 w_idx += 1
1996 Gates.RZ(w[w_idx], wires=q, **kwargs)
1997 w_idx += 1
1999 class Circuit_1(Circuit):
2000 @staticmethod
2001 def n_params_per_layer(n_qubits: int) -> int:
2002 """
2003 Returns the number of parameters per layer for Circuit_1.
2005 The total number of parameters is determined by the number of qubits, with
2006 each qubit contributing 2 parameters.
2008 Parameters
2009 ----------
2010 n_qubits : int
2011 Number of qubits in the circuit
2013 Returns
2014 -------
2015 int
2016 Number of parameters per layer
2017 """
2018 return n_qubits * 2
2020 @staticmethod
2021 def n_pulse_params_per_layer(n_qubits: int) -> int:
2022 """
2023 Returns the number of pulse parameters per layer for Circuit_9.
2025 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
2026 qubits only.
2028 Parameters
2029 ----------
2030 n_qubits : int
2031 Number of qubits in the circuit
2033 Returns
2034 -------
2035 int
2036 Number of pulse parameters required for one layer of the circuit.
2037 """
2038 n_params = PulseInformation.num_params("RX")
2039 n_params += PulseInformation.num_params("RZ")
2040 n_params *= n_qubits
2042 return n_params
2044 @staticmethod
2045 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2046 """
2047 No controlled rotation gates available. Always None.
2049 Parameters
2050 ----------
2051 n_qubits : int
2052 Number of qubits in the circuit
2054 Returns
2055 -------
2056 Optional[np.ndarray]
2057 List of all controlled indices, or None if the circuit does not
2058 contain controlled rotation gates.
2059 """
2060 return None
2062 @staticmethod
2063 def build(w: np.ndarray, n_qubits: int, **kwargs):
2064 """
2065 Creates a Circuit1 ansatz.
2067 Length of flattened vector must be n_qubits*2
2069 Parameters
2070 ----------
2071 w : np.ndarray
2072 Weight vector of size n_qubits*2
2073 n_qubits : int
2074 Number of qubits
2075 noise_params : Optional[Dict[str, float]], optional
2076 Dictionary of noise parameters to apply to the gates
2077 """
2078 w_idx = 0
2079 for q in range(n_qubits):
2080 Gates.RX(w[w_idx], wires=q, **kwargs)
2081 w_idx += 1
2082 Gates.RZ(w[w_idx], wires=q, **kwargs)
2083 w_idx += 1
2085 class Circuit_2(Circuit):
2086 @staticmethod
2087 def n_params_per_layer(n_qubits: int) -> int:
2088 """
2089 Returns the number of parameters per layer for Circuit_2.
2091 The total number of parameters is determined by the number of qubits, with
2092 each qubit contributing 2 parameters.
2094 Parameters
2095 ----------
2096 n_qubits : int
2097 Number of qubits in the circuit
2099 Returns
2100 -------
2101 int
2102 Number of parameters per layer
2103 """
2104 return n_qubits * 2
2106 @staticmethod
2107 def n_pulse_params_per_layer(n_qubits: int) -> int:
2108 """
2109 Returns the number of pulse parameters per layer for Circuit_2.
2111 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
2112 qubits, and controlled rotations (`CX`) on each qubit except one if more
2113 than one qubit is present.
2115 Parameters
2116 ----------
2117 n_qubits : int
2118 Number of qubits in the circuit
2120 Returns
2121 -------
2122 int
2123 Number of pulse parameters required for one layer of the circuit.
2124 """
2125 n_params = PulseInformation.num_params("RX")
2126 n_params += PulseInformation.num_params("RZ")
2127 n_params *= n_qubits
2129 if n_qubits > 1:
2130 n_params += (n_qubits - 1) * PulseInformation.num_params("CX")
2132 return 0
2134 @staticmethod
2135 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2136 """
2137 No controlled rotation gates available. Always None.
2139 Parameters
2140 ----------
2141 n_qubits : int
2142 Number of qubits in the circuit
2144 Returns
2145 -------
2146 Optional[np.ndarray]
2147 List of all controlled indices, or None if the circuit does not
2148 contain controlled rotation gates.
2149 """
2150 return None
2152 @staticmethod
2153 def build(w: np.ndarray, n_qubits: int, **kwargs):
2154 """
2155 Creates a Circuit2 ansatz.
2157 Length of flattened vector must be n_qubits*2
2159 Parameters
2160 ----------
2161 w : np.ndarray
2162 Weight vector of size n_qubits*2
2163 n_qubits : int
2164 Number of qubits
2165 noise_params : Optional[Dict[str, float]], optional
2166 Dictionary of noise parameters to apply to the gates
2167 """
2168 w_idx = 0
2169 for q in range(n_qubits):
2170 Gates.RX(w[w_idx], wires=q, **kwargs)
2171 w_idx += 1
2172 Gates.RZ(w[w_idx], wires=q, **kwargs)
2173 w_idx += 1
2175 for q in range(n_qubits - 1):
2176 Gates.CX(
2177 wires=[n_qubits - q - 1, n_qubits - q - 2],
2178 **kwargs,
2179 )
2181 class Circuit_3(Circuit):
2182 @staticmethod
2183 def n_params_per_layer(n_qubits: int) -> int:
2184 """
2185 Calculates the number of parameters per layer for Circuit3.
2187 The number of parameters per layer is given by the number of qubits, with
2188 each qubit contributing 3 parameters. The last qubit only contributes 2
2189 parameters because it is the target qubit for the controlled gates.
2191 Parameters
2192 ----------
2193 n_qubits : int
2194 Number of qubits in the circuit
2196 Returns
2197 -------
2198 int
2199 Number of parameters per layer
2200 """
2201 return n_qubits * 3 - 1
2203 @staticmethod
2204 def n_pulse_params_per_layer(n_qubits: int) -> int:
2205 """
2206 Returns the number of pulse parameters per layer for Circuit_3.
2208 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
2209 qubits, and controlled rotations (`CRZ`) on each qubit except one if more
2210 than one qubit is present.
2212 Parameters
2213 ----------
2214 n_qubits : int
2215 Number of qubits in the circuit
2217 Returns
2218 -------
2219 int
2220 Number of pulse parameters required for one layer of the circuit.
2221 """
2222 n_params = PulseInformation.num_params("RX")
2223 n_params = PulseInformation.num_params("RZ")
2224 n_params *= n_qubits
2226 n_params += (n_qubits - 1) * PulseInformation.num_params("CRZ")
2228 return n_params
2230 @staticmethod
2231 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2232 """
2233 No controlled rotation gates available. Always None.
2235 Parameters
2236 ----------
2237 n_qubits : int
2238 Number of qubits in the circuit
2240 Returns
2241 -------
2242 Optional[np.ndarray]
2243 List of all controlled indices, or None if the circuit does not
2244 contain controlled rotation gates.
2245 """
2246 if n_qubits > 1:
2247 return [-(n_qubits - 1), None, None]
2248 else:
2249 return None
2251 @staticmethod
2252 def build(w: np.ndarray, n_qubits: int, **kwargs):
2253 """
2254 Creates a Circuit3 ansatz.
2256 Length of flattened vector must be n_qubits*3-1
2258 Parameters
2259 ----------
2260 w : np.ndarray
2261 Weight vector of size n_qubits*3-1
2262 n_qubits : int
2263 Number of qubits
2264 noise_params : Optional[Dict[str, float]], optional
2265 Dictionary of noise parameters to apply to the gates
2266 """
2267 w_idx = 0
2268 for q in range(n_qubits):
2269 Gates.RX(w[w_idx], wires=q, **kwargs)
2270 w_idx += 1
2271 Gates.RZ(w[w_idx], wires=q, **kwargs)
2272 w_idx += 1
2274 for q in range(n_qubits - 1):
2275 Gates.CRZ(
2276 w[w_idx],
2277 wires=[n_qubits - q - 1, n_qubits - q - 2],
2278 **kwargs,
2279 )
2280 w_idx += 1
2282 class Circuit_4(Circuit):
2283 @staticmethod
2284 def n_params_per_layer(n_qubits: int) -> int:
2285 """
2286 Returns the number of parameters per layer for the Circuit_4 ansatz.
2288 The number of parameters is calculated as n_qubits*3-1.
2290 Parameters
2291 ----------
2292 n_qubits : int
2293 Number of qubits in the circuit
2295 Returns
2296 -------
2297 int
2298 Number of parameters per layer
2299 """
2300 return n_qubits * 3 - 1
2302 @staticmethod
2303 def n_pulse_params_per_layer(n_qubits: int) -> int:
2304 """
2305 Returns the number of pulse parameters per layer for Circuit_4.
2307 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
2308 qubits, and controlled rotations (`CRX`) on each qubit except one if more
2309 than one qubit is present.
2311 Parameters
2312 ----------
2313 n_qubits : int
2314 Number of qubits in the circuit
2316 Returns
2317 -------
2318 int
2319 Number of pulse parameters required for one layer of the circuit.
2320 """
2321 n_params = PulseInformation.num_params("RX")
2322 n_params += PulseInformation.num_params("RZ")
2323 n_params *= n_qubits
2325 n_params += (n_qubits - 1) * PulseInformation.num_params("CRX")
2327 return 0
2329 @staticmethod
2330 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2331 """
2332 No controlled rotation gates available. Always None.
2334 Parameters
2335 ----------
2336 n_qubits : int
2337 Number of qubits in the circuit
2339 Returns
2340 -------
2341 Optional[np.ndarray]
2342 List of all controlled indices, or None if the circuit does not
2343 contain controlled rotation gates.
2344 """
2345 if n_qubits > 1:
2346 return [-(n_qubits - 1), None, None]
2347 else:
2348 return None
2350 @staticmethod
2351 def build(w: np.ndarray, n_qubits: int, **kwargs):
2352 """
2353 Creates a Circuit4 ansatz.
2355 Length of flattened vector must be n_qubits*3-1
2357 Parameters
2358 ----------
2359 w : np.ndarray
2360 Weight vector of size n_qubits*3-1
2361 n_qubits : int
2362 Number of qubits
2363 noise_params : Optional[Dict[str, float]], optional
2364 Dictionary of noise parameters to apply to the gates
2365 """
2366 w_idx = 0
2367 for q in range(n_qubits):
2368 Gates.RX(w[w_idx], wires=q, **kwargs)
2369 w_idx += 1
2370 Gates.RZ(w[w_idx], wires=q, **kwargs)
2371 w_idx += 1
2373 for q in range(n_qubits - 1):
2374 Gates.CRX(
2375 w[w_idx],
2376 wires=[n_qubits - q - 1, n_qubits - q - 2],
2377 **kwargs,
2378 )
2379 w_idx += 1
2381 class Circuit_10(Circuit):
2382 @staticmethod
2383 def n_params_per_layer(n_qubits: int) -> int:
2384 """
2385 Returns the number of parameters per layer for the Circuit_10 ansatz.
2387 The number of parameters is calculated as n_qubits*2.
2389 Parameters
2390 ----------
2391 n_qubits : int
2392 Number of qubits in the circuit
2394 Returns
2395 -------
2396 int
2397 Number of parameters per layer
2398 """
2399 return n_qubits * 2 # constant gates not considered yet. has to be fixed
2401 @staticmethod
2402 def n_pulse_params_per_layer(n_qubits: int) -> int:
2403 """
2404 Returns the number of pulse parameters per layer for Circuit_10.
2406 This includes contributions from single-qubit rotations (`RY`) on all
2407 qubits, controlled rotations (`CZ`) on each qubit except one if more
2408 than one qubit is present and a final controlled rotation (`CZ`) if
2409 more than two qubits are present.
2411 Parameters
2412 ----------
2413 n_qubits : int
2414 Number of qubits in the circuit.
2416 Returns
2417 -------
2418 int
2419 Number of pulse parameters required for one layer of the circuit.
2420 """
2421 n_params = 2 * PulseInformation.num_params("RY")
2422 n_params *= n_qubits
2424 n_params += (n_qubits - 1) * PulseInformation.num_params("CZ")
2426 n_params += PulseInformation.num_params("CZ") if n_qubits > 2 else 0
2428 return n_params
2430 @staticmethod
2431 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2432 """
2433 No controlled rotation gates available. Always None.
2435 Parameters
2436 ----------
2437 n_qubits : int
2438 Number of qubits in the circuit.
2440 Returns
2441 -------
2442 Optional[np.ndarray]
2443 List of all controlled indices, or None if the circuit does not
2444 contain controlled rotation gates.
2445 """
2446 return None
2448 @staticmethod
2449 def build(w: np.ndarray, n_qubits: int, **kwargs):
2450 """
2451 Creates a Circuit10 ansatz.
2453 Length of flattened vector must be n_qubits*2
2455 Parameters
2456 ----------
2457 w : np.ndarray
2458 Weight vector of size n_qubits*2
2459 n_qubits : int
2460 Number of qubits
2461 noise_params : Optional[Dict[str, float]], optional
2462 Dictionary of noise parameters to apply to the gates
2463 """
2464 w_idx = 0
2465 # constant gates, independent of layers. has to be fixed
2466 for q in range(n_qubits):
2467 Gates.RY(w[w_idx], wires=q, **kwargs)
2468 w_idx += 1
2470 for q in range(n_qubits - 1):
2471 Gates.CZ(
2472 wires=[
2473 (n_qubits - q - 2) % n_qubits,
2474 (n_qubits - q - 1) % n_qubits,
2475 ],
2476 **kwargs,
2477 )
2478 if n_qubits > 2:
2479 Gates.CZ(wires=[n_qubits - 1, 0], **kwargs)
2481 for q in range(n_qubits):
2482 Gates.RY(w[w_idx], wires=q, **kwargs)
2483 w_idx += 1
2485 class Circuit_16(Circuit):
2486 @staticmethod
2487 def n_params_per_layer(n_qubits: int) -> int:
2488 """
2489 Returns the number of parameters per layer for the Circuit_16 ansatz.
2491 The number of parameters is calculated as n_qubits*3-1.
2493 Parameters
2494 ----------
2495 n_qubits : int
2496 Number of qubits in the circuit
2498 Returns
2499 -------
2500 int
2501 Number of parameters per layer
2502 """
2503 return n_qubits * 3 - 1
2505 @staticmethod
2506 def n_pulse_params_per_layer(n_qubits: int) -> int:
2507 """
2508 Returns the number of pulse parameters per layer for Circuit_16.
2510 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
2511 qubits, and controlled rotations (`CRZ`) if more than one qubit is present.
2513 Parameters
2514 ----------
2515 n_qubits : int
2516 Number of qubits in the circuit.
2518 Returns
2519 -------
2520 int
2521 Number of pulse parameters required for one layer of the circuit.
2522 """
2523 n_params = PulseInformation.num_params("RX")
2524 n_params += PulseInformation.num_params("RZ")
2525 n_params *= n_qubits
2527 n_CRZ = n_qubits * (n_qubits - 1) // 2
2528 n_params += n_CRZ * PulseInformation.num_params("CRZ")
2530 return n_params
2532 @staticmethod
2533 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2534 """
2535 No controlled rotation gates available. Always None.
2537 Parameters
2538 ----------
2539 n_qubits : int
2540 Number of qubits in the circuit
2542 Returns
2543 -------
2544 Optional[np.ndarray]
2545 List of all controlled indices, or None if the circuit does not
2546 contain controlled rotation gates.
2547 """
2548 if n_qubits > 1:
2549 return [-(n_qubits - 1), None, None]
2550 else:
2551 return None
2553 @staticmethod
2554 def build(w: np.ndarray, n_qubits: int, **kwargs):
2555 """
2556 Creates a Circuit16 ansatz.
2558 Length of flattened vector must be n_qubits*3-1
2560 Parameters
2561 ----------
2562 w : np.ndarray
2563 Weight vector of size n_qubits*3-1
2564 n_qubits : int
2565 Number of qubits
2566 noise_params : Optional[Dict[str, float]], optional
2567 Dictionary of noise parameters to apply to the gates
2568 """
2569 w_idx = 0
2570 for q in range(n_qubits):
2571 Gates.RX(w[w_idx], wires=q, **kwargs)
2572 w_idx += 1
2573 Gates.RZ(w[w_idx], wires=q, **kwargs)
2574 w_idx += 1
2576 if n_qubits > 1:
2577 for q in range(n_qubits // 2):
2578 Gates.CRZ(
2579 w[w_idx],
2580 wires=[(2 * q + 1), (2 * q)],
2581 **kwargs,
2582 )
2583 w_idx += 1
2585 for q in range((n_qubits - 1) // 2):
2586 Gates.CRZ(
2587 w[w_idx],
2588 wires=[(2 * q + 2), (2 * q + 1)],
2589 **kwargs,
2590 )
2591 w_idx += 1
2593 class Circuit_17(Circuit):
2594 @staticmethod
2595 def n_params_per_layer(n_qubits: int) -> int:
2596 """
2597 Returns the number of parameters per layer for the Circuit_17 ansatz.
2599 The number of parameters is calculated as n_qubits*3-1.
2601 Parameters
2602 ----------
2603 n_qubits : int
2604 Number of qubits in the circuit
2606 Returns
2607 -------
2608 int
2609 Number of parameters per layer
2610 """
2611 return n_qubits * 3 - 1
2613 @staticmethod
2614 def n_pulse_params_per_layer(n_qubits: int) -> int:
2615 """
2616 Returns the number of pulse parameters per layer for Circuit_17.
2618 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all
2619 qubits, and controlled rotations (`CRX`) if more than one qubit is present.
2621 Parameters
2622 ----------
2623 n_qubits : int
2624 Number of qubits in the circuit.
2626 Returns
2627 -------
2628 int
2629 Number of pulse parameters required for one layer of the circuit.
2630 """
2631 n_params = PulseInformation.num_params("RX")
2632 n_params += PulseInformation.num_params("RZ")
2633 n_params *= n_qubits
2635 n_CRZ = n_qubits * (n_qubits - 1) // 2
2636 n_params += n_CRZ * PulseInformation.num_params("CRX")
2638 return n_params
2640 @staticmethod
2641 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2642 """
2643 No controlled rotation gates available. Always None.
2645 Parameters
2646 ----------
2647 n_qubits : int
2648 Number of qubits in the circuit
2650 Returns
2651 -------
2652 Optional[np.ndarray]
2653 List of all controlled indices, or None if the circuit does not
2654 contain controlled rotation gates.
2655 """
2656 if n_qubits > 1:
2657 return [-(n_qubits - 1), None, None]
2658 else:
2659 return None
2661 @staticmethod
2662 def build(w: np.ndarray, n_qubits: int, **kwargs):
2663 """
2664 Creates a Circuit17 ansatz.
2666 Length of flattened vector must be n_qubits*3-1
2668 Parameters
2669 ----------
2670 w : np.ndarray
2671 Weight vector of size n_qubits*3-1
2672 n_qubits : int
2673 Number of qubits
2674 noise_params : Optional[Dict[str, float]], optional
2675 Dictionary of noise parameters to apply to the gates
2676 """
2677 w_idx = 0
2678 for q in range(n_qubits):
2679 Gates.RX(w[w_idx], wires=q, **kwargs)
2680 w_idx += 1
2681 Gates.RZ(w[w_idx], wires=q, **kwargs)
2682 w_idx += 1
2684 if n_qubits > 1:
2685 for q in range(n_qubits // 2):
2686 Gates.CRX(
2687 w[w_idx],
2688 wires=[(2 * q + 1), (2 * q)],
2689 **kwargs,
2690 )
2691 w_idx += 1
2693 for q in range((n_qubits - 1) // 2):
2694 Gates.CRX(
2695 w[w_idx],
2696 wires=[(2 * q + 2), (2 * q + 1)],
2697 **kwargs,
2698 )
2699 w_idx += 1
2701 class Strongly_Entangling(Circuit):
2702 @staticmethod
2703 def n_params_per_layer(n_qubits: int) -> int:
2704 """
2705 Returns the number of parameters per layer for the
2706 Strongly Entangling ansatz.
2708 The number of parameters is calculated as n_qubits*6.
2710 Parameters
2711 ----------
2712 n_qubits : int
2713 Number of qubits in the circuit
2715 Returns
2716 -------
2717 int
2718 Number of parameters per layer
2719 """
2720 if n_qubits < 2:
2721 log.warning("Number of Qubits < 2, no entanglement available")
2722 return n_qubits * 6
2724 @staticmethod
2725 def n_pulse_params_per_layer(n_qubits: int) -> int:
2726 """
2727 Returns the number of pulse parameters per layer for Strongly_Entangling
2728 circuit.
2730 This includes contributions from single-qubit rotations (`Rot`) on all
2731 qubits, and controlled rotations (`CX`) if more than one qubit is present.
2733 Parameters
2734 ----------
2735 n_qubits : int
2736 Number of qubits in the circuit.
2738 Returns
2739 -------
2740 int
2741 Number of pulse parameters required for one layer of the circuit.
2742 """
2743 n_params = 2 * PulseInformation.num_params("Rot")
2744 n_params *= n_qubits
2746 if n_qubits > 1:
2747 n_params += n_qubits * 2 * PulseInformation.num_params("CX")
2749 return n_params
2751 @staticmethod
2752 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2753 """
2754 No controlled rotation gates available. Always None.
2756 Parameters
2757 ----------
2758 n_qubits : int
2759 Number of qubits in the circuit
2761 Returns
2762 -------
2763 Optional[np.ndarray]
2764 List of all controlled indices, or None if the circuit does not
2765 contain controlled rotation gates.
2766 """
2767 return None
2769 @staticmethod
2770 def build(w: np.ndarray, n_qubits: int, **kwargs) -> None:
2771 """
2772 Creates a Strongly Entangling ansatz.
2774 Length of flattened vector must be n_qubits*6
2776 Parameters
2777 ----------
2778 w : np.ndarray
2779 Weight vector of size n_qubits*6
2780 n_qubits : int
2781 Number of qubits
2782 noise_params : Optional[Dict[str, float]], optional
2783 Dictionary of noise parameters to apply to the gates
2784 """
2785 w_idx = 0
2786 for q in range(n_qubits):
2787 Gates.Rot(
2788 w[w_idx],
2789 w[w_idx + 1],
2790 w[w_idx + 2],
2791 wires=q,
2792 **kwargs,
2793 )
2794 w_idx += 3
2796 if n_qubits > 1:
2797 for q in range(n_qubits):
2798 Gates.CX(wires=[q, (q + 1) % n_qubits], **kwargs)
2800 for q in range(n_qubits):
2801 Gates.Rot(
2802 w[w_idx],
2803 w[w_idx + 1],
2804 w[w_idx + 2],
2805 wires=q,
2806 **kwargs,
2807 )
2808 w_idx += 3
2810 if n_qubits > 1:
2811 for q in range(n_qubits):
2812 Gates.CX(
2813 wires=[q, (q + n_qubits // 2) % n_qubits],
2814 **kwargs,
2815 )
2817 class No_Entangling(Circuit):
2818 @staticmethod
2819 def n_params_per_layer(n_qubits: int) -> int:
2820 """
2821 Returns the number of parameters per layer for the NoEntangling ansatz.
2823 The number of parameters is calculated as n_qubits*3.
2825 Parameters
2826 ----------
2827 n_qubits : int
2828 Number of qubits in the circuit
2830 Returns
2831 -------
2832 int
2833 Number of parameters per layer
2834 """
2835 return n_qubits * 3
2837 @staticmethod
2838 def n_pulse_params_per_layer(n_qubits: int) -> int:
2839 """
2840 Returns the number of pulse parameters per layer for No_Entangling circuit.
2842 This includes contributions from single-qubit rotations (`Rot`) on all
2843 qubits only.
2845 Parameters
2846 ----------
2847 n_qubits : int
2848 Number of qubits in the circuit.
2850 Returns
2851 -------
2852 int
2853 Number of pulse parameters required for one layer of the circuit.
2854 """
2855 n_params = PulseInformation.num_params("Rot")
2856 n_params *= n_qubits
2858 return n_params
2860 @staticmethod
2861 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
2862 """
2863 No controlled rotation gates available. Always None.
2865 Parameters
2866 ----------
2867 n_qubits : int
2868 Number of qubits in the circuit
2870 Returns
2871 -------
2872 Optional[np.ndarray]
2873 List of all controlled indices, or None if the circuit does not
2874 contain controlled rotation gates.
2875 """
2876 return None
2878 @staticmethod
2879 def build(w: np.ndarray, n_qubits: int, **kwargs):
2880 """
2881 Creates a circuit without entangling, but with U3 gates on all qubits
2883 Length of flattened vector must be n_qubits*3
2885 Parameters
2886 ----------
2887 w : np.ndarray
2888 Weight vector of size n_qubits*3
2889 n_qubits : int
2890 Number of qubits
2891 noise_params : Optional[Dict[str, float]], optional
2892 Dictionary of noise parameters to apply to the gates
2893 """
2894 w_idx = 0
2895 for q in range(n_qubits):
2896 Gates.Rot(
2897 w[w_idx],
2898 w[w_idx + 1],
2899 w[w_idx + 2],
2900 wires=q,
2901 **kwargs,
2902 )
2903 w_idx += 3