essreduce 25.2.4__tar.gz → 25.2.5__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 (137) hide show
  1. {essreduce-25.2.4/src/essreduce.egg-info → essreduce-25.2.5}/PKG-INFO +1 -1
  2. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/_nexus_loader.py +9 -4
  3. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/streaming.py +56 -3
  4. {essreduce-25.2.4 → essreduce-25.2.5/src/essreduce.egg-info}/PKG-INFO +1 -1
  5. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/nexus_loader_test.py +49 -15
  6. {essreduce-25.2.4 → essreduce-25.2.5}/tests/streaming_test.py +101 -5
  7. {essreduce-25.2.4 → essreduce-25.2.5}/.copier-answers.ess.yml +0 -0
  8. {essreduce-25.2.4 → essreduce-25.2.5}/.copier-answers.yml +0 -0
  9. {essreduce-25.2.4 → essreduce-25.2.5}/.github/ISSUE_TEMPLATE/blank.md +0 -0
  10. {essreduce-25.2.4 → essreduce-25.2.5}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
  11. {essreduce-25.2.4 → essreduce-25.2.5}/.github/dependabot.yml +0 -0
  12. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/ci.yml +0 -0
  13. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/docs.yml +0 -0
  14. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/nightly_at_main.yml +0 -0
  15. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/nightly_at_release.yml +0 -0
  16. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/python-version-ci +0 -0
  17. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/release.yml +0 -0
  18. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/test.yml +0 -0
  19. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/unpinned.yml +0 -0
  20. {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/weekly_windows_macos.yml +0 -0
  21. {essreduce-25.2.4 → essreduce-25.2.5}/.gitignore +0 -0
  22. {essreduce-25.2.4 → essreduce-25.2.5}/.pre-commit-config.yaml +0 -0
  23. {essreduce-25.2.4 → essreduce-25.2.5}/.python-version +0 -0
  24. {essreduce-25.2.4 → essreduce-25.2.5}/CODE_OF_CONDUCT.md +0 -0
  25. {essreduce-25.2.4 → essreduce-25.2.5}/CONTRIBUTING.md +0 -0
  26. {essreduce-25.2.4 → essreduce-25.2.5}/LICENSE +0 -0
  27. {essreduce-25.2.4 → essreduce-25.2.5}/MANIFEST.in +0 -0
  28. {essreduce-25.2.4 → essreduce-25.2.5}/README.md +0 -0
  29. {essreduce-25.2.4 → essreduce-25.2.5}/conda/meta.yaml +0 -0
  30. {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/anaconda-icon.js +0 -0
  31. {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/favicon.svg +0 -0
  32. {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/logo-dark.svg +0 -0
  33. {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/logo.svg +0 -0
  34. {essreduce-25.2.4 → essreduce-25.2.5}/docs/_templates/class-template.rst +0 -0
  35. {essreduce-25.2.4 → essreduce-25.2.5}/docs/_templates/doc_version.html +0 -0
  36. {essreduce-25.2.4 → essreduce-25.2.5}/docs/_templates/module-template.rst +0 -0
  37. {essreduce-25.2.4 → essreduce-25.2.5}/docs/about/index.md +0 -0
  38. {essreduce-25.2.4 → essreduce-25.2.5}/docs/api-reference/index.md +0 -0
  39. {essreduce-25.2.4 → essreduce-25.2.5}/docs/conf.py +0 -0
  40. {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/coding-conventions.md +0 -0
  41. {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/dependency-management.md +0 -0
  42. {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/getting-started.md +0 -0
  43. {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/gui.ipynb +0 -0
  44. {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/index.md +0 -0
  45. {essreduce-25.2.4 → essreduce-25.2.5}/docs/index.md +0 -0
  46. {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/index.md +0 -0
  47. {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
  48. {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/dream.ipynb +0 -0
  49. {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/frame-unwrapping.ipynb +0 -0
  50. {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/index.md +0 -0
  51. {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/wfm.ipynb +0 -0
  52. {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/widget.md +0 -0
  53. {essreduce-25.2.4 → essreduce-25.2.5}/pyproject.toml +0 -0
  54. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/base.in +0 -0
  55. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/base.txt +0 -0
  56. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/basetest.in +0 -0
  57. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/basetest.txt +0 -0
  58. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/ci.in +0 -0
  59. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/ci.txt +0 -0
  60. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/dev.in +0 -0
  61. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/dev.txt +0 -0
  62. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/docs.in +0 -0
  63. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/docs.txt +0 -0
  64. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/make_base.py +0 -0
  65. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/mypy.in +0 -0
  66. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/mypy.txt +0 -0
  67. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/nightly.in +0 -0
  68. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/nightly.txt +0 -0
  69. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/static.in +0 -0
  70. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/static.txt +0 -0
  71. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/test.in +0 -0
  72. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/test.txt +0 -0
  73. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/wheels.in +0 -0
  74. {essreduce-25.2.4 → essreduce-25.2.5}/requirements/wheels.txt +0 -0
  75. {essreduce-25.2.4 → essreduce-25.2.5}/resources/logo.svg +0 -0
  76. {essreduce-25.2.4 → essreduce-25.2.5}/setup.cfg +0 -0
  77. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/__init__.py +0 -0
  78. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/data.py +0 -0
  79. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/__init__.py +0 -0
  80. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/raw.py +0 -0
  81. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/roi.py +0 -0
  82. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/workflow.py +0 -0
  83. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/logging.py +0 -0
  84. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/__init__.py +0 -0
  85. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/json_generator.py +0 -0
  86. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/json_nexus.py +0 -0
  87. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/types.py +0 -0
  88. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/workflow.py +0 -0
  89. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/parameter.py +0 -0
  90. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/py.typed +0 -0
  91. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/scripts/grow_nexus.py +0 -0
  92. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/__init__.py +0 -0
  93. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/fakes.py +0 -0
  94. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/simulation.py +0 -0
  95. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/to_events.py +0 -0
  96. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/toa_to_tof.py +0 -0
  97. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/types.py +0 -0
  98. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/ui.py +0 -0
  99. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/uncertainty.py +0 -0
  100. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/__init__.py +0 -0
  101. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_base.py +0 -0
  102. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_binedges_widget.py +0 -0
  103. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_bounds_widget.py +0 -0
  104. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_config.py +0 -0
  105. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_filename_widget.py +0 -0
  106. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_linspace_widget.py +0 -0
  107. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_optional_widget.py +0 -0
  108. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_spinner.py +0 -0
  109. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_string_widget.py +0 -0
  110. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
  111. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_vector_widget.py +0 -0
  112. {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/workflow.py +0 -0
  113. {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/SOURCES.txt +0 -0
  114. {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/dependency_links.txt +0 -0
  115. {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/entry_points.txt +0 -0
  116. {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/requires.txt +0 -0
  117. {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/top_level.txt +0 -0
  118. {essreduce-25.2.4 → essreduce-25.2.5}/tests/live/raw_test.py +0 -0
  119. {essreduce-25.2.4 → essreduce-25.2.5}/tests/live/roi_test.py +0 -0
  120. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_generator_test.py +0 -0
  121. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
  122. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/dataset.json +0 -0
  123. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/detector.json +0 -0
  124. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/entry.json +0 -0
  125. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/event_data.json +0 -0
  126. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/instrument.json +0 -0
  127. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/log.json +0 -0
  128. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_test.py +0 -0
  129. {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/workflow_test.py +0 -0
  130. {essreduce-25.2.4 → essreduce-25.2.5}/tests/package_test.py +0 -0
  131. {essreduce-25.2.4 → essreduce-25.2.5}/tests/scripts/test_grow_nexus.py +0 -0
  132. {essreduce-25.2.4 → essreduce-25.2.5}/tests/time_of_flight/to_events_test.py +0 -0
  133. {essreduce-25.2.4 → essreduce-25.2.5}/tests/time_of_flight/unwrap_test.py +0 -0
  134. {essreduce-25.2.4 → essreduce-25.2.5}/tests/time_of_flight/wfm_test.py +0 -0
  135. {essreduce-25.2.4 → essreduce-25.2.5}/tests/uncertainty_test.py +0 -0
  136. {essreduce-25.2.4 → essreduce-25.2.5}/tests/widget_test.py +0 -0
  137. {essreduce-25.2.4 → essreduce-25.2.5}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: essreduce
3
- Version: 25.2.4
3
+ Version: 25.2.5
4
4
  Summary: Common data reduction tools for the ESS facility
5
5
  Author: Scipp contributors
6
6
  License: BSD 3-Clause License
@@ -180,10 +180,15 @@ def _attempt_to_open_without_locking(
180
180
  # HDF5 tracks file locking flags internally within a single process.
181
181
  # If the same file is opened multiple times, we can get a flag mismatch.
182
182
  # We can try opening without locking, maybe this matches the original flags.
183
- if "file locking flag values don't match" in err.args[0]:
184
- return True
185
- if "file locking 'ignore disabled locks' flag values don't match" in err.args[0]:
186
- return True
183
+ error_message = err.args[0]
184
+ if isinstance(error_message, str):
185
+ if "file locking flag values don't match" in error_message:
186
+ return True
187
+ if (
188
+ "file locking 'ignore disabled locks' flag values don't match"
189
+ in error_message
190
+ ):
191
+ return True
187
192
  return False
188
193
 
189
194
 
@@ -68,7 +68,18 @@ class Accumulator(ABC, Generic[T]):
68
68
  def _do_push(self, value: T) -> None: ...
69
69
 
70
70
  @property
71
- @abstractmethod
71
+ def is_empty(self) -> bool:
72
+ """
73
+ Check if the accumulator is empty.
74
+
75
+ Returns
76
+ -------
77
+ :
78
+ True if the accumulator is empty, False otherwise.
79
+ """
80
+ return False
81
+
82
+ @property
72
83
  def value(self) -> T:
73
84
  """
74
85
  Get the accumulated value.
@@ -77,6 +88,24 @@ class Accumulator(ABC, Generic[T]):
77
88
  -------
78
89
  :
79
90
  Accumulated value.
91
+
92
+ Raises
93
+ ------
94
+ ValueError
95
+ If the accumulator is empty.
96
+ """
97
+ if self.is_empty:
98
+ raise ValueError("Cannot get value from empty accumulator")
99
+ return self._get_value()
100
+
101
+ @abstractmethod
102
+ def _get_value(self) -> T:
103
+ """Return the accumulated value, assuming it exists."""
104
+
105
+ @abstractmethod
106
+ def clear(self) -> None:
107
+ """
108
+ Clear the accumulator, resetting it to its initial state.
80
109
  """
81
110
 
82
111
 
@@ -92,7 +121,10 @@ class EternalAccumulator(Accumulator[T]):
92
121
  self._value: T | None = None
93
122
 
94
123
  @property
95
- def value(self) -> T:
124
+ def is_empty(self) -> bool:
125
+ return self._value is None
126
+
127
+ def _get_value(self) -> T:
96
128
  return deepcopy(self._value)
97
129
 
98
130
  def _do_push(self, value: T) -> None:
@@ -101,6 +133,10 @@ class EternalAccumulator(Accumulator[T]):
101
133
  else:
102
134
  self._value += value
103
135
 
136
+ def clear(self) -> None:
137
+ """Clear the accumulated value."""
138
+ self._value = None
139
+
104
140
 
105
141
  class RollingAccumulator(Accumulator[T]):
106
142
  """
@@ -121,7 +157,10 @@ class RollingAccumulator(Accumulator[T]):
121
157
  self._values: list[T] = []
122
158
 
123
159
  @property
124
- def value(self) -> T:
160
+ def is_empty(self) -> bool:
161
+ return len(self._values) == 0
162
+
163
+ def _get_value(self) -> T:
125
164
  # Naive and potentially slow implementation if values and/or window are large!
126
165
  return sc.reduce(self._values).sum()
127
166
 
@@ -130,6 +169,10 @@ class RollingAccumulator(Accumulator[T]):
130
169
  if len(self._values) > self._window:
131
170
  self._values.pop(0)
132
171
 
172
+ def clear(self) -> None:
173
+ """Clear the accumulated values."""
174
+ self._values = []
175
+
133
176
 
134
177
  class StreamProcessor:
135
178
  """
@@ -299,6 +342,16 @@ class StreamProcessor:
299
342
  self._finalize_workflow[key] = self._accumulators[key].value
300
343
  return self._finalize_workflow.compute(self._target_keys)
301
344
 
345
+ def clear(self) -> None:
346
+ """
347
+ Clear all accumulators, resetting them to their initial state.
348
+
349
+ This is useful for restarting a streaming computation without
350
+ creating a new StreamProcessor instance.
351
+ """
352
+ for accumulator in self._accumulators.values():
353
+ accumulator.clear()
354
+
302
355
 
303
356
  def _find_descendants(
304
357
  workflow: sciline.Pipeline, keys: tuple[sciline.typing.Key, ...]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: essreduce
3
- Version: 25.2.4
3
+ Version: 25.2.5
4
4
  Summary: Common data reduction tools for the ESS facility
5
5
  Author: Scipp contributors
6
6
  License: BSD 3-Clause License
@@ -1,6 +1,7 @@
1
1
  # SPDX-License-Identifier: BSD-3-Clause
2
2
  # Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
3
-
3
+ import os
4
+ import sys
4
5
  from contextlib import contextmanager
5
6
  from io import BytesIO
6
7
  from pathlib import Path
@@ -661,17 +662,34 @@ def test_open_nexus_file_multiple_times(tmp_path: Path, locks: tuple[Any, Any])
661
662
  assert f1.name == f2.name
662
663
 
663
664
 
665
+ def _in_conda_env():
666
+ return 'CONDA_PREFIX' in os.environ
667
+
668
+
669
+ def _test_open_nexus_file_with_mismatched_locking(
670
+ tmp_path: Path, locks: tuple[Any, Any]
671
+ ) -> None:
672
+ from ess.reduce.nexus._nexus_loader import _open_nexus_file
673
+
674
+ path = FilePath(tmp_path / "file.nxs")
675
+ with snx.File(path, "w"):
676
+ pass
677
+
678
+ with _open_nexus_file(path, locking=locks[0]):
679
+ with pytest.raises(OSError, match="flag values don't match"):
680
+ _ = _open_nexus_file(path, locking=locks[1])
681
+
682
+
683
+ @pytest.mark.skipif(
684
+ sys.platform in ("darwin", "win32")
685
+ or (sys.platform == "linux" and _in_conda_env()),
686
+ reason="HDF5 has different file locking flags on MacOS, Windows and Linux(conda)",
687
+ )
664
688
  @pytest.mark.parametrize(
665
689
  "locks",
666
690
  [
667
- (True, False),
668
691
  (True, None),
669
- (False, True),
670
- (False, None),
671
692
  (None, True),
672
- (None, False),
673
- # On a read-only filesystem, this would work:
674
- (NoLockingIfNeeded, False),
675
693
  # This could be supported, but it could cause problems because the first
676
694
  # user expects the file to be locked.
677
695
  (True, NoLockingIfNeeded),
@@ -679,15 +697,31 @@ def test_open_nexus_file_multiple_times(tmp_path: Path, locks: tuple[Any, Any])
679
697
  (NoLockingIfNeeded, True),
680
698
  ],
681
699
  )
682
- def test_open_nexus_file_with_mismatched_locking(
700
+ def test_open_nexus_file_with_mismatched_locking_pypi_linux(
683
701
  tmp_path: Path, locks: tuple[Any, Any]
684
702
  ) -> None:
685
- from ess.reduce.nexus._nexus_loader import _open_nexus_file
703
+ _test_open_nexus_file_with_mismatched_locking(tmp_path, locks)
686
704
 
687
- path = FilePath(tmp_path / "file.nxs")
688
- with snx.File(path, "w"):
689
- pass
690
705
 
691
- with _open_nexus_file(path, locking=locks[0]):
692
- with pytest.raises(OSError, match="flag values don't match"):
693
- _ = _open_nexus_file(path, locking=locks[1])
706
+ @pytest.mark.parametrize(
707
+ "locks",
708
+ [
709
+ (True, False),
710
+ (False, True),
711
+ (False, None),
712
+ (None, False),
713
+ # On a read-only filesystem, this would work:
714
+ (NoLockingIfNeeded, False),
715
+ ],
716
+ )
717
+ def test_open_nexus_file_with_mismatched_locking_all(
718
+ tmp_path: Path, locks: tuple[Any, Any]
719
+ ) -> None:
720
+ _test_open_nexus_file_with_mismatched_locking(tmp_path, locks)
721
+
722
+
723
+ def test_open_nonexisting_file_raises_filenotfounderror():
724
+ from ess.reduce.nexus._nexus_loader import _open_nexus_file
725
+
726
+ with pytest.raises(FileNotFoundError):
727
+ _open_nexus_file(nexus.types.FilePath(Path("doesnotexist.hdf")))
@@ -45,6 +45,18 @@ def test_eternal_accumulator_does_not_modify_pushed_values() -> None:
45
45
  assert sc.identical(var, original)
46
46
 
47
47
 
48
+ def test_eternal_accumulator_clear() -> None:
49
+ accum = streaming.EternalAccumulator()
50
+ var = sc.linspace(dim='x', start=0, stop=1, num=10)
51
+ for i in range(10):
52
+ accum.push(var[i].copy())
53
+ assert sc.identical(accum.value, sc.sum(var))
54
+ accum.clear()
55
+ assert accum.is_empty
56
+ with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
57
+ _ = accum.value
58
+
59
+
48
60
  def test_rolling_accumulator_sums_over_window() -> None:
49
61
  accum = streaming.RollingAccumulator(window=3)
50
62
  var = sc.linspace(dim='x', start=0, stop=1, num=10)
@@ -94,6 +106,52 @@ def test_rolling_accumulator_does_not_modify_pushed_values() -> None:
94
106
  assert sc.identical(var, original)
95
107
 
96
108
 
109
+ def test_rolling_accumulator_clear() -> None:
110
+ accum = streaming.RollingAccumulator(window=3)
111
+ var = sc.linspace(dim='x', start=0, stop=1, num=10)
112
+ for i in range(5):
113
+ accum.push(var[i].copy())
114
+ assert sc.identical(accum.value, var[2:5].sum())
115
+ accum.clear()
116
+ assert accum.is_empty
117
+ with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
118
+ _ = accum.value
119
+
120
+
121
+ def test_eternal_accumulator_is_empty() -> None:
122
+ accum = streaming.EternalAccumulator()
123
+ assert accum.is_empty
124
+ with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
125
+ _ = accum.value
126
+
127
+ var = sc.linspace(dim='x', start=0, stop=1, num=10)
128
+ accum.push(var[0].copy())
129
+ assert not accum.is_empty
130
+ assert sc.identical(accum.value, var[0])
131
+
132
+ accum.clear()
133
+ assert accum.is_empty
134
+ with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
135
+ _ = accum.value
136
+
137
+
138
+ def test_rolling_accumulator_is_empty() -> None:
139
+ accum = streaming.RollingAccumulator(window=3)
140
+ assert accum.is_empty
141
+ with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
142
+ _ = accum.value
143
+
144
+ var = sc.linspace(dim='x', start=0, stop=1, num=10)
145
+ accum.push(var[0].copy())
146
+ assert not accum.is_empty
147
+ assert sc.identical(accum.value, var[0])
148
+
149
+ accum.clear()
150
+ assert accum.is_empty
151
+ with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
152
+ _ = accum.value
153
+
154
+
97
155
  DynamicA = NewType('DynamicA', float)
98
156
  DynamicB = NewType('DynamicB', float)
99
157
  DynamicC = NewType('DynamicC', float)
@@ -161,9 +219,16 @@ def test_StreamProcessor_uses_custom_accumulator() -> None:
161
219
  pass
162
220
 
163
221
  @property
164
- def value(self) -> sc.Variable:
222
+ def is_empty(self) -> bool:
223
+ return False
224
+
225
+ def _get_value(self) -> sc.Variable:
165
226
  return sc.scalar(42)
166
227
 
228
+ def clear(self) -> None:
229
+ # Nothing to clear
230
+ pass
231
+
167
232
  base_workflow = sciline.Pipeline(
168
233
  (make_static_a, make_accum_a, make_accum_b, make_target)
169
234
  )
@@ -370,10 +435,9 @@ def test_StreamProcessor_raises_given_partial_update_for_accumulator() -> None:
370
435
  accumulators=(Target, AccumC), # Target depends on both A and B
371
436
  )
372
437
  # We can update either (A, B) and/or C...
373
- result = streaming_wf.add_chunk({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
374
- assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
375
- assert result[AccumC] is None
376
- result = streaming_wf.add_chunk({DynamicC: sc.scalar(11)})
438
+ streaming_wf.accumulate({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
439
+ streaming_wf.accumulate({DynamicC: sc.scalar(11)})
440
+ result = streaming_wf.finalize()
377
441
  assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
378
442
  assert sc.identical(result[AccumC], sc.scalar(11))
379
443
  result = streaming_wf.add_chunk({DynamicA: sc.scalar(2), DynamicB: sc.scalar(5)})
@@ -428,3 +492,35 @@ def test_StreamProcessor_raises_when_trying_to_update_non_dynamic_key() -> None:
428
492
  match=r'Got non-dynamic keys: {tests.streaming_test.Target}',
429
493
  ):
430
494
  result = streaming_wf.add_chunk({Target: sc.scalar(2)})
495
+
496
+
497
+ def test_StreamProcessor_clear() -> None:
498
+ base_workflow = sciline.Pipeline(
499
+ (make_static_a, make_accum_a, make_accum_b, make_target)
500
+ )
501
+
502
+ # Reset call counter to ensure we can track it properly
503
+ make_static_a.call_count = 0
504
+
505
+ streaming_wf = streaming.StreamProcessor(
506
+ base_workflow=base_workflow,
507
+ dynamic_keys=(DynamicA, DynamicB),
508
+ target_keys=(Target,),
509
+ accumulators=(AccumA, AccumB),
510
+ )
511
+ # Add some data
512
+ result = streaming_wf.add_chunk({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
513
+ assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
514
+ result = streaming_wf.add_chunk({DynamicA: sc.scalar(2), DynamicB: sc.scalar(5)})
515
+ assert sc.identical(result[Target], sc.scalar(2 * 3.0 / 9.0))
516
+
517
+ # Make sure static_a was called exactly once
518
+ assert make_static_a.call_count == 1
519
+
520
+ # Clear and verify we get back to initial state
521
+ streaming_wf.clear()
522
+ result = streaming_wf.add_chunk({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
523
+ assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
524
+
525
+ # Static values should be preserved after clear, so call_count remains 1
526
+ assert make_static_a.call_count == 1
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