Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"). You 

4# may not use this file except in compliance with the License. A copy of 

5# the License is located at 

6# 

7# http://aws.amazon.com/apache2.0/ 

8# 

9# or in the "license" file accompanying this file. This file is 

10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 

11# ANY KIND, either express or implied. See the License for the specific 

12# language governing permissions and limitations under the License. 

13from __future__ import annotations 

14 

15import functools 

16import itertools 

17import math 

18from typing import Dict, List, Tuple, Union 

19 

20import numpy as np 

21from braket.circuits.gate import Gate 

22from braket.circuits.observable import Observable, StandardObservable 

23from braket.circuits.quantum_operator_helpers import ( 

24 get_pauli_eigenvalues, 

25 is_hermitian, 

26 verify_quantum_operator_matrix_dimensions, 

27) 

28 

29 

30class H(StandardObservable): 

31 """Hadamard operation as an observable.""" 

32 

33 def __init__(self): 

34 """ 

35 Examples: 

36 >>> Observable.I() 

37 """ 

38 super().__init__(qubit_count=1, ascii_symbols=["H"]) 

39 

40 def to_ir(self) -> List[str]: 

41 return ["h"] 

42 

43 def to_matrix(self) -> np.ndarray: 

44 return 1.0 / np.sqrt(2.0) * np.array([[1.0, 1.0], [1.0, -1.0]], dtype=complex) 

45 

46 @property 

47 def basis_rotation_gates(self) -> Tuple[Gate]: 

48 return tuple([Gate.Ry(-math.pi / 4)]) 

49 

50 

51Observable.register_observable(H) 

52 

53 

54class I(Observable): # noqa: E742, E261 

55 """Identity operation as an observable.""" 

56 

57 def __init__(self): 

58 """ 

59 Examples: 

60 >>> Observable.I() 

61 """ 

62 super().__init__(qubit_count=1, ascii_symbols=["I"]) 

63 

64 def to_ir(self) -> List[str]: 

65 return ["i"] 

66 

67 def to_matrix(self) -> np.ndarray: 

68 return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) 

69 

70 @property 

71 def basis_rotation_gates(self) -> Tuple[Gate]: 

72 return () 

73 

74 @property 

75 def eigenvalues(self) -> np.ndarray: 

76 return np.array([1, 1]) 

77 

78 

79Observable.register_observable(I) 

80 

81 

82class X(StandardObservable): 

83 """Pauli-X operation as an observable.""" 

84 

85 def __init__(self): 

86 """ 

87 Examples: 

88 >>> Observable.X() 

89 """ 

90 super().__init__(qubit_count=1, ascii_symbols=["X"]) 

91 

92 def to_ir(self) -> List[str]: 

93 return ["x"] 

94 

95 def to_matrix(self) -> np.ndarray: 

96 return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) 

97 

98 @property 

99 def basis_rotation_gates(self) -> Tuple[Gate]: 

100 return tuple([Gate.H()]) 

101 

102 

103Observable.register_observable(X) 

104 

105 

106class Y(StandardObservable): 

107 """Pauli-Y operation as an observable.""" 

108 

109 def __init__(self): 

110 """ 

111 Examples: 

112 >>> Observable.Y() 

113 """ 

114 super().__init__(qubit_count=1, ascii_symbols=["Y"]) 

115 

116 def to_ir(self) -> List[str]: 

117 return ["y"] 

118 

119 def to_matrix(self) -> np.ndarray: 

120 return np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) 

121 

122 @property 

123 def basis_rotation_gates(self) -> Tuple[Gate]: 

124 return tuple([Gate.Z(), Gate.S(), Gate.H()]) 

125 

126 

127Observable.register_observable(Y) 

128 

129 

130class Z(StandardObservable): 

131 """Pauli-Z operation as an observable.""" 

132 

133 def __init__(self): 

134 """ 

135 Examples: 

136 >>> Observable.Z() 

137 """ 

138 super().__init__(qubit_count=1, ascii_symbols=["Z"]) 

139 

140 def to_ir(self) -> List[str]: 

141 return ["z"] 

142 

143 def to_matrix(self) -> np.ndarray: 

144 return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) 

145 

146 @property 

147 def basis_rotation_gates(self) -> Tuple[Gate]: 

148 return () 

149 

150 

151Observable.register_observable(Z) 

152 

153 

154class TensorProduct(Observable): 

155 """Tensor product of observables""" 

156 

157 def __init__(self, observables: List[Observable]): 

158 """ 

159 Args: 

160 observables (List[Observable]): List of observables for tensor product 

161 

162 Examples: 

163 >>> t1 = Observable.Y() @ Observable.X() 

164 >>> t1.to_matrix() 

165 array([[0.+0.j, 0.+0.j, 0.-0.j, 0.-1.j], 

166 [0.+0.j, 0.+0.j, 0.-1.j, 0.-0.j], 

167 [0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j], 

168 [0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j]]) 

169 >>> t2 = Observable.Z() @ t1 

170 >>> t2.observables 

171 (Z('qubit_count': 1), Y('qubit_count': 1), X('qubit_count': 1)) 

172 

173 Note: list of observables for tensor product must be given in the desired order that 

174 the tensor product will be calculated. For `TensorProduct(observables=[ob1, ob2, ob3])`, 

175 the tensor product's matrix will be the result of the tensor product of `ob1`, `ob2`, 

176 `ob3`, or `np.kron(np.kron(ob1.to_matrix(), ob2.to_matrix()), ob3.to_matrix())` 

177 """ 

178 self._observables = tuple(observables) 

179 qubit_count = sum([obs.qubit_count for obs in observables]) 

180 display_name = "@".join([obs.ascii_symbols[0] for obs in observables]) 

181 super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) 

182 self._eigenvalues = TensorProduct._compute_eigenvalues(self._observables, qubit_count) 

183 

184 def to_ir(self) -> List[str]: 

185 ir = [] 

186 for obs in self.observables: 

187 ir.extend(obs.to_ir()) 

188 return ir 

189 

190 @property 

191 def observables(self) -> Tuple[Observable]: 

192 """Tuple[Observable]: observables part of tensor product""" 

193 return self._observables 

194 

195 def to_matrix(self) -> np.ndarray: 

196 return functools.reduce(np.kron, [obs.to_matrix() for obs in self.observables]) 

197 

198 @property 

199 def basis_rotation_gates(self) -> Tuple[Gate]: 

200 gates = [] 

201 for obs in self.observables: 

202 gates.extend(obs.basis_rotation_gates) 

203 return tuple(gates) 

204 

205 @property 

206 def eigenvalues(self): 

207 return self._eigenvalues 

208 

209 def __matmul__(self, other): 

210 if isinstance(other, TensorProduct): 

211 return TensorProduct(list(self.observables) + list(other.observables)) 

212 

213 if isinstance(other, Observable): 

214 return TensorProduct(list(self.observables) + [other]) 

215 

216 raise ValueError("Can only perform tensor products between observables.") 

217 

218 def __rmatmul__(self, other): 

219 if isinstance(other, Observable): 

220 return TensorProduct([other] + list(self.observables)) 

221 

222 raise ValueError("Can only perform tensor products between observables.") 

223 

224 def __repr__(self): 

225 return "TensorProduct(" + ", ".join([repr(o) for o in self.observables]) + ")" 

226 

227 def __eq__(self, other): 

228 return self.matrix_equivalence(other) 

229 

230 @staticmethod 

231 def _compute_eigenvalues(observables: Tuple[Observable], num_qubits: int) -> np.ndarray: 

232 if False in [isinstance(observable, StandardObservable) for observable in observables]: 

233 # Tensor product of observables contains a mixture 

234 # of standard and non-standard observables 

235 eigenvalues = np.array([1]) 

236 for k, g in itertools.groupby(observables, lambda x: isinstance(x, StandardObservable)): 

237 if k: 

238 # Subgroup g contains only standard observables. 

239 eigenvalues = np.kron(eigenvalues, get_pauli_eigenvalues(len(list(g)))) 

240 else: 

241 # Subgroup g contains only non-standard observables. 

242 for nonstandard in g: 

243 # loop through all non-standard observables 

244 eigenvalues = np.kron(eigenvalues, nonstandard.eigenvalues) 

245 else: 

246 eigenvalues = get_pauli_eigenvalues(num_qubits=num_qubits) 

247 

248 eigenvalues.setflags(write=False) 

249 return eigenvalues 

250 

251 

252Observable.register_observable(TensorProduct) 

253 

254 

255class Hermitian(Observable): 

256 """Hermitian matrix as an observable.""" 

257 

258 # Cache of eigenpairs 

259 _eigenpairs = {} 

260 

261 def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): 

262 """ 

263 Args: 

264 matrix (numpy.ndarray): Hermitian matrix which defines the observable. 

265 display_name (str): Name to be used for an instance of this Hermitian matrix 

266 observable for circuit diagrams. Defaults to `Hermitian`. 

267 

268 Raises: 

269 ValueError: If `matrix` is not a two-dimensional square matrix, 

270 or has a dimension length which is not a positive exponent of 2, 

271 or is non-hermitian. 

272 

273 Example: 

274 >>> Observable.Hermitian(matrix=np.array([[0, 1],[1, 0]])) 

275 """ 

276 verify_quantum_operator_matrix_dimensions(matrix) 

277 self._matrix = np.array(matrix, dtype=complex) 

278 qubit_count = int(np.log2(self._matrix.shape[0])) 

279 

280 if not is_hermitian(self._matrix): 

281 raise ValueError(f"{self._matrix} is not hermitian") 

282 

283 super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) 

284 

285 def to_ir(self) -> List[List[List[List[float]]]]: 

286 return [ 

287 [[[element.real, element.imag] for element in row] for row in self._matrix.tolist()] 

288 ] 

289 

290 def to_matrix(self) -> np.ndarray: 

291 return self._matrix 

292 

293 def __eq__(self, other) -> bool: 

294 return self.matrix_equivalence(other) 

295 

296 @property 

297 def basis_rotation_gates(self) -> Tuple[Gate]: 

298 return tuple([Gate.Unitary(matrix=self._get_eigendecomposition()["eigenvectors_conj_t"])]) 

299 

300 @property 

301 def eigenvalues(self): 

302 return self._get_eigendecomposition()["eigenvalues"] 

303 

304 def _get_eigendecomposition(self) -> Dict[str, np.ndarray]: 

305 """ 

306 Decomposes the Hermitian matrix into its eigenvectors and associated eigenvalues. 

307 The eigendecomposition is cached so that if another Hermitian observable 

308 is created with the same matrix, the eigendecomposition doesn't have to 

309 be recalculated. 

310 

311 Returns: 

312 Dict[str, np.ndarray]: The keys are "eigenvectors_conj_t", mapping to the 

313 conjugate transpose of a matrix whose columns are the eigenvectors of the matrix, 

314 and "eigenvalues", a list of associated eigenvalues in the order of their 

315 corresponding eigenvectors in the "eigenvectors" matrix. These cached values 

316 are immutable. 

317 """ 

318 mat_key = tuple(self._matrix.flatten().tolist()) 

319 if mat_key not in Hermitian._eigenpairs: 

320 eigenvalues, eigenvectors = np.linalg.eigh(self._matrix) 

321 eigenvalues.setflags(write=False) 

322 eigenvectors_conj_t = eigenvectors.conj().T 

323 eigenvectors_conj_t.setflags(write=False) 

324 Hermitian._eigenpairs[mat_key] = { 

325 "eigenvectors_conj_t": eigenvectors_conj_t, 

326 "eigenvalues": eigenvalues, 

327 } 

328 return Hermitian._eigenpairs[mat_key] 

329 

330 

331Observable.register_observable(Hermitian) 

332 

333 

334def observable_from_ir(ir_observable: List[Union[str, List[List[List[float]]]]]) -> Observable: 

335 """ 

336 Create an observable from the IR observable list. This can be a tensor product of 

337 observables or a single observable. 

338 

339 Args: 

340 ir_observable (List[Union[str, List[List[List[float]]]]]): observable as defined in IR 

341 

342 Return: 

343 Observable: observable object 

344 """ 

345 if len(ir_observable) == 1: 

346 return _observable_from_ir_list_item(ir_observable[0]) 

347 else: 

348 observable = TensorProduct([_observable_from_ir_list_item(obs) for obs in ir_observable]) 

349 return observable 

350 

351 

352def _observable_from_ir_list_item(observable: Union[str, List[List[List[float]]]]) -> Observable: 

353 if observable == "i": 

354 return I() 

355 elif observable == "h": 

356 return H() 

357 elif observable == "x": 

358 return X() 

359 elif observable == "y": 

360 return Y() 

361 elif observable == "z": 

362 return Z() 

363 else: 

364 try: 

365 matrix = np.array( 

366 [[complex(element[0], element[1]) for element in row] for row in observable] 

367 ) 

368 return Hermitian(matrix) 

369 except Exception as e: 

370 raise ValueError(f"Invalid observable specified: {observable} error: {e}")