Coverage for /home/runner/work/tket/tket/pytket/pytket/backends/backendinfo.py: 99%

116 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-14 11:30 +0000

1# Copyright Quantinuum 

2# 

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

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""BackendInfo class: additional information on Backends""" 

16 

17from dataclasses import asdict, dataclass, field 

18from typing import Any 

19 

20from pytket.architecture import Architecture, FullyConnected 

21from pytket.circuit import Node, OpType 

22 

23_OpTypeErrs = dict[OpType, float] 

24_Edge = tuple[Node, Node] 

25 

26 

27def _serialize_all_node_gate_errors( 

28 d: dict[Node, _OpTypeErrs] | None, 

29) -> list[list] | None: 

30 if d is None: 

31 return None 

32 return [ 

33 [n.to_list(), {ot.name: err for ot, err in errs.items()}] 

34 for n, errs in d.items() 

35 ] 

36 

37 

38def _deserialize_all_node_gate_errors( 

39 l: list[list] | None, 

40) -> dict[Node, _OpTypeErrs] | None: 

41 if l is None: 

42 return None 

43 return { 

44 Node.from_list(n): {OpType.from_name(ot): err for ot, err in errs.items()} 

45 for n, errs in l 

46 } 

47 

48 

49def _serialize_all_edge_gate_errors(d: dict[_Edge, _OpTypeErrs] | None) -> list | None: 

50 if d is None: 

51 return None 

52 return [ 

53 [[n0.to_list(), n1.to_list()], {ot.name: err for ot, err in errs.items()}] 

54 for (n0, n1), errs in d.items() 

55 ] 

56 

57 

58def _deserialize_all_edge_gate_errors( 

59 l: list | None, 

60) -> dict[_Edge, _OpTypeErrs] | None: 

61 if l is None: 

62 return None 

63 return { 

64 (Node.from_list(n0), Node.from_list(n1)): { 

65 OpType.from_name(ot): err for ot, err in errs.items() 

66 } 

67 for (n0, n1), errs in l 

68 } 

69 

70 

71def _serialize_all_readout_errors( 

72 d: dict[Node, list[list[float]]] | None, 

73) -> list[list] | None: 

74 if d is None: 

75 return None 

76 return [[n.to_list(), errs] for n, errs in d.items()] 

77 

78 

79def _deserialize_all_readout_errors( 

80 l: list[list] | None, 

81) -> dict[Node, list[list[float]]] | None: 

82 if l is None: 

83 return None 

84 return {Node.from_list(n): errs for n, errs in l} 

85 

86 

87def _serialize_averaged_node_gate_errors( 

88 d: dict[Node, float] | None, 

89) -> list[list] | None: 

90 if d is None: 

91 return None 

92 return [[n.to_list(), err] for n, err in d.items()] 

93 

94 

95def _deserialize_averaged_node_gate_errors( 

96 l: list[list] | None, 

97) -> dict[Node, float] | None: 

98 if l is None: 

99 return None 

100 return {Node.from_list(n): err for n, err in l} 

101 

102 

103def _serialize_averaged_edge_gate_errors( 

104 d: dict[_Edge, float] | None, 

105) -> list[list] | None: 

106 if d is None: 

107 return None 

108 return [[[n0.to_list(), n1.to_list()], err] for (n0, n1), err in d.items()] 

109 

110 

111def _deserialize_averaged_edge_gate_errors( 

112 l: list[list] | None, 

113) -> dict[tuple, float] | None: 

114 if l is None: 

115 return None 

116 return {(Node.from_list(n0), Node.from_list(n1)): err for (n0, n1), err in l} 

117 

118 

119def _serialize_averaged_readout_errors( 

120 d: dict[Node, float] | None, 

121) -> list[list] | None: 

122 if d is None: 

123 return None 

124 return [[n.to_list(), err] for n, err in d.items()] 

125 

126 

127def _deserialize_averaged_readout_errors( 

128 l: list[list] | None, 

129) -> dict[Node, float] | None: 

130 if l is None: 

131 return None 

132 return {Node.from_list(n): err for n, err in l} 

133 

134 

135@dataclass 

136class BackendInfo: 

137 """ 

138 Stores various properties of a Backend. 

139 

140 This provides all device information useful for compilation. 

141 

142 :param name: Class name of the backend. 

143 :param device_name: Name of the device. 

144 :param version: Pytket-extension version installed when creating object. 

145 :param architecture: Optional device connectivity. 

146 :param gate_set: Set of supported gate types. 

147 :param n_cl_reg: number of classical registers supported. 

148 :param supports_fast_feedforward: Flag for hardware support of fast feedforward. 

149 :param supports_reset: Flag for hardware support of reset operation 

150 :param supports_midcircuit_meas: Flag for hardware support of midcircuit 

151 measurement. 

152 :param all_node_gate_errors: Dictionary between architecture Node and error rate 

153 for different single qubit operations. 

154 :param all_edge_gate_errors: Dictionary between architecture couplings and error 

155 rate for different two-qubit operations. 

156 :param all_readout_errors: Dictionary between architecture Node and uncorrelated 

157 single qubit readout errors (2x2 readout probability matrix). 

158 :param averaged_node_gate_errors: Dictionary between architecture Node and averaged 

159 error rate for all single qubit operations. 

160 :param averaged_edge_gate_errors: Dictionary between architecture couplings and 

161 averaged error rate for all two-qubit operations. 

162 :param averaged_readout_errors: Dictionary between architecture Node and averaged 

163 readout errors. 

164 :param misc: key-value map with further provider-specific information (must be 

165 JSON-serializable) 

166 """ 

167 

168 # identifying information 

169 name: str 

170 device_name: str | None 

171 version: str 

172 # hardware constraints 

173 architecture: Architecture | FullyConnected | None 

174 gate_set: set[OpType] 

175 n_cl_reg: int | None = None 

176 # additional feature support 

177 supports_fast_feedforward: bool = False 

178 supports_reset: bool = False 

179 supports_midcircuit_measurement: bool = False 

180 # additional basic device characterisation information 

181 all_node_gate_errors: dict[Node, dict[OpType, float]] | None = None 

182 all_edge_gate_errors: dict[tuple[Node, Node], dict[OpType, float]] | None = None 

183 all_readout_errors: dict[Node, list[list[float]]] | None = None 

184 averaged_node_gate_errors: dict[Node, float] | None = None 

185 averaged_edge_gate_errors: dict[tuple[Node, Node], float] | None = None 

186 averaged_readout_errors: dict[Node, float] | None = None 

187 

188 # miscellaneous, eg additional noise characterisation and provider-supplied 

189 # information 

190 misc: dict[str, Any] = field(default_factory=dict) 

191 

192 @property 

193 def nodes(self) -> list[Node]: 

194 """ 

195 List of device nodes of the backend. Returns empty list 

196 if the `architecture` field is not provided. 

197 

198 :return: List of nodes. 

199 :rtype: List[Node] 

200 """ 

201 if self.architecture is None: 201 ↛ 202line 201 didn't jump to line 202 because the condition on line 201 was never true

202 return [] 

203 return self.architecture.nodes 

204 

205 @property 

206 def n_nodes(self) -> int: 

207 """ 

208 Number of nodes in the architecture of the device. Returns 0 

209 if the `architecture` field is not provided. 

210 

211 :return: Number of nodes. 

212 :rtype: int 

213 """ 

214 return len(self.nodes) 

215 

216 def add_misc(self, key: str, val: Any) -> None: 

217 """ 

218 Add a new entry in BackendInfo's dictionary of additional information. 

219 

220 :param key: Key to store and retrieve value. 

221 :type key: str 

222 :param val: Value to be stored. 

223 """ 

224 if key in self.misc: 

225 raise KeyError("Attempting to add an already existing entry to misc dict") 

226 self.misc[key] = val 

227 

228 def get_misc(self, key: str) -> Any: 

229 """ 

230 Retrieve information stored in Backend's additional information store 

231 

232 :param key: Key to retrieve value. 

233 :type key: str 

234 

235 :raises KeyError: There is no value stored with the given key. 

236 

237 :return: The value stored at the given key. 

238 """ 

239 return self.misc[key] 

240 

241 def to_dict(self) -> dict[str, Any]: 

242 """ 

243 Generate a dictionary serialized representation of BackendInfo, 

244 suitable for writing to JSON. 

245 

246 :return: JSON serializable dictionary. 

247 :rtype: Dict[str, Any] 

248 """ 

249 

250 self_dict = asdict(self) 

251 if self_dict["architecture"] is not None: 

252 self_dict["architecture"] = self_dict["architecture"].to_dict() 

253 self_dict["gate_set"] = [op.value for op in self_dict["gate_set"]] 

254 self_dict["all_node_gate_errors"] = _serialize_all_node_gate_errors( 

255 self_dict["all_node_gate_errors"] 

256 ) 

257 self_dict["all_edge_gate_errors"] = _serialize_all_edge_gate_errors( 

258 self_dict["all_edge_gate_errors"] 

259 ) 

260 self_dict["all_readout_errors"] = _serialize_all_readout_errors( 

261 self_dict["all_readout_errors"] 

262 ) 

263 self_dict["averaged_node_gate_errors"] = _serialize_averaged_node_gate_errors( 

264 self_dict["averaged_node_gate_errors"] 

265 ) 

266 self_dict["averaged_edge_gate_errors"] = _serialize_averaged_edge_gate_errors( 

267 self_dict["averaged_edge_gate_errors"] 

268 ) 

269 self_dict["averaged_readout_errors"] = _serialize_averaged_readout_errors( 

270 self_dict["averaged_readout_errors"] 

271 ) 

272 return self_dict 

273 

274 @classmethod 

275 def from_dict(cls, d: dict[str, Any]) -> "BackendInfo": 

276 """ 

277 Construct BackendInfo object from JSON serializable dictionary 

278 representation, as generated by BackendInfo.to_dict. 

279 

280 :return: Instance of BackendInfo constructed from dictionary. 

281 :rtype: BackendInfo 

282 """ 

283 args = dict(**d) 

284 arch = args["architecture"] 

285 if arch is not None: 

286 if "links" in arch: 

287 args["architecture"] = Architecture.from_dict(args["architecture"]) 

288 else: 

289 args["architecture"] = FullyConnected.from_dict(args["architecture"]) 

290 args["gate_set"] = {OpType(op) for op in args["gate_set"]} 

291 args["all_node_gate_errors"] = _deserialize_all_node_gate_errors( 

292 args["all_node_gate_errors"] 

293 ) 

294 args["all_edge_gate_errors"] = _deserialize_all_edge_gate_errors( 

295 args["all_edge_gate_errors"] 

296 ) 

297 args["all_readout_errors"] = _deserialize_all_readout_errors( 

298 args["all_readout_errors"] 

299 ) 

300 args["averaged_node_gate_errors"] = _deserialize_averaged_node_gate_errors( 

301 args["averaged_node_gate_errors"] 

302 ) 

303 args["averaged_edge_gate_errors"] = _deserialize_averaged_edge_gate_errors( 

304 args["averaged_edge_gate_errors"] 

305 ) 

306 args["averaged_readout_errors"] = _deserialize_averaged_readout_errors( 

307 args["averaged_readout_errors"] 

308 ) 

309 return cls(**args) 

310 

311 

312def fully_connected_backendinfo( # type: ignore 

313 name: str, 

314 device_name: str | None, 

315 version: str, 

316 n_nodes: int, 

317 gate_set: set[OpType], 

318 **kwargs 

319) -> BackendInfo: 

320 """ 

321 Construct a BackendInfo with a FullyConnected architecture. 

322 

323 :param name: Class name of the backend. 

324 :param device_name: Name of the device. 

325 :param version: Version of the pytket Backend. 

326 :param n_nodes: Number of nodes of the device. 

327 :param gate_set: Set of supported gate types. 

328 :param \\**kwargs: All further arguments are passed to the BackendInfo constructor. 

329 """ 

330 return BackendInfo( 

331 name, device_name, version, FullyConnected(n_nodes), gate_set, **kwargs 

332 )