lsst-pipe-base 29.2025.3900__py3-none-any.whl → 29.2025.4100__py3-none-any.whl
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/pipe/base/_task_metadata.py +15 -0
- lsst/pipe/base/dot_tools.py +14 -152
- lsst/pipe/base/exec_fixup_data_id.py +17 -44
- lsst/pipe/base/execution_graph_fixup.py +49 -18
- lsst/pipe/base/graph/_versionDeserializers.py +6 -5
- lsst/pipe/base/graph/graph.py +30 -10
- lsst/pipe/base/graph/graphSummary.py +30 -0
- lsst/pipe/base/graph_walker.py +119 -0
- lsst/pipe/base/log_capture.py +5 -2
- lsst/pipe/base/mermaid_tools.py +11 -64
- lsst/pipe/base/mp_graph_executor.py +298 -236
- lsst/pipe/base/pipeline_graph/io.py +1 -1
- lsst/pipe/base/quantum_graph/__init__.py +32 -0
- lsst/pipe/base/quantum_graph/_common.py +632 -0
- lsst/pipe/base/quantum_graph/_multiblock.py +808 -0
- lsst/pipe/base/quantum_graph/_predicted.py +1950 -0
- lsst/pipe/base/quantum_graph/visualization.py +302 -0
- lsst/pipe/base/quantum_graph_builder.py +292 -34
- lsst/pipe/base/quantum_graph_executor.py +2 -1
- lsst/pipe/base/quantum_provenance_graph.py +16 -7
- lsst/pipe/base/quantum_reports.py +45 -0
- lsst/pipe/base/separable_pipeline_executor.py +126 -15
- lsst/pipe/base/simple_pipeline_executor.py +44 -43
- lsst/pipe/base/single_quantum_executor.py +1 -40
- lsst/pipe/base/tests/mocks/__init__.py +1 -1
- lsst/pipe/base/tests/mocks/_pipeline_task.py +16 -1
- lsst/pipe/base/tests/mocks/{_in_memory_repo.py → _repo.py} +324 -45
- lsst/pipe/base/tests/mocks/_storage_class.py +51 -0
- lsst/pipe/base/tests/simpleQGraph.py +11 -5
- lsst/pipe/base/version.py +1 -1
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/METADATA +2 -1
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/RECORD +40 -34
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/WHEEL +0 -0
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/entry_points.txt +0 -0
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/COPYRIGHT +0 -0
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/LICENSE +0 -0
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/bsd_license.txt +0 -0
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/licenses/gpl-v3.0.txt +0 -0
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/top_level.txt +0 -0
- {lsst_pipe_base-29.2025.3900.dist-info → lsst_pipe_base-29.2025.4100.dist-info}/zip-safe +0 -0
|
@@ -47,6 +47,7 @@ from .all_dimensions_quantum_graph_builder import AllDimensionsQuantumGraphBuild
|
|
|
47
47
|
from .graph import QuantumGraph
|
|
48
48
|
from .mp_graph_executor import MPGraphExecutor
|
|
49
49
|
from .pipeline import Pipeline
|
|
50
|
+
from .quantum_graph import PredictedQuantumGraph
|
|
50
51
|
from .quantum_graph_builder import QuantumGraphBuilder
|
|
51
52
|
from .quantum_graph_executor import QuantumGraphExecutor
|
|
52
53
|
from .single_quantum_executor import SingleQuantumExecutor
|
|
@@ -120,7 +121,7 @@ class SeparablePipelineExecutor:
|
|
|
120
121
|
|
|
121
122
|
def pre_execute_qgraph(
|
|
122
123
|
self,
|
|
123
|
-
graph: QuantumGraph,
|
|
124
|
+
graph: QuantumGraph | PredictedQuantumGraph,
|
|
124
125
|
register_dataset_types: bool = False,
|
|
125
126
|
save_init_outputs: bool = True,
|
|
126
127
|
save_versions: bool = True,
|
|
@@ -133,7 +134,7 @@ class SeparablePipelineExecutor:
|
|
|
133
134
|
|
|
134
135
|
Parameters
|
|
135
136
|
----------
|
|
136
|
-
graph : `.QuantumGraph`
|
|
137
|
+
graph : `.QuantumGraph` or `.quantum_graph.PredictedQuantumGraph`
|
|
137
138
|
The quantum graph defining the pipeline and datasets to
|
|
138
139
|
be initialized.
|
|
139
140
|
register_dataset_types : `bool`, optional
|
|
@@ -169,6 +170,55 @@ class SeparablePipelineExecutor:
|
|
|
169
170
|
"""
|
|
170
171
|
return Pipeline.from_uri(pipeline_uri)
|
|
171
172
|
|
|
173
|
+
def make_quantum_graph_builder(
|
|
174
|
+
self,
|
|
175
|
+
pipeline: Pipeline,
|
|
176
|
+
where: str = "",
|
|
177
|
+
*,
|
|
178
|
+
builder_class: type[QuantumGraphBuilder] = AllDimensionsQuantumGraphBuilder,
|
|
179
|
+
**kwargs: Any,
|
|
180
|
+
) -> QuantumGraphBuilder:
|
|
181
|
+
"""Initialize a quantum graph builder from a pipeline and input
|
|
182
|
+
datasets.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
pipeline : `.Pipeline`
|
|
187
|
+
The pipeline for which to generate a quantum graph.
|
|
188
|
+
where : `str`, optional
|
|
189
|
+
A data ID query that constrains the quanta generated. Must not be
|
|
190
|
+
provided if a custom ``builder_class`` is given and that class does
|
|
191
|
+
not accept ``where`` as a construction argument.
|
|
192
|
+
builder_class : `type` [ \
|
|
193
|
+
`.quantum_graph_builder.QuantumGraphBuilder` ], optional
|
|
194
|
+
Quantum graph builder implementation. Ignored if ``builder`` is
|
|
195
|
+
provided.
|
|
196
|
+
**kwargs
|
|
197
|
+
Additional keyword arguments are forwarded to ``builder_class``
|
|
198
|
+
when a quantum graph builder instance is constructed. All
|
|
199
|
+
arguments accepted by the
|
|
200
|
+
`~.quantum_graph_builder.QuantumGraphBuilder` base
|
|
201
|
+
class are provided automatically (from explicit arguments to this
|
|
202
|
+
method and executor attributes) and do not need to be included
|
|
203
|
+
as keyword arguments.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
builder : `.quantum_graph_builder.QuantumGraphBuilder`
|
|
208
|
+
A quantum graph builder.
|
|
209
|
+
"""
|
|
210
|
+
if where:
|
|
211
|
+
# Only pass 'where' if it's actually provided, since some
|
|
212
|
+
# QuantumGraphBuilder subclasses may not accept it.
|
|
213
|
+
kwargs["where"] = where
|
|
214
|
+
return builder_class(
|
|
215
|
+
pipeline.to_graph(),
|
|
216
|
+
self._butler,
|
|
217
|
+
skip_existing_in=self._skip_existing_in,
|
|
218
|
+
clobber=self._clobber_output,
|
|
219
|
+
**kwargs,
|
|
220
|
+
)
|
|
221
|
+
|
|
172
222
|
def make_quantum_graph(
|
|
173
223
|
self,
|
|
174
224
|
pipeline: Pipeline,
|
|
@@ -180,6 +230,10 @@ class SeparablePipelineExecutor:
|
|
|
180
230
|
) -> QuantumGraph:
|
|
181
231
|
"""Build a quantum graph from a pipeline and input datasets.
|
|
182
232
|
|
|
233
|
+
This returns an instance of the old `.QuantumGraph` class. Use
|
|
234
|
+
`build_quantum_graph` to construct a
|
|
235
|
+
`.quantum_graph.PredictedQuantumGraph`.
|
|
236
|
+
|
|
183
237
|
Parameters
|
|
184
238
|
----------
|
|
185
239
|
pipeline : `.Pipeline`
|
|
@@ -225,17 +279,7 @@ class SeparablePipelineExecutor:
|
|
|
225
279
|
"user": getpass.getuser(),
|
|
226
280
|
"time": str(datetime.datetime.now()),
|
|
227
281
|
}
|
|
228
|
-
|
|
229
|
-
# Only pass 'where' if it's actually provided, since some
|
|
230
|
-
# QuantumGraphBuilder subclasses may not accept it.
|
|
231
|
-
kwargs["where"] = where
|
|
232
|
-
qg_builder = builder_class(
|
|
233
|
-
pipeline.to_graph(),
|
|
234
|
-
self._butler,
|
|
235
|
-
skip_existing_in=self._skip_existing_in,
|
|
236
|
-
clobber=self._clobber_output,
|
|
237
|
-
**kwargs,
|
|
238
|
-
)
|
|
282
|
+
qg_builder = self.make_quantum_graph_builder(pipeline, where, builder_class=builder_class, **kwargs)
|
|
239
283
|
graph = qg_builder.build(metadata=metadata, attach_datastore_records=attach_datastore_records)
|
|
240
284
|
_LOG.info(
|
|
241
285
|
"QuantumGraph contains %d quanta for %d tasks, graph ID: %r",
|
|
@@ -245,9 +289,76 @@ class SeparablePipelineExecutor:
|
|
|
245
289
|
)
|
|
246
290
|
return graph
|
|
247
291
|
|
|
292
|
+
def build_quantum_graph(
|
|
293
|
+
self,
|
|
294
|
+
pipeline: Pipeline,
|
|
295
|
+
where: str = "",
|
|
296
|
+
*,
|
|
297
|
+
builder_class: type[QuantumGraphBuilder] = AllDimensionsQuantumGraphBuilder,
|
|
298
|
+
attach_datastore_records: bool = False,
|
|
299
|
+
**kwargs: Any,
|
|
300
|
+
) -> PredictedQuantumGraph:
|
|
301
|
+
"""Build a quantum graph from a pipeline and input datasets.
|
|
302
|
+
|
|
303
|
+
This returns an instance of the new
|
|
304
|
+
`.quantum_graph.PredictedQuantumGraph` class. Use `make_quantum_graph`
|
|
305
|
+
to construct a `.QuantumGraph`.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
pipeline : `.Pipeline`
|
|
310
|
+
The pipeline for which to generate a quantum graph.
|
|
311
|
+
where : `str`, optional
|
|
312
|
+
A data ID query that constrains the quanta generated. Must not be
|
|
313
|
+
provided if a custom ``builder_class`` is given and that class does
|
|
314
|
+
not accept ``where`` as a construction argument.
|
|
315
|
+
builder_class : `type` [ \
|
|
316
|
+
`.quantum_graph_builder.QuantumGraphBuilder` ], optional
|
|
317
|
+
Quantum graph builder implementation. Ignored if ``builder`` is
|
|
318
|
+
provided.
|
|
319
|
+
attach_datastore_records : `bool`, optional
|
|
320
|
+
Whether to attach datastore records. These are currently used only
|
|
321
|
+
by `lsst.daf.butler.QuantumBackedButler`, which is not used by
|
|
322
|
+
`SeparablePipelineExecutor` for execution.
|
|
323
|
+
**kwargs
|
|
324
|
+
Additional keyword arguments are forwarded to ``builder_class``
|
|
325
|
+
when a quantum graph builder instance is constructed. All
|
|
326
|
+
arguments accepted by the
|
|
327
|
+
`~.quantum_graph_builder.QuantumGraphBuilder` base
|
|
328
|
+
class are provided automatically (from explicit arguments to this
|
|
329
|
+
method and executor attributes) and do not need to be included
|
|
330
|
+
as keyword arguments.
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
graph : `.QuantumGraph`
|
|
335
|
+
The quantum graph for ``.Pipeline`` as run on the datasets
|
|
336
|
+
identified by ``where``.
|
|
337
|
+
|
|
338
|
+
Notes
|
|
339
|
+
-----
|
|
340
|
+
This method does no special handling of empty quantum graphs. If
|
|
341
|
+
needed, clients can use `len` to test if the returned graph is empty.
|
|
342
|
+
"""
|
|
343
|
+
metadata = {
|
|
344
|
+
"skip_existing_in": self._skip_existing_in,
|
|
345
|
+
"skip_existing": bool(self._skip_existing_in),
|
|
346
|
+
"data_query": where,
|
|
347
|
+
}
|
|
348
|
+
qg_builder = self.make_quantum_graph_builder(pipeline, where, builder_class=builder_class, **kwargs)
|
|
349
|
+
graph = qg_builder.finish(
|
|
350
|
+
metadata=metadata, attach_datastore_records=attach_datastore_records
|
|
351
|
+
).assemble()
|
|
352
|
+
_LOG.info(
|
|
353
|
+
"PredictedQuantumGraph contains %d quanta for %d tasks.",
|
|
354
|
+
len(graph),
|
|
355
|
+
len(graph.quanta_by_task),
|
|
356
|
+
)
|
|
357
|
+
return graph
|
|
358
|
+
|
|
248
359
|
def run_pipeline(
|
|
249
360
|
self,
|
|
250
|
-
graph: QuantumGraph,
|
|
361
|
+
graph: QuantumGraph | PredictedQuantumGraph,
|
|
251
362
|
fail_fast: bool = False,
|
|
252
363
|
graph_executor: QuantumGraphExecutor | None = None,
|
|
253
364
|
num_proc: int = 1,
|
|
@@ -259,7 +370,7 @@ class SeparablePipelineExecutor:
|
|
|
259
370
|
|
|
260
371
|
Parameters
|
|
261
372
|
----------
|
|
262
|
-
graph : `.QuantumGraph`
|
|
373
|
+
graph : `.QuantumGraph` or `.quantum_graph.PredictedQuantumGraph`
|
|
263
374
|
The pipeline and datasets to execute.
|
|
264
375
|
fail_fast : `bool`, optional
|
|
265
376
|
If `True`, abort all execution if any task fails when
|
|
@@ -29,20 +29,15 @@ from __future__ import annotations
|
|
|
29
29
|
|
|
30
30
|
__all__ = ("SimplePipelineExecutor",)
|
|
31
31
|
|
|
32
|
-
import datetime
|
|
33
|
-
import getpass
|
|
34
|
-
import itertools
|
|
35
32
|
import os
|
|
36
33
|
from collections.abc import Iterable, Iterator, Mapping
|
|
37
|
-
from typing import Any
|
|
34
|
+
from typing import Any
|
|
38
35
|
|
|
39
36
|
from lsst.daf.butler import (
|
|
40
37
|
Butler,
|
|
41
38
|
CollectionType,
|
|
42
39
|
DataCoordinate,
|
|
43
40
|
DatasetRef,
|
|
44
|
-
DimensionDataExtractor,
|
|
45
|
-
DimensionGroup,
|
|
46
41
|
Quantum,
|
|
47
42
|
)
|
|
48
43
|
from lsst.pex.config import Config
|
|
@@ -54,6 +49,7 @@ from .graph import QuantumGraph
|
|
|
54
49
|
from .pipeline import Pipeline
|
|
55
50
|
from .pipeline_graph import PipelineGraph
|
|
56
51
|
from .pipelineTask import PipelineTask
|
|
52
|
+
from .quantum_graph import PredictedQuantumGraph
|
|
57
53
|
from .single_quantum_executor import SingleQuantumExecutor
|
|
58
54
|
from .taskFactory import TaskFactory
|
|
59
55
|
|
|
@@ -95,12 +91,19 @@ class SimplePipelineExecutor:
|
|
|
95
91
|
|
|
96
92
|
def __init__(
|
|
97
93
|
self,
|
|
98
|
-
quantum_graph: QuantumGraph,
|
|
94
|
+
quantum_graph: QuantumGraph | PredictedQuantumGraph,
|
|
99
95
|
butler: Butler,
|
|
100
96
|
resources: ExecutionResources | None = None,
|
|
101
97
|
raise_on_partial_outputs: bool = True,
|
|
102
98
|
):
|
|
103
|
-
|
|
99
|
+
from .graph import QuantumGraph
|
|
100
|
+
|
|
101
|
+
self._quantum_graph: QuantumGraph | None = None
|
|
102
|
+
if isinstance(quantum_graph, QuantumGraph):
|
|
103
|
+
self._quantum_graph = quantum_graph
|
|
104
|
+
self.predicted = PredictedQuantumGraph.from_old_quantum_graph(self._quantum_graph)
|
|
105
|
+
else:
|
|
106
|
+
self.predicted = quantum_graph
|
|
104
107
|
self.butler = butler
|
|
105
108
|
self.resources = resources
|
|
106
109
|
self.raise_on_partial_outputs = raise_on_partial_outputs
|
|
@@ -442,25 +445,29 @@ class SimplePipelineExecutor:
|
|
|
442
445
|
pipeline_graph, butler, where=where, bind=bind, output_run=output_run
|
|
443
446
|
)
|
|
444
447
|
metadata = {
|
|
445
|
-
"input": list(butler.collections.defaults),
|
|
446
|
-
"output": output,
|
|
447
|
-
"output_run": output_run,
|
|
448
448
|
"skip_existing_in": [],
|
|
449
449
|
"skip_existing": False,
|
|
450
450
|
"data_query": where,
|
|
451
|
-
"user": getpass.getuser(),
|
|
452
|
-
"time": str(datetime.datetime.now()),
|
|
453
451
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
452
|
+
predicted = quantum_graph_builder.finish(
|
|
453
|
+
output=output,
|
|
454
|
+
metadata=metadata,
|
|
455
|
+
attach_datastore_records=attach_datastore_records,
|
|
456
|
+
).assemble()
|
|
457
457
|
return cls(
|
|
458
|
-
|
|
458
|
+
predicted,
|
|
459
459
|
butler=butler,
|
|
460
460
|
resources=resources,
|
|
461
461
|
raise_on_partial_outputs=raise_on_partial_outputs,
|
|
462
462
|
)
|
|
463
463
|
|
|
464
|
+
@property
|
|
465
|
+
def quantum_graph(self) -> QuantumGraph:
|
|
466
|
+
"""The quantum graph run by this executor."""
|
|
467
|
+
if self._quantum_graph is None:
|
|
468
|
+
self._quantum_graph = self.predicted.to_old_quantum_graph()
|
|
469
|
+
return self._quantum_graph
|
|
470
|
+
|
|
464
471
|
def use_local_butler(
|
|
465
472
|
self, root: str, register_dataset_types: bool = True, transfer_dimensions: bool = True
|
|
466
473
|
) -> Butler:
|
|
@@ -503,9 +510,9 @@ class SimplePipelineExecutor:
|
|
|
503
510
|
Butler.makeRepo(root)
|
|
504
511
|
out_butler = Butler.from_config(root, writeable=True)
|
|
505
512
|
|
|
506
|
-
output_run = self.
|
|
513
|
+
output_run = self.predicted.header.output_run
|
|
507
514
|
out_butler.collections.register(output_run, CollectionType.RUN)
|
|
508
|
-
output = self.
|
|
515
|
+
output = self.predicted.header.output
|
|
509
516
|
inputs: str | None = None
|
|
510
517
|
if output is not None:
|
|
511
518
|
inputs = f"{output}/inputs"
|
|
@@ -525,12 +532,12 @@ class SimplePipelineExecutor:
|
|
|
525
532
|
# into a TAGGED collection.
|
|
526
533
|
refs: set[DatasetRef] = set()
|
|
527
534
|
to_tag_by_type: dict[str, dict[DataCoordinate, DatasetRef | None]] = {}
|
|
528
|
-
pipeline_graph = self.
|
|
535
|
+
pipeline_graph = self.predicted.pipeline_graph
|
|
529
536
|
for name, dataset_type_node in pipeline_graph.iter_overall_inputs():
|
|
530
537
|
assert dataset_type_node is not None, "PipelineGraph should be resolved."
|
|
531
538
|
to_tag_for_type = to_tag_by_type.setdefault(name, {})
|
|
532
539
|
for task_node in pipeline_graph.consumers_of(name):
|
|
533
|
-
for quantum in self.
|
|
540
|
+
for quantum in self.predicted.build_execution_quanta(task_label=task_node.label).values():
|
|
534
541
|
for ref in quantum.inputs[name]:
|
|
535
542
|
ref = dataset_type_node.generalize_ref(ref)
|
|
536
543
|
refs.add(ref)
|
|
@@ -563,7 +570,7 @@ class SimplePipelineExecutor:
|
|
|
563
570
|
return self.butler
|
|
564
571
|
|
|
565
572
|
def run(self, register_dataset_types: bool = False, save_versions: bool = True) -> list[Quantum]:
|
|
566
|
-
"""Run all the quanta in the
|
|
573
|
+
"""Run all the quanta in the quantum graph in topological order.
|
|
567
574
|
|
|
568
575
|
Use this method to run all quanta in the graph. Use
|
|
569
576
|
`as_generator` to get a generator to run the quanta one at
|
|
@@ -594,7 +601,7 @@ class SimplePipelineExecutor:
|
|
|
594
601
|
def as_generator(
|
|
595
602
|
self, register_dataset_types: bool = False, save_versions: bool = True
|
|
596
603
|
) -> Iterator[Quantum]:
|
|
597
|
-
"""Yield quanta in the
|
|
604
|
+
"""Yield quanta in the quantum graph in topological order.
|
|
598
605
|
|
|
599
606
|
These quanta will be run as the returned generator is iterated
|
|
600
607
|
over. Use this method to run the quanta one at a time.
|
|
@@ -623,11 +630,11 @@ class SimplePipelineExecutor:
|
|
|
623
630
|
guarantees are made about the order in which quanta are processed.
|
|
624
631
|
"""
|
|
625
632
|
if register_dataset_types:
|
|
626
|
-
self.
|
|
627
|
-
self.
|
|
628
|
-
self.
|
|
633
|
+
self.predicted.pipeline_graph.register_dataset_types(self.butler)
|
|
634
|
+
self.predicted.write_configs(self.butler, compare_existing=False)
|
|
635
|
+
self.predicted.write_init_outputs(self.butler, skip_existing=False)
|
|
629
636
|
if save_versions:
|
|
630
|
-
self.
|
|
637
|
+
self.predicted.write_packages(self.butler, compare_existing=False)
|
|
631
638
|
task_factory = TaskFactory()
|
|
632
639
|
single_quantum_executor = SingleQuantumExecutor(
|
|
633
640
|
butler=self.butler,
|
|
@@ -635,14 +642,20 @@ class SimplePipelineExecutor:
|
|
|
635
642
|
resources=self.resources,
|
|
636
643
|
raise_on_partial_outputs=self.raise_on_partial_outputs,
|
|
637
644
|
)
|
|
645
|
+
self.predicted.build_execution_quanta()
|
|
646
|
+
nodes_map = self.predicted.quantum_only_xgraph.nodes
|
|
638
647
|
# Important that this returns a generator expression rather than being
|
|
639
648
|
# a generator itself; that is what makes the init stuff above happen
|
|
640
649
|
# immediately instead of when the first quanta is executed, which might
|
|
641
650
|
# be useful for callers who want to check the state of the repo in
|
|
642
651
|
# between.
|
|
643
652
|
return (
|
|
644
|
-
single_quantum_executor.execute(
|
|
645
|
-
|
|
653
|
+
single_quantum_executor.execute(
|
|
654
|
+
nodes_map[quantum_id]["pipeline_node"],
|
|
655
|
+
nodes_map[quantum_id]["quantum"],
|
|
656
|
+
quantum_id,
|
|
657
|
+
)[0]
|
|
658
|
+
for quantum_id in self.predicted
|
|
646
659
|
)
|
|
647
660
|
|
|
648
661
|
def _transfer_qg_dimension_records(self, out_butler: Butler) -> None:
|
|
@@ -653,20 +666,8 @@ class SimplePipelineExecutor:
|
|
|
653
666
|
out_butler : `lsst.daf.butler.Butler`
|
|
654
667
|
Butler to transfer records to.
|
|
655
668
|
"""
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
*pipeline_graph.group_by_dimensions(prerequisites=True).keys(),
|
|
659
|
-
universe=self.butler.dimensions,
|
|
660
|
-
)
|
|
661
|
-
dimension_data_extractor = DimensionDataExtractor.from_dimension_group(all_dimensions)
|
|
662
|
-
for task_node in pipeline_graph.tasks.values():
|
|
663
|
-
task_quanta = self.quantum_graph.get_task_quanta(task_node.label)
|
|
664
|
-
for quantum in task_quanta.values():
|
|
665
|
-
dimension_data_extractor.update([cast(DataCoordinate, quantum.dataId)])
|
|
666
|
-
for refs in itertools.chain(quantum.inputs.values(), quantum.outputs.values()):
|
|
667
|
-
dimension_data_extractor.update(ref.dataId for ref in refs)
|
|
668
|
-
for element_name in all_dimensions.elements:
|
|
669
|
-
record_set = dimension_data_extractor.records.get(element_name)
|
|
669
|
+
assert self.predicted.dimension_data is not None, "Dimension data must be present for execution."
|
|
670
|
+
for record_set in self.predicted.dimension_data.records.values():
|
|
670
671
|
if record_set and record_set.element.has_own_table:
|
|
671
672
|
out_butler.registry.insertDimensionData(
|
|
672
673
|
record_set.element,
|
|
@@ -33,7 +33,7 @@ import uuid
|
|
|
33
33
|
from collections import defaultdict
|
|
34
34
|
from collections.abc import Callable, Mapping
|
|
35
35
|
from itertools import chain
|
|
36
|
-
from typing import Any
|
|
36
|
+
from typing import Any
|
|
37
37
|
|
|
38
38
|
from lsst.daf.butler import (
|
|
39
39
|
Butler,
|
|
@@ -46,7 +46,6 @@ from lsst.daf.butler import (
|
|
|
46
46
|
)
|
|
47
47
|
from lsst.utils.timer import logInfo
|
|
48
48
|
|
|
49
|
-
from ._instrument import Instrument
|
|
50
49
|
from ._quantumContext import ExecutionResources, QuantumContext
|
|
51
50
|
from ._status import AnnotatedPartialOutputsError, InvalidQuantumError, NoWorkFound, QuantumSuccessCaveats
|
|
52
51
|
from .connections import AdjustQuantumHelper
|
|
@@ -238,9 +237,6 @@ class SingleQuantumExecutor(QuantumExecutor):
|
|
|
238
237
|
except ImportError:
|
|
239
238
|
_LOG.warning("No 'debug' module found.")
|
|
240
239
|
|
|
241
|
-
# initialize global state
|
|
242
|
-
self._init_globals(quantum)
|
|
243
|
-
|
|
244
240
|
# Ensure that we are executing a frozen config
|
|
245
241
|
task_node.config.freeze()
|
|
246
242
|
logInfo(None, "init", metadata=quantumMetadata) # type: ignore[arg-type]
|
|
@@ -569,41 +565,6 @@ class SingleQuantumExecutor(QuantumExecutor):
|
|
|
569
565
|
) from exc
|
|
570
566
|
limited_butler.put(metadata, ref)
|
|
571
567
|
|
|
572
|
-
def _init_globals(self, quantum: Quantum) -> None:
|
|
573
|
-
"""Initialize global state needed for task execution.
|
|
574
|
-
|
|
575
|
-
Parameters
|
|
576
|
-
----------
|
|
577
|
-
quantum : `~lsst.daf.butler.Quantum`
|
|
578
|
-
Single Quantum instance.
|
|
579
|
-
|
|
580
|
-
Notes
|
|
581
|
-
-----
|
|
582
|
-
There is an issue with initializing filters singleton which is done
|
|
583
|
-
by instrument, to avoid requiring tasks to do it in runQuantum()
|
|
584
|
-
we do it here when any dataId has an instrument dimension. Also for
|
|
585
|
-
now we only allow single instrument, verify that all instrument
|
|
586
|
-
names in all dataIds are identical.
|
|
587
|
-
|
|
588
|
-
This will need revision when filter singleton disappears.
|
|
589
|
-
"""
|
|
590
|
-
# can only work for full butler
|
|
591
|
-
if self._butler is None:
|
|
592
|
-
return
|
|
593
|
-
oneInstrument = None
|
|
594
|
-
for datasetRefs in chain(quantum.inputs.values(), quantum.outputs.values()):
|
|
595
|
-
for datasetRef in datasetRefs:
|
|
596
|
-
dataId = datasetRef.dataId
|
|
597
|
-
instrument = cast(str, dataId.get("instrument"))
|
|
598
|
-
if instrument is not None:
|
|
599
|
-
if oneInstrument is not None:
|
|
600
|
-
assert ( # type: ignore
|
|
601
|
-
instrument == oneInstrument
|
|
602
|
-
), "Currently require that only one instrument is used per graph"
|
|
603
|
-
else:
|
|
604
|
-
oneInstrument = instrument
|
|
605
|
-
Instrument.fromName(instrument, self._butler.registry)
|
|
606
|
-
|
|
607
568
|
def _should_assume_exists(self, quantum: Quantum, ref: DatasetRef) -> bool | None:
|
|
608
569
|
"""Report whether the given dataset can be assumed to exist because
|
|
609
570
|
some previous check reported that it did.
|
|
@@ -56,6 +56,7 @@ from lsst.utils.iteration import ensure_iterable
|
|
|
56
56
|
|
|
57
57
|
from ... import connectionTypes as cT
|
|
58
58
|
from ..._status import AlgorithmError, AnnotatedPartialOutputsError
|
|
59
|
+
from ...automatic_connection_constants import METADATA_OUTPUT_CONNECTION_NAME, METADATA_OUTPUT_STORAGE_CLASS
|
|
59
60
|
from ...config import PipelineTaskConfig
|
|
60
61
|
from ...connections import InputQuantizedConnection, OutputQuantizedConnection, PipelineTaskConnections
|
|
61
62
|
from ...pipeline_graph import PipelineGraph
|
|
@@ -202,6 +203,13 @@ class BaseTestPipelineTaskConfig(PipelineTaskConfig, pipelineConnections=BaseTes
|
|
|
202
203
|
doc="Time to sleep (seconds) before mock execution reading inputs or failing.",
|
|
203
204
|
)
|
|
204
205
|
|
|
206
|
+
int_value = Field[int](
|
|
207
|
+
"Arbitrary integer value to write into mock output datasets", dtype=int, optional=True, default=None
|
|
208
|
+
)
|
|
209
|
+
str_value = Field[str](
|
|
210
|
+
"Arbitrary string value to write into mock output datasets", dtype=str, optional=True, default=None
|
|
211
|
+
)
|
|
212
|
+
|
|
205
213
|
def data_id_match(self) -> DataIdMatch | None:
|
|
206
214
|
if not self.fail_condition:
|
|
207
215
|
return None
|
|
@@ -294,6 +302,8 @@ class BaseTestPipelineTask(PipelineTask):
|
|
|
294
302
|
run=None, # task also has no way to get this
|
|
295
303
|
quantum=mock_dataset_quantum,
|
|
296
304
|
output_connection_name=connection_name,
|
|
305
|
+
int_value=self.config.int_value,
|
|
306
|
+
str_value=self.config.str_value,
|
|
297
307
|
)
|
|
298
308
|
setattr(self, connection_name, output_dataset)
|
|
299
309
|
|
|
@@ -373,6 +383,8 @@ class BaseTestPipelineTask(PipelineTask):
|
|
|
373
383
|
run=ref.run,
|
|
374
384
|
quantum=mock_dataset_quantum,
|
|
375
385
|
output_connection_name=name,
|
|
386
|
+
int_value=self.config.int_value,
|
|
387
|
+
str_value=self.config.str_value,
|
|
376
388
|
)
|
|
377
389
|
butlerQC.put(output, ref)
|
|
378
390
|
|
|
@@ -476,7 +488,10 @@ class MockPipelineTaskConnections(BaseTestPipelineTaskConnections, dimensions=()
|
|
|
476
488
|
raise ValueError(
|
|
477
489
|
f"Unmocked dataset type {connection.name!r} cannot be used as an init-output."
|
|
478
490
|
)
|
|
479
|
-
elif
|
|
491
|
+
elif (
|
|
492
|
+
connection.name.endswith(METADATA_OUTPUT_CONNECTION_NAME)
|
|
493
|
+
and connection.storageClass == METADATA_OUTPUT_STORAGE_CLASS
|
|
494
|
+
):
|
|
480
495
|
# Task metadata does not use a mock storage class, because it's
|
|
481
496
|
# written by the system, but it does end up with the _mock_*
|
|
482
497
|
# prefix because the task label does.
|