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 

16from typing import Callable, Dict, Iterable, TypeVar 

17 

18from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram 

19from braket.circuits.instruction import Instruction 

20from braket.circuits.moments import Moments 

21from braket.circuits.qubit import QubitInput 

22from braket.circuits.qubit_set import QubitSet, QubitSetInput 

23from braket.ir.jaqcd import Program 

24 

25SubroutineReturn = TypeVar("SubroutineReturn", Iterable[Instruction], Instruction) 

26SubroutineCallable = TypeVar("SubroutineCallable", bound=Callable[..., SubroutineReturn]) 

27AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) 

28 

29# TODO: Add parameterization 

30 

31 

32class Circuit: 

33 """ 

34 A representation of a quantum circuit that contains the instructions to be performed on a 

35 quantum device. See :mod:`braket.circuits.gates` module for all of the supported instructions. 

36 """ 

37 

38 @classmethod 

39 def register_subroutine(cls, func: SubroutineCallable) -> None: 

40 """ 

41 Register the subroutine `func` as an attribute of the `Circuit` class. The attribute name 

42 is the name of `func`. 

43 

44 Args: 

45 func (Callable[..., Union[Instruction, Iterable[Instruction]]]): The function of the 

46 subroutine to add to the class. 

47 

48 Examples: 

49 >>> def h_on_all(target): 

50 ... circ = Circuit() 

51 ... for qubit in target: 

52 ... circ += Instruction(Gate.H(), qubit) 

53 ... return circ 

54 ... 

55 >>> Circuit.register_subroutine(h_on_all) 

56 >>> circ = Circuit().h_on_all(range(2)) 

57 >>> for instr in circ.instructions: 

58 ... print(instr) 

59 ... 

60 Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) 

61 Instruction('operator': 'H', 'target': QubitSet(Qubit(1),)) 

62 """ 

63 

64 def method_from_subroutine(self, *args, **kwargs) -> SubroutineReturn: 

65 return self.add(func, *args, **kwargs) 

66 

67 function_name = func.__name__ 

68 setattr(cls, function_name, method_from_subroutine) 

69 

70 function_attr = getattr(cls, function_name) 

71 setattr(function_attr, "__doc__", func.__doc__) 

72 

73 def __init__(self, addable: AddableTypes = None, *args, **kwargs): 

74 """ 

75 Args: 

76 addable (Instruction, iterable of `Instruction`, or `SubroutineCallable`, optional): The 

77 instruction-like item(s) to add to self. Default = None. 

78 *args: Variable length argument list. Supports any arguments that `add()` offers. 

79 **kwargs: Arbitrary keyword arguments. Supports any keyword arguments that `add()` 

80 offers. 

81 

82 Raises: 

83 TypeError: If `addable` is an unsupported type. 

84 

85 Examples: 

86 >>> circ = Circuit([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) 

87 >>> circ = Circuit().h(0).cnot(0, 1) 

88 

89 >>> @circuit.subroutine(register=True) 

90 >>> def bell_pair(target): 

91 ... return Circ().h(target[0]).cnot(target[0:2]) 

92 ... 

93 >>> circ = Circuit(bell_pair, [4,5]) 

94 >>> circ = Circuit().bell_pair([4,5]) 

95 """ 

96 self._moments: Moments = Moments() 

97 

98 if addable is not None: 

99 self.add(addable, *args, **kwargs) 

100 

101 @property 

102 def depth(self) -> int: 

103 """int: Get the circuit depth.""" 

104 return self._moments.depth 

105 

106 @property 

107 def instructions(self) -> Iterable[Instruction]: 

108 """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" 

109 return self._moments.values() 

110 

111 @property 

112 def moments(self) -> Moments: 

113 """Moments: Get the `moments` for this circuit.""" 

114 return self._moments 

115 

116 @property 

117 def qubit_count(self) -> int: 

118 """Get the qubit count for this circuit.""" 

119 return self._moments.qubit_count 

120 

121 @property 

122 def qubits(self) -> QubitSet: 

123 """QubitSet: Get a copy of the qubits for this circuit.""" 

124 return QubitSet(self._moments.qubits) 

125 

126 def add_instruction( 

127 self, 

128 instruction: Instruction, 

129 target: QubitSetInput = None, 

130 target_mapping: Dict[QubitInput, QubitInput] = {}, 

131 ) -> Circuit: 

132 """ 

133 Add an instruction to `self`, returns `self` for chaining ability. 

134 

135 Args: 

136 instruction (Instruction): `Instruction` to add into `self`. 

137 target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the 

138 `instruction`. If a single qubit gate, an instruction is created for every index 

139 in `target`. 

140 Default = None. 

141 target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of 

142 qubit mappings to apply to the `instruction.target`. Key is the qubit in 

143 `instruction.target` and the value is what the key will be changed to. Default = {}. 

144 

145 Returns: 

146 Circuit: self 

147 

148 Raises: 

149 TypeError: If both `target_mapping` and `target` are supplied. 

150 

151 Examples: 

152 >>> instr = Instruction(Gate.CNot(), [0, 1]) 

153 >>> circ = Circuit().add_instruction(instr) 

154 >>> print(circ.instructions[0]) 

155 Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) 

156 

157 >>> instr = Instruction(Gate.CNot(), [0, 1]) 

158 >>> circ = Circuit().add_instruction(instr, target_mapping={0: 10, 1: 11}) 

159 >>> print(circ.instructions[0]) 

160 Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) 

161 

162 >>> instr = Instruction(Gate.CNot(), [0, 1]) 

163 >>> circ = Circuit().add_instruction(instr, target=[10, 11]) 

164 >>> print(circ.instructions[0]) 

165 Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) 

166 

167 >>> instr = Instruction(Gate.H(), 0) 

168 >>> circ = Circuit().add_instruction(instr, target=[10, 11]) 

169 >>> print(circ.instructions[0]) 

170 Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) 

171 >>> print(circ.instructions[1]) 

172 Instruction('operator': 'H', 'target': QubitSet(Qubit(11),)) 

173 """ 

174 if target_mapping and target is not None: 

175 raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") 

176 

177 if not target_mapping and not target: 

178 # Nothing has been supplied, add instruction 

179 instructions_to_add = [instruction] 

180 elif target_mapping: 

181 # Target mapping has been supplied, copy instruction 

182 instructions_to_add = [instruction.copy(target_mapping=target_mapping)] 

183 elif hasattr(instruction.operator, "qubit_count") and instruction.operator.qubit_count == 1: 

184 # single qubit operator with target, add an instruction for each target 

185 instructions_to_add = [instruction.copy(target=qubit) for qubit in target] 

186 else: 

187 # non single qubit operator with target, add instruction with target 

188 instructions_to_add = [instruction.copy(target=target)] 

189 

190 self._moments.add(instructions_to_add) 

191 

192 return self 

193 

194 def add_circuit( 

195 self, 

196 circuit: Circuit, 

197 target: QubitSetInput = None, 

198 target_mapping: Dict[QubitInput, QubitInput] = {}, 

199 ) -> Circuit: 

200 """ 

201 Add a `circuit` to self, returns self for chaining ability. This is a composite form of 

202 `add_instruction()` since it adds all of the instructions of `circuit` to this circuit. 

203 

204 Args: 

205 circuit (Circuit): Circuit to add into self. 

206 target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the 

207 supplied circuit. This is a macro over `target_mapping`; `target` is converted to 

208 a `target_mapping` by zipping together a sorted `circuit.qubits` and `target`. 

209 Default = None. 

210 target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of 

211 qubit mappings to apply to the qubits of `circuit.instructions`. Key is the qubit 

212 to map, and the Value is what to change it to. Default = {}. 

213 

214 Returns: 

215 Circuit: self 

216 

217 Raises: 

218 TypeError: If both `target_mapping` and `target` are supplied. 

219 

220 Note: 

221 Supplying `target` sorts `circuit.qubits` to have deterministic behavior since 

222 `circuit.qubits` ordering is based on how instructions are inserted. 

223 Use caution when using this with circuits that with a lot of qubits, as the sort 

224 can be resource-intensive. Use `target_mapping` to use a linear runtime to remap 

225 the qubits. 

226 

227 Examples: 

228 >>> widget = Circuit().h(0).cnot([0, 1]) 

229 >>> circ = Circuit().add_circuit(widget) 

230 >>> print(circ.instructions[0]) 

231 Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) 

232 >>> print(circ.instructions[1]) 

233 Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(0), Qubit(1))) 

234 

235 >>> widget = Circuit().h(0).cnot([0, 1]) 

236 >>> circ = Circuit().add_circuit(widget, target_mapping={0: 10, 1: 11}) 

237 >>> print(circ.instructions[0]) 

238 Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) 

239 >>> print(circ.instructions[1]) 

240 Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) 

241 

242 >>> widget = Circuit().h(0).cnot([0, 1]) 

243 >>> circ = Circuit().add_circuit(widget, target=[10, 11]) 

244 >>> print(circ.instructions[0]) 

245 Instruction('operator': 'H', 'target': QubitSet(Qubit(10),)) 

246 >>> print(circ.instructions[1]) 

247 Instruction('operator': 'CNOT', 'target': QubitSet(Qubit(10), Qubit(11))) 

248 """ 

249 if target_mapping and target is not None: 

250 raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") 

251 elif target is not None: 

252 keys = sorted(circuit.qubits) 

253 values = target 

254 target_mapping = dict(zip(keys, values)) 

255 

256 for instruction in circuit.instructions: 

257 self.add_instruction(instruction, target_mapping=target_mapping) 

258 

259 return self 

260 

261 def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: 

262 """ 

263 Generic add method for adding instruction-like item(s) to self. Any arguments that 

264 `add_circuit()` and / or `add_instruction()` supports are supported by this method. 

265 If adding a subroutine, check with that subroutines documentation to determine what input it 

266 allows. 

267 

268 Args: 

269 addable (Instruction, iterable of Instruction, or SubroutineCallable, optional): The 

270 instruction-like item(s) to add to self. Default = None. 

271 *args: Variable length argument list. 

272 **kwargs: Arbitrary keyword arguments. 

273 

274 Returns: 

275 Circuit: self 

276 

277 Raises: 

278 TypeError: If `addable` is an unsupported type 

279 

280 See Also: 

281 `add_circuit()` 

282 

283 `add_instruction()` 

284 

285 Examples: 

286 >>> circ = Circuit().add([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) 

287 

288 >>> circ = Circuit().h(4).cnot([4, 5]) 

289 

290 >>> @circuit.subroutine() 

291 >>> def bell_pair(target): 

292 ... return Circuit().h(target[0]).cnot(target[0: 2]) 

293 ... 

294 >>> circ = Circuit().add(bell_pair, [4,5]) 

295 """ 

296 

297 def _flatten(addable): 

298 if isinstance(addable, Iterable): 

299 for item in addable: 

300 yield from _flatten(item) 

301 else: 

302 yield addable 

303 

304 for item in _flatten(addable): 

305 if isinstance(item, Instruction): 

306 self.add_instruction(item, *args, **kwargs) 

307 elif isinstance(item, Circuit): 

308 self.add_circuit(item, *args, **kwargs) 

309 elif callable(item): 

310 self.add(item(*args, **kwargs)) 

311 else: 

312 raise TypeError(f"Cannot add a '{type(item)}' to a Circuit") 

313 

314 return self 

315 

316 def diagram(self, circuit_diagram_class=AsciiCircuitDiagram) -> str: 

317 """ 

318 Get a diagram for the current circuit. 

319 

320 Args: 

321 circuit_diagram_class (Class, optional): A `CircuitDiagram` class that builds the 

322 diagram for this circuit. Default = AsciiCircuitDiagram. 

323 

324 Returns: 

325 str: An ASCII string circuit diagram. 

326 """ 

327 return circuit_diagram_class.build_diagram(self) 

328 

329 def to_ir(self) -> Program: 

330 """ 

331 Converts the circuit into the canonical intermediate representation. 

332 If the circuit is sent over the wire, this method is called before it is sent. 

333 

334 Returns: 

335 (Program): An AWS quantum circuit description program in JSON format. 

336 """ 

337 ir_instructions = [instr.to_ir() for instr in self.instructions] 

338 return Program(instructions=ir_instructions) 

339 

340 def _copy(self) -> Circuit: 

341 """ 

342 Return a shallow copy of the circuit. 

343 

344 Returns: 

345 Circuit: A shallow copy of the circuit. 

346 """ 

347 return Circuit().add(self.instructions) 

348 

349 def __iadd__(self, addable: AddableTypes) -> Circuit: 

350 return self.add(addable) 

351 

352 def __add__(self, addable: AddableTypes) -> Circuit: 

353 new = self._copy() 

354 new.add(addable) 

355 return new 

356 

357 def __repr__(self): 

358 return f"Circuit('instructions': {list(self.instructions)})" 

359 

360 def __str__(self): 

361 return self.diagram(AsciiCircuitDiagram) 

362 

363 def __eq__(self, other): 

364 if isinstance(other, Circuit): 

365 return list(self.instructions) == list(other.instructions) 

366 return NotImplemented 

367 

368 

369def subroutine(register=False): 

370 """ 

371 Subroutine is a function that returns instructions or circuits. 

372 

373 Args: 

374 register (bool, optional): If `True`, adds this subroutine into the `Circuit` class. 

375 Default = False. 

376 

377 Examples: 

378 >>> @circuit.subroutine(register=True) 

379 >>> def bell_circuit(): 

380 ... return Circuit().h(0).cnot(0, 1) 

381 ... 

382 >>> circ = Circuit().bell_circuit() 

383 >>> for instr in circ.instructions: 

384 ... print(instr) 

385 ... 

386 Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) 

387 Instruction('operator': 'H', 'target': QubitSet(Qubit(1),)) 

388 """ 

389 

390 def subroutine_function_wrapper(func: Callable[..., SubroutineReturn]) -> SubroutineReturn: 

391 if register: 

392 Circuit.register_subroutine(func) 

393 return func 

394 

395 return subroutine_function_wrapper