Coverage for tests/test_entanglement.py: 28%

94 statements  

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

1from qml_essentials.model import Model 

2from qml_essentials.entanglement import Entanglement 

3 

4import logging 

5import math 

6import pytest 

7 

8from copy import deepcopy 

9 

10logger = logging.getLogger(__name__) 

11 

12 

13def get_test_cases(): 

14 # Results taken from: https://doi.org/10.1002/qute.201900070 

15 

16 circuits = [ 

17 # "No_Entangling", 

18 # "Strongly_Entangling", 

19 1, 

20 7, 

21 3, 

22 16, 

23 8, 

24 5, 

25 18, 

26 17, 

27 4, 

28 10, 

29 19, 

30 13, 

31 12, 

32 14, 

33 11, 

34 6, 

35 2, 

36 15, 

37 9, 

38 ] 

39 

40 results_n_layers_1 = [ 

41 # 0.0000, 

42 # 0.8379, 

43 0.0000, 

44 0.3241, 

45 0.3412, 

46 0.3439, 

47 0.3926, 

48 0.4090, 

49 0.4385, 

50 0.4533, 

51 0.4721, 

52 0.5362, 

53 0.5916, 

54 0.6077, 

55 0.6486, 

56 0.6604, 

57 0.7335, 

58 0.7781, 

59 0.8104, 

60 0.8184, 

61 1.0000, 

62 ] 

63 

64 results_n_layers_3 = [ 

65 0.0000, 

66 0.6194, 

67 0.5852, 

68 0.5859, 

69 0.6567, 

70 0.7953, 

71 0.7130, 

72 0.6557, 

73 0.6607, 

74 0.7865, 

75 0.7906, 

76 0.8224, 

77 0.7838, 

78 0.8557, 

79 0.8288, 

80 0.8721, 

81 0.8657, 

82 0.8734, 

83 1.0000, 

84 ] 

85 

86 # Circuits [5,7,8,11,12,13,14] are not included in the test cases, 

87 # because not implemented in ansaetze.py 

88 

89 # Circuit 10 excluded because implementation with current setup not possible 

90 skip_indices = [5, 7, 8, 11, 12, 13, 14, 10] 

91 skip_indices += [2, 3] # exclude these for now as order is failing 

92 

93 return circuits, results_n_layers_1, results_n_layers_3, skip_indices 

94 

95 

96@pytest.mark.expensive 

97@pytest.mark.unittest 

98def test_mw_measure() -> None: 

99 circuits, results_n_layers_1, results_n_layers_3, skip_indices = get_test_cases() 

100 

101 test_cases = [] 

102 for circuit_id, res_1l, res_3l in zip( 

103 circuits, results_n_layers_1, results_n_layers_3 

104 ): 

105 if circuit_id in skip_indices: 

106 continue 

107 if isinstance(circuit_id, int): 

108 test_cases.append( 

109 { 

110 "circuit_type": f"Circuit_{circuit_id}", 

111 "n_qubits": 4, 

112 "n_layers": 1, 

113 "result": res_1l, 

114 } 

115 ) 

116 elif isinstance(circuit_id, str): 

117 test_cases.append( 

118 { 

119 "circuit_type": circuit_id, 

120 "n_qubits": 4, 

121 "n_layers": 1, 

122 "result": res_1l, 

123 } 

124 ) 

125 

126 tolerance = 0.55 # FIXME: reduce when reason for discrepancy is found 

127 ent_caps: list[tuple[str, float]] = [] 

128 for test_case in test_cases: 

129 print(f"--- Running Entanglement test for {test_case['circuit_type']} ---") 

130 model = Model( 

131 n_qubits=test_case["n_qubits"], 

132 n_layers=test_case["n_layers"], 

133 circuit_type=test_case["circuit_type"], 

134 data_reupload=False, 

135 initialization="random", 

136 ) 

137 

138 ent_cap = Entanglement.meyer_wallach( 

139 model, n_samples=5000, seed=1000, cache=False 

140 ) 

141 

142 # Save results for later comparison 

143 circuit_number = test_case["circuit_type"] 

144 if circuit_number.split("_")[1].isdigit(): 

145 circuit_number = int(circuit_number.split("_")[1]) 

146 ent_caps.append((circuit_number, ent_cap)) 

147 

148 difference = abs(ent_cap - test_case["result"]) 

149 if math.isclose(difference, 0.0, abs_tol=1e-3): 

150 error = 0 

151 else: 

152 error = abs(ent_cap - test_case["result"]) / (test_case["result"]) 

153 

154 print( 

155 f"Entangling-capability: {ent_cap},\t" 

156 + f"Expected Result: {test_case['result']},\t" 

157 + f"Error: {error}" 

158 ) 

159 assert ( 

160 error < tolerance 

161 ), f"Entangling-capability of circuit {test_case['circuit_type']} is not\ 

162 {test_case['result']} but {ent_cap} instead.\ 

163 Deviation {(error * 100):.1f}%>{tolerance * 100}%" 

164 

165 references = sorted( 

166 [ 

167 (circuit, ent_result) 

168 for circuit, ent_result in zip(circuits, results_n_layers_1) 

169 if circuit not in skip_indices 

170 ], 

171 key=lambda x: x[1], 

172 ) 

173 

174 actuals = sorted(ent_caps, key=lambda x: x[1]) 

175 

176 print("Expected \t| Actual") 

177 for reference, actual in zip(references, actuals): 

178 print(f"{reference[0]}, {reference[1]} \t| {actual[0]}, {actual[1]}") 

179 assert [circuit for circuit, _ in actuals] == [ 

180 circuit for circuit, _ in references 

181 ], f"Order of circuits does not match: {actuals} != {references}" 

182 

183 

184@pytest.mark.smoketest 

185def test_no_sampling() -> None: 

186 model = Model( 

187 n_qubits=2, 

188 n_layers=1, 

189 circuit_type="Hardware_Efficient", 

190 data_reupload=False, 

191 initialization="random", 

192 ) 

193 

194 _ = Entanglement.meyer_wallach(model, n_samples=-1, seed=1000, cache=False) 

195 

196 

197@pytest.mark.expensive 

198@pytest.mark.unittest 

199def test_bell_measure() -> None: 

200 circuits, results_n_layers_1, results_n_layers_3, skip_indices = get_test_cases() 

201 

202 test_cases = [] 

203 for circuit_id, res_1l in zip(circuits, results_n_layers_1): 

204 if circuit_id in skip_indices: 

205 continue 

206 if isinstance(circuit_id, int): 

207 test_cases.append( 

208 { 

209 "circuit_type": f"Circuit_{circuit_id}", 

210 "n_qubits": 4, 

211 "n_layers": 1, 

212 "result": res_1l, 

213 } 

214 ) 

215 elif isinstance(circuit_id, str): 

216 test_cases.append( 

217 { 

218 "circuit_type": circuit_id, 

219 "n_qubits": 4, 

220 "n_layers": 1, 

221 "result": res_1l, 

222 } 

223 ) 

224 

225 tolerance = 0.55 # FIXME: reduce when reason for discrepancy is found 

226 ent_caps: list[tuple[str, float]] = [] 

227 for test_case in test_cases: 

228 print(f"--- Running Entanglement test for {test_case['circuit_type']} ---") 

229 model = Model( 

230 n_qubits=test_case["n_qubits"], 

231 n_layers=test_case["n_layers"], 

232 circuit_type=test_case["circuit_type"], 

233 data_reupload=False, 

234 initialization="random", 

235 ) 

236 

237 ent_cap = Entanglement.bell_measurements( 

238 model, n_samples=5000, seed=1000, cache=False 

239 ) 

240 

241 # Save results for later comparison 

242 circuit_number = test_case["circuit_type"] 

243 if circuit_number.split("_")[1].isdigit(): 

244 circuit_number = int(circuit_number.split("_")[1]) 

245 ent_caps.append((circuit_number, ent_cap)) 

246 

247 difference = abs(ent_cap - test_case["result"]) 

248 if math.isclose(difference, 0.0, abs_tol=1e-3): 

249 error = 0 

250 else: 

251 error = abs(ent_cap - test_case["result"]) / (test_case["result"]) 

252 

253 print( 

254 f"Entangling-capability: {ent_cap},\t" 

255 + f"Expected Result: {test_case['result']},\t" 

256 + f"Error: {error}" 

257 ) 

258 assert ( 

259 error < tolerance 

260 ), f"Entangling-capability of circuit {test_case['circuit_type']} is not\ 

261 {test_case['result']} but {ent_cap} instead.\ 

262 Deviation {(error * 100):.1f}%>{tolerance * 100}%" 

263 

264 references = sorted( 

265 [ 

266 (circuit, ent_result) 

267 for circuit, ent_result in zip(circuits, results_n_layers_1) 

268 if circuit not in skip_indices 

269 ], 

270 key=lambda x: x[1], 

271 ) 

272 

273 actuals = sorted(ent_caps, key=lambda x: x[1]) 

274 

275 print("Expected \t| Actual") 

276 for reference, actual in zip(references, actuals): 

277 print(f"{reference[0]}, {reference[1]} \t| {actual[0]}, {actual[1]}") 

278 assert [circuit for circuit, _ in actuals] == [ 

279 circuit for circuit, _ in references 

280 ], f"Order of circuits does not match: {actuals} != {references}" 

281 

282 

283@pytest.mark.unittest 

284def test_entangling_measures() -> None: 

285 test_cases = [ 

286 {"circuit_type": "Circuit_4", "n_qubits": 2, "n_layers": 1}, 

287 {"circuit_type": "Circuit_4", "n_qubits": 3, "n_layers": 1}, 

288 {"circuit_type": "Circuit_4", "n_qubits": 4, "n_layers": 1}, 

289 {"circuit_type": "Circuit_4", "n_qubits": 5, "n_layers": 1}, 

290 ] 

291 

292 for test_case in test_cases: 

293 model = Model( 

294 n_qubits=test_case["n_qubits"], 

295 n_layers=test_case["n_layers"], 

296 circuit_type=test_case["circuit_type"], 

297 data_reupload=False, 

298 initialization="random", 

299 ) 

300 

301 mw_meas = Entanglement.meyer_wallach( 

302 deepcopy(model), n_samples=2000, seed=1000, cache=False 

303 ) 

304 

305 bell_meas = Entanglement.bell_measurements( 

306 model, n_samples=2000, seed=1000, cache=False 

307 ) 

308 

309 assert math.isclose(mw_meas, bell_meas, abs_tol=1e-5), ( 

310 f"Meyer-Wallach and Bell-measurement are not the same. Got {mw_meas} " 

311 f"and {bell_meas}, respectively." 

312 )