Coverage for qml_essentials/ansaetze.py: 96%

908 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-10-02 13:10 +0000

1from abc import ABC, abstractmethod 

2from typing import Any, Optional, List, Union, Dict 

3import numbers 

4import pennylane.numpy as np 

5import pennylane as qml 

6import jax 

7from jax import numpy as jnp 

8import itertools 

9from contextlib import contextmanager 

10import logging 

11 

12jax.config.update("jax_enable_x64", True) 

13log = logging.getLogger(__name__) 

14 

15 

16class Circuit(ABC): 

17 def __init__(self): 

18 pass 

19 

20 @abstractmethod 

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

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

23 

24 def n_pulse_params_per_layer(n_qubits: int) -> int: 

25 """ 

26 Return the number of pulse parameters per layer. 

27 

28 Subclasses that do not use pulse-level simulation do not need to override this. 

29 If called and not overridden, this will raise NotImplementedError. 

30 """ 

31 raise NotImplementedError("n_pulse_params_per_layer method is not implemented") 

32 

33 @abstractmethod 

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

35 """ 

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

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

38 [indices[0]:indices[1]:indices[2]] 

39 

40 Parameters 

41 ---------- 

42 n_qubits : int 

43 Number of qubits in the circuit 

44 

45 Returns 

46 ------- 

47 Optional[np.ndarray] 

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

49 contain controlled rotation gates. 

50 """ 

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

52 

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

54 """ 

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

56 all parameters for one layer. 

57 

58 Parameters 

59 ---------- 

60 w : np.ndarray 

61 List of parameters for one layer 

62 n_qubits : int 

63 Number of qubits in the circuit 

64 

65 Returns 

66 ------- 

67 Optional[np.ndarray] 

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

69 contain controlled rotation gates. 

70 """ 

71 indices = self.get_control_indices(n_qubits) 

72 if indices is None: 

73 return np.array([]) 

74 

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

76 

77 def _build(self, w: np.ndarray, n_qubits: int, **kwargs): 

78 """ 

79 Builds one layer of the circuit using either unitary or pulse-level parameters. 

80 

81 Parameters 

82 ---------- 

83 w : np.ndarray 

84 Array of parameters for the current layer. 

85 n_qubits : int 

86 Number of qubits in the circuit. 

87 **kwargs 

88 Additional keyword arguments. Supports: 

89 - gate_mode : str, optional 

90 "unitary" (default) or "pulse" to use pulse-level simulation. 

91 - pulse_params : jnp.ndarray, optional 

92 Array of pulse parameters to use if gate_mode="pulse". 

93 - noise_params : dict, optional 

94 Dictionary of noise parameters. 

95 

96 Raises 

97 ------ 

98 ValueError 

99 If the number of provided pulse parameters does not match the expected 

100 number per layer. 

101 """ 

102 gate_mode = kwargs.get("gate_mode", "unitary") 

103 

104 if gate_mode == "pulse" and "pulse_params" in kwargs: 

105 pulse_params_per_layer = self.n_pulse_params_per_layer(n_qubits) 

106 

107 if len(kwargs["pulse_params"]) != pulse_params_per_layer: 

108 raise ValueError( 

109 f"Pulse params length {len(kwargs['pulse_params'])} " 

110 f"does not match expected {pulse_params_per_layer} " 

111 f"for {n_qubits} qubits" 

112 ) 

113 

114 with Gates.pulse_manager_context(kwargs["pulse_params"]): 

115 return self.build(w, n_qubits, **kwargs) 

116 else: 

117 return self.build(w, n_qubits, **kwargs) 

118 

119 @abstractmethod 

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

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

122 

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

124 self._build(*args, **kwds) 

125 

126 

127class UnitaryGates: 

128 rng = np.random.default_rng() 

129 batch_gate_error = True 

130 

131 @staticmethod 

132 def init_rng(seed: int): 

133 """ 

134 Initializes the random number generator with the given seed. 

135 

136 Parameters 

137 ---------- 

138 seed : int 

139 The seed for the random number generator. 

140 """ 

141 UnitaryGates.rng = np.random.default_rng(seed) 

142 

143 @staticmethod 

144 def NQubitDepolarizingChannel(p, wires): 

145 """ 

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

147 

148 The n-qubit depolarizing channel is defined as: 

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

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

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

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

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

154 

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

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

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

158 

159 Parameters 

160 ---------- 

161 p : float 

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

163 Must satisfy 0 ≤ p ≤ 1. 

164 

165 wires : Sequence[int] 

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

167 Must contain at least 2 qubits. 

168 

169 Returns 

170 ------- 

171 qml.QubitChannel 

172 A PennyLane QubitChannel constructed from the Kraus operators representing 

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

174 """ 

175 

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

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

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

179 if n < 2: 

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

181 

182 Id = np.eye(2) 

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

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

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

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

187 

188 dim = 2**n 

189 all_ops = [] 

190 

191 # Generate all n-qubit Pauli tensor products: 

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

193 P = np.eye(1) 

194 for idx in indices: 

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

196 all_ops.append(P) 

197 

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

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

200 

201 kraus_ops = [] 

202 for i, P in enumerate(all_ops): 

203 if i == 0: 

204 # Skip the identity, already handled as K0 

205 continue 

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

207 

208 return [K0] + kraus_ops 

209 

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

211 

212 @staticmethod 

213 def Noise( 

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

215 ) -> None: 

216 """ 

217 Applies noise to the given wires. 

218 

219 Parameters 

220 ---------- 

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

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

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

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

225 supported: 

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

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

228 -Depolarizing: Applies a depolarizing channel error to the 

229 given wires. 

230 -MultiQubitDepolarizing: Applies a two-qubit depolarizing channel 

231 error to the given wires. 

232 

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

234 """ 

235 if noise_params is not None: 

236 if isinstance(wires, int): 

237 wires = [wires] # single qubit gate 

238 

239 # noise on single qubits 

240 for wire in wires: 

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

242 if bf > 0: 

243 qml.BitFlip(bf, wires=wire) 

244 

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

246 if pf > 0: 

247 qml.PhaseFlip(pf, wires=wire) 

248 

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

250 if dp > 0: 

251 qml.DepolarizingChannel(dp, wires=wire) 

252 

253 # noise on two-qubits 

254 if len(wires) > 1: 

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

256 if p > 0: 

257 UnitaryGates.NQubitDepolarizingChannel(p, wires) 

258 

259 @staticmethod 

260 def GateError( 

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

262 ) -> np.ndarray: 

263 """ 

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

265 

266 Parameters 

267 ---------- 

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

269 The rotation angle in radians. 

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

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

272 supported: 

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

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

275 the "GateError" key in the dictionary. 

276 

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

278 

279 Returns 

280 ------- 

281 float 

282 The modified rotation angle after applying the gate error. 

283 """ 

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

285 w += UnitaryGates.rng.normal( 

286 0, 

287 noise_params["GateError"], 

288 ( 

289 w.shape 

290 if isinstance(w, np.ndarray) and UnitaryGates.batch_gate_error 

291 else None 

292 ), 

293 ) 

294 return w 

295 

296 @staticmethod 

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

298 """ 

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

300 

301 Parameters 

302 ---------- 

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

304 The first rotation angle in radians. 

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

306 The second rotation angle in radians. 

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

308 The third rotation angle in radians. 

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

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

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

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

313 supported: 

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

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

316 -Depolarizing: Applies a depolarizing channel error to the 

317 given wires. 

318 

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

320 """ 

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

322 phi = UnitaryGates.GateError(phi, noise_params) 

323 theta = UnitaryGates.GateError(theta, noise_params) 

324 omega = UnitaryGates.GateError(omega, noise_params) 

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

326 UnitaryGates.Noise(wires, noise_params) 

327 

328 @staticmethod 

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

330 """ 

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

332 

333 Parameters 

334 ---------- 

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

336 The rotation angle in radians. 

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

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

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

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

341 supported: 

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

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

344 -Depolarizing: Applies a depolarizing channel error to the 

345 given wires. 

346 

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

348 """ 

349 w = UnitaryGates.GateError(w, noise_params) 

350 qml.RX(w, wires=wires) 

351 UnitaryGates.Noise(wires, noise_params) 

352 

353 @staticmethod 

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

355 """ 

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

357 

358 Parameters 

359 ---------- 

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

361 The rotation angle in radians. 

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

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

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

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

366 supported: 

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

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

369 -Depolarizing: Applies a depolarizing channel error to the 

370 given wires. 

371 

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

373 """ 

374 w = UnitaryGates.GateError(w, noise_params) 

375 qml.RY(w, wires=wires) 

376 UnitaryGates.Noise(wires, noise_params) 

377 

378 @staticmethod 

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

380 """ 

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

382 

383 Parameters 

384 ---------- 

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

386 The rotation angle in radians. 

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

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

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

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

391 supported: 

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

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

394 -Depolarizing: Applies a depolarizing channel error to the 

395 given wires. 

396 

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

398 """ 

399 w = UnitaryGates.GateError(w, noise_params) 

400 qml.RZ(w, wires=wires) 

401 UnitaryGates.Noise(wires, noise_params) 

402 

403 @staticmethod 

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

405 """ 

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

407 and adds `Noise` 

408 

409 Parameters 

410 ---------- 

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

412 The rotation angle in radians. 

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

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

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

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

417 supported: 

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

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

420 -Depolarizing: Applies a depolarizing channel error to the 

421 given wires. 

422 

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

424 """ 

425 w = UnitaryGates.GateError(w, noise_params) 

426 qml.CRX(w, wires=wires) 

427 UnitaryGates.Noise(wires, noise_params) 

428 

429 @staticmethod 

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

431 """ 

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

433 and adds `Noise` 

434 

435 Parameters 

436 ---------- 

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

438 The rotation angle in radians. 

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

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

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

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

443 supported: 

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

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

446 -Depolarizing: Applies a depolarizing channel error to the 

447 given wires. 

448 

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

450 """ 

451 w = UnitaryGates.GateError(w, noise_params) 

452 qml.CRY(w, wires=wires) 

453 UnitaryGates.Noise(wires, noise_params) 

454 

455 @staticmethod 

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

457 """ 

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

459 and adds `Noise` 

460 

461 Parameters 

462 ---------- 

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

464 The rotation angle in radians. 

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

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

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

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

469 supported: 

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

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

472 -Depolarizing: Applies a depolarizing channel error to the 

473 given wires. 

474 

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

476 """ 

477 w = UnitaryGates.GateError(w, noise_params) 

478 qml.CRZ(w, wires=wires) 

479 UnitaryGates.Noise(wires, noise_params) 

480 

481 @staticmethod 

482 def CX(wires, noise_params=None): 

483 """ 

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

485 

486 Parameters 

487 ---------- 

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

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

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

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

492 supported: 

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

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

495 -Depolarizing: Applies a depolarizing channel error to the 

496 given wires. 

497 

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

499 """ 

500 qml.CNOT(wires=wires) 

501 UnitaryGates.Noise(wires, noise_params) 

502 

503 @staticmethod 

504 def CY(wires, noise_params=None): 

505 """ 

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

507 

508 Parameters 

509 ---------- 

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

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

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

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

514 supported: 

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

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

517 -Depolarizing: Applies a depolarizing channel error to the 

518 given wires. 

519 

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

521 """ 

522 qml.CY(wires=wires) 

523 UnitaryGates.Noise(wires, noise_params) 

524 

525 @staticmethod 

526 def CZ(wires, noise_params=None): 

527 """ 

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

529 

530 Parameters 

531 ---------- 

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

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

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

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

536 supported: 

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

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

539 -Depolarizing: Applies a depolarizing channel error to the 

540 given wires. 

541 

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

543 """ 

544 qml.CZ(wires=wires) 

545 UnitaryGates.Noise(wires, noise_params) 

546 

547 @staticmethod 

548 def H(wires, noise_params=None): 

549 """ 

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

551 

552 Parameters 

553 ---------- 

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

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

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

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

558 supported: 

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

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

561 -Depolarizing: Applies a depolarizing channel error to the 

562 given wires. 

563 

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

565 """ 

566 qml.Hadamard(wires=wires) 

567 UnitaryGates.Noise(wires, noise_params) 

568 

569 

570class PulseInformation: 

571 """ 

572 Stores pulse parameter counts and optimized pulse parameters for quantum gates. 

573 """ 

574 

575 PULSE_PARAM_COUNTS: Dict[str, int] = {"RX": 3, "RY": 3, "RZ": 1, "CZ": 1, "H": 3} 

576 PULSE_PARAM_COUNTS["Rot"] = 2 * PULSE_PARAM_COUNTS["RZ"] + PULSE_PARAM_COUNTS["RY"] 

577 PULSE_PARAM_COUNTS["CX"] = 2 * PULSE_PARAM_COUNTS["H"] + PULSE_PARAM_COUNTS["CZ"] 

578 PULSE_PARAM_COUNTS["CY"] = 2 * PULSE_PARAM_COUNTS["RZ"] + PULSE_PARAM_COUNTS["CX"] 

579 PULSE_PARAM_COUNTS["CRZ"] = 2 * PULSE_PARAM_COUNTS["RZ"] + PULSE_PARAM_COUNTS["CZ"] 

580 PULSE_PARAM_COUNTS["CRY"] = 2 * PULSE_PARAM_COUNTS["RX"] + PULSE_PARAM_COUNTS["CRZ"] 

581 PULSE_PARAM_COUNTS["CRX"] = 2 * PULSE_PARAM_COUNTS["H"] + PULSE_PARAM_COUNTS["CRZ"] 

582 

583 OPTIMIZED_PULSES: Dict[str, Optional[jnp.ndarray]] = { 

584 "Rot": jnp.array( 

585 [0.5, 7.857992399021039, 21.57270102638842, 0.9000668764608991, 0.5] 

586 ), 

587 "RX": jnp.array([15.70989327341467, 29.5230665326707, 0.7499810441330634]), 

588 "RY": jnp.array([7.8787724942614235, 22.001319411513432, 1.098524473819202]), 

589 "RZ": jnp.array([0.5]), 

590 "CRX": jnp.array( 

591 [ 

592 9.345887537573672, 

593 12.785220434787014, 

594 0.7109351566377278, 

595 0.5, 

596 15.102609209445896, 

597 0.5, 

598 2.9162064326095, 

599 0.019005851299126367, 

600 10.000000000000078, 

601 ] 

602 ), 

603 "CRY": jnp.array( 

604 [ 

605 19.113133239181412, 

606 23.385853735839447, 

607 1.2499994641504941, 

608 0.5, 

609 1.0796514845999126, 

610 0.5, 

611 12.313295392726795, 

612 17.310360723575805, 

613 0.8499715424933506, 

614 ] 

615 ), 

616 "CRZ": jnp.array([0.5, 1.7037270017441872, 0.5]), 

617 "CX": jnp.array( 

618 [ 

619 7.951920934692106, 

620 21.655479574101687, 

621 0.8929524493211076, 

622 0.9548359253748596, 

623 7.94488020182026, 

624 21.61729834699293, 

625 0.9067943033364354, 

626 ] 

627 ), 

628 "CY": jnp.array( 

629 [ 

630 0.5, 

631 13.679990291069169, 

632 6.86497650976022, 

633 1.0547555119435108, 

634 14.96056469588421, 

635 13.040583781891456, 

636 0.33844677502596704, 

637 0.8709563476069772, 

638 0.5, 

639 ] 

640 ), 

641 "CZ": jnp.array([0.962596375687258]), 

642 "H": jnp.array([7.857992398977854, 21.572701026008765, 0.9000668764548863]), 

643 } 

644 

645 @staticmethod 

646 def num_params(gate: str) -> int: 

647 """Return the number of pulse parameters for a given gate.""" 

648 if gate not in PulseInformation.PULSE_PARAM_COUNTS: 

649 raise ValueError(f"Unknown gate '{gate}'") 

650 return PulseInformation.PULSE_PARAM_COUNTS[gate] 

651 

652 @staticmethod 

653 def optimized_params(gate: str) -> Optional[jnp.ndarray]: 

654 """Return the optimized pulse parameters for a given gate.""" 

655 if gate not in PulseInformation.OPTIMIZED_PULSES: 

656 raise ValueError(f"Unknown gate '{gate}'") 

657 return PulseInformation.OPTIMIZED_PULSES[gate] 

658 

659 

660class PulseGates: 

661 # NOTE: Implementation of S, RX, RY, RZ, CZ, CNOT/CX and H pulse level 

662 # gates closely follow https://doi.org/10.5445/IR/1000184129 

663 # TODO: Mention deviations from the above? 

664 omega_q = 10 * jnp.pi 

665 omega_c = 10 * jnp.pi 

666 

667 H_static = jnp.array( 

668 [[jnp.exp(1j * omega_q / 2), 0], [0, jnp.exp(-1j * omega_q / 2)]] 

669 ) 

670 

671 Id = jnp.eye(2, dtype=jnp.complex64) 

672 X = jnp.array([[0, 1], [1, 0]]) 

673 Y = jnp.array([[0, -1j], [1j, 0]]) 

674 Z = jnp.array([[1, 0], [0, -1]]) 

675 

676 @staticmethod 

677 def S(p, t, phi_c): 

678 """ 

679 Generates a shaped pulse envelope modulated by a carrier. 

680 

681 The pulse is a Gaussian envelope multiplied by a cosine carrier, commonly 

682 used in implementing rotation gates (e.g., RX, RY). 

683 

684 Parameters 

685 ---------- 

686 p : sequence of float 

687 Pulse parameters `[A, sigma]`: 

688 - A : float, amplitude of the Gaussian 

689 - sigma : float, width of the Gaussian 

690 t : float or sequence of float 

691 Time or time interval over which the pulse is applied. If a sequence, 

692 `t_c` is taken as the midpoint `(t[0] + t[1]) / 2`. 

693 phi_c : float 

694 Phase of the carrier cosine. 

695 

696 Returns 

697 ------- 

698 jnp.ndarray 

699 The shaped pulse at each time step `t`. 

700 """ 

701 A, sigma = p 

702 t_c = (t[0] + t[1]) / 2 if isinstance(t, (list, tuple)) else t / 2 

703 

704 f = A * jnp.exp(-0.5 * ((t - t_c) / sigma) ** 2) 

705 x = jnp.cos(PulseGates.omega_c * t + phi_c) 

706 

707 return f * x 

708 

709 @staticmethod 

710 def Rot(phi, theta, omega, wires, pulse_params=None): 

711 """ 

712 Applies a general single-qubit rotation using a decomposition. 

713 

714 Decomposition: 

715 Rot(phi, theta, omega) = RZ(phi) · RY(theta) · RZ(omega) 

716 

717 Parameters 

718 ---------- 

719 phi : float 

720 The first rotation angle. 

721 theta : float 

722 The second rotation angle. 

723 omega : float 

724 The third rotation angle. 

725 wires : List[int] 

726 The wire(s) to apply the rotation to. 

727 pulse_params : np.ndarray, optional 

728 Pulse parameters for the composing gates. Defaults 

729 to optimized parameters if None. 

730 """ 

731 n_RZ = PulseInformation.num_params("RZ") 

732 n_RY = PulseInformation.num_params("RY") 

733 

734 idx1 = n_RZ 

735 idx2 = idx1 + n_RY 

736 idx3 = idx2 + n_RZ 

737 

738 if pulse_params is None: 

739 opt = PulseInformation.optimized_params("Rot") 

740 params_RZ_1 = opt[:idx1] 

741 params_RY = opt[idx1:idx2] 

742 params_RZ_2 = opt[idx2:idx3] 

743 else: 

744 params_RZ_1 = pulse_params[:idx1] 

745 params_RY = pulse_params[idx1:idx2] 

746 params_RZ_2 = pulse_params[idx2:idx3] 

747 

748 PulseGates.RZ(phi, wires=wires, pulse_params=params_RZ_1) 

749 PulseGates.RY(theta, wires=wires, pulse_params=params_RY) 

750 PulseGates.RZ(omega, wires=wires, pulse_params=params_RZ_2) 

751 

752 @staticmethod 

753 def RX(w, wires, pulse_params=None): 

754 """ 

755 Applies a rotation around the X axis pulse to the given wires. 

756 

757 Parameters 

758 ---------- 

759 w : float 

760 The rotation angle in radians. 

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

762 The wire(s) to apply the rotation to. 

763 pulse_params : np.ndarray, optional 

764 Array containing pulse parameters `A`, `sigma` and time `t` for the 

765 Gaussian envelope. Defaults to optimized parameters and time. 

766 """ 

767 n_RX = PulseInformation.num_params("RX") 

768 idx = n_RX - 1 

769 if pulse_params is None: 

770 opt = PulseInformation.optimized_params("RX") 

771 pulse_params, t = opt[:idx], opt[idx] 

772 else: 

773 pulse_params, t = pulse_params[:idx], pulse_params[idx] 

774 

775 def Sx(p, t): 

776 return PulseGates.S(p, t, phi_c=jnp.pi) * w 

777 

778 _H = PulseGates.H_static.conj().T @ PulseGates.X @ PulseGates.H_static 

779 _H = qml.Hermitian(_H, wires=wires) 

780 H_eff = Sx * _H 

781 

782 return qml.evolve(H_eff)([pulse_params], t) 

783 

784 @staticmethod 

785 def RY(w, wires, pulse_params=None): 

786 """ 

787 Applies a rotation around the Y axis pulse to the given wires. 

788 

789 Parameters 

790 ---------- 

791 w : float 

792 The rotation angle in radians. 

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

794 The wire(s) to apply the rotation to. 

795 pulse_params : np.ndarray, optional 

796 Array containing pulse parameters `A`, `sigma` and time `t` for the 

797 Gaussian envelope. Defaults to optimized parameters and time. 

798 """ 

799 n_RY = PulseInformation.num_params("RY") 

800 idx = n_RY - 1 

801 if pulse_params is None: 

802 opt = PulseInformation.optimized_params("RY") 

803 pulse_params, t = opt[:idx], opt[idx] 

804 else: 

805 pulse_params, t = pulse_params[:idx], pulse_params[idx] 

806 

807 def Sy(p, t): 

808 return PulseGates.S(p, t, phi_c=-jnp.pi / 2) * w 

809 

810 _H = PulseGates.H_static.conj().T @ PulseGates.Y @ PulseGates.H_static 

811 _H = qml.Hermitian(_H, wires=wires) 

812 H_eff = Sy * _H 

813 

814 return qml.evolve(H_eff)([pulse_params], t) 

815 

816 @staticmethod 

817 def RZ(w, wires, pulse_params=None): 

818 """ 

819 Applies a rotation around the Z axis to the given wires. 

820 

821 Parameters 

822 ---------- 

823 w : float 

824 The rotation angle in radians. 

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

826 The wire(s) to apply the rotation to. 

827 pulse_params : float, optional 

828 Duration of the pulse. Rotation angle = w * 2 * t. 

829 Defaults to 0.5 if None. 

830 """ 

831 idx = PulseInformation.num_params("RZ") - 1 

832 if pulse_params is None: 

833 t = PulseInformation.optimized_params("RZ")[idx] 

834 elif isinstance(pulse_params, (float, int)): 

835 t = pulse_params 

836 else: 

837 t = pulse_params[idx] 

838 

839 _H = qml.Hermitian(PulseGates.Z, wires=wires) 

840 

841 # TODO: Put comment why p, t has no effect here 

842 def Sz(p, t): 

843 return w 

844 

845 H_eff = Sz * _H 

846 

847 return qml.evolve(H_eff)([0], t) 

848 

849 @staticmethod 

850 def CRX(w, wires, pulse_params=None): 

851 """ 

852 Applies a controlled-RX(w) gate using a decomposition. 

853 

854 Decomposition: 

855 CRX(w) = H_t · CRZ(w) · H_t 

856 

857 Parameters 

858 ---------- 

859 w : float 

860 Rotation angle. 

861 wires : List[int] 

862 The control and target wires. 

863 pulse_params : np.ndarray 

864 Pulse parameters for the composing gates. Defaults 

865 to optimized parameters if None. 

866 """ 

867 n_H = PulseInformation.num_params("H") 

868 n_CRZ = PulseInformation.num_params("CRZ") 

869 

870 idx1 = n_H 

871 idx2 = idx1 + n_CRZ 

872 idx3 = idx2 + n_H 

873 

874 if pulse_params is None: 

875 opt = PulseInformation.optimized_params("CRX") 

876 params_H_1 = opt[:idx1] 

877 params_CRZ = opt[idx1:idx2] 

878 params_H_2 = opt[idx2:idx3] 

879 else: 

880 params_H_1 = pulse_params[:idx1] 

881 params_CRZ = pulse_params[idx1:idx2] 

882 params_H_2 = pulse_params[idx2:idx3] 

883 

884 target = wires[1] 

885 

886 PulseGates.H(wires=target, pulse_params=params_H_1) 

887 PulseGates.CRZ(w, wires, pulse_params=params_CRZ) 

888 PulseGates.H(wires=target, pulse_params=params_H_2) 

889 

890 return 

891 

892 @staticmethod 

893 def CRY(w, wires, pulse_params=None): 

894 """ 

895 Applies a controlled-RY(w) gate using a decomposition. 

896 

897 Decomposition: 

898 CRY(w) = RX(-π/2)_t · CRZ(w) · RX(π/2)_t 

899 

900 Parameters 

901 ---------- 

902 w : float 

903 Rotation angle. 

904 wires : List[int] 

905 The control and target wires. 

906 pulse_params : np.ndarray 

907 Pulse parameters for the composing gates. Defaults 

908 to optimized parameters if None. 

909 """ 

910 n_RX = PulseInformation.num_params("RX") 

911 n_CRZ = PulseInformation.num_params("CRZ") 

912 

913 idx1 = n_RX 

914 idx2 = idx1 + n_CRZ 

915 idx3 = idx2 + n_RX 

916 

917 if pulse_params is None: 

918 opt = PulseInformation.optimized_params("CRY") 

919 params_RX_1 = opt[:idx1] 

920 params_CRZ = opt[idx1:idx2] 

921 params_RX_2 = opt[idx2:idx3] 

922 else: 

923 params_RX_1 = pulse_params[:idx1] 

924 params_CRZ = pulse_params[idx1:idx2] 

925 params_RX_2 = pulse_params[idx2:idx3] 

926 

927 target = wires[1] 

928 

929 PulseGates.RX(-np.pi / 2, wires=target, pulse_params=params_RX_1) 

930 PulseGates.CRZ(w, wires=wires, pulse_params=params_CRZ) 

931 PulseGates.RX(np.pi / 2, wires=target, pulse_params=params_RX_2) 

932 

933 return 

934 

935 @staticmethod 

936 def CRZ(w, wires, pulse_params=None): 

937 """ 

938 Applies a controlled-RZ(w) gate using a decomposition. 

939 

940 Decomposition: 

941 CRZ(w) = RZ(w/2)_t · CZ · RZ(-w/2)_t 

942 

943 Parameters 

944 ---------- 

945 w : float 

946 Rotation angle. 

947 wires : List[int] 

948 The control and target wires. 

949 pulse_params : np.ndarray 

950 Pulse parameters for the composing gates. Defaults 

951 to optimized parameters if None. 

952 """ 

953 n_RZ = PulseInformation.num_params("RZ") 

954 n_CZ = PulseInformation.num_params("CZ") 

955 

956 idx1 = n_RZ 

957 idx2 = idx1 + n_CZ 

958 idx3 = idx2 + n_RZ 

959 

960 if pulse_params is None: 

961 opt = PulseInformation.optimized_params("CRZ") 

962 params_RZ_1 = opt[:idx1] 

963 params_CZ = opt[idx1:idx2] 

964 params_RZ_2 = opt[idx2:idx3] 

965 else: 

966 params_RZ_1 = pulse_params[:idx1] 

967 params_CZ = pulse_params[idx1:idx2] 

968 params_RZ_2 = pulse_params[idx2:idx3] 

969 

970 target = wires[1] 

971 

972 PulseGates.RZ(w / 2, wires=target, pulse_params=params_RZ_1) 

973 PulseGates.CZ(wires=wires, pulse_params=params_CZ) 

974 PulseGates.RZ(-w / 2, wires=target, pulse_params=params_RZ_2) 

975 

976 return 

977 

978 @staticmethod 

979 def CX(wires, pulse_params=None): 

980 """ 

981 Applies a CNOT gate using a decomposition. 

982 

983 Decomposition: 

984 CNOT = H_t · CZ · H_t 

985 

986 Parameters 

987 ---------- 

988 wires : List[int] 

989 The control and target wires for the CNOT gate. 

990 pulse_params : np.ndarray, optional 

991 Pulse parameters for the composing gates. Defaults 

992 to optimized parameters if None. 

993 """ 

994 n_H = PulseInformation.num_params("H") 

995 n_CZ = PulseInformation.num_params("CZ") 

996 

997 idx1 = n_H 

998 idx2 = idx1 + n_CZ 

999 idx3 = idx2 + n_H 

1000 

1001 if pulse_params is None: 

1002 opt = PulseInformation.optimized_params("CX") 

1003 params_H_1 = opt[:idx1] 

1004 t_CZ = opt[idx1:idx2] 

1005 params_H_2 = opt[idx2:idx3] 

1006 

1007 else: 

1008 params_H_1 = pulse_params[:idx1] 

1009 t_CZ = pulse_params[idx1:idx2] 

1010 params_H_2 = pulse_params[idx2:idx3] 

1011 

1012 target = wires[1] 

1013 

1014 PulseGates.H(wires=target, pulse_params=params_H_1) 

1015 PulseGates.CZ(wires=wires, pulse_params=t_CZ) 

1016 PulseGates.H(wires=target, pulse_params=params_H_2) 

1017 

1018 return 

1019 

1020 @staticmethod 

1021 def CY(wires, pulse_params=None): 

1022 """ 

1023 Applies a controlled-Y gate using a decomposition. 

1024 

1025 Decomposition: 

1026 CY = RZ(-π/2)_t · CX · RZ(π/2)_t 

1027 

1028 Parameters 

1029 ---------- 

1030 wires : List[int] 

1031 The control and target wires. 

1032 pulse_params : np.ndarray 

1033 Pulse parameters for the composing gates. Defaults 

1034 to optimized parameters if None. 

1035 """ 

1036 n_RZ = PulseInformation.num_params("RZ") 

1037 n_CX = PulseInformation.num_params("CX") 

1038 

1039 idx1 = n_RZ 

1040 idx2 = idx1 + n_CX 

1041 idx3 = idx2 + n_RZ 

1042 

1043 if pulse_params is None: 

1044 opt = PulseInformation.optimized_params("CY") 

1045 params_RZ_1 = opt[:idx1] 

1046 params_CX = opt[idx1:idx2] 

1047 params_RZ_2 = opt[idx2:idx3] 

1048 else: 

1049 params_RZ_1 = pulse_params[:idx1] 

1050 params_CX = pulse_params[idx1:idx2] 

1051 params_RZ_2 = pulse_params[idx2:idx3] 

1052 

1053 target = wires[1] 

1054 

1055 PulseGates.RZ(-np.pi / 2, wires=target, pulse_params=params_RZ_1) 

1056 PulseGates.CX(wires=wires, pulse_params=params_CX) 

1057 PulseGates.RZ(np.pi / 2, wires=target, pulse_params=params_RZ_2) 

1058 

1059 return 

1060 

1061 @staticmethod 

1062 def CZ(wires, pulse_params=None): 

1063 """ 

1064 Applies a controlled Z gate to the given wires. 

1065 

1066 Parameters 

1067 ---------- 

1068 wires : List[int] 

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

1070 pulse_params : float, optional 

1071 Time or time interval for the evolution. 

1072 Defaults to optimized time if None. 

1073 """ 

1074 idx = PulseInformation.num_params("CZ") - 1 

1075 if pulse_params is None: 

1076 t = PulseInformation.optimized_params("CZ")[idx] 

1077 elif isinstance(pulse_params, (float, int)): 

1078 t = pulse_params 

1079 else: 

1080 t = pulse_params[idx] 

1081 

1082 I_I = jnp.kron(PulseGates.Id, PulseGates.Id) 

1083 Z_I = jnp.kron(PulseGates.Z, PulseGates.Id) 

1084 I_Z = jnp.kron(PulseGates.Id, PulseGates.Z) 

1085 Z_Z = jnp.kron(PulseGates.Z, PulseGates.Z) 

1086 

1087 # TODO: explain why p, t not in signal 

1088 def Scz(p, t): 

1089 return jnp.pi 

1090 

1091 _H = (jnp.pi / 4) * (I_I - Z_I - I_Z + Z_Z) 

1092 _H = qml.Hermitian(_H, wires=wires) 

1093 H_eff = Scz * _H 

1094 

1095 return qml.evolve(H_eff)([0], t) 

1096 

1097 @staticmethod 

1098 def H(wires, pulse_params=None): 

1099 """ 

1100 Applies Hadamard gate to the given wires. 

1101 

1102 Parameters 

1103 ---------- 

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

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

1106 pulse_params : np.ndarray, optional 

1107 Pulse parameters for the composing gates. Defaults 

1108 to optimized parameters and time. 

1109 """ 

1110 if pulse_params is None: 

1111 pulse_params = PulseInformation.optimized_params("H") 

1112 else: 

1113 pulse_params = pulse_params 

1114 

1115 # qml.GlobalPhase(-jnp.pi / 2) # this could act as substitute to Sc 

1116 # TODO: Explain why p, t not in signal 

1117 def Sc(p, t): 

1118 return -1.0 

1119 

1120 _H = jnp.pi / 2 * jnp.eye(2, dtype=jnp.complex64) 

1121 _H = qml.Hermitian(_H, wires=wires) 

1122 H_corr = Sc * _H 

1123 

1124 qml.evolve(H_corr)([0], 1) 

1125 

1126 PulseGates.RZ(jnp.pi, wires=wires) 

1127 PulseGates.RY(jnp.pi / 2, wires=wires, pulse_params=pulse_params) 

1128 

1129 return 

1130 

1131 

1132# Meta class to avoid instantiating the Gates class 

1133class GatesMeta(type): 

1134 def __getattr__(cls, gate_name): 

1135 def handler(*args, **kwargs): 

1136 return Gates._inner_getattr(gate_name, *args, **kwargs) 

1137 

1138 return handler 

1139 

1140 

1141class Gates(metaclass=GatesMeta): 

1142 """ 

1143 Dynamic accessor for quantum gates. 

1144 

1145 Routes calls like `Gates.RX(...)` to either `UnitaryGates` or `PulseGates` 

1146 depending on the `gate_mode` keyword (defaults to 'unitary'). 

1147 

1148 During circuit building, the pulse manager can be activated via 

1149 `pulse_manager_context`, which slices the global model pulse parameters 

1150 and passes them to each gate. Model pulse parameters act as element-wise 

1151 scalers on the gate's optimized pulse parameters. 

1152 

1153 Parameters 

1154 ---------- 

1155 gate_mode : str, optional 

1156 Determines the backend. 'unitary' for UnitaryGates, 'pulse' for PulseGates. 

1157 Defaults to 'unitary'. 

1158 

1159 Examples 

1160 -------- 

1161 >>> Gates.RX(w, wires) 

1162 >>> Gates.RX(w, wires, gate_mode="unitary") 

1163 >>> Gates.RX(w, wires, gate_mode="pulse") 

1164 >>> Gates.RX(w, wires, pulse_params, gate_mode="pulse") 

1165 """ 

1166 

1167 def __getattr__(self, gate_name): 

1168 def handler(**kwargs): 

1169 return self._inner_getattr(gate_name, **kwargs) 

1170 

1171 return handler 

1172 

1173 @staticmethod 

1174 def _inner_getattr(gate_name, *args, **kwargs): 

1175 gate_mode = kwargs.pop("gate_mode", "unitary") 

1176 

1177 # Backend selection and kwargs filtering 

1178 allowed_args = ["w", "wires", "phi", "theta", "omega"] 

1179 if gate_mode == "unitary": 

1180 gate_backend = UnitaryGates 

1181 allowed_args += ["noise_params"] 

1182 elif gate_mode == "pulse": 

1183 gate_backend = PulseGates 

1184 allowed_args += ["pulse_params"] 

1185 else: 

1186 raise ValueError( 

1187 f"Unknown gate mode: {gate_mode}. Use 'unitary' or 'pulse'." 

1188 ) 

1189 

1190 kwargs = {k: v for k, v in kwargs.items() if k in allowed_args} 

1191 pulse_params = kwargs.get("pulse_params") 

1192 pulse_mgr = getattr(Gates, "_pulse_mgr", None) 

1193 

1194 # Type check on pulse parameters 

1195 if pulse_params is not None: 

1196 # flatten pulse parameters 

1197 if isinstance(pulse_params, (list, tuple)): 

1198 flat_params = pulse_params 

1199 

1200 elif isinstance(pulse_params, jax.core.Tracer): 

1201 flat_params = jnp.ravel(pulse_params) 

1202 

1203 elif isinstance(pulse_params, (np.ndarray, jnp.ndarray)): 

1204 flat_params = pulse_params.flatten().tolist() 

1205 

1206 else: 

1207 raise TypeError(f"Unsupported pulse_params type: {type(pulse_params)}") 

1208 

1209 # checks elements in flat parameters are real numbers or jax Tracer 

1210 if not all( 

1211 isinstance(x, (numbers.Real, jax.core.Tracer)) for x in flat_params 

1212 ): 

1213 raise TypeError( 

1214 "All elements in pulse_params must be int or float, " 

1215 f"got {pulse_params}, type {type(pulse_params)}. " 

1216 ) 

1217 

1218 # Len check on pulse parameters 

1219 if pulse_params is not None and not isinstance(pulse_mgr, PulseParamManager): 

1220 n_params = PulseInformation.num_params(gate_name) 

1221 if len(flat_params) != n_params: 

1222 raise ValueError( 

1223 f"Gate '{gate_name}' expects {n_params} pulse parameters, " 

1224 f"got {len(flat_params)}" 

1225 ) 

1226 

1227 # Pulse slicing + scaling 

1228 if gate_mode == "pulse" and isinstance(pulse_mgr, PulseParamManager): 

1229 n_params = PulseInformation.num_params(gate_name) 

1230 scalers = pulse_mgr.get(n_params) 

1231 base = PulseInformation.optimized_params(gate_name) 

1232 kwargs["pulse_params"] = scalers * base # element-wise scaling 

1233 

1234 # Call the selected gate backend 

1235 gate = getattr(gate_backend, gate_name, None) 

1236 if gate is None: 

1237 raise AttributeError( 

1238 f"'{gate_backend.__class__.__name__}' object " 

1239 f"has no attribute '{gate_name}'" 

1240 ) 

1241 

1242 return gate(*args, **kwargs) 

1243 

1244 @staticmethod 

1245 @contextmanager 

1246 def pulse_manager_context(pulse_params: np.ndarray): 

1247 """Temporarily set the global pulse manager for circuit building.""" 

1248 Gates._pulse_mgr = PulseParamManager(pulse_params) 

1249 try: 

1250 yield 

1251 finally: 

1252 Gates._pulse_mgr = None 

1253 

1254 

1255class PulseParamManager: 

1256 def __init__(self, pulse_params: np.ndarray): 

1257 self.pulse_params = pulse_params 

1258 self.idx = 0 

1259 

1260 def get(self, n: int): 

1261 """Return the next n parameters and advance the cursor.""" 

1262 if self.idx + n > len(self.pulse_params): 

1263 raise ValueError("Not enough pulse parameters left for this gate") 

1264 params = self.pulse_params[self.idx : self.idx + n] 

1265 self.idx += n 

1266 return params 

1267 

1268 

1269class Ansaetze: 

1270 def get_available(): 

1271 return [ 

1272 Ansaetze.No_Ansatz, 

1273 Ansaetze.Circuit_1, 

1274 Ansaetze.Circuit_2, 

1275 Ansaetze.Circuit_3, 

1276 Ansaetze.Circuit_4, 

1277 Ansaetze.Circuit_6, 

1278 Ansaetze.Circuit_9, 

1279 Ansaetze.Circuit_10, 

1280 Ansaetze.Circuit_15, 

1281 Ansaetze.Circuit_16, 

1282 Ansaetze.Circuit_17, 

1283 Ansaetze.Circuit_18, 

1284 Ansaetze.Circuit_19, 

1285 Ansaetze.No_Entangling, 

1286 Ansaetze.Strongly_Entangling, 

1287 Ansaetze.Hardware_Efficient, 

1288 Ansaetze.GHZ, 

1289 ] 

1290 

1291 class No_Ansatz(Circuit): 

1292 @staticmethod 

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

1294 return 0 

1295 

1296 @staticmethod 

1297 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1298 return 0 

1299 

1300 @staticmethod 

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

1302 return None 

1303 

1304 @staticmethod 

1305 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1306 pass 

1307 

1308 class GHZ(Circuit): 

1309 @staticmethod 

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

1311 return 0 

1312 

1313 @staticmethod 

1314 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1315 """ 

1316 Returns the number of pulse parameters per layer for the GHZ circuit. 

1317 

1318 Parameters 

1319 ---------- 

1320 n_qubits : int 

1321 Number of qubits in the circuit. 

1322 

1323 Returns 

1324 ------- 

1325 int 

1326 Total number of pulse parameters required for one layer of the circuit. 

1327 """ 

1328 n_params = PulseInformation.num_params("H") 

1329 n_params += (n_qubits - 1) * PulseInformation.num_params("CX") 

1330 

1331 return n_params 

1332 

1333 @staticmethod 

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

1335 return None 

1336 

1337 @staticmethod 

1338 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1339 Gates.H(0, **kwargs) 

1340 

1341 for q in range(n_qubits - 1): 

1342 Gates.CX([q, q + 1], **kwargs) 

1343 

1344 class Hardware_Efficient(Circuit): 

1345 @staticmethod 

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

1347 """ 

1348 Returns the number of parameters per layer for the 

1349 Hardware Efficient Ansatz. 

1350 

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

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

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

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

1355 

1356 Parameters 

1357 ---------- 

1358 n_qubits : int 

1359 Number of qubits in the circuit. 

1360 

1361 Returns 

1362 ------- 

1363 int 

1364 Number of parameters required for one layer of the circuit. 

1365 """ 

1366 if n_qubits < 2: 

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

1368 return n_qubits * 3 

1369 

1370 @staticmethod 

1371 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1372 """ 

1373 Returns the number of pulse parameters per layer for the 

1374 Hardware Efficient Ansatz. 

1375 

1376 This counts all parameters needed if the circuit is used at the 

1377 pulse level. It includes contributions from single-qubit rotations 

1378 (`RY` and `RZ`) and multi-qubit gates (`CX`) if more than one qubit 

1379 is present. 

1380 

1381 Parameters 

1382 ---------- 

1383 n_qubits : int 

1384 Number of qubits in the circuit 

1385 

1386 Returns 

1387 ------- 

1388 int 

1389 Number of pulse parameters required for one layer of the circuit. 

1390 """ 

1391 n_params = 2 * PulseInformation.num_params("RY") 

1392 n_params += PulseInformation.num_params("RZ") 

1393 n_params *= n_qubits 

1394 

1395 n_CX = (n_qubits // 2) + ((n_qubits - 1) // 2) 

1396 n_CX += 1 if n_qubits > 2 else 0 

1397 n_params += n_CX * PulseInformation.num_params("CX") 

1398 

1399 return n_params 

1400 

1401 @staticmethod 

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

1403 """ 

1404 No controlled rotation gates available. Always None. 

1405 

1406 Parameters 

1407 ---------- 

1408 n_qubits : int 

1409 Number of qubits in the circuit 

1410 

1411 Returns 

1412 ------- 

1413 Optional[np.ndarray] 

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

1415 contain controlled rotation gates. 

1416 """ 

1417 return None 

1418 

1419 @staticmethod 

1420 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1421 """ 

1422 Creates a Hardware-Efficient ansatz, as proposed in 

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

1424 

1425 Parameters 

1426 ---------- 

1427 w : np.ndarray 

1428 Weight vector of size n_qubits*3 

1429 n_qubits : int 

1430 Number of qubits 

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

1432 Dictionary of noise parameters to apply to the gates 

1433 """ 

1434 w_idx = 0 

1435 for q in range(n_qubits): 

1436 Gates.RY(w[w_idx], wires=q, **kwargs) 

1437 w_idx += 1 

1438 Gates.RZ(w[w_idx], wires=q, **kwargs) 

1439 w_idx += 1 

1440 Gates.RY(w[w_idx], wires=q, **kwargs) 

1441 w_idx += 1 

1442 

1443 if n_qubits > 1: 

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

1445 Gates.CX(wires=[(2 * q), (2 * q + 1)], **kwargs) 

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

1447 Gates.CX(wires=[(2 * q + 1), (2 * q + 2)], **kwargs) 

1448 if n_qubits > 2: 

1449 Gates.CX(wires=[(n_qubits - 1), 0], **kwargs) 

1450 

1451 class Circuit_19(Circuit): 

1452 @staticmethod 

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

1454 """ 

1455 Returns the number of parameters per layer for Circuit_19. 

1456 

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

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

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

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

1461 

1462 Parameters 

1463 ---------- 

1464 n_qubits : int 

1465 Number of qubits in the circuit 

1466 

1467 Returns 

1468 ------- 

1469 int 

1470 Number of parameters required for one layer of the circuit 

1471 """ 

1472 if n_qubits > 1: 

1473 return n_qubits * 3 

1474 else: 

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

1476 return 2 

1477 

1478 @staticmethod 

1479 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1480 """ 

1481 Returns the number of pulse parameters per layer for Circuit_19. 

1482 

1483 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

1484 qubits, and controlled rotations (`CRX`) on each qubit if more than one 

1485 qubit is present. 

1486 

1487 Parameters 

1488 ---------- 

1489 n_qubits : int 

1490 Number of qubits in the circuit 

1491 

1492 Returns 

1493 ------- 

1494 int 

1495 Number of pulse parameters required for one layer of the circuit. 

1496 """ 

1497 n_params = PulseInformation.num_params("RX") 

1498 n_params += PulseInformation.num_params("RZ") 

1499 n_params *= n_qubits 

1500 

1501 if n_qubits > 1: 

1502 n_params += PulseInformation.num_params("CRX") * n_qubits 

1503 

1504 return n_params 

1505 

1506 @staticmethod 

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

1508 """ 

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

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

1511 [indices[0]:indices[1]:indices[2]] 

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 if n_qubits > 1: 

1525 return [-n_qubits, None, None] 

1526 else: 

1527 return None 

1528 

1529 @staticmethod 

1530 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1531 """ 

1532 Creates a Circuit19 ansatz. 

1533 

1534 Length of flattened vector must be n_qubits*3 

1535 because for >1 qubits there are three gates 

1536 

1537 Parameters 

1538 ---------- 

1539 w : np.ndarray 

1540 Weight vector of size n_qubits*3 

1541 n_qubits : int 

1542 Number of qubits 

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

1544 Dictionary of noise parameters to apply to the gates 

1545 """ 

1546 w_idx = 0 

1547 for q in range(n_qubits): 

1548 Gates.RX(w[w_idx], wires=q, **kwargs) 

1549 w_idx += 1 

1550 Gates.RZ(w[w_idx], wires=q, **kwargs) 

1551 w_idx += 1 

1552 

1553 if n_qubits > 1: 

1554 for q in range(n_qubits): 

1555 Gates.CRX( 

1556 w[w_idx], 

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

1558 **kwargs, 

1559 ) 

1560 w_idx += 1 

1561 

1562 class Circuit_18(Circuit): 

1563 @staticmethod 

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

1565 """ 

1566 Returns the number of parameters per layer for Circuit_18. 

1567 

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

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

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

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

1572 

1573 Parameters 

1574 ---------- 

1575 n_qubits : int 

1576 Number of qubits in the circuit 

1577 

1578 Returns 

1579 ------- 

1580 int 

1581 Number of parameters required for one layer of the circuit 

1582 """ 

1583 if n_qubits > 1: 

1584 return n_qubits * 3 

1585 else: 

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

1587 return 2 

1588 

1589 @staticmethod 

1590 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1591 """ 

1592 Returns the number of pulse parameters per layer for Circuit_18. 

1593 

1594 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

1595 qubits, and controlled rotations (`CRZ`) on each qubit if more than one 

1596 qubit is present. 

1597 

1598 Parameters 

1599 ---------- 

1600 n_qubits : int 

1601 Number of qubits in the circuit 

1602 

1603 Returns 

1604 ------- 

1605 int 

1606 Number of pulse parameters required for one layer of the circuit. 

1607 """ 

1608 n_params = PulseInformation.num_params("RX") 

1609 n_params += PulseInformation.num_params("RZ") 

1610 n_params *= n_qubits 

1611 

1612 if n_qubits > 1: 

1613 n_params += PulseInformation.num_params("CRZ") * n_qubits 

1614 

1615 return n_params 

1616 

1617 @staticmethod 

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

1619 """ 

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

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

1622 [indices[0]:indices[1]:indices[2]] 

1623 

1624 Parameters 

1625 ---------- 

1626 n_qubits : int 

1627 Number of qubits in the circuit 

1628 

1629 Returns 

1630 ------- 

1631 Optional[np.ndarray] 

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

1633 contain controlled rotation gates. 

1634 """ 

1635 if n_qubits > 1: 

1636 return [-n_qubits, None, None] 

1637 else: 

1638 return None 

1639 

1640 @staticmethod 

1641 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1642 """ 

1643 Creates a Circuit18 ansatz. 

1644 

1645 Length of flattened vector must be n_qubits*3 

1646 

1647 Parameters 

1648 ---------- 

1649 w : np.ndarray 

1650 Weight vector of size n_qubits*3 

1651 n_qubits : int 

1652 Number of qubits 

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

1654 Dictionary of noise parameters to apply to the gates 

1655 """ 

1656 w_idx = 0 

1657 for q in range(n_qubits): 

1658 Gates.RX(w[w_idx], wires=q, **kwargs) 

1659 w_idx += 1 

1660 Gates.RZ(w[w_idx], wires=q, **kwargs) 

1661 w_idx += 1 

1662 

1663 if n_qubits > 1: 

1664 for q in range(n_qubits): 

1665 Gates.CRZ( 

1666 w[w_idx], 

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

1668 **kwargs, 

1669 ) 

1670 w_idx += 1 

1671 

1672 class Circuit_15(Circuit): 

1673 @staticmethod 

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

1675 """ 

1676 Returns the number of parameters per layer for Circuit_15. 

1677 

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

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

1680 

1681 Parameters 

1682 ---------- 

1683 n_qubits : int 

1684 Number of qubits in the circuit 

1685 

1686 Returns 

1687 ------- 

1688 int 

1689 Number of parameters required for one layer of the circuit 

1690 """ 

1691 if n_qubits > 1: 

1692 return n_qubits * 2 

1693 else: 

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

1695 return 2 

1696 

1697 @staticmethod 

1698 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1699 """ 

1700 Returns the number of pulse parameters per layer for Circuit_15. 

1701 

1702 This includes contributions from single-qubit rotations (`RY`) on all 

1703 qubits, and controlled rotations (`CX`) on each qubit if more than one 

1704 qubit is present. 

1705 

1706 Parameters 

1707 ---------- 

1708 n_qubits : int 

1709 Number of qubits in the circuit 

1710 

1711 Returns 

1712 ------- 

1713 int 

1714 Number of pulse parameters required for one layer of the circuit. 

1715 """ 

1716 n_params = 2 * PulseInformation.num_params("RY") 

1717 n_params *= n_qubits 

1718 

1719 if n_qubits > 1: 

1720 n_params += PulseInformation.num_params("CX") * n_qubits 

1721 

1722 return n_params 

1723 

1724 @staticmethod 

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

1726 """ 

1727 No controlled rotation gates available. Always None. 

1728 

1729 Parameters 

1730 ---------- 

1731 n_qubits : int 

1732 Number of qubits in the circuit 

1733 

1734 Returns 

1735 ------- 

1736 Optional[np.ndarray] 

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

1738 contain controlled rotation gates. 

1739 """ 

1740 return None 

1741 

1742 @staticmethod 

1743 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1744 """ 

1745 Creates a Circuit15 ansatz. 

1746 

1747 Length of flattened vector must be n_qubits*2 

1748 because for >1 qubits there are three gates 

1749 

1750 Parameters 

1751 ---------- 

1752 w : np.ndarray 

1753 Weight vector of size n_qubits*2 

1754 n_qubits : int 

1755 Number of qubits 

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

1757 Dictionary of noise parameters to apply to the gates 

1758 """ 

1759 w_idx = 0 

1760 for q in range(n_qubits): 

1761 Gates.RY(w[w_idx], wires=q, **kwargs) 

1762 w_idx += 1 

1763 

1764 if n_qubits > 1: 

1765 for q in range(n_qubits): 

1766 Gates.CX( 

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

1768 **kwargs, 

1769 ) 

1770 

1771 for q in range(n_qubits): 

1772 Gates.RY(w[w_idx], wires=q, **kwargs) 

1773 w_idx += 1 

1774 

1775 if n_qubits > 1: 

1776 for q in range(n_qubits): 

1777 Gates.CX( 

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

1779 **kwargs, 

1780 ) 

1781 

1782 class Circuit_9(Circuit): 

1783 @staticmethod 

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

1785 """ 

1786 Returns the number of parameters per layer for Circuit_9. 

1787 

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

1789 

1790 Parameters 

1791 ---------- 

1792 n_qubits : int 

1793 Number of qubits in the circuit 

1794 

1795 Returns 

1796 ------- 

1797 int 

1798 Number of parameters required for one layer of the circuit 

1799 """ 

1800 return n_qubits 

1801 

1802 @staticmethod 

1803 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1804 """ 

1805 Returns the number of pulse parameters per layer for Circuit_9. 

1806 

1807 This includes contributions from single-qubit rotations (`H`, `RX`) on all 

1808 qubits, and controlled rotations (`CZ`) on each qubit except one if more 

1809 than one qubit is present. 

1810 

1811 Parameters 

1812 ---------- 

1813 n_qubits : int 

1814 Number of qubits in the circuit 

1815 

1816 Returns 

1817 ------- 

1818 int 

1819 Number of pulse parameters required for one layer of the circuit. 

1820 """ 

1821 n_params = PulseInformation.num_params("H") 

1822 n_params += PulseInformation.num_params("RX") 

1823 n_params *= n_qubits 

1824 

1825 n_params += (n_qubits - 1) * PulseInformation.num_params("CZ") 

1826 

1827 return n_params 

1828 

1829 @staticmethod 

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

1831 """ 

1832 No controlled rotation gates available. Always None. 

1833 

1834 Parameters 

1835 ---------- 

1836 n_qubits : int 

1837 Number of qubits in the circuit 

1838 

1839 Returns 

1840 ------- 

1841 Optional[np.ndarray] 

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

1843 contain controlled rotation gates. 

1844 """ 

1845 return None 

1846 

1847 @staticmethod 

1848 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1849 """ 

1850 Creates a Circuit9 ansatz. 

1851 

1852 Length of flattened vector must be n_qubits 

1853 

1854 Parameters 

1855 ---------- 

1856 w : np.ndarray 

1857 Weight vector of size n_qubits 

1858 n_qubits : int 

1859 Number of qubits 

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

1861 Dictionary of noise parameters to apply to the gates 

1862 """ 

1863 w_idx = 0 

1864 for q in range(n_qubits): 

1865 Gates.H(wires=q, **kwargs) 

1866 

1867 for q in range(n_qubits - 1): 

1868 Gates.CZ( 

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

1870 **kwargs, 

1871 ) 

1872 

1873 for q in range(n_qubits): 

1874 Gates.RX(w[w_idx], wires=q, **kwargs) 

1875 w_idx += 1 

1876 

1877 class Circuit_6(Circuit): 

1878 @staticmethod 

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

1880 """ 

1881 Returns the number of parameters per layer for Circuit_6. 

1882 

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

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

1885 n_qubits**2. 

1886 

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

1888 since no entanglement is possible. 

1889 

1890 Parameters 

1891 ---------- 

1892 n_qubits : int 

1893 Number of qubits 

1894 

1895 Returns 

1896 ------- 

1897 int 

1898 Number of parameters per layer 

1899 """ 

1900 if n_qubits > 1: 

1901 return n_qubits * 3 + n_qubits**2 

1902 else: 

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

1904 return 4 

1905 

1906 @staticmethod 

1907 def n_pulse_params_per_layer(n_qubits: int) -> int: 

1908 """ 

1909 Returns the number of pulse parameters per layer for Circuit_6. 

1910 

1911 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

1912 qubits, and controlled rotations (`CRX`) on each qubit twice except repeats 

1913 if more than one qubit is present. 

1914 

1915 Parameters 

1916 ---------- 

1917 n_qubits : int 

1918 Number of qubits in the circuit 

1919 

1920 Returns 

1921 ------- 

1922 int 

1923 Number of pulse parameters required for one layer of the circuit. 

1924 """ 

1925 n_params = 2 * PulseInformation.num_params("RX") 

1926 n_params += 2 * PulseInformation.num_params("RZ") 

1927 n_params *= n_qubits 

1928 

1929 n_CRX = n_qubits * (n_qubits - 1) 

1930 n_params += n_CRX * PulseInformation.num_params("CRX") 

1931 

1932 return 0 

1933 

1934 @staticmethod 

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

1936 """ 

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

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

1939 [indices[0]:indices[1]:indices[2]] 

1940 

1941 Parameters 

1942 ---------- 

1943 n_qubits : int 

1944 Number of qubits in the circuit 

1945 

1946 Returns 

1947 ------- 

1948 Optional[np.ndarray] 

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

1950 contain controlled rotation gates. 

1951 """ 

1952 # TODO: implement 

1953 return None 

1954 

1955 @staticmethod 

1956 def build(w: np.ndarray, n_qubits: int, **kwargs): 

1957 """ 

1958 Creates a Circuit6 ansatz. 

1959 

1960 Length of flattened vector must be 

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

1962 n_qubits*3+n_qubits**2 

1963 

1964 Parameters 

1965 ---------- 

1966 w : np.ndarray 

1967 Weight vector of size 

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

1969 n_qubits : int 

1970 Number of qubits 

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

1972 Dictionary of noise parameters to apply to the gates 

1973 """ 

1974 w_idx = 0 

1975 for q in range(n_qubits): 

1976 Gates.RX(w[w_idx], wires=q, **kwargs) 

1977 w_idx += 1 

1978 Gates.RZ(w[w_idx], wires=q, **kwargs) 

1979 w_idx += 1 

1980 

1981 if n_qubits > 1: 

1982 for ql in range(n_qubits): 

1983 for q in range(n_qubits): 

1984 if q == ql: 

1985 continue 

1986 Gates.CRX( 

1987 w[w_idx], 

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

1989 **kwargs, 

1990 ) 

1991 w_idx += 1 

1992 

1993 for q in range(n_qubits): 

1994 Gates.RX(w[w_idx], wires=q, **kwargs) 

1995 w_idx += 1 

1996 Gates.RZ(w[w_idx], wires=q, **kwargs) 

1997 w_idx += 1 

1998 

1999 class Circuit_1(Circuit): 

2000 @staticmethod 

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

2002 """ 

2003 Returns the number of parameters per layer for Circuit_1. 

2004 

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

2006 each qubit contributing 2 parameters. 

2007 

2008 Parameters 

2009 ---------- 

2010 n_qubits : int 

2011 Number of qubits in the circuit 

2012 

2013 Returns 

2014 ------- 

2015 int 

2016 Number of parameters per layer 

2017 """ 

2018 return n_qubits * 2 

2019 

2020 @staticmethod 

2021 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2022 """ 

2023 Returns the number of pulse parameters per layer for Circuit_9. 

2024 

2025 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

2026 qubits only. 

2027 

2028 Parameters 

2029 ---------- 

2030 n_qubits : int 

2031 Number of qubits in the circuit 

2032 

2033 Returns 

2034 ------- 

2035 int 

2036 Number of pulse parameters required for one layer of the circuit. 

2037 """ 

2038 n_params = PulseInformation.num_params("RX") 

2039 n_params += PulseInformation.num_params("RZ") 

2040 n_params *= n_qubits 

2041 

2042 return n_params 

2043 

2044 @staticmethod 

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

2046 """ 

2047 No controlled rotation gates available. Always None. 

2048 

2049 Parameters 

2050 ---------- 

2051 n_qubits : int 

2052 Number of qubits in the circuit 

2053 

2054 Returns 

2055 ------- 

2056 Optional[np.ndarray] 

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

2058 contain controlled rotation gates. 

2059 """ 

2060 return None 

2061 

2062 @staticmethod 

2063 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2064 """ 

2065 Creates a Circuit1 ansatz. 

2066 

2067 Length of flattened vector must be n_qubits*2 

2068 

2069 Parameters 

2070 ---------- 

2071 w : np.ndarray 

2072 Weight vector of size n_qubits*2 

2073 n_qubits : int 

2074 Number of qubits 

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

2076 Dictionary of noise parameters to apply to the gates 

2077 """ 

2078 w_idx = 0 

2079 for q in range(n_qubits): 

2080 Gates.RX(w[w_idx], wires=q, **kwargs) 

2081 w_idx += 1 

2082 Gates.RZ(w[w_idx], wires=q, **kwargs) 

2083 w_idx += 1 

2084 

2085 class Circuit_2(Circuit): 

2086 @staticmethod 

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

2088 """ 

2089 Returns the number of parameters per layer for Circuit_2. 

2090 

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

2092 each qubit contributing 2 parameters. 

2093 

2094 Parameters 

2095 ---------- 

2096 n_qubits : int 

2097 Number of qubits in the circuit 

2098 

2099 Returns 

2100 ------- 

2101 int 

2102 Number of parameters per layer 

2103 """ 

2104 return n_qubits * 2 

2105 

2106 @staticmethod 

2107 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2108 """ 

2109 Returns the number of pulse parameters per layer for Circuit_2. 

2110 

2111 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

2112 qubits, and controlled rotations (`CX`) on each qubit except one if more 

2113 than one qubit is present. 

2114 

2115 Parameters 

2116 ---------- 

2117 n_qubits : int 

2118 Number of qubits in the circuit 

2119 

2120 Returns 

2121 ------- 

2122 int 

2123 Number of pulse parameters required for one layer of the circuit. 

2124 """ 

2125 n_params = PulseInformation.num_params("RX") 

2126 n_params += PulseInformation.num_params("RZ") 

2127 n_params *= n_qubits 

2128 

2129 if n_qubits > 1: 

2130 n_params += (n_qubits - 1) * PulseInformation.num_params("CX") 

2131 

2132 return 0 

2133 

2134 @staticmethod 

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

2136 """ 

2137 No controlled rotation gates available. Always None. 

2138 

2139 Parameters 

2140 ---------- 

2141 n_qubits : int 

2142 Number of qubits in the circuit 

2143 

2144 Returns 

2145 ------- 

2146 Optional[np.ndarray] 

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

2148 contain controlled rotation gates. 

2149 """ 

2150 return None 

2151 

2152 @staticmethod 

2153 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2154 """ 

2155 Creates a Circuit2 ansatz. 

2156 

2157 Length of flattened vector must be n_qubits*2 

2158 

2159 Parameters 

2160 ---------- 

2161 w : np.ndarray 

2162 Weight vector of size n_qubits*2 

2163 n_qubits : int 

2164 Number of qubits 

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

2166 Dictionary of noise parameters to apply to the gates 

2167 """ 

2168 w_idx = 0 

2169 for q in range(n_qubits): 

2170 Gates.RX(w[w_idx], wires=q, **kwargs) 

2171 w_idx += 1 

2172 Gates.RZ(w[w_idx], wires=q, **kwargs) 

2173 w_idx += 1 

2174 

2175 for q in range(n_qubits - 1): 

2176 Gates.CX( 

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

2178 **kwargs, 

2179 ) 

2180 

2181 class Circuit_3(Circuit): 

2182 @staticmethod 

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

2184 """ 

2185 Calculates the number of parameters per layer for Circuit3. 

2186 

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

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

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

2190 

2191 Parameters 

2192 ---------- 

2193 n_qubits : int 

2194 Number of qubits in the circuit 

2195 

2196 Returns 

2197 ------- 

2198 int 

2199 Number of parameters per layer 

2200 """ 

2201 return n_qubits * 3 - 1 

2202 

2203 @staticmethod 

2204 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2205 """ 

2206 Returns the number of pulse parameters per layer for Circuit_3. 

2207 

2208 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

2209 qubits, and controlled rotations (`CRZ`) on each qubit except one if more 

2210 than one qubit is present. 

2211 

2212 Parameters 

2213 ---------- 

2214 n_qubits : int 

2215 Number of qubits in the circuit 

2216 

2217 Returns 

2218 ------- 

2219 int 

2220 Number of pulse parameters required for one layer of the circuit. 

2221 """ 

2222 n_params = PulseInformation.num_params("RX") 

2223 n_params = PulseInformation.num_params("RZ") 

2224 n_params *= n_qubits 

2225 

2226 n_params += (n_qubits - 1) * PulseInformation.num_params("CRZ") 

2227 

2228 return n_params 

2229 

2230 @staticmethod 

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

2232 """ 

2233 No controlled rotation gates available. Always None. 

2234 

2235 Parameters 

2236 ---------- 

2237 n_qubits : int 

2238 Number of qubits in the circuit 

2239 

2240 Returns 

2241 ------- 

2242 Optional[np.ndarray] 

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

2244 contain controlled rotation gates. 

2245 """ 

2246 if n_qubits > 1: 

2247 return [-(n_qubits - 1), None, None] 

2248 else: 

2249 return None 

2250 

2251 @staticmethod 

2252 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2253 """ 

2254 Creates a Circuit3 ansatz. 

2255 

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

2257 

2258 Parameters 

2259 ---------- 

2260 w : np.ndarray 

2261 Weight vector of size n_qubits*3-1 

2262 n_qubits : int 

2263 Number of qubits 

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

2265 Dictionary of noise parameters to apply to the gates 

2266 """ 

2267 w_idx = 0 

2268 for q in range(n_qubits): 

2269 Gates.RX(w[w_idx], wires=q, **kwargs) 

2270 w_idx += 1 

2271 Gates.RZ(w[w_idx], wires=q, **kwargs) 

2272 w_idx += 1 

2273 

2274 for q in range(n_qubits - 1): 

2275 Gates.CRZ( 

2276 w[w_idx], 

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

2278 **kwargs, 

2279 ) 

2280 w_idx += 1 

2281 

2282 class Circuit_4(Circuit): 

2283 @staticmethod 

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

2285 """ 

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

2287 

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

2289 

2290 Parameters 

2291 ---------- 

2292 n_qubits : int 

2293 Number of qubits in the circuit 

2294 

2295 Returns 

2296 ------- 

2297 int 

2298 Number of parameters per layer 

2299 """ 

2300 return n_qubits * 3 - 1 

2301 

2302 @staticmethod 

2303 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2304 """ 

2305 Returns the number of pulse parameters per layer for Circuit_4. 

2306 

2307 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

2308 qubits, and controlled rotations (`CRX`) on each qubit except one if more 

2309 than one qubit is present. 

2310 

2311 Parameters 

2312 ---------- 

2313 n_qubits : int 

2314 Number of qubits in the circuit 

2315 

2316 Returns 

2317 ------- 

2318 int 

2319 Number of pulse parameters required for one layer of the circuit. 

2320 """ 

2321 n_params = PulseInformation.num_params("RX") 

2322 n_params += PulseInformation.num_params("RZ") 

2323 n_params *= n_qubits 

2324 

2325 n_params += (n_qubits - 1) * PulseInformation.num_params("CRX") 

2326 

2327 return 0 

2328 

2329 @staticmethod 

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

2331 """ 

2332 No controlled rotation gates available. Always None. 

2333 

2334 Parameters 

2335 ---------- 

2336 n_qubits : int 

2337 Number of qubits in the circuit 

2338 

2339 Returns 

2340 ------- 

2341 Optional[np.ndarray] 

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

2343 contain controlled rotation gates. 

2344 """ 

2345 if n_qubits > 1: 

2346 return [-(n_qubits - 1), None, None] 

2347 else: 

2348 return None 

2349 

2350 @staticmethod 

2351 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2352 """ 

2353 Creates a Circuit4 ansatz. 

2354 

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

2356 

2357 Parameters 

2358 ---------- 

2359 w : np.ndarray 

2360 Weight vector of size n_qubits*3-1 

2361 n_qubits : int 

2362 Number of qubits 

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

2364 Dictionary of noise parameters to apply to the gates 

2365 """ 

2366 w_idx = 0 

2367 for q in range(n_qubits): 

2368 Gates.RX(w[w_idx], wires=q, **kwargs) 

2369 w_idx += 1 

2370 Gates.RZ(w[w_idx], wires=q, **kwargs) 

2371 w_idx += 1 

2372 

2373 for q in range(n_qubits - 1): 

2374 Gates.CRX( 

2375 w[w_idx], 

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

2377 **kwargs, 

2378 ) 

2379 w_idx += 1 

2380 

2381 class Circuit_10(Circuit): 

2382 @staticmethod 

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

2384 """ 

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

2386 

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

2388 

2389 Parameters 

2390 ---------- 

2391 n_qubits : int 

2392 Number of qubits in the circuit 

2393 

2394 Returns 

2395 ------- 

2396 int 

2397 Number of parameters per layer 

2398 """ 

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

2400 

2401 @staticmethod 

2402 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2403 """ 

2404 Returns the number of pulse parameters per layer for Circuit_10. 

2405 

2406 This includes contributions from single-qubit rotations (`RY`) on all 

2407 qubits, controlled rotations (`CZ`) on each qubit except one if more 

2408 than one qubit is present and a final controlled rotation (`CZ`) if 

2409 more than two qubits are present. 

2410 

2411 Parameters 

2412 ---------- 

2413 n_qubits : int 

2414 Number of qubits in the circuit. 

2415 

2416 Returns 

2417 ------- 

2418 int 

2419 Number of pulse parameters required for one layer of the circuit. 

2420 """ 

2421 n_params = 2 * PulseInformation.num_params("RY") 

2422 n_params *= n_qubits 

2423 

2424 n_params += (n_qubits - 1) * PulseInformation.num_params("CZ") 

2425 

2426 n_params += PulseInformation.num_params("CZ") if n_qubits > 2 else 0 

2427 

2428 return n_params 

2429 

2430 @staticmethod 

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

2432 """ 

2433 No controlled rotation gates available. Always None. 

2434 

2435 Parameters 

2436 ---------- 

2437 n_qubits : int 

2438 Number of qubits in the circuit. 

2439 

2440 Returns 

2441 ------- 

2442 Optional[np.ndarray] 

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

2444 contain controlled rotation gates. 

2445 """ 

2446 return None 

2447 

2448 @staticmethod 

2449 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2450 """ 

2451 Creates a Circuit10 ansatz. 

2452 

2453 Length of flattened vector must be n_qubits*2 

2454 

2455 Parameters 

2456 ---------- 

2457 w : np.ndarray 

2458 Weight vector of size n_qubits*2 

2459 n_qubits : int 

2460 Number of qubits 

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

2462 Dictionary of noise parameters to apply to the gates 

2463 """ 

2464 w_idx = 0 

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

2466 for q in range(n_qubits): 

2467 Gates.RY(w[w_idx], wires=q, **kwargs) 

2468 w_idx += 1 

2469 

2470 for q in range(n_qubits - 1): 

2471 Gates.CZ( 

2472 wires=[ 

2473 (n_qubits - q - 2) % n_qubits, 

2474 (n_qubits - q - 1) % n_qubits, 

2475 ], 

2476 **kwargs, 

2477 ) 

2478 if n_qubits > 2: 

2479 Gates.CZ(wires=[n_qubits - 1, 0], **kwargs) 

2480 

2481 for q in range(n_qubits): 

2482 Gates.RY(w[w_idx], wires=q, **kwargs) 

2483 w_idx += 1 

2484 

2485 class Circuit_16(Circuit): 

2486 @staticmethod 

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

2488 """ 

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

2490 

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

2492 

2493 Parameters 

2494 ---------- 

2495 n_qubits : int 

2496 Number of qubits in the circuit 

2497 

2498 Returns 

2499 ------- 

2500 int 

2501 Number of parameters per layer 

2502 """ 

2503 return n_qubits * 3 - 1 

2504 

2505 @staticmethod 

2506 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2507 """ 

2508 Returns the number of pulse parameters per layer for Circuit_16. 

2509 

2510 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

2511 qubits, and controlled rotations (`CRZ`) if more than one qubit is present. 

2512 

2513 Parameters 

2514 ---------- 

2515 n_qubits : int 

2516 Number of qubits in the circuit. 

2517 

2518 Returns 

2519 ------- 

2520 int 

2521 Number of pulse parameters required for one layer of the circuit. 

2522 """ 

2523 n_params = PulseInformation.num_params("RX") 

2524 n_params += PulseInformation.num_params("RZ") 

2525 n_params *= n_qubits 

2526 

2527 n_CRZ = n_qubits * (n_qubits - 1) // 2 

2528 n_params += n_CRZ * PulseInformation.num_params("CRZ") 

2529 

2530 return n_params 

2531 

2532 @staticmethod 

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

2534 """ 

2535 No controlled rotation gates available. Always None. 

2536 

2537 Parameters 

2538 ---------- 

2539 n_qubits : int 

2540 Number of qubits in the circuit 

2541 

2542 Returns 

2543 ------- 

2544 Optional[np.ndarray] 

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

2546 contain controlled rotation gates. 

2547 """ 

2548 if n_qubits > 1: 

2549 return [-(n_qubits - 1), None, None] 

2550 else: 

2551 return None 

2552 

2553 @staticmethod 

2554 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2555 """ 

2556 Creates a Circuit16 ansatz. 

2557 

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

2559 

2560 Parameters 

2561 ---------- 

2562 w : np.ndarray 

2563 Weight vector of size n_qubits*3-1 

2564 n_qubits : int 

2565 Number of qubits 

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

2567 Dictionary of noise parameters to apply to the gates 

2568 """ 

2569 w_idx = 0 

2570 for q in range(n_qubits): 

2571 Gates.RX(w[w_idx], wires=q, **kwargs) 

2572 w_idx += 1 

2573 Gates.RZ(w[w_idx], wires=q, **kwargs) 

2574 w_idx += 1 

2575 

2576 if n_qubits > 1: 

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

2578 Gates.CRZ( 

2579 w[w_idx], 

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

2581 **kwargs, 

2582 ) 

2583 w_idx += 1 

2584 

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

2586 Gates.CRZ( 

2587 w[w_idx], 

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

2589 **kwargs, 

2590 ) 

2591 w_idx += 1 

2592 

2593 class Circuit_17(Circuit): 

2594 @staticmethod 

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

2596 """ 

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

2598 

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

2600 

2601 Parameters 

2602 ---------- 

2603 n_qubits : int 

2604 Number of qubits in the circuit 

2605 

2606 Returns 

2607 ------- 

2608 int 

2609 Number of parameters per layer 

2610 """ 

2611 return n_qubits * 3 - 1 

2612 

2613 @staticmethod 

2614 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2615 """ 

2616 Returns the number of pulse parameters per layer for Circuit_17. 

2617 

2618 This includes contributions from single-qubit rotations (`RX`, `RZ`) on all 

2619 qubits, and controlled rotations (`CRX`) if more than one qubit is present. 

2620 

2621 Parameters 

2622 ---------- 

2623 n_qubits : int 

2624 Number of qubits in the circuit. 

2625 

2626 Returns 

2627 ------- 

2628 int 

2629 Number of pulse parameters required for one layer of the circuit. 

2630 """ 

2631 n_params = PulseInformation.num_params("RX") 

2632 n_params += PulseInformation.num_params("RZ") 

2633 n_params *= n_qubits 

2634 

2635 n_CRZ = n_qubits * (n_qubits - 1) // 2 

2636 n_params += n_CRZ * PulseInformation.num_params("CRX") 

2637 

2638 return n_params 

2639 

2640 @staticmethod 

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

2642 """ 

2643 No controlled rotation gates available. Always None. 

2644 

2645 Parameters 

2646 ---------- 

2647 n_qubits : int 

2648 Number of qubits in the circuit 

2649 

2650 Returns 

2651 ------- 

2652 Optional[np.ndarray] 

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

2654 contain controlled rotation gates. 

2655 """ 

2656 if n_qubits > 1: 

2657 return [-(n_qubits - 1), None, None] 

2658 else: 

2659 return None 

2660 

2661 @staticmethod 

2662 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2663 """ 

2664 Creates a Circuit17 ansatz. 

2665 

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

2667 

2668 Parameters 

2669 ---------- 

2670 w : np.ndarray 

2671 Weight vector of size n_qubits*3-1 

2672 n_qubits : int 

2673 Number of qubits 

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

2675 Dictionary of noise parameters to apply to the gates 

2676 """ 

2677 w_idx = 0 

2678 for q in range(n_qubits): 

2679 Gates.RX(w[w_idx], wires=q, **kwargs) 

2680 w_idx += 1 

2681 Gates.RZ(w[w_idx], wires=q, **kwargs) 

2682 w_idx += 1 

2683 

2684 if n_qubits > 1: 

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

2686 Gates.CRX( 

2687 w[w_idx], 

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

2689 **kwargs, 

2690 ) 

2691 w_idx += 1 

2692 

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

2694 Gates.CRX( 

2695 w[w_idx], 

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

2697 **kwargs, 

2698 ) 

2699 w_idx += 1 

2700 

2701 class Strongly_Entangling(Circuit): 

2702 @staticmethod 

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

2704 """ 

2705 Returns the number of parameters per layer for the 

2706 Strongly Entangling ansatz. 

2707 

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

2709 

2710 Parameters 

2711 ---------- 

2712 n_qubits : int 

2713 Number of qubits in the circuit 

2714 

2715 Returns 

2716 ------- 

2717 int 

2718 Number of parameters per layer 

2719 """ 

2720 if n_qubits < 2: 

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

2722 return n_qubits * 6 

2723 

2724 @staticmethod 

2725 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2726 """ 

2727 Returns the number of pulse parameters per layer for Strongly_Entangling 

2728 circuit. 

2729 

2730 This includes contributions from single-qubit rotations (`Rot`) on all 

2731 qubits, and controlled rotations (`CX`) if more than one qubit is present. 

2732 

2733 Parameters 

2734 ---------- 

2735 n_qubits : int 

2736 Number of qubits in the circuit. 

2737 

2738 Returns 

2739 ------- 

2740 int 

2741 Number of pulse parameters required for one layer of the circuit. 

2742 """ 

2743 n_params = 2 * PulseInformation.num_params("Rot") 

2744 n_params *= n_qubits 

2745 

2746 if n_qubits > 1: 

2747 n_params += n_qubits * 2 * PulseInformation.num_params("CX") 

2748 

2749 return n_params 

2750 

2751 @staticmethod 

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

2753 """ 

2754 No controlled rotation gates available. Always None. 

2755 

2756 Parameters 

2757 ---------- 

2758 n_qubits : int 

2759 Number of qubits in the circuit 

2760 

2761 Returns 

2762 ------- 

2763 Optional[np.ndarray] 

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

2765 contain controlled rotation gates. 

2766 """ 

2767 return None 

2768 

2769 @staticmethod 

2770 def build(w: np.ndarray, n_qubits: int, **kwargs) -> None: 

2771 """ 

2772 Creates a Strongly Entangling ansatz. 

2773 

2774 Length of flattened vector must be n_qubits*6 

2775 

2776 Parameters 

2777 ---------- 

2778 w : np.ndarray 

2779 Weight vector of size n_qubits*6 

2780 n_qubits : int 

2781 Number of qubits 

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

2783 Dictionary of noise parameters to apply to the gates 

2784 """ 

2785 w_idx = 0 

2786 for q in range(n_qubits): 

2787 Gates.Rot( 

2788 w[w_idx], 

2789 w[w_idx + 1], 

2790 w[w_idx + 2], 

2791 wires=q, 

2792 **kwargs, 

2793 ) 

2794 w_idx += 3 

2795 

2796 if n_qubits > 1: 

2797 for q in range(n_qubits): 

2798 Gates.CX(wires=[q, (q + 1) % n_qubits], **kwargs) 

2799 

2800 for q in range(n_qubits): 

2801 Gates.Rot( 

2802 w[w_idx], 

2803 w[w_idx + 1], 

2804 w[w_idx + 2], 

2805 wires=q, 

2806 **kwargs, 

2807 ) 

2808 w_idx += 3 

2809 

2810 if n_qubits > 1: 

2811 for q in range(n_qubits): 

2812 Gates.CX( 

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

2814 **kwargs, 

2815 ) 

2816 

2817 class No_Entangling(Circuit): 

2818 @staticmethod 

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

2820 """ 

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

2822 

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

2824 

2825 Parameters 

2826 ---------- 

2827 n_qubits : int 

2828 Number of qubits in the circuit 

2829 

2830 Returns 

2831 ------- 

2832 int 

2833 Number of parameters per layer 

2834 """ 

2835 return n_qubits * 3 

2836 

2837 @staticmethod 

2838 def n_pulse_params_per_layer(n_qubits: int) -> int: 

2839 """ 

2840 Returns the number of pulse parameters per layer for No_Entangling circuit. 

2841 

2842 This includes contributions from single-qubit rotations (`Rot`) on all 

2843 qubits only. 

2844 

2845 Parameters 

2846 ---------- 

2847 n_qubits : int 

2848 Number of qubits in the circuit. 

2849 

2850 Returns 

2851 ------- 

2852 int 

2853 Number of pulse parameters required for one layer of the circuit. 

2854 """ 

2855 n_params = PulseInformation.num_params("Rot") 

2856 n_params *= n_qubits 

2857 

2858 return n_params 

2859 

2860 @staticmethod 

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

2862 """ 

2863 No controlled rotation gates available. Always None. 

2864 

2865 Parameters 

2866 ---------- 

2867 n_qubits : int 

2868 Number of qubits in the circuit 

2869 

2870 Returns 

2871 ------- 

2872 Optional[np.ndarray] 

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

2874 contain controlled rotation gates. 

2875 """ 

2876 return None 

2877 

2878 @staticmethod 

2879 def build(w: np.ndarray, n_qubits: int, **kwargs): 

2880 """ 

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

2882 

2883 Length of flattened vector must be n_qubits*3 

2884 

2885 Parameters 

2886 ---------- 

2887 w : np.ndarray 

2888 Weight vector of size n_qubits*3 

2889 n_qubits : int 

2890 Number of qubits 

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

2892 Dictionary of noise parameters to apply to the gates 

2893 """ 

2894 w_idx = 0 

2895 for q in range(n_qubits): 

2896 Gates.Rot( 

2897 w[w_idx], 

2898 w[w_idx + 1], 

2899 w[w_idx + 2], 

2900 wires=q, 

2901 **kwargs, 

2902 ) 

2903 w_idx += 3