Coverage for qml_essentials/ansaetze.py: 90%
403 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-23 11:23 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-23 11:23 +0000
1from abc import ABC, abstractmethod
2from typing import Any, Optional
3import pennylane.numpy as np
4import pennylane as qml
6from typing import List
8import logging
10log = logging.getLogger(__name__)
13class Circuit(ABC):
14 def __init__(self):
15 pass
17 @abstractmethod
18 def n_params_per_layer(n_qubits: int) -> int:
19 return
21 @abstractmethod
22 def get_control_indices(self, n_qubits: int) -> List[int]:
23 """
24 Returns the indices for the controlled rotation gates for one layer.
25 Indices should slice the list of all parameters for one layer as follows:
26 [indices[0]:indices[1]:indices[2]]
28 Parameters
29 ----------
30 n_qubits : int
31 Number of qubits in the circuit
33 Returns
34 -------
35 Optional[np.ndarray]
36 List of all controlled indices, or None if the circuit does not
37 contain controlled rotation gates.
38 """
39 return
41 def get_control_angles(self, w: np.ndarray, n_qubits: int) -> Optional[np.ndarray]:
42 """
43 Returns the angles for the controlled rotation gates from the list of
44 all parameters for one layer.
46 Parameters
47 ----------
48 w : np.ndarray
49 List of parameters for one layer
50 n_qubits : int
51 Number of qubits in the circuit
53 Returns
54 -------
55 Optional[np.ndarray]
56 List of all controlled parameters, or None if the circuit does not
57 contain controlled rotation gates.
58 """
59 indices = self.get_control_indices(n_qubits)
60 if indices is None:
61 return None
63 return w[indices[0] : indices[1] : indices[2]]
65 @abstractmethod
66 def build(self, n_qubits: int, n_layers: int):
67 return
69 def __call__(self, *args: Any, **kwds: Any) -> Any:
70 self.build(*args, **kwds)
73class Gates:
74 def noise_gate(wires, noise_params=None):
75 if noise_params is not None:
76 if isinstance(wires, int):
77 wires = [wires] # single qubit gate
78 # iterate for multi qubit gates
79 for wire in wires:
80 qml.BitFlip(noise_params.get("BitFlip", 0.0), wires=wire)
81 qml.PhaseFlip(noise_params.get("PhaseFlip", 0.0), wires=wire)
82 qml.DepolarizingChannel(
83 noise_params.get("DepolarizingChannel", 0.0), wires=wire
84 )
86 def Rot(phi, theta, omega, wires, noise_params=None):
87 qml.Rot(phi, theta, omega, wires=wires)
88 Gates.noise_gate(wires, noise_params)
90 def RX(w, wires, noise_params=None):
91 qml.RX(w, wires=wires)
92 Gates.noise_gate(wires, noise_params)
94 def RY(w, wires, noise_params=None):
95 qml.RY(w, wires=wires)
96 Gates.noise_gate(wires, noise_params)
98 def RZ(w, wires, noise_params=None):
99 qml.RZ(w, wires=wires)
100 Gates.noise_gate(wires, noise_params)
102 def CRX(w, wires, noise_params=None):
103 qml.CRX(w, wires=wires)
104 Gates.noise_gate(wires, noise_params)
106 def CRY(w, wires, noise_params=None):
107 qml.CRY(w, wires=wires)
108 Gates.noise_gate(wires, noise_params)
110 def CRZ(w, wires, noise_params=None):
111 qml.CRZ(w, wires=wires)
112 Gates.noise_gate(wires, noise_params)
114 def CX(wires, noise_params=None):
115 qml.CNOT(wires=wires)
116 Gates.noise_gate(wires, noise_params)
118 def CY(wires, noise_params=None):
119 qml.CY(wires=wires)
120 Gates.noise_gate(wires, noise_params)
122 def CZ(wires, noise_params=None):
123 qml.CZ(wires=wires)
124 Gates.noise_gate(wires, noise_params)
126 def H(wires, noise_params=None):
127 qml.Hadamard(wires=wires)
128 Gates.noise_gate(wires, noise_params)
131class Ansaetze:
133 def get_available():
134 return [
135 Ansaetze.No_Ansatz,
136 Ansaetze.Circuit_1,
137 Ansaetze.Circuit_2,
138 Ansaetze.Circuit_3,
139 Ansaetze.Circuit_4,
140 Ansaetze.Circuit_6,
141 Ansaetze.Circuit_9,
142 Ansaetze.Circuit_10,
143 Ansaetze.Circuit_15,
144 Ansaetze.Circuit_16,
145 Ansaetze.Circuit_17,
146 Ansaetze.Circuit_18,
147 Ansaetze.Circuit_19,
148 Ansaetze.No_Entangling,
149 Ansaetze.Strongly_Entangling,
150 Ansaetze.Hardware_Efficient,
151 ]
153 class No_Ansatz(Circuit):
154 @staticmethod
155 def n_params_per_layer(n_qubits: int) -> int:
156 return 0
158 @staticmethod
159 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
160 return None
162 @staticmethod
163 def build(w: np.ndarray, n_qubits: int, noise_params=None):
164 pass
166 class Hardware_Efficient(Circuit):
167 @staticmethod
168 def n_params_per_layer(n_qubits: int) -> int:
169 if n_qubits < 2:
170 log.warning("Number of Qubits < 2, no entanglement available")
171 return n_qubits * 3
173 @staticmethod
174 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
175 return None
177 @staticmethod
178 def build(w: np.ndarray, n_qubits: int, noise_params=None):
179 """
180 Creates a Hardware-Efficient ansatz, as proposed in
181 https://arxiv.org/pdf/2309.03279
183 Length of flattened vector must be n_qubits*3
185 Args:
186 w (np.ndarray): weight vector of size n_layers*(n_qubits*3)
187 n_qubits (int): number of qubits
188 """
189 w_idx = 0
190 for q in range(n_qubits):
191 Gates.RY(w[w_idx], wires=q, noise_params=noise_params)
192 w_idx += 1
193 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
194 w_idx += 1
195 Gates.RY(w[w_idx], wires=q, noise_params=noise_params)
196 w_idx += 1
198 if n_qubits > 1:
199 for q in range(n_qubits // 2):
200 Gates.CX(wires=[(2 * q), (2 * q + 1)], noise_params=noise_params)
201 for q in range((n_qubits - 1) // 2):
202 Gates.CX(
203 wires=[(2 * q + 1), (2 * q + 2)], noise_params=noise_params
204 )
205 if n_qubits > 2:
206 Gates.CX(wires=[(n_qubits - 1), 0], noise_params=noise_params)
208 class Circuit_19(Circuit):
209 @staticmethod
210 def n_params_per_layer(n_qubits: int) -> int:
211 if n_qubits > 1:
212 return n_qubits * 3
213 else:
214 log.warning("Number of Qubits < 2, no entanglement available")
215 return 2
217 @staticmethod
218 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
219 if n_qubits > 1:
220 return [-n_qubits, None, None]
221 else:
222 return None
224 @staticmethod
225 def build(w: np.ndarray, n_qubits: int, noise_params=None):
226 """
227 Creates a Circuit19 ansatz.
229 Length of flattened vector must be n_qubits*3-1
230 because for >1 qubits there are three gates
232 Args:
233 w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
234 n_qubits (int): number of qubits
235 """
236 w_idx = 0
237 for q in range(n_qubits):
238 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
239 w_idx += 1
240 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
241 w_idx += 1
243 if n_qubits > 1:
244 for q in range(n_qubits):
245 Gates.CRX(
246 w[w_idx],
247 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
248 noise_params=noise_params,
249 )
250 w_idx += 1
252 class Circuit_18(Circuit):
253 @staticmethod
254 def n_params_per_layer(n_qubits: int) -> int:
255 if n_qubits > 1:
256 return n_qubits * 3
257 else:
258 log.warning("Number of Qubits < 2, no entanglement available")
259 return 2
261 @staticmethod
262 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
263 if n_qubits > 1:
264 return [-n_qubits, None, None]
265 else:
266 return None
268 @staticmethod
269 def build(w: np.ndarray, n_qubits: int, noise_params=None):
270 """
271 Creates a Circuit18 ansatz.
273 Length of flattened vector must be n_qubits*3
275 Args:
276 w (np.ndarray): weight vector of size n_layers*(n_qubits*3)
277 n_qubits (int): number of qubits
278 """
279 w_idx = 0
280 for q in range(n_qubits):
281 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
282 w_idx += 1
283 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
284 w_idx += 1
286 if n_qubits > 1:
287 for q in range(n_qubits):
288 Gates.CRZ(
289 w[w_idx],
290 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
291 noise_params=noise_params,
292 )
293 w_idx += 1
295 class Circuit_15(Circuit):
296 @staticmethod
297 def n_params_per_layer(n_qubits: int) -> int:
298 if n_qubits > 1:
299 return n_qubits * 2
300 else:
301 log.warning("Number of Qubits < 2, no entanglement available")
302 return 2
304 @staticmethod
305 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
306 return None
308 @staticmethod
309 def build(w: np.ndarray, n_qubits: int, noise_params=None):
310 """
311 Creates a Circuit15 ansatz.
313 Length of flattened vector must be n_qubits*2
314 because for >1 qubits there are three gates
316 Args:
317 w (np.ndarray): weight vector of size n_layers*(n_qubits*2)
318 n_qubits (int): number of qubits
319 """
320 w_idx = 0
321 for q in range(n_qubits):
322 Gates.RY(w[w_idx], wires=q, noise_params=noise_params)
323 w_idx += 1
325 if n_qubits > 1:
326 for q in range(n_qubits):
327 Gates.CX(
328 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits],
329 noise_params=noise_params,
330 )
332 for q in range(n_qubits):
333 Gates.RY(w[w_idx], wires=q, noise_params=noise_params)
334 w_idx += 1
336 if n_qubits > 1:
337 for q in range(n_qubits):
338 Gates.CX(
339 wires=[(q - 1) % n_qubits, (q - 2) % n_qubits],
340 noise_params=noise_params,
341 )
343 class Circuit_9(Circuit):
344 @staticmethod
345 def n_params_per_layer(n_qubits: int) -> int:
346 return n_qubits
348 @staticmethod
349 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
350 return None
352 @staticmethod
353 def build(w: np.ndarray, n_qubits: int, noise_params=None):
354 """
355 Creates a Circuit9 ansatz.
357 Length of flattened vector must be n_qubits
359 Args:
360 w (np.ndarray): weight vector of size n_layers*n_qubits
361 n_qubits (int): number of qubits
362 """
363 w_idx = 0
364 for q in range(n_qubits):
365 Gates.H(wires=q, noise_params=noise_params)
367 if n_qubits > 1:
368 for q in range(n_qubits - 1):
369 Gates.CZ(
370 wires=[n_qubits - q - 2, n_qubits - q - 1],
371 noise_params=noise_params,
372 )
374 for q in range(n_qubits):
375 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
376 w_idx += 1
378 class Circuit_6(Circuit):
379 @staticmethod
380 def n_params_per_layer(n_qubits: int) -> int:
381 if n_qubits > 1:
382 return n_qubits * 3 + n_qubits**2
383 else:
384 log.warning("Number of Qubits < 2, no entanglement available")
385 return 4
387 @staticmethod
388 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
389 if n_qubits > 1:
390 return [-n_qubits, None, None]
391 else:
392 return None
394 @staticmethod
395 def build(w: np.ndarray, n_qubits: int, noise_params=None):
396 """
397 Creates a Circuit6 ansatz.
399 Length of flattened vector must be
400 n_qubits * 4 + n_qubits * (n_qubits - 1) =
401 n_qubits * 3 + n_qubits**2
403 Args:
404 w (np.ndarray): weight vector of size
405 n_layers * (n_qubits * 3 + n_qubits**2)
406 n_qubits (int): number of qubits
407 """
408 w_idx = 0
409 for q in range(n_qubits):
410 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
411 w_idx += 1
412 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
413 w_idx += 1
415 if n_qubits > 1:
416 for ql in range(n_qubits):
417 for q in range(n_qubits):
418 if q == ql:
419 continue
420 Gates.CRX(
421 w[w_idx],
422 wires=[n_qubits - ql - 1, (n_qubits - q - 1) % n_qubits],
423 noise_params=noise_params,
424 )
425 w_idx += 1
427 for q in range(n_qubits):
428 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
429 w_idx += 1
430 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
431 w_idx += 1
433 class Circuit_1(Circuit):
434 @staticmethod
435 def n_params_per_layer(n_qubits: int) -> int:
436 return n_qubits * 2
438 @staticmethod
439 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
440 return None
442 @staticmethod
443 def build(w: np.ndarray, n_qubits: int, noise_params=None):
444 """
445 Creates a Circuit1 ansatz.
447 Length of flattened vector must be n_qubits*2
449 Args:
450 w (np.ndarray): weight vector of size n_layers*(n_qubits*2)
451 n_qubits (int): number of qubits
452 """
453 w_idx = 0
454 for q in range(n_qubits):
455 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
456 w_idx += 1
457 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
458 w_idx += 1
460 class Circuit_2(Circuit):
461 @staticmethod
462 def n_params_per_layer(n_qubits: int) -> int:
463 return n_qubits * 2
465 @staticmethod
466 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
467 return None
469 @staticmethod
470 def build(w: np.ndarray, n_qubits: int, noise_params=None):
471 """
472 Creates a Circuit2 ansatz.
474 Length of flattened vector must be n_qubits*2
476 Args:
477 w (np.ndarray): weight vector of size n_layers*(n_qubits*2)
478 n_qubits (int): number of qubits
479 """
480 w_idx = 0
481 for q in range(n_qubits):
482 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
483 w_idx += 1
484 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
485 w_idx += 1
487 if n_qubits > 1:
488 for q in range(n_qubits - 1):
489 Gates.CX(
490 wires=[n_qubits - q - 1, n_qubits - q - 2],
491 noise_params=noise_params,
492 )
494 class Circuit_3(Circuit):
495 @staticmethod
496 def n_params_per_layer(n_qubits: int) -> int:
497 return n_qubits * 3 - 1
499 @staticmethod
500 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
501 return None
503 @staticmethod
504 def build(w: np.ndarray, n_qubits: int, noise_params=None):
505 """
506 Creates a Circuit3 ansatz.
508 Length of flattened vector must be n_qubits*3-1
510 Args:
511 w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
512 n_qubits (int): number of qubits
513 """
514 w_idx = 0
515 for q in range(n_qubits):
516 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
517 w_idx += 1
518 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
519 w_idx += 1
521 if n_qubits > 1:
522 for q in range(n_qubits - 1):
523 Gates.CRZ(
524 w[w_idx],
525 wires=[n_qubits - q - 1, n_qubits - q - 2],
526 noise_params=noise_params,
527 )
528 w_idx += 1
530 class Circuit_4(Circuit):
531 @staticmethod
532 def n_params_per_layer(n_qubits: int) -> int:
533 return n_qubits * 3 - 1
535 @staticmethod
536 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
537 return None
539 @staticmethod
540 def build(w: np.ndarray, n_qubits: int, noise_params=None):
541 """
542 Creates a Circuit4 ansatz.
544 Length of flattened vector must be n_qubits*3-1
546 Args:
547 w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1)
548 n_qubits (int): number of qubits
549 """
550 w_idx = 0
551 for q in range(n_qubits):
552 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
553 w_idx += 1
554 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
555 w_idx += 1
557 if n_qubits > 1:
558 for q in range(n_qubits - 1):
559 Gates.CRX(
560 w[w_idx],
561 wires=[n_qubits - q - 1, n_qubits - q - 2],
562 noise_params=noise_params,
563 )
564 w_idx += 1
566 class Circuit_10(Circuit):
567 @staticmethod
568 def n_params_per_layer(n_qubits: int) -> int:
569 return n_qubits * 2 # constant gates not considered yet. has to be fixed
571 @staticmethod
572 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
573 return None
575 @staticmethod
576 def build(w: np.ndarray, n_qubits: int, noise_params=None):
577 """
578 Creates a Circuit10 ansatz.
580 Length of flattened vector must be n_qubits
582 Args:
583 w (np.ndarray): weight vector of size n_layers*n_qubits
584 n_qubits (int): number of qubits
585 """
586 w_idx = 0
587 # constant gates, independent of layers. has to be fixed
588 for q in range(n_qubits):
589 Gates.RY(w[w_idx], wires=q, noise_params=noise_params)
590 w_idx += 1
592 if n_qubits > 1:
593 for q in range(n_qubits - 1):
594 Gates.CZ(
595 wires=[
596 (n_qubits - q - 2) % n_qubits,
597 (n_qubits - q - 1) % n_qubits,
598 ],
599 noise_params=noise_params,
600 )
601 if n_qubits > 2:
602 Gates.CZ(wires=[n_qubits - 1, 0], noise_params=noise_params)
604 for q in range(n_qubits):
605 Gates.RY(w[w_idx], wires=q, noise_params=noise_params)
606 w_idx += 1
608 class Circuit_16(Circuit):
609 @staticmethod
610 def n_params_per_layer(n_qubits: int) -> int:
611 return n_qubits * 3 - 1
613 @staticmethod
614 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
615 return None
617 @staticmethod
618 def build(w: np.ndarray, n_qubits: int, noise_params=None):
619 """
620 Creates a Circuit16 ansatz.
622 Length of flattened vector must be n_qubits*3-1
624 Args:
625 w (np.ndarray): weight vector of size n_layers*n_qubits*3-1
626 n_qubits (int): number of qubits
627 """
628 w_idx = 0
629 for q in range(n_qubits):
630 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
631 w_idx += 1
632 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
633 w_idx += 1
635 if n_qubits > 1:
636 for q in range(n_qubits // 2):
637 Gates.CRZ(
638 w[w_idx],
639 wires=[(2 * q + 1), (2 * q)],
640 noise_params=noise_params,
641 )
642 w_idx += 1
644 for q in range((n_qubits - 1) // 2):
645 Gates.CRZ(
646 w[w_idx],
647 wires=[(2 * q + 2), (2 * q + 1)],
648 noise_params=noise_params,
649 )
650 w_idx += 1
652 class Circuit_17(Circuit):
653 @staticmethod
654 def n_params_per_layer(n_qubits: int) -> int:
655 return n_qubits * 3 - 1
657 @staticmethod
658 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
659 return None
661 @staticmethod
662 def build(w: np.ndarray, n_qubits: int, noise_params=None):
663 """
664 Creates a Circuit17 ansatz.
666 Length of flattened vector must be n_qubits*3-1
668 Args:
669 w (np.ndarray): weight vector of size n_layers*n_qubits*3-1
670 n_qubits (int): number of qubits
671 """
672 w_idx = 0
673 for q in range(n_qubits):
674 Gates.RX(w[w_idx], wires=q, noise_params=noise_params)
675 w_idx += 1
676 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
677 w_idx += 1
679 if n_qubits > 1:
680 for q in range(n_qubits // 2):
681 Gates.CRX(
682 w[w_idx],
683 wires=[(2 * q + 1), (2 * q)],
684 noise_params=noise_params,
685 )
686 w_idx += 1
688 for q in range((n_qubits - 1) // 2):
689 Gates.CRX(
690 w[w_idx],
691 wires=[(2 * q + 2), (2 * q + 1)],
692 noise_params=noise_params,
693 )
694 w_idx += 1
696 class Strongly_Entangling(Circuit):
697 @staticmethod
698 def n_params_per_layer(n_qubits: int) -> int:
699 if n_qubits < 2:
700 log.warning("Number of Qubits < 2, no entanglement available")
701 return n_qubits * 6
703 @staticmethod
704 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
705 return None
707 @staticmethod
708 def build(w: np.ndarray, n_qubits: int, noise_params=None) -> None:
709 """
710 Creates a StronglyEntanglingLayers ansatz.
712 Args:
713 w (np.ndarray): weight vector of size n_layers*(n_qubits*6)
714 n_qubits (int): number of qubits
715 """
716 w_idx = 0
717 for q in range(n_qubits):
718 Gates.Rot(
719 w[w_idx],
720 w[w_idx + 1],
721 w[w_idx + 2],
722 wires=q,
723 noise_params=noise_params,
724 )
725 w_idx += 3
727 if n_qubits > 1:
728 for q in range(n_qubits):
729 Gates.CX(wires=[q, (q + 1) % n_qubits], noise_params=noise_params)
731 for q in range(n_qubits):
732 Gates.Rot(
733 w[w_idx],
734 w[w_idx + 1],
735 w[w_idx + 2],
736 wires=q,
737 noise_params=noise_params,
738 )
739 w_idx += 3
741 if n_qubits > 1:
742 for q in range(n_qubits):
743 Gates.CX(
744 wires=[q, (q + n_qubits // 2) % n_qubits],
745 noise_params=noise_params,
746 )
748 class No_Entangling(Circuit):
749 @staticmethod
750 def n_params_per_layer(n_qubits: int) -> int:
751 return n_qubits * 3
753 @staticmethod
754 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
755 return None
757 @staticmethod
758 def build(w: np.ndarray, n_qubits: int, noise_params=None):
759 """
760 Creates a circuit without entangling, but with U3 gates on all qubits
762 Length of flattened vector must be n_qubits*3
764 Args:
765 w (np.ndarray): weight vector of size n_layers*(n_qubits*3)
766 n_qubits (int): number of qubits
767 """
768 w_idx = 0
769 for q in range(n_qubits):
770 Gates.Rot(
771 w[w_idx],
772 w[w_idx + 1],
773 w[w_idx + 2],
774 wires=q,
775 noise_params=noise_params,
776 )
777 w_idx += 3