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. 

13 

14from __future__ import annotations 

15 

16import functools 

17import itertools 

18import math 

19from typing import Dict, List, Tuple, Union 

20 

21import numpy as np 

22from braket.circuits.gate import Gate 

23from braket.circuits.observable import Observable, StandardObservable 

24from braket.circuits.quantum_operator_helpers import ( 

25 get_pauli_eigenvalues, 

26 is_hermitian, 

27 verify_quantum_operator_matrix_dimensions, 

28) 

29 

30 

31class H(StandardObservable): 

32 """Hadamard operation as an observable.""" 

33 

34 def __init__(self): 

35 """ 

36 Examples: 

37 >>> Observable.I() 

38 """ 

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

40 

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

42 return ["h"] 

43 

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

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

46 

47 @property 

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

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

50 

51 

52Observable.register_observable(H) 

53 

54 

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

56 """Identity operation as an observable.""" 

57 

58 def __init__(self): 

59 """ 

60 Examples: 

61 >>> Observable.I() 

62 """ 

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

64 

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

66 return ["i"] 

67 

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

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

70 

71 @property 

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

73 return () 

74 

75 @property 

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

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

78 

79 

80Observable.register_observable(I) 

81 

82 

83class X(StandardObservable): 

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

85 

86 def __init__(self): 

87 """ 

88 Examples: 

89 >>> Observable.X() 

90 """ 

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

92 

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

94 return ["x"] 

95 

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

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

98 

99 @property 

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

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

102 

103 

104Observable.register_observable(X) 

105 

106 

107class Y(StandardObservable): 

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

109 

110 def __init__(self): 

111 """ 

112 Examples: 

113 >>> Observable.Y() 

114 """ 

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

116 

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

118 return ["y"] 

119 

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

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

122 

123 @property 

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

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

126 

127 

128Observable.register_observable(Y) 

129 

130 

131class Z(StandardObservable): 

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

133 

134 def __init__(self): 

135 """ 

136 Examples: 

137 >>> Observable.Z() 

138 """ 

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

140 

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

142 return ["z"] 

143 

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

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

146 

147 @property 

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

149 return () 

150 

151 

152Observable.register_observable(Z) 

153 

154 

155class TensorProduct(Observable): 

156 """Tensor product of observables""" 

157 

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

159 """ 

160 Args: 

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

162 

163 Examples: 

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

165 >>> t1.to_matrix() 

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

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

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

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

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

171 >>> t2.factors 

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

173 

174 Note: You must provide the list of observables for the tensor product to be evaluated 

175 in the order that you want the tensor product to be calculated. 

176 For `TensorProduct(observables=[ob1, ob2, ob3])`, the tensor product's matrix is the 

177 result of the tensor product of `ob1`, `ob2`, `ob3`, or `np.kron(np.kron(ob1.to_matrix(), 

178 ob2.to_matrix()), ob3.to_matrix())`. 

179 """ 

180 self._observables = tuple(observables) 

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

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

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

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

185 

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

187 ir = [] 

188 for obs in self.factors: 

189 ir.extend(obs.to_ir()) 

190 return ir 

191 

192 @property 

193 def factors(self) -> Tuple[Observable]: 

194 """ Tuple[Observable]: The observables that comprise this tensor product.""" 

195 return self._observables 

196 

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

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

199 

200 @property 

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

202 gates = [] 

203 for obs in self.factors: 

204 gates.extend(obs.basis_rotation_gates) 

205 return tuple(gates) 

206 

207 @property 

208 def eigenvalues(self): 

209 return self._eigenvalues 

210 

211 def __matmul__(self, other): 

212 if isinstance(other, TensorProduct): 

213 return TensorProduct(list(self.factors) + list(other.factors)) 

214 

215 if isinstance(other, Observable): 

216 return TensorProduct(list(self.factors) + [other]) 

217 

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

219 

220 def __rmatmul__(self, other): 

221 if isinstance(other, Observable): 

222 return TensorProduct([other] + list(self.factors)) 

223 

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

225 

226 def __repr__(self): 

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

228 

229 def __eq__(self, other): 

230 return self.matrix_equivalence(other) 

231 

232 @staticmethod 

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

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

235 # Tensor product of observables contains a mixture 

236 # of standard and non-standard observables 

237 eigenvalues = np.array([1]) 

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

239 if k: 

240 # Subgroup g contains only standard observables. 

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

242 else: 

243 # Subgroup g contains only non-standard observables. 

244 for nonstandard in g: 

245 # loop through all non-standard observables 

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

247 else: 

248 eigenvalues = get_pauli_eigenvalues(num_qubits=num_qubits) 

249 

250 eigenvalues.setflags(write=False) 

251 return eigenvalues 

252 

253 

254Observable.register_observable(TensorProduct) 

255 

256 

257class Hermitian(Observable): 

258 """Hermitian matrix as an observable.""" 

259 

260 # Cache of eigenpairs 

261 _eigenpairs = {} 

262 

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

264 """ 

265 Args: 

266 matrix (numpy.ndarray): Hermitian matrix that defines the observable. 

267 display_name (str): Name to use for an instance of this Hermitian matrix 

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

269 

270 Raises: 

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

272 or has a dimension length that is not a positive exponent of 2, 

273 or is non-hermitian. 

274 

275 Example: 

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

277 """ 

278 verify_quantum_operator_matrix_dimensions(matrix) 

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

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

281 

282 if not is_hermitian(self._matrix): 

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

284 

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

286 

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

288 return [ 

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

290 ] 

291 

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

293 return self._matrix 

294 

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

296 return self.matrix_equivalence(other) 

297 

298 @property 

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

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

301 

302 @property 

303 def eigenvalues(self): 

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

305 

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

307 """ 

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

309 The eigendecomposition is cached so that if another Hermitian observable 

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

311 be recalculated. 

312 

313 Returns: 

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

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

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

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

318 are immutable. 

319 """ 

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

321 if mat_key not in Hermitian._eigenpairs: 

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

323 eigenvalues.setflags(write=False) 

324 eigenvectors_conj_t = eigenvectors.conj().T 

325 eigenvectors_conj_t.setflags(write=False) 

326 Hermitian._eigenpairs[mat_key] = { 

327 "eigenvectors_conj_t": eigenvectors_conj_t, 

328 "eigenvalues": eigenvalues, 

329 } 

330 return Hermitian._eigenpairs[mat_key] 

331 

332 

333Observable.register_observable(Hermitian) 

334 

335 

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

337 """ 

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

339 observables or a single observable. 

340 

341 Args: 

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

343 

344 Return: 

345 Observable: observable object 

346 """ 

347 if len(ir_observable) == 1: 

348 return _observable_from_ir_list_item(ir_observable[0]) 

349 else: 

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

351 return observable 

352 

353 

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

355 if observable == "i": 

356 return I() 

357 elif observable == "h": 

358 return H() 

359 elif observable == "x": 

360 return X() 

361 elif observable == "y": 

362 return Y() 

363 elif observable == "z": 

364 return Z() 

365 else: 

366 try: 

367 matrix = np.array( 

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

369 ) 

370 return Hermitian(matrix) 

371 except Exception as e: 

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