essreduce 25.12.1__tar.gz → 26.1.1__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 (149) hide show
  1. {essreduce-25.12.1 → essreduce-26.1.1}/PKG-INFO +1 -1
  2. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/tof/dream.ipynb +7 -7
  3. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/tof/frame-unwrapping.ipynb +6 -6
  4. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/tof/wfm.ipynb +4 -4
  5. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/base.txt +1 -1
  6. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/basetest.txt +1 -1
  7. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/streaming.py +15 -1
  8. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/__init__.py +4 -0
  9. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/eto_to_tof.py +8 -8
  10. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/lut.py +9 -12
  11. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/types.py +9 -2
  12. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/workflow.py +5 -5
  13. {essreduce-25.12.1 → essreduce-26.1.1}/src/essreduce.egg-info/PKG-INFO +1 -1
  14. {essreduce-25.12.1 → essreduce-26.1.1}/tests/streaming_test.py +129 -0
  15. {essreduce-25.12.1 → essreduce-26.1.1}/tests/time_of_flight/lut_test.py +4 -4
  16. {essreduce-25.12.1 → essreduce-26.1.1}/tests/time_of_flight/unwrap_test.py +3 -9
  17. {essreduce-25.12.1 → essreduce-26.1.1}/tests/time_of_flight/wfm_test.py +1 -3
  18. {essreduce-25.12.1 → essreduce-26.1.1}/tests/time_of_flight/workflow_test.py +76 -11
  19. {essreduce-25.12.1 → essreduce-26.1.1}/.copier-answers.ess.yml +0 -0
  20. {essreduce-25.12.1 → essreduce-26.1.1}/.copier-answers.yml +0 -0
  21. {essreduce-25.12.1 → essreduce-26.1.1}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
  22. {essreduce-25.12.1 → essreduce-26.1.1}/.github/dependabot.yml +0 -0
  23. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/ci.yml +0 -0
  24. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/docs.yml +0 -0
  25. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/nightly_at_main.yml +0 -0
  26. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/nightly_at_main_lower_bound.yml +0 -0
  27. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/nightly_at_release.yml +0 -0
  28. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/python-version-ci +0 -0
  29. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/release.yml +0 -0
  30. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/test.yml +0 -0
  31. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/unpinned.yml +0 -0
  32. {essreduce-25.12.1 → essreduce-26.1.1}/.github/workflows/weekly_windows_macos.yml +0 -0
  33. {essreduce-25.12.1 → essreduce-26.1.1}/.gitignore +0 -0
  34. {essreduce-25.12.1 → essreduce-26.1.1}/.pre-commit-config.yaml +0 -0
  35. {essreduce-25.12.1 → essreduce-26.1.1}/.python-version +0 -0
  36. {essreduce-25.12.1 → essreduce-26.1.1}/CODE_OF_CONDUCT.md +0 -0
  37. {essreduce-25.12.1 → essreduce-26.1.1}/CONTRIBUTING.md +0 -0
  38. {essreduce-25.12.1 → essreduce-26.1.1}/LICENSE +0 -0
  39. {essreduce-25.12.1 → essreduce-26.1.1}/MANIFEST.in +0 -0
  40. {essreduce-25.12.1 → essreduce-26.1.1}/README.md +0 -0
  41. {essreduce-25.12.1 → essreduce-26.1.1}/docs/_static/anaconda-icon.js +0 -0
  42. {essreduce-25.12.1 → essreduce-26.1.1}/docs/_static/favicon.svg +0 -0
  43. {essreduce-25.12.1 → essreduce-26.1.1}/docs/_static/logo-dark.svg +0 -0
  44. {essreduce-25.12.1 → essreduce-26.1.1}/docs/_static/logo.svg +0 -0
  45. {essreduce-25.12.1 → essreduce-26.1.1}/docs/_templates/class-template.rst +0 -0
  46. {essreduce-25.12.1 → essreduce-26.1.1}/docs/_templates/doc_version.html +0 -0
  47. {essreduce-25.12.1 → essreduce-26.1.1}/docs/_templates/module-template.rst +0 -0
  48. {essreduce-25.12.1 → essreduce-26.1.1}/docs/about/index.md +0 -0
  49. {essreduce-25.12.1 → essreduce-26.1.1}/docs/api-reference/index.md +0 -0
  50. {essreduce-25.12.1 → essreduce-26.1.1}/docs/conf.py +0 -0
  51. {essreduce-25.12.1 → essreduce-26.1.1}/docs/developer/coding-conventions.md +0 -0
  52. {essreduce-25.12.1 → essreduce-26.1.1}/docs/developer/dependency-management.md +0 -0
  53. {essreduce-25.12.1 → essreduce-26.1.1}/docs/developer/getting-started.md +0 -0
  54. {essreduce-25.12.1 → essreduce-26.1.1}/docs/developer/gui.ipynb +0 -0
  55. {essreduce-25.12.1 → essreduce-26.1.1}/docs/developer/index.md +0 -0
  56. {essreduce-25.12.1 → essreduce-26.1.1}/docs/index.md +0 -0
  57. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/index.md +0 -0
  58. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/installation.md +0 -0
  59. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
  60. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/tof/index.md +0 -0
  61. {essreduce-25.12.1 → essreduce-26.1.1}/docs/user-guide/widget.md +0 -0
  62. {essreduce-25.12.1 → essreduce-26.1.1}/pyproject.toml +0 -0
  63. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/base.in +0 -0
  64. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/basetest.in +0 -0
  65. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/ci.in +0 -0
  66. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/ci.txt +0 -0
  67. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/dev.in +0 -0
  68. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/dev.txt +0 -0
  69. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/docs.in +0 -0
  70. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/docs.txt +0 -0
  71. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/make_base.py +0 -0
  72. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/mypy.in +0 -0
  73. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/mypy.txt +0 -0
  74. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/nightly.in +0 -0
  75. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/nightly.txt +0 -0
  76. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/static.in +0 -0
  77. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/static.txt +0 -0
  78. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/test.in +0 -0
  79. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/test.txt +0 -0
  80. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/wheels.in +0 -0
  81. {essreduce-25.12.1 → essreduce-26.1.1}/requirements/wheels.txt +0 -0
  82. {essreduce-25.12.1 → essreduce-26.1.1}/resources/logo.svg +0 -0
  83. {essreduce-25.12.1 → essreduce-26.1.1}/setup.cfg +0 -0
  84. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/__init__.py +0 -0
  85. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/data/__init__.py +0 -0
  86. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/data/_registry.py +0 -0
  87. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/live/__init__.py +0 -0
  88. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/live/raw.py +0 -0
  89. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/live/roi.py +0 -0
  90. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/live/workflow.py +0 -0
  91. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/logging.py +0 -0
  92. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/nexus/__init__.py +0 -0
  93. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/nexus/_nexus_loader.py +0 -0
  94. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/nexus/json_generator.py +0 -0
  95. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/nexus/json_nexus.py +0 -0
  96. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/nexus/types.py +0 -0
  97. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/nexus/workflow.py +0 -0
  98. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/normalization.py +0 -0
  99. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/parameter.py +0 -0
  100. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/py.typed +0 -0
  101. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/scripts/grow_nexus.py +0 -0
  102. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/fakes.py +0 -0
  103. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/interpolator_numba.py +0 -0
  104. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/interpolator_scipy.py +0 -0
  105. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/resample.py +0 -0
  106. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/ui.py +0 -0
  107. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/uncertainty.py +0 -0
  108. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/__init__.py +0 -0
  109. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_base.py +0 -0
  110. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_binedges_widget.py +0 -0
  111. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_bounds_widget.py +0 -0
  112. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_config.py +0 -0
  113. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_filename_widget.py +0 -0
  114. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_linspace_widget.py +0 -0
  115. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_optional_widget.py +0 -0
  116. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_spinner.py +0 -0
  117. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_string_widget.py +0 -0
  118. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
  119. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/widgets/_vector_widget.py +0 -0
  120. {essreduce-25.12.1 → essreduce-26.1.1}/src/ess/reduce/workflow.py +0 -0
  121. {essreduce-25.12.1 → essreduce-26.1.1}/src/essreduce.egg-info/SOURCES.txt +0 -0
  122. {essreduce-25.12.1 → essreduce-26.1.1}/src/essreduce.egg-info/dependency_links.txt +0 -0
  123. {essreduce-25.12.1 → essreduce-26.1.1}/src/essreduce.egg-info/entry_points.txt +0 -0
  124. {essreduce-25.12.1 → essreduce-26.1.1}/src/essreduce.egg-info/requires.txt +0 -0
  125. {essreduce-25.12.1 → essreduce-26.1.1}/src/essreduce.egg-info/top_level.txt +0 -0
  126. {essreduce-25.12.1 → essreduce-26.1.1}/tests/accumulators_test.py +0 -0
  127. {essreduce-25.12.1 → essreduce-26.1.1}/tests/conftest.py +0 -0
  128. {essreduce-25.12.1 → essreduce-26.1.1}/tests/live/raw_test.py +0 -0
  129. {essreduce-25.12.1 → essreduce-26.1.1}/tests/live/roi_test.py +0 -0
  130. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_generator_test.py +0 -0
  131. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
  132. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/dataset.json +0 -0
  133. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/detector.json +0 -0
  134. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/entry.json +0 -0
  135. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/event_data.json +0 -0
  136. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/instrument.json +0 -0
  137. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/log.json +0 -0
  138. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/json_nexus_test.py +0 -0
  139. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/nexus_loader_test.py +0 -0
  140. {essreduce-25.12.1 → essreduce-26.1.1}/tests/nexus/workflow_test.py +0 -0
  141. {essreduce-25.12.1 → essreduce-26.1.1}/tests/normalization_test.py +0 -0
  142. {essreduce-25.12.1 → essreduce-26.1.1}/tests/package_test.py +0 -0
  143. {essreduce-25.12.1 → essreduce-26.1.1}/tests/scripts/test_grow_nexus.py +0 -0
  144. {essreduce-25.12.1 → essreduce-26.1.1}/tests/time_of_flight/interpolator_test.py +0 -0
  145. {essreduce-25.12.1 → essreduce-26.1.1}/tests/time_of_flight/resample_tests.py +0 -0
  146. {essreduce-25.12.1 → essreduce-26.1.1}/tests/uncertainty_test.py +0 -0
  147. {essreduce-25.12.1 → essreduce-26.1.1}/tests/widget_test.py +0 -0
  148. {essreduce-25.12.1 → essreduce-26.1.1}/tools/shrink_nexus.py +0 -0
  149. {essreduce-25.12.1 → essreduce-26.1.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: essreduce
3
- Version: 25.12.1
3
+ Version: 26.1.1
4
4
  Summary: Common data reduction tools for the ESS facility
5
5
  Author: Scipp contributors
6
6
  License-Expression: BSD-3-Clause
@@ -311,7 +311,7 @@
311
311
  "id": "21",
312
312
  "metadata": {},
313
313
  "source": [
314
- "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n",
314
+ "By default, the workflow tries to load a `TofLookupTable` from a file.\n",
315
315
  "\n",
316
316
  "In this notebook, instead of using such a pre-made file,\n",
317
317
  "we will build our own lookup table from the chopper information and apply it to the workflow."
@@ -346,7 +346,7 @@
346
346
  " sc.scalar(75.5, unit=\"m\"),\n",
347
347
  " sc.scalar(78.0, unit=\"m\"),\n",
348
348
  ")\n",
349
- "lut_wf.visualize(TimeOfFlightLookupTable)"
349
+ "lut_wf.visualize(TofLookupTable)"
350
350
  ]
351
351
  },
352
352
  {
@@ -411,7 +411,7 @@
411
411
  "metadata": {},
412
412
  "outputs": [],
413
413
  "source": [
414
- "table = lut_wf.compute(TimeOfFlightLookupTable)\n",
414
+ "table = lut_wf.compute(TofLookupTable)\n",
415
415
  "\n",
416
416
  "# Overlay mean on the figure above\n",
417
417
  "table.array[\"distance\", 13].plot(ax=fig2.ax, color=\"C1\", ls=\"-\", marker=None)"
@@ -453,7 +453,7 @@
453
453
  "outputs": [],
454
454
  "source": [
455
455
  "# Set the computed lookup table onto the original workflow\n",
456
- "wf[TimeOfFlightLookupTable] = table\n",
456
+ "wf[TofLookupTable] = table\n",
457
457
  "\n",
458
458
  "# Compute time-of-flight of neutron events\n",
459
459
  "tofs = wf.compute(TofDetector[SampleRun])\n",
@@ -743,7 +743,7 @@
743
743
  "metadata": {},
744
744
  "outputs": [],
745
745
  "source": [
746
- "table = lut_wf.compute(TimeOfFlightLookupTable).array\n",
746
+ "table = lut_wf.compute(TofLookupTable).array\n",
747
747
  "table.plot() / (sc.stddevs(table) / sc.values(table)).plot(norm=\"log\")"
748
748
  ]
749
749
  },
@@ -769,7 +769,7 @@
769
769
  "source": [
770
770
  "lut_wf[LookupTableRelativeErrorThreshold] = 0.01\n",
771
771
  "\n",
772
- "table = lut_wf.compute(TimeOfFlightLookupTable)\n",
772
+ "table = lut_wf.compute(TofLookupTable)\n",
773
773
  "table.plot()"
774
774
  ]
775
775
  },
@@ -797,7 +797,7 @@
797
797
  "wf[RawDetector[SampleRun]] = ess_beamline.get_monitor(\"detector\")[0]\n",
798
798
  "wf[DetectorLtotal[SampleRun]] = Ltotal\n",
799
799
  "\n",
800
- "wf[TimeOfFlightLookupTable] = table\n",
800
+ "wf[TofLookupTable] = table\n",
801
801
  "\n",
802
802
  "# Compute time-of-flight\n",
803
803
  "tofs = wf.compute(TofDetector[SampleRun])\n",
@@ -171,7 +171,7 @@
171
171
  "id": "9",
172
172
  "metadata": {},
173
173
  "source": [
174
- "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n",
174
+ "By default, the workflow tries to load a `TofLookupTable` from a file.\n",
175
175
  "\n",
176
176
  "In this notebook, instead of using such a pre-made file,\n",
177
177
  "we will build our own lookup table from the chopper information and apply it to the workflow.\n",
@@ -227,7 +227,7 @@
227
227
  "}\n",
228
228
  "lut_wf[SourcePosition] = sc.vector([0, 0, 0], unit=\"m\")\n",
229
229
  "\n",
230
- "lut_wf.visualize(TimeOfFlightLookupTable)"
230
+ "lut_wf.visualize(TofLookupTable)"
231
231
  ]
232
232
  },
233
233
  {
@@ -245,7 +245,7 @@
245
245
  "metadata": {},
246
246
  "outputs": [],
247
247
  "source": [
248
- "table = lut_wf.compute(TimeOfFlightLookupTable)\n",
248
+ "table = lut_wf.compute(TofLookupTable)\n",
249
249
  "table.plot()"
250
250
  ]
251
251
  },
@@ -267,7 +267,7 @@
267
267
  "outputs": [],
268
268
  "source": [
269
269
  "# Set the computed lookup table on the original workflow\n",
270
- "wf[TimeOfFlightLookupTable] = table\n",
270
+ "wf[TofLookupTable] = table\n",
271
271
  "\n",
272
272
  "# Compute neutron tofs\n",
273
273
  "tofs = wf.compute(TofDetector[SampleRun])\n",
@@ -432,7 +432,7 @@
432
432
  "metadata": {},
433
433
  "outputs": [],
434
434
  "source": [
435
- "table = lut_wf.compute(TimeOfFlightLookupTable)\n",
435
+ "table = lut_wf.compute(TofLookupTable)\n",
436
436
  "\n",
437
437
  "table.plot(figsize=(9, 4))"
438
438
  ]
@@ -457,7 +457,7 @@
457
457
  "nxevent_data = results.to_nxevent_data()\n",
458
458
  "wf[RawDetector[SampleRun]] = nxevent_data\n",
459
459
  "wf[DetectorLtotal[SampleRun]] = nxevent_data.coords[\"Ltotal\"]\n",
460
- "wf[TimeOfFlightLookupTable] = table\n",
460
+ "wf[TofLookupTable] = table\n",
461
461
  "\n",
462
462
  "tofs = wf.compute(TofDetector[SampleRun])\n",
463
463
  "\n",
@@ -332,7 +332,7 @@
332
332
  "id": "20",
333
333
  "metadata": {},
334
334
  "source": [
335
- "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n",
335
+ "By default, the workflow tries to load a `TofLookupTable` from a file.\n",
336
336
  "\n",
337
337
  "In this notebook, instead of using such a pre-made file,\n",
338
338
  "we will build our own lookup table from the chopper information and apply it to the workflow."
@@ -365,7 +365,7 @@
365
365
  "lut_wf[SourcePosition] = source_position\n",
366
366
  "lut_wf[LtotalRange] = Ltotal, Ltotal\n",
367
367
  "lut_wf[LookupTableRelativeErrorThreshold] = 0.1\n",
368
- "lut_wf.visualize(TimeOfFlightLookupTable)"
368
+ "lut_wf.visualize(TofLookupTable)"
369
369
  ]
370
370
  },
371
371
  {
@@ -430,7 +430,7 @@
430
430
  "metadata": {},
431
431
  "outputs": [],
432
432
  "source": [
433
- "table = lut_wf.compute(TimeOfFlightLookupTable)\n",
433
+ "table = lut_wf.compute(TofLookupTable)\n",
434
434
  "\n",
435
435
  "# Overlay mean on the figure above\n",
436
436
  "table.array[\"distance\", 1].plot(ax=fig2.ax, color=\"C1\", ls=\"-\", marker=None)\n",
@@ -463,7 +463,7 @@
463
463
  "metadata": {},
464
464
  "outputs": [],
465
465
  "source": [
466
- "wf[TimeOfFlightLookupTable] = table\n",
466
+ "wf[TofLookupTable] = table\n",
467
467
  "\n",
468
468
  "tofs = wf.compute(TofDetector[SampleRun])\n",
469
469
  "tofs"
@@ -67,7 +67,7 @@ python-dateutil==2.9.0.post0
67
67
  # scippneutron
68
68
  sciline==25.11.1
69
69
  # via -r base.in
70
- scipp==25.11.0
70
+ scipp==25.12.0
71
71
  # via
72
72
  # -r base.in
73
73
  # scippneutron
@@ -99,7 +99,7 @@ python-dateutil==2.9.0.post0
99
99
  # via matplotlib
100
100
  requests==2.32.5
101
101
  # via pooch
102
- scipp==25.11.0
102
+ scipp==25.12.0
103
103
  # via tof
104
104
  scipy==1.16.3
105
105
  # via -r basetest.in
@@ -108,6 +108,14 @@ class Accumulator(ABC, Generic[T]):
108
108
  Clear the accumulator, resetting it to its initial state.
109
109
  """
110
110
 
111
+ def on_finalize(self) -> None:
112
+ """
113
+ Called after finalize retrieves value.
114
+
115
+ Override this method to perform custom cleanup after each finalize cycle.
116
+ The default implementation does nothing.
117
+ """
118
+
111
119
 
112
120
  class EternalAccumulator(Accumulator[T]):
113
121
  """
@@ -422,6 +430,9 @@ class StreamProcessor:
422
430
  needs_recompute |= self._context_key_to_cached_context_nodes_map[key]
423
431
  for key, value in context.items():
424
432
  self._context_workflow[key] = value
433
+ # Propagate context values to finalize workflow so providers that depend
434
+ # on context keys receive the updated values during finalize().
435
+ self._finalize_workflow[key] = value
425
436
  results = self._context_workflow.compute(needs_recompute)
426
437
  for key, value in results.items():
427
438
  if key in self._target_keys:
@@ -505,7 +516,10 @@ class StreamProcessor:
505
516
  """
506
517
  for key in self._accumulators:
507
518
  self._finalize_workflow[key] = self._accumulators[key].value
508
- return self._finalize_workflow.compute(self._target_keys)
519
+ result = self._finalize_workflow.compute(self._target_keys)
520
+ for acc in self._accumulators.values():
521
+ acc.on_finalize()
522
+ return result
509
523
 
510
524
  def clear(self) -> None:
511
525
  """
@@ -30,6 +30,8 @@ from .types import (
30
30
  TimeOfFlightLookupTableFilename,
31
31
  ToaDetector,
32
32
  TofDetector,
33
+ TofLookupTable,
34
+ TofLookupTableFilename,
33
35
  TofMonitor,
34
36
  )
35
37
  from .workflow import GenericTofWorkflow
@@ -54,6 +56,8 @@ __all__ = [
54
56
  "TimeResolution",
55
57
  "ToaDetector",
56
58
  "TofDetector",
59
+ "TofLookupTable",
60
+ "TofLookupTableFilename",
57
61
  "TofLookupTableWorkflow",
58
62
  "TofMonitor",
59
63
  "providers",
@@ -35,9 +35,9 @@ from .types import (
35
35
  DetectorLtotal,
36
36
  MonitorLtotal,
37
37
  PulseStrideOffset,
38
- TimeOfFlightLookupTable,
39
38
  ToaDetector,
40
39
  TofDetector,
40
+ TofLookupTable,
41
41
  TofMonitor,
42
42
  )
43
43
 
@@ -96,7 +96,7 @@ class TofInterpolator:
96
96
 
97
97
 
98
98
  def _time_of_flight_data_histogram(
99
- da: sc.DataArray, lookup: TimeOfFlightLookupTable, ltotal: sc.Variable
99
+ da: sc.DataArray, lookup: TofLookupTable, ltotal: sc.Variable
100
100
  ) -> sc.DataArray:
101
101
  # In NeXus, 'time_of_flight' is the canonical name in NXmonitor, but in some files,
102
102
  # it may be called 'tof' or 'frame_time'.
@@ -201,7 +201,7 @@ def _guess_pulse_stride_offset(
201
201
 
202
202
  def _prepare_tof_interpolation_inputs(
203
203
  da: sc.DataArray,
204
- lookup: TimeOfFlightLookupTable,
204
+ lookup: TofLookupTable,
205
205
  ltotal: sc.Variable,
206
206
  pulse_stride_offset: int | None,
207
207
  ) -> dict:
@@ -295,7 +295,7 @@ def _prepare_tof_interpolation_inputs(
295
295
 
296
296
  def _time_of_flight_data_events(
297
297
  da: sc.DataArray,
298
- lookup: TimeOfFlightLookupTable,
298
+ lookup: TofLookupTable,
299
299
  ltotal: sc.Variable,
300
300
  pulse_stride_offset: int | None,
301
301
  ) -> sc.DataArray:
@@ -395,7 +395,7 @@ def monitor_ltotal_from_straight_line_approximation(
395
395
 
396
396
  def _compute_tof_data(
397
397
  da: sc.DataArray,
398
- lookup: TimeOfFlightLookupTable,
398
+ lookup: TofLookupTable,
399
399
  ltotal: sc.Variable,
400
400
  pulse_stride_offset: int,
401
401
  ) -> sc.DataArray:
@@ -413,7 +413,7 @@ def _compute_tof_data(
413
413
 
414
414
  def detector_time_of_flight_data(
415
415
  detector_data: RawDetector[RunType],
416
- lookup: TimeOfFlightLookupTable,
416
+ lookup: TofLookupTable,
417
417
  ltotal: DetectorLtotal[RunType],
418
418
  pulse_stride_offset: PulseStrideOffset,
419
419
  ) -> TofDetector[RunType]:
@@ -447,7 +447,7 @@ def detector_time_of_flight_data(
447
447
 
448
448
  def monitor_time_of_flight_data(
449
449
  monitor_data: RawMonitor[RunType, MonitorType],
450
- lookup: TimeOfFlightLookupTable,
450
+ lookup: TofLookupTable,
451
451
  ltotal: MonitorLtotal[RunType, MonitorType],
452
452
  pulse_stride_offset: PulseStrideOffset,
453
453
  ) -> TofMonitor[RunType, MonitorType]:
@@ -481,7 +481,7 @@ def monitor_time_of_flight_data(
481
481
 
482
482
  def detector_time_of_arrival_data(
483
483
  detector_data: RawDetector[RunType],
484
- lookup: TimeOfFlightLookupTable,
484
+ lookup: TofLookupTable,
485
485
  ltotal: DetectorLtotal[RunType],
486
486
  pulse_stride_offset: PulseStrideOffset,
487
487
  ) -> ToaDetector[RunType]:
@@ -13,7 +13,7 @@ import sciline as sl
13
13
  import scipp as sc
14
14
 
15
15
  from ..nexus.types import AnyRun, DiskChoppers
16
- from .types import TimeOfFlightLookupTable
16
+ from .types import TofLookupTable
17
17
 
18
18
 
19
19
  @dataclass
@@ -30,9 +30,6 @@ class SimulationResults:
30
30
  time_of_arrival:
31
31
  Time of arrival of the neutrons at the position where the events were recorded
32
32
  (1d array of size N).
33
- speed:
34
- Speed of the neutrons, typically derived from the wavelength of the neutrons
35
- (1d array of size N).
36
33
  wavelength:
37
34
  Wavelength of the neutrons (1d array of size N).
38
35
  weight:
@@ -48,12 +45,14 @@ class SimulationResults:
48
45
  """
49
46
 
50
47
  time_of_arrival: sc.Variable
51
- speed: sc.Variable
52
48
  wavelength: sc.Variable
53
49
  weight: sc.Variable
54
50
  distance: sc.Variable
55
51
  choppers: DiskChoppers[AnyRun] | None = None
56
52
 
53
+ def __post_init__(self):
54
+ self.speed = (sc.constants.h / sc.constants.m_n) / self.wavelength
55
+
57
56
 
58
57
  NumberOfSimulatedNeutrons = NewType("NumberOfSimulatedNeutrons", int)
59
58
  """
@@ -230,7 +229,7 @@ def make_tof_lookup_table(
230
229
  pulse_period: PulsePeriod,
231
230
  pulse_stride: PulseStride,
232
231
  error_threshold: LookupTableRelativeErrorThreshold,
233
- ) -> TimeOfFlightLookupTable:
232
+ ) -> TofLookupTable:
234
233
  """
235
234
  Compute a lookup table for time-of-flight as a function of distance and
236
235
  time-of-arrival.
@@ -239,7 +238,7 @@ def make_tof_lookup_table(
239
238
  ----------
240
239
  simulation:
241
240
  Results of a time-of-flight simulation used to create a lookup table.
242
- The results should be a flat table with columns for time-of-arrival, speed,
241
+ The results should be a flat table with columns for time-of-arrival,
243
242
  wavelength, and weight.
244
243
  ltotal_range:
245
244
  Range of total flight path lengths from the source to the detector.
@@ -372,7 +371,7 @@ def make_tof_lookup_table(
372
371
  # In-place masking for better performance
373
372
  _mask_large_uncertainty(table, error_threshold)
374
373
 
375
- return TimeOfFlightLookupTable(
374
+ return TofLookupTable(
376
375
  array=table,
377
376
  pulse_period=pulse_period,
378
377
  pulse_stride=pulse_stride,
@@ -398,13 +397,13 @@ def simulate_chopper_cascade_using_tof(
398
397
  ) -> SimulationResults:
399
398
  """
400
399
  Simulate a pulse of neutrons propagating through a chopper cascade using the
401
- ``tof`` package (https://tof.readthedocs.io).
400
+ ``tof`` package (https://scipp.github.io/tof).
402
401
 
403
402
  Parameters
404
403
  ----------
405
404
  choppers:
406
405
  A dict of DiskChopper objects representing the choppers in the beamline. See
407
- https://scipp.github.io/scippneutron/user-guide/chopper/processing-nexus-choppers.html#Build-DiskChopper
406
+ https://scipp.github.io/scippneutron/user-guide/chopper/processing-nexus-choppers.html
408
407
  for more information.
409
408
  source_position:
410
409
  A scalar variable with ``dtype=vector3`` that defines the source position.
@@ -436,7 +435,6 @@ def simulate_chopper_cascade_using_tof(
436
435
  events = source.data.squeeze().flatten(to='event')
437
436
  return SimulationResults(
438
437
  time_of_arrival=events.coords["birth_time"],
439
- speed=events.coords["speed"],
440
438
  wavelength=events.coords["wavelength"],
441
439
  weight=events.data,
442
440
  distance=0.0 * sc.units.m,
@@ -451,7 +449,6 @@ def simulate_chopper_cascade_using_tof(
451
449
  ]
452
450
  return SimulationResults(
453
451
  time_of_arrival=events.coords["toa"],
454
- speed=events.coords["speed"],
455
452
  wavelength=events.coords["wavelength"],
456
453
  weight=events.data,
457
454
  distance=furthest_chopper.distance,
@@ -10,12 +10,15 @@ import scipp as sc
10
10
 
11
11
  from ..nexus.types import MonitorType, RunType
12
12
 
13
- TimeOfFlightLookupTableFilename = NewType("TimeOfFlightLookupTableFilename", str)
13
+ TofLookupTableFilename = NewType("TofLookupTableFilename", str)
14
14
  """Filename of the time-of-flight lookup table."""
15
15
 
16
+ TimeOfFlightLookupTableFilename = TofLookupTableFilename
17
+ """Filename of the time-of-flight lookup table (alias)."""
18
+
16
19
 
17
20
  @dataclass
18
- class TimeOfFlightLookupTable:
21
+ class TofLookupTable:
19
22
  """
20
23
  Lookup table giving time-of-flight as a function of distance and time of arrival.
21
24
  """
@@ -47,6 +50,10 @@ class TimeOfFlightLookupTable:
47
50
  return self.array.plot(*args, **kwargs)
48
51
 
49
52
 
53
+ TimeOfFlightLookupTable = TofLookupTable
54
+ """Lookup table giving time-of-flight as a function of distance and time of arrival
55
+ (alias)."""
56
+
50
57
  PulseStrideOffset = NewType("PulseStrideOffset", int | None)
51
58
  """
52
59
  When pulse-skipping, the offset of the first pulse in the stride. This is typically
@@ -9,14 +9,14 @@ from ..nexus import GenericNeXusWorkflow
9
9
  from . import eto_to_tof
10
10
  from .types import (
11
11
  PulseStrideOffset,
12
- TimeOfFlightLookupTable,
13
- TimeOfFlightLookupTableFilename,
12
+ TofLookupTable,
13
+ TofLookupTableFilename,
14
14
  )
15
15
 
16
16
 
17
17
  def load_tof_lookup_table(
18
- filename: TimeOfFlightLookupTableFilename,
19
- ) -> TimeOfFlightLookupTable:
18
+ filename: TofLookupTableFilename,
19
+ ) -> TofLookupTable:
20
20
  """Load a time-of-flight lookup table from an HDF5 file."""
21
21
  table = sc.io.load_hdf5(filename)
22
22
 
@@ -40,7 +40,7 @@ def load_tof_lookup_table(
40
40
  "error_threshold": table.coords["error_threshold"].value,
41
41
  }
42
42
 
43
- return TimeOfFlightLookupTable(**table)
43
+ return TofLookupTable(**table)
44
44
 
45
45
 
46
46
  def GenericTofWorkflow(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: essreduce
3
- Version: 25.12.1
3
+ Version: 26.1.1
4
4
  Summary: Common data reduction tools for the ESS facility
5
5
  Author: Scipp contributors
6
6
  License-Expression: BSD-3-Clause
@@ -908,3 +908,132 @@ def test_StreamProcessor_rejects_context_keys_depending_on_dynamic_keys() -> Non
908
908
  target_keys=(Output,),
909
909
  accumulators=(Output,),
910
910
  )
911
+
912
+
913
+ def test_StreamProcessor_calls_on_finalize_after_finalize() -> None:
914
+ """Test that on_finalize hook is called on accumulators after finalize."""
915
+
916
+ class TrackingAccumulator(streaming.EternalAccumulator[sc.Variable]):
917
+ def __init__(self) -> None:
918
+ super().__init__()
919
+ self.finalize_count = 0
920
+
921
+ def on_finalize(self) -> None:
922
+ self.finalize_count += 1
923
+
924
+ base_workflow = sciline.Pipeline(
925
+ (make_static_a, make_accum_a, make_accum_b, make_target)
926
+ )
927
+
928
+ accum_a = TrackingAccumulator()
929
+ accum_b = TrackingAccumulator()
930
+
931
+ streaming_wf = streaming.StreamProcessor(
932
+ base_workflow=base_workflow,
933
+ dynamic_keys=(DynamicA, DynamicB),
934
+ target_keys=(Target,),
935
+ accumulators={AccumA: accum_a, AccumB: accum_b},
936
+ )
937
+
938
+ assert accum_a.finalize_count == 0
939
+ assert accum_b.finalize_count == 0
940
+
941
+ streaming_wf.accumulate({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
942
+ result = streaming_wf.finalize()
943
+ assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
944
+
945
+ assert accum_a.finalize_count == 1
946
+ assert accum_b.finalize_count == 1
947
+
948
+ streaming_wf.accumulate({DynamicA: sc.scalar(2), DynamicB: sc.scalar(5)})
949
+ result = streaming_wf.finalize()
950
+ assert sc.identical(result[Target], sc.scalar(2 * 3.0 / 9.0))
951
+
952
+ assert accum_a.finalize_count == 2
953
+ assert accum_b.finalize_count == 2
954
+
955
+
956
+ def test_on_finalize_can_clear_accumulator_for_window_behavior() -> None:
957
+ """Test that on_finalize can be used to create windowed accumulators."""
958
+
959
+ class WindowAccumulator(streaming.EternalAccumulator[sc.Variable]):
960
+ """Accumulator that clears after each finalize cycle."""
961
+
962
+ def on_finalize(self) -> None:
963
+ self.clear()
964
+
965
+ base_workflow = sciline.Pipeline(
966
+ (make_static_a, make_accum_a, make_accum_b, make_target)
967
+ )
968
+
969
+ # Use windowed accumulator for AccumA, eternal for AccumB
970
+ streaming_wf = streaming.StreamProcessor(
971
+ base_workflow=base_workflow,
972
+ dynamic_keys=(DynamicA, DynamicB),
973
+ target_keys=(Target,),
974
+ accumulators={
975
+ AccumA: WindowAccumulator(),
976
+ AccumB: streaming.EternalAccumulator(),
977
+ },
978
+ )
979
+
980
+ streaming_wf.accumulate({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
981
+ result = streaming_wf.finalize()
982
+ # AccumA = 2*1 = 2, AccumB = 4
983
+ assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
984
+
985
+ streaming_wf.accumulate({DynamicA: sc.scalar(2), DynamicB: sc.scalar(5)})
986
+ result = streaming_wf.finalize()
987
+ # AccumA was cleared, now 2*2 = 4, AccumB accumulated to 4+5 = 9
988
+ assert sc.identical(result[Target], sc.scalar(2 * 2.0 / 9.0))
989
+
990
+ streaming_wf.accumulate({DynamicA: sc.scalar(3), DynamicB: sc.scalar(6)})
991
+ result = streaming_wf.finalize()
992
+ # AccumA was cleared, now 2*3 = 6, AccumB accumulated to 9+6 = 15
993
+ assert sc.identical(result[Target], sc.scalar(2 * 3.0 / 15.0))
994
+
995
+
996
+ def test_StreamProcessor_finalize_provider_uses_context_directly() -> None:
997
+ """Test that finalize-time providers can access context keys directly.
998
+
999
+ This covers the case where a provider that runs during finalize() (i.e., one
1000
+ that depends on accumulated values) also needs access to a context key.
1001
+ The context value must be propagated to the finalize workflow.
1002
+ """
1003
+ Streamed = NewType('Streamed', int)
1004
+ Context = NewType('Context', int)
1005
+ Accumulated = NewType('Accumulated', int)
1006
+ Output = NewType('Output', int)
1007
+
1008
+ def accumulate(streamed: Streamed) -> Accumulated:
1009
+ return Accumulated(streamed)
1010
+
1011
+ def make_output(accumulated: Accumulated, context: Context) -> Output:
1012
+ # This provider runs at finalize time (depends on Accumulated)
1013
+ # and needs direct access to Context
1014
+ return Output(accumulated + context)
1015
+
1016
+ wf = sciline.Pipeline((accumulate, make_output))
1017
+ streaming_wf = streaming.StreamProcessor(
1018
+ base_workflow=wf,
1019
+ dynamic_keys=(Streamed,),
1020
+ context_keys=(Context,),
1021
+ target_keys=(Output,),
1022
+ accumulators=(Accumulated,),
1023
+ )
1024
+
1025
+ streaming_wf.set_context({Context: sc.scalar(100)})
1026
+ streaming_wf.accumulate({Streamed: sc.scalar(1)})
1027
+ streaming_wf.accumulate({Streamed: sc.scalar(2)})
1028
+ result = streaming_wf.finalize()
1029
+
1030
+ # Accumulated = 1 + 2 = 3, Context = 100, Output = 3 + 100 = 103
1031
+ assert sc.identical(result[Output], sc.scalar(103))
1032
+
1033
+ # Update context and verify finalize sees the new value
1034
+ streaming_wf.set_context({Context: sc.scalar(200)})
1035
+ streaming_wf.accumulate({Streamed: sc.scalar(4)})
1036
+ result = streaming_wf.finalize()
1037
+
1038
+ # Accumulated = 3 + 4 = 7, Context = 200, Output = 7 + 200 = 207
1039
+ assert sc.identical(result[Output], sc.scalar(207))
@@ -26,7 +26,7 @@ def test_lut_workflow_computes_table():
26
26
  wf[time_of_flight.DistanceResolution] = dres
27
27
  wf[time_of_flight.TimeResolution] = tres
28
28
 
29
- table = wf.compute(time_of_flight.TimeOfFlightLookupTable)
29
+ table = wf.compute(time_of_flight.TofLookupTable)
30
30
 
31
31
  assert table.array.coords['distance'].min() < lmin
32
32
  assert table.array.coords['distance'].max() > lmax
@@ -56,7 +56,7 @@ def test_lut_workflow_computes_table_in_chunks():
56
56
  wf[time_of_flight.DistanceResolution] = dres
57
57
  wf[time_of_flight.TimeResolution] = tres
58
58
 
59
- table = wf.compute(time_of_flight.TimeOfFlightLookupTable)
59
+ table = wf.compute(time_of_flight.TofLookupTable)
60
60
 
61
61
  assert table.array.coords['distance'].min() < lmin
62
62
  assert table.array.coords['distance'].max() > lmax
@@ -85,7 +85,7 @@ def test_lut_workflow_pulse_skipping():
85
85
  wf[time_of_flight.DistanceResolution] = dres
86
86
  wf[time_of_flight.TimeResolution] = tres
87
87
 
88
- table = wf.compute(time_of_flight.TimeOfFlightLookupTable)
88
+ table = wf.compute(time_of_flight.TofLookupTable)
89
89
 
90
90
  assert table.array.coords['event_time_offset'].max() == 2 * sc.scalar(
91
91
  1 / 14, unit='s'
@@ -108,7 +108,7 @@ def test_lut_workflow_non_exact_distance_range():
108
108
  wf[time_of_flight.DistanceResolution] = dres
109
109
  wf[time_of_flight.TimeResolution] = tres
110
110
 
111
- table = wf.compute(time_of_flight.TimeOfFlightLookupTable)
111
+ table = wf.compute(time_of_flight.TofLookupTable)
112
112
 
113
113
  assert table.array.coords['distance'].min() < lmin
114
114
  assert table.array.coords['distance'].max() > lmax
@@ -75,9 +75,7 @@ def _make_workflow_event_mode(
75
75
  lut_wf[time_of_flight.LtotalRange] = distance, distance
76
76
  lut_wf[time_of_flight.LookupTableRelativeErrorThreshold] = error_threshold
77
77
 
78
- pl[time_of_flight.TimeOfFlightLookupTable] = lut_wf.compute(
79
- time_of_flight.TimeOfFlightLookupTable
80
- )
78
+ pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable)
81
79
 
82
80
  return pl, ref
83
81
 
@@ -104,9 +102,7 @@ def _make_workflow_histogram_mode(dim, distance, choppers, lut_workflow, seed):
104
102
  lut_wf = lut_workflow.copy()
105
103
  lut_wf[time_of_flight.LtotalRange] = distance, distance
106
104
 
107
- pl[time_of_flight.TimeOfFlightLookupTable] = lut_wf.compute(
108
- time_of_flight.TimeOfFlightLookupTable
109
- )
105
+ pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable)
110
106
 
111
107
  return pl, ref
112
108
 
@@ -340,9 +336,7 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing() -> No
340
336
  pl[RawDetector[SampleRun]] = a.bins.concat('event_time_zero')
341
337
  pl[time_of_flight.DetectorLtotal[SampleRun]] = distance
342
338
 
343
- pl[time_of_flight.TimeOfFlightLookupTable] = lut_wf.compute(
344
- time_of_flight.TimeOfFlightLookupTable
345
- )
339
+ pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable)
346
340
  pl[time_of_flight.PulseStrideOffset] = 1 # Start the stride at the second pulse
347
341
 
348
342
  tofs = pl.compute(time_of_flight.TofDetector[SampleRun])