Coverage for /home/runner/work/tket/tket/pytket/pytket/utils/serialization/migration.py: 90%

100 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 10:53 +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 

15from copy import deepcopy 

16from typing import Any 

17 

18from pytket.circuit.logic_exp import ( # noqa: F401 pylint: disable=W0611 

19 BitWiseOp, 

20 LogicExp, 

21 RegWiseOp, 

22) 

23 

24 

25def _convert_op(exp_op: str) -> str: 

26 assert exp_op.startswith(("RegWiseOp.", "BitWiseOp.")) 

27 return LogicExp.factory(eval(exp_op)).__name__ 

28 

29 

30def _convert_regwise_terms(args: list, regs: list[tuple[str, int]]) -> list: 

31 terms = [] 

32 for arg in args: 

33 assert isinstance(arg, dict) 

34 regname = arg.get("name") 

35 if regname is None: 

36 # It's a nested expression. 

37 terms.append( 

38 { 

39 "type": "expr", 

40 "input": { 

41 "op": _convert_op(arg["op"]), 

42 "args": _convert_regwise_terms(arg["args"], regs), 

43 }, 

44 } 

45 ) 

46 else: 

47 # It's a register. 

48 i = regs.index((regname, arg["size"])) 

49 terms.append( 

50 { 

51 "type": "term", 

52 "input": { 

53 "type": "var", 

54 "term": {"type": "reg", "var": {"index": i}}, 

55 }, 

56 } 

57 ) 

58 return terms 

59 

60 

61def _convert_bitwise_terms(args: list, bits: list[tuple[str, tuple[int]]]) -> list: 

62 terms = [] 

63 for arg in args: 

64 if isinstance(arg, dict): 64 ↛ 66line 64 didn't jump to line 66 because the condition on line 64 was never true

65 # It's a nested expression. 

66 terms.append( 

67 { 

68 "type": "expr", 

69 "input": { 

70 "op": _convert_op(arg["op"]), 

71 "args": _convert_bitwise_terms(arg["args"], bits), 

72 }, 

73 } 

74 ) 

75 else: 

76 # It's a bit. 

77 assert isinstance(arg, list) 

78 assert len(arg) == 2 

79 name, index = arg 

80 i = bits.index((name, tuple(index))) 

81 terms.append( 

82 { 

83 "type": "term", 

84 "input": { 

85 "type": "var", 

86 "term": {"type": "bit", "var": {"index": i}}, 

87 }, 

88 } 

89 ) 

90 return terms 

91 

92 

93def _find_regs_in_expr(exp: dict[str, Any], regs: list[tuple[str, int]]) -> None: 

94 op = exp["op"] 

95 args = exp["args"] 

96 assert op.startswith("RegWiseOp.") 

97 for arg in args: 

98 assert isinstance(arg, dict) 

99 regname = arg.get("name") 

100 if regname is None: 

101 # It's a nested expression. 

102 _find_regs_in_expr(arg, regs) 

103 else: 

104 # It's a register. 

105 reg = (regname, arg["size"]) 

106 if reg not in regs: 

107 regs.append(reg) 

108 

109 

110def _find_bits_in_expr(exp: dict[str, Any], bits: list[tuple[str, tuple[int]]]) -> None: 

111 op = exp["op"] 

112 args = exp["args"] 

113 assert op.startswith("BitWiseOp.") 

114 for arg in args: 

115 if isinstance(arg, dict): 115 ↛ 117line 115 didn't jump to line 117 because the condition on line 115 was never true

116 # It's a nested expression. 

117 _find_bits_in_expr(arg, bits) 

118 else: 

119 # It's a bit. 

120 assert isinstance(arg, list) 

121 bit = (arg[0], tuple(arg[1])) 

122 if bit not in bits: 122 ↛ 114line 122 didn't jump to line 114 because the condition on line 122 was always true

123 bits.append(bit) 

124 

125 

126def _clexpr_from_regwise_classicalexpbox( 

127 box: dict[str, Any], command_args: list 

128) -> dict[str, Any]: 

129 exp = box["exp"] 

130 regs: list[tuple[str, int]] = [] 

131 _find_regs_in_expr(exp, regs) # construct ordered list of reg vars 

132 reg_posn = [ 

133 [i, [command_args.index([name, [j]]) for j in range(size)]] 

134 for i, (name, size) in enumerate(regs) 

135 ] 

136 terms = _convert_regwise_terms(exp["args"], regs) 

137 return { 

138 "expr": {"op": _convert_op(exp["op"]), "args": terms}, 

139 "bit_posn": [], 

140 "reg_posn": reg_posn, 

141 "output_posn": list(range(box["n_i"], len(command_args))), 

142 } 

143 

144 

145def _clexpr_from_bitwise_classicalexpbox( 

146 box: dict[str, Any], command_args: list 

147) -> dict[str, Any]: 

148 exp = box["exp"] 

149 bits: list[tuple[str, tuple[int]]] = [] 

150 _find_bits_in_expr(exp, bits) # construct ordered list of bit vars 

151 bit_posn = [ 

152 [i, command_args.index([name, list(index)])] 

153 for i, (name, index) in enumerate(bits) 

154 ] 

155 terms = _convert_bitwise_terms(exp["args"], bits) 

156 return { 

157 "expr": {"op": _convert_op(exp["op"]), "args": terms}, 

158 "bit_posn": bit_posn, 

159 "reg_posn": [], 

160 "output_posn": list(range(box["n_i"], len(command_args))), 

161 } 

162 

163 

164def _clexpr_from_classicalexpbox( 

165 box: dict[str, Any], command_args: list 

166) -> dict[str, Any]: 

167 exp_op = box["exp"]["op"] 

168 if exp_op.startswith("RegWiseOp."): 

169 return _clexpr_from_regwise_classicalexpbox(box, command_args) 

170 assert exp_op.startswith("BitWiseOp.") 

171 return _clexpr_from_bitwise_classicalexpbox(box, command_args) 

172 

173 

174def _new_op(op: dict[str, Any], args: list) -> dict[str, Any]: 

175 match op["type"]: 

176 case "Conditional": 

177 new_data = deepcopy(op) 

178 new_data["conditional"]["op"] = _new_op( 

179 op["conditional"]["op"], args[op["conditional"]["width"] :] 

180 ) 

181 return new_data 

182 case "QControlBox": 182 ↛ 183line 182 didn't jump to line 183 because the pattern on line 182 never matched

183 new_data = deepcopy(op) 

184 new_data["box"]["op"] = _new_op(op["box"]["op"], args) 

185 return new_data 

186 case "CircBox": 

187 new_data = deepcopy(op) 

188 new_data["box"]["circuit"] = circuit_dict_from_pytket1_dict( 

189 op["box"]["circuit"] 

190 ) 

191 return new_data 

192 case "CustomGate": 192 ↛ 193line 192 didn't jump to line 193 because the pattern on line 192 never matched

193 new_data = deepcopy(op) 

194 new_data["box"]["gate"] = circuit_dict_from_pytket1_dict(op["box"]["gate"]) 

195 return new_data 

196 case "ClassicalExpBox": 

197 return { 

198 "type": "ClExpr", 

199 "expr": _clexpr_from_classicalexpbox(op["box"], args), 

200 } 

201 case _: 

202 return op 

203 

204 

205def _new_command(command: dict[str, Any]) -> dict[str, Any]: 

206 new_data = deepcopy(command) 

207 new_data["op"] = _new_op(command["op"], command["args"]) 

208 return new_data 

209 

210 

211def circuit_dict_from_pytket1_dict(circuit_data: dict[str, Any]) -> dict[str, Any]: 

212 """Update the serialization of a pytket 1 circuit to an equivalent pytket circuit. 

213 (This converts ClassicalExpBox to ClExprOp operations.) 

214 

215 :param circuit_data: serialization of pytket 1 circuit 

216 :return: serialization of equivalent pytket circuit 

217 """ 

218 # Replace all ClassicalExpBox ops. Need to recurse into CircBoxes etc. 

219 new_data = deepcopy(circuit_data) 

220 new_data["commands"] = [ 

221 _new_command(command) for command in circuit_data["commands"] 

222 ] 

223 return new_data