Coverage for tests / test_quantum_clustering_funcs.py: 20%
262 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 08:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 08:22 +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."""
29# Turn off "doesn't conform to snake_case naming style" because matching
30# the unittest casing.
31# pylint: disable=invalid-name
33import logging
34import os
35import unittest
37from cqg_test_utils import check_cqg
38from qg_test_utils import make_test_quantum_graph
40import lsst.ctrl.bps.quantum_clustering_funcs as qcf
41from lsst.ctrl.bps import BpsConfig
43TESTDIR = os.path.abspath(os.path.dirname(__file__))
46class TestSingleQuantumClustering(unittest.TestCase):
47 """Tests for single_quantum_clustering method."""
49 def setUp(self):
50 self.qgraph = make_test_quantum_graph()
52 def tearDown(self):
53 pass
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 )
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 }
105 check_cqg(cqg, answer)
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"}}})
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))
120class TestDimensionClustering(unittest.TestCase):
121 """Tests for dimension_clustering method."""
123 def setUp(self):
124 self.qgraph = make_test_quantum_graph()
126 def tearDown(self):
127 pass
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 }
160 cqg = qcf.dimension_clustering(config, self.qgraph, name)
161 check_cqg(cqg, answer)
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 }
201 cqg = qcf.dimension_clustering(config, self.qgraph, name)
202 check_cqg(cqg, answer)
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 }
240 cqg = qcf.dimension_clustering(config, self.qgraph, name)
241 check_cqg(cqg, answer)
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 )
260 with self.assertRaises(RuntimeError):
261 _ = qcf.dimension_clustering(config, self.qgraph, name)
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 )
272 with self.assertRaises(RuntimeError):
273 _ = qcf.dimension_clustering(config, self.qgraph, "missing-dim-value")
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 }
322 cqg = qcf.dimension_clustering(config, self.qgraph, name)
323 check_cqg(cqg, answer)
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 }
365 cqg = qcf.dimension_clustering(config, self.qgraph, name)
366 check_cqg(cqg, answer)
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 }
406 cqg = qcf.dimension_clustering(config, self.qgraph, name)
407 check_cqg(cqg, answer)
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 }
452 cqg = qcf.dimension_clustering(config, self.qgraph, name)
453 check_cqg(cqg, answer)
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 }
543 cqg = qcf.dimension_clustering(config, self.qgraph, name)
544 check_cqg(cqg, answer)
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 }
591 cqg = qcf.dimension_clustering(config, self.qgraph, name)
592 check_cqg(cqg, answer)
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 )
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: .*")
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 )
623 with self.assertRaises(RuntimeError):
624 _ = qcf.dimension_clustering(config, self.qgraph, "task-depends")
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 )
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
1367class TestPartitionClusterValues(unittest.TestCase):
1368 """Test partition_cluster_values function"""
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)
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)
1396 def testMaxSizeEven(self):
1397 values = list(range(1, 51))
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)
1404 def testMaxSizeUneven(self):
1405 values = list(range(1, 51))
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)
1412 def testBadMaxClusters(self):
1413 values = list(range(1, 51))
1415 config = BpsConfig({"partitionMaxClusters": 16})
1416 with self.assertRaisesRegex(RuntimeError, "same or larger than"):
1417 _ = qcf.partition_cluster_values(config, "cluster1", values, 189)
1419 def testMaxClustersEven(self):
1420 # 10 "detectors", 50 "visits" each detector, want 100 clusters/jobs
1421 values = list(range(1, 51))
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)
1431 def testMaxClustersSomewhatUneven(self):
1432 self.maxDiff = None
1434 # 10 "detectors", 50 "visits" each detector, want 90 clusters/jobs
1435 values = list(range(1, 51))
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)
1448 def testMaxClustersUneven(self):
1449 self.maxDiff = None
1451 # 10 "detectors", 50 "visits" each detector, want 85 clusters/jobs
1452 values = list(range(1, 51))
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)
1466if __name__ == "__main__":
1467 unittest.main()