lsst-pipe-base 30.0.1rc1__py3-none-any.whl → 30.2025.5200__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/_instrument.py +20 -31
- lsst/pipe/base/_quantumContext.py +3 -3
- lsst/pipe/base/_status.py +10 -43
- lsst/pipe/base/_task_metadata.py +2 -2
- lsst/pipe/base/all_dimensions_quantum_graph_builder.py +3 -8
- lsst/pipe/base/automatic_connection_constants.py +1 -20
- lsst/pipe/base/cli/cmd/__init__.py +2 -18
- lsst/pipe/base/cli/cmd/commands.py +4 -149
- lsst/pipe/base/connectionTypes.py +160 -72
- lsst/pipe/base/connections.py +9 -6
- lsst/pipe/base/execution_reports.py +5 -0
- lsst/pipe/base/graph/graph.py +10 -11
- lsst/pipe/base/graph/quantumNode.py +4 -4
- lsst/pipe/base/graph_walker.py +10 -8
- lsst/pipe/base/log_capture.py +5 -9
- lsst/pipe/base/mp_graph_executor.py +15 -51
- lsst/pipe/base/pipeline.py +6 -5
- lsst/pipe/base/pipelineIR.py +8 -2
- lsst/pipe/base/pipelineTask.py +7 -5
- lsst/pipe/base/pipeline_graph/_dataset_types.py +2 -2
- lsst/pipe/base/pipeline_graph/_edges.py +22 -32
- lsst/pipe/base/pipeline_graph/_mapping_views.py +7 -4
- lsst/pipe/base/pipeline_graph/_pipeline_graph.py +7 -14
- lsst/pipe/base/pipeline_graph/expressions.py +2 -2
- lsst/pipe/base/pipeline_graph/io.py +10 -7
- lsst/pipe/base/pipeline_graph/visualization/_dot.py +12 -13
- lsst/pipe/base/pipeline_graph/visualization/_layout.py +18 -16
- lsst/pipe/base/pipeline_graph/visualization/_merge.py +7 -4
- lsst/pipe/base/pipeline_graph/visualization/_printer.py +10 -10
- lsst/pipe/base/pipeline_graph/visualization/_status_annotator.py +0 -7
- lsst/pipe/base/prerequisite_helpers.py +1 -2
- lsst/pipe/base/quantum_graph/_common.py +20 -19
- lsst/pipe/base/quantum_graph/_multiblock.py +31 -37
- lsst/pipe/base/quantum_graph/_predicted.py +13 -111
- lsst/pipe/base/quantum_graph/_provenance.py +45 -1136
- lsst/pipe/base/quantum_graph/aggregator/__init__.py +1 -0
- lsst/pipe/base/quantum_graph/aggregator/_communicators.py +289 -204
- lsst/pipe/base/quantum_graph/aggregator/_config.py +9 -87
- lsst/pipe/base/quantum_graph/aggregator/_ingester.py +12 -13
- lsst/pipe/base/quantum_graph/aggregator/_scanner.py +235 -49
- lsst/pipe/base/quantum_graph/aggregator/_structs.py +116 -6
- lsst/pipe/base/quantum_graph/aggregator/_supervisor.py +39 -29
- lsst/pipe/base/quantum_graph/aggregator/_writer.py +351 -34
- lsst/pipe/base/quantum_graph/visualization.py +1 -5
- lsst/pipe/base/quantum_graph_builder.py +8 -21
- lsst/pipe/base/quantum_graph_executor.py +13 -116
- lsst/pipe/base/quantum_graph_skeleton.py +29 -31
- lsst/pipe/base/quantum_provenance_graph.py +12 -29
- lsst/pipe/base/separable_pipeline_executor.py +3 -19
- lsst/pipe/base/single_quantum_executor.py +42 -67
- lsst/pipe/base/struct.py +0 -4
- lsst/pipe/base/testUtils.py +3 -3
- lsst/pipe/base/tests/mocks/_storage_class.py +1 -2
- lsst/pipe/base/version.py +1 -1
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/METADATA +3 -3
- lsst_pipe_base-30.2025.5200.dist-info/RECORD +125 -0
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/WHEEL +1 -1
- lsst/pipe/base/log_on_close.py +0 -76
- lsst/pipe/base/quantum_graph/aggregator/_workers.py +0 -303
- lsst/pipe/base/quantum_graph/formatter.py +0 -171
- lsst/pipe/base/quantum_graph/ingest_graph.py +0 -413
- lsst_pipe_base-30.0.1rc1.dist-info/RECORD +0 -129
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/entry_points.txt +0 -0
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/licenses/COPYRIGHT +0 -0
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/licenses/LICENSE +0 -0
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/licenses/bsd_license.txt +0 -0
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/licenses/gpl-v3.0.txt +0 -0
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/top_level.txt +0 -0
- {lsst_pipe_base-30.0.1rc1.dist-info → lsst_pipe_base-30.2025.5200.dist-info}/zip-safe +0 -0
|
@@ -30,14 +30,130 @@ from __future__ import annotations
|
|
|
30
30
|
__all__ = ("Writer",)
|
|
31
31
|
|
|
32
32
|
import dataclasses
|
|
33
|
+
import itertools
|
|
34
|
+
import logging
|
|
35
|
+
import operator
|
|
36
|
+
import uuid
|
|
37
|
+
from typing import TypeVar
|
|
33
38
|
|
|
39
|
+
import networkx
|
|
34
40
|
import zstandard
|
|
35
41
|
|
|
36
|
-
from
|
|
42
|
+
from lsst.utils.packages import Packages
|
|
43
|
+
|
|
44
|
+
from ... import automatic_connection_constants as acc
|
|
37
45
|
from ...pipeline_graph import TaskImportMode
|
|
38
|
-
from ..
|
|
39
|
-
from ..
|
|
46
|
+
from .._common import BaseQuantumGraphWriter
|
|
47
|
+
from .._multiblock import Compressor, MultiblockWriter
|
|
48
|
+
from .._predicted import PredictedDatasetModel, PredictedQuantumGraphComponents, PredictedQuantumGraphReader
|
|
49
|
+
from .._provenance import (
|
|
50
|
+
DATASET_ADDRESS_INDEX,
|
|
51
|
+
DATASET_MB_NAME,
|
|
52
|
+
LOG_ADDRESS_INDEX,
|
|
53
|
+
LOG_MB_NAME,
|
|
54
|
+
METADATA_ADDRESS_INDEX,
|
|
55
|
+
METADATA_MB_NAME,
|
|
56
|
+
QUANTUM_ADDRESS_INDEX,
|
|
57
|
+
QUANTUM_MB_NAME,
|
|
58
|
+
ProvenanceDatasetModel,
|
|
59
|
+
ProvenanceInitQuantaModel,
|
|
60
|
+
ProvenanceInitQuantumModel,
|
|
61
|
+
ProvenanceQuantumModel,
|
|
62
|
+
)
|
|
40
63
|
from ._communicators import WriterCommunicator
|
|
64
|
+
from ._structs import WriteRequest
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclasses.dataclass
|
|
68
|
+
class _DataWriters:
|
|
69
|
+
"""A struct of low-level writer objects for the main components of a
|
|
70
|
+
provenance quantum graph.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
comms : `WriterCommunicator`
|
|
75
|
+
Communicator helper object for the writer.
|
|
76
|
+
predicted : `.PredictedQuantumGraphComponents`
|
|
77
|
+
Components of the predicted graph.
|
|
78
|
+
indices : `dict` [ `uuid.UUID`, `int` ]
|
|
79
|
+
Mapping from UUID to internal integer ID, including both quanta and
|
|
80
|
+
datasets.
|
|
81
|
+
compressor : `Compressor`
|
|
82
|
+
Object that can compress `bytes`.
|
|
83
|
+
cdict_data : `bytes` or `None`, optional
|
|
84
|
+
Bytes representation of the compression dictionary used by the
|
|
85
|
+
compressor.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
comms: WriterCommunicator,
|
|
91
|
+
predicted: PredictedQuantumGraphComponents,
|
|
92
|
+
indices: dict[uuid.UUID, int],
|
|
93
|
+
compressor: Compressor,
|
|
94
|
+
cdict_data: bytes | None = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
assert comms.config.output_path is not None
|
|
97
|
+
header = predicted.header.model_copy()
|
|
98
|
+
header.graph_type = "provenance"
|
|
99
|
+
self.graph = comms.enter(
|
|
100
|
+
BaseQuantumGraphWriter.open(
|
|
101
|
+
comms.config.output_path,
|
|
102
|
+
header,
|
|
103
|
+
predicted.pipeline_graph,
|
|
104
|
+
indices,
|
|
105
|
+
address_filename="nodes",
|
|
106
|
+
compressor=compressor,
|
|
107
|
+
cdict_data=cdict_data,
|
|
108
|
+
),
|
|
109
|
+
on_close="Finishing writing provenance quantum graph.",
|
|
110
|
+
is_progress_log=True,
|
|
111
|
+
)
|
|
112
|
+
self.graph.address_writer.addresses = [{}, {}, {}, {}]
|
|
113
|
+
self.logs = comms.enter(
|
|
114
|
+
MultiblockWriter.open_in_zip(self.graph.zf, LOG_MB_NAME, header.int_size, use_tempfile=True),
|
|
115
|
+
on_close="Copying logs into zip archive.",
|
|
116
|
+
is_progress_log=True,
|
|
117
|
+
)
|
|
118
|
+
self.graph.address_writer.addresses[LOG_ADDRESS_INDEX] = self.logs.addresses
|
|
119
|
+
self.metadata = comms.enter(
|
|
120
|
+
MultiblockWriter.open_in_zip(self.graph.zf, METADATA_MB_NAME, header.int_size, use_tempfile=True),
|
|
121
|
+
on_close="Copying metadata into zip archive.",
|
|
122
|
+
is_progress_log=True,
|
|
123
|
+
)
|
|
124
|
+
self.graph.address_writer.addresses[METADATA_ADDRESS_INDEX] = self.metadata.addresses
|
|
125
|
+
self.datasets = comms.enter(
|
|
126
|
+
MultiblockWriter.open_in_zip(self.graph.zf, DATASET_MB_NAME, header.int_size, use_tempfile=True),
|
|
127
|
+
on_close="Copying dataset provenance into zip archive.",
|
|
128
|
+
is_progress_log=True,
|
|
129
|
+
)
|
|
130
|
+
self.graph.address_writer.addresses[DATASET_ADDRESS_INDEX] = self.datasets.addresses
|
|
131
|
+
self.quanta = comms.enter(
|
|
132
|
+
MultiblockWriter.open_in_zip(self.graph.zf, QUANTUM_MB_NAME, header.int_size, use_tempfile=True),
|
|
133
|
+
on_close="Copying quantum provenance into zip archive.",
|
|
134
|
+
is_progress_log=True,
|
|
135
|
+
)
|
|
136
|
+
self.graph.address_writer.addresses[QUANTUM_ADDRESS_INDEX] = self.quanta.addresses
|
|
137
|
+
|
|
138
|
+
graph: BaseQuantumGraphWriter
|
|
139
|
+
"""The parent graph writer."""
|
|
140
|
+
|
|
141
|
+
datasets: MultiblockWriter
|
|
142
|
+
"""A writer for dataset provenance."""
|
|
143
|
+
|
|
144
|
+
quanta: MultiblockWriter
|
|
145
|
+
"""A writer for quantum provenance."""
|
|
146
|
+
|
|
147
|
+
metadata: MultiblockWriter
|
|
148
|
+
"""A writer for metadata content."""
|
|
149
|
+
|
|
150
|
+
logs: MultiblockWriter
|
|
151
|
+
"""A writer for log content."""
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def compressor(self) -> Compressor:
|
|
155
|
+
"""Object that should be used to compress all JSON blocks."""
|
|
156
|
+
return self.graph.compressor
|
|
41
157
|
|
|
42
158
|
|
|
43
159
|
@dataclasses.dataclass
|
|
@@ -55,13 +171,46 @@ class Writer:
|
|
|
55
171
|
predicted: PredictedQuantumGraphComponents = dataclasses.field(init=False)
|
|
56
172
|
"""Components of the predicted quantum graph."""
|
|
57
173
|
|
|
58
|
-
|
|
174
|
+
existing_init_outputs: dict[uuid.UUID, set[uuid.UUID]] = dataclasses.field(default_factory=dict)
|
|
175
|
+
"""Mapping that tracks which init-outputs exist.
|
|
176
|
+
|
|
177
|
+
This mapping is updated as scanners inform the writer about init-output
|
|
178
|
+
existence, since we want to write that provenance information out only at
|
|
179
|
+
the end.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
indices: dict[uuid.UUID, int] = dataclasses.field(default_factory=dict)
|
|
183
|
+
"""Mapping from UUID to internal integer ID, including both quanta and
|
|
184
|
+
datasets.
|
|
185
|
+
|
|
186
|
+
This is fully initialized at construction.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
output_dataset_ids: set[uuid.UUID] = dataclasses.field(default_factory=set)
|
|
190
|
+
"""The IDs of all datasets that are produced by this graph.
|
|
191
|
+
|
|
192
|
+
This is fully initialized at construction.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
overall_inputs: dict[uuid.UUID, PredictedDatasetModel] = dataclasses.field(default_factory=dict)
|
|
196
|
+
"""All datasets that are not produced by any quantum in this graph."""
|
|
197
|
+
|
|
198
|
+
xgraph: networkx.DiGraph = dataclasses.field(default_factory=networkx.DiGraph)
|
|
199
|
+
"""A bipartite NetworkX graph linking datasets to quanta and quanta to
|
|
200
|
+
datasets.
|
|
201
|
+
|
|
202
|
+
This is fully initialized at construction. There are no node or edge
|
|
203
|
+
attributes in this graph; we only need it to store adjacency information
|
|
204
|
+
with datasets as well as with quanta.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
pending_compression_training: list[WriteRequest] = dataclasses.field(default_factory=list)
|
|
59
208
|
"""Unprocessed quantum scans that are being accumulated in order to
|
|
60
209
|
build a compression dictionary.
|
|
61
210
|
"""
|
|
62
211
|
|
|
63
212
|
def __post_init__(self) -> None:
|
|
64
|
-
assert self.comms.config.
|
|
213
|
+
assert self.comms.config.output_path is not None, "Writer should not be used if writing is disabled."
|
|
65
214
|
self.comms.log.info("Reading predicted quantum graph.")
|
|
66
215
|
with PredictedQuantumGraphReader.open(
|
|
67
216
|
self.predicted_path, import_mode=TaskImportMode.DO_NOT_IMPORT
|
|
@@ -71,6 +220,58 @@ class Writer:
|
|
|
71
220
|
self.comms.check_for_cancel()
|
|
72
221
|
reader.read_quantum_datasets()
|
|
73
222
|
self.predicted = reader.components
|
|
223
|
+
for predicted_init_quantum in self.predicted.init_quanta.root:
|
|
224
|
+
self.existing_init_outputs[predicted_init_quantum.quantum_id] = set()
|
|
225
|
+
self.comms.check_for_cancel()
|
|
226
|
+
self.comms.log.info("Generating integer indexes and identifying outputs.")
|
|
227
|
+
self._populate_indices_and_outputs()
|
|
228
|
+
self.comms.check_for_cancel()
|
|
229
|
+
self._populate_xgraph_and_inputs()
|
|
230
|
+
self.comms.check_for_cancel()
|
|
231
|
+
self.comms.log_progress(
|
|
232
|
+
# We add one here for 'packages', which we do ingest but don't
|
|
233
|
+
# record provenance for.
|
|
234
|
+
logging.INFO,
|
|
235
|
+
f"Graph has {len(self.output_dataset_ids) + 1} predicted output dataset(s).",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _populate_indices_and_outputs(self) -> None:
|
|
239
|
+
all_uuids = set(self.predicted.quantum_datasets.keys())
|
|
240
|
+
for quantum in self.comms.periodically_check_for_cancel(
|
|
241
|
+
itertools.chain(
|
|
242
|
+
self.predicted.init_quanta.root,
|
|
243
|
+
self.predicted.quantum_datasets.values(),
|
|
244
|
+
)
|
|
245
|
+
):
|
|
246
|
+
if not quantum.task_label:
|
|
247
|
+
# Skip the 'packages' producer quantum.
|
|
248
|
+
continue
|
|
249
|
+
all_uuids.update(quantum.iter_input_dataset_ids())
|
|
250
|
+
self.output_dataset_ids.update(quantum.iter_output_dataset_ids())
|
|
251
|
+
all_uuids.update(self.output_dataset_ids)
|
|
252
|
+
self.indices = {
|
|
253
|
+
node_id: node_index
|
|
254
|
+
for node_index, node_id in self.comms.periodically_check_for_cancel(
|
|
255
|
+
enumerate(sorted(all_uuids, key=operator.attrgetter("int")))
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
def _populate_xgraph_and_inputs(self) -> None:
|
|
260
|
+
for predicted_quantum in self.comms.periodically_check_for_cancel(
|
|
261
|
+
itertools.chain(
|
|
262
|
+
self.predicted.init_quanta.root,
|
|
263
|
+
self.predicted.quantum_datasets.values(),
|
|
264
|
+
)
|
|
265
|
+
):
|
|
266
|
+
if not predicted_quantum.task_label:
|
|
267
|
+
# Skip the 'packages' producer quantum.
|
|
268
|
+
continue
|
|
269
|
+
for predicted_input in itertools.chain.from_iterable(predicted_quantum.inputs.values()):
|
|
270
|
+
self.xgraph.add_edge(predicted_input.dataset_id, predicted_quantum.quantum_id)
|
|
271
|
+
if predicted_input.dataset_id not in self.output_dataset_ids:
|
|
272
|
+
self.overall_inputs.setdefault(predicted_input.dataset_id, predicted_input)
|
|
273
|
+
for predicted_output in itertools.chain.from_iterable(predicted_quantum.outputs.values()):
|
|
274
|
+
self.xgraph.add_edge(predicted_quantum.quantum_id, predicted_output.dataset_id)
|
|
74
275
|
|
|
75
276
|
@staticmethod
|
|
76
277
|
def run(predicted_path: str, comms: WriterCommunicator) -> None:
|
|
@@ -86,7 +287,7 @@ class Writer:
|
|
|
86
287
|
Notes
|
|
87
288
|
-----
|
|
88
289
|
This method is designed to run as the ``target`` in
|
|
89
|
-
`
|
|
290
|
+
`WorkerContext.make_worker`.
|
|
90
291
|
"""
|
|
91
292
|
with comms:
|
|
92
293
|
writer = Writer(predicted_path, comms)
|
|
@@ -94,59 +295,52 @@ class Writer:
|
|
|
94
295
|
|
|
95
296
|
def loop(self) -> None:
|
|
96
297
|
"""Run the main loop for the writer."""
|
|
97
|
-
|
|
298
|
+
data_writers: _DataWriters | None = None
|
|
98
299
|
if not self.comms.config.zstd_dict_size:
|
|
99
|
-
|
|
300
|
+
data_writers = self.make_data_writers()
|
|
100
301
|
self.comms.log.info("Polling for write requests from scanners.")
|
|
101
302
|
for request in self.comms.poll():
|
|
102
|
-
if
|
|
303
|
+
if data_writers is None:
|
|
103
304
|
self.pending_compression_training.append(request)
|
|
104
305
|
if len(self.pending_compression_training) >= self.comms.config.zstd_dict_n_inputs:
|
|
105
|
-
|
|
306
|
+
data_writers = self.make_data_writers()
|
|
106
307
|
else:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
self.comms.log.info("Writing init outputs.")
|
|
112
|
-
qg_writer.write_init_outputs(assume_existence=False)
|
|
308
|
+
self.process_request(request, data_writers)
|
|
309
|
+
if data_writers is None:
|
|
310
|
+
data_writers = self.make_data_writers()
|
|
311
|
+
self.write_init_outputs(data_writers)
|
|
113
312
|
|
|
114
|
-
def
|
|
313
|
+
def make_data_writers(self) -> _DataWriters:
|
|
115
314
|
"""Make a compression dictionary, open the low-level writers, and
|
|
116
315
|
write any accumulated scans that were needed to make the compression
|
|
117
316
|
dictionary.
|
|
118
317
|
|
|
119
318
|
Returns
|
|
120
319
|
-------
|
|
121
|
-
|
|
320
|
+
data_writers : `_DataWriters`
|
|
122
321
|
Low-level writers struct.
|
|
123
322
|
"""
|
|
124
323
|
cdict = self.make_compression_dictionary()
|
|
125
324
|
self.comms.send_compression_dict(cdict.as_bytes())
|
|
126
|
-
assert self.comms.config.
|
|
127
|
-
self.comms.log.info("Opening output files
|
|
128
|
-
|
|
129
|
-
self.comms
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
zstd_level=self.comms.config.zstd_level,
|
|
325
|
+
assert self.comms.config.output_path is not None
|
|
326
|
+
self.comms.log.info("Opening output files.")
|
|
327
|
+
data_writers = _DataWriters(
|
|
328
|
+
self.comms,
|
|
329
|
+
self.predicted,
|
|
330
|
+
self.indices,
|
|
331
|
+
compressor=zstandard.ZstdCompressor(self.comms.config.zstd_level, cdict),
|
|
134
332
|
cdict_data=cdict.as_bytes(),
|
|
135
|
-
loop_wrapper=self.comms.periodically_check_for_cancel,
|
|
136
|
-
log=self.comms.log,
|
|
137
333
|
)
|
|
138
334
|
self.comms.check_for_cancel()
|
|
139
335
|
self.comms.log.info("Compressing and writing queued scan requests.")
|
|
140
336
|
for request in self.pending_compression_training:
|
|
141
|
-
|
|
142
|
-
self.comms.report_write()
|
|
337
|
+
self.process_request(request, data_writers)
|
|
143
338
|
del self.pending_compression_training
|
|
144
339
|
self.comms.check_for_cancel()
|
|
145
|
-
self.
|
|
146
|
-
|
|
147
|
-
qg_writer.write_packages()
|
|
340
|
+
self.write_overall_inputs(data_writers)
|
|
341
|
+
self.write_packages(data_writers)
|
|
148
342
|
self.comms.log.info("Returning to write request loop.")
|
|
149
|
-
return
|
|
343
|
+
return data_writers
|
|
150
344
|
|
|
151
345
|
def make_compression_dictionary(self) -> zstandard.ZstdCompressionDict:
|
|
152
346
|
"""Make the compression dictionary.
|
|
@@ -182,3 +376,126 @@ class Writer:
|
|
|
182
376
|
training_inputs.append(write_request.metadata)
|
|
183
377
|
training_inputs.append(write_request.logs)
|
|
184
378
|
return zstandard.train_dictionary(self.comms.config.zstd_dict_size, training_inputs)
|
|
379
|
+
|
|
380
|
+
def write_init_outputs(self, data_writers: _DataWriters) -> None:
|
|
381
|
+
"""Write provenance for init-output datasets and init-quanta.
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
data_writers : `_DataWriters`
|
|
386
|
+
Low-level writers struct.
|
|
387
|
+
"""
|
|
388
|
+
self.comms.log.info("Writing init outputs.")
|
|
389
|
+
init_quanta = ProvenanceInitQuantaModel()
|
|
390
|
+
for predicted_init_quantum in self.predicted.init_quanta.root:
|
|
391
|
+
if not predicted_init_quantum.task_label:
|
|
392
|
+
# Skip the 'packages' producer quantum.
|
|
393
|
+
continue
|
|
394
|
+
existing_outputs = self.existing_init_outputs[predicted_init_quantum.quantum_id]
|
|
395
|
+
for predicted_output in itertools.chain.from_iterable(predicted_init_quantum.outputs.values()):
|
|
396
|
+
provenance_output = ProvenanceDatasetModel.from_predicted(
|
|
397
|
+
predicted_output,
|
|
398
|
+
producer=predicted_init_quantum.quantum_id,
|
|
399
|
+
consumers=self.xgraph.successors(predicted_output.dataset_id),
|
|
400
|
+
)
|
|
401
|
+
provenance_output.produced = predicted_output.dataset_id in existing_outputs
|
|
402
|
+
data_writers.datasets.write_model(
|
|
403
|
+
provenance_output.dataset_id, provenance_output, data_writers.compressor
|
|
404
|
+
)
|
|
405
|
+
init_quanta.root.append(ProvenanceInitQuantumModel.from_predicted(predicted_init_quantum))
|
|
406
|
+
data_writers.graph.write_single_model("init_quanta", init_quanta)
|
|
407
|
+
|
|
408
|
+
def write_overall_inputs(self, data_writers: _DataWriters) -> None:
|
|
409
|
+
"""Write provenance for overall-input datasets.
|
|
410
|
+
|
|
411
|
+
Parameters
|
|
412
|
+
----------
|
|
413
|
+
data_writers : `_DataWriters`
|
|
414
|
+
Low-level writers struct.
|
|
415
|
+
"""
|
|
416
|
+
self.comms.log.info("Writing overall inputs.")
|
|
417
|
+
for predicted_input in self.comms.periodically_check_for_cancel(self.overall_inputs.values()):
|
|
418
|
+
if predicted_input.dataset_id not in data_writers.datasets.addresses:
|
|
419
|
+
data_writers.datasets.write_model(
|
|
420
|
+
predicted_input.dataset_id,
|
|
421
|
+
ProvenanceDatasetModel.from_predicted(
|
|
422
|
+
predicted_input,
|
|
423
|
+
producer=None,
|
|
424
|
+
consumers=self.xgraph.successors(predicted_input.dataset_id),
|
|
425
|
+
),
|
|
426
|
+
data_writers.compressor,
|
|
427
|
+
)
|
|
428
|
+
del self.overall_inputs
|
|
429
|
+
|
|
430
|
+
@staticmethod
|
|
431
|
+
def write_packages(data_writers: _DataWriters) -> None:
|
|
432
|
+
"""Write package version information to the provenance graph.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
data_writers : `_DataWriters`
|
|
437
|
+
Low-level writers struct.
|
|
438
|
+
"""
|
|
439
|
+
packages = Packages.fromSystem(include_all=True)
|
|
440
|
+
data = packages.toBytes("json")
|
|
441
|
+
data_writers.graph.write_single_block("packages", data)
|
|
442
|
+
|
|
443
|
+
def process_request(self, request: WriteRequest, data_writers: _DataWriters) -> None:
|
|
444
|
+
"""Process a `WriteRequest` into `_ScanData`.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
request : `WriteRequest`
|
|
449
|
+
Result of a quantum scan.
|
|
450
|
+
data_writers : `_DataWriters`
|
|
451
|
+
Low-level writers struct.
|
|
452
|
+
"""
|
|
453
|
+
if (existing_init_outputs := self.existing_init_outputs.get(request.quantum_id)) is not None:
|
|
454
|
+
self.comms.log.debug("Handling init-output scan for %s.", request.quantum_id)
|
|
455
|
+
existing_init_outputs.update(request.existing_outputs)
|
|
456
|
+
self.comms.report_write()
|
|
457
|
+
return
|
|
458
|
+
self.comms.log.debug("Handling quantum scan for %s.", request.quantum_id)
|
|
459
|
+
predicted_quantum = self.predicted.quantum_datasets[request.quantum_id]
|
|
460
|
+
outputs: dict[uuid.UUID, bytes] = {}
|
|
461
|
+
for predicted_output in itertools.chain.from_iterable(predicted_quantum.outputs.values()):
|
|
462
|
+
provenance_output = ProvenanceDatasetModel.from_predicted(
|
|
463
|
+
predicted_output,
|
|
464
|
+
producer=predicted_quantum.quantum_id,
|
|
465
|
+
consumers=self.xgraph.successors(predicted_output.dataset_id),
|
|
466
|
+
)
|
|
467
|
+
provenance_output.produced = provenance_output.dataset_id in request.existing_outputs
|
|
468
|
+
outputs[provenance_output.dataset_id] = data_writers.compressor.compress(
|
|
469
|
+
provenance_output.model_dump_json().encode()
|
|
470
|
+
)
|
|
471
|
+
if not request.quantum:
|
|
472
|
+
request.quantum = (
|
|
473
|
+
ProvenanceQuantumModel.from_predicted(predicted_quantum).model_dump_json().encode()
|
|
474
|
+
)
|
|
475
|
+
if request.is_compressed:
|
|
476
|
+
request.quantum = data_writers.compressor.compress(request.quantum)
|
|
477
|
+
if not request.is_compressed:
|
|
478
|
+
request.quantum = data_writers.compressor.compress(request.quantum)
|
|
479
|
+
if request.metadata:
|
|
480
|
+
request.metadata = data_writers.compressor.compress(request.metadata)
|
|
481
|
+
if request.logs:
|
|
482
|
+
request.logs = data_writers.compressor.compress(request.logs)
|
|
483
|
+
self.comms.log.debug("Writing quantum %s.", request.quantum_id)
|
|
484
|
+
data_writers.quanta.write_bytes(request.quantum_id, request.quantum)
|
|
485
|
+
for dataset_id, dataset_data in outputs.items():
|
|
486
|
+
data_writers.datasets.write_bytes(dataset_id, dataset_data)
|
|
487
|
+
if request.metadata:
|
|
488
|
+
(metadata_output,) = predicted_quantum.outputs[acc.METADATA_OUTPUT_CONNECTION_NAME]
|
|
489
|
+
address = data_writers.metadata.write_bytes(request.quantum_id, request.metadata)
|
|
490
|
+
data_writers.metadata.addresses[metadata_output.dataset_id] = address
|
|
491
|
+
if request.logs:
|
|
492
|
+
(log_output,) = predicted_quantum.outputs[acc.LOG_OUTPUT_CONNECTION_NAME]
|
|
493
|
+
address = data_writers.logs.write_bytes(request.quantum_id, request.logs)
|
|
494
|
+
data_writers.logs.addresses[log_output.dataset_id] = address
|
|
495
|
+
# We shouldn't need this predicted quantum anymore; delete it in the
|
|
496
|
+
# hopes that'll free up some memory.
|
|
497
|
+
del self.predicted.quantum_datasets[request.quantum_id]
|
|
498
|
+
self.comms.report_write()
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
_T = TypeVar("_T")
|
|
@@ -37,16 +37,12 @@ from typing import IO, ClassVar, Generic, TypeVar
|
|
|
37
37
|
from ..pipeline_graph import NodeType
|
|
38
38
|
from ._common import BaseQuantumGraph, BipartiteEdgeInfo, DatasetInfo, QuantumInfo
|
|
39
39
|
|
|
40
|
-
# We use the old generic syntax in this module because for some reason the new
|
|
41
|
-
# one confused Sphinx (or one of its plugins), even though it seems fine with
|
|
42
|
-
# it in other places. We can try again when we're ready to remove types from
|
|
43
|
-
# the docstrings of annotated functions, in case that matters.
|
|
44
40
|
_G = TypeVar("_G", bound=BaseQuantumGraph, contravariant=True)
|
|
45
41
|
_Q = TypeVar("_Q", bound=QuantumInfo, contravariant=True)
|
|
46
42
|
_D = TypeVar("_D", bound=DatasetInfo, contravariant=True)
|
|
47
43
|
|
|
48
44
|
|
|
49
|
-
class QuantumGraphVisualizer(Generic[_G, _Q, _D]):
|
|
45
|
+
class QuantumGraphVisualizer(Generic[_G, _Q, _D]):
|
|
50
46
|
"""A base class for exporting quantum graphs to graph-visualization
|
|
51
47
|
languages.
|
|
52
48
|
|
|
@@ -380,6 +380,8 @@ class QuantumGraphBuilder(ABC):
|
|
|
380
380
|
|
|
381
381
|
Parameters
|
|
382
382
|
----------
|
|
383
|
+
metadata : `~collections.abc.Mapping`, optional
|
|
384
|
+
Flexible metadata to add to the quantum graph.
|
|
383
385
|
attach_datastore_records : `bool`, optional
|
|
384
386
|
Whether to include datastore records in the graph. Required for
|
|
385
387
|
`lsst.daf.butler.QuantumBackedButler` execution.
|
|
@@ -678,26 +680,6 @@ class QuantumGraphBuilder(ABC):
|
|
|
678
680
|
"Dropping task %s because no quanta remain%s.", task_node.label, message_parenthetical
|
|
679
681
|
)
|
|
680
682
|
skeleton.remove_task(task_node.label)
|
|
681
|
-
if len(no_work_quanta) > len(remaining_quanta):
|
|
682
|
-
only_overall_inputs = self._get_task_inputs_if_overall_only(task_node)
|
|
683
|
-
self.log.warning(
|
|
684
|
-
"More than half of %s quanta had no work to do given available inputs.\n"
|
|
685
|
-
"A query constraint on one of %s may yield a much faster build.",
|
|
686
|
-
task_node.label,
|
|
687
|
-
only_overall_inputs,
|
|
688
|
-
)
|
|
689
|
-
|
|
690
|
-
def _get_task_inputs_if_overall_only(self, task_node: TaskNode) -> list[str] | None:
|
|
691
|
-
"""If the given task consumes only overall-inputs, return their names.
|
|
692
|
-
Otherwise return `None`.
|
|
693
|
-
"""
|
|
694
|
-
result: list[str] = []
|
|
695
|
-
for read_edge in task_node.inputs.values():
|
|
696
|
-
if self._pipeline_graph.producer_of(read_edge.parent_dataset_type_name) is None:
|
|
697
|
-
result.append(read_edge.parent_dataset_type_name)
|
|
698
|
-
else:
|
|
699
|
-
return None
|
|
700
|
-
return result
|
|
701
683
|
|
|
702
684
|
def _skip_quantum_if_metadata_exists(
|
|
703
685
|
self, task_node: TaskNode, quantum_key: QuantumKey, skeleton: QuantumGraphSkeleton
|
|
@@ -905,6 +887,11 @@ class QuantumGraphBuilder(ABC):
|
|
|
905
887
|
Identifier for this quantum in the graph.
|
|
906
888
|
skeleton : `.quantum_graph_skeleton.QuantumGraphSkeleton`
|
|
907
889
|
Preliminary quantum graph, to be modified in-place.
|
|
890
|
+
skypix_bounds_builder : `~prerequisite_helpers.SkyPixBoundsBuilder`
|
|
891
|
+
An object that accumulates the appropriate spatial bounds for a
|
|
892
|
+
quantum.
|
|
893
|
+
timespan_builder : `~prerequisite_helpers.TimespanBuilder`
|
|
894
|
+
An object that accumulates the appropriate timespan for a quantum.
|
|
908
895
|
|
|
909
896
|
Returns
|
|
910
897
|
-------
|
|
@@ -1157,7 +1144,7 @@ class QuantumGraphBuilder(ABC):
|
|
|
1157
1144
|
"outputs" attributes on all quantum nodes, as added by
|
|
1158
1145
|
`_resolve_task_quanta`, as well as a "datastore_records" attribute
|
|
1159
1146
|
as added by `_attach_datastore_records`.
|
|
1160
|
-
metadata :
|
|
1147
|
+
metadata : `Mapping`
|
|
1161
1148
|
Flexible metadata to add to the graph.
|
|
1162
1149
|
|
|
1163
1150
|
Returns
|