Coverage for tests/test_quantum_clustering_funcs.py: 27%

103 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-14 03:01 -0800

1# This file is part of ctrl_bps. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21"""Unit tests for the clustering methods. 

22""" 

23 

24# Turn off "doesn't conform to snake_case naming style" because matching 

25# the unittest casing. 

26# pylint: disable=invalid-name 

27 

28import os 

29import unittest 

30 

31from cqg_test_utils import check_cqg 

32from lsst.ctrl.bps import BpsConfig 

33from lsst.ctrl.bps.quantum_clustering_funcs import dimension_clustering, single_quantum_clustering 

34from qg_test_utils import make_test_quantum_graph 

35 

36TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

37 

38 

39class TestSingleQuantumClustering(unittest.TestCase): 

40 """Tests for single_quantum_clustering method.""" 

41 

42 def setUp(self): 

43 self.qgraph = make_test_quantum_graph() 

44 

45 def tearDown(self): 

46 pass 

47 

48 def testClustering(self): 

49 """Test valid single quantum clustering.""" 

50 

51 # Note: the cluster config should be ignored. 

52 config = BpsConfig( 

53 { 

54 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

55 "cluster": {"cl1": {"pipetasks": "T1, T2, T3, T4", "dimensions": "D1, D2"}}, 

56 } 

57 ) 

58 

59 cqg = single_quantum_clustering(config, self.qgraph, "single") 

60 self.assertIsNotNone(cqg) 

61 self.assertIn(cqg.name, "single") 

62 self.assertEqual(len(cqg), len(self.qgraph)) 

63 

64 def testClusteringNoTemplate(self): 

65 """Test valid single quantum clustering wihtout a template for the 

66 cluster names.""" 

67 # Note: the cluster config should be ignored. 

68 config = BpsConfig({"cluster": {"cl1": {"pipetasks": "T1, T2, T3, T4", "dimensions": "D1, D2"}}}) 

69 

70 cqg = single_quantum_clustering(config, self.qgraph, "single-no-template") 

71 self.assertIsNotNone(cqg) 

72 self.assertIn(cqg.name, "single-no-template") 

73 self.assertEqual(len(cqg), len(self.qgraph)) 

74 

75 

76class TestDimensionClustering(unittest.TestCase): 

77 """Tests for dimension_clustering method.""" 

78 

79 def setUp(self): 

80 self.qgraph = make_test_quantum_graph() 

81 

82 def tearDown(self): 

83 pass 

84 

85 def testClusterAllInOne(self): 

86 """All tasks in one cluster.""" 

87 name = "all-in-one" 

88 config = BpsConfig( 

89 { 

90 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

91 "cluster": {"cl1": {"pipetasks": "T1, T2, T3, T4", "dimensions": "D1, D2"}}, 

92 } 

93 ) 

94 answer = { 

95 "name": name, 

96 "nodes": { 

97 "cl1_1_2": { 

98 "label": "cl1", 

99 "dims": {"D1": 1, "D2": 2}, 

100 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

101 }, 

102 "cl1_3_4": { 

103 "label": "cl1", 

104 "dims": {"D1": 3, "D2": 4}, 

105 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

106 }, 

107 }, 

108 "edges": [], 

109 } 

110 

111 cqg = dimension_clustering(config, self.qgraph, name) 

112 check_cqg(cqg, answer) 

113 

114 def testClusterTemplate(self): 

115 """Test uses clusterTemplate value to name clusters.""" 

116 name = "cluster-template" 

117 config = BpsConfig( 

118 { 

119 "templateDataId": "tdid_{D1}_{D2}_{D3}_{D4}", 

120 "cluster": { 

121 "cl1": { 

122 "clusterTemplate": "ct_{D1}_{D2}_{D3}_{D4}", 

123 "pipetasks": "T1, T2, T3, T4", 

124 "dimensions": "D1, D2", 

125 } 

126 }, 

127 } 

128 ) 

129 # Note: clusterTemplate can produce trailing underscore 

130 answer = { 

131 "name": name, 

132 "nodes": { 

133 "ct_1_2_": { 

134 "label": "cl1", 

135 "dims": {"D1": 1, "D2": 2}, 

136 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

137 }, 

138 "ct_3_4_": { 

139 "label": "cl1", 

140 "dims": {"D1": 3, "D2": 4}, 

141 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

142 }, 

143 }, 

144 "edges": [], 

145 } 

146 

147 cqg = dimension_clustering(config, self.qgraph, name) 

148 check_cqg(cqg, answer) 

149 

150 def testClusterNoDims(self): 

151 """Test if clusters have no dimensions.""" 

152 name = "cluster-no-dims" 

153 config = BpsConfig( 

154 { 

155 "templateDataId": "tdid_{D1}_{D2}_{D3}_{D4}", 

156 "cluster": { 

157 "cl1": { 

158 "pipetasks": "T1, T2", 

159 }, 

160 "cl2": { 

161 "pipetasks": "T3, T4", 

162 }, 

163 }, 

164 } 

165 ) 

166 answer = { 

167 "name": name, 

168 "nodes": { 

169 "cl1": {"label": "cl1", "dims": {}, "counts": {"T1": 2, "T2": 2}}, 

170 "cl2": {"label": "cl2", "dims": {}, "counts": {"T3": 2, "T4": 2}}, 

171 }, 

172 "edges": [("cl1", "cl2")], 

173 } 

174 

175 cqg = dimension_clustering(config, self.qgraph, name) 

176 check_cqg(cqg, answer) 

177 

178 def testClusterTaskRepeat(self): 

179 """Can't have PipelineTask in more than one cluster.""" 

180 name = "task-repeat" 

181 config = BpsConfig( 

182 { 

183 "templateDataId": "tdid_{D1}_{D2}_{D3}_{D4}", 

184 "cluster": { 

185 "cl1": { 

186 "pipetasks": "T1, T2", 

187 }, 

188 "cl2": { 

189 "pipetasks": "T2, T3, T4", 

190 }, 

191 }, 

192 } 

193 ) 

194 

195 with self.assertRaises(RuntimeError): 

196 _ = dimension_clustering(config, self.qgraph, name) 

197 

198 def testClusterMissingDimValue(self): 

199 """Quantum can't be missing a value for a clustering dimension.""" 

200 config = BpsConfig( 

201 { 

202 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

203 "cluster": {"cl1": {"pipetasks": "T1, T2, T3, T4", "dimensions": "D1, NotThere"}}, 

204 } 

205 ) 

206 

207 with self.assertRaises(RuntimeError): 

208 _ = dimension_clustering(config, self.qgraph, "missing-dim-value") 

209 

210 def testClusterEqualDim1(self): 

211 """Test equalDimensions using right half.""" 

212 name = "equal-dim" 

213 config = BpsConfig( 

214 { 

215 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

216 "cluster": { 

217 "cl1": { 

218 "pipetasks": "T1, T2, T3, T4", 

219 "dimensions": "D1, NotThere", 

220 "equalDimensions": "NotThere:D2", 

221 } 

222 }, 

223 } 

224 ) 

225 answer = { 

226 "name": name, 

227 "nodes": { 

228 "cl1_1_2": { 

229 "label": "cl1", 

230 "dims": {"D1": 1, "NotThere": 2}, 

231 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

232 }, 

233 "cl1_3_4": { 

234 "label": "cl1", 

235 "dims": {"D1": 3, "NotThere": 4}, 

236 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

237 }, 

238 }, 

239 "edges": [], 

240 } 

241 

242 cqg = dimension_clustering(config, self.qgraph, name) 

243 check_cqg(cqg, answer) 

244 

245 def testClusterEqualDim2(self): 

246 """Test equalDimensions using left half.""" 

247 name = "equal-dim-2" 

248 config = BpsConfig( 

249 { 

250 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

251 "cluster": { 

252 "cl1": { 

253 "pipetasks": "T1, T2, T3, T4", 

254 "dimensions": "D1, NotThere", 

255 "equalDimensions": "D2:NotThere", 

256 } 

257 }, 

258 } 

259 ) 

260 answer = { 

261 "name": name, 

262 "nodes": { 

263 "cl1_1_2": { 

264 "label": "cl1", 

265 "dims": {"D1": 1, "NotThere": 2}, 

266 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

267 }, 

268 "cl1_3_4": { 

269 "label": "cl1", 

270 "dims": {"D1": 3, "NotThere": 4}, 

271 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

272 }, 

273 }, 

274 "edges": [], 

275 } 

276 

277 cqg = dimension_clustering(config, self.qgraph, name) 

278 check_cqg(cqg, answer) 

279 

280 def testClusterMult(self): 

281 """Test multiple tasks in multiple clusters.""" 

282 name = "cluster-mult" 

283 config = BpsConfig( 

284 { 

285 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

286 "cluster": { 

287 "cl1": {"pipetasks": "T1, T2", "dimensions": "D1, D2"}, 

288 "cl2": {"pipetasks": "T3, T4", "dimensions": "D1, D2"}, 

289 }, 

290 } 

291 ) 

292 answer = { 

293 "name": name, 

294 "nodes": { 

295 "cl1_1_2": {"label": "cl1", "dims": {"D1": 1, "D2": 2}, "counts": {"T1": 1, "T2": 1}}, 

296 "cl1_3_4": {"label": "cl1", "dims": {"D1": 3, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

297 "cl2_1_2": {"label": "cl2", "dims": {"D1": 1, "D2": 2}, "counts": {"T3": 1, "T4": 1}}, 

298 "cl2_3_4": {"label": "cl2", "dims": {"D1": 3, "D2": 4}, "counts": {"T3": 1, "T4": 1}}, 

299 }, 

300 "edges": [("cl1_3_4", "cl2_3_4"), ("cl1_1_2", "cl2_1_2")], 

301 } 

302 

303 cqg = dimension_clustering(config, self.qgraph, name) 

304 check_cqg(cqg, answer) 

305 

306 def testClusterPart(self): 

307 """Test will use templateDataId if no clusterTemplate.""" 

308 name = "cluster-part" 

309 config = BpsConfig( 

310 { 

311 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

312 "cluster": { 

313 "cl1": {"pipetasks": "T1, T2", "dimensions": "D1, D2"}, 

314 }, 

315 } 

316 ) 

317 answer = { 

318 "name": name, 

319 "nodes": { 

320 "cl1_1_2": {"label": "cl1", "dims": {"D1": 1, "D2": 2}, "counts": {"T1": 1, "T2": 1}}, 

321 "cl1_3_4": {"label": "cl1", "dims": {"D1": 3, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

322 "NODENAME_T3_1_2_": {"label": "T3", "dims": {"D1": 1, "D2": 2}, "counts": {"T3": 1}}, 

323 "NODENAME_T3_3_4_": {"label": "T3", "dims": {"D1": 3, "D2": 4}, "counts": {"T3": 1}}, 

324 "NODENAME_T4_1_2_": {"label": "T4", "dims": {"D1": 1, "D2": 2}, "counts": {"T4": 1}}, 

325 "NODENAME_T4_3_4_": {"label": "T4", "dims": {"D1": 3, "D2": 4}, "counts": {"T4": 1}}, 

326 }, 

327 "edges": [("cl1_1_2", "NODENAME_T3_1_2_"), ("cl1_3_4", "NODENAME_T3_3_4_")], 

328 } 

329 

330 cqg = dimension_clustering(config, self.qgraph, name) 

331 check_cqg(cqg, answer) 

332 

333 def testClusterPartNoTemplate(self): 

334 """No templateDataId nor clusterTemplate (use cluster label).""" 

335 name = "cluster-part-no-template" 

336 config = BpsConfig( 

337 { 

338 "cluster": { 

339 "cl1": {"pipetasks": "T1, T2"}, 

340 } 

341 } 

342 ) 

343 answer = { 

344 "name": name, 

345 "nodes": { 

346 "cl1": {"label": "cl1", "dims": {}, "counts": {"T1": 2, "T2": 2}}, 

347 "NODEONLY_T3_{'D1': 1, 'D2': 2}": { 

348 "label": "T3", 

349 "dims": {"D1": 1, "D2": 2}, 

350 "counts": {"T3": 1}, 

351 }, 

352 "NODEONLY_T3_{'D1': 3, 'D2': 4}": { 

353 "label": "T3", 

354 "dims": {"D1": 3, "D2": 4}, 

355 "counts": {"T3": 1}, 

356 }, 

357 "NODEONLY_T4_{'D1': 1, 'D2': 2}": { 

358 "label": "T4", 

359 "dims": {"D1": 1, "D2": 2}, 

360 "counts": {"T4": 1}, 

361 }, 

362 "NODEONLY_T4_{'D1': 3, 'D2': 4}": { 

363 "label": "T4", 

364 "dims": {"D1": 3, "D2": 4}, 

365 "counts": {"T4": 1}, 

366 }, 

367 }, 

368 "edges": [("cl1", "NODEONLY_T3_{'D1': 1, 'D2': 2}"), ("cl1", "NODEONLY_T3_{'D1': 3, 'D2': 4}")], 

369 } 

370 

371 cqg = dimension_clustering(config, self.qgraph, name) 

372 check_cqg(cqg, answer) 

373 

374 def testClusterExtra(self): 

375 """Clustering includes labels of pipetasks that aren't in QGraph. 

376 They should just be ignored.""" 

377 name = "extra" 

378 config = BpsConfig( 

379 { 

380 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

381 "cluster": { 

382 "cl1": {"pipetasks": "T1, Extra1, T2, Extra2, T3, T4", "dimensions": "D1, D2"}, 

383 }, 

384 } 

385 ) 

386 answer = { 

387 "name": name, 

388 "nodes": { 

389 "cl1_1_2": { 

390 "label": "cl1", 

391 "dims": {"D1": 1, "D2": 2}, 

392 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

393 }, 

394 "cl1_3_4": { 

395 "label": "cl1", 

396 "dims": {"D1": 3, "D2": 4}, 

397 "counts": {"T1": 1, "T2": 1, "T3": 1, "T4": 1}, 

398 }, 

399 }, 

400 "edges": [], 

401 } 

402 

403 cqg = dimension_clustering(config, self.qgraph, name) 

404 check_cqg(cqg, answer) 

405 

406 def testClusterRepeat(self): 

407 """A PipelineTask appears in more than one cluster definition.""" 

408 config = BpsConfig( 

409 { 

410 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

411 "cluster": { 

412 "cl1": {"pipetasks": "T1, T2, T3, T4", "dimensions": "D1, D2"}, 

413 "cl2": {"pipetasks": "T2", "dimensions": "D1, D2"}, 

414 }, 

415 } 

416 ) 

417 

418 with self.assertRaises(RuntimeError): 

419 _ = dimension_clustering(config, self.qgraph, "repeat-task") 

420 

421 def testClusterDepends(self): 

422 """Part of a chain of PipelineTask appears in different cluster.""" 

423 config = BpsConfig( 

424 { 

425 "templateDataId": "{D1}_{D2}_{D3}_{D4}", 

426 "cluster": { 

427 "cl1": {"pipetasks": "T1, T3, T4", "dimensions": "D1, D2"}, 

428 "cl2": {"pipetasks": "T2", "dimensions": "D1, D2"}, 

429 }, 

430 } 

431 ) 

432 

433 with self.assertRaises(RuntimeError): 

434 _ = dimension_clustering(config, self.qgraph, "task-depends") 

435 

436 

437if __name__ == "__main__": 437 ↛ 438line 437 didn't jump to line 438, because the condition on line 437 was never true

438 unittest.main()