Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 0 additions & 2 deletions tests/__init__.py

This file was deleted.

73 changes: 0 additions & 73 deletions tests/common.py

This file was deleted.

70 changes: 70 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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'),
)
100 changes: 48 additions & 52 deletions tests/model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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'
Expand All @@ -297,76 +291,78 @@ 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
del model['dummy_detector']
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(
Expand All @@ -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
Expand Down
Loading
Loading