lsst-ctrl-bps 29.2025.3900__tar.gz → 29.2025.4000__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lsst_ctrl_bps-29.2025.3900/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-29.2025.4000}/PKG-INFO +1 -1
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/doc/lsst.ctrl.bps/quickstart.rst +6 -7
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/bps_utils.py +1 -1
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/clustered_quantum_graph.py +48 -61
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/drivers.py +3 -3
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/etc/bps_defaults.yaml +2 -2
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/pre_transform.py +18 -8
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/quantum_clustering_funcs.py +96 -83
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/transform.py +6 -18
- lsst_ctrl_bps-29.2025.4000/python/lsst/ctrl/bps/version.py +2 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000/python/lsst_ctrl_bps.egg-info}/PKG-INFO +1 -1
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_clustered_quantum_graph.py +18 -22
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_pre_transform.py +6 -7
- lsst_ctrl_bps-29.2025.3900/python/lsst/ctrl/bps/version.py +0 -2
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/COPYRIGHT +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/LICENSE +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/MANIFEST.in +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/README.md +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/bsd_license.txt +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/doc/lsst.ctrl.bps/CHANGES.rst +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/doc/lsst.ctrl.bps/index.rst +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/gpl-v3.0.txt +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/pyproject.toml +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/_exceptions.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/bps_config.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/bps_draw.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/bps_reports.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cancel.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/bps.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/cmd/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/cmd/commands.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/opt/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/opt/arguments.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/opt/option_groups.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/opt/options.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/constants.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/construct.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/generic_workflow.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/initialize.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/ping.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/prepare.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/report.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/restart.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/status.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/submit.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/tests/config_test_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/tests/gw_test_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/wms_service.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/SOURCES.txt +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/dependency_links.txt +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/entry_points.txt +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/requires.txt +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/top_level.txt +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/zip-safe +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/setup.cfg +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_bps_reports.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_bps_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_bpsconfig.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_cli_commands.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_construct.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_drivers.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_generic_workflow.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_initialize.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_ping.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_quantum_clustering_funcs.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_report.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_status.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_transform.py +0 -0
- {lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_wms_service.py +0 -0
{lsst_ctrl_bps-29.2025.3900/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-29.2025.4000}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-ctrl-bps
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.4000
|
|
4
4
|
Summary: Pluggable execution of workflow graphs from Rubin pipelines.
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -157,7 +157,7 @@ or a pre-made file containing a serialized QuantumGraph, for example
|
|
|
157
157
|
|
|
158
158
|
.. code-block:: YAML
|
|
159
159
|
|
|
160
|
-
qgraphFile: pipelines_check_w_2020_45.
|
|
160
|
+
qgraphFile: pipelines_check_w_2020_45.qg
|
|
161
161
|
|
|
162
162
|
.. warning::
|
|
163
163
|
|
|
@@ -846,7 +846,7 @@ Supported settings
|
|
|
846
846
|
When to output job QuantumGraph files (default = TRANSFORM).
|
|
847
847
|
|
|
848
848
|
* NEVER = all jobs will use full QuantumGraph file. (Warning: make sure
|
|
849
|
-
runQuantumCommand has ``--qgraph-
|
|
849
|
+
runQuantumCommand has ``--qgraph-node-id {qgraphNodeId}``.)
|
|
850
850
|
* TRANSFORM = Output QuantumGraph files after creating GenericWorkflow.
|
|
851
851
|
* PREPARE = QuantumGraph files are output after creating WMS submission.
|
|
852
852
|
|
|
@@ -904,7 +904,7 @@ Reserved keywords
|
|
|
904
904
|
However, contrary to YAML specification, it is currently not portable.
|
|
905
905
|
|
|
906
906
|
**qgraphId**
|
|
907
|
-
|
|
907
|
+
Ignored; accepted for backwards compatibility.
|
|
908
908
|
|
|
909
909
|
**qgraphNodeId**
|
|
910
910
|
Comma-separated list of internal QuantumGraph node numbers to be
|
|
@@ -1079,13 +1079,12 @@ single full QuantumGraph file plus node numbers for each job. The default is
|
|
|
1079
1079
|
using per-job QuantumGraph files.
|
|
1080
1080
|
|
|
1081
1081
|
To use full QuantumGraph file, the submit YAML must set ``whenSaveJobQgraph`` to
|
|
1082
|
-
"NEVER" and the ``pipetask run`` command must include ``--qgraph-id {
|
|
1083
|
-
--qgraph-node-id {qgraphNodeId}``. For example:
|
|
1082
|
+
"NEVER" and the ``pipetask run`` command must include ``--qgraph-node-id {qgraphNodeId}``. For example:
|
|
1084
1083
|
|
|
1085
1084
|
.. code::
|
|
1086
1085
|
|
|
1087
1086
|
whenSaveJobQgraph: "NEVER"
|
|
1088
|
-
runQuantumCommand: "${CTRL_MPEXEC_DIR}/bin/pipetask --long-log run -b {butlerConfig} --output {output} --output-run {outputRun} --qgraph {qgraphFile} --qgraph-
|
|
1087
|
+
runQuantumCommand: "${CTRL_MPEXEC_DIR}/bin/pipetask --long-log run -b {butlerConfig} --output {output} --output-run {outputRun} --qgraph {qgraphFile} --qgraph-node-id {qgraphNodeId} --skip-init-writes --extend-run --clobber-outputs --skip-existing"
|
|
1089
1088
|
|
|
1090
1089
|
|
|
1091
1090
|
.. warning::
|
|
@@ -1244,7 +1243,7 @@ The major differences to users are:
|
|
|
1244
1243
|
the output run in the provided pre-existing quantum graph.
|
|
1245
1244
|
- ``final_post_finalJob.out``: An internal file for debugging incorrect
|
|
1246
1245
|
reporting of final run status.
|
|
1247
|
-
- ``<qgraph_filename>_orig.
|
|
1246
|
+
- ``<qgraph_filename>_orig.qg``: A backup copy of the original
|
|
1248
1247
|
pre-existing quantum graph file that was used for submitting the run. Note
|
|
1249
1248
|
that this file will *not* be present in the submit directory if the
|
|
1250
1249
|
pipeline YAML specification was used during the submission instead.
|
|
@@ -146,7 +146,7 @@ def create_job_quantum_graph_filename(config, job, out_prefix=None):
|
|
|
146
146
|
found, subdir = config.search("subDirTemplate", opt={"curvals": curvals})
|
|
147
147
|
if not found:
|
|
148
148
|
subdir = "{job.label}"
|
|
149
|
-
full_filename = Path("inputs") / subdir / f"quantum_{job.name}.
|
|
149
|
+
full_filename = Path("inputs") / subdir / f"quantum_{job.name}.qg"
|
|
150
150
|
|
|
151
151
|
if out_prefix is not None:
|
|
152
152
|
full_filename = Path(out_prefix) / full_filename
|
|
@@ -29,18 +29,21 @@
|
|
|
29
29
|
a QuantumGraph.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
from __future__ import annotations
|
|
33
33
|
|
|
34
|
+
__all__ = ["ClusteredQuantumGraph", "QuantaCluster"]
|
|
34
35
|
|
|
35
36
|
import logging
|
|
36
37
|
import pickle
|
|
37
38
|
import re
|
|
39
|
+
import uuid
|
|
38
40
|
from collections import Counter, defaultdict
|
|
39
41
|
from pathlib import Path
|
|
40
42
|
|
|
41
43
|
from networkx import DiGraph, is_directed_acyclic_graph, is_isomorphic, topological_sort
|
|
42
44
|
|
|
43
|
-
from lsst.pipe.base import
|
|
45
|
+
from lsst.pipe.base.pipeline_graph import TaskImportMode
|
|
46
|
+
from lsst.pipe.base.quantum_graph import PredictedQuantumGraph, QuantumInfo
|
|
44
47
|
from lsst.utils.iteration import ensure_iterable
|
|
45
48
|
|
|
46
49
|
from .bps_draw import draw_networkx_dot
|
|
@@ -79,13 +82,17 @@ class QuantaCluster:
|
|
|
79
82
|
self.tags = {}
|
|
80
83
|
|
|
81
84
|
@classmethod
|
|
82
|
-
def
|
|
83
|
-
|
|
85
|
+
def from_quantum_info(
|
|
86
|
+
cls, quantum_id: uuid.UUID, quantum_info: QuantumInfo, template: str
|
|
87
|
+
) -> QuantaCluster:
|
|
88
|
+
"""Create single quantum cluster from the given quantum information.
|
|
84
89
|
|
|
85
90
|
Parameters
|
|
86
91
|
----------
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
quantum_id : `uuid.UUID`
|
|
93
|
+
ID of the quantum.
|
|
94
|
+
quantum_info : `lsst.pipe.base.quantum_graph.QuantumInfo`
|
|
95
|
+
Dictionary of additional information about the quantum.
|
|
89
96
|
template : `str`
|
|
90
97
|
Template for creating cluster name.
|
|
91
98
|
|
|
@@ -94,14 +101,13 @@ class QuantaCluster:
|
|
|
94
101
|
cluster : `QuantaCluster`
|
|
95
102
|
Newly created cluster containing the given quantum.
|
|
96
103
|
"""
|
|
97
|
-
label =
|
|
98
|
-
|
|
99
|
-
data_id = quantum_node.quantum.dataId
|
|
104
|
+
label = quantum_info["task_label"]
|
|
105
|
+
data_id = quantum_info["data_id"]
|
|
100
106
|
|
|
101
107
|
# Gather info for name template into a dictionary.
|
|
102
108
|
info = dict(data_id.required)
|
|
103
109
|
info["label"] = label
|
|
104
|
-
info["node_number"] =
|
|
110
|
+
info["node_number"] = quantum_id
|
|
105
111
|
_LOG.debug("template = %s", template)
|
|
106
112
|
_LOG.debug("info for template = %s", info)
|
|
107
113
|
|
|
@@ -116,7 +122,7 @@ class QuantaCluster:
|
|
|
116
122
|
_LOG.debug("template name = %s", name)
|
|
117
123
|
|
|
118
124
|
cluster = QuantaCluster(name, label, info)
|
|
119
|
-
cluster.add_quantum(
|
|
125
|
+
cluster.add_quantum(quantum_id, label)
|
|
120
126
|
return cluster
|
|
121
127
|
|
|
122
128
|
@property
|
|
@@ -130,24 +136,12 @@ class QuantaCluster:
|
|
|
130
136
|
"""Counts of Quanta per taskDef.label in this cluster."""
|
|
131
137
|
return Counter(self._task_label_counts)
|
|
132
138
|
|
|
133
|
-
def add_quantum_node(self, quantum_node):
|
|
134
|
-
"""Add a quantumNode to this cluster.
|
|
135
|
-
|
|
136
|
-
Parameters
|
|
137
|
-
----------
|
|
138
|
-
quantum_node : `lsst.pipe.base.QuantumNode`
|
|
139
|
-
Quantum node to add.
|
|
140
|
-
"""
|
|
141
|
-
_LOG.debug("quantum_node = %s", quantum_node)
|
|
142
|
-
_LOG.debug("quantum_node.nodeId = %s", quantum_node.nodeId)
|
|
143
|
-
self.add_quantum(quantum_node.nodeId, quantum_node.taskDef.label)
|
|
144
|
-
|
|
145
139
|
def add_quantum(self, node_id, task_label):
|
|
146
140
|
"""Add a quantumNode to this cluster.
|
|
147
141
|
|
|
148
142
|
Parameters
|
|
149
143
|
----------
|
|
150
|
-
node_id : `
|
|
144
|
+
node_id : `uuid.UUID`
|
|
151
145
|
ID for quantumNode to be added to cluster.
|
|
152
146
|
task_label : `str`
|
|
153
147
|
Task label for quantumNode to be added to cluster.
|
|
@@ -185,11 +179,10 @@ class ClusteredQuantumGraph:
|
|
|
185
179
|
----------
|
|
186
180
|
name : `str`
|
|
187
181
|
Name to be given to the ClusteredQuantumGraph.
|
|
188
|
-
qgraph : `lsst.pipe.base.
|
|
189
|
-
The
|
|
182
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
183
|
+
The quantum graph to be clustered.
|
|
190
184
|
qgraph_filename : `str`
|
|
191
|
-
Filename for given
|
|
192
|
-
serialized.
|
|
185
|
+
Filename for given quantum graph.
|
|
193
186
|
|
|
194
187
|
Raises
|
|
195
188
|
------
|
|
@@ -203,11 +196,12 @@ class ClusteredQuantumGraph:
|
|
|
203
196
|
use API over totally minimized memory usage.
|
|
204
197
|
"""
|
|
205
198
|
|
|
206
|
-
def __init__(self, name, qgraph, qgraph_filename
|
|
199
|
+
def __init__(self, name: str, qgraph: PredictedQuantumGraph, qgraph_filename: str):
|
|
207
200
|
if "/" in name:
|
|
208
201
|
raise ValueError(f"name cannot have a / ({name})")
|
|
209
202
|
self._name = name
|
|
210
203
|
self._quantum_graph = qgraph
|
|
204
|
+
self._quantum_only_xgraph = qgraph.quantum_only_xgraph
|
|
211
205
|
self._quantum_graph_filename = Path(qgraph_filename).resolve()
|
|
212
206
|
self._cluster_graph = DiGraph()
|
|
213
207
|
|
|
@@ -228,22 +222,27 @@ class ClusteredQuantumGraph:
|
|
|
228
222
|
return False
|
|
229
223
|
if len(self) != len(other):
|
|
230
224
|
return False
|
|
231
|
-
return self.
|
|
225
|
+
return is_isomorphic(self.qxgraph, other.qxgraph) and is_isomorphic(
|
|
232
226
|
self._cluster_graph, other._cluster_graph
|
|
233
227
|
)
|
|
234
228
|
|
|
235
229
|
@property
|
|
236
|
-
def name(self):
|
|
230
|
+
def name(self) -> str:
|
|
237
231
|
"""The name of the ClusteredQuantumGraph."""
|
|
238
232
|
return self._name
|
|
239
233
|
|
|
240
234
|
@property
|
|
241
|
-
def qgraph(self):
|
|
242
|
-
"""The
|
|
235
|
+
def qgraph(self) -> PredictedQuantumGraph:
|
|
236
|
+
"""The quantum graph associated with this Clustered
|
|
243
237
|
QuantumGraph.
|
|
244
238
|
"""
|
|
245
239
|
return self._quantum_graph
|
|
246
240
|
|
|
241
|
+
@property
|
|
242
|
+
def qxgraph(self) -> DiGraph:
|
|
243
|
+
"""A networkx graph of all quanta."""
|
|
244
|
+
return self._quantum_only_xgraph
|
|
245
|
+
|
|
247
246
|
def add_cluster(self, clusters_for_adding):
|
|
248
247
|
"""Add a cluster of quanta as a node in the graph.
|
|
249
248
|
|
|
@@ -286,30 +285,26 @@ class ClusteredQuantumGraph:
|
|
|
286
285
|
raise KeyError(f"{self.name} does not have a cluster named {name}") from ex
|
|
287
286
|
return attr["cluster"]
|
|
288
287
|
|
|
289
|
-
def
|
|
290
|
-
"""Retrieve a
|
|
288
|
+
def get_quantum_info(self, id_: uuid.UUID) -> QuantumInfo:
|
|
289
|
+
"""Retrieve a quantum info dict from the ClusteredQuantumGraph by ID.
|
|
291
290
|
|
|
292
291
|
Parameters
|
|
293
292
|
----------
|
|
294
|
-
id_ : `
|
|
295
|
-
ID of the
|
|
293
|
+
id_ : `uuid.UUID`
|
|
294
|
+
ID of the quantum to retrieve.
|
|
296
295
|
|
|
297
296
|
Returns
|
|
298
297
|
-------
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
quantum_info : `lsst.pipe.base.quantum_graph.QuantumInfo`
|
|
299
|
+
Quantum info dictionary for the given ID.
|
|
301
300
|
|
|
302
301
|
Raises
|
|
303
302
|
------
|
|
304
303
|
KeyError
|
|
305
304
|
Raised if the ClusteredQuantumGraph does not contain
|
|
306
|
-
a
|
|
305
|
+
a quantum with given ID.
|
|
307
306
|
"""
|
|
308
|
-
|
|
309
|
-
if isinstance(id_, int):
|
|
310
|
-
node_id = NodeId(id, self._quantum_graph.graphID)
|
|
311
|
-
_LOG.debug("get_quantum_node: node_id = %s", node_id)
|
|
312
|
-
return self._quantum_graph.getQuantumNodeByNodeId(node_id)
|
|
307
|
+
return self._quantum_only_xgraph.nodes[id_]
|
|
313
308
|
|
|
314
309
|
def __iter__(self):
|
|
315
310
|
"""Iterate over names of clusters.
|
|
@@ -414,8 +409,8 @@ class ClusteredQuantumGraph:
|
|
|
414
409
|
|
|
415
410
|
def save(self, filename, format_=None):
|
|
416
411
|
"""Save the ClusteredQuantumGraph in a format that is loadable.
|
|
417
|
-
|
|
418
|
-
|
|
412
|
+
|
|
413
|
+
The quantum graph is assumed to have been saved separately.
|
|
419
414
|
|
|
420
415
|
Parameters
|
|
421
416
|
----------
|
|
@@ -433,14 +428,6 @@ class ClusteredQuantumGraph:
|
|
|
433
428
|
if format_ not in {"pickle"}:
|
|
434
429
|
raise RuntimeError(f"Unknown format ({format_})")
|
|
435
430
|
|
|
436
|
-
if not self._quantum_graph_filename:
|
|
437
|
-
# Create filename based on given ClusteredQuantumGraph filename
|
|
438
|
-
self._quantum_graph_filename = path.with_suffix(".qgraph")
|
|
439
|
-
|
|
440
|
-
# If QuantumGraph file doesn't already exist, save it:
|
|
441
|
-
if not Path(self._quantum_graph_filename).exists():
|
|
442
|
-
self._quantum_graph.saveUri(self._quantum_graph_filename)
|
|
443
|
-
|
|
444
431
|
if format_ == "pickle":
|
|
445
432
|
# Don't save QuantumGraph in same file.
|
|
446
433
|
tmp_qgraph = self._quantum_graph
|
|
@@ -503,14 +490,14 @@ class ClusteredQuantumGraph:
|
|
|
503
490
|
cgraph = None
|
|
504
491
|
if format_ == "pickle":
|
|
505
492
|
with open(filename, "rb") as fh:
|
|
506
|
-
cgraph = pickle.load(fh)
|
|
493
|
+
cgraph: ClusteredQuantumGraph = pickle.load(fh)
|
|
507
494
|
|
|
508
495
|
# The QuantumGraph was saved separately
|
|
509
|
-
|
|
510
|
-
cgraph.
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
cgraph._quantum_graph =
|
|
496
|
+
with PredictedQuantumGraph.open(
|
|
497
|
+
cgraph._quantum_graph_filename, import_mode=TaskImportMode.DO_NOT_IMPORT
|
|
498
|
+
) as reader:
|
|
499
|
+
reader.read_thin_graph()
|
|
500
|
+
cgraph._quantum_graph = reader.finish()
|
|
514
501
|
|
|
515
502
|
return cgraph
|
|
516
503
|
|
|
@@ -50,7 +50,7 @@ import logging
|
|
|
50
50
|
import os
|
|
51
51
|
from pathlib import Path
|
|
52
52
|
|
|
53
|
-
from lsst.pipe.base import
|
|
53
|
+
from lsst.pipe.base.quantum_graph import PredictedQuantumGraph
|
|
54
54
|
from lsst.utils.timer import time_this
|
|
55
55
|
from lsst.utils.usage import get_peak_mem_usage
|
|
56
56
|
|
|
@@ -111,7 +111,7 @@ def _init_submission_driver(config_file: str, **kwargs) -> BpsConfig:
|
|
|
111
111
|
return config
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def acquire_qgraph_driver(config_file: str, **kwargs) -> tuple[BpsConfig,
|
|
114
|
+
def acquire_qgraph_driver(config_file: str, **kwargs) -> tuple[BpsConfig, PredictedQuantumGraph]:
|
|
115
115
|
"""Read a quantum graph from a file or create one from pipeline definition.
|
|
116
116
|
|
|
117
117
|
Parameters
|
|
@@ -125,7 +125,7 @@ def acquire_qgraph_driver(config_file: str, **kwargs) -> tuple[BpsConfig, Quantu
|
|
|
125
125
|
-------
|
|
126
126
|
config : `lsst.ctrl.bps.BpsConfig`
|
|
127
127
|
Updated configuration.
|
|
128
|
-
qgraph : `lsst.pipe.base.
|
|
128
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
129
129
|
A graph representing quanta.
|
|
130
130
|
"""
|
|
131
131
|
config = _init_submission_driver(config_file, **kwargs)
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/etc/bps_defaults.yaml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#USER pipelineYaml: "${OBS_SUBARU_DIR}/pipelines/DRP.yaml#processCcd"
|
|
2
2
|
# OR
|
|
3
|
-
#USER qgraphFile: "/path/to/existing/file.
|
|
3
|
+
#USER qgraphFile: "/path/to/existing/file.qg"
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# At minimum, following group used in bps report and can be
|
|
@@ -94,7 +94,7 @@ clusterAlgorithm: lsst.ctrl.bps.quantum_clustering_funcs.single_quantum_clusteri
|
|
|
94
94
|
|
|
95
95
|
# Templates for bps filenames
|
|
96
96
|
submitPath: ${PWD}/submit/{outputRun}
|
|
97
|
-
qgraphFileTemplate: "{uniqProcName}.
|
|
97
|
+
qgraphFileTemplate: "{uniqProcName}.qg"
|
|
98
98
|
subDirTemplate: "{label}/{tract}/{patch}/{band}/{subfilter}/{physical_filter}/{visit}/{exposure}"
|
|
99
99
|
templateDataId: "{tract}_{patch}_{band}_{visit}_{exposure}_{detector}"
|
|
100
100
|
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/pre_transform.py
RENAMED
|
@@ -38,7 +38,10 @@ import subprocess
|
|
|
38
38
|
from pathlib import Path
|
|
39
39
|
|
|
40
40
|
from lsst.ctrl.bps import BpsConfig, BpsSubprocessError
|
|
41
|
-
from lsst.pipe.base
|
|
41
|
+
from lsst.pipe.base import QuantumGraph
|
|
42
|
+
from lsst.pipe.base.pipeline_graph import TaskImportMode
|
|
43
|
+
from lsst.pipe.base.quantum_graph import PredictedQuantumGraph
|
|
44
|
+
from lsst.resources import ResourcePath
|
|
42
45
|
from lsst.utils import doImport
|
|
43
46
|
from lsst.utils.logging import VERBOSE
|
|
44
47
|
from lsst.utils.timer import time_this, timeMethod
|
|
@@ -47,7 +50,7 @@ _LOG = logging.getLogger(__name__)
|
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
@timeMethod(logger=_LOG, logLevel=VERBOSE)
|
|
50
|
-
def acquire_quantum_graph(config: BpsConfig, out_prefix: str = "") -> tuple[str,
|
|
53
|
+
def acquire_quantum_graph(config: BpsConfig, out_prefix: str = "") -> tuple[str, PredictedQuantumGraph]:
|
|
51
54
|
"""Read a quantum graph from a file or create one from scratch.
|
|
52
55
|
|
|
53
56
|
Parameters
|
|
@@ -62,8 +65,8 @@ def acquire_quantum_graph(config: BpsConfig, out_prefix: str = "") -> tuple[str,
|
|
|
62
65
|
-------
|
|
63
66
|
qgraph_filename : `str`
|
|
64
67
|
Name of file containing QuantumGraph that was read into qgraph.
|
|
65
|
-
qgraph : `lsst.pipe.base.
|
|
66
|
-
A
|
|
68
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
69
|
+
A quantum graph read in from pre-generated file or one that is the
|
|
67
70
|
result of running code that generates it.
|
|
68
71
|
"""
|
|
69
72
|
# Check to see if user provided pre-generated QuantumGraph.
|
|
@@ -90,8 +93,15 @@ def acquire_quantum_graph(config: BpsConfig, out_prefix: str = "") -> tuple[str,
|
|
|
90
93
|
|
|
91
94
|
_LOG.info("Reading quantum graph from '%s'", qgraph_filename)
|
|
92
95
|
with time_this(log=_LOG, level=logging.INFO, prefix=None, msg="Completed reading quantum graph"):
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
qgraph_path = ResourcePath(qgraph_filename)
|
|
97
|
+
if qgraph_path.getExtension() == ".qg":
|
|
98
|
+
with PredictedQuantumGraph.open(qgraph_path, import_mode=TaskImportMode.DO_NOT_IMPORT) as reader:
|
|
99
|
+
reader.read_thin_graph()
|
|
100
|
+
qgraph = reader.finish()
|
|
101
|
+
elif qgraph_path.getExtension() == ".qgraph":
|
|
102
|
+
qgraph = PredictedQuantumGraph.from_old_quantum_graph(QuantumGraph.loadUri(qgraph_path))
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError(f"Unrecognized extension for quantum graph file: {qgraph_filename}.")
|
|
95
105
|
return qgraph_filename, qgraph
|
|
96
106
|
|
|
97
107
|
|
|
@@ -244,8 +254,8 @@ def cluster_quanta(config, qgraph, name):
|
|
|
244
254
|
----------
|
|
245
255
|
config : `lsst.ctrl.bps.BpsConfig`
|
|
246
256
|
BPS configuration.
|
|
247
|
-
qgraph : `lsst.pipe.base.
|
|
248
|
-
Original full
|
|
257
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
258
|
+
Original full quantum graph for the run.
|
|
249
259
|
name : `str`
|
|
250
260
|
Name for the ClusteredQuantumGraph that will be generated.
|
|
251
261
|
|
|
@@ -31,28 +31,31 @@ __all__ = ["check_clustering_config"]
|
|
|
31
31
|
|
|
32
32
|
import logging
|
|
33
33
|
import re
|
|
34
|
+
import uuid
|
|
34
35
|
from collections import defaultdict
|
|
35
36
|
from typing import Any
|
|
36
37
|
from uuid import UUID
|
|
37
38
|
|
|
38
39
|
from networkx import DiGraph, NetworkXNoCycle, find_cycle, topological_sort
|
|
39
40
|
|
|
40
|
-
from lsst.pipe.base import
|
|
41
|
+
from lsst.pipe.base.quantum_graph import PredictedQuantumGraph, QuantumInfo
|
|
41
42
|
|
|
42
43
|
from . import BpsConfig, ClusteredQuantumGraph, QuantaCluster
|
|
43
44
|
|
|
44
45
|
_LOG = logging.getLogger(__name__)
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
def single_quantum_clustering(
|
|
48
|
+
def single_quantum_clustering(
|
|
49
|
+
config: BpsConfig, qgraph: PredictedQuantumGraph, name: str
|
|
50
|
+
) -> ClusteredQuantumGraph:
|
|
48
51
|
"""Create clusters with only single quantum.
|
|
49
52
|
|
|
50
53
|
Parameters
|
|
51
54
|
----------
|
|
52
55
|
config : `lsst.ctrl.bps.BpsConfig`
|
|
53
56
|
BPS configuration.
|
|
54
|
-
qgraph : `lsst.pipe.base.
|
|
55
|
-
|
|
57
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
58
|
+
Quantum graph to break into clusters for ClusteredQuantumGraph.
|
|
56
59
|
name : `str`
|
|
57
60
|
Name to give to ClusteredQuantumGraph.
|
|
58
61
|
|
|
@@ -76,18 +79,19 @@ def single_quantum_clustering(config: BpsConfig, qgraph: QuantumGraph, name: str
|
|
|
76
79
|
cached_template = {}
|
|
77
80
|
|
|
78
81
|
# Create cluster of single quantum.
|
|
79
|
-
for
|
|
80
|
-
|
|
82
|
+
for quantum_id, quantum_info in cqgraph.qxgraph.nodes.items():
|
|
83
|
+
task_label = quantum_info["task_label"]
|
|
84
|
+
if task_label not in cached_template:
|
|
81
85
|
found, template_data_id = config.search(
|
|
82
86
|
"templateDataId",
|
|
83
|
-
opt={"curvals": {"curr_pipetask":
|
|
87
|
+
opt={"curvals": {"curr_pipetask": task_label}, "replaceVars": False},
|
|
84
88
|
)
|
|
85
89
|
if found:
|
|
86
90
|
template = "{label}_" + template_data_id
|
|
87
91
|
_, use_node_number = config.search(
|
|
88
92
|
"useNodeIdInClusterName",
|
|
89
93
|
opt={
|
|
90
|
-
"curvals": {"curr_pipetask":
|
|
94
|
+
"curvals": {"curr_pipetask": task_label},
|
|
91
95
|
"replaceVars": False,
|
|
92
96
|
"default": True,
|
|
93
97
|
},
|
|
@@ -96,21 +100,21 @@ def single_quantum_clustering(config: BpsConfig, qgraph: QuantumGraph, name: str
|
|
|
96
100
|
template = "{node_number}_" + template
|
|
97
101
|
else:
|
|
98
102
|
template = "{node_number}"
|
|
99
|
-
cached_template[
|
|
103
|
+
cached_template[task_label] = template
|
|
100
104
|
|
|
101
|
-
cluster = QuantaCluster.
|
|
105
|
+
cluster = QuantaCluster.from_quantum_info(quantum_id, quantum_info, cached_template[task_label])
|
|
102
106
|
|
|
103
107
|
# Save mapping for use when creating dependencies.
|
|
104
|
-
number_to_name[
|
|
108
|
+
number_to_name[quantum_id] = cluster.name
|
|
105
109
|
|
|
106
110
|
cqgraph.add_cluster(cluster)
|
|
107
111
|
|
|
108
112
|
# Add cluster dependencies.
|
|
109
|
-
for
|
|
113
|
+
for quantum_id in cqgraph.qxgraph:
|
|
110
114
|
# Get child nodes.
|
|
111
|
-
children =
|
|
115
|
+
children = cqgraph.qxgraph.successors(quantum_id)
|
|
112
116
|
for child in children:
|
|
113
|
-
cqgraph.add_dependency(number_to_name[
|
|
117
|
+
cqgraph.add_dependency(number_to_name[quantum_id], number_to_name[child])
|
|
114
118
|
|
|
115
119
|
return cqgraph
|
|
116
120
|
|
|
@@ -209,15 +213,17 @@ def check_clustering_config(
|
|
|
209
213
|
return list(topological_sort(clustered_task_graph)), ordered_tasks
|
|
210
214
|
|
|
211
215
|
|
|
212
|
-
def dimension_clustering(
|
|
216
|
+
def dimension_clustering(
|
|
217
|
+
config: BpsConfig, qgraph: PredictedQuantumGraph, name: str
|
|
218
|
+
) -> ClusteredQuantumGraph:
|
|
213
219
|
"""Follow config instructions to make clusters based upon dimensions.
|
|
214
220
|
|
|
215
221
|
Parameters
|
|
216
222
|
----------
|
|
217
223
|
config : `lsst.ctrl.bps.BpsConfig`
|
|
218
224
|
BPS configuration.
|
|
219
|
-
qgraph : `lsst.pipe.base.
|
|
220
|
-
|
|
225
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
226
|
+
Quantum graph to break into clusters for ClusteredQuantumGraph.
|
|
221
227
|
name : `str`
|
|
222
228
|
Name to give to ClusteredQuantumGraph.
|
|
223
229
|
|
|
@@ -264,7 +270,7 @@ def dimension_clustering(config: BpsConfig, qgraph: QuantumGraph, name: str) ->
|
|
|
264
270
|
def add_clusters_per_quantum(
|
|
265
271
|
config: BpsConfig,
|
|
266
272
|
label: str,
|
|
267
|
-
qgraph:
|
|
273
|
+
qgraph: PredictedQuantumGraph,
|
|
268
274
|
cqgraph: ClusteredQuantumGraph,
|
|
269
275
|
quantum_to_cluster: dict[UUID, str],
|
|
270
276
|
) -> None:
|
|
@@ -276,8 +282,8 @@ def add_clusters_per_quantum(
|
|
|
276
282
|
BPS configuration.
|
|
277
283
|
label : `str`
|
|
278
284
|
The taskDef label for which to add clusters.
|
|
279
|
-
qgraph : `lsst.pipe.base.
|
|
280
|
-
|
|
285
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
286
|
+
Quantum graph providing quanta for the clusters.
|
|
281
287
|
cqgraph : `lsst.ctrl.bps.ClusteredQuantumGraph`
|
|
282
288
|
The ClusteredQuantumGraph to which the new 1-quantum
|
|
283
289
|
clusters are added (modified in method).
|
|
@@ -304,16 +310,10 @@ def add_clusters_per_quantum(
|
|
|
304
310
|
else:
|
|
305
311
|
template = "{node_number}"
|
|
306
312
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
task_def = qgraph.findTaskDefByLabel(label)
|
|
310
|
-
assert task_def is not None, f"Given taskDef label ({label}) not found in QuantumGraph" # for mypy
|
|
311
|
-
quantum_nodes = qgraph.getNodesForTask(task_def)
|
|
312
|
-
|
|
313
|
-
for qnode in quantum_nodes:
|
|
314
|
-
cluster = QuantaCluster.from_quantum_node(qnode, template)
|
|
313
|
+
for quantum_id in qgraph.quanta_by_task[label].values():
|
|
314
|
+
cluster = QuantaCluster.from_quantum_info(quantum_id, cqgraph.qxgraph.nodes[quantum_id], template)
|
|
315
315
|
cqgraph.add_cluster(cluster)
|
|
316
|
-
quantum_to_cluster[
|
|
316
|
+
quantum_to_cluster[quantum_id] = cluster.name
|
|
317
317
|
add_cluster_dependencies(cqgraph, cluster, quantum_to_cluster)
|
|
318
318
|
|
|
319
319
|
|
|
@@ -467,7 +467,7 @@ def partition_cluster_values(
|
|
|
467
467
|
def add_dim_clusters(
|
|
468
468
|
cluster_config: BpsConfig,
|
|
469
469
|
cluster_label: str,
|
|
470
|
-
qgraph:
|
|
470
|
+
qgraph: PredictedQuantumGraph,
|
|
471
471
|
ordered_tasks: dict[str, DiGraph],
|
|
472
472
|
cqgraph: ClusteredQuantumGraph,
|
|
473
473
|
quantum_to_cluster: dict[UUID, str],
|
|
@@ -505,14 +505,15 @@ def add_dim_clusters(
|
|
|
505
505
|
partition_values: set[str] = set()
|
|
506
506
|
quanta_info: dict[str, list[dict[str, Any]]] = {}
|
|
507
507
|
for task_label in topological_sort(ordered_tasks[cluster_label]):
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
508
|
+
for quantum_id in qgraph.quanta_by_task[task_label].values():
|
|
509
|
+
cluster_name, info = get_cluster_name_from_info(
|
|
510
|
+
quantum_id,
|
|
511
|
+
cqgraph.qxgraph.nodes[quantum_id],
|
|
512
|
+
cluster_dims,
|
|
513
|
+
cluster_label,
|
|
514
|
+
template,
|
|
515
|
+
equal_dims,
|
|
516
|
+
partition_dims,
|
|
516
517
|
)
|
|
517
518
|
if "partition_key" in info:
|
|
518
519
|
partition_values.add(info["partition_key"])
|
|
@@ -551,23 +552,22 @@ def add_cluster_dependencies(
|
|
|
551
552
|
from quantum_to_cluster or if their parent quantum node ids
|
|
552
553
|
are missing from quantum_to_cluster.
|
|
553
554
|
"""
|
|
554
|
-
qgraph = cqgraph.qgraph
|
|
555
555
|
for node_id in cluster.qgraph_node_ids:
|
|
556
|
-
|
|
557
|
-
parents =
|
|
558
|
-
for
|
|
556
|
+
cluster_node_info = cqgraph.get_quantum_info(node_id)
|
|
557
|
+
parents = cqgraph.qxgraph.predecessors(node_id)
|
|
558
|
+
for parent_id in parents:
|
|
559
559
|
try:
|
|
560
|
-
if quantum_to_cluster[
|
|
561
|
-
cqgraph.add_dependency(quantum_to_cluster[
|
|
560
|
+
if quantum_to_cluster[parent_id] != quantum_to_cluster[node_id]:
|
|
561
|
+
cqgraph.add_dependency(quantum_to_cluster[parent_id], quantum_to_cluster[node_id])
|
|
562
562
|
except KeyError as e: # pragma: no cover
|
|
563
563
|
# For debugging a problem internal to method
|
|
564
|
-
|
|
564
|
+
qnode_info = cqgraph.get_quantum_info(e.args[0])
|
|
565
565
|
_LOG.error(
|
|
566
566
|
"Quanta missing when clustering: cluster node = %s, %s; missing = %s, %s",
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
567
|
+
cluster_node_info["task_label"],
|
|
568
|
+
cluster_node_info["data_id"],
|
|
569
|
+
qnode_info["task_label"],
|
|
570
|
+
qnode_info["data_id"],
|
|
571
571
|
)
|
|
572
572
|
_LOG.error(quantum_to_cluster)
|
|
573
573
|
raise
|
|
@@ -576,13 +576,13 @@ def add_cluster_dependencies(
|
|
|
576
576
|
def add_dim_clusters_dependency(
|
|
577
577
|
cluster_config: BpsConfig,
|
|
578
578
|
cluster_label: str,
|
|
579
|
-
qgraph:
|
|
579
|
+
qgraph: PredictedQuantumGraph,
|
|
580
580
|
ordered_tasks: dict[str, DiGraph],
|
|
581
581
|
cqgraph: ClusteredQuantumGraph,
|
|
582
582
|
quantum_to_cluster: dict[UUID, str],
|
|
583
583
|
) -> None:
|
|
584
584
|
"""Add clusters for a cluster label to a ClusteredQuantumGraph using
|
|
585
|
-
|
|
585
|
+
quantum graph dependencies as well as dimension values to help when
|
|
586
586
|
some do not have particular dimension value.
|
|
587
587
|
|
|
588
588
|
Parameters
|
|
@@ -591,8 +591,8 @@ def add_dim_clusters_dependency(
|
|
|
591
591
|
BPS configuration for specific cluster label.
|
|
592
592
|
cluster_label : `str`
|
|
593
593
|
Cluster label for which to add clusters.
|
|
594
|
-
qgraph : `lsst.pipe.base.
|
|
595
|
-
|
|
594
|
+
qgraph : `lsst.pipe.base.quantum_graph.PredictedQuantumGraph`
|
|
595
|
+
Quantum graph providing quanta for the clusters.
|
|
596
596
|
ordered_tasks : `dict` [`str`, `networkx.DiGraph`]
|
|
597
597
|
Mapping of cluster label to task label subgraph.
|
|
598
598
|
cqgraph : `lsst.ctrl.bps.ClusteredQuantumGraph`
|
|
@@ -618,9 +618,9 @@ def add_dim_clusters_dependency(
|
|
|
618
618
|
method = cluster_config["findDependencyMethod"]
|
|
619
619
|
match method:
|
|
620
620
|
case "source":
|
|
621
|
-
find_possible_nodes =
|
|
621
|
+
find_possible_nodes = cqgraph.qxgraph.successors
|
|
622
622
|
case "sink":
|
|
623
|
-
find_possible_nodes =
|
|
623
|
+
find_possible_nodes = cqgraph.qxgraph.predecessors
|
|
624
624
|
label_search_order.reverse()
|
|
625
625
|
case _:
|
|
626
626
|
raise RuntimeError(f"Invalid findDependencyMethod ({method})")
|
|
@@ -634,49 +634,60 @@ def add_dim_clusters_dependency(
|
|
|
634
634
|
# quicker to check
|
|
635
635
|
quanta_visited = set()
|
|
636
636
|
for task_label in label_search_order:
|
|
637
|
-
|
|
638
|
-
assert task_def is not None # for mypy
|
|
639
|
-
for node in qgraph.getNodesForTask(task_def):
|
|
637
|
+
for quantum_id in qgraph.quanta_by_task[task_label].values():
|
|
640
638
|
# skip if visited before
|
|
641
|
-
if
|
|
639
|
+
if quantum_id in quanta_visited:
|
|
642
640
|
continue
|
|
643
641
|
|
|
644
|
-
cluster_name, info =
|
|
645
|
-
|
|
642
|
+
cluster_name, info = get_cluster_name_from_info(
|
|
643
|
+
quantum_id,
|
|
644
|
+
cqgraph.qxgraph.nodes[quantum_id],
|
|
645
|
+
cluster_dims,
|
|
646
|
+
cluster_label,
|
|
647
|
+
template,
|
|
648
|
+
equal_dims,
|
|
649
|
+
partition_dims,
|
|
646
650
|
)
|
|
647
651
|
if "partition_key" in info:
|
|
648
652
|
partition_values.add(info["partition_key"])
|
|
649
653
|
quanta_info.setdefault(cluster_name, []).append(info)
|
|
650
|
-
quanta_visited.add(
|
|
654
|
+
quanta_visited.add(quantum_id)
|
|
651
655
|
|
|
652
656
|
# Use dependencies to find other quantum to add
|
|
653
657
|
# Note: in testing, using the following code was faster than
|
|
654
658
|
# using networkx descendants and ancestors functions
|
|
655
659
|
# While traversing the QuantumGraph, nodes may appear
|
|
656
660
|
# repeatedly in possible_nodes.
|
|
657
|
-
nodes_to_use = [
|
|
661
|
+
nodes_to_use = [quantum_id]
|
|
658
662
|
while nodes_to_use:
|
|
659
663
|
node_to_use = nodes_to_use.pop()
|
|
660
|
-
|
|
661
|
-
for
|
|
664
|
+
possible_node_ids = find_possible_nodes(node_to_use)
|
|
665
|
+
for possible_node_id in possible_node_ids:
|
|
662
666
|
# skip if visited before
|
|
663
|
-
if
|
|
667
|
+
if possible_node_id in quanta_visited:
|
|
664
668
|
continue
|
|
665
|
-
quanta_visited.add(
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
669
|
+
quanta_visited.add(possible_node_id)
|
|
670
|
+
|
|
671
|
+
possible_node_info = cqgraph.qxgraph.nodes[possible_node_id]
|
|
672
|
+
if possible_node_info["task_label"] in ordered_tasks[cluster_label]:
|
|
673
|
+
cluster_name, info = get_cluster_name_from_info(
|
|
674
|
+
possible_node_id,
|
|
675
|
+
possible_node_info,
|
|
676
|
+
cluster_dims,
|
|
677
|
+
cluster_label,
|
|
678
|
+
template,
|
|
679
|
+
equal_dims,
|
|
680
|
+
partition_dims,
|
|
670
681
|
)
|
|
671
682
|
if "partition_key" in info:
|
|
672
683
|
partition_values.add(info["partition_key"])
|
|
673
684
|
quanta_info.setdefault(cluster_name, []).append(info)
|
|
674
|
-
nodes_to_use.append(
|
|
685
|
+
nodes_to_use.append(possible_node_id)
|
|
675
686
|
else:
|
|
676
687
|
_LOG.debug(
|
|
677
688
|
"label (%s) not in ordered_tasks. Not adding possible quantum %s",
|
|
678
|
-
|
|
679
|
-
|
|
689
|
+
possible_node_info["task_label"],
|
|
690
|
+
possible_node_id,
|
|
680
691
|
)
|
|
681
692
|
|
|
682
693
|
make_and_add_clusters(
|
|
@@ -746,8 +757,9 @@ def make_and_add_clusters(
|
|
|
746
757
|
add_cluster_dependencies(cqgraph, cluster, quantum_to_cluster)
|
|
747
758
|
|
|
748
759
|
|
|
749
|
-
def
|
|
750
|
-
|
|
760
|
+
def get_cluster_name_from_info(
|
|
761
|
+
quantum_id: uuid.UUID,
|
|
762
|
+
quantum_info: QuantumInfo,
|
|
751
763
|
cluster_dims: list[str],
|
|
752
764
|
cluster_label: str,
|
|
753
765
|
template: str,
|
|
@@ -758,8 +770,10 @@ def get_cluster_name_from_node(
|
|
|
758
770
|
|
|
759
771
|
Parameters
|
|
760
772
|
----------
|
|
761
|
-
|
|
762
|
-
|
|
773
|
+
quantum_id : `uuid.UUID`
|
|
774
|
+
Unique ID for the quantum.
|
|
775
|
+
quantum_info : `lsst.pipe.base.quantum_graph.QuantumInfo`
|
|
776
|
+
Info dictionary from which to create the cluster.
|
|
763
777
|
cluster_dims : `list` [`str`]
|
|
764
778
|
Dimension names to be used when clustering.
|
|
765
779
|
cluster_label : `str`
|
|
@@ -780,8 +794,8 @@ def get_cluster_name_from_node(
|
|
|
780
794
|
"""
|
|
781
795
|
# Gather info for cluster name template into a dictionary.
|
|
782
796
|
info: dict[str, Any] = {
|
|
783
|
-
"node_number":
|
|
784
|
-
"node_label":
|
|
797
|
+
"node_number": quantum_id,
|
|
798
|
+
"node_label": quantum_info["task_label"],
|
|
785
799
|
"label": cluster_label,
|
|
786
800
|
}
|
|
787
801
|
|
|
@@ -789,8 +803,7 @@ def get_cluster_name_from_node(
|
|
|
789
803
|
all_dims = cluster_dims + partition_dims
|
|
790
804
|
|
|
791
805
|
missing_info = set()
|
|
792
|
-
|
|
793
|
-
data_id_info = dict(node.quantum.dataId.mapping)
|
|
806
|
+
data_id_info = dict(quantum_info["data_id"].mapping)
|
|
794
807
|
for dim_name in all_dims:
|
|
795
808
|
_LOG.debug("dim_name = %s", dim_name)
|
|
796
809
|
if dim_name in data_id_info:
|
|
@@ -807,7 +820,7 @@ def get_cluster_name_from_node(
|
|
|
807
820
|
|
|
808
821
|
if missing_info:
|
|
809
822
|
raise RuntimeError(
|
|
810
|
-
f"Quantum {
|
|
823
|
+
f"Quantum {quantum_id} ({data_id_info}) missing dimensions: {','.join(missing_info)}; "
|
|
811
824
|
f"required for cluster {cluster_label}"
|
|
812
825
|
)
|
|
813
826
|
|
|
@@ -190,16 +190,6 @@ def create_init_workflow(
|
|
|
190
190
|
# Adjust job attributes values if necessary.
|
|
191
191
|
_handle_job_values(job_values, gwjob)
|
|
192
192
|
|
|
193
|
-
# Pick a node id for each task (not quantum!) to avoid reading the entire
|
|
194
|
-
# quantum graph during the initialization stage.
|
|
195
|
-
node_ids = []
|
|
196
|
-
for task_label in qgraph.pipeline_graph.tasks:
|
|
197
|
-
task_def = qgraph.findTaskDefByLabel(task_label)
|
|
198
|
-
node = next(iter(qgraph.getNodesForTask(task_def)))
|
|
199
|
-
node_ids.append(node.nodeId)
|
|
200
|
-
gwjob.cmdvals["qgraphId"] = qgraph.graphID
|
|
201
|
-
gwjob.cmdvals["qgraphNodeId"] = ",".join(sorted([f"{node_id}" for node_id in node_ids]))
|
|
202
|
-
|
|
203
193
|
init_workflow.add_job(gwjob)
|
|
204
194
|
init_workflow.add_job_inputs(gwjob.name, [qgraph_gwfile])
|
|
205
195
|
_enhance_command(config, init_workflow, gwjob, {})
|
|
@@ -682,14 +672,13 @@ def create_generic_workflow(
|
|
|
682
672
|
# either common or aggregate for all Quanta in cluster.
|
|
683
673
|
for node_id in iter(cluster.qgraph_node_ids):
|
|
684
674
|
_LOG.debug("node_id=%s", node_id)
|
|
685
|
-
|
|
675
|
+
quantum_info = cqgraph.get_quantum_info(node_id)
|
|
686
676
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
_handle_job_values(cached_pipetask_values[qnode.taskDef.label], gwjob, unset_attributes)
|
|
677
|
+
task_label = quantum_info["task_label"]
|
|
678
|
+
if task_label not in cached_pipetask_values:
|
|
679
|
+
search_opt["curvals"]["curr_pipetask"] = task_label
|
|
680
|
+
cached_pipetask_values[task_label] = _get_job_values(config, search_opt, "runQuantumCommand")
|
|
681
|
+
_handle_job_values(cached_pipetask_values[task_label], gwjob, unset_attributes)
|
|
693
682
|
|
|
694
683
|
# Update job with workflow attribute and profile values.
|
|
695
684
|
qgraph_gwfile = _get_qgraph_gwfile(
|
|
@@ -699,7 +688,6 @@ def create_generic_workflow(
|
|
|
699
688
|
generic_workflow.add_job(gwjob)
|
|
700
689
|
generic_workflow.add_job_inputs(gwjob.name, [qgraph_gwfile])
|
|
701
690
|
|
|
702
|
-
gwjob.cmdvals["qgraphId"] = cqgraph.qgraph.graphID
|
|
703
691
|
gwjob.cmdvals["qgraphNodeId"] = ",".join(
|
|
704
692
|
sorted([f"{node_id}" for node_id in cluster.qgraph_node_ids])
|
|
705
693
|
)
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000/python/lsst_ctrl_bps.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-ctrl-bps
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.4000
|
|
4
4
|
Summary: Pluggable execution of workflow graphs from Rubin pipelines.
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: BSD 3-Clause License
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_clustered_quantum_graph.py
RENAMED
|
@@ -50,54 +50,49 @@ class TestQuantaCluster(unittest.TestCase):
|
|
|
50
50
|
|
|
51
51
|
def setUp(self):
|
|
52
52
|
self.qgraph = make_test_quantum_graph()
|
|
53
|
-
|
|
54
|
-
self.
|
|
55
|
-
self.
|
|
53
|
+
self.id1, self.id2, *_ = self.qgraph.quanta_by_task["T1"].values()
|
|
54
|
+
self.info1 = self.qgraph.quantum_only_xgraph.nodes[self.id1]
|
|
55
|
+
self.info2 = self.qgraph.quantum_only_xgraph.nodes[self.id2]
|
|
56
56
|
|
|
57
57
|
def tearDown(self):
|
|
58
58
|
pass
|
|
59
59
|
|
|
60
60
|
def testQgraphNodeIds(self):
|
|
61
|
-
qc = QuantaCluster.
|
|
62
|
-
self.assertEqual(qc.qgraph_node_ids, frozenset([self.
|
|
61
|
+
qc = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
62
|
+
self.assertEqual(qc.qgraph_node_ids, frozenset([self.id1]))
|
|
63
63
|
|
|
64
64
|
def testQuantaCountsNone(self):
|
|
65
65
|
qc = QuantaCluster("NoQuanta", "the_label")
|
|
66
66
|
self.assertEqual(qc.quanta_counts, Counter())
|
|
67
67
|
|
|
68
68
|
def testQuantaCounts(self):
|
|
69
|
-
qc = QuantaCluster.
|
|
69
|
+
qc = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
70
70
|
self.assertEqual(qc.quanta_counts, Counter({"T1": 1}))
|
|
71
71
|
|
|
72
|
-
def testAddQuantumNode(self):
|
|
73
|
-
qc = QuantaCluster.from_quantum_node(self.qnode1, "{node_number}")
|
|
74
|
-
qc.add_quantum_node(self.qnode2)
|
|
75
|
-
self.assertEqual(qc.quanta_counts, Counter({"T1": 2}))
|
|
76
|
-
|
|
77
72
|
def testAddQuantum(self):
|
|
78
|
-
qc = QuantaCluster.
|
|
79
|
-
qc.add_quantum(self.
|
|
73
|
+
qc = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
74
|
+
qc.add_quantum(self.id2, self.info2["task_label"])
|
|
80
75
|
self.assertEqual(qc.quanta_counts, Counter({"T1": 2}))
|
|
81
76
|
|
|
82
77
|
def testStr(self):
|
|
83
|
-
qc = QuantaCluster.
|
|
78
|
+
qc = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
84
79
|
self.assertIn(qc.name, str(qc))
|
|
85
80
|
self.assertIn("T1", str(qc))
|
|
86
81
|
self.assertIn("tags", str(qc))
|
|
87
82
|
|
|
88
83
|
def testEqual(self):
|
|
89
|
-
qc1 = QuantaCluster.
|
|
90
|
-
qc2 = QuantaCluster.
|
|
84
|
+
qc1 = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
85
|
+
qc2 = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
91
86
|
self.assertEqual(qc1, qc2)
|
|
92
87
|
|
|
93
88
|
def testNotEqual(self):
|
|
94
|
-
qc1 = QuantaCluster.
|
|
95
|
-
qc2 = QuantaCluster.
|
|
89
|
+
qc1 = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
90
|
+
qc2 = QuantaCluster.from_quantum_info(self.id2, self.info2, "{node_number}")
|
|
96
91
|
self.assertNotEqual(qc1, qc2)
|
|
97
92
|
|
|
98
93
|
def testHash(self):
|
|
99
|
-
qc1 = QuantaCluster.
|
|
100
|
-
qc2 = QuantaCluster.
|
|
94
|
+
qc1 = QuantaCluster.from_quantum_info(self.id1, self.info1, "{node_number}")
|
|
95
|
+
qc2 = QuantaCluster.from_quantum_info(self.id2, self.info2, "{node_number}")
|
|
101
96
|
self.assertNotEqual(hash(qc1), hash(qc2))
|
|
102
97
|
|
|
103
98
|
|
|
@@ -196,8 +191,9 @@ class TestClusteredQuantumGraph(unittest.TestCase):
|
|
|
196
191
|
def testValidateDuplicateId(self):
|
|
197
192
|
# Add new Quanta with duplicate Quantum
|
|
198
193
|
qc1 = self.cqg1.get_cluster("T1_1_2")
|
|
199
|
-
|
|
200
|
-
|
|
194
|
+
quantum_id = next(iter(qc1.qgraph_node_ids))
|
|
195
|
+
quantum_info = self.cqg1.get_quantum_info(quantum_id)
|
|
196
|
+
qc = QuantaCluster.from_quantum_info(quantum_id, quantum_info, "DuplicateId")
|
|
201
197
|
self.cqg1.add_cluster(qc)
|
|
202
198
|
qc2 = self.cqg1.get_cluster("T23_1_2")
|
|
203
199
|
self.cqg1.add_dependency(qc2, qc)
|
|
@@ -35,8 +35,7 @@ from pathlib import Path
|
|
|
35
35
|
|
|
36
36
|
from lsst.ctrl.bps import BpsConfig, BpsSubprocessError, ClusteredQuantumGraph
|
|
37
37
|
from lsst.ctrl.bps.pre_transform import cluster_quanta, create_quantum_graph, execute, update_quantum_graph
|
|
38
|
-
from lsst.
|
|
39
|
-
from lsst.pipe.base import QuantumGraph
|
|
38
|
+
from lsst.pipe.base.tests.mocks import InMemoryRepo
|
|
40
39
|
|
|
41
40
|
TESTDIR = os.path.abspath(os.path.dirname(__file__))
|
|
42
41
|
_LOG = logging.getLogger(__name__)
|
|
@@ -82,7 +81,7 @@ class TestCreatingQuantumGraph(unittest.TestCase):
|
|
|
82
81
|
"submitPath": self.tmpdir,
|
|
83
82
|
"whenSaveJobQgraph": "NEVER",
|
|
84
83
|
"uniqProcName": "my_test",
|
|
85
|
-
"qgraphFileTemplate": "{uniqProcName}.
|
|
84
|
+
"qgraphFileTemplate": "{uniqProcName}.qg",
|
|
86
85
|
}
|
|
87
86
|
self.logger = logging.getLogger("lsst.ctrl.bps")
|
|
88
87
|
|
|
@@ -125,8 +124,8 @@ class TestUpdatingQuantumGraph(unittest.TestCase):
|
|
|
125
124
|
"submitPath": self.tmpdir,
|
|
126
125
|
"whenSaveJobQgraph": "NEVER",
|
|
127
126
|
"uniqProcName": "my_test",
|
|
128
|
-
"qgraphFileTemplate": "{uniqProcName}.
|
|
129
|
-
"inputQgraphFile": f"{self.tmpdir}/src.
|
|
127
|
+
"qgraphFileTemplate": "{uniqProcName}.qg",
|
|
128
|
+
"inputQgraphFile": f"{self.tmpdir}/src.qg",
|
|
130
129
|
}
|
|
131
130
|
self.logger = logging.getLogger("lsst.ctrl.bps")
|
|
132
131
|
|
|
@@ -195,7 +194,7 @@ class TestClusterQuanta(unittest.TestCase):
|
|
|
195
194
|
"validateClusteredQgraph": True,
|
|
196
195
|
}
|
|
197
196
|
config = BpsConfig(settings, search_order=[])
|
|
198
|
-
qgraph =
|
|
197
|
+
qgraph = InMemoryRepo().make_quantum_graph()
|
|
199
198
|
with self.assertRaisesRegex(RuntimeError, "Fake error"):
|
|
200
199
|
_ = cluster_quanta(config, qgraph, "a_name")
|
|
201
200
|
|
|
@@ -209,7 +208,7 @@ class TestClusterQuanta(unittest.TestCase):
|
|
|
209
208
|
"validateClusteredQgraph": False,
|
|
210
209
|
}
|
|
211
210
|
config = BpsConfig(settings, search_order=[])
|
|
212
|
-
qgraph =
|
|
211
|
+
qgraph = InMemoryRepo().make_quantum_graph()
|
|
213
212
|
_ = cluster_quanta(config, qgraph, "a_name")
|
|
214
213
|
|
|
215
214
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/_exceptions.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/bps_config.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/bps_reports.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/cmd/__init__.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/cmd/commands.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/opt/__init__.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/opt/arguments.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/cli/opt/options.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/generic_workflow.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/initialize.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst/ctrl/bps/wms_service.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/python/lsst_ctrl_bps.egg-info/zip-safe
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3900 → lsst_ctrl_bps-29.2025.4000}/tests/test_quantum_clustering_funcs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|