essreduce 25.2.4__tar.gz → 25.2.6__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.6}/PKG-INFO +1 -1
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/live/raw.py +34 -65
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/nexus/_nexus_loader.py +9 -4
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/streaming.py +114 -3
- {essreduce-25.2.4 → essreduce-25.2.6/src/essreduce.egg-info}/PKG-INFO +1 -1
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/live/raw_test.py +2 -3
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/live/roi_test.py +4 -5
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/nexus_loader_test.py +49 -15
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/streaming_test.py +125 -5
- {essreduce-25.2.4 → essreduce-25.2.6}/.copier-answers.ess.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.copier-answers.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/ISSUE_TEMPLATE/blank.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/dependabot.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/ci.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/docs.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/nightly_at_main.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/nightly_at_release.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/python-version-ci +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/release.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/test.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/unpinned.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.github/workflows/weekly_windows_macos.yml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.gitignore +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.pre-commit-config.yaml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/.python-version +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/CODE_OF_CONDUCT.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/CONTRIBUTING.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/LICENSE +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/MANIFEST.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/README.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/conda/meta.yaml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/_static/anaconda-icon.js +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/_static/favicon.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/_static/logo-dark.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/_static/logo.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/_templates/class-template.rst +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/_templates/doc_version.html +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/_templates/module-template.rst +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/about/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/api-reference/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/conf.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/developer/coding-conventions.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/developer/dependency-management.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/developer/getting-started.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/developer/gui.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/developer/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/user-guide/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/user-guide/tof/dream.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/user-guide/tof/frame-unwrapping.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/user-guide/tof/index.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/user-guide/tof/wfm.ipynb +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/docs/user-guide/widget.md +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/pyproject.toml +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/base.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/base.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/basetest.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/basetest.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/ci.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/ci.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/dev.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/dev.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/docs.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/docs.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/make_base.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/mypy.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/mypy.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/nightly.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/nightly.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/static.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/static.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/test.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/test.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/wheels.in +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/requirements/wheels.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/resources/logo.svg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/setup.cfg +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/data.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/live/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/live/roi.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/live/workflow.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/logging.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/nexus/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/nexus/json_generator.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/nexus/json_nexus.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/nexus/types.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/nexus/workflow.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/parameter.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/py.typed +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/scripts/grow_nexus.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/time_of_flight/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/time_of_flight/fakes.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/time_of_flight/simulation.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/time_of_flight/to_events.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/time_of_flight/toa_to_tof.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/time_of_flight/types.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/ui.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/uncertainty.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/__init__.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_base.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_binedges_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_bounds_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_config.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_filename_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_linspace_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_optional_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_spinner.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_string_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/widgets/_vector_widget.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/ess/reduce/workflow.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/essreduce.egg-info/SOURCES.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/essreduce.egg-info/dependency_links.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/essreduce.egg-info/entry_points.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/essreduce.egg-info/requires.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/src/essreduce.egg-info/top_level.txt +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_generator_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_examples/dataset.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_examples/detector.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_examples/entry.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_examples/event_data.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_examples/instrument.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_examples/log.json +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/json_nexus_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/nexus/workflow_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/package_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/scripts/test_grow_nexus.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/time_of_flight/to_events_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/time_of_flight/unwrap_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/time_of_flight/wfm_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/uncertainty_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tests/widget_test.py +0 -0
- {essreduce-25.2.4 → essreduce-25.2.6}/tox.ini +0 -0
|
@@ -15,14 +15,12 @@ options:
|
|
|
15
15
|
- `'xy_plane'`: Project the data onto the x-y plane, i.e., perpendicular to the beam.
|
|
16
16
|
- `'cylinder_mantle_z'`: Project the data onto the mantle of a cylinder aligned with the
|
|
17
17
|
z-axis.
|
|
18
|
-
-
|
|
19
|
-
flatten dimensions of the data.
|
|
18
|
+
- A callable, e.g., to select and flatten dimensions of the data.
|
|
20
19
|
"""
|
|
21
20
|
|
|
22
21
|
from __future__ import annotations
|
|
23
22
|
|
|
24
23
|
from collections.abc import Callable, Sequence
|
|
25
|
-
from dataclasses import dataclass, field
|
|
26
24
|
from math import ceil
|
|
27
25
|
from typing import Literal, NewType
|
|
28
26
|
|
|
@@ -140,44 +138,6 @@ class Histogrammer:
|
|
|
140
138
|
return self._hist(replicated, coords=self._coords) / self.replicas
|
|
141
139
|
|
|
142
140
|
|
|
143
|
-
@dataclass
|
|
144
|
-
class LogicalView:
|
|
145
|
-
"""
|
|
146
|
-
Logical view of a multi-dimensional detector.
|
|
147
|
-
|
|
148
|
-
Instances can be used as a "projection" function for a detector view.
|
|
149
|
-
|
|
150
|
-
Parameters
|
|
151
|
-
----------
|
|
152
|
-
fold:
|
|
153
|
-
Dimensions to fold. This is useful is the raw data has a single dimension that
|
|
154
|
-
corresponds to multiple dimensions in the logical view.
|
|
155
|
-
transpose:
|
|
156
|
-
Dimensions to transpose. This is useful for reordering dimensions.
|
|
157
|
-
select:
|
|
158
|
-
Dimensions with associated index to select from the data. This extracts a slice
|
|
159
|
-
of the data for each given dimension.
|
|
160
|
-
flatten:
|
|
161
|
-
Dimensions to flatten.
|
|
162
|
-
"""
|
|
163
|
-
|
|
164
|
-
fold: dict[str, int] | None = None
|
|
165
|
-
transpose: tuple[str, ...] | None = None
|
|
166
|
-
select: dict[str, int] = field(default_factory=dict)
|
|
167
|
-
flatten: dict[str, list[str]] = field(default_factory=dict)
|
|
168
|
-
|
|
169
|
-
def __call__(self, da: sc.DataArray) -> sc.DataArray:
|
|
170
|
-
if self.fold is not None:
|
|
171
|
-
da = da.fold(da.dim, sizes=self.fold)
|
|
172
|
-
if self.transpose is not None:
|
|
173
|
-
da = da.transpose(self.transpose)
|
|
174
|
-
for dim, index in self.select.items():
|
|
175
|
-
da = da[dim, index]
|
|
176
|
-
for to, dims in self.flatten.items():
|
|
177
|
-
da = da.flatten(dims, to=to)
|
|
178
|
-
return da.copy()
|
|
179
|
-
|
|
180
|
-
|
|
181
141
|
class Detector:
|
|
182
142
|
def __init__(self, detector_number: sc.Variable):
|
|
183
143
|
self._data = sc.DataArray(
|
|
@@ -295,7 +255,7 @@ class RollingDetectorView(Detector):
|
|
|
295
255
|
else:
|
|
296
256
|
indices = sc.ones(sizes=self.data.sizes, dtype='int32', unit=None)
|
|
297
257
|
indices = sc.cumsum(indices, mode='exclusive')
|
|
298
|
-
if
|
|
258
|
+
if self._projection is not None:
|
|
299
259
|
indices = self._projection(indices)
|
|
300
260
|
return roi.ROIFilter(indices=indices, norm=norm)
|
|
301
261
|
|
|
@@ -357,17 +317,23 @@ class RollingDetectorView(Detector):
|
|
|
357
317
|
)
|
|
358
318
|
|
|
359
319
|
@staticmethod
|
|
360
|
-
def
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
320
|
+
def from_detector_with_projection(
|
|
321
|
+
projection: Callable[[sc.DataArray], sc.DataArray] | None,
|
|
322
|
+
) -> Callable[
|
|
323
|
+
[CalibratedDetector[SampleRun], RollingDetectorViewWindow], RollingDetectorView
|
|
324
|
+
]:
|
|
325
|
+
def factory(
|
|
326
|
+
detector: CalibratedDetector[SampleRun],
|
|
327
|
+
window: RollingDetectorViewWindow,
|
|
328
|
+
) -> RollingDetectorView:
|
|
329
|
+
"""Helper for constructing via a Sciline workflow."""
|
|
330
|
+
return RollingDetectorView(
|
|
331
|
+
detector_number=detector.coords['detector_number'],
|
|
332
|
+
window=window,
|
|
333
|
+
projection=projection,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return factory
|
|
371
337
|
|
|
372
338
|
@staticmethod
|
|
373
339
|
def from_nexus(
|
|
@@ -375,7 +341,9 @@ class RollingDetectorView(Detector):
|
|
|
375
341
|
*,
|
|
376
342
|
detector_name: str,
|
|
377
343
|
window: int,
|
|
378
|
-
projection: Literal['xy_plane', 'cylinder_mantle_z']
|
|
344
|
+
projection: Literal['xy_plane', 'cylinder_mantle_z']
|
|
345
|
+
| Callable[[sc.DataArray], sc.DataArray]
|
|
346
|
+
| None = None,
|
|
379
347
|
resolution: dict[str, int] | None = None,
|
|
380
348
|
pixel_noise: Literal['cylindrical'] | sc.Variable | None = None,
|
|
381
349
|
) -> RollingDetectorView:
|
|
@@ -396,10 +364,12 @@ class RollingDetectorView(Detector):
|
|
|
396
364
|
Size of the rolling window.
|
|
397
365
|
projection:
|
|
398
366
|
Projection to use for the detector data. This can be a string selecting a
|
|
399
|
-
predefined projection or a
|
|
367
|
+
predefined projection or a function that takes a DataArray and returns a
|
|
368
|
+
DataArray. The predefined projections are 'xy_plane' and
|
|
369
|
+
'cylinder_mantle_z'.
|
|
400
370
|
resolution:
|
|
401
|
-
Resolution to use for histogramming the detector data.
|
|
402
|
-
|
|
371
|
+
Resolution to use for histogramming the detector data. Only required for
|
|
372
|
+
'xy_plane' and 'cylinder_mantle_z' projections.
|
|
403
373
|
pixel_noise:
|
|
404
374
|
Noise to add to the pixel positions. This can be a scalar value to add
|
|
405
375
|
Gaussian noise to the pixel positions or the string 'cylindrical' to add
|
|
@@ -413,13 +383,7 @@ class RollingDetectorView(Detector):
|
|
|
413
383
|
noise_replica_count = 16
|
|
414
384
|
wf = GenericNeXusWorkflow(run_types=[SampleRun], monitor_types=[])
|
|
415
385
|
wf[RollingDetectorViewWindow] = window
|
|
416
|
-
if
|
|
417
|
-
wf[LogicalView] = projection
|
|
418
|
-
wf[NeXusTransformation[snx.NXdetector, SampleRun]] = NeXusTransformation[
|
|
419
|
-
snx.NXdetector, SampleRun
|
|
420
|
-
](sc.scalar(1))
|
|
421
|
-
wf.insert(RollingDetectorView.from_detector_and_logical_view)
|
|
422
|
-
elif projection == 'cylinder_mantle_z':
|
|
386
|
+
if projection == 'cylinder_mantle_z':
|
|
423
387
|
wf.insert(make_cylinder_mantle_coords)
|
|
424
388
|
wf.insert(RollingDetectorView.from_detector_and_histogrammer)
|
|
425
389
|
wf[DetectorViewResolution] = resolution
|
|
@@ -428,7 +392,12 @@ class RollingDetectorView(Detector):
|
|
|
428
392
|
wf.insert(RollingDetectorView.from_detector_and_histogrammer)
|
|
429
393
|
wf[DetectorViewResolution] = resolution
|
|
430
394
|
else:
|
|
431
|
-
|
|
395
|
+
wf[NeXusTransformation[snx.NXdetector, SampleRun]] = NeXusTransformation[
|
|
396
|
+
snx.NXdetector, SampleRun
|
|
397
|
+
](sc.scalar(1))
|
|
398
|
+
wf.insert(
|
|
399
|
+
RollingDetectorView.from_detector_with_projection(projection=projection)
|
|
400
|
+
)
|
|
432
401
|
if isinstance(pixel_noise, sc.Variable):
|
|
433
402
|
wf.insert(gaussian_position_noise)
|
|
434
403
|
wf[PositionNoiseSigma] = pixel_noise
|
|
@@ -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,68 @@ 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
|
+
|
|
176
|
+
|
|
177
|
+
class MinAccumulator(Accumulator):
|
|
178
|
+
"""Keeps the minimum value seen so far.
|
|
179
|
+
|
|
180
|
+
Only supports scalar values.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
184
|
+
super().__init__(**kwargs)
|
|
185
|
+
self._cur_min: sc.Variable | None = None
|
|
186
|
+
|
|
187
|
+
def _do_push(self, value: sc.Variable) -> None:
|
|
188
|
+
if self._cur_min is None:
|
|
189
|
+
self._cur_min = value
|
|
190
|
+
else:
|
|
191
|
+
self._cur_min = min(self._cur_min, value)
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def is_empty(self) -> bool:
|
|
195
|
+
"""Check if the accumulator has collected a minimum value."""
|
|
196
|
+
return self._cur_min is None
|
|
197
|
+
|
|
198
|
+
def _get_value(self) -> Any:
|
|
199
|
+
return self._cur_min
|
|
200
|
+
|
|
201
|
+
def clear(self) -> None:
|
|
202
|
+
"""Clear the accumulated minimum value."""
|
|
203
|
+
self._cur_min = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class MaxAccumulator(Accumulator):
|
|
207
|
+
"""Keeps the maximum value seen so far.
|
|
208
|
+
|
|
209
|
+
Only supports scalar values.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
213
|
+
super().__init__(**kwargs)
|
|
214
|
+
self._cur_max: sc.Variable | None = None
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def is_empty(self) -> bool:
|
|
218
|
+
"""Check if the accumulator has collected a maximum value."""
|
|
219
|
+
return self._cur_max is None
|
|
220
|
+
|
|
221
|
+
def _do_push(self, value: sc.Variable) -> None:
|
|
222
|
+
if self._cur_max is None:
|
|
223
|
+
self._cur_max = value
|
|
224
|
+
else:
|
|
225
|
+
self._cur_max = max(self._cur_max, value)
|
|
226
|
+
|
|
227
|
+
def _get_value(self) -> sc.Variable | None:
|
|
228
|
+
return self._cur_max
|
|
229
|
+
|
|
230
|
+
def clear(self) -> None:
|
|
231
|
+
"""Clear the accumulated maximum value."""
|
|
232
|
+
self._cur_max = None
|
|
233
|
+
|
|
133
234
|
|
|
134
235
|
class StreamProcessor:
|
|
135
236
|
"""
|
|
@@ -299,6 +400,16 @@ class StreamProcessor:
|
|
|
299
400
|
self._finalize_workflow[key] = self._accumulators[key].value
|
|
300
401
|
return self._finalize_workflow.compute(self._target_keys)
|
|
301
402
|
|
|
403
|
+
def clear(self) -> None:
|
|
404
|
+
"""
|
|
405
|
+
Clear all accumulators, resetting them to their initial state.
|
|
406
|
+
|
|
407
|
+
This is useful for restarting a streaming computation without
|
|
408
|
+
creating a new StreamProcessor instance.
|
|
409
|
+
"""
|
|
410
|
+
for accumulator in self._accumulators.values():
|
|
411
|
+
accumulator.clear()
|
|
412
|
+
|
|
302
413
|
|
|
303
414
|
def _find_descendants(
|
|
304
415
|
workflow: sciline.Pipeline, keys: tuple[sciline.typing.Key, ...]
|
|
@@ -354,13 +354,12 @@ def test_ROIFilter_from_trivial_RollingDetectorView() -> None:
|
|
|
354
354
|
assert sc.identical(scale, sc.ones(dims=['detector_number'], shape=[2]))
|
|
355
355
|
|
|
356
356
|
|
|
357
|
-
def
|
|
358
|
-
logical_view = raw.LogicalView(select={'z': 0})
|
|
357
|
+
def test_ROIFilter_from_RollingDetectorView_with_custom_projection() -> None:
|
|
359
358
|
detector_number = sc.array(
|
|
360
359
|
dims=['x', 'y', 'z'], values=[[[1, 2], [3, 4]], [[5, 6], [7, 8]]], unit=None
|
|
361
360
|
)
|
|
362
361
|
view = raw.RollingDetectorView(
|
|
363
|
-
detector_number=detector_number, window=2, projection=
|
|
362
|
+
detector_number=detector_number, window=2, projection=lambda da: da['z', 0]
|
|
364
363
|
)
|
|
365
364
|
roi_filter = view.make_roi_filter()
|
|
366
365
|
data = detector_number.copy()
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import pytest
|
|
4
4
|
import scipp as sc
|
|
5
5
|
|
|
6
|
-
from ess.reduce.live import
|
|
6
|
+
from ess.reduce.live import roi
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@pytest.fixture
|
|
@@ -146,13 +146,12 @@ def test_apply_selection_fails_with_out_of_bounds_index():
|
|
|
146
146
|
roi.apply_selection(data, selection=selection)
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return raw.LogicalView(fold={'x': 3, 'y': 4, 'z': 2}, select={'z': 0})
|
|
149
|
+
def logical_view(da: sc.DataArray) -> sc.DataArray:
|
|
150
|
+
return da.fold(da.dim, sizes={'x': 3, 'y': 4, 'z': 2})['z', 0]
|
|
152
151
|
|
|
153
152
|
|
|
154
153
|
@pytest.fixture
|
|
155
|
-
def roi_filter(
|
|
154
|
+
def roi_filter() -> roi.ROIFilter:
|
|
156
155
|
indices = sc.ones(sizes={'detector_number': 24}, dtype='int32', unit=None)
|
|
157
156
|
indices = sc.cumsum(indices, mode='exclusive')
|
|
158
157
|
return roi.ROIFilter(logical_view(indices))
|
|
@@ -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,76 @@ 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
|
+
|
|
155
|
+
def test_min_accumulator() -> None:
|
|
156
|
+
accum = streaming.MinAccumulator()
|
|
157
|
+
var = sc.array(dims=['x'], values=[1.0, 2.0, 3.0, 2.0, 1.0])
|
|
158
|
+
for scalar_var in var:
|
|
159
|
+
accum.push(scalar_var)
|
|
160
|
+
assert sc.identical(accum.value, sc.min(var))
|
|
161
|
+
accum.clear()
|
|
162
|
+
assert accum.is_empty
|
|
163
|
+
with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
|
|
164
|
+
_ = accum.value
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_max_accumulator() -> None:
|
|
168
|
+
accum = streaming.MaxAccumulator()
|
|
169
|
+
var = sc.array(dims=['x'], values=[1.0, 2.0, 3.0, 2.0, 1.0])
|
|
170
|
+
for scalar_var in var:
|
|
171
|
+
accum.push(scalar_var)
|
|
172
|
+
assert sc.identical(accum.value, sc.max(var))
|
|
173
|
+
accum.clear()
|
|
174
|
+
assert accum.is_empty
|
|
175
|
+
with pytest.raises(ValueError, match="Cannot get value from empty accumulator"):
|
|
176
|
+
_ = accum.value
|
|
177
|
+
|
|
178
|
+
|
|
97
179
|
DynamicA = NewType('DynamicA', float)
|
|
98
180
|
DynamicB = NewType('DynamicB', float)
|
|
99
181
|
DynamicC = NewType('DynamicC', float)
|
|
@@ -161,9 +243,16 @@ def test_StreamProcessor_uses_custom_accumulator() -> None:
|
|
|
161
243
|
pass
|
|
162
244
|
|
|
163
245
|
@property
|
|
164
|
-
def
|
|
246
|
+
def is_empty(self) -> bool:
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
def _get_value(self) -> sc.Variable:
|
|
165
250
|
return sc.scalar(42)
|
|
166
251
|
|
|
252
|
+
def clear(self) -> None:
|
|
253
|
+
# Nothing to clear
|
|
254
|
+
pass
|
|
255
|
+
|
|
167
256
|
base_workflow = sciline.Pipeline(
|
|
168
257
|
(make_static_a, make_accum_a, make_accum_b, make_target)
|
|
169
258
|
)
|
|
@@ -370,10 +459,9 @@ def test_StreamProcessor_raises_given_partial_update_for_accumulator() -> None:
|
|
|
370
459
|
accumulators=(Target, AccumC), # Target depends on both A and B
|
|
371
460
|
)
|
|
372
461
|
# We can update either (A, B) and/or C...
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
result = streaming_wf.add_chunk({DynamicC: sc.scalar(11)})
|
|
462
|
+
streaming_wf.accumulate({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
|
|
463
|
+
streaming_wf.accumulate({DynamicC: sc.scalar(11)})
|
|
464
|
+
result = streaming_wf.finalize()
|
|
377
465
|
assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
|
|
378
466
|
assert sc.identical(result[AccumC], sc.scalar(11))
|
|
379
467
|
result = streaming_wf.add_chunk({DynamicA: sc.scalar(2), DynamicB: sc.scalar(5)})
|
|
@@ -428,3 +516,35 @@ def test_StreamProcessor_raises_when_trying_to_update_non_dynamic_key() -> None:
|
|
|
428
516
|
match=r'Got non-dynamic keys: {tests.streaming_test.Target}',
|
|
429
517
|
):
|
|
430
518
|
result = streaming_wf.add_chunk({Target: sc.scalar(2)})
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def test_StreamProcessor_clear() -> None:
|
|
522
|
+
base_workflow = sciline.Pipeline(
|
|
523
|
+
(make_static_a, make_accum_a, make_accum_b, make_target)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Reset call counter to ensure we can track it properly
|
|
527
|
+
make_static_a.call_count = 0
|
|
528
|
+
|
|
529
|
+
streaming_wf = streaming.StreamProcessor(
|
|
530
|
+
base_workflow=base_workflow,
|
|
531
|
+
dynamic_keys=(DynamicA, DynamicB),
|
|
532
|
+
target_keys=(Target,),
|
|
533
|
+
accumulators=(AccumA, AccumB),
|
|
534
|
+
)
|
|
535
|
+
# Add some data
|
|
536
|
+
result = streaming_wf.add_chunk({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
|
|
537
|
+
assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
|
|
538
|
+
result = streaming_wf.add_chunk({DynamicA: sc.scalar(2), DynamicB: sc.scalar(5)})
|
|
539
|
+
assert sc.identical(result[Target], sc.scalar(2 * 3.0 / 9.0))
|
|
540
|
+
|
|
541
|
+
# Make sure static_a was called exactly once
|
|
542
|
+
assert make_static_a.call_count == 1
|
|
543
|
+
|
|
544
|
+
# Clear and verify we get back to initial state
|
|
545
|
+
streaming_wf.clear()
|
|
546
|
+
result = streaming_wf.add_chunk({DynamicA: sc.scalar(1), DynamicB: sc.scalar(4)})
|
|
547
|
+
assert sc.identical(result[Target], sc.scalar(2 * 1.0 / 4.0))
|
|
548
|
+
|
|
549
|
+
# Static values should be preserved after clear, so call_count remains 1
|
|
550
|
+
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
|