diff --git a/pyproject.toml b/pyproject.toml index cde86d39..d7827280 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,12 +29,20 @@ requires-python = ">=3.11" # IMPORTANT: # Run 'tox -e deps' after making changes here. This will update requirement files. # Make sure to list one dependency per line. -dependencies = ["plopp", "scipp", "scipy", "lazy-loader"] +dependencies = [ + "plopp>=23.10.0", + "scipp>=24.11.0", + "scipy>=1.12.0", + "lazy-loader>=0.3", +] dynamic = ["version"] [project.optional-dependencies] -test = ["pytest", "scippneutron"] +test = [ + "pytest>=8.0", + "scippneutron>=24.12.0", +] [project.urls] "Bug Tracker" = "https://github.com/scipp/tof/issues" diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index df176980..00000000 --- a/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) diff --git a/tests/common.py b/tests/common.py deleted file mode 100644 index b4e910a8..00000000 --- a/tests/common.py +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) - -import numpy as np -import scipp as sc - -import tof - -Hz = sc.Unit('Hz') -deg = sc.Unit('deg') -meter = sc.Unit('m') -ms = sc.Unit('ms') -two_pi = 2.0 * np.pi * sc.units.rad - - -def make_chopper(topen, tclose, f, phase, distance, name): - aopen = two_pi * sc.concat(topen, dim='cutout').to(unit='s') * f - aclose = two_pi * sc.concat(tclose, dim='cutout').to(unit='s') * f - return tof.Chopper( - frequency=f, - open=aopen, - close=aclose, - phase=phase, - distance=distance, - name=name, - ) - - -def make_source(arrival_times, distance, pulses=1, frequency=None): - # Arrival times are distance * m_over_h * wavelength - return tof.Source.from_neutrons( - birth_times=sc.array( - dims=['event'], - values=[0.0] * len(arrival_times), - unit='s', - ), - wavelengths=arrival_times.to(unit='s') / (distance * tof.utils.m_over_h), - pulses=pulses, - frequency=frequency, - ) - - -def dummy_chopper(): - return tof.Chopper( - frequency=1.0 * Hz, - open=sc.array(dims=['cutout'], values=[0.0], unit='deg'), - close=sc.array(dims=['cutout'], values=[1.0], unit='deg'), - phase=0.0 * deg, - distance=1.0 * meter, - name='dummy_chopper', - ) - - -def dummy_detector(): - return tof.Detector( - distance=1.0 * meter, - name='dummy_detector', - ) - - -def dummy_source(): - return tof.Source.from_neutrons( - birth_times=sc.array( - dims=['event'], - values=[0.0], - unit='s', - ), - wavelengths=sc.array( - dims=['event'], - values=[1.0], - unit='angstrom', - ), - ) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..fd6d061c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2025 Scipp contributors (https://github.com/scipp) + +import numpy as np +import pytest +import scipp as sc + +import tof + + +@pytest.fixture +def make_chopper(): + def _make_chopper(topen, tclose, f, phase, distance, name): + two_pi = 2.0 * np.pi * sc.units.rad + aopen = two_pi * sc.concat(topen, dim='cutout').to(unit='s') * f + aclose = two_pi * sc.concat(tclose, dim='cutout').to(unit='s') * f + return tof.Chopper( + frequency=f, + open=aopen, + close=aclose, + phase=phase, + distance=distance, + name=name, + ) + + return _make_chopper + + +@pytest.fixture +def make_source(): + def _make_source(arrival_times, distance, pulses=1, frequency=None): + # Arrival times are distance * m_over_h * wavelength + return tof.Source.from_neutrons( + birth_times=sc.array( + dims=['event'], values=[0.0] * len(arrival_times), unit='s' + ), + wavelengths=arrival_times.to(unit='s') / (distance * tof.utils.m_over_h), + pulses=pulses, + frequency=frequency, + ) + + return _make_source + + +@pytest.fixture +def dummy_chopper(): + return tof.Chopper( + frequency=sc.scalar(1.0, unit="Hz"), + open=sc.array(dims=['cutout'], values=[0.0], unit='deg'), + close=sc.array(dims=['cutout'], values=[1.0], unit='deg'), + phase=sc.scalar(0.0, unit="deg"), + distance=sc.scalar(1.0, unit="m"), + name='dummy_chopper', + ) + + +@pytest.fixture +def dummy_detector(): + return tof.Detector( + distance=sc.scalar(1.0, unit="m"), + name='dummy_detector', + ) + + +@pytest.fixture +def dummy_source(): + return tof.Source.from_neutrons( + birth_times=sc.array(dims=['event'], values=[0.0], unit='s'), + wavelengths=sc.array(dims=['event'], values=[1.0], unit='angstrom'), + ) diff --git a/tests/model_test.py b/tests/model_test.py index 34935a5f..70a3a8df 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -11,21 +11,13 @@ import tof -from .common import ( - dummy_chopper, - dummy_detector, - dummy_source, - make_chopper, - make_source, -) - Hz = sc.Unit('Hz') deg = sc.Unit('deg') meter = sc.Unit('m') ms = sc.Unit('ms') -def test_one_chopper_one_opening(): +def test_one_chopper_one_opening(make_chopper, make_source): # Make a chopper open from 10-20 ms. Assume zero phase. topen = 10.0 * ms tclose = 20.0 * ms @@ -79,7 +71,7 @@ def test_one_chopper_one_opening(): ) -def test_two_choppers_one_opening(): +def test_two_choppers_one_opening(make_chopper, make_source): # Make a first chopper open from 5-16 ms. Assume zero phase. topen = 5.0 * ms tclose = 16.0 * ms @@ -166,7 +158,7 @@ def test_two_choppers_one_opening(): ) -def test_two_choppers_one_and_two_openings(): +def test_two_choppers_one_and_two_openings(make_chopper, make_source): topen = 5.0 * ms tclose = 16.0 * ms chopper1 = make_chopper( @@ -224,7 +216,7 @@ def test_two_choppers_one_and_two_openings(): ) -def test_neutron_conservation(): +def test_neutron_conservation(make_chopper): N = 100_000 source = tof.Source(facility='ess', neutrons=N) @@ -266,20 +258,22 @@ def test_neutron_conservation(): assert det.sum().value + det.masks['blocked_by_others'].sum().value == N -def test_add_chopper_and_detector(): - chopper = dummy_chopper() - detector = dummy_detector() - model = tof.Model(source=dummy_source()) +def test_add_chopper_and_detector(dummy_chopper, dummy_detector, dummy_source): + chopper = dummy_chopper + detector = dummy_detector + model = tof.Model(source=dummy_source) model.add(chopper) assert 'dummy_chopper' in model.choppers model.add(detector) assert 'dummy_detector' in model.detectors -def test_add_components_with_same_name_raises(): - chopper = dummy_chopper() - detector = dummy_detector() - model = tof.Model(source=dummy_source()) +def test_add_components_with_same_name_raises( + dummy_chopper, dummy_detector, dummy_source +): + chopper = dummy_chopper + detector = dummy_detector + model = tof.Model(source=dummy_source) model.add(chopper) with pytest.raises( KeyError, match='Component with name dummy_chopper already exists' @@ -297,33 +291,35 @@ def test_add_components_with_same_name_raises(): model.add(detector2) -def test_create_model_with_duplicate_component_names_raises(): - chopper = dummy_chopper() - detector = dummy_detector() +def test_create_model_with_duplicate_component_names_raises( + dummy_chopper, dummy_detector, dummy_source +): + chopper = dummy_chopper + detector = dummy_detector with pytest.raises( ValueError, match="More than one component named 'dummy_chopper' found" ): - tof.Model(source=dummy_source(), choppers=[chopper, chopper]) + tof.Model(source=dummy_source, choppers=[chopper, chopper]) with pytest.raises( ValueError, match="More than one component named 'dummy_detector' found" ): - tof.Model(source=dummy_source(), detectors=[detector, detector]) + tof.Model(source=dummy_source, detectors=[detector, detector]) -def test_iter(): - chopper = dummy_chopper() - detector = dummy_detector() - model = tof.Model(source=dummy_source()) +def test_iter(dummy_chopper, dummy_detector, dummy_source): + chopper = dummy_chopper + detector = dummy_detector + model = tof.Model(source=dummy_source) model.add(chopper) assert 'dummy_chopper' in model.choppers model.add(detector) assert 'dummy_detector' in model.detectors -def test_remove(): - chopper = dummy_chopper() - detector = dummy_detector() - model = tof.Model(source=dummy_source(), choppers=[chopper], detectors=[detector]) +def test_remove(dummy_chopper, dummy_detector, dummy_source): + chopper = dummy_chopper + detector = dummy_detector + model = tof.Model(source=dummy_source, choppers=[chopper], detectors=[detector]) del model['dummy_chopper'] assert 'dummy_chopper' not in model assert 'dummy_detector' in model @@ -331,42 +327,42 @@ def test_remove(): assert 'dummy_detector' not in model -def test_getitem(): - chopper = dummy_chopper() - detector = dummy_detector() - model = tof.Model(source=dummy_source(), choppers=[chopper], detectors=[detector]) +def test_getitem(dummy_chopper, dummy_detector, dummy_source): + chopper = dummy_chopper + detector = dummy_detector + model = tof.Model(source=dummy_source, choppers=[chopper], detectors=[detector]) assert model['dummy_chopper'] is chopper assert model['dummy_detector'] is detector with pytest.raises(KeyError, match='No component with name foo'): model['foo'] -def test_input_can_be_single_component(): - chopper = dummy_chopper() - detector = dummy_detector() - model = tof.Model(source=dummy_source(), choppers=chopper, detectors=detector) +def test_input_can_be_single_component(dummy_chopper, dummy_detector, dummy_source): + chopper = dummy_chopper + detector = dummy_detector + model = tof.Model(source=dummy_source, choppers=chopper, detectors=detector) assert 'dummy_chopper' in model.choppers assert 'dummy_detector' in model.detectors -def test_bad_input_type_raises(): - chopper = dummy_chopper() - detector = dummy_detector() +def test_bad_input_type_raises(dummy_chopper, dummy_detector, dummy_source): + chopper = dummy_chopper + detector = dummy_detector with pytest.raises(TypeError, match='Invalid input type'): - _ = tof.Model(source=dummy_source(), choppers='bad chopper') + _ = tof.Model(source=dummy_source, choppers='bad chopper') with pytest.raises(TypeError, match='Invalid input type'): - _ = tof.Model(source=dummy_source(), choppers=[chopper], detectors='abc') + _ = tof.Model(source=dummy_source, choppers=[chopper], detectors='abc') with pytest.raises(TypeError, match='Invalid input type'): - _ = tof.Model(source=dummy_source(), choppers=[chopper, 'bad chopper']) + _ = tof.Model(source=dummy_source, choppers=[chopper, 'bad chopper']) with pytest.raises(TypeError, match='Invalid input type'): - _ = tof.Model(source=dummy_source(), detectors=(1234, detector)) + _ = tof.Model(source=dummy_source, detectors=(1234, detector)) with pytest.raises(TypeError, match='Invalid input type'): - _ = tof.Model(source=dummy_source(), choppers=[detector]) + _ = tof.Model(source=dummy_source, choppers=[detector]) with pytest.raises(TypeError, match='Invalid input type'): - _ = tof.Model(source=dummy_source(), detectors=[chopper]) + _ = tof.Model(source=dummy_source, detectors=[chopper]) -def test_model_repr_does_not_raise(): +def test_model_repr_does_not_raise(make_chopper, make_source): N = 10_000 source = tof.Source(facility='ess', neutrons=N) chopper1 = make_chopper( @@ -392,7 +388,7 @@ def test_model_repr_does_not_raise(): assert repr(model) is not None -def test_component_distance(): +def test_component_distance(make_chopper, make_source): # Make a chopper open from 10-20 ms. Assume zero phase. topen = 10.0 * ms tclose = 20.0 * ms diff --git a/tests/result_test.py b/tests/result_test.py index c8dc0bd3..f72872ab 100644 --- a/tests/result_test.py +++ b/tests/result_test.py @@ -8,8 +8,6 @@ import tof -from .common import make_chopper, make_source - Hz = sc.Unit('Hz') deg = sc.Unit('deg') meter = sc.Unit('m') @@ -19,7 +17,7 @@ @pytest.fixture -def chopper(): +def chopper(make_chopper): return make_chopper( topen=[topen], tclose=[tclose], @@ -36,7 +34,7 @@ def detector(): @pytest.fixture -def source(chopper): +def source(chopper, make_source): return make_source( arrival_times=sc.concat( [0.9 * topen, 0.5 * (topen + tclose), 1.1 * tclose], dim='event' @@ -46,7 +44,7 @@ def source(chopper): @pytest.fixture -def multi_pulse_source(chopper): +def multi_pulse_source(chopper, make_source): return make_source( arrival_times=sc.concat( [0.9 * topen, 0.5 * (topen + tclose), 1.1 * tclose], dim='event'