essreduce 26.1.0__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-26.1.0 → essreduce-26.1.1}/PKG-INFO +1 -1
  2. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/streaming.py +15 -1
  3. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/lut.py +4 -7
  4. {essreduce-26.1.0 → essreduce-26.1.1}/src/essreduce.egg-info/PKG-INFO +1 -1
  5. {essreduce-26.1.0 → essreduce-26.1.1}/tests/streaming_test.py +129 -0
  6. {essreduce-26.1.0 → essreduce-26.1.1}/.copier-answers.ess.yml +0 -0
  7. {essreduce-26.1.0 → essreduce-26.1.1}/.copier-answers.yml +0 -0
  8. {essreduce-26.1.0 → essreduce-26.1.1}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
  9. {essreduce-26.1.0 → essreduce-26.1.1}/.github/dependabot.yml +0 -0
  10. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/ci.yml +0 -0
  11. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/docs.yml +0 -0
  12. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/nightly_at_main.yml +0 -0
  13. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/nightly_at_main_lower_bound.yml +0 -0
  14. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/nightly_at_release.yml +0 -0
  15. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/python-version-ci +0 -0
  16. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/release.yml +0 -0
  17. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/test.yml +0 -0
  18. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/unpinned.yml +0 -0
  19. {essreduce-26.1.0 → essreduce-26.1.1}/.github/workflows/weekly_windows_macos.yml +0 -0
  20. {essreduce-26.1.0 → essreduce-26.1.1}/.gitignore +0 -0
  21. {essreduce-26.1.0 → essreduce-26.1.1}/.pre-commit-config.yaml +0 -0
  22. {essreduce-26.1.0 → essreduce-26.1.1}/.python-version +0 -0
  23. {essreduce-26.1.0 → essreduce-26.1.1}/CODE_OF_CONDUCT.md +0 -0
  24. {essreduce-26.1.0 → essreduce-26.1.1}/CONTRIBUTING.md +0 -0
  25. {essreduce-26.1.0 → essreduce-26.1.1}/LICENSE +0 -0
  26. {essreduce-26.1.0 → essreduce-26.1.1}/MANIFEST.in +0 -0
  27. {essreduce-26.1.0 → essreduce-26.1.1}/README.md +0 -0
  28. {essreduce-26.1.0 → essreduce-26.1.1}/docs/_static/anaconda-icon.js +0 -0
  29. {essreduce-26.1.0 → essreduce-26.1.1}/docs/_static/favicon.svg +0 -0
  30. {essreduce-26.1.0 → essreduce-26.1.1}/docs/_static/logo-dark.svg +0 -0
  31. {essreduce-26.1.0 → essreduce-26.1.1}/docs/_static/logo.svg +0 -0
  32. {essreduce-26.1.0 → essreduce-26.1.1}/docs/_templates/class-template.rst +0 -0
  33. {essreduce-26.1.0 → essreduce-26.1.1}/docs/_templates/doc_version.html +0 -0
  34. {essreduce-26.1.0 → essreduce-26.1.1}/docs/_templates/module-template.rst +0 -0
  35. {essreduce-26.1.0 → essreduce-26.1.1}/docs/about/index.md +0 -0
  36. {essreduce-26.1.0 → essreduce-26.1.1}/docs/api-reference/index.md +0 -0
  37. {essreduce-26.1.0 → essreduce-26.1.1}/docs/conf.py +0 -0
  38. {essreduce-26.1.0 → essreduce-26.1.1}/docs/developer/coding-conventions.md +0 -0
  39. {essreduce-26.1.0 → essreduce-26.1.1}/docs/developer/dependency-management.md +0 -0
  40. {essreduce-26.1.0 → essreduce-26.1.1}/docs/developer/getting-started.md +0 -0
  41. {essreduce-26.1.0 → essreduce-26.1.1}/docs/developer/gui.ipynb +0 -0
  42. {essreduce-26.1.0 → essreduce-26.1.1}/docs/developer/index.md +0 -0
  43. {essreduce-26.1.0 → essreduce-26.1.1}/docs/index.md +0 -0
  44. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/index.md +0 -0
  45. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/installation.md +0 -0
  46. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
  47. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/tof/dream.ipynb +0 -0
  48. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/tof/frame-unwrapping.ipynb +0 -0
  49. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/tof/index.md +0 -0
  50. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/tof/wfm.ipynb +0 -0
  51. {essreduce-26.1.0 → essreduce-26.1.1}/docs/user-guide/widget.md +0 -0
  52. {essreduce-26.1.0 → essreduce-26.1.1}/pyproject.toml +0 -0
  53. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/base.in +0 -0
  54. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/base.txt +0 -0
  55. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/basetest.in +0 -0
  56. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/basetest.txt +0 -0
  57. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/ci.in +0 -0
  58. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/ci.txt +0 -0
  59. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/dev.in +0 -0
  60. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/dev.txt +0 -0
  61. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/docs.in +0 -0
  62. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/docs.txt +0 -0
  63. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/make_base.py +0 -0
  64. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/mypy.in +0 -0
  65. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/mypy.txt +0 -0
  66. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/nightly.in +0 -0
  67. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/nightly.txt +0 -0
  68. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/static.in +0 -0
  69. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/static.txt +0 -0
  70. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/test.in +0 -0
  71. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/test.txt +0 -0
  72. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/wheels.in +0 -0
  73. {essreduce-26.1.0 → essreduce-26.1.1}/requirements/wheels.txt +0 -0
  74. {essreduce-26.1.0 → essreduce-26.1.1}/resources/logo.svg +0 -0
  75. {essreduce-26.1.0 → essreduce-26.1.1}/setup.cfg +0 -0
  76. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/__init__.py +0 -0
  77. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/data/__init__.py +0 -0
  78. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/data/_registry.py +0 -0
  79. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/live/__init__.py +0 -0
  80. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/live/raw.py +0 -0
  81. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/live/roi.py +0 -0
  82. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/live/workflow.py +0 -0
  83. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/logging.py +0 -0
  84. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/nexus/__init__.py +0 -0
  85. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/nexus/_nexus_loader.py +0 -0
  86. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/nexus/json_generator.py +0 -0
  87. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/nexus/json_nexus.py +0 -0
  88. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/nexus/types.py +0 -0
  89. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/nexus/workflow.py +0 -0
  90. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/normalization.py +0 -0
  91. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/parameter.py +0 -0
  92. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/py.typed +0 -0
  93. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/scripts/grow_nexus.py +0 -0
  94. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/__init__.py +0 -0
  95. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/eto_to_tof.py +0 -0
  96. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/fakes.py +0 -0
  97. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/interpolator_numba.py +0 -0
  98. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/interpolator_scipy.py +0 -0
  99. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/resample.py +0 -0
  100. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/types.py +0 -0
  101. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/time_of_flight/workflow.py +0 -0
  102. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/ui.py +0 -0
  103. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/uncertainty.py +0 -0
  104. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/__init__.py +0 -0
  105. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_base.py +0 -0
  106. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_binedges_widget.py +0 -0
  107. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_bounds_widget.py +0 -0
  108. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_config.py +0 -0
  109. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_filename_widget.py +0 -0
  110. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_linspace_widget.py +0 -0
  111. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_optional_widget.py +0 -0
  112. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_spinner.py +0 -0
  113. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_string_widget.py +0 -0
  114. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
  115. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/widgets/_vector_widget.py +0 -0
  116. {essreduce-26.1.0 → essreduce-26.1.1}/src/ess/reduce/workflow.py +0 -0
  117. {essreduce-26.1.0 → essreduce-26.1.1}/src/essreduce.egg-info/SOURCES.txt +0 -0
  118. {essreduce-26.1.0 → essreduce-26.1.1}/src/essreduce.egg-info/dependency_links.txt +0 -0
  119. {essreduce-26.1.0 → essreduce-26.1.1}/src/essreduce.egg-info/entry_points.txt +0 -0
  120. {essreduce-26.1.0 → essreduce-26.1.1}/src/essreduce.egg-info/requires.txt +0 -0
  121. {essreduce-26.1.0 → essreduce-26.1.1}/src/essreduce.egg-info/top_level.txt +0 -0
  122. {essreduce-26.1.0 → essreduce-26.1.1}/tests/accumulators_test.py +0 -0
  123. {essreduce-26.1.0 → essreduce-26.1.1}/tests/conftest.py +0 -0
  124. {essreduce-26.1.0 → essreduce-26.1.1}/tests/live/raw_test.py +0 -0
  125. {essreduce-26.1.0 → essreduce-26.1.1}/tests/live/roi_test.py +0 -0
  126. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_generator_test.py +0 -0
  127. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
  128. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/dataset.json +0 -0
  129. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/detector.json +0 -0
  130. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/entry.json +0 -0
  131. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/event_data.json +0 -0
  132. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/instrument.json +0 -0
  133. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_examples/log.json +0 -0
  134. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/json_nexus_test.py +0 -0
  135. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/nexus_loader_test.py +0 -0
  136. {essreduce-26.1.0 → essreduce-26.1.1}/tests/nexus/workflow_test.py +0 -0
  137. {essreduce-26.1.0 → essreduce-26.1.1}/tests/normalization_test.py +0 -0
  138. {essreduce-26.1.0 → essreduce-26.1.1}/tests/package_test.py +0 -0
  139. {essreduce-26.1.0 → essreduce-26.1.1}/tests/scripts/test_grow_nexus.py +0 -0
  140. {essreduce-26.1.0 → essreduce-26.1.1}/tests/time_of_flight/interpolator_test.py +0 -0
  141. {essreduce-26.1.0 → essreduce-26.1.1}/tests/time_of_flight/lut_test.py +0 -0
  142. {essreduce-26.1.0 → essreduce-26.1.1}/tests/time_of_flight/resample_tests.py +0 -0
  143. {essreduce-26.1.0 → essreduce-26.1.1}/tests/time_of_flight/unwrap_test.py +0 -0
  144. {essreduce-26.1.0 → essreduce-26.1.1}/tests/time_of_flight/wfm_test.py +0 -0
  145. {essreduce-26.1.0 → essreduce-26.1.1}/tests/time_of_flight/workflow_test.py +0 -0
  146. {essreduce-26.1.0 → essreduce-26.1.1}/tests/uncertainty_test.py +0 -0
  147. {essreduce-26.1.0 → essreduce-26.1.1}/tests/widget_test.py +0 -0
  148. {essreduce-26.1.0 → essreduce-26.1.1}/tools/shrink_nexus.py +0 -0
  149. {essreduce-26.1.0 → essreduce-26.1.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: essreduce
3
- Version: 26.1.0
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
@@ -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,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
  """
@@ -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.
@@ -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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: essreduce
3
- Version: 26.1.0
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))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes