Coverage for tests / test_quantum_clustering_funcs.py: 20%

262 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-24 08:21 +0000

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 software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

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

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

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

18# (at your option) any later version. 

19# 

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

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

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

23# GNU General Public License for more details. 

24# 

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

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

27"""Unit tests for the clustering methods.""" 

28 

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

30# the unittest casing. 

31# pylint: disable=invalid-name 

32 

33import logging 

34import os 

35import unittest 

36 

37from cqg_test_utils import check_cqg 

38from qg_test_utils import make_test_quantum_graph 

39 

40import lsst.ctrl.bps.quantum_clustering_funcs as qcf 

41from lsst.ctrl.bps import BpsConfig 

42 

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

44 

45 

46class TestSingleQuantumClustering(unittest.TestCase): 

47 """Tests for single_quantum_clustering method.""" 

48 

49 def setUp(self): 

50 self.qgraph = make_test_quantum_graph() 

51 

52 def tearDown(self): 

53 pass 

54 

55 def testClustering(self): 

56 """Test valid single quantum clustering.""" 

57 # Note: the cluster config should be ignored. 

58 config = BpsConfig( 

59 { 

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

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

62 } 

63 ) 

64 

65 name = "single" 

66 cqg = qcf.single_quantum_clustering(config, self.qgraph, name) 

67 answer = { 

68 "name": name, 

69 "nodes": { 

70 "NODENAME_T1_1_2_": {"label": "T1", "dims": {"D1": 1, "D2": 2}, "counts": {"T1": 1}}, 

71 "NODENAME_T1_1_4_": {"label": "T1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1}}, 

72 "NODENAME_T1_3_4_": {"label": "T1", "dims": {"D1": 3, "D2": 4}, "counts": {"T1": 1}}, 

73 "NODENAME_T2_1_2_": {"label": "T2", "dims": {"D1": 1, "D2": 2}, "counts": {"T2": 1}}, 

74 "NODENAME_T2_1_4_": {"label": "T2", "dims": {"D1": 1, "D2": 4}, "counts": {"T2": 1}}, 

75 "NODENAME_T2_3_4_": {"label": "T2", "dims": {"D1": 3, "D2": 4}, "counts": {"T2": 1}}, 

76 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

77 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

78 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

80 "NODENAME_T3_1_4_": {"label": "T3", "dims": {"D1": 1, "D2": 4}, "counts": {"T3": 1}}, 

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

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

83 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

85 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

86 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

87 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

88 }, 

89 "edges": [ 

90 ("NODENAME_T1_1_2_", "NODENAME_T2_1_2_"), 

91 ("NODENAME_T1_1_4_", "NODENAME_T2_1_4_"), 

92 ("NODENAME_T1_3_4_", "NODENAME_T2_3_4_"), 

93 ("NODENAME_T2_1_2_", "NODENAME_T2b_1_2_"), 

94 ("NODENAME_T2_1_4_", "NODENAME_T2b_1_4_"), 

95 ("NODENAME_T2_3_4_", "NODENAME_T2b_3_4_"), 

96 ("NODENAME_T2_1_2_", "NODENAME_T3_1_2_"), 

97 ("NODENAME_T2_1_4_", "NODENAME_T3_1_4_"), 

98 ("NODENAME_T2_3_4_", "NODENAME_T3_3_4_"), 

99 ("NODENAME_T3_1_2_", "NODENAME_T4_1_2_"), 

100 ("NODENAME_T3_1_4_", "NODENAME_T4_1_4_"), 

101 ("NODENAME_T3_3_4_", "NODENAME_T4_3_4_"), 

102 ], 

103 } 

104 

105 check_cqg(cqg, answer) 

106 

107 def testClusteringNoTemplate(self): 

108 """Test valid single quantum clustering without a template for the 

109 cluster names. 

110 """ 

111 # Note: the cluster config should be ignored. 

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

113 

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

115 self.assertIsNotNone(cqg) 

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

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

118 

119 

120class TestDimensionClustering(unittest.TestCase): 

121 """Tests for dimension_clustering method.""" 

122 

123 def setUp(self): 

124 self.qgraph = make_test_quantum_graph() 

125 

126 def tearDown(self): 

127 pass 

128 

129 def testClusterAllInOne(self): 

130 """All tasks in one cluster.""" 

131 name = "all-in-one" 

132 config = BpsConfig( 

133 { 

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

135 "cluster": {"cl1": {"pipetasks": "T1, T2, T2b, T3, T4, T5", "dimensions": "D1, D2"}}, 

136 } 

137 ) 

138 answer = { 

139 "name": name, 

140 "nodes": { 

141 "cl1_1_2": { 

142 "label": "cl1", 

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

144 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1, "T5": 1}, 

145 }, 

146 "cl1_1_4": { 

147 "label": "cl1", 

148 "dims": {"D1": 1, "D2": 4}, 

149 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1, "T5": 1}, 

150 }, 

151 "cl1_3_4": { 

152 "label": "cl1", 

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

154 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1, "T5": 1}, 

155 }, 

156 }, 

157 "edges": [], 

158 } 

159 

160 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

161 check_cqg(cqg, answer) 

162 

163 def testClusterTemplate(self): 

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

165 name = "cluster-template" 

166 config = BpsConfig( 

167 { 

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

169 "cluster": { 

170 "cl1": { 

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

172 "pipetasks": "T1, T2, T2b, T3, T4, T5", 

173 "dimensions": "D1, D2", 

174 } 

175 }, 

176 } 

177 ) 

178 # Note: clusterTemplate can produce trailing underscore 

179 answer = { 

180 "name": name, 

181 "nodes": { 

182 "ct_1_2_": { 

183 "label": "cl1", 

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

185 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1, "T5": 1}, 

186 }, 

187 "ct_1_4_": { 

188 "label": "cl1", 

189 "dims": {"D1": 1, "D2": 4}, 

190 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1, "T5": 1}, 

191 }, 

192 "ct_3_4_": { 

193 "label": "cl1", 

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

195 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1, "T5": 1}, 

196 }, 

197 }, 

198 "edges": [], 

199 } 

200 

201 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

202 check_cqg(cqg, answer) 

203 

204 def testClusterNoDims(self): 

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

206 name = "cluster-no-dims" 

207 config = BpsConfig( 

208 { 

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

210 "cluster": { 

211 "cl1": { 

212 "pipetasks": "T1, T2", 

213 }, 

214 "cl2": { 

215 "pipetasks": "T3, T4", 

216 }, 

217 }, 

218 } 

219 ) 

220 answer = { 

221 "name": name, 

222 "nodes": { 

223 "cl1": {"label": "cl1", "dims": {}, "counts": {"T1": 3, "T2": 3}}, 

224 "NODENAME_T2b_tdid_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

225 "NODENAME_T2b_tdid_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

226 "NODENAME_T2b_tdid_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

227 "cl2": {"label": "cl2", "dims": {}, "counts": {"T3": 3, "T4": 3}}, 

228 "NODENAME_T5_tdid_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

229 "NODENAME_T5_tdid_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

230 "NODENAME_T5_tdid_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

231 }, 

232 "edges": [ 

233 ("cl1", "cl2"), 

234 ("cl1", "NODENAME_T2b_tdid_1_2_"), 

235 ("cl1", "NODENAME_T2b_tdid_1_4_"), 

236 ("cl1", "NODENAME_T2b_tdid_3_4_"), 

237 ], 

238 } 

239 

240 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

241 check_cqg(cqg, answer) 

242 

243 def testClusterTaskRepeat(self): 

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

245 name = "task-repeat" 

246 config = BpsConfig( 

247 { 

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

249 "cluster": { 

250 "cl1": { 

251 "pipetasks": "T1, T2", 

252 }, 

253 "cl2": { 

254 "pipetasks": "T2, T3, T4", 

255 }, 

256 }, 

257 } 

258 ) 

259 

260 with self.assertRaises(RuntimeError): 

261 _ = qcf.dimension_clustering(config, self.qgraph, name) 

262 

263 def testClusterMissingDimValue(self): 

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

265 config = BpsConfig( 

266 { 

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

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

269 } 

270 ) 

271 

272 with self.assertRaises(RuntimeError): 

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

274 

275 def testClusterEqualDim1(self): 

276 """Test equalDimensions using right half.""" 

277 name = "equal-dim" 

278 config = BpsConfig( 

279 { 

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

281 "cluster": { 

282 "cl1": { 

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

284 "dimensions": "D1, NotThere", 

285 "equalDimensions": "NotThere:D2", 

286 } 

287 }, 

288 } 

289 ) 

290 answer = { 

291 "name": name, 

292 "nodes": { 

293 "cl1_1_2": { 

294 "label": "cl1", 

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

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

297 }, 

298 "cl1_1_4": { 

299 "label": "cl1", 

300 "dims": {"D1": 1, "NotThere": 4}, 

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

302 }, 

303 "cl1_3_4": { 

304 "label": "cl1", 

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

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

307 }, 

308 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

309 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

310 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

311 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

312 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

313 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

314 }, 

315 "edges": [ 

316 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

317 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

318 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

319 ], 

320 } 

321 

322 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

323 check_cqg(cqg, answer) 

324 

325 def testClusterEqualDim2(self): 

326 """Test equalDimensions using left half.""" 

327 name = "equal-dim-2" 

328 config = BpsConfig( 

329 { 

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

331 "cluster": { 

332 "cl1": { 

333 "pipetasks": "T1, T2, T2b, T3, T4", 

334 "dimensions": "D1, NotThere", 

335 "equalDimensions": "D2:NotThere", 

336 } 

337 }, 

338 } 

339 ) 

340 answer = { 

341 "name": name, 

342 "nodes": { 

343 "cl1_1_2": { 

344 "label": "cl1", 

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

346 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1}, 

347 }, 

348 "cl1_1_4": { 

349 "label": "cl1", 

350 "dims": {"D1": 1, "NotThere": 4}, 

351 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1}, 

352 }, 

353 "cl1_3_4": { 

354 "label": "cl1", 

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

356 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1}, 

357 }, 

358 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

359 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

360 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

361 }, 

362 "edges": [], 

363 } 

364 

365 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

366 check_cqg(cqg, answer) 

367 

368 def testClusterMult(self): 

369 """Test multiple tasks in multiple clusters.""" 

370 name = "cluster-mult" 

371 config = BpsConfig( 

372 { 

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

374 "cluster": { 

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

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

377 }, 

378 } 

379 ) 

380 answer = { 

381 "name": name, 

382 "nodes": { 

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

384 "cl1_1_4": {"label": "cl1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

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

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

387 "cl2_1_4": {"label": "cl2", "dims": {"D1": 1, "D2": 4}, "counts": {"T3": 1, "T4": 1}}, 

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

389 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

390 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

391 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

392 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

393 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

394 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

395 }, 

396 "edges": [ 

397 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

398 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

399 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

400 ("cl1_1_2", "cl2_1_2"), 

401 ("cl1_1_4", "cl2_1_4"), 

402 ("cl1_3_4", "cl2_3_4"), 

403 ], 

404 } 

405 

406 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

407 check_cqg(cqg, answer) 

408 

409 def testClusterPart(self): 

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

411 name = "cluster-part" 

412 config = BpsConfig( 

413 { 

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

415 "cluster": { 

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

417 }, 

418 } 

419 ) 

420 answer = { 

421 "name": name, 

422 "nodes": { 

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

424 "cl1_1_4": {"label": "cl1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

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

426 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

427 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

428 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

430 "NODENAME_T3_1_4_": {"label": "T3", "dims": {"D1": 1, "D2": 4}, "counts": {"T3": 1}}, 

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

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

433 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

435 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

436 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

437 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

438 }, 

439 "edges": [ 

440 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

441 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

442 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

443 ("cl1_1_2", "NODENAME_T3_1_2_"), 

444 ("cl1_3_4", "NODENAME_T3_3_4_"), 

445 ("cl1_1_4", "NODENAME_T3_1_4_"), 

446 ("NODENAME_T3_1_2_", "NODENAME_T4_1_2_"), 

447 ("NODENAME_T3_3_4_", "NODENAME_T4_3_4_"), 

448 ("NODENAME_T3_1_4_", "NODENAME_T4_1_4_"), 

449 ], 

450 } 

451 

452 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

453 check_cqg(cqg, answer) 

454 

455 def testClusterPartNoTemplate(self): 

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

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

458 config = BpsConfig( 

459 { 

460 "cluster": { 

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

462 } 

463 } 

464 ) 

465 answer = { 

466 "name": name, 

467 "nodes": { 

468 "cl1": {"label": "cl1", "dims": {}, "counts": {"T1": 3, "T2": 3}}, 

469 "NODEONLY_T2b_{'D1': 1, 'D2': 2}": { 

470 "label": "T2b", 

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

472 "counts": {"T2b": 1}, 

473 }, 

474 "NODEONLY_T2b_{'D1': 1, 'D2': 4}": { 

475 "label": "T2b", 

476 "dims": {"D1": 1, "D2": 4}, 

477 "counts": {"T2b": 1}, 

478 }, 

479 "NODEONLY_T2b_{'D1': 3, 'D2': 4}": { 

480 "label": "T2b", 

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

482 "counts": {"T2b": 1}, 

483 }, 

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

485 "label": "T3", 

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

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

488 }, 

489 "NODEONLY_T3_{'D1': 1, 'D2': 4}": { 

490 "label": "T3", 

491 "dims": {"D1": 1, "D2": 4}, 

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

493 }, 

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

495 "label": "T3", 

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

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

498 }, 

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

500 "label": "T4", 

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

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

503 }, 

504 "NODEONLY_T4_{'D1': 1, 'D2': 4}": { 

505 "label": "T4", 

506 "dims": {"D1": 1, "D2": 4}, 

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

508 }, 

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

510 "label": "T4", 

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

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

513 }, 

514 "NODEONLY_T5_{'D1': 1, 'D2': 2}": { 

515 "label": "T5", 

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

517 "counts": {"T5": 1}, 

518 }, 

519 "NODEONLY_T5_{'D1': 1, 'D2': 4}": { 

520 "label": "T5", 

521 "dims": {"D1": 1, "D2": 4}, 

522 "counts": {"T5": 1}, 

523 }, 

524 "NODEONLY_T5_{'D1': 3, 'D2': 4}": { 

525 "label": "T5", 

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

527 "counts": {"T5": 1}, 

528 }, 

529 }, 

530 "edges": [ 

531 ("cl1", "NODEONLY_T2b_{'D1': 1, 'D2': 2}"), 

532 ("cl1", "NODEONLY_T2b_{'D1': 1, 'D2': 4}"), 

533 ("cl1", "NODEONLY_T2b_{'D1': 3, 'D2': 4}"), 

534 ("cl1", "NODEONLY_T3_{'D1': 1, 'D2': 2}"), 

535 ("cl1", "NODEONLY_T3_{'D1': 1, 'D2': 4}"), 

536 ("cl1", "NODEONLY_T3_{'D1': 3, 'D2': 4}"), 

537 ("NODEONLY_T3_{'D1': 1, 'D2': 2}", "NODEONLY_T4_{'D1': 1, 'D2': 2}"), 

538 ("NODEONLY_T3_{'D1': 1, 'D2': 4}", "NODEONLY_T4_{'D1': 1, 'D2': 4}"), 

539 ("NODEONLY_T3_{'D1': 3, 'D2': 4}", "NODEONLY_T4_{'D1': 3, 'D2': 4}"), 

540 ], 

541 } 

542 

543 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

544 check_cqg(cqg, answer) 

545 

546 def testClusterExtra(self): 

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

548 They should just be ignored. 

549 """ 

550 name = "extra" 

551 config = BpsConfig( 

552 { 

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

554 "cluster": { 

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

556 }, 

557 } 

558 ) 

559 answer = { 

560 "name": name, 

561 "nodes": { 

562 "cl1_1_2": { 

563 "label": "cl1", 

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

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

566 }, 

567 "cl1_1_4": { 

568 "label": "cl1", 

569 "dims": {"D1": 1, "D2": 4}, 

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

571 }, 

572 "cl1_3_4": { 

573 "label": "cl1", 

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

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

576 }, 

577 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

578 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

579 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

580 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

581 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

582 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

583 }, 

584 "edges": [ 

585 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

586 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

587 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

588 ], 

589 } 

590 

591 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

592 check_cqg(cqg, answer) 

593 

594 def testClusterCycle(self): 

595 """The clustering produces a cycle.""" 

596 config = BpsConfig( 

597 { 

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

599 "cluster": { 

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

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

602 }, 

603 } 

604 ) 

605 

606 with self.assertLogs("lsst.ctrl.bps.quantum_clustering_funcs", level=logging.ERROR) as cm: 

607 with self.assertRaisesRegex(RuntimeError, "Cluster pipetasks do not create a DAG"): 

608 _ = qcf.dimension_clustering(config, self.qgraph, "cycle") 

609 self.assertRegex(cm.records[-1].getMessage(), "Found cycle when making clusters: .*") 

610 

611 def testClusterDepends(self): 

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

613 config = BpsConfig( 

614 { 

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

616 "cluster": { 

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

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

619 }, 

620 } 

621 ) 

622 

623 with self.assertRaises(RuntimeError): 

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

625 

626 def testClusterOrder(self): 

627 """Ensure clusters method is in topological order as some 

628 uses require to always have processed parent before 

629 children. 

630 """ 

631 config = BpsConfig( 

632 { 

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

634 "cluster": { 

635 "cl2": {"pipetasks": "T2, T3", "dimensions": "D1, D2"}, 

636 }, 

637 } 

638 ) 

639 

640 cqg = qcf.dimension_clustering(config, self.qgraph, "task-cluster-order") 

641 processed = set() 

642 for cluster in cqg.clusters(): 

643 for parent in cqg.predecessors(cluster.name): 

644 self.assertIn( 

645 parent.name, 

646 processed, 

647 f"clusters() returned {cluster.name} before its parent {parent.name}", 

648 ) 

649 processed.add(cluster.name) 

650 

651 def testMissingMaxSize(self): 

652 name = "partition1" 

653 config = BpsConfig( 

654 { 

655 "useNodeIdInClusterName": False, 

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

657 "cluster": { 

658 "cl1": { 

659 "pipetasks": "T1, T2, T2b, T3, T4", 

660 "dimensions": "D1", 

661 "partitionDimensions": "D2", 

662 }, 

663 }, 

664 } 

665 ) 

666 with self.assertRaisesRegex(KeyError, "missing.*partitionMaxSize"): 

667 _ = qcf.dimension_clustering(config, self.qgraph, name) 

668 

669 def testPartition(self): 

670 name = "partition1" 

671 config = BpsConfig( 

672 { 

673 "useNodeIdInClusterName": False, 

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

675 "cluster": { 

676 "cl1": { 

677 "pipetasks": "T1, T2, T2b, T3, T4", 

678 "dimensions": "D1", 

679 "partitionDimensions": "D2", 

680 "partitionMaxSize": 1, 

681 }, 

682 }, 

683 } 

684 ) 

685 answer = { 

686 "name": name, 

687 "nodes": { 

688 "cl1_1_001": { 

689 "label": "cl1", 

690 "dims": {"D1": 1}, 

691 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1}, 

692 }, 

693 "cl1_1_002": { 

694 "label": "cl1", 

695 "dims": {"D1": 1}, 

696 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1}, 

697 }, 

698 "cl1_3_002": { 

699 "label": "cl1", 

700 "dims": {"D1": 3}, 

701 "counts": {"T1": 1, "T2": 1, "T2b": 1, "T3": 1, "T4": 1}, 

702 }, 

703 "T5_1_2_": { 

704 "label": "T5", 

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

706 "counts": {"T5": 1}, 

707 }, 

708 "T5_1_4_": { 

709 "label": "T5", 

710 "dims": {"D1": 1, "D2": 4}, 

711 "counts": {"T5": 1}, 

712 }, 

713 "T5_3_4_": { 

714 "label": "T5", 

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

716 "counts": {"T5": 1}, 

717 }, 

718 }, 

719 "edges": [], 

720 } 

721 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

722 check_cqg(cqg, answer) 

723 

724 def testDependenciesSink(self): 

725 name = "DependenciesSink" 

726 config = BpsConfig( 

727 { 

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

729 "cluster": { 

730 "cl1": { 

731 "pipetasks": "T2, T3", 

732 "dimensions": "D1, D2", 

733 "findDependencyMethod": "sink", 

734 }, 

735 }, 

736 } 

737 ) 

738 answer = { 

739 "name": name, 

740 "nodes": { 

741 "NODENAME_T1_1_2_": {"label": "T1", "dims": {"D1": 1, "D2": 2}, "counts": {"T1": 1}}, 

742 "NODENAME_T1_1_4_": {"label": "T1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1}}, 

743 "NODENAME_T1_3_4_": {"label": "T1", "dims": {"D1": 3, "D2": 4}, "counts": {"T1": 1}}, 

744 "cl1_1_2": {"label": "cl1", "dims": {"D1": 1, "D2": 2}, "counts": {"T2": 1, "T3": 1}}, 

745 "cl1_1_4": {"label": "cl1", "dims": {"D1": 1, "D2": 4}, "counts": {"T2": 1, "T3": 1}}, 

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

747 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

748 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

749 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

751 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

753 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

754 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

755 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

756 }, 

757 "edges": [ 

758 ("NODENAME_T1_1_2_", "cl1_1_2"), 

759 ("NODENAME_T1_1_4_", "cl1_1_4"), 

760 ("NODENAME_T1_3_4_", "cl1_3_4"), 

761 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

762 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

763 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

764 ("cl1_1_2", "NODENAME_T4_1_2_"), 

765 ("cl1_1_4", "NODENAME_T4_1_4_"), 

766 ("cl1_3_4", "NODENAME_T4_3_4_"), 

767 ], 

768 } 

769 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

770 check_cqg(cqg, answer) 

771 

772 def testDependenciesSink2Clusters(self): 

773 name = "DependenciesSink2Clusters" 

774 config = BpsConfig( 

775 { 

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

777 "cluster": { 

778 "cl1": { 

779 "pipetasks": "T1, T2", 

780 "dimensions": "D1, D2", 

781 "findDependencyMethod": "sink", 

782 }, 

783 "cl2": { 

784 "pipetasks": "T3, T4", 

785 "dimensions": "D1, D2", 

786 "findDependencyMethod": "sink", 

787 }, 

788 }, 

789 } 

790 ) 

791 answer = { 

792 "name": name, 

793 "nodes": { 

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

795 "cl1_1_4": {"label": "cl1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

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

797 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

798 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

799 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

801 "cl2_1_4": {"label": "cl2", "dims": {"D1": 1, "D2": 4}, "counts": {"T3": 1, "T4": 1}}, 

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

803 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

804 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

805 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

806 }, 

807 "edges": [ 

808 ("cl1_1_2", "cl2_1_2"), 

809 ("cl1_1_4", "cl2_1_4"), 

810 ("cl1_3_4", "cl2_3_4"), 

811 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

812 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

813 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

814 ], 

815 } 

816 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

817 check_cqg(cqg, answer) 

818 

819 def testDependenciesSinkMultDepends(self): 

820 name = "DependenciesSinkMultClusters" 

821 config = BpsConfig( 

822 { 

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

824 "cluster": { 

825 "cl1": { 

826 "pipetasks": "T2, T3", 

827 "findDependencyMethod": "sink", 

828 }, 

829 }, 

830 } 

831 ) 

832 answer = { 

833 "name": name, 

834 "nodes": { 

835 "NODENAME_T1_1_2_": {"label": "T1", "dims": {"D1": 1, "D2": 2}, "counts": {"T1": 1}}, 

836 "NODENAME_T1_1_4_": {"label": "T1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1}}, 

837 "NODENAME_T1_3_4_": {"label": "T1", "dims": {"D1": 3, "D2": 4}, "counts": {"T1": 1}}, 

838 "cl1": {"label": "cl1", "dims": {}, "counts": {"T2": 3, "T3": 3}}, 

839 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

840 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

841 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

843 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

845 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

846 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

847 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

848 }, 

849 "edges": [ 

850 ("NODENAME_T1_1_2_", "cl1"), 

851 ("NODENAME_T1_1_4_", "cl1"), 

852 ("NODENAME_T1_3_4_", "cl1"), 

853 ("cl1", "NODENAME_T2b_1_2_"), 

854 ("cl1", "NODENAME_T2b_1_4_"), 

855 ("cl1", "NODENAME_T2b_3_4_"), 

856 ("cl1", "NODENAME_T4_1_2_"), 

857 ("cl1", "NODENAME_T4_1_4_"), 

858 ("cl1", "NODENAME_T4_3_4_"), 

859 ], 

860 } 

861 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

862 check_cqg(cqg, answer) 

863 

864 def testDependenciesSource(self): 

865 name = "DependenciesSource" 

866 config = BpsConfig( 

867 { 

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

869 "cluster": { 

870 "cl1": { 

871 "pipetasks": "T1, T2, T3", 

872 "dimensions": "D1, D2", 

873 "findDependencyMethod": "source", 

874 }, 

875 }, 

876 } 

877 ) 

878 answer = { 

879 "name": name, 

880 "nodes": { 

881 "cl1_1_2": { 

882 "label": "cl1", 

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

884 "counts": {"T1": 1, "T2": 1, "T3": 1}, 

885 }, 

886 "cl1_1_4": { 

887 "label": "cl1", 

888 "dims": {"D1": 1, "D2": 4}, 

889 "counts": {"T1": 1, "T2": 1, "T3": 1}, 

890 }, 

891 "cl1_3_4": { 

892 "label": "cl1", 

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

894 "counts": {"T1": 1, "T2": 1, "T3": 1}, 

895 }, 

896 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

897 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

898 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

900 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

902 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

903 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

904 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

905 }, 

906 "edges": [ 

907 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

908 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

909 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

910 ("cl1_1_2", "NODENAME_T4_1_2_"), 

911 ("cl1_1_4", "NODENAME_T4_1_4_"), 

912 ("cl1_3_4", "NODENAME_T4_3_4_"), 

913 ], 

914 } 

915 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

916 check_cqg(cqg, answer) 

917 

918 def testDependenciesSource2Clusters(self): 

919 name = "DependenciesSource2Clusters" 

920 config = BpsConfig( 

921 { 

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

923 "cluster": { 

924 "cl1": { 

925 "pipetasks": "T1, T2", 

926 "dimensions": "D1, D2", 

927 "findDependencyMethod": "source", 

928 }, 

929 "cl2": { 

930 "pipetasks": "T3, T4", 

931 "dimensions": "D1, D2", 

932 "findDependencyMethod": "source", 

933 }, 

934 }, 

935 } 

936 ) 

937 answer = { 

938 "name": name, 

939 "nodes": { 

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

941 "cl1_1_4": {"label": "cl1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

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

943 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

944 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

945 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

947 "cl2_1_4": {"label": "cl2", "dims": {"D1": 1, "D2": 4}, "counts": {"T3": 1, "T4": 1}}, 

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

949 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

950 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

951 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

952 }, 

953 "edges": [ 

954 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

955 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

956 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

957 ("cl1_1_2", "cl2_1_2"), 

958 ("cl1_1_4", "cl2_1_4"), 

959 ("cl1_3_4", "cl2_3_4"), 

960 ], 

961 } 

962 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

963 check_cqg(cqg, answer) 

964 

965 def testDependenciesSourceMultDepends(self): 

966 name = "DependenciesSourceMultClusters" 

967 config = BpsConfig( 

968 { 

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

970 "cluster": { 

971 "cl1": { 

972 "pipetasks": "T2, T3", 

973 "findDependencyMethod": "source", 

974 }, 

975 }, 

976 } 

977 ) 

978 answer = { 

979 "name": name, 

980 "nodes": { 

981 "NODENAME_T1_1_2_": {"label": "T1", "dims": {"D1": 1, "D2": 2}, "counts": {"T1": 1}}, 

982 "NODENAME_T1_1_4_": {"label": "T1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1}}, 

983 "NODENAME_T1_3_4_": {"label": "T1", "dims": {"D1": 3, "D2": 4}, "counts": {"T1": 1}}, 

984 "cl1": {"label": "cl1", "dims": {}, "counts": {"T2": 3, "T3": 3}}, 

985 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

986 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

987 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

989 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

991 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

992 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

993 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

994 }, 

995 "edges": [ 

996 ("NODENAME_T1_1_2_", "cl1"), 

997 ("NODENAME_T1_1_4_", "cl1"), 

998 ("NODENAME_T1_3_4_", "cl1"), 

999 ("cl1", "NODENAME_T2b_1_2_"), 

1000 ("cl1", "NODENAME_T2b_1_4_"), 

1001 ("cl1", "NODENAME_T2b_3_4_"), 

1002 ("cl1", "NODENAME_T4_1_2_"), 

1003 ("cl1", "NODENAME_T4_1_4_"), 

1004 ("cl1", "NODENAME_T4_3_4_"), 

1005 ], 

1006 } 

1007 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

1008 check_cqg(cqg, answer) 

1009 

1010 def testDependenciesExtra(self): 

1011 # Test if dependencies return more than in cluster 

1012 name = "DependenciesExtra" 

1013 config = BpsConfig( 

1014 { 

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

1016 "cluster": { 

1017 "cl1": { 

1018 "pipetasks": "T1, T2", 

1019 "dimensions": "D1, D2", 

1020 "findDependencyMethod": "source", 

1021 }, 

1022 }, 

1023 } 

1024 ) 

1025 answer = { 

1026 "name": name, 

1027 "nodes": { 

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

1029 "cl1_1_4": {"label": "cl1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

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

1031 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

1032 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

1033 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

1035 "NODENAME_T3_1_4_": {"label": "T3", "dims": {"D1": 1, "D2": 4}, "counts": {"T3": 1}}, 

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

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

1038 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

1040 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

1041 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

1042 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

1043 }, 

1044 "edges": [ 

1045 ("cl1_1_2", "NODENAME_T3_1_2_"), 

1046 ("cl1_1_4", "NODENAME_T3_1_4_"), 

1047 ("cl1_3_4", "NODENAME_T3_3_4_"), 

1048 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

1049 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

1050 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

1051 ("NODENAME_T3_1_2_", "NODENAME_T4_1_2_"), 

1052 ("NODENAME_T3_1_4_", "NODENAME_T4_1_4_"), 

1053 ("NODENAME_T3_3_4_", "NODENAME_T4_3_4_"), 

1054 ], 

1055 } 

1056 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

1057 check_cqg(cqg, answer) 

1058 

1059 def testDependenciesSameCluster(self): 

1060 name = "DependenciesSameCluster" 

1061 config = BpsConfig( 

1062 { 

1063 "cluster": { 

1064 "cl1": { 

1065 "pipetasks": "T1, T2, T3", 

1066 "dimensions": "D1", 

1067 "findDependencyMethod": "source", 

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

1069 }, 

1070 }, 

1071 } 

1072 ) 

1073 answer = { 

1074 "name": name, 

1075 "nodes": { 

1076 "ct_1_": { 

1077 "label": "cl1", 

1078 "dims": {"D1": 1}, 

1079 "counts": {"T1": 2, "T2": 2, "T3": 2}, 

1080 }, 

1081 "ct_3_": { 

1082 "label": "cl1", 

1083 "dims": {"D1": 3}, 

1084 "counts": {"T1": 1, "T2": 1, "T3": 1}, 

1085 }, 

1086 "NODEONLY_T2b_{'D1': 1, 'D2': 2}": { 

1087 "label": "T2b", 

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

1089 "counts": {"T2b": 1}, 

1090 }, 

1091 "NODEONLY_T2b_{'D1': 1, 'D2': 4}": { 

1092 "label": "T2b", 

1093 "dims": {"D1": 1, "D2": 4}, 

1094 "counts": {"T2b": 1}, 

1095 }, 

1096 "NODEONLY_T2b_{'D1': 3, 'D2': 4}": { 

1097 "label": "T2b", 

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

1099 "counts": {"T2b": 1}, 

1100 }, 

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

1102 "label": "T4", 

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

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

1105 }, 

1106 "NODEONLY_T4_{'D1': 1, 'D2': 4}": { 

1107 "label": "T4", 

1108 "dims": {"D1": 1, "D2": 4}, 

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

1110 }, 

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

1112 "label": "T4", 

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

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

1115 }, 

1116 "NODEONLY_T5_{'D1': 1, 'D2': 2}": { 

1117 "label": "T5", 

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

1119 "counts": {"T5": 1}, 

1120 }, 

1121 "NODEONLY_T5_{'D1': 1, 'D2': 4}": { 

1122 "label": "T5", 

1123 "dims": {"D1": 1, "D2": 4}, 

1124 "counts": {"T5": 1}, 

1125 }, 

1126 "NODEONLY_T5_{'D1': 3, 'D2': 4}": { 

1127 "label": "T5", 

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

1129 "counts": {"T5": 1}, 

1130 }, 

1131 }, 

1132 "edges": [ 

1133 ("ct_1_", "NODEONLY_T2b_{'D1': 1, 'D2': 2}"), 

1134 ("ct_1_", "NODEONLY_T2b_{'D1': 1, 'D2': 4}"), 

1135 ("ct_3_", "NODEONLY_T2b_{'D1': 3, 'D2': 4}"), 

1136 ("ct_1_", "NODEONLY_T4_{'D1': 1, 'D2': 2}"), 

1137 ("ct_1_", "NODEONLY_T4_{'D1': 1, 'D2': 4}"), 

1138 ("ct_3_", "NODEONLY_T4_{'D1': 3, 'D2': 4}"), 

1139 ], 

1140 } 

1141 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

1142 check_cqg(cqg, answer) 

1143 

1144 def testDependenciesBoth2Clusters(self): 

1145 name = "DependenciesBoth2Clusters" 

1146 config = BpsConfig( 

1147 { 

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

1149 "cluster": { 

1150 "cl1": { 

1151 "pipetasks": "T1, T2", 

1152 "dimensions": "D1, D2", 

1153 "findDependencyMethod": "sink", 

1154 }, 

1155 "cl2": { 

1156 "pipetasks": "T3, T4", 

1157 "dimensions": "D1, D2", 

1158 "findDependencyMethod": "source", 

1159 }, 

1160 }, 

1161 } 

1162 ) 

1163 answer = { 

1164 "name": name, 

1165 "nodes": { 

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

1167 "cl1_1_4": {"label": "cl1", "dims": {"D1": 1, "D2": 4}, "counts": {"T1": 1, "T2": 1}}, 

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

1169 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

1170 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

1171 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

1173 "cl2_1_4": {"label": "cl2", "dims": {"D1": 1, "D2": 4}, "counts": {"T3": 1, "T4": 1}}, 

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

1175 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

1176 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

1177 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

1178 }, 

1179 "edges": [ 

1180 ("cl1_1_2", "NODENAME_T2b_1_2_"), 

1181 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

1182 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

1183 ("cl1_1_2", "cl2_1_2"), 

1184 ("cl1_1_4", "cl2_1_4"), 

1185 ("cl1_3_4", "cl2_3_4"), 

1186 ], 

1187 } 

1188 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

1189 check_cqg(cqg, answer) 

1190 

1191 def testDependenciesBadMethod(self): 

1192 name = "DependenciesBadMethod" 

1193 config = BpsConfig( 

1194 { 

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

1196 "cluster": { 

1197 "cl1": { 

1198 "pipetasks": "T1, T2, T3", 

1199 "dimensions": "D1, D2", 

1200 "findDependencyMethod": "bad", 

1201 }, 

1202 }, 

1203 } 

1204 ) 

1205 with self.assertRaises(RuntimeError): 

1206 _ = qcf.dimension_clustering(config, self.qgraph, name) 

1207 

1208 def testDependenciesSinkUneven(self): 

1209 name = "DependenciesSinkUneven" 

1210 config = BpsConfig( 

1211 { 

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

1213 "cluster": { 

1214 "cl1": { 

1215 "pipetasks": "T1, T2, T3", 

1216 "dimensions": "D1, D2", 

1217 "findDependencyMethod": "sink", 

1218 }, 

1219 }, 

1220 } 

1221 ) 

1222 answer = { 

1223 "name": name, 

1224 "nodes": { 

1225 "cl1_1_2": { 

1226 "label": "cl1", 

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

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

1229 }, 

1230 "cl1_1_4": { 

1231 "label": "cl1", 

1232 "dims": {"D1": 1, "D2": 4}, 

1233 "counts": {"T2": 1, "T3": 1}, 

1234 }, 

1235 "cl1_3_4": { 

1236 "label": "cl1", 

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

1238 "counts": {"T1": 1, "T2": 1, "T3": 1}, 

1239 }, 

1240 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

1241 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

1242 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

1244 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

1246 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

1247 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

1248 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

1249 }, 

1250 "edges": [ 

1251 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

1252 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

1253 ("cl1_1_2", "NODENAME_T4_1_2_"), 

1254 ("cl1_1_4", "NODENAME_T4_1_4_"), 

1255 ("cl1_3_4", "NODENAME_T4_3_4_"), 

1256 ], 

1257 } 

1258 qgraph = make_test_quantum_graph(uneven=True) 

1259 cqg = qcf.dimension_clustering(config, qgraph, name) 

1260 check_cqg(cqg, answer) 

1261 

1262 def testDependenciesSourceUneven(self): 

1263 name = "DependenciesSourceUneven" 

1264 config = BpsConfig( 

1265 { 

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

1267 "cluster": { 

1268 "cl1": { 

1269 "pipetasks": "T1, T2, T3", 

1270 "dimensions": "D1, D2", 

1271 "findDependencyMethod": "source", 

1272 }, 

1273 }, 

1274 } 

1275 ) 

1276 answer = { 

1277 "name": name, 

1278 "nodes": { 

1279 "cl1_1_2": { 

1280 "label": "cl1", 

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

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

1283 }, 

1284 "cl1_1_4": { 

1285 "label": "cl1", 

1286 "dims": {"D1": 1, "D2": 4}, 

1287 "counts": {"T2": 1, "T3": 1}, 

1288 }, 

1289 "cl1_3_4": { 

1290 "label": "cl1", 

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

1292 "counts": {"T1": 1, "T2": 1, "T3": 1}, 

1293 }, 

1294 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

1295 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

1296 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

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

1298 "NODENAME_T4_1_4_": {"label": "T4", "dims": {"D1": 1, "D2": 4}, "counts": {"T4": 1}}, 

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

1300 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

1301 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

1302 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

1303 }, 

1304 "edges": [ 

1305 ("cl1_1_4", "NODENAME_T2b_1_4_"), 

1306 ("cl1_3_4", "NODENAME_T2b_3_4_"), 

1307 ("cl1_1_2", "NODENAME_T4_1_2_"), 

1308 ("cl1_1_4", "NODENAME_T4_1_4_"), 

1309 ("cl1_3_4", "NODENAME_T4_3_4_"), 

1310 ], 

1311 } 

1312 qgraph = make_test_quantum_graph(uneven=True) 

1313 cqg = qcf.dimension_clustering(config, qgraph, name) 

1314 check_cqg(cqg, answer) 

1315 

1316 def testDependenciesSinkPartition(self): 

1317 name = "DependenciesSinkPartition" 

1318 config = BpsConfig( 

1319 { 

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

1321 "cluster": { 

1322 "cl1": { 

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

1324 "dimensions": "D1", 

1325 "findDependencyMethod": "sink", 

1326 "partitionDimensions": "D2", 

1327 "partitionMaxSize": 1, 

1328 }, 

1329 }, 

1330 } 

1331 ) 

1332 answer = { 

1333 "name": name, 

1334 "nodes": { 

1335 "cl1_1_001": { 

1336 "label": "cl1", 

1337 "dims": {"D1": 1}, 

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

1339 }, 

1340 "cl1_1_002": { 

1341 "label": "cl1", 

1342 "dims": {"D1": 1}, 

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

1344 }, 

1345 "cl1_3_002": { 

1346 "label": "cl1", 

1347 "dims": {"D1": 3}, 

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

1349 }, 

1350 "NODENAME_T2b_1_2_": {"label": "T2b", "dims": {"D1": 1, "D2": 2}, "counts": {"T2b": 1}}, 

1351 "NODENAME_T2b_1_4_": {"label": "T2b", "dims": {"D1": 1, "D2": 4}, "counts": {"T2b": 1}}, 

1352 "NODENAME_T2b_3_4_": {"label": "T2b", "dims": {"D1": 3, "D2": 4}, "counts": {"T2b": 1}}, 

1353 "NODENAME_T5_1_2_": {"label": "T5", "dims": {"D1": 1, "D2": 2}, "counts": {"T5": 1}}, 

1354 "NODENAME_T5_1_4_": {"label": "T5", "dims": {"D1": 1, "D2": 4}, "counts": {"T5": 1}}, 

1355 "NODENAME_T5_3_4_": {"label": "T5", "dims": {"D1": 3, "D2": 4}, "counts": {"T5": 1}}, 

1356 }, 

1357 "edges": [ 

1358 ("cl1_1_001", "NODENAME_T2b_1_2_"), 

1359 ("cl1_1_002", "NODENAME_T2b_1_4_"), 

1360 ("cl1_3_002", "NODENAME_T2b_3_4_"), 

1361 ], 

1362 } 

1363 cqg = qcf.dimension_clustering(config, self.qgraph, name) 

1364 check_cqg(cqg, answer) 

1365 

1366 

1367class TestPartitionClusterValues(unittest.TestCase): 

1368 """Test partition_cluster_values function""" 

1369 

1370 def testMissingSizing(self): 

1371 values = list(range(1, 51)) 

1372 config = BpsConfig( 

1373 { 

1374 "pipetasks": "T1, T2, T2b, T3, T4", 

1375 "dimensions": "D1", 

1376 "partitionDimensions": "D2", 

1377 } 

1378 ) 

1379 with self.assertRaisesRegex(KeyError, "missing.*partitionMaxSize"): 

1380 _ = qcf.partition_cluster_values(config, "cluster1", values, 0) 

1381 

1382 def testExtraSizing(self): 

1383 values = list(range(1, 51)) 

1384 config = BpsConfig( 

1385 { 

1386 "pipetasks": "T1, T2, T2b, T3, T4", 

1387 "dimensions": "D1", 

1388 "partitionDimensions": "D2", 

1389 "partitionMaxClusters": 4, 

1390 "partitionMaxSize": 3, 

1391 } 

1392 ) 

1393 with self.assertRaisesRegex(KeyError, "more than one.*partitionMaxSize"): 

1394 _ = qcf.partition_cluster_values(config, "cluster1", values, 0) 

1395 

1396 def testMaxSizeEven(self): 

1397 values = list(range(1, 51)) 

1398 

1399 config = BpsConfig({"partitionMaxSize": 25}) 

1400 results = qcf.partition_cluster_values(config, "cluster1", values, 0) 

1401 truth = dict(zip(values, [1] * 25 + [2] * 25, strict=True)) 

1402 self.assertEqual(results, truth) 

1403 

1404 def testMaxSizeUneven(self): 

1405 values = list(range(1, 51)) 

1406 

1407 config = BpsConfig({"partitionMaxSize": 16}) 

1408 results = qcf.partition_cluster_values(config, "cluster1", values, 0) 

1409 truth = dict(zip(values, [1] * 16 + [2] * 16 + [3] * 16 + [4] * 2, strict=True)) 

1410 self.assertEqual(results, truth) 

1411 

1412 def testBadMaxClusters(self): 

1413 values = list(range(1, 51)) 

1414 

1415 config = BpsConfig({"partitionMaxClusters": 16}) 

1416 with self.assertRaisesRegex(RuntimeError, "same or larger than"): 

1417 _ = qcf.partition_cluster_values(config, "cluster1", values, 189) 

1418 

1419 def testMaxClustersEven(self): 

1420 # 10 "detectors", 50 "visits" each detector, want 100 clusters/jobs 

1421 values = list(range(1, 51)) 

1422 

1423 config = BpsConfig({"partitionMaxClusters": 100}) 

1424 results = qcf.partition_cluster_values(config, "cluster1", values, 10) 

1425 truth_partitions = [] 

1426 for x in range(1, 11): 

1427 truth_partitions.extend([x] * 5) 

1428 truth = dict(zip(values, truth_partitions, strict=True)) 

1429 self.assertEqual(results, truth) 

1430 

1431 def testMaxClustersSomewhatUneven(self): 

1432 self.maxDiff = None 

1433 

1434 # 10 "detectors", 50 "visits" each detector, want 90 clusters/jobs 

1435 values = list(range(1, 51)) 

1436 

1437 config = BpsConfig({"partitionMaxClusters": 90}) 

1438 results = qcf.partition_cluster_values(config, "cluster1", values, 10) 

1439 truth_partitions = [] 

1440 for x in range(1, 10): 

1441 if x <= 5: 

1442 truth_partitions.extend([x] * 6) 

1443 else: 

1444 truth_partitions.extend([x] * 5) 

1445 truth = dict(zip(values, truth_partitions, strict=True)) 

1446 self.assertEqual(results, truth) 

1447 

1448 def testMaxClustersUneven(self): 

1449 self.maxDiff = None 

1450 

1451 # 10 "detectors", 50 "visits" each detector, want 85 clusters/jobs 

1452 values = list(range(1, 51)) 

1453 

1454 config = BpsConfig({"partitionMaxClusters": 85}) 

1455 results = qcf.partition_cluster_values(config, "cluster1", values, 10) 

1456 truth_partitions = [] 

1457 for x in range(1, 9): 

1458 if x <= 2: 

1459 truth_partitions.extend([x] * 7) 

1460 else: 

1461 truth_partitions.extend([x] * 6) 

1462 truth = dict(zip(values, truth_partitions, strict=True)) 

1463 self.assertEqual(results, truth) 

1464 

1465 

1466if __name__ == "__main__": 

1467 unittest.main()