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 typing import Callable, Dict, Iterable, TypeVar 

15 

16from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram 

17from braket.circuits.instruction import Instruction 

18from braket.circuits.moments import Moments 

19from braket.circuits.qubit import QubitInput 

20from braket.circuits.qubit_set import QubitSet, QubitSetInput 

21from braket.ir.jaqcd import Program 

22 

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

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

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

26 

27# TODO: Add parameterization 

28 

29 

30class Circuit: 

31 """ 

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

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

34 """ 

35 

36 @classmethod 

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

38 """ 

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

40 is the name of `func`. 

41 

42 Args: 

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

44 subroutine to add to the class. 

45 

46 Examples: 

47 >>> def h_on_all(target): 

48 ... circ = Circuit() 

49 ... for qubit in target: 

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

51 ... return circ 

52 ... 

53 >>> Circuit.register_subroutine(h_on_all) 

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

55 >>> for instr in circ.instructions: 

56 ... print(instr) 

57 ... 

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

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

60 """ 

61 

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

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

64 

65 function_name = func.__name__ 

66 setattr(cls, function_name, method_from_subroutine) 

67 

68 function_attr = getattr(cls, function_name) 

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

70 

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

72 """ 

73 Args: 

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

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

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

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

78 offers. 

79 

80 Raises: 

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

82 

83 Examples: 

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

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

86 

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

88 >>> def bell_pair(target): 

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

90 ... 

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

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

93 """ 

94 self._moments: Moments = Moments() 

95 

96 if addable is not None: 

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

98 

99 @property 

100 def depth(self) -> int: 

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

102 return self._moments.depth 

103 

104 @property 

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

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

107 return self._moments.values() 

108 

109 @property 

110 def moments(self) -> Moments: 

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

112 return self._moments 

113 

114 @property 

115 def qubit_count(self) -> int: 

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

117 return self._moments.qubit_count 

118 

119 @property 

120 def qubits(self) -> QubitSet: 

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

122 return QubitSet(self._moments.qubits) 

123 

124 def add_instruction( 

125 self, 

126 instruction: Instruction, 

127 target: QubitSetInput = None, 

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

129 ) -> "Circuit": 

130 """ 

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

132 

133 Args: 

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

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

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

137 in `target`. 

138 Default = None. 

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

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

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

142 

143 Returns: 

144 Circuit: self 

145 

146 Raises: 

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

148 

149 Examples: 

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

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

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

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

154 

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

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

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

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

159 

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

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

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

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

164 

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

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

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

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

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

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

171 """ 

172 if target_mapping and target is not None: 

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

174 

175 if not target_mapping and not target: 

176 # Nothing has been supplied, add instruction 

177 instructions_to_add = [instruction] 

178 elif target_mapping: 

179 # Target mapping has been supplied, copy instruction 

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

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

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

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

184 else: 

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

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

187 

188 self._moments.add(instructions_to_add) 

189 

190 return self 

191 

192 def add_circuit( 

193 self, 

194 circuit: "Circuit", 

195 target: QubitSetInput = None, 

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

197 ) -> "Circuit": 

198 """ 

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

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

201 

202 Args: 

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

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

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

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

207 Default = None. 

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

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

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

211 

212 Returns: 

213 Circuit: self 

214 

215 Raises: 

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

217 

218 Note: 

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

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

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

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

223 the qubits. 

224 

225 Examples: 

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

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

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

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

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

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

232 

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

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

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

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

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

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

239 

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

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

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

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

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

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

246 """ 

247 if target_mapping and target is not None: 

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

249 elif target is not None: 

250 keys = sorted(circuit.qubits) 

251 values = target 

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

253 

254 for instruction in circuit.instructions: 

255 self.add_instruction(instruction, target_mapping=target_mapping) 

256 

257 return self 

258 

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

260 """ 

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

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

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

264 allows. 

265 

266 Args: 

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

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

269 *args: Variable length argument list. 

270 **kwargs: Arbitrary keyword arguments. 

271 

272 Returns: 

273 Circuit: self 

274 

275 Raises: 

276 TypeError: If `addable` is an unsupported type 

277 

278 See Also: 

279 `add_circuit()` 

280 

281 `add_instruction()` 

282 

283 Examples: 

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

285 

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

287 

288 >>> @circuit.subroutine() 

289 >>> def bell_pair(target): 

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

291 ... 

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

293 """ 

294 

295 def _flatten(addable): 

296 if isinstance(addable, Iterable): 

297 for item in addable: 

298 yield from _flatten(item) 

299 else: 

300 yield addable 

301 

302 for item in _flatten(addable): 

303 if isinstance(item, Instruction): 

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

305 elif isinstance(item, Circuit): 

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

307 elif callable(item): 

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

309 else: 

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

311 

312 return self 

313 

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

315 """ 

316 Get a diagram for the current circuit. 

317 

318 Args: 

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

320 diagram for this circuit. Default = AsciiCircuitDiagram. 

321 

322 Returns: 

323 str: An ASCII string circuit diagram. 

324 """ 

325 return circuit_diagram_class.build_diagram(self) 

326 

327 def to_ir(self) -> Program: 

328 """ 

329 Converts the circuit into the canonical intermediate representation. 

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

331 

332 Returns: 

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

334 """ 

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

336 return Program(instructions=ir_instructions) 

337 

338 def _copy(self) -> "Circuit": 

339 """ 

340 Return a shallow copy of the circuit. 

341 

342 Returns: 

343 Circuit: A shallow copy of the circuit. 

344 """ 

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

346 

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

348 return self.add(addable) 

349 

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

351 new = self._copy() 

352 new.add(addable) 

353 return new 

354 

355 def __repr__(self): 

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

357 

358 def __str__(self): 

359 return self.diagram(AsciiCircuitDiagram) 

360 

361 def __eq__(self, other): 

362 if isinstance(other, Circuit): 

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

364 return NotImplemented 

365 

366 

367def subroutine(register=False): 

368 """ 

369 Subroutine is a function that returns instructions or circuits. 

370 

371 Args: 

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

373 Default = False. 

374 

375 Examples: 

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

377 >>> def bell_circuit(): 

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

379 ... 

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

381 >>> for instr in circ.instructions: 

382 ... print(instr) 

383 ... 

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

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

386 """ 

387 

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

389 if register: 

390 Circuit.register_subroutine(func) 

391 return func 

392 

393 return subroutine_function_wrapper