lsst-pipe-base 29.2025.4600__tar.gz → 29.2025.4700__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.
Files changed (183) hide show
  1. {lsst_pipe_base-29.2025.4600/python/lsst_pipe_base.egg-info → lsst_pipe_base-29.2025.4700}/PKG-INFO +1 -1
  2. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/_common.py +15 -1
  3. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/_multiblock.py +14 -39
  4. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/_predicted.py +77 -73
  5. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/_provenance.py +73 -144
  6. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_communicators.py +10 -10
  7. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_scanner.py +88 -60
  8. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_structs.py +36 -19
  9. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_supervisor.py +7 -10
  10. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_writer.py +55 -144
  11. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph_builder.py +0 -1
  12. lsst_pipe_base-29.2025.4700/python/lsst/pipe/base/version.py +2 -0
  13. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700/python/lsst_pipe_base.egg-info}/PKG-INFO +1 -1
  14. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_aggregator.py +27 -2
  15. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_predicted_qg.py +2 -0
  16. lsst_pipe_base-29.2025.4600/python/lsst/pipe/base/version.py +0 -2
  17. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/COPYRIGHT +0 -0
  18. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/LICENSE +0 -0
  19. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/MANIFEST.in +0 -0
  20. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/README.md +0 -0
  21. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/bsd_license.txt +0 -0
  22. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/CHANGES.rst +0 -0
  23. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/creating-a-pipeline.rst +0 -0
  24. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/creating-a-pipelinetask.rst +0 -0
  25. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/creating-a-task.rst +0 -0
  26. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/index.rst +0 -0
  27. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/task-framework-overview.rst +0 -0
  28. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/task-retargeting-howto.rst +0 -0
  29. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/testing-a-pipeline-task.rst +0 -0
  30. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/testing-pipelines-with-mocks.rst +0 -0
  31. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/doc/lsst.pipe.base/working-with-pipeline-graphs.rst +0 -0
  32. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/gpl-v3.0.txt +0 -0
  33. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/pyproject.toml +0 -0
  34. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/__init__.py +0 -0
  35. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/__init__.py +0 -0
  36. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/__init__.py +0 -0
  37. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/_datasetQueryConstraints.py +0 -0
  38. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/_dataset_handle.py +0 -0
  39. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/_instrument.py +0 -0
  40. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/_observation_dimension_packer.py +0 -0
  41. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/_quantumContext.py +0 -0
  42. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/_status.py +0 -0
  43. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/_task_metadata.py +0 -0
  44. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/all_dimensions_quantum_graph_builder.py +0 -0
  45. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/automatic_connection_constants.py +0 -0
  46. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/caching_limited_butler.py +0 -0
  47. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/cli/__init__.py +0 -0
  48. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/cli/_get_cli_subcommands.py +0 -0
  49. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/cli/cmd/__init__.py +0 -0
  50. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/cli/cmd/commands.py +0 -0
  51. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/cli/opt/__init__.py +0 -0
  52. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/cli/opt/arguments.py +0 -0
  53. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/cli/opt/options.py +0 -0
  54. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/config.py +0 -0
  55. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/configOverrides.py +0 -0
  56. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/connectionTypes.py +0 -0
  57. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/connections.py +0 -0
  58. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/dot_tools.py +0 -0
  59. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/exec_fixup_data_id.py +0 -0
  60. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/execution_graph_fixup.py +0 -0
  61. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/execution_reports.py +0 -0
  62. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/formatters/__init__.py +0 -0
  63. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/formatters/pexConfig.py +0 -0
  64. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph/__init__.py +0 -0
  65. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph/_implDetails.py +0 -0
  66. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph/_loadHelpers.py +0 -0
  67. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph/_versionDeserializers.py +0 -0
  68. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph/graph.py +0 -0
  69. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph/graphSummary.py +0 -0
  70. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph/quantumNode.py +0 -0
  71. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/graph_walker.py +0 -0
  72. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/log_capture.py +0 -0
  73. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/mermaid_tools.py +0 -0
  74. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/mp_graph_executor.py +0 -0
  75. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline.py +0 -0
  76. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipelineIR.py +0 -0
  77. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipelineTask.py +0 -0
  78. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/__init__.py +0 -0
  79. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/__main__.py +0 -0
  80. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_dataset_types.py +0 -0
  81. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_edges.py +0 -0
  82. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_exceptions.py +0 -0
  83. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_mapping_views.py +0 -0
  84. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_nodes.py +0 -0
  85. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_pipeline_graph.py +0 -0
  86. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_task_subsets.py +0 -0
  87. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/_tasks.py +0 -0
  88. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/expressions.py +0 -0
  89. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/io.py +0 -0
  90. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/__init__.py +0 -0
  91. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_dot.py +0 -0
  92. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_formatting.py +0 -0
  93. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_layout.py +0 -0
  94. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_merge.py +0 -0
  95. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_mermaid.py +0 -0
  96. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_options.py +0 -0
  97. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_printer.py +0 -0
  98. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_show.py +0 -0
  99. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/pipeline_graph/visualization/_status_annotator.py +0 -0
  100. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/prerequisite_helpers.py +0 -0
  101. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/py.typed +0 -0
  102. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/__init__.py +0 -0
  103. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/__init__.py +0 -0
  104. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_config.py +0 -0
  105. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_ingester.py +0 -0
  106. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/aggregator/_progress.py +0 -0
  107. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph/visualization.py +0 -0
  108. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph_executor.py +0 -0
  109. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_graph_skeleton.py +0 -0
  110. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_provenance_graph.py +0 -0
  111. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/quantum_reports.py +0 -0
  112. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/resource_usage.py +0 -0
  113. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/script/__init__.py +0 -0
  114. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/script/register_instrument.py +0 -0
  115. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/script/retrieve_artifacts_for_quanta.py +0 -0
  116. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/script/transfer_from_graph.py +0 -0
  117. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/script/utils.py +0 -0
  118. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/script/zip_from_graph.py +0 -0
  119. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/separable_pipeline_executor.py +0 -0
  120. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/simple_pipeline_executor.py +0 -0
  121. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/single_quantum_executor.py +0 -0
  122. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/struct.py +0 -0
  123. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/task.py +0 -0
  124. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/taskFactory.py +0 -0
  125. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/testUtils.py +0 -0
  126. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/__init__.py +0 -0
  127. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/in_memory_limited_butler.py +0 -0
  128. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/mocks/__init__.py +0 -0
  129. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/mocks/_data_id_match.py +0 -0
  130. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/mocks/_pipeline_task.py +0 -0
  131. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/mocks/_repo.py +0 -0
  132. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/mocks/_storage_class.py +0 -0
  133. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/no_dimensions.py +0 -0
  134. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/pipelineStepTester.py +0 -0
  135. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/simpleQGraph.py +0 -0
  136. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/tests/util.py +0 -0
  137. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst/pipe/base/utils.py +0 -0
  138. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst_pipe_base.egg-info/SOURCES.txt +0 -0
  139. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst_pipe_base.egg-info/dependency_links.txt +0 -0
  140. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst_pipe_base.egg-info/entry_points.txt +0 -0
  141. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst_pipe_base.egg-info/requires.txt +0 -0
  142. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst_pipe_base.egg-info/top_level.txt +0 -0
  143. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/python/lsst_pipe_base.egg-info/zip-safe +0 -0
  144. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/setup.cfg +0 -0
  145. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_adjust_all_quanta.py +0 -0
  146. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_caching_limited_butler.py +0 -0
  147. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_cliCmdRegisterInstrument.py +0 -0
  148. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_configOverrides.py +0 -0
  149. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_config_formatter.py +0 -0
  150. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_connections.py +0 -0
  151. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_dataid_match.py +0 -0
  152. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_dataset_handle.py +0 -0
  153. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_dot_tools.py +0 -0
  154. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_dynamic_connections.py +0 -0
  155. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_execution_reports.py +0 -0
  156. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_execution_storage_class_conversion.py +0 -0
  157. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_graphBuilder.py +0 -0
  158. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_graph_walker.py +0 -0
  159. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_init_output_run.py +0 -0
  160. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_instrument.py +0 -0
  161. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_mermaid.py +0 -0
  162. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_mp_graph_executor.py +0 -0
  163. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_pipeline.py +0 -0
  164. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_pipelineIR.py +0 -0
  165. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_pipelineLoadSubset.py +0 -0
  166. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_pipelineTask.py +0 -0
  167. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_pipeline_graph.py +0 -0
  168. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_pipeline_graph_expressions.py +0 -0
  169. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_qg_builder_dimensions.py +0 -0
  170. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_quantumGraph.py +0 -0
  171. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_quantum_provenance_graph.py +0 -0
  172. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_quantum_reports.py +0 -0
  173. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_quantum_success_caveats.py +0 -0
  174. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_script_utils.py +0 -0
  175. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_separable_pipeline_executor.py +0 -0
  176. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_simple_pipeline_executor.py +0 -0
  177. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_single_quantum_executor.py +0 -0
  178. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_struct.py +0 -0
  179. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_task.py +0 -0
  180. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_task_factory.py +0 -0
  181. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_taskmetadata.py +0 -0
  182. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_testUtils.py +0 -0
  183. {lsst_pipe_base-29.2025.4600 → lsst_pipe_base-29.2025.4700}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-pipe-base
3
- Version: 29.2025.4600
3
+ Version: 29.2025.4700
4
4
  Summary: Pipeline infrastructure for the Rubin Science Pipelines.
5
5
  Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
6
6
  License-Expression: BSD-3-Clause OR GPL-3.0-or-later
@@ -28,6 +28,7 @@
28
28
  from __future__ import annotations
29
29
 
30
30
  __all__ = (
31
+ "FORMAT_VERSION",
31
32
  "BaseQuantumGraph",
32
33
  "BaseQuantumGraphReader",
33
34
  "BipartiteEdgeInfo",
@@ -92,6 +93,19 @@ DataCoordinateValues: TypeAlias = list[DataIdValue]
92
93
 
93
94
  _T = TypeVar("_T", bound=pydantic.BaseModel)
94
95
 
96
+ FORMAT_VERSION: int = 1
97
+ """
98
+ File format version number for new files.
99
+
100
+ This applies to both predicted and provenance QGs, since they usually change
101
+ in concert.
102
+
103
+ CHANGELOG:
104
+
105
+ - 0: Initial version.
106
+ - 1: Switched from internal integer IDs to UUIDs in all models.
107
+ """
108
+
95
109
 
96
110
  class IncompleteQuantumGraphError(RuntimeError):
97
111
  pass
@@ -100,7 +114,7 @@ class IncompleteQuantumGraphError(RuntimeError):
100
114
  class HeaderModel(pydantic.BaseModel):
101
115
  """Data model for the header of a quantum graph file."""
102
116
 
103
- version: int = 0
117
+ version: int = FORMAT_VERSION
104
118
  """File format / data model version number."""
105
119
 
106
120
  graph_type: str = ""
@@ -323,10 +323,11 @@ class AddressReader:
323
323
  rows: dict[uuid.UUID, AddressRow] = dataclasses.field(default_factory=dict)
324
324
  """Rows that have already been read."""
325
325
 
326
- rows_by_index: dict[int, AddressRow] = dataclasses.field(default_factory=dict)
327
- """Rows that have already been read, keyed by integer index."""
328
-
329
326
  pages: list[AddressPage] = dataclasses.field(default_factory=list)
327
+ """Descriptions of the file offsets and integer row indexes of pages and
328
+ flags for whether they have been read already.
329
+ """
330
+
330
331
  page_bounds: dict[int, PageBounds] = dataclasses.field(default_factory=dict)
331
332
  """Mapping from page index to page boundary information."""
332
333
 
@@ -502,32 +503,23 @@ class AddressReader:
502
503
  self.pages.clear()
503
504
  return self.rows
504
505
 
505
- def find(self, key: uuid.UUID | int) -> AddressRow:
506
+ def find(self, key: uuid.UUID) -> AddressRow:
506
507
  """Read the row for the given UUID or integer index.
507
508
 
508
509
  Parameters
509
510
  ----------
510
- key : `uuid.UUID` or `int`
511
- UUID or integer index to find.
511
+ key : `uuid.UUID`
512
+ UUID to find.
512
513
 
513
514
  Returns
514
515
  -------
515
516
  row : `AddressRow`
516
517
  Addresses for the given UUID.
517
518
  """
518
- match key:
519
- case uuid.UUID():
520
- return self._find_uuid(key)
521
- case int():
522
- return self._find_index(key)
523
- case _:
524
- raise TypeError(f"Invalid argument: {key}.")
525
-
526
- def _find_uuid(self, target: uuid.UUID) -> AddressRow:
527
- if (row := self.rows.get(target)) is not None:
519
+ if (row := self.rows.get(key)) is not None:
528
520
  return row
529
521
  if self.n_rows == 0 or not self.pages:
530
- raise LookupError(f"Address for {target} not found.")
522
+ raise LookupError(f"Address for {key} not found.")
531
523
 
532
524
  # Use a binary search to find the page containing the target UUID.
533
525
  left = 0
@@ -535,35 +527,19 @@ class AddressReader:
535
527
  while left <= right:
536
528
  mid = left + ((right - left) // 2)
537
529
  self._read_page(mid)
538
- if (row := self.rows.get(target)) is not None:
530
+ if (row := self.rows.get(key)) is not None:
539
531
  return row
540
532
  bounds = self.page_bounds[mid]
541
- if target.int < bounds.uuid_int_begin:
533
+ if key.int < bounds.uuid_int_begin:
542
534
  right = mid - 1
543
- elif target.int > bounds.uuid_int_end:
535
+ elif key.int > bounds.uuid_int_end:
544
536
  left = mid + 1
545
537
  else:
546
538
  # Should have been on this page, but it wasn't.
547
- raise LookupError(f"Address for {target} not found.")
539
+ raise LookupError(f"Address for {key} not found.")
548
540
 
549
541
  # Ran out of pages to search.
550
- raise LookupError(f"Address for {target} not found.")
551
-
552
- def _find_index(self, target: int) -> AddressRow:
553
- # First shortcut if we've already loaded this row.
554
- if (row := self.rows_by_index.get(target)) is not None:
555
- return row
556
- if target < 0 or target >= self.n_rows:
557
- raise LookupError(f"Address for index {target} not found.")
558
- # Since all indexes should be present, we can predict the right page
559
- # exactly.
560
- page_index = target // self.rows_per_page
561
- self._read_page(page_index)
562
- try:
563
- return self.rows_by_index[target]
564
- except KeyError:
565
- _LOG.debug("Index find failed: %s should have been in page %s.", target, page_index)
566
- raise LookupError(f"Address for {target} not found.") from None
542
+ raise LookupError(f"Address for {key} not found.")
567
543
 
568
544
  def _read_page(self, page_index: int, page_stream: BytesIO | None = None) -> bool:
569
545
  page = self.pages[page_index]
@@ -586,7 +562,6 @@ class AddressReader:
586
562
  def _read_row(self, page_stream: BytesIO) -> AddressRow:
587
563
  row = AddressRow.read(page_stream, self.n_addresses, self.int_size)
588
564
  self.rows[row.key] = row
589
- self.rows_by_index[row.index] = row
590
565
  _LOG.debug("Read address row %s.", row)
591
566
  return row
592
567
 
@@ -43,7 +43,6 @@ __all__ = (
43
43
  import dataclasses
44
44
  import itertools
45
45
  import logging
46
- import operator
47
46
  import sys
48
47
  import uuid
49
48
  import warnings
@@ -89,6 +88,7 @@ from ..pipeline_graph import (
89
88
  log_config_mismatch,
90
89
  )
91
90
  from ._common import (
91
+ FORMAT_VERSION,
92
92
  BaseQuantumGraph,
93
93
  BaseQuantumGraphReader,
94
94
  BaseQuantumGraphWriter,
@@ -103,7 +103,7 @@ from ._common import (
103
103
  QuantumInfo,
104
104
  TaskLabel,
105
105
  )
106
- from ._multiblock import DEFAULT_PAGE_SIZE, MultiblockReader, MultiblockWriter
106
+ from ._multiblock import DEFAULT_PAGE_SIZE, AddressRow, MultiblockReader, MultiblockWriter
107
107
 
108
108
  if TYPE_CHECKING:
109
109
  from ..config import PipelineTaskConfig
@@ -115,7 +115,7 @@ _LOG = logging.getLogger(__name__)
115
115
  _T = TypeVar("_T", bound=pydantic.BaseModel)
116
116
 
117
117
 
118
- class PredictedThinQuantumModel(pydantic.BaseModel):
118
+ class _PredictedThinQuantumModelV0(pydantic.BaseModel):
119
119
  """Data model for a quantum data ID and internal integer ID in a predicted
120
120
  quantum graph.
121
121
  """
@@ -126,6 +126,18 @@ class PredictedThinQuantumModel(pydantic.BaseModel):
126
126
  data_coordinate: DataCoordinateValues = pydantic.Field(default_factory=list)
127
127
  """Full (required and implied) data coordinate values for this quantum."""
128
128
 
129
+
130
+ class PredictedThinQuantumModel(pydantic.BaseModel):
131
+ """Data model for a quantum data ID and UUID in a predicted
132
+ quantum graph.
133
+ """
134
+
135
+ quantum_id: uuid.UUID
136
+ """Universally unique ID for this quantum."""
137
+
138
+ data_coordinate: DataCoordinateValues = pydantic.Field(default_factory=list)
139
+ """Full (required and implied) data coordinate values for this quantum."""
140
+
129
141
  # Work around the fact that Sphinx chokes on Pydantic docstring formatting,
130
142
  # when we inherit those docstrings in our public classes.
131
143
  if "sphinx" in sys.modules and not TYPE_CHECKING:
@@ -172,17 +184,45 @@ class PredictedThinQuantumModel(pydantic.BaseModel):
172
184
  return super().model_validate_strings(*args, **kwargs)
173
185
 
174
186
 
175
- class PredictedThinGraphModel(pydantic.BaseModel):
187
+ class _PredictedThinGraphModelV0(pydantic.BaseModel):
176
188
  """Data model for the predicted quantum graph component that maps each
177
189
  task label to the data IDs and internal integer IDs of its quanta.
178
190
  """
179
191
 
180
- quanta: dict[TaskLabel, list[PredictedThinQuantumModel]] = pydantic.Field(default_factory=dict)
192
+ quanta: dict[TaskLabel, list[_PredictedThinQuantumModelV0]] = pydantic.Field(default_factory=dict)
181
193
  """Minimal descriptions of all quanta, grouped by task label."""
182
194
 
183
195
  edges: list[tuple[QuantumIndex, QuantumIndex]] = pydantic.Field(default_factory=list)
184
196
  """Pairs of (predecessor, successor) internal integer quantum IDs."""
185
197
 
198
+ def _upgraded(self, address_rows: Mapping[uuid.UUID, AddressRow]) -> PredictedThinGraphModel:
199
+ """Convert to the v1+ model."""
200
+ uuid_by_index = {v.index: k for k, v in address_rows.items()}
201
+ return PredictedThinGraphModel(
202
+ quanta={
203
+ task_label: [
204
+ PredictedThinQuantumModel(
205
+ quantum_id=uuid_by_index[q.quantum_index], data_coordinate=q.data_coordinate
206
+ )
207
+ for q in quanta
208
+ ]
209
+ for task_label, quanta in self.quanta.items()
210
+ },
211
+ edges=[(uuid_by_index[index1], uuid_by_index[index2]) for index1, index2 in self.edges],
212
+ )
213
+
214
+
215
+ class PredictedThinGraphModel(pydantic.BaseModel):
216
+ """Data model for the predicted quantum graph component that maps each
217
+ task label to the data IDs and UUIDs of its quanta.
218
+ """
219
+
220
+ quanta: dict[TaskLabel, list[PredictedThinQuantumModel]] = pydantic.Field(default_factory=dict)
221
+ """Minimal descriptions of all quanta, grouped by task label."""
222
+
223
+ edges: list[tuple[uuid.UUID, uuid.UUID]] = pydantic.Field(default_factory=list)
224
+ """Pairs of (predecessor, successor) quantum IDs."""
225
+
186
226
  # Work around the fact that Sphinx chokes on Pydantic docstring formatting,
187
227
  # when we inherit those docstrings in our public classes.
188
228
  if "sphinx" in sys.modules and not TYPE_CHECKING:
@@ -673,7 +713,7 @@ class PredictedQuantumGraph(BaseQuantumGraph):
673
713
  self._add_init_quanta(components.init_quanta)
674
714
  self._quantum_datasets: dict[uuid.UUID, PredictedQuantumDatasetsModel] = {}
675
715
  self._expanded_data_ids: dict[DataCoordinate, DataCoordinate] = {}
676
- self._add_thin_graph(components.thin_graph, components.quantum_indices)
716
+ self._add_thin_graph(components.thin_graph)
677
717
  for quantum_datasets in components.quantum_datasets.values():
678
718
  self._add_quantum_datasets(quantum_datasets)
679
719
  if not components.thin_graph.edges:
@@ -710,19 +750,11 @@ class PredictedQuantumGraph(BaseQuantumGraph):
710
750
  quantum_datasets.task_label,
711
751
  )
712
752
 
713
- def _add_thin_graph(
714
- self, component: PredictedThinGraphModel, indices: Mapping[uuid.UUID, QuantumIndex]
715
- ) -> None:
716
- uuid_by_index = {v: k for k, v in indices.items()}
717
- for index1, index2 in component.edges:
718
- self._quantum_only_xgraph.add_edge(uuid_by_index[index1], uuid_by_index[index2])
753
+ def _add_thin_graph(self, component: PredictedThinGraphModel) -> None:
754
+ self._quantum_only_xgraph.add_edges_from(component.edges)
719
755
  for task_label, thin_quanta_for_task in component.quanta.items():
720
756
  for thin_quantum in thin_quanta_for_task:
721
- self._add_quantum(
722
- uuid_by_index[thin_quantum.quantum_index],
723
- task_label,
724
- thin_quantum.data_coordinate,
725
- )
757
+ self._add_quantum(thin_quantum.quantum_id, task_label, thin_quantum.data_coordinate)
726
758
 
727
759
  def _add_quantum_datasets(self, quantum_datasets: PredictedQuantumDatasetsModel) -> None:
728
760
  self._quantum_datasets[quantum_datasets.quantum_id] = quantum_datasets
@@ -1496,8 +1528,6 @@ class PredictedQuantumGraphComponents:
1496
1528
  thin_graph: PredictedThinGraphModel = dataclasses.field(default_factory=PredictedThinGraphModel)
1497
1529
  """A lightweight quantum-quantum DAG with task labels and data IDs only.
1498
1530
 
1499
- This uses internal integer IDs ("indexes") for node IDs.
1500
-
1501
1531
  This does not include the special "init" quanta.
1502
1532
  """
1503
1533
 
@@ -1511,18 +1541,6 @@ class PredictedQuantumGraphComponents:
1511
1541
  This does not include special "init" quanta.
1512
1542
  """
1513
1543
 
1514
- quantum_indices: dict[uuid.UUID, QuantumIndex] = dataclasses.field(default_factory=dict)
1515
- """A mapping from external universal quantum ID to internal integer ID.
1516
-
1517
- While this `dict` does not need to be sorted, the internal integer IDs do
1518
- need to correspond exactly to ``enumerate(sorted(uuids))``.
1519
-
1520
- When used to construct a `PredictedQuantumGraph`, this must be fully
1521
- populated if `thin_graph` is. It can be empty otherwise.
1522
-
1523
- This does include special "init" quanta.
1524
- """
1525
-
1526
1544
  def make_dataset_ref(self, predicted: PredictedDatasetModel) -> DatasetRef:
1527
1545
  """Make a `lsst.daf.butler.DatasetRef` from information in the
1528
1546
  predicted quantum graph.
@@ -1555,48 +1573,35 @@ class PredictedQuantumGraphComponents:
1555
1573
  id=predicted.dataset_id,
1556
1574
  )
1557
1575
 
1558
- def set_quantum_indices(self) -> None:
1559
- """Populate the `quantum_indices` component by sorting the UUIDs in the
1560
- `init_quanta` and `quantum_datasets` components (which must both be
1561
- complete).
1562
- """
1563
- all_quantum_ids = [q.quantum_id for q in self.init_quanta.root]
1564
- all_quantum_ids.extend(self.quantum_datasets.keys())
1565
- all_quantum_ids.sort(key=operator.attrgetter("int"))
1566
- self.quantum_indices = {quantum_id: index for index, quantum_id in enumerate(all_quantum_ids)}
1567
-
1568
1576
  def set_thin_graph(self) -> None:
1569
1577
  """Populate the `thin_graph` component from the `pipeline_graph`,
1570
- `quantum_datasets` and `quantum_indices` components (which must all be
1571
- complete).
1578
+ `quantum_datasets` components (which must be complete).
1572
1579
  """
1573
1580
  bipartite_xgraph = networkx.DiGraph()
1574
1581
  self.thin_graph.quanta = {task_label: [] for task_label in self.pipeline_graph.tasks}
1575
- graph_quantum_indices = []
1582
+ graph_quantum_ids: list[uuid.UUID] = []
1576
1583
  for quantum_datasets in self.quantum_datasets.values():
1577
- quantum_index = self.quantum_indices[quantum_datasets.quantum_id]
1578
1584
  self.thin_graph.quanta[quantum_datasets.task_label].append(
1579
1585
  PredictedThinQuantumModel.model_construct(
1580
- quantum_index=quantum_index,
1586
+ quantum_id=quantum_datasets.quantum_id,
1581
1587
  data_coordinate=quantum_datasets.data_coordinate,
1582
1588
  )
1583
1589
  )
1584
1590
  for dataset in itertools.chain.from_iterable(quantum_datasets.inputs.values()):
1585
- bipartite_xgraph.add_edge(dataset.dataset_id, quantum_index)
1591
+ bipartite_xgraph.add_edge(dataset.dataset_id, quantum_datasets.quantum_id)
1586
1592
  for dataset in itertools.chain.from_iterable(quantum_datasets.outputs.values()):
1587
- bipartite_xgraph.add_edge(quantum_index, dataset.dataset_id)
1588
- graph_quantum_indices.append(quantum_index)
1593
+ bipartite_xgraph.add_edge(quantum_datasets.quantum_id, dataset.dataset_id)
1594
+ graph_quantum_ids.append(quantum_datasets.quantum_id)
1589
1595
  quantum_only_xgraph: networkx.DiGraph = networkx.bipartite.projected_graph(
1590
- bipartite_xgraph, graph_quantum_indices
1596
+ bipartite_xgraph, graph_quantum_ids
1591
1597
  )
1592
1598
  self.thin_graph.edges = list(quantum_only_xgraph.edges)
1593
1599
 
1594
1600
  def set_header_counts(self) -> None:
1595
1601
  """Populate the quantum and dataset counts in the header from the
1596
- `quantum_indices`, `thin_graph`, `init_quanta`, and `quantum_datasets`
1597
- components.
1602
+ `thin_graph`, `init_quanta`, and `quantum_datasets` components.
1598
1603
  """
1599
- self.header.n_quanta = len(self.quantum_indices) - len(self.init_quanta.root)
1604
+ self.header.n_quanta = len(self.quantum_datasets)
1600
1605
  self.header.n_task_quanta = {
1601
1606
  task_label: len(thin_quanta) for task_label, thin_quanta in self.thin_graph.quanta.items()
1602
1607
  }
@@ -1642,8 +1647,7 @@ class PredictedQuantumGraphComponents:
1642
1647
  )
1643
1648
  # Update the keys of the quantum_datasets dict.
1644
1649
  self.quantum_datasets = {qd.quantum_id: qd for qd in self.quantum_datasets.values()}
1645
- # Since the UUIDs have changed, the indices need to change, too.
1646
- self.set_quantum_indices()
1650
+ # Since the UUIDs have changed, the thin graph needs to be rewritten.
1647
1651
  self.set_thin_graph()
1648
1652
  # Update the header last, since we use it above to get the old run.
1649
1653
  self.header.output_run = output_run
@@ -1728,7 +1732,6 @@ class PredictedQuantumGraphComponents:
1728
1732
  records=dimension_data_extractor.records.values(),
1729
1733
  dimensions=result.pipeline_graph.get_all_dimensions(),
1730
1734
  )
1731
- result.set_quantum_indices()
1732
1735
  result.set_thin_graph()
1733
1736
  result.set_header_counts()
1734
1737
  return result
@@ -1764,11 +1767,15 @@ class PredictedQuantumGraphComponents:
1764
1767
  Only a complete predicted quantum graph with all components fully
1765
1768
  populated should be written.
1766
1769
  """
1767
- if self.header.n_quanta + len(self.init_quanta.root) != len(self.quantum_indices):
1770
+ if self.header.n_task_quanta != {
1771
+ task_label: len(quanta) for task_label, quanta in self.thin_graph.quanta.items()
1772
+ }:
1768
1773
  raise RuntimeError(
1769
- f"Cannot save graph after partial read of quanta: expected {self.header.n_quanta}, "
1770
- f"got {len(self.quantum_indices)}."
1774
+ "Cannot save graph after partial read of quanta: thin graph is inconsistent with header."
1771
1775
  )
1776
+ # Ensure we record the actual version we're about to write, in case
1777
+ # we're rewriting an old graph in a new format.
1778
+ self.header.version = FORMAT_VERSION
1772
1779
  uri = ResourcePath(uri)
1773
1780
  match uri.getExtension():
1774
1781
  case ".qg":
@@ -1811,11 +1818,12 @@ class PredictedQuantumGraphComponents:
1811
1818
  else:
1812
1819
  cdict_data = cdict.as_bytes()
1813
1820
  compressor = zstandard.ZstdCompressor(level=zstd_level, dict_data=cdict)
1821
+ indices = {quantum_id: n for n, quantum_id in enumerate(sorted(self.quantum_datasets.keys()))}
1814
1822
  with BaseQuantumGraphWriter.open(
1815
1823
  uri,
1816
1824
  header=self.header,
1817
1825
  pipeline_graph=self.pipeline_graph,
1818
- indices=self.quantum_indices,
1826
+ indices=indices,
1819
1827
  address_filename="quanta",
1820
1828
  compressor=compressor,
1821
1829
  cdict_data=cdict_data,
@@ -1907,18 +1915,17 @@ class PredictedQuantumGraphReader(BaseQuantumGraphReader):
1907
1915
  def read_thin_graph(self) -> None:
1908
1916
  """Read the thin graph.
1909
1917
 
1910
- The thin graph is a quantum-quantum DAG with internal integer IDs for
1911
- nodes and just task labels and data IDs as node attributes. It always
1912
- includes all regular quanta, and does not include init-input or
1913
- init-output information.
1918
+ The thin graph is a quantum-quantum DAG with just task labels and data
1919
+ IDs as node attributes. It always includes all regular quanta, and
1920
+ does not include init-input or init-output information.
1914
1921
  """
1915
1922
  if not self.components.thin_graph.quanta:
1916
- self.components.thin_graph = self._read_single_block("thin_graph", PredictedThinGraphModel)
1917
- if len(self.components.quantum_indices) != self.components.header.n_quanta:
1918
- self.address_reader.read_all()
1919
- self.components.quantum_indices.update(
1920
- {row.key: row.index for row in self.address_reader.rows.values()}
1921
- )
1923
+ if self.header.version > 0:
1924
+ self.components.thin_graph = self._read_single_block("thin_graph", PredictedThinGraphModel)
1925
+ else:
1926
+ self.address_reader.read_all()
1927
+ thin_graph_v0 = self._read_single_block("thin_graph", _PredictedThinGraphModelV0)
1928
+ self.components.thin_graph = thin_graph_v0._upgraded(self.address_reader.rows)
1922
1929
 
1923
1930
  def read_init_quanta(self) -> None:
1924
1931
  """Read the list of special quanta that represent init-inputs and
@@ -1971,8 +1978,6 @@ class PredictedQuantumGraphReader(BaseQuantumGraphReader):
1971
1978
  ):
1972
1979
  self.components.quantum_datasets.setdefault(quantum_datasets.quantum_id, quantum_datasets)
1973
1980
  self.address_reader.read_all()
1974
- for address_row in self.address_reader.rows.values():
1975
- self.components.quantum_indices[address_row.key] = address_row.index
1976
1981
  return
1977
1982
  with MultiblockReader.open_in_zip(
1978
1983
  self.zf, "quantum_datasets", int_size=self.components.header.int_size
@@ -1981,7 +1986,6 @@ class PredictedQuantumGraphReader(BaseQuantumGraphReader):
1981
1986
  if quantum_id in self.components.quantum_datasets:
1982
1987
  continue
1983
1988
  address_row = self.address_reader.find(quantum_id)
1984
- self.components.quantum_indices[address_row.key] = address_row.index
1985
1989
  quantum_datasets = mb_reader.read_model(
1986
1990
  address_row.addresses[0], PredictedQuantumDatasetsModel, self.decompressor
1987
1991
  )