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.
- {essreduce-25.2.4/src/essreduce.egg-info → essreduce-25.2.5}/PKG-INFO +1 -1
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/_nexus_loader.py +9 -4
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/streaming.py +56 -3
- {essreduce-25.2.4 → essreduce-25.2.5/src/essreduce.egg-info}/PKG-INFO +1 -1
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/nexus_loader_test.py +49 -15
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/streaming_test.py +101 -5
- {essreduce-25.2.4 → essreduce-25.2.5}/.copier-answers.ess.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.copier-answers.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/ISSUE_TEMPLATE/blank.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/dependabot.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/ci.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/docs.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/nightly_at_main.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/nightly_at_release.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/python-version-ci +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/release.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/test.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/unpinned.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.github/workflows/weekly_windows_macos.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.gitignore +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.pre-commit-config.yaml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/.python-version +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/CODE_OF_CONDUCT.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/CONTRIBUTING.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/LICENSE +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/MANIFEST.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/README.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/conda/meta.yaml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/anaconda-icon.js +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/favicon.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/logo-dark.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/_static/logo.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/_templates/class-template.rst +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/_templates/doc_version.html +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/_templates/module-template.rst +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/about/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/api-reference/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/conf.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/coding-conventions.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/dependency-management.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/getting-started.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/gui.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/developer/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/dream.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/frame-unwrapping.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/tof/wfm.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/docs/user-guide/widget.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/pyproject.toml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/base.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/base.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/basetest.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/basetest.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/ci.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/ci.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/dev.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/dev.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/docs.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/docs.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/make_base.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/mypy.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/mypy.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/nightly.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/nightly.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/static.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/static.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/test.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/test.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/wheels.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/requirements/wheels.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/resources/logo.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/setup.cfg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/data.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/raw.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/roi.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/live/workflow.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/logging.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/json_generator.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/json_nexus.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/types.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/nexus/workflow.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/parameter.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/py.typed +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/scripts/grow_nexus.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/fakes.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/simulation.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/to_events.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/toa_to_tof.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/time_of_flight/types.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/ui.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/uncertainty.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_base.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_binedges_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_bounds_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_config.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_filename_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_linspace_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_optional_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_spinner.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_string_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/widgets/_vector_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/ess/reduce/workflow.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/SOURCES.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/dependency_links.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/entry_points.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/requires.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/src/essreduce.egg-info/top_level.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/live/raw_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/live/roi_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_generator_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/dataset.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/detector.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/entry.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/event_data.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/instrument.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_examples/log.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/json_nexus_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/nexus/workflow_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/package_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/scripts/test_grow_nexus.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/time_of_flight/to_events_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/time_of_flight/unwrap_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/time_of_flight/wfm_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/uncertainty_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tests/widget_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.5}/tox.ini +0 -0
|
@@ -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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,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
|
|
700
|
+
def test_open_nexus_file_with_mismatched_locking_pypi_linux(
|
|
683
701
|
tmp_path: Path, locks: tuple[Any, Any]
|
|
684
702
|
) -> None:
|
|
685
|
-
|
|
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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|