Coverage for qml_essentials/ansaetze.py: 91%

484 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-29 14:55 +0000

1from abc import ABC, abstractmethod 

2from typing import Any, Optional 

3import pennylane.numpy as np 

4import pennylane as qml 

5import itertools 

6 

7from typing import List, Union, Dict 

8 

9import logging 

10 

11log = logging.getLogger(__name__) 

12 

13 

14class Circuit(ABC): 

15 def __init__(self): 

16 pass 

17 

18 @abstractmethod 

19 def n_params_per_layer(n_qubits: int) -> int: 

20 raise NotImplementedError("n_params_per_layer method is not implemented") 

21 

22 @abstractmethod 

23 def get_control_indices(self, n_qubits: int) -> List[int]: 

24 """ 

25 Returns the indices for the controlled rotation gates for one layer. 

26 Indices should slice the list of all parameters for one layer as follows: 

27 [indices[0]:indices[1]:indices[2]] 

28 

29 Parameters 

30 ---------- 

31 n_qubits : int 

32 Number of qubits in the circuit 

33 

34 Returns 

35 ------- 

36 Optional[np.ndarray] 

37 List of all controlled indices, or None if the circuit does not 

38 contain controlled rotation gates. 

39 """ 

40 raise NotImplementedError("get_control_indices method is not implemented") 

41 

42 def get_control_angles(self, w: np.ndarray, n_qubits: int) -> Optional[np.ndarray]: 

43 """ 

44 Returns the angles for the controlled rotation gates from the list of 

45 all parameters for one layer. 

46 

47 Parameters 

48 ---------- 

49 w : np.ndarray 

50 List of parameters for one layer 

51 n_qubits : int 

52 Number of qubits in the circuit 

53 

54 Returns 

55 ------- 

56 Optional[np.ndarray] 

57 List of all controlled parameters, or None if the circuit does not 

58 contain controlled rotation gates. 

59 """ 

60 indices = self.get_control_indices(n_qubits) 

61 if indices is None: 

62 return np.array([]) 

63 

64 return w[indices[0] : indices[1] : indices[2]] 

65 

66 @abstractmethod 

67 def build(self, n_qubits: int, n_layers: int): 

68 raise NotImplementedError("build method is not implemented") 

69 

70 def __call__(self, *args: Any, **kwds: Any) -> Any: 

71 self.build(*args, **kwds) 

72 

73 

74class Gates: 

75 rng = np.random.default_rng() 

76 

77 @staticmethod 

78 def init_rng(seed: int): 

79 """ 

80 Initializes the random number generator with the given seed. 

81 

82 Parameters 

83 ---------- 

84 seed : int 

85 The seed for the random number generator. 

86 """ 

87 Gates.rng = np.random.default_rng(seed) 

88 

89 @staticmethod 

90 def NQubitDepolarizingChannel(p, wires): 

91 """ 

92 Generates the Kraus operators for an n-qubit depolarizing channel. 

93 

94 The n-qubit depolarizing channel is defined as: 

95 E(rho) = sqrt(1 - p * (4^n - 1) / 4^n) * rho 

96 + sqrt(p / 4^n) * ∑_{P ≠ I^{⊗n}} P rho P† 

97 where the sum is over all non-identity n-qubit Pauli operators 

98 (i.e., tensor products of {I, X, Y, Z} excluding the identity operator I^{⊗n}). 

99 Each Pauli error operator is weighted equally by p / 4^n. 

100 

101 This operator-sum (Kraus) representation models uniform depolarizing noise 

102 acting on n qubits simultaneously. It is useful for simulating realistic 

103 multi-qubit noise affecting entangling gates in noisy quantum circuits. 

104 

105 Parameters 

106 ---------- 

107 p : float 

108 The total probability of an n-qubit depolarizing error occurring. 

109 Must satisfy 0 ≤ p ≤ 1. 

110 

111 wires : Sequence[int] 

112 The list of qubit indices (wires) on which the channel acts. 

113 Must contain at least 2 qubits. 

114 

115 Returns 

116 ------- 

117 qml.QubitChannel 

118 A PennyLane QubitChannel constructed from the Kraus operators representing 

119 the n-qubit depolarizing noise channel acting on the specified wires. 

120 """ 

121 

122 def n_qubit_depolarizing_kraus(p: float, n: int) -> List[np.ndarray]: 

123 if not (0.0 <= p <= 1.0): 

124 raise ValueError(f"Probability p must be between 0 and 1, got {p}") 

125 if n < 2: 

126 raise ValueError(f"Number of qubits must be >= 2, got {n}") 

127 

128 Id = np.eye(2) 

129 X = qml.matrix(qml.PauliX(0)) 

130 Y = qml.matrix(qml.PauliY(0)) 

131 Z = qml.matrix(qml.PauliZ(0)) 

132 paulis = [Id, X, Y, Z] 

133 

134 dim = 2**n 

135 all_ops = [] 

136 

137 # Generate all n-qubit Pauli tensor products: 

138 for indices in itertools.product(range(4), repeat=n): 

139 P = np.eye(1) 

140 for idx in indices: 

141 P = np.kron(P, paulis[idx]) 

142 all_ops.append(P) 

143 

144 # Identity operator corresponds to all zeros indices (Id^n) 

145 K0 = np.sqrt(1 - p * (4**n - 1) / (4**n)) * np.eye(dim) 

146 

147 kraus_ops = [] 

148 for i, P in enumerate(all_ops): 

149 if i == 0: 

150 # Skip the identity, already handled as K0 

151 continue 

152 kraus_ops.append(np.sqrt(p / (4**n)) * P) 

153 

154 return [K0] + kraus_ops 

155 

156 return qml.QubitChannel(n_qubit_depolarizing_kraus(p, len(wires)), wires=wires) 

157 

158 @staticmethod 

159 def Noise( 

160 wires: Union[int, List[int]], noise_params: Optional[Dict[str, float]] = None 

161 ) -> None: 

162 """ 

163 Applies noise to the given wires. 

164 

165 Parameters 

166 ---------- 

167 wires : Union[int, List[int]] 

168 The wire(s) to apply the noise to. 

169 noise_params : Optional[Dict[str, float]] 

170 A dictionary of noise parameters. The following noise gates are 

171 supported: 

172 -BitFlip: Applies a bit flip error to the given wires. 

173 -PhaseFlip: Applies a phase flip error to the given wires. 

174 -Depolarizing: Applies a depolarizing channel error to the 

175 given wires. 

176 -MultiQubitDepolarizing: Applies a two-qubit depolarizing channel 

177 error to the given wires. 

178 

179 All parameters are optional and default to 0.0 if not provided. 

180 """ 

181 if noise_params is not None: 

182 if isinstance(wires, int): 

183 wires = [wires] # single qubit gate 

184 

185 # noise on single qubits 

186 for wire in wires: 

187 bf = noise_params.get("BitFlip", 0.0) 

188 if bf > 0: 

189 qml.BitFlip(bf, wires=wire) 

190 

191 pf = noise_params.get("PhaseFlip", 0.0) 

192 if pf > 0: 

193 qml.PhaseFlip(pf, wires=wire) 

194 

195 dp = noise_params.get("Depolarizing", 0.0) 

196 if dp > 0: 

197 qml.DepolarizingChannel(dp, wires=wire) 

198 

199 # noise on two-qubits 

200 if len(wires) > 1: 

201 p = noise_params.get("MultiQubitDepolarizing", 0.0) 

202 if p > 0: 

203 Gates.NQubitDepolarizingChannel(p, wires) 

204 

205 @staticmethod 

206 def GateError( 

207 w: float, noise_params: Optional[Dict[str, float]] = None 

208 ) -> np.ndarray: 

209 """ 

210 Applies a gate error to the given rotation angle(s). 

211 

212 Parameters 

213 ---------- 

214 w : Union[float, np.ndarray, List[float]] 

215 The rotation angle in radians. 

216 noise_params : Optional[Dict[str, float]] 

217 A dictionary of noise parameters. The following noise gates are 

218 supported: 

219 -GateError: Applies a normal distribution error to the rotation 

220 angle. The standard deviation of the noise is specified by 

221 the "GateError" key in the dictionary. 

222 

223 All parameters are optional and default to 0.0 if not provided. 

224 

225 Returns 

226 ------- 

227 float 

228 The modified rotation angle after applying the gate error. 

229 """ 

230 if noise_params is not None and noise_params.get("GateError", None) is not None: 

231 w += Gates.rng.normal( 

232 0, 

233 noise_params["GateError"], 

234 w.shape if isinstance(w, np.ndarray) else 1, 

235 ) 

236 return w 

237 

238 @staticmethod 

239 def Rot(phi, theta, omega, wires, noise_params=None): 

240 """ 

241 Applies a rotation gate to the given wires and adds `Noise` 

242 

243 Parameters 

244 ---------- 

245 phi : Union[float, np.ndarray, List[float]] 

246 The first rotation angle in radians. 

247 theta : Union[float, np.ndarray, List[float]] 

248 The second rotation angle in radians. 

249 omega : Union[float, np.ndarray, List[float]] 

250 The third rotation angle in radians. 

251 wires : Union[int, List[int]] 

252 The wire(s) to apply the rotation gate to. 

253 noise_params : Optional[Dict[str, float]] 

254 A dictionary of noise parameters. The following noise gates are 

255 supported: 

256 -BitFlip: Applies a bit flip error to the given wires. 

257 -PhaseFlip: Applies a phase flip error to the given wires. 

258 -Depolarizing: Applies a depolarizing channel error to the 

259 given wires. 

260 

261 All parameters are optional and default to 0.0 if not provided. 

262 """ 

263 if noise_params is not None and "GateError" in noise_params: 

264 phi = Gates.GateError(phi, noise_params) 

265 theta = Gates.GateError(theta, noise_params) 

266 omega = Gates.GateError(omega, noise_params) 

267 # phi += Gates.rng.normal(0, noise_params["GateError"]) 

268 # theta += Gates.rng.normal(0, noise_params["GateError"]) 

269 # omega += Gates.rng.normal(0, noise_params["GateError"]) 

270 qml.Rot(phi, theta, omega, wires=wires) 

271 Gates.Noise(wires, noise_params) 

272 

273 @staticmethod 

274 def RX(w, wires, noise_params=None): 

275 """ 

276 Applies a rotation around the X axis to the given wires and adds `Noise` 

277 

278 Parameters 

279 ---------- 

280 w : Union[float, np.ndarray, List[float]] 

281 The rotation angle in radians. 

282 wires : Union[int, List[int]] 

283 The wire(s) to apply the rotation gate to. 

284 noise_params : Optional[Dict[str, float]] 

285 A dictionary of noise parameters. The following noise gates are 

286 supported: 

287 -BitFlip: Applies a bit flip error to the given wires. 

288 -PhaseFlip: Applies a phase flip error to the given wires. 

289 -Depolarizing: Applies a depolarizing channel error to the 

290 given wires. 

291 

292 All parameters are optional and default to 0.0 if not provided. 

293 """ 

294 w = Gates.GateError(w, noise_params) 

295 qml.RX(w, wires=wires) 

296 Gates.Noise(wires, noise_params) 

297 

298 @staticmethod 

299 def RY(w, wires, noise_params=None): 

300 """ 

301 Applies a rotation around the Y axis to the given wires and adds `Noise` 

302 

303 Parameters 

304 ---------- 

305 w : Union[float, np.ndarray, List[float]] 

306 The rotation angle in radians. 

307 wires : Union[int, List[int]] 

308 The wire(s) to apply the rotation gate to. 

309 noise_params : Optional[Dict[str, float]] 

310 A dictionary of noise parameters. The following noise gates are 

311 supported: 

312 -BitFlip: Applies a bit flip error to the given wires. 

313 -PhaseFlip: Applies a phase flip error to the given wires. 

314 -Depolarizing: Applies a depolarizing channel error to the 

315 given wires. 

316 

317 All parameters are optional and default to 0.0 if not provided. 

318 """ 

319 w = Gates.GateError(w, noise_params) 

320 qml.RY(w, wires=wires) 

321 Gates.Noise(wires, noise_params) 

322 

323 @staticmethod 

324 def RZ(w, wires, noise_params=None): 

325 """ 

326 Applies a rotation around the Z axis to the given wires and adds `Noise` 

327 

328 Parameters 

329 ---------- 

330 w : Union[float, np.ndarray, List[float]] 

331 The rotation angle in radians. 

332 wires : Union[int, List[int]] 

333 The wire(s) to apply the rotation gate to. 

334 noise_params : Optional[Dict[str, float]] 

335 A dictionary of noise parameters. The following noise gates are 

336 supported: 

337 -BitFlip: Applies a bit flip error to the given wires. 

338 -PhaseFlip: Applies a phase flip error to the given wires. 

339 -Depolarizing: Applies a depolarizing channel error to the 

340 given wires. 

341 

342 All parameters are optional and default to 0.0 if not provided. 

343 """ 

344 w = Gates.GateError(w, noise_params) 

345 qml.RZ(w, wires=wires) 

346 Gates.Noise(wires, noise_params) 

347 

348 @staticmethod 

349 def CRX(w, wires, noise_params=None): 

350 """ 

351 Applies a controlled rotation around the X axis to the given wires 

352 and adds `Noise` 

353 

354 Parameters 

355 ---------- 

356 w : Union[float, np.ndarray, List[float]] 

357 The rotation angle in radians. 

358 wires : Union[int, List[int]] 

359 The wire(s) to apply the controlled rotation gate to. 

360 noise_params : Optional[Dict[str, float]] 

361 A dictionary of noise parameters. The following noise gates are 

362 supported: 

363 -BitFlip: Applies a bit flip error to the given wires. 

364 -PhaseFlip: Applies a phase flip error to the given wires. 

365 -Depolarizing: Applies a depolarizing channel error to the 

366 given wires. 

367 

368 All parameters are optional and default to 0.0 if not provided. 

369 """ 

370 w = Gates.GateError(w, noise_params) 

371 qml.CRX(w, wires=wires) 

372 Gates.Noise(wires, noise_params) 

373 

374 @staticmethod 

375 def CRY(w, wires, noise_params=None): 

376 """ 

377 Applies a controlled rotation around the Y axis to the given wires 

378 and adds `Noise` 

379 

380 Parameters 

381 ---------- 

382 w : Union[float, np.ndarray, List[float]] 

383 The rotation angle in radians. 

384 wires : Union[int, List[int]] 

385 The wire(s) to apply the controlled rotation gate to. 

386 noise_params : Optional[Dict[str, float]] 

387 A dictionary of noise parameters. The following noise gates are 

388 supported: 

389 -BitFlip: Applies a bit flip error to the given wires. 

390 -PhaseFlip: Applies a phase flip error to the given wires. 

391 -Depolarizing: Applies a depolarizing channel error to the 

392 given wires. 

393 

394 All parameters are optional and default to 0.0 if not provided. 

395 """ 

396 w = Gates.GateError(w, noise_params) 

397 qml.CRY(w, wires=wires) 

398 Gates.Noise(wires, noise_params) 

399 

400 @staticmethod 

401 def CRZ(w, wires, noise_params=None): 

402 """ 

403 Applies a controlled rotation around the Z axis to the given wires 

404 and adds `Noise` 

405 

406 Parameters 

407 ---------- 

408 w : Union[float, np.ndarray, List[float]] 

409 The rotation angle in radians. 

410 wires : Union[int, List[int]] 

411 The wire(s) to apply the controlled rotation gate to. 

412 noise_params : Optional[Dict[str, float]] 

413 A dictionary of noise parameters. The following noise gates are 

414 supported: 

415 -BitFlip: Applies a bit flip error to the given wires. 

416 -PhaseFlip: Applies a phase flip error to the given wires. 

417 -Depolarizing: Applies a depolarizing channel error to the 

418 given wires. 

419 

420 All parameters are optional and default to 0.0 if not provided. 

421 """ 

422 w = Gates.GateError(w, noise_params) 

423 qml.CRZ(w, wires=wires) 

424 Gates.Noise(wires, noise_params) 

425 

426 @staticmethod 

427 def CX(wires, noise_params=None): 

428 """ 

429 Applies a controlled NOT gate to the given wires and adds `Noise` 

430 

431 Parameters 

432 ---------- 

433 wires : Union[int, List[int]] 

434 The wire(s) to apply the controlled NOT gate to. 

435 noise_params : Optional[Dict[str, float]] 

436 A dictionary of noise parameters. The following noise gates are 

437 supported: 

438 -BitFlip: Applies a bit flip error to the given wires. 

439 -PhaseFlip: Applies a phase flip error to the given wires. 

440 -Depolarizing: Applies a depolarizing channel error to the 

441 given wires. 

442 

443 All parameters are optional and default to 0.0 if not provided. 

444 """ 

445 qml.CNOT(wires=wires) 

446 Gates.Noise(wires, noise_params) 

447 

448 @staticmethod 

449 def CY(wires, noise_params=None): 

450 """ 

451 Applies a controlled Y gate to the given wires and adds `Noise` 

452 

453 Parameters 

454 ---------- 

455 wires : Union[int, List[int]] 

456 The wire(s) to apply the controlled Y gate to. 

457 noise_params : Optional[Dict[str, float]] 

458 A dictionary of noise parameters. The following noise gates are 

459 supported: 

460 -BitFlip: Applies a bit flip error to the given wires. 

461 -PhaseFlip: Applies a phase flip error to the given wires. 

462 -Depolarizing: Applies a depolarizing channel error to the 

463 given wires. 

464 

465 All parameters are optional and default to 0.0 if not provided. 

466 """ 

467 qml.CY(wires=wires) 

468 Gates.Noise(wires, noise_params) 

469 

470 @staticmethod 

471 def CZ(wires, noise_params=None): 

472 """ 

473 Applies a controlled Z gate to the given wires and adds `Noise` 

474 

475 Parameters 

476 ---------- 

477 wires : Union[int, List[int]] 

478 The wire(s) to apply the controlled Z gate to. 

479 noise_params : Optional[Dict[str, float]] 

480 A dictionary of noise parameters. The following noise gates are 

481 supported: 

482 -BitFlip: Applies a bit flip error to the given wires. 

483 -PhaseFlip: Applies a phase flip error to the given wires. 

484 -Depolarizing: Applies a depolarizing channel error to the 

485 given wires. 

486 

487 All parameters are optional and default to 0.0 if not provided. 

488 """ 

489 qml.CZ(wires=wires) 

490 Gates.Noise(wires, noise_params) 

491 

492 @staticmethod 

493 def H(wires, noise_params=None): 

494 """ 

495 Applies a Hadamard gate to the given wires and adds `Noise` 

496 

497 Parameters 

498 ---------- 

499 wires : Union[int, List[int]] 

500 The wire(s) to apply the Hadamard gate to. 

501 noise_params : Optional[Dict[str, float]] 

502 A dictionary of noise parameters. The following noise gates are 

503 supported: 

504 -BitFlip: Applies a bit flip error to the given wires. 

505 -PhaseFlip: Applies a phase flip error to the given wires. 

506 -Depolarizing: Applies a depolarizing channel error to the 

507 given wires. 

508 

509 All parameters are optional and default to 0.0 if not provided. 

510 """ 

511 qml.Hadamard(wires=wires) 

512 Gates.Noise(wires, noise_params) 

513 

514 

515class Ansaetze: 

516 

517 def get_available(): 

518 return [ 

519 Ansaetze.No_Ansatz, 

520 Ansaetze.Circuit_1, 

521 Ansaetze.Circuit_2, 

522 Ansaetze.Circuit_3, 

523 Ansaetze.Circuit_4, 

524 Ansaetze.Circuit_6, 

525 Ansaetze.Circuit_9, 

526 Ansaetze.Circuit_10, 

527 Ansaetze.Circuit_15, 

528 Ansaetze.Circuit_16, 

529 Ansaetze.Circuit_17, 

530 Ansaetze.Circuit_18, 

531 Ansaetze.Circuit_19, 

532 Ansaetze.No_Entangling, 

533 Ansaetze.Strongly_Entangling, 

534 Ansaetze.Hardware_Efficient, 

535 Ansaetze.GHZ, 

536 ] 

537 

538 class No_Ansatz(Circuit): 

539 @staticmethod 

540 def n_params_per_layer(n_qubits: int) -> int: 

541 return 0 

542 

543 @staticmethod 

544 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

545 return None 

546 

547 @staticmethod 

548 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

549 pass 

550 

551 class GHZ(Circuit): 

552 @staticmethod 

553 def n_params_per_layer(n_qubits: int) -> int: 

554 return 0 

555 

556 @staticmethod 

557 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

558 return None 

559 

560 @staticmethod 

561 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

562 Gates.H(0, noise_params=noise_params) 

563 

564 for q in range(n_qubits - 1): 

565 Gates.CX([q, q + 1], noise_params=noise_params) 

566 

567 class Hardware_Efficient(Circuit): 

568 @staticmethod 

569 def n_params_per_layer(n_qubits: int) -> int: 

570 """ 

571 Returns the number of parameters per layer for the 

572 Hardware Efficient Ansatz. 

573 

574 The number of parameters is 3 times the number of qubits when there 

575 is more than one qubit, as each qubit contributes 3 parameters. 

576 If the number of qubits is less than 2, a warning is logged since 

577 no entanglement is possible, and a fixed number of 2 parameters is used. 

578 

579 Parameters 

580 ---------- 

581 n_qubits : int 

582 Number of qubits in the circuit 

583 

584 Returns 

585 ------- 

586 int 

587 Number of parameters required for one layer of the circuit 

588 """ 

589 if n_qubits < 2: 

590 log.warning("Number of Qubits < 2, no entanglement available") 

591 return n_qubits * 3 

592 

593 @staticmethod 

594 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

595 """ 

596 No controlled rotation gates available. Always None. 

597 

598 Parameters 

599 ---------- 

600 n_qubits : int 

601 Number of qubits in the circuit 

602 

603 Returns 

604 ------- 

605 Optional[np.ndarray] 

606 List of all controlled indices, or None if the circuit does not 

607 contain controlled rotation gates. 

608 """ 

609 return None 

610 

611 @staticmethod 

612 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

613 """ 

614 Creates a Hardware-Efficient ansatz, as proposed in 

615 https://arxiv.org/pdf/2309.03279 

616 

617 Parameters 

618 ---------- 

619 w : np.ndarray 

620 Weight vector of size n_qubits*3 

621 n_qubits : int 

622 Number of qubits 

623 noise_params : Optional[Dict[str, float]], optional 

624 Dictionary of noise parameters to apply to the gates 

625 """ 

626 w_idx = 0 

627 for q in range(n_qubits): 

628 Gates.RY(w[w_idx], wires=q, noise_params=noise_params) 

629 w_idx += 1 

630 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

631 w_idx += 1 

632 Gates.RY(w[w_idx], wires=q, noise_params=noise_params) 

633 w_idx += 1 

634 

635 if n_qubits > 1: 

636 for q in range(n_qubits // 2): 

637 Gates.CX(wires=[(2 * q), (2 * q + 1)], noise_params=noise_params) 

638 for q in range((n_qubits - 1) // 2): 

639 Gates.CX( 

640 wires=[(2 * q + 1), (2 * q + 2)], noise_params=noise_params 

641 ) 

642 if n_qubits > 2: 

643 Gates.CX(wires=[(n_qubits - 1), 0], noise_params=noise_params) 

644 

645 class Circuit_19(Circuit): 

646 @staticmethod 

647 def n_params_per_layer(n_qubits: int) -> int: 

648 """ 

649 Returns the number of parameters per layer for Circuit_19. 

650 

651 The number of parameters is 3 times the number of qubits when there 

652 is more than one qubit, as each qubit contributes 3 parameters. 

653 If the number of qubits is less than 2, a warning is logged since 

654 no entanglement is possible, and a fixed number of 2 parameters is used. 

655 

656 Parameters 

657 ---------- 

658 n_qubits : int 

659 Number of qubits in the circuit 

660 

661 Returns 

662 ------- 

663 int 

664 Number of parameters required for one layer of the circuit 

665 """ 

666 

667 if n_qubits > 1: 

668 return n_qubits * 3 

669 else: 

670 log.warning("Number of Qubits < 2, no entanglement available") 

671 return 2 

672 

673 @staticmethod 

674 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

675 """ 

676 Returns the indices for the controlled rotation gates for one layer. 

677 Indices should slice the list of all parameters for one layer as follows: 

678 [indices[0]:indices[1]:indices[2]] 

679 

680 Parameters 

681 ---------- 

682 n_qubits : int 

683 Number of qubits in the circuit 

684 

685 Returns 

686 ------- 

687 Optional[np.ndarray] 

688 List of all controlled indices, or None if the circuit does not 

689 contain controlled rotation gates. 

690 """ 

691 if n_qubits > 1: 

692 return [-n_qubits, None, None] 

693 else: 

694 return None 

695 

696 @staticmethod 

697 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

698 """ 

699 Creates a Circuit19 ansatz. 

700 

701 Length of flattened vector must be n_qubits*3 

702 because for >1 qubits there are three gates 

703 

704 Parameters 

705 ---------- 

706 w : np.ndarray 

707 Weight vector of size n_qubits*3 

708 n_qubits : int 

709 Number of qubits 

710 noise_params : Optional[Dict[str, float]], optional 

711 Dictionary of noise parameters to apply to the gates 

712 """ 

713 w_idx = 0 

714 for q in range(n_qubits): 

715 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

716 w_idx += 1 

717 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

718 w_idx += 1 

719 

720 if n_qubits > 1: 

721 for q in range(n_qubits): 

722 Gates.CRX( 

723 w[w_idx], 

724 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits], 

725 noise_params=noise_params, 

726 ) 

727 w_idx += 1 

728 

729 class Circuit_18(Circuit): 

730 @staticmethod 

731 def n_params_per_layer(n_qubits: int) -> int: 

732 """ 

733 Returns the number of parameters per layer for Circuit_18. 

734 

735 The number of parameters is 3 times the number of qubits when there 

736 is more than one qubit, as each qubit contributes 3 parameters. 

737 If the number of qubits is less than 2, a warning is logged since 

738 no entanglement is possible, and a fixed number of 2 parameters is used. 

739 

740 Parameters 

741 ---------- 

742 n_qubits : int 

743 Number of qubits in the circuit 

744 

745 Returns 

746 ------- 

747 int 

748 Number of parameters required for one layer of the circuit 

749 """ 

750 if n_qubits > 1: 

751 return n_qubits * 3 

752 else: 

753 log.warning("Number of Qubits < 2, no entanglement available") 

754 return 2 

755 

756 @staticmethod 

757 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

758 """ 

759 Returns the indices for the controlled rotation gates for one layer. 

760 Indices should slice the list of all parameters for one layer as follows: 

761 [indices[0]:indices[1]:indices[2]] 

762 

763 Parameters 

764 ---------- 

765 n_qubits : int 

766 Number of qubits in the circuit 

767 

768 Returns 

769 ------- 

770 Optional[np.ndarray] 

771 List of all controlled indices, or None if the circuit does not 

772 contain controlled rotation gates. 

773 """ 

774 if n_qubits > 1: 

775 return [-n_qubits, None, None] 

776 else: 

777 return None 

778 

779 @staticmethod 

780 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

781 """ 

782 Creates a Circuit18 ansatz. 

783 

784 Length of flattened vector must be n_qubits*3 

785 

786 Parameters 

787 ---------- 

788 w : np.ndarray 

789 Weight vector of size n_qubits*3 

790 n_qubits : int 

791 Number of qubits 

792 noise_params : Optional[Dict[str, float]], optional 

793 Dictionary of noise parameters to apply to the gates 

794 """ 

795 w_idx = 0 

796 for q in range(n_qubits): 

797 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

798 w_idx += 1 

799 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

800 w_idx += 1 

801 

802 if n_qubits > 1: 

803 for q in range(n_qubits): 

804 Gates.CRZ( 

805 w[w_idx], 

806 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits], 

807 noise_params=noise_params, 

808 ) 

809 w_idx += 1 

810 

811 class Circuit_15(Circuit): 

812 @staticmethod 

813 def n_params_per_layer(n_qubits: int) -> int: 

814 """ 

815 Returns the number of parameters per layer for Circuit_15. 

816 

817 The number of parameters is 2 times the number of qubits. 

818 A warning is logged if the number of qubits is less than 2. 

819 

820 Parameters 

821 ---------- 

822 n_qubits : int 

823 Number of qubits in the circuit 

824 

825 Returns 

826 ------- 

827 int 

828 Number of parameters required for one layer of the circuit 

829 """ 

830 if n_qubits > 1: 

831 return n_qubits * 2 

832 else: 

833 log.warning("Number of Qubits < 2, no entanglement available") 

834 return 2 

835 

836 @staticmethod 

837 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

838 """ 

839 No controlled rotation gates available. Always None. 

840 

841 Parameters 

842 ---------- 

843 n_qubits : int 

844 Number of qubits in the circuit 

845 

846 Returns 

847 ------- 

848 Optional[np.ndarray] 

849 List of all controlled indices, or None if the circuit does not 

850 contain controlled rotation gates. 

851 """ 

852 return None 

853 

854 @staticmethod 

855 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

856 """ 

857 Creates a Circuit15 ansatz. 

858 

859 Length of flattened vector must be n_qubits*2 

860 because for >1 qubits there are three gates 

861 

862 Parameters 

863 ---------- 

864 w : np.ndarray 

865 Weight vector of size n_qubits*2 

866 n_qubits : int 

867 Number of qubits 

868 noise_params : Optional[Dict[str, float]], optional 

869 Dictionary of noise parameters to apply to the gates 

870 """ 

871 w_idx = 0 

872 for q in range(n_qubits): 

873 Gates.RY(w[w_idx], wires=q, noise_params=noise_params) 

874 w_idx += 1 

875 

876 if n_qubits > 1: 

877 for q in range(n_qubits): 

878 Gates.CX( 

879 wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits], 

880 noise_params=noise_params, 

881 ) 

882 

883 for q in range(n_qubits): 

884 Gates.RY(w[w_idx], wires=q, noise_params=noise_params) 

885 w_idx += 1 

886 

887 if n_qubits > 1: 

888 for q in range(n_qubits): 

889 Gates.CX( 

890 wires=[(q - 1) % n_qubits, (q - 2) % n_qubits], 

891 noise_params=noise_params, 

892 ) 

893 

894 class Circuit_9(Circuit): 

895 @staticmethod 

896 def n_params_per_layer(n_qubits: int) -> int: 

897 """ 

898 Returns the number of parameters per layer for Circuit_9. 

899 

900 The number of parameters is equal to the number of qubits. 

901 

902 Parameters 

903 ---------- 

904 n_qubits : int 

905 Number of qubits in the circuit 

906 

907 Returns 

908 ------- 

909 int 

910 Number of parameters required for one layer of the circuit 

911 """ 

912 return n_qubits 

913 

914 @staticmethod 

915 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

916 """ 

917 No controlled rotation gates available. Always None. 

918 

919 Parameters 

920 ---------- 

921 n_qubits : int 

922 Number of qubits in the circuit 

923 

924 Returns 

925 ------- 

926 Optional[np.ndarray] 

927 List of all controlled indices, or None if the circuit does not 

928 contain controlled rotation gates. 

929 """ 

930 return None 

931 

932 @staticmethod 

933 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

934 """ 

935 Creates a Circuit9 ansatz. 

936 

937 Length of flattened vector must be n_qubits 

938 

939 Parameters 

940 ---------- 

941 w : np.ndarray 

942 Weight vector of size n_qubits 

943 n_qubits : int 

944 Number of qubits 

945 noise_params : Optional[Dict[str, float]], optional 

946 Dictionary of noise parameters to apply to the gates 

947 """ 

948 w_idx = 0 

949 for q in range(n_qubits): 

950 Gates.H(wires=q, noise_params=noise_params) 

951 

952 if n_qubits > 1: 

953 for q in range(n_qubits - 1): 

954 Gates.CZ( 

955 wires=[n_qubits - q - 2, n_qubits - q - 1], 

956 noise_params=noise_params, 

957 ) 

958 

959 for q in range(n_qubits): 

960 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

961 w_idx += 1 

962 

963 class Circuit_6(Circuit): 

964 @staticmethod 

965 def n_params_per_layer(n_qubits: int) -> int: 

966 """ 

967 Returns the number of parameters per layer for Circuit_6. 

968 

969 The total number of parameters is n_qubits*3+n_qubits**2, which is 

970 the number of rotations n_qubits*3 plus the number of entangling gates 

971 n_qubits**2. 

972 

973 If n_qubits is 1, the number of parameters is 4, and a warning is logged 

974 since no entanglement is possible. 

975 

976 Parameters 

977 ---------- 

978 n_qubits : int 

979 Number of qubits 

980 

981 Returns 

982 ------- 

983 int 

984 Number of parameters per layer 

985 """ 

986 if n_qubits > 1: 

987 return n_qubits * 3 + n_qubits**2 

988 else: 

989 log.warning("Number of Qubits < 2, no entanglement available") 

990 return 4 

991 

992 @staticmethod 

993 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

994 """ 

995 Returns the indices for the controlled rotation gates for one layer. 

996 Indices should slice the list of all parameters for one layer as follows: 

997 [indices[0]:indices[1]:indices[2]] 

998 

999 Parameters 

1000 ---------- 

1001 n_qubits : int 

1002 Number of qubits in the circuit 

1003 

1004 Returns 

1005 ------- 

1006 Optional[np.ndarray] 

1007 List of all controlled indices, or None if the circuit does not 

1008 contain controlled rotation gates. 

1009 """ 

1010 if n_qubits > 1: 

1011 return [-n_qubits, None, None] 

1012 else: 

1013 return None 

1014 

1015 @staticmethod 

1016 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1017 """ 

1018 Creates a Circuit6 ansatz. 

1019 

1020 Length of flattened vector must be 

1021 n_qubits*4+n_qubits*(n_qubits-1) = 

1022 n_qubits*3+n_qubits**2 

1023 

1024 Parameters 

1025 ---------- 

1026 w : np.ndarray 

1027 Weight vector of size 

1028 n_layers*(n_qubits*3+n_qubits**2) 

1029 n_qubits : int 

1030 Number of qubits 

1031 noise_params : Optional[Dict[str, float]], optional 

1032 Dictionary of noise parameters to apply to the gates 

1033 """ 

1034 w_idx = 0 

1035 for q in range(n_qubits): 

1036 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1037 w_idx += 1 

1038 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1039 w_idx += 1 

1040 

1041 if n_qubits > 1: 

1042 for ql in range(n_qubits): 

1043 for q in range(n_qubits): 

1044 if q == ql: 

1045 continue 

1046 Gates.CRX( 

1047 w[w_idx], 

1048 wires=[n_qubits - ql - 1, (n_qubits - q - 1) % n_qubits], 

1049 noise_params=noise_params, 

1050 ) 

1051 w_idx += 1 

1052 

1053 for q in range(n_qubits): 

1054 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1055 w_idx += 1 

1056 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1057 w_idx += 1 

1058 

1059 class Circuit_1(Circuit): 

1060 @staticmethod 

1061 def n_params_per_layer(n_qubits: int) -> int: 

1062 """ 

1063 Returns the number of parameters per layer for Circuit_1. 

1064 

1065 The total number of parameters is determined by the number of qubits, with 

1066 each qubit contributing 2 parameters. 

1067 

1068 Parameters 

1069 ---------- 

1070 n_qubits : int 

1071 Number of qubits in the circuit 

1072 

1073 Returns 

1074 ------- 

1075 int 

1076 Number of parameters per layer 

1077 """ 

1078 return n_qubits * 2 

1079 

1080 @staticmethod 

1081 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1082 """ 

1083 No controlled rotation gates available. Always None. 

1084 

1085 Parameters 

1086 ---------- 

1087 n_qubits : int 

1088 Number of qubits in the circuit 

1089 

1090 Returns 

1091 ------- 

1092 Optional[np.ndarray] 

1093 List of all controlled indices, or None if the circuit does not 

1094 contain controlled rotation gates. 

1095 """ 

1096 return None 

1097 

1098 @staticmethod 

1099 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1100 """ 

1101 Creates a Circuit1 ansatz. 

1102 

1103 Length of flattened vector must be n_qubits*2 

1104 

1105 Parameters 

1106 ---------- 

1107 w : np.ndarray 

1108 Weight vector of size n_qubits*2 

1109 n_qubits : int 

1110 Number of qubits 

1111 noise_params : Optional[Dict[str, float]], optional 

1112 Dictionary of noise parameters to apply to the gates 

1113 """ 

1114 w_idx = 0 

1115 for q in range(n_qubits): 

1116 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1117 w_idx += 1 

1118 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1119 w_idx += 1 

1120 

1121 class Circuit_2(Circuit): 

1122 @staticmethod 

1123 def n_params_per_layer(n_qubits: int) -> int: 

1124 """ 

1125 Returns the number of parameters per layer for Circuit_2. 

1126 

1127 The total number of parameters is determined by the number of qubits, with 

1128 each qubit contributing 2 parameters. 

1129 

1130 Parameters 

1131 ---------- 

1132 n_qubits : int 

1133 Number of qubits in the circuit 

1134 

1135 Returns 

1136 ------- 

1137 int 

1138 Number of parameters per layer 

1139 """ 

1140 return n_qubits * 2 

1141 

1142 @staticmethod 

1143 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1144 """ 

1145 No controlled rotation gates available. Always None. 

1146 

1147 Parameters 

1148 ---------- 

1149 n_qubits : int 

1150 Number of qubits in the circuit 

1151 

1152 Returns 

1153 ------- 

1154 Optional[np.ndarray] 

1155 List of all controlled indices, or None if the circuit does not 

1156 contain controlled rotation gates. 

1157 """ 

1158 return None 

1159 

1160 @staticmethod 

1161 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1162 """ 

1163 Creates a Circuit2 ansatz. 

1164 

1165 Length of flattened vector must be n_qubits*2 

1166 

1167 Parameters 

1168 ---------- 

1169 w : np.ndarray 

1170 Weight vector of size n_qubits*2 

1171 n_qubits : int 

1172 Number of qubits 

1173 noise_params : Optional[Dict[str, float]], optional 

1174 Dictionary of noise parameters to apply to the gates 

1175 """ 

1176 w_idx = 0 

1177 for q in range(n_qubits): 

1178 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1179 w_idx += 1 

1180 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1181 w_idx += 1 

1182 

1183 if n_qubits > 1: 

1184 for q in range(n_qubits - 1): 

1185 Gates.CX( 

1186 wires=[n_qubits - q - 1, n_qubits - q - 2], 

1187 noise_params=noise_params, 

1188 ) 

1189 

1190 class Circuit_3(Circuit): 

1191 @staticmethod 

1192 def n_params_per_layer(n_qubits: int) -> int: 

1193 """ 

1194 Calculates the number of parameters per layer for Circuit3. 

1195 

1196 The number of parameters per layer is given by the number of qubits, with 

1197 each qubit contributing 3 parameters. The last qubit only contributes 2 

1198 parameters because it is the target qubit for the controlled gates. 

1199 

1200 Parameters 

1201 ---------- 

1202 n_qubits : int 

1203 Number of qubits in the circuit 

1204 

1205 Returns 

1206 ------- 

1207 int 

1208 Number of parameters per layer 

1209 """ 

1210 return n_qubits * 3 - 1 

1211 

1212 @staticmethod 

1213 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1214 """ 

1215 No controlled rotation gates available. Always None. 

1216 

1217 Parameters 

1218 ---------- 

1219 n_qubits : int 

1220 Number of qubits in the circuit 

1221 

1222 Returns 

1223 ------- 

1224 Optional[np.ndarray] 

1225 List of all controlled indices, or None if the circuit does not 

1226 contain controlled rotation gates. 

1227 """ 

1228 return None 

1229 

1230 @staticmethod 

1231 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1232 """ 

1233 Creates a Circuit3 ansatz. 

1234 

1235 Length of flattened vector must be n_qubits*3-1 

1236 

1237 Parameters 

1238 ---------- 

1239 w : np.ndarray 

1240 Weight vector of size n_qubits*3-1 

1241 n_qubits : int 

1242 Number of qubits 

1243 noise_params : Optional[Dict[str, float]], optional 

1244 Dictionary of noise parameters to apply to the gates 

1245 """ 

1246 w_idx = 0 

1247 for q in range(n_qubits): 

1248 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1249 w_idx += 1 

1250 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1251 w_idx += 1 

1252 

1253 if n_qubits > 1: 

1254 for q in range(n_qubits - 1): 

1255 Gates.CRZ( 

1256 w[w_idx], 

1257 wires=[n_qubits - q - 1, n_qubits - q - 2], 

1258 noise_params=noise_params, 

1259 ) 

1260 w_idx += 1 

1261 

1262 class Circuit_4(Circuit): 

1263 @staticmethod 

1264 def n_params_per_layer(n_qubits: int) -> int: 

1265 """ 

1266 Returns the number of parameters per layer for the Circuit_4 ansatz. 

1267 

1268 The number of parameters is calculated as n_qubits*3-1. 

1269 

1270 Parameters 

1271 ---------- 

1272 n_qubits : int 

1273 Number of qubits in the circuit 

1274 

1275 Returns 

1276 ------- 

1277 int 

1278 Number of parameters per layer 

1279 """ 

1280 return n_qubits * 3 - 1 

1281 

1282 @staticmethod 

1283 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1284 """ 

1285 No controlled rotation gates available. Always None. 

1286 

1287 Parameters 

1288 ---------- 

1289 n_qubits : int 

1290 Number of qubits in the circuit 

1291 

1292 Returns 

1293 ------- 

1294 Optional[np.ndarray] 

1295 List of all controlled indices, or None if the circuit does not 

1296 contain controlled rotation gates. 

1297 """ 

1298 return None 

1299 

1300 @staticmethod 

1301 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1302 """ 

1303 Creates a Circuit4 ansatz. 

1304 

1305 Length of flattened vector must be n_qubits*3-1 

1306 

1307 Parameters 

1308 ---------- 

1309 w : np.ndarray 

1310 Weight vector of size n_qubits*3-1 

1311 n_qubits : int 

1312 Number of qubits 

1313 noise_params : Optional[Dict[str, float]], optional 

1314 Dictionary of noise parameters to apply to the gates 

1315 """ 

1316 w_idx = 0 

1317 for q in range(n_qubits): 

1318 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1319 w_idx += 1 

1320 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1321 w_idx += 1 

1322 

1323 if n_qubits > 1: 

1324 for q in range(n_qubits - 1): 

1325 Gates.CRX( 

1326 w[w_idx], 

1327 wires=[n_qubits - q - 1, n_qubits - q - 2], 

1328 noise_params=noise_params, 

1329 ) 

1330 w_idx += 1 

1331 

1332 class Circuit_10(Circuit): 

1333 @staticmethod 

1334 def n_params_per_layer(n_qubits: int) -> int: 

1335 """ 

1336 Returns the number of parameters per layer for the Circuit_10 ansatz. 

1337 

1338 The number of parameters is calculated as n_qubits*2. 

1339 

1340 Parameters 

1341 ---------- 

1342 n_qubits : int 

1343 Number of qubits in the circuit 

1344 

1345 Returns 

1346 ------- 

1347 int 

1348 Number of parameters per layer 

1349 """ 

1350 return n_qubits * 2 # constant gates not considered yet. has to be fixed 

1351 

1352 @staticmethod 

1353 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1354 """ 

1355 No controlled rotation gates available. Always None. 

1356 

1357 Parameters 

1358 ---------- 

1359 n_qubits : int 

1360 Number of qubits in the circuit 

1361 

1362 Returns 

1363 ------- 

1364 Optional[np.ndarray] 

1365 List of all controlled indices, or None if the circuit does not 

1366 contain controlled rotation gates. 

1367 """ 

1368 return None 

1369 

1370 @staticmethod 

1371 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1372 """ 

1373 Creates a Circuit10 ansatz. 

1374 

1375 Length of flattened vector must be n_qubits*2 

1376 

1377 Parameters 

1378 ---------- 

1379 w : np.ndarray 

1380 Weight vector of size n_qubits*2 

1381 n_qubits : int 

1382 Number of qubits 

1383 noise_params : Optional[Dict[str, float]], optional 

1384 Dictionary of noise parameters to apply to the gates 

1385 """ 

1386 w_idx = 0 

1387 # constant gates, independent of layers. has to be fixed 

1388 for q in range(n_qubits): 

1389 Gates.RY(w[w_idx], wires=q, noise_params=noise_params) 

1390 w_idx += 1 

1391 

1392 if n_qubits > 1: 

1393 for q in range(n_qubits - 1): 

1394 Gates.CZ( 

1395 wires=[ 

1396 (n_qubits - q - 2) % n_qubits, 

1397 (n_qubits - q - 1) % n_qubits, 

1398 ], 

1399 noise_params=noise_params, 

1400 ) 

1401 if n_qubits > 2: 

1402 Gates.CZ(wires=[n_qubits - 1, 0], noise_params=noise_params) 

1403 

1404 for q in range(n_qubits): 

1405 Gates.RY(w[w_idx], wires=q, noise_params=noise_params) 

1406 w_idx += 1 

1407 

1408 class Circuit_16(Circuit): 

1409 @staticmethod 

1410 def n_params_per_layer(n_qubits: int) -> int: 

1411 """ 

1412 Returns the number of parameters per layer for the Circuit_16 ansatz. 

1413 

1414 The number of parameters is calculated as n_qubits*3-1. 

1415 

1416 Parameters 

1417 ---------- 

1418 n_qubits : int 

1419 Number of qubits in the circuit 

1420 

1421 Returns 

1422 ------- 

1423 int 

1424 Number of parameters per layer 

1425 """ 

1426 

1427 return n_qubits * 3 - 1 

1428 

1429 @staticmethod 

1430 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1431 """ 

1432 No controlled rotation gates available. Always None. 

1433 

1434 Parameters 

1435 ---------- 

1436 n_qubits : int 

1437 Number of qubits in the circuit 

1438 

1439 Returns 

1440 ------- 

1441 Optional[np.ndarray] 

1442 List of all controlled indices, or None if the circuit does not 

1443 contain controlled rotation gates. 

1444 """ 

1445 return None 

1446 

1447 @staticmethod 

1448 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1449 """ 

1450 Creates a Circuit16 ansatz. 

1451 

1452 Length of flattened vector must be n_qubits*3-1 

1453 

1454 Parameters 

1455 ---------- 

1456 w : np.ndarray 

1457 Weight vector of size n_qubits*3-1 

1458 n_qubits : int 

1459 Number of qubits 

1460 noise_params : Optional[Dict[str, float]], optional 

1461 Dictionary of noise parameters to apply to the gates 

1462 """ 

1463 w_idx = 0 

1464 for q in range(n_qubits): 

1465 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1466 w_idx += 1 

1467 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1468 w_idx += 1 

1469 

1470 if n_qubits > 1: 

1471 for q in range(n_qubits // 2): 

1472 Gates.CRZ( 

1473 w[w_idx], 

1474 wires=[(2 * q + 1), (2 * q)], 

1475 noise_params=noise_params, 

1476 ) 

1477 w_idx += 1 

1478 

1479 for q in range((n_qubits - 1) // 2): 

1480 Gates.CRZ( 

1481 w[w_idx], 

1482 wires=[(2 * q + 2), (2 * q + 1)], 

1483 noise_params=noise_params, 

1484 ) 

1485 w_idx += 1 

1486 

1487 class Circuit_17(Circuit): 

1488 @staticmethod 

1489 def n_params_per_layer(n_qubits: int) -> int: 

1490 """ 

1491 Returns the number of parameters per layer for the Circuit_17 ansatz. 

1492 

1493 The number of parameters is calculated as n_qubits*3-1. 

1494 

1495 Parameters 

1496 ---------- 

1497 n_qubits : int 

1498 Number of qubits in the circuit 

1499 

1500 Returns 

1501 ------- 

1502 int 

1503 Number of parameters per layer 

1504 """ 

1505 

1506 return n_qubits * 3 - 1 

1507 

1508 @staticmethod 

1509 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1510 """ 

1511 No controlled rotation gates available. Always None. 

1512 

1513 Parameters 

1514 ---------- 

1515 n_qubits : int 

1516 Number of qubits in the circuit 

1517 

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 return None 

1525 

1526 @staticmethod 

1527 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1528 """ 

1529 Creates a Circuit17 ansatz. 

1530 

1531 Length of flattened vector must be n_qubits*3-1 

1532 

1533 Parameters 

1534 ---------- 

1535 w : np.ndarray 

1536 Weight vector of size n_qubits*3-1 

1537 n_qubits : int 

1538 Number of qubits 

1539 noise_params : Optional[Dict[str, float]], optional 

1540 Dictionary of noise parameters to apply to the gates 

1541 """ 

1542 w_idx = 0 

1543 for q in range(n_qubits): 

1544 Gates.RX(w[w_idx], wires=q, noise_params=noise_params) 

1545 w_idx += 1 

1546 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params) 

1547 w_idx += 1 

1548 

1549 if n_qubits > 1: 

1550 for q in range(n_qubits // 2): 

1551 Gates.CRX( 

1552 w[w_idx], 

1553 wires=[(2 * q + 1), (2 * q)], 

1554 noise_params=noise_params, 

1555 ) 

1556 w_idx += 1 

1557 

1558 for q in range((n_qubits - 1) // 2): 

1559 Gates.CRX( 

1560 w[w_idx], 

1561 wires=[(2 * q + 2), (2 * q + 1)], 

1562 noise_params=noise_params, 

1563 ) 

1564 w_idx += 1 

1565 

1566 class Strongly_Entangling(Circuit): 

1567 @staticmethod 

1568 def n_params_per_layer(n_qubits: int) -> int: 

1569 """ 

1570 Returns the number of parameters per layer for the 

1571 Strongly Entangling ansatz. 

1572 

1573 The number of parameters is calculated as n_qubits*6. 

1574 

1575 Parameters 

1576 ---------- 

1577 n_qubits : int 

1578 Number of qubits in the circuit 

1579 

1580 Returns 

1581 ------- 

1582 int 

1583 Number of parameters per layer 

1584 """ 

1585 if n_qubits < 2: 

1586 log.warning("Number of Qubits < 2, no entanglement available") 

1587 return n_qubits * 6 

1588 

1589 @staticmethod 

1590 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1591 """ 

1592 No controlled rotation gates available. Always None. 

1593 

1594 Parameters 

1595 ---------- 

1596 n_qubits : int 

1597 Number of qubits in the circuit 

1598 

1599 Returns 

1600 ------- 

1601 Optional[np.ndarray] 

1602 List of all controlled indices, or None if the circuit does not 

1603 contain controlled rotation gates. 

1604 """ 

1605 return None 

1606 

1607 @staticmethod 

1608 def build(w: np.ndarray, n_qubits: int, noise_params=None) -> None: 

1609 """ 

1610 Creates a Strongly Entangling ansatz. 

1611 

1612 Length of flattened vector must be n_qubits*6 

1613 

1614 Parameters 

1615 ---------- 

1616 w : np.ndarray 

1617 Weight vector of size n_qubits*6 

1618 n_qubits : int 

1619 Number of qubits 

1620 noise_params : Optional[Dict[str, float]], optional 

1621 Dictionary of noise parameters to apply to the gates 

1622 """ 

1623 w_idx = 0 

1624 for q in range(n_qubits): 

1625 Gates.Rot( 

1626 w[w_idx], 

1627 w[w_idx + 1], 

1628 w[w_idx + 2], 

1629 wires=q, 

1630 noise_params=noise_params, 

1631 ) 

1632 w_idx += 3 

1633 

1634 if n_qubits > 1: 

1635 for q in range(n_qubits): 

1636 Gates.CX(wires=[q, (q + 1) % n_qubits], noise_params=noise_params) 

1637 

1638 for q in range(n_qubits): 

1639 Gates.Rot( 

1640 w[w_idx], 

1641 w[w_idx + 1], 

1642 w[w_idx + 2], 

1643 wires=q, 

1644 noise_params=noise_params, 

1645 ) 

1646 w_idx += 3 

1647 

1648 if n_qubits > 1: 

1649 for q in range(n_qubits): 

1650 Gates.CX( 

1651 wires=[q, (q + n_qubits // 2) % n_qubits], 

1652 noise_params=noise_params, 

1653 ) 

1654 

1655 class No_Entangling(Circuit): 

1656 @staticmethod 

1657 def n_params_per_layer(n_qubits: int) -> int: 

1658 """ 

1659 Returns the number of parameters per layer for the NoEntangling ansatz. 

1660 

1661 The number of parameters is calculated as n_qubits*3. 

1662 

1663 Parameters 

1664 ---------- 

1665 n_qubits : int 

1666 Number of qubits in the circuit 

1667 

1668 Returns 

1669 ------- 

1670 int 

1671 Number of parameters per layer 

1672 """ 

1673 return n_qubits * 3 

1674 

1675 @staticmethod 

1676 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]: 

1677 """ 

1678 No controlled rotation gates available. Always None. 

1679 

1680 Parameters 

1681 ---------- 

1682 n_qubits : int 

1683 Number of qubits in the circuit 

1684 

1685 Returns 

1686 ------- 

1687 Optional[np.ndarray] 

1688 List of all controlled indices, or None if the circuit does not 

1689 contain controlled rotation gates. 

1690 """ 

1691 return None 

1692 

1693 @staticmethod 

1694 def build(w: np.ndarray, n_qubits: int, noise_params=None): 

1695 """ 

1696 Creates a circuit without entangling, but with U3 gates on all qubits 

1697 

1698 Length of flattened vector must be n_qubits*3 

1699 

1700 Parameters 

1701 ---------- 

1702 w : np.ndarray 

1703 Weight vector of size n_qubits*3 

1704 n_qubits : int 

1705 Number of qubits 

1706 noise_params : Optional[Dict[str, float]], optional 

1707 Dictionary of noise parameters to apply to the gates 

1708 """ 

1709 w_idx = 0 

1710 for q in range(n_qubits): 

1711 Gates.Rot( 

1712 w[w_idx], 

1713 w[w_idx + 1], 

1714 w[w_idx + 2], 

1715 wires=q, 

1716 noise_params=noise_params, 

1717 ) 

1718 w_idx += 3