Coverage for qml_essentials/ansaetze.py: 84%

228 statements  

« prev     ^ index     » next       coverage.py v7.6.5, created at 2024-11-15 11:13 +0000

1from abc import ABC, abstractmethod 

2from typing import Any, Optional 

3import pennylane.numpy as np 

4import pennylane as qml 

5 

6from typing import List 

7 

8import logging 

9 

10log = logging.getLogger(__name__) 

11 

12 

13class Circuit(ABC): 

14 def __init__(self): 

15 pass 

16 

17 @abstractmethod 

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

19 return 

20 

21 @abstractmethod 

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

23 """ 

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

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

26 [indices[0]:indices[1]:indices[2]] 

27 

28 Parameters 

29 ---------- 

30 n_qubits : int 

31 Number of qubits in the circuit 

32 

33 Returns 

34 ------- 

35 Optional[np.ndarray] 

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

37 contain controlled rotation gates. 

38 """ 

39 return 

40 

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

42 """ 

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

44 all parameters for one layer. 

45 

46 Parameters 

47 ---------- 

48 w : np.ndarray 

49 List of parameters for one layer 

50 n_qubits : int 

51 Number of qubits in the circuit 

52 

53 Returns 

54 ------- 

55 Optional[np.ndarray] 

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

57 contain controlled rotation gates. 

58 """ 

59 indices = self.get_control_indices(n_qubits) 

60 if indices is None: 

61 return None 

62 

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

64 

65 @abstractmethod 

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

67 return 

68 

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

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

71 

72 

73class Ansaetze: 

74 def get_available(): 

75 return [ 

76 Ansaetze.No_Ansatz, 

77 Ansaetze.Circuit_1, 

78 Ansaetze.Circuit_6, 

79 Ansaetze.Circuit_9, 

80 Ansaetze.Circuit_15, 

81 Ansaetze.Circuit_18, 

82 Ansaetze.Circuit_19, 

83 Ansaetze.No_Entangling, 

84 Ansaetze.Strongly_Entangling, 

85 Ansaetze.Hardware_Efficient, 

86 ] 

87 

88 class No_Ansatz(Circuit): 

89 @staticmethod 

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

91 return 0 

92 

93 @staticmethod 

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

95 return None 

96 

97 @staticmethod 

98 def build(w: np.ndarray, n_qubits: int): 

99 pass 

100 

101 class Hardware_Efficient(Circuit): 

102 @staticmethod 

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

104 if n_qubits > 1: 

105 return n_qubits * 3 

106 else: 

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

108 return 3 

109 

110 @staticmethod 

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

112 return None 

113 

114 @staticmethod 

115 def build(w: np.ndarray, n_qubits: int): 

116 """ 

117 Creates a Hardware-Efficient ansatz, as proposed in 

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

119 

120 Length of flattened vector must be n_qubits*3 

121 

122 Args: 

123 w (np.ndarray): weight vector of size n_layers*(n_qubits*3) 

124 n_qubits (int): number of qubits 

125 """ 

126 w_idx = 0 

127 for q in range(n_qubits): 

128 qml.RX(w[w_idx], wires=q) 

129 w_idx += 1 

130 qml.RY(w[w_idx], wires=q) 

131 w_idx += 1 

132 qml.RX(w[w_idx], wires=q) 

133 w_idx += 1 

134 

135 if n_qubits > 1: 

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

137 qml.CZ(wires=[(2 * q), (2 * q + 1)]) 

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

139 qml.CZ(wires=[(2 * q + 1), (2 * q + 2)]) 

140 

141 class Circuit_19(Circuit): 

142 @staticmethod 

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

144 if n_qubits > 1: 

145 return n_qubits * 3 

146 else: 

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

148 return 2 

149 

150 @staticmethod 

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

152 if n_qubits > 1: 

153 return [-n_qubits, None, None] 

154 else: 

155 return None 

156 

157 @staticmethod 

158 def build(w: np.ndarray, n_qubits: int): 

159 """ 

160 Creates a Circuit19 ansatz. 

161 

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

163 because for >1 qubits there are three gates 

164 

165 Args: 

166 w (np.ndarray): weight vector of size n_layers*(n_qubits*3-1) 

167 n_qubits (int): number of qubits 

168 """ 

169 w_idx = 0 

170 for q in range(n_qubits): 

171 qml.RX(w[w_idx], wires=q) 

172 w_idx += 1 

173 qml.RZ(w[w_idx], wires=q) 

174 w_idx += 1 

175 

176 if n_qubits > 1: 

177 for q in range(n_qubits): 

178 qml.CRX( 

179 w[w_idx], 

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

181 ) 

182 w_idx += 1 

183 

184 class Circuit_18(Circuit): 

185 @staticmethod 

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

187 if n_qubits > 1: 

188 return n_qubits * 3 

189 else: 

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

191 return 2 

192 

193 @staticmethod 

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

195 if n_qubits > 1: 

196 return [-n_qubits, None, None] 

197 else: 

198 return None 

199 

200 @staticmethod 

201 def build(w: np.ndarray, n_qubits: int): 

202 """ 

203 Creates a Circuit18 ansatz. 

204 

205 Length of flattened vector must be n_qubits*3 

206 

207 Args: 

208 w (np.ndarray): weight vector of size n_layers*(n_qubits*3) 

209 n_qubits (int): number of qubits 

210 """ 

211 w_idx = 0 

212 for q in range(n_qubits): 

213 qml.RX(w[w_idx], wires=q) 

214 w_idx += 1 

215 qml.RZ(w[w_idx], wires=q) 

216 w_idx += 1 

217 

218 if n_qubits > 1: 

219 for q in range(n_qubits): 

220 qml.CRZ( 

221 w[w_idx], 

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

223 ) 

224 w_idx += 1 

225 

226 class Circuit_15(Circuit): 

227 @staticmethod 

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

229 if n_qubits > 1: 

230 return n_qubits * 2 

231 else: 

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

233 return 2 

234 

235 @staticmethod 

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

237 return None 

238 

239 @staticmethod 

240 def build(w: np.ndarray, n_qubits: int): 

241 """ 

242 Creates a Circuit15 ansatz. 

243 

244 Length of flattened vector must be n_qubits*2 

245 because for >1 qubits there are three gates 

246 

247 Args: 

248 w (np.ndarray): weight vector of size n_layers*(n_qubits*2) 

249 n_qubits (int): number of qubits 

250 """ 

251 raise NotImplementedError # Did not figured out the entangling sequence yet 

252 

253 w_idx = 0 

254 for q in range(n_qubits): 

255 qml.RX(w[w_idx], wires=q) 

256 w_idx += 1 

257 

258 if n_qubits > 1: 

259 for q in range(n_qubits): 

260 qml.CNOT(wires=[n_qubits - q - 1, (n_qubits - q) % n_qubits]) 

261 

262 for q in range(n_qubits): 

263 qml.RZ(w[w_idx], wires=q) 

264 w_idx += 1 

265 

266 class Circuit_9(Circuit): 

267 @staticmethod 

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

269 return n_qubits 

270 

271 @staticmethod 

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

273 return None 

274 

275 @staticmethod 

276 def build(w: np.ndarray, n_qubits: int): 

277 """ 

278 Creates a Circuit9 ansatz. 

279 

280 Length of flattened vector must be n_qubits 

281 

282 Args: 

283 w (np.ndarray): weight vector of size n_layers*n_qubits 

284 n_qubits (int): number of qubits 

285 """ 

286 w_idx = 0 

287 for q in range(n_qubits): 

288 qml.Hadamard(wires=q) 

289 

290 if n_qubits > 1: 

291 for q in range(n_qubits - 1): 

292 qml.CZ(wires=[n_qubits - q - 2, n_qubits - q - 1]) 

293 

294 for q in range(n_qubits): 

295 qml.RX(w[w_idx], wires=q) 

296 w_idx += 1 

297 

298 class Circuit_6(Circuit): 

299 @staticmethod 

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

301 if n_qubits > 1: 

302 return n_qubits * 3 + n_qubits**2 

303 else: 

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

305 return 4 

306 

307 @staticmethod 

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

309 if n_qubits > 1: 

310 return [-n_qubits, None, None] 

311 else: 

312 return None 

313 

314 @staticmethod 

315 def build(w: np.ndarray, n_qubits: int): 

316 """ 

317 Creates a Circuit6 ansatz. 

318 

319 Length of flattened vector must be 

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

321 n_qubits * 3 + n_qubits**2 

322 

323 Args: 

324 w (np.ndarray): weight vector of size 

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

326 n_qubits (int): number of qubits 

327 """ 

328 w_idx = 0 

329 for q in range(n_qubits): 

330 qml.RX(w[w_idx], wires=q) 

331 w_idx += 1 

332 qml.RZ(w[w_idx], wires=q) 

333 w_idx += 1 

334 

335 if n_qubits > 1: 

336 for ql in range(n_qubits): 

337 for q in range(n_qubits): 

338 if q == ql: 

339 continue 

340 qml.CRX( 

341 w[w_idx], 

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

343 ) 

344 w_idx += 1 

345 

346 for q in range(n_qubits): 

347 qml.RX(w[w_idx], wires=q) 

348 w_idx += 1 

349 qml.RZ(w[w_idx], wires=q) 

350 w_idx += 1 

351 

352 class Circuit_1(Circuit): 

353 @staticmethod 

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

355 return n_qubits * 2 

356 

357 @staticmethod 

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

359 return None 

360 

361 @staticmethod 

362 def build(w: np.ndarray, n_qubits: int): 

363 """ 

364 Creates a Circuit1 ansatz. 

365 

366 Length of flattened vector must be n_qubits*2 

367 

368 Args: 

369 w (np.ndarray): weight vector of size n_layers*(n_qubits*2) 

370 n_qubits (int): number of qubits 

371 """ 

372 w_idx = 0 

373 for q in range(n_qubits): 

374 qml.RX(w[w_idx], wires=q) 

375 w_idx += 1 

376 qml.RZ(w[w_idx], wires=q) 

377 w_idx += 1 

378 

379 class Strongly_Entangling(Circuit): 

380 @staticmethod 

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

382 if n_qubits > 1: 

383 return n_qubits * 6 

384 else: 

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

386 return 2 

387 

388 @staticmethod 

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

390 return None 

391 

392 @staticmethod 

393 def build(w: np.ndarray, n_qubits: int) -> None: 

394 """ 

395 Creates a StronglyEntanglingLayers ansatz. 

396 

397 Args: 

398 w (np.ndarray): weight vector of size n_layers*(n_qubits*6) 

399 n_qubits (int): number of qubits 

400 """ 

401 w_idx = 0 

402 for q in range(n_qubits): 

403 qml.Rot(w[w_idx], w[w_idx + 1], w[w_idx + 2], wires=q) 

404 w_idx += 3 

405 

406 if n_qubits > 1: 

407 for q in range(n_qubits): 

408 qml.CNOT(wires=[q, (q + 1) % n_qubits]) 

409 

410 for q in range(n_qubits): 

411 qml.Rot(w[w_idx], w[w_idx + 1], w[w_idx + 2], wires=q) 

412 w_idx += 3 

413 

414 if n_qubits > 1: 

415 for q in range(n_qubits): 

416 qml.CNOT(wires=[q, (q + n_qubits // 2) % n_qubits]) 

417 

418 class No_Entangling(Circuit): 

419 @staticmethod 

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

421 return n_qubits * 3 

422 

423 @staticmethod 

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

425 return None 

426 

427 @staticmethod 

428 def build(w: np.ndarray, n_qubits: int): 

429 """ 

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

431 

432 Length of flattened vector must be n_qubits*3 

433 

434 Args: 

435 w (np.ndarray): weight vector of size n_layers*(n_qubits*3) 

436 n_qubits (int): number of qubits 

437 """ 

438 w_idx = 0 

439 for q in range(n_qubits): 

440 qml.Rot(w[w_idx], w[w_idx + 1], w[w_idx + 2], wires=q) 

441 w_idx += 3