Coverage for qml_essentials/ansaetze.py: 62%

478 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-04 21:13 +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 return 

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 return 

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 None 

63 

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

65 

66 @abstractmethod 

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

68 return 

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 def n_qubit_depolarizing_kraus(p: float, n: int) -> List[np.ndarray]: 

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

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

124 if n < 2: 

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

126 

127 Id = np.eye(2) 

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

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

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

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

132 

133 dim = 2 ** n 

134 all_ops = [] 

135 

136 # Generate all n-qubit Pauli tensor products: 

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

138 P = np.eye(1) 

139 for idx in indices: 

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

141 all_ops.append(P) 

142 

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

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

145 

146 kraus_ops = [] 

147 for i, P in enumerate(all_ops): 

148 if i == 0: 

149 # Skip the identity, already handled as K0 

150 continue 

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

152 

153 return [K0] + kraus_ops 

154 

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

156 

157 @staticmethod 

158 def Noise( 

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

160 ) -> None: 

161 """ 

162 Applies noise to the given wires. 

163 

164 Parameters 

165 ---------- 

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

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

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

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

170 supported: 

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

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

173 -Depolarizing: Applies a depolarizing channel error to the 

174 given wires. 

175 -MultiQubitDepolarizing: Applies a two-qubit depolarizing channel 

176 error to the given wires. 

177 

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

179 """ 

180 if noise_params is not None: 

181 if isinstance(wires, int): 

182 wires = [wires] # single qubit gate 

183 

184 # noise on single qubits 

185 for wire in wires: 

186 qml.BitFlip(noise_params.get("BitFlip", 0.0), wires=wire) 

187 qml.PhaseFlip(noise_params.get("PhaseFlip", 0.0), wires=wire) 

188 qml.DepolarizingChannel( 

189 noise_params.get("Depolarizing", 0.0), wires=wire 

190 ) 

191 

192 # noise on two-qubits 

193 if len(wires) > 1: 

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

195 if p > 0: 

196 Gates.NQubitDepolarizingChannel(p, wires) 

197 

198 @staticmethod 

199 def GateError( 

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

201 ) -> np.ndarray: 

202 """ 

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

204 

205 Parameters 

206 ---------- 

207 w : float 

208 The rotation angle in radians. 

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

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

211 supported: 

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

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

214 the "GateError" key in the dictionary. 

215 

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

217 

218 Returns 

219 ------- 

220 float 

221 The modified rotation angle after applying the gate error. 

222 """ 

223 if ( 

224 noise_params is not None 

225 and noise_params.get("GateError", None) is not None 

226 ): 

227 w += Gates.rng.normal(0, noise_params["GateError"]) 

228 return w 

229 

230 @staticmethod 

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

232 """ 

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

234 

235 Parameters 

236 ---------- 

237 phi : float 

238 The first rotation angle in radians. 

239 theta : float 

240 The second rotation angle in radians. 

241 omega : float 

242 The third rotation angle in radians. 

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

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

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

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

247 supported: 

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

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

250 -Depolarizing: Applies a depolarizing channel error to the 

251 given wires. 

252 

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

254 """ 

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

256 phi = Gates.GateError(phi, noise_params) 

257 theta = Gates.GateError(theta, noise_params) 

258 omega = Gates.GateError(omega, noise_params) 

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

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

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

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

263 Gates.Noise(wires, noise_params) 

264 

265 @staticmethod 

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

267 """ 

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

269 

270 Parameters 

271 ---------- 

272 w : float 

273 The rotation angle in radians. 

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

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

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

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

278 supported: 

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

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

281 -Depolarizing: Applies a depolarizing channel error to the 

282 given wires. 

283 

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

285 """ 

286 w = Gates.GateError(w, noise_params) 

287 qml.RX(w, wires=wires) 

288 Gates.Noise(wires, noise_params) 

289 

290 @staticmethod 

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

292 """ 

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

294 

295 Parameters 

296 ---------- 

297 w : float 

298 The rotation angle in radians. 

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

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

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

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

303 supported: 

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

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

306 -Depolarizing: Applies a depolarizing channel error to the 

307 given wires. 

308 

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

310 """ 

311 w = Gates.GateError(w, noise_params) 

312 qml.RY(w, wires=wires) 

313 Gates.Noise(wires, noise_params) 

314 

315 @staticmethod 

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

317 """ 

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

319 

320 Parameters 

321 ---------- 

322 w : float 

323 The rotation angle in radians. 

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

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

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

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

328 supported: 

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

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

331 -Depolarizing: Applies a depolarizing channel error to the 

332 given wires. 

333 

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

335 """ 

336 w = Gates.GateError(w, noise_params) 

337 qml.RZ(w, wires=wires) 

338 Gates.Noise(wires, noise_params) 

339 

340 @staticmethod 

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

342 """ 

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

344 and adds `Noise` 

345 

346 Parameters 

347 ---------- 

348 w : float 

349 The rotation angle in radians. 

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

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

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

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

354 supported: 

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

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

357 -Depolarizing: Applies a depolarizing channel error to the 

358 given wires. 

359 

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

361 """ 

362 w = Gates.GateError(w, noise_params) 

363 qml.CRX(w, wires=wires) 

364 Gates.Noise(wires, noise_params) 

365 

366 @staticmethod 

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

368 """ 

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

370 and adds `Noise` 

371 

372 Parameters 

373 ---------- 

374 w : float 

375 The rotation angle in radians. 

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

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

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

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

380 supported: 

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

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

383 -Depolarizing: Applies a depolarizing channel error to the 

384 given wires. 

385 

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

387 """ 

388 w = Gates.GateError(w, noise_params) 

389 qml.CRY(w, wires=wires) 

390 Gates.Noise(wires, noise_params) 

391 

392 @staticmethod 

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

394 """ 

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

396 and adds `Noise` 

397 

398 Parameters 

399 ---------- 

400 w : float 

401 The rotation angle in radians. 

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

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

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

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

406 supported: 

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

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

409 -Depolarizing: Applies a depolarizing channel error to the 

410 given wires. 

411 

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

413 """ 

414 w = Gates.GateError(w, noise_params) 

415 qml.CRZ(w, wires=wires) 

416 Gates.Noise(wires, noise_params) 

417 

418 @staticmethod 

419 def CX(wires, noise_params=None): 

420 """ 

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

422 

423 Parameters 

424 ---------- 

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

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

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

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

429 supported: 

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

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

432 -Depolarizing: Applies a depolarizing channel error to the 

433 given wires. 

434 

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

436 """ 

437 qml.CNOT(wires=wires) 

438 Gates.Noise(wires, noise_params) 

439 

440 @staticmethod 

441 def CY(wires, noise_params=None): 

442 """ 

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

444 

445 Parameters 

446 ---------- 

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

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

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

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

451 supported: 

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

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

454 -Depolarizing: Applies a depolarizing channel error to the 

455 given wires. 

456 

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

458 """ 

459 qml.CY(wires=wires) 

460 Gates.Noise(wires, noise_params) 

461 

462 @staticmethod 

463 def CZ(wires, noise_params=None): 

464 """ 

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

466 

467 Parameters 

468 ---------- 

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

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

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

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

473 supported: 

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

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

476 -Depolarizing: Applies a depolarizing channel error to the 

477 given wires. 

478 

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

480 """ 

481 qml.CZ(wires=wires) 

482 Gates.Noise(wires, noise_params) 

483 

484 @staticmethod 

485 def H(wires, noise_params=None): 

486 """ 

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

488 

489 Parameters 

490 ---------- 

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

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

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

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

495 supported: 

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

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

498 -Depolarizing: Applies a depolarizing channel error to the 

499 given wires. 

500 

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

502 """ 

503 qml.Hadamard(wires=wires) 

504 Gates.Noise(wires, noise_params) 

505 

506 

507class Ansaetze: 

508 

509 def get_available(): 

510 return [ 

511 Ansaetze.No_Ansatz, 

512 Ansaetze.Circuit_1, 

513 Ansaetze.Circuit_2, 

514 Ansaetze.Circuit_3, 

515 Ansaetze.Circuit_4, 

516 Ansaetze.Circuit_6, 

517 Ansaetze.Circuit_9, 

518 Ansaetze.Circuit_10, 

519 Ansaetze.Circuit_15, 

520 Ansaetze.Circuit_16, 

521 Ansaetze.Circuit_17, 

522 Ansaetze.Circuit_18, 

523 Ansaetze.Circuit_19, 

524 Ansaetze.No_Entangling, 

525 Ansaetze.Strongly_Entangling, 

526 Ansaetze.Hardware_Efficient, 

527 Ansaetze.GHZ, 

528 ] 

529 

530 class No_Ansatz(Circuit): 

531 @staticmethod 

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

533 return 0 

534 

535 @staticmethod 

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

537 return None 

538 

539 @staticmethod 

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

541 pass 

542 

543 class GHZ(Circuit): 

544 @staticmethod 

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

546 return 0 

547 

548 @staticmethod 

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

550 return None 

551 

552 @staticmethod 

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

554 Gates.H(0, noise_params=noise_params) 

555 

556 for q in range(n_qubits - 1): 

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

558 

559 class Hardware_Efficient(Circuit): 

560 @staticmethod 

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

562 """ 

563 Returns the number of parameters per layer for the 

564 Hardware Efficient Ansatz. 

565 

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

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

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

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

570 

571 Parameters 

572 ---------- 

573 n_qubits : int 

574 Number of qubits in the circuit 

575 

576 Returns 

577 ------- 

578 int 

579 Number of parameters required for one layer of the circuit 

580 """ 

581 if n_qubits < 2: 

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

583 return n_qubits * 3 

584 

585 @staticmethod 

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

587 """ 

588 No controlled rotation gates available. Always None. 

589 

590 Parameters 

591 ---------- 

592 n_qubits : int 

593 Number of qubits in the circuit 

594 

595 Returns 

596 ------- 

597 Optional[np.ndarray] 

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

599 contain controlled rotation gates. 

600 """ 

601 return None 

602 

603 @staticmethod 

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

605 """ 

606 Creates a Hardware-Efficient ansatz, as proposed in 

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

608 

609 Parameters 

610 ---------- 

611 w : np.ndarray 

612 Weight vector of size n_qubits*3 

613 n_qubits : int 

614 Number of qubits 

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

616 Dictionary of noise parameters to apply to the gates 

617 """ 

618 w_idx = 0 

619 for q in range(n_qubits): 

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

621 w_idx += 1 

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

623 w_idx += 1 

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

625 w_idx += 1 

626 

627 if n_qubits > 1: 

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

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

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

631 Gates.CX( 

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

633 ) 

634 if n_qubits > 2: 

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

636 

637 class Circuit_19(Circuit): 

638 @staticmethod 

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

640 """ 

641 Returns the number of parameters per layer for Circuit_19. 

642 

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

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

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

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

647 

648 Parameters 

649 ---------- 

650 n_qubits : int 

651 Number of qubits in the circuit 

652 

653 Returns 

654 ------- 

655 int 

656 Number of parameters required for one layer of the circuit 

657 """ 

658 

659 if n_qubits > 1: 

660 return n_qubits * 3 

661 else: 

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

663 return 2 

664 

665 @staticmethod 

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

667 """ 

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

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

670 [indices[0]:indices[1]:indices[2]] 

671 

672 Parameters 

673 ---------- 

674 n_qubits : int 

675 Number of qubits in the circuit 

676 

677 Returns 

678 ------- 

679 Optional[np.ndarray] 

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

681 contain controlled rotation gates. 

682 """ 

683 if n_qubits > 1: 

684 return [-n_qubits, None, None] 

685 else: 

686 return None 

687 

688 @staticmethod 

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

690 """ 

691 Creates a Circuit19 ansatz. 

692 

693 Length of flattened vector must be n_qubits*3 

694 because for >1 qubits there are three gates 

695 

696 Parameters 

697 ---------- 

698 w : np.ndarray 

699 Weight vector of size n_qubits*3 

700 n_qubits : int 

701 Number of qubits 

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

703 Dictionary of noise parameters to apply to the gates 

704 """ 

705 w_idx = 0 

706 for q in range(n_qubits): 

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

708 w_idx += 1 

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

710 w_idx += 1 

711 

712 if n_qubits > 1: 

713 for q in range(n_qubits): 

714 Gates.CRX( 

715 w[w_idx], 

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

717 noise_params=noise_params, 

718 ) 

719 w_idx += 1 

720 

721 class Circuit_18(Circuit): 

722 @staticmethod 

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

724 """ 

725 Returns the number of parameters per layer for Circuit_18. 

726 

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

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

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

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

731 

732 Parameters 

733 ---------- 

734 n_qubits : int 

735 Number of qubits in the circuit 

736 

737 Returns 

738 ------- 

739 int 

740 Number of parameters required for one layer of the circuit 

741 """ 

742 if n_qubits > 1: 

743 return n_qubits * 3 

744 else: 

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

746 return 2 

747 

748 @staticmethod 

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

750 """ 

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

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

753 [indices[0]:indices[1]:indices[2]] 

754 

755 Parameters 

756 ---------- 

757 n_qubits : int 

758 Number of qubits in the circuit 

759 

760 Returns 

761 ------- 

762 Optional[np.ndarray] 

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

764 contain controlled rotation gates. 

765 """ 

766 if n_qubits > 1: 

767 return [-n_qubits, None, None] 

768 else: 

769 return None 

770 

771 @staticmethod 

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

773 """ 

774 Creates a Circuit18 ansatz. 

775 

776 Length of flattened vector must be n_qubits*3 

777 

778 Parameters 

779 ---------- 

780 w : np.ndarray 

781 Weight vector of size n_qubits*3 

782 n_qubits : int 

783 Number of qubits 

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

785 Dictionary of noise parameters to apply to the gates 

786 """ 

787 w_idx = 0 

788 for q in range(n_qubits): 

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

790 w_idx += 1 

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

792 w_idx += 1 

793 

794 if n_qubits > 1: 

795 for q in range(n_qubits): 

796 Gates.CRZ( 

797 w[w_idx], 

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

799 noise_params=noise_params, 

800 ) 

801 w_idx += 1 

802 

803 class Circuit_15(Circuit): 

804 @staticmethod 

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

806 """ 

807 Returns the number of parameters per layer for Circuit_15. 

808 

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

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

811 

812 Parameters 

813 ---------- 

814 n_qubits : int 

815 Number of qubits in the circuit 

816 

817 Returns 

818 ------- 

819 int 

820 Number of parameters required for one layer of the circuit 

821 """ 

822 if n_qubits > 1: 

823 return n_qubits * 2 

824 else: 

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

826 return 2 

827 

828 @staticmethod 

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

830 """ 

831 No controlled rotation gates available. Always None. 

832 

833 Parameters 

834 ---------- 

835 n_qubits : int 

836 Number of qubits in the circuit 

837 

838 Returns 

839 ------- 

840 Optional[np.ndarray] 

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

842 contain controlled rotation gates. 

843 """ 

844 return None 

845 

846 @staticmethod 

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

848 """ 

849 Creates a Circuit15 ansatz. 

850 

851 Length of flattened vector must be n_qubits*2 

852 because for >1 qubits there are three gates 

853 

854 Parameters 

855 ---------- 

856 w : np.ndarray 

857 Weight vector of size n_qubits*2 

858 n_qubits : int 

859 Number of qubits 

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

861 Dictionary of noise parameters to apply to the gates 

862 """ 

863 w_idx = 0 

864 for q in range(n_qubits): 

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

866 w_idx += 1 

867 

868 if n_qubits > 1: 

869 for q in range(n_qubits): 

870 Gates.CX( 

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

872 noise_params=noise_params, 

873 ) 

874 

875 for q in range(n_qubits): 

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

877 w_idx += 1 

878 

879 if n_qubits > 1: 

880 for q in range(n_qubits): 

881 Gates.CX( 

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

883 noise_params=noise_params, 

884 ) 

885 

886 class Circuit_9(Circuit): 

887 @staticmethod 

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

889 """ 

890 Returns the number of parameters per layer for Circuit_9. 

891 

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

893 

894 Parameters 

895 ---------- 

896 n_qubits : int 

897 Number of qubits in the circuit 

898 

899 Returns 

900 ------- 

901 int 

902 Number of parameters required for one layer of the circuit 

903 """ 

904 return n_qubits 

905 

906 @staticmethod 

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

908 """ 

909 No controlled rotation gates available. Always None. 

910 

911 Parameters 

912 ---------- 

913 n_qubits : int 

914 Number of qubits in the circuit 

915 

916 Returns 

917 ------- 

918 Optional[np.ndarray] 

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

920 contain controlled rotation gates. 

921 """ 

922 return None 

923 

924 @staticmethod 

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

926 """ 

927 Creates a Circuit9 ansatz. 

928 

929 Length of flattened vector must be n_qubits 

930 

931 Parameters 

932 ---------- 

933 w : np.ndarray 

934 Weight vector of size n_qubits 

935 n_qubits : int 

936 Number of qubits 

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

938 Dictionary of noise parameters to apply to the gates 

939 """ 

940 w_idx = 0 

941 for q in range(n_qubits): 

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

943 

944 if n_qubits > 1: 

945 for q in range(n_qubits - 1): 

946 Gates.CZ( 

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

948 noise_params=noise_params, 

949 ) 

950 

951 for q in range(n_qubits): 

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

953 w_idx += 1 

954 

955 class Circuit_6(Circuit): 

956 @staticmethod 

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

958 """ 

959 Returns the number of parameters per layer for Circuit_6. 

960 

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

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

963 n_qubits**2. 

964 

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

966 since no entanglement is possible. 

967 

968 Parameters 

969 ---------- 

970 n_qubits : int 

971 Number of qubits 

972 

973 Returns 

974 ------- 

975 int 

976 Number of parameters per layer 

977 """ 

978 if n_qubits > 1: 

979 return n_qubits * 3 + n_qubits**2 

980 else: 

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

982 return 4 

983 

984 @staticmethod 

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

986 """ 

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

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

989 [indices[0]:indices[1]:indices[2]] 

990 

991 Parameters 

992 ---------- 

993 n_qubits : int 

994 Number of qubits in the circuit 

995 

996 Returns 

997 ------- 

998 Optional[np.ndarray] 

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

1000 contain controlled rotation gates. 

1001 """ 

1002 if n_qubits > 1: 

1003 return [-n_qubits, None, None] 

1004 else: 

1005 return None 

1006 

1007 @staticmethod 

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

1009 """ 

1010 Creates a Circuit6 ansatz. 

1011 

1012 Length of flattened vector must be 

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

1014 n_qubits*3+n_qubits**2 

1015 

1016 Parameters 

1017 ---------- 

1018 w : np.ndarray 

1019 Weight vector of size 

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

1021 n_qubits : int 

1022 Number of qubits 

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

1024 Dictionary of noise parameters to apply to the gates 

1025 """ 

1026 w_idx = 0 

1027 for q in range(n_qubits): 

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

1029 w_idx += 1 

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

1031 w_idx += 1 

1032 

1033 if n_qubits > 1: 

1034 for ql in range(n_qubits): 

1035 for q in range(n_qubits): 

1036 if q == ql: 

1037 continue 

1038 Gates.CRX( 

1039 w[w_idx], 

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

1041 noise_params=noise_params, 

1042 ) 

1043 w_idx += 1 

1044 

1045 for q in range(n_qubits): 

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

1047 w_idx += 1 

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

1049 w_idx += 1 

1050 

1051 class Circuit_1(Circuit): 

1052 @staticmethod 

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

1054 """ 

1055 Returns the number of parameters per layer for Circuit_1. 

1056 

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

1058 each qubit contributing 2 parameters. 

1059 

1060 Parameters 

1061 ---------- 

1062 n_qubits : int 

1063 Number of qubits in the circuit 

1064 

1065 Returns 

1066 ------- 

1067 int 

1068 Number of parameters per layer 

1069 """ 

1070 return n_qubits * 2 

1071 

1072 @staticmethod 

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

1074 """ 

1075 No controlled rotation gates available. Always None. 

1076 

1077 Parameters 

1078 ---------- 

1079 n_qubits : int 

1080 Number of qubits in the circuit 

1081 

1082 Returns 

1083 ------- 

1084 Optional[np.ndarray] 

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

1086 contain controlled rotation gates. 

1087 """ 

1088 return None 

1089 

1090 @staticmethod 

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

1092 """ 

1093 Creates a Circuit1 ansatz. 

1094 

1095 Length of flattened vector must be n_qubits*2 

1096 

1097 Parameters 

1098 ---------- 

1099 w : np.ndarray 

1100 Weight vector of size n_qubits*2 

1101 n_qubits : int 

1102 Number of qubits 

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

1104 Dictionary of noise parameters to apply to the gates 

1105 """ 

1106 w_idx = 0 

1107 for q in range(n_qubits): 

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

1109 w_idx += 1 

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

1111 w_idx += 1 

1112 

1113 class Circuit_2(Circuit): 

1114 @staticmethod 

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

1116 """ 

1117 Returns the number of parameters per layer for Circuit_2. 

1118 

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

1120 each qubit contributing 2 parameters. 

1121 

1122 Parameters 

1123 ---------- 

1124 n_qubits : int 

1125 Number of qubits in the circuit 

1126 

1127 Returns 

1128 ------- 

1129 int 

1130 Number of parameters per layer 

1131 """ 

1132 return n_qubits * 2 

1133 

1134 @staticmethod 

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

1136 """ 

1137 No controlled rotation gates available. Always None. 

1138 

1139 Parameters 

1140 ---------- 

1141 n_qubits : int 

1142 Number of qubits in the circuit 

1143 

1144 Returns 

1145 ------- 

1146 Optional[np.ndarray] 

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

1148 contain controlled rotation gates. 

1149 """ 

1150 return None 

1151 

1152 @staticmethod 

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

1154 """ 

1155 Creates a Circuit2 ansatz. 

1156 

1157 Length of flattened vector must be n_qubits*2 

1158 

1159 Parameters 

1160 ---------- 

1161 w : np.ndarray 

1162 Weight vector of size n_qubits*2 

1163 n_qubits : int 

1164 Number of qubits 

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

1166 Dictionary of noise parameters to apply to the gates 

1167 """ 

1168 w_idx = 0 

1169 for q in range(n_qubits): 

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

1171 w_idx += 1 

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

1173 w_idx += 1 

1174 

1175 if n_qubits > 1: 

1176 for q in range(n_qubits - 1): 

1177 Gates.CX( 

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

1179 noise_params=noise_params, 

1180 ) 

1181 

1182 class Circuit_3(Circuit): 

1183 @staticmethod 

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

1185 """ 

1186 Calculates the number of parameters per layer for Circuit3. 

1187 

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

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

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

1191 

1192 Parameters 

1193 ---------- 

1194 n_qubits : int 

1195 Number of qubits in the circuit 

1196 

1197 Returns 

1198 ------- 

1199 int 

1200 Number of parameters per layer 

1201 """ 

1202 return n_qubits * 3 - 1 

1203 

1204 @staticmethod 

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

1206 """ 

1207 No controlled rotation gates available. Always None. 

1208 

1209 Parameters 

1210 ---------- 

1211 n_qubits : int 

1212 Number of qubits in the circuit 

1213 

1214 Returns 

1215 ------- 

1216 Optional[np.ndarray] 

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

1218 contain controlled rotation gates. 

1219 """ 

1220 return None 

1221 

1222 @staticmethod 

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

1224 """ 

1225 Creates a Circuit3 ansatz. 

1226 

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

1228 

1229 Parameters 

1230 ---------- 

1231 w : np.ndarray 

1232 Weight vector of size n_qubits*3-1 

1233 n_qubits : int 

1234 Number of qubits 

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

1236 Dictionary of noise parameters to apply to the gates 

1237 """ 

1238 w_idx = 0 

1239 for q in range(n_qubits): 

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

1241 w_idx += 1 

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

1243 w_idx += 1 

1244 

1245 if n_qubits > 1: 

1246 for q in range(n_qubits - 1): 

1247 Gates.CRZ( 

1248 w[w_idx], 

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

1250 noise_params=noise_params, 

1251 ) 

1252 w_idx += 1 

1253 

1254 class Circuit_4(Circuit): 

1255 @staticmethod 

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

1257 """ 

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

1259 

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

1261 

1262 Parameters 

1263 ---------- 

1264 n_qubits : int 

1265 Number of qubits in the circuit 

1266 

1267 Returns 

1268 ------- 

1269 int 

1270 Number of parameters per layer 

1271 """ 

1272 return n_qubits * 3 - 1 

1273 

1274 @staticmethod 

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

1276 """ 

1277 No controlled rotation gates available. Always None. 

1278 

1279 Parameters 

1280 ---------- 

1281 n_qubits : int 

1282 Number of qubits in the circuit 

1283 

1284 Returns 

1285 ------- 

1286 Optional[np.ndarray] 

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

1288 contain controlled rotation gates. 

1289 """ 

1290 return None 

1291 

1292 @staticmethod 

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

1294 """ 

1295 Creates a Circuit4 ansatz. 

1296 

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

1298 

1299 Parameters 

1300 ---------- 

1301 w : np.ndarray 

1302 Weight vector of size n_qubits*3-1 

1303 n_qubits : int 

1304 Number of qubits 

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

1306 Dictionary of noise parameters to apply to the gates 

1307 """ 

1308 w_idx = 0 

1309 for q in range(n_qubits): 

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

1311 w_idx += 1 

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

1313 w_idx += 1 

1314 

1315 if n_qubits > 1: 

1316 for q in range(n_qubits - 1): 

1317 Gates.CRX( 

1318 w[w_idx], 

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

1320 noise_params=noise_params, 

1321 ) 

1322 w_idx += 1 

1323 

1324 class Circuit_10(Circuit): 

1325 @staticmethod 

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

1327 """ 

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

1329 

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

1331 

1332 Parameters 

1333 ---------- 

1334 n_qubits : int 

1335 Number of qubits in the circuit 

1336 

1337 Returns 

1338 ------- 

1339 int 

1340 Number of parameters per layer 

1341 """ 

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

1343 

1344 @staticmethod 

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

1346 """ 

1347 No controlled rotation gates available. Always None. 

1348 

1349 Parameters 

1350 ---------- 

1351 n_qubits : int 

1352 Number of qubits in the circuit 

1353 

1354 Returns 

1355 ------- 

1356 Optional[np.ndarray] 

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

1358 contain controlled rotation gates. 

1359 """ 

1360 return None 

1361 

1362 @staticmethod 

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

1364 """ 

1365 Creates a Circuit10 ansatz. 

1366 

1367 Length of flattened vector must be n_qubits*2 

1368 

1369 Parameters 

1370 ---------- 

1371 w : np.ndarray 

1372 Weight vector of size n_qubits*2 

1373 n_qubits : int 

1374 Number of qubits 

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

1376 Dictionary of noise parameters to apply to the gates 

1377 """ 

1378 w_idx = 0 

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

1380 for q in range(n_qubits): 

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

1382 w_idx += 1 

1383 

1384 if n_qubits > 1: 

1385 for q in range(n_qubits - 1): 

1386 Gates.CZ( 

1387 wires=[ 

1388 (n_qubits - q - 2) % n_qubits, 

1389 (n_qubits - q - 1) % n_qubits, 

1390 ], 

1391 noise_params=noise_params, 

1392 ) 

1393 if n_qubits > 2: 

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

1395 

1396 for q in range(n_qubits): 

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

1398 w_idx += 1 

1399 

1400 class Circuit_16(Circuit): 

1401 @staticmethod 

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

1403 """ 

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

1405 

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

1407 

1408 Parameters 

1409 ---------- 

1410 n_qubits : int 

1411 Number of qubits in the circuit 

1412 

1413 Returns 

1414 ------- 

1415 int 

1416 Number of parameters per layer 

1417 """ 

1418 

1419 return n_qubits * 3 - 1 

1420 

1421 @staticmethod 

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

1423 """ 

1424 No controlled rotation gates available. Always None. 

1425 

1426 Parameters 

1427 ---------- 

1428 n_qubits : int 

1429 Number of qubits in the circuit 

1430 

1431 Returns 

1432 ------- 

1433 Optional[np.ndarray] 

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

1435 contain controlled rotation gates. 

1436 """ 

1437 return None 

1438 

1439 @staticmethod 

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

1441 """ 

1442 Creates a Circuit16 ansatz. 

1443 

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

1445 

1446 Parameters 

1447 ---------- 

1448 w : np.ndarray 

1449 Weight vector of size n_qubits*3-1 

1450 n_qubits : int 

1451 Number of qubits 

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

1453 Dictionary of noise parameters to apply to the gates 

1454 """ 

1455 w_idx = 0 

1456 for q in range(n_qubits): 

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

1458 w_idx += 1 

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

1460 w_idx += 1 

1461 

1462 if n_qubits > 1: 

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

1464 Gates.CRZ( 

1465 w[w_idx], 

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

1467 noise_params=noise_params, 

1468 ) 

1469 w_idx += 1 

1470 

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

1472 Gates.CRZ( 

1473 w[w_idx], 

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

1475 noise_params=noise_params, 

1476 ) 

1477 w_idx += 1 

1478 

1479 class Circuit_17(Circuit): 

1480 @staticmethod 

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

1482 """ 

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

1484 

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

1486 

1487 Parameters 

1488 ---------- 

1489 n_qubits : int 

1490 Number of qubits in the circuit 

1491 

1492 Returns 

1493 ------- 

1494 int 

1495 Number of parameters per layer 

1496 """ 

1497 

1498 return n_qubits * 3 - 1 

1499 

1500 @staticmethod 

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

1502 """ 

1503 No controlled rotation gates available. Always None. 

1504 

1505 Parameters 

1506 ---------- 

1507 n_qubits : int 

1508 Number of qubits in the circuit 

1509 

1510 Returns 

1511 ------- 

1512 Optional[np.ndarray] 

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

1514 contain controlled rotation gates. 

1515 """ 

1516 return None 

1517 

1518 @staticmethod 

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

1520 """ 

1521 Creates a Circuit17 ansatz. 

1522 

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

1524 

1525 Parameters 

1526 ---------- 

1527 w : np.ndarray 

1528 Weight vector of size n_qubits*3-1 

1529 n_qubits : int 

1530 Number of qubits 

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

1532 Dictionary of noise parameters to apply to the gates 

1533 """ 

1534 w_idx = 0 

1535 for q in range(n_qubits): 

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

1537 w_idx += 1 

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

1539 w_idx += 1 

1540 

1541 if n_qubits > 1: 

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

1543 Gates.CRX( 

1544 w[w_idx], 

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

1546 noise_params=noise_params, 

1547 ) 

1548 w_idx += 1 

1549 

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

1551 Gates.CRX( 

1552 w[w_idx], 

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

1554 noise_params=noise_params, 

1555 ) 

1556 w_idx += 1 

1557 

1558 class Strongly_Entangling(Circuit): 

1559 @staticmethod 

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

1561 """ 

1562 Returns the number of parameters per layer for the 

1563 Strongly Entangling ansatz. 

1564 

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

1566 

1567 Parameters 

1568 ---------- 

1569 n_qubits : int 

1570 Number of qubits in the circuit 

1571 

1572 Returns 

1573 ------- 

1574 int 

1575 Number of parameters per layer 

1576 """ 

1577 if n_qubits < 2: 

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

1579 return n_qubits * 6 

1580 

1581 @staticmethod 

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

1583 """ 

1584 No controlled rotation gates available. Always None. 

1585 

1586 Parameters 

1587 ---------- 

1588 n_qubits : int 

1589 Number of qubits in the circuit 

1590 

1591 Returns 

1592 ------- 

1593 Optional[np.ndarray] 

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

1595 contain controlled rotation gates. 

1596 """ 

1597 return None 

1598 

1599 @staticmethod 

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

1601 """ 

1602 Creates a Strongly Entangling ansatz. 

1603 

1604 Length of flattened vector must be n_qubits*6 

1605 

1606 Parameters 

1607 ---------- 

1608 w : np.ndarray 

1609 Weight vector of size n_qubits*6 

1610 n_qubits : int 

1611 Number of qubits 

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

1613 Dictionary of noise parameters to apply to the gates 

1614 """ 

1615 w_idx = 0 

1616 for q in range(n_qubits): 

1617 Gates.Rot( 

1618 w[w_idx], 

1619 w[w_idx + 1], 

1620 w[w_idx + 2], 

1621 wires=q, 

1622 noise_params=noise_params, 

1623 ) 

1624 w_idx += 3 

1625 

1626 if n_qubits > 1: 

1627 for q in range(n_qubits): 

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

1629 

1630 for q in range(n_qubits): 

1631 Gates.Rot( 

1632 w[w_idx], 

1633 w[w_idx + 1], 

1634 w[w_idx + 2], 

1635 wires=q, 

1636 noise_params=noise_params, 

1637 ) 

1638 w_idx += 3 

1639 

1640 if n_qubits > 1: 

1641 for q in range(n_qubits): 

1642 Gates.CX( 

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

1644 noise_params=noise_params, 

1645 ) 

1646 

1647 class No_Entangling(Circuit): 

1648 @staticmethod 

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

1650 """ 

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

1652 

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

1654 

1655 Parameters 

1656 ---------- 

1657 n_qubits : int 

1658 Number of qubits in the circuit 

1659 

1660 Returns 

1661 ------- 

1662 int 

1663 Number of parameters per layer 

1664 """ 

1665 return n_qubits * 3 

1666 

1667 @staticmethod 

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

1669 """ 

1670 No controlled rotation gates available. Always None. 

1671 

1672 Parameters 

1673 ---------- 

1674 n_qubits : int 

1675 Number of qubits in the circuit 

1676 

1677 Returns 

1678 ------- 

1679 Optional[np.ndarray] 

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

1681 contain controlled rotation gates. 

1682 """ 

1683 return None 

1684 

1685 @staticmethod 

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

1687 """ 

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

1689 

1690 Length of flattened vector must be n_qubits*3 

1691 

1692 Parameters 

1693 ---------- 

1694 w : np.ndarray 

1695 Weight vector of size n_qubits*3 

1696 n_qubits : int 

1697 Number of qubits 

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

1699 Dictionary of noise parameters to apply to the gates 

1700 """ 

1701 w_idx = 0 

1702 for q in range(n_qubits): 

1703 Gates.Rot( 

1704 w[w_idx], 

1705 w[w_idx + 1], 

1706 w[w_idx + 2], 

1707 wires=q, 

1708 noise_params=noise_params, 

1709 ) 

1710 w_idx += 3