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
8 changes: 7 additions & 1 deletion src/tof/chopper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, replace
from enum import Enum

import scipp as sc
Expand Down Expand Up @@ -215,3 +216,8 @@ def __repr__(self) -> str:

def __str__(self) -> str:
return self.__repr__()

def __getitem__(self, val: int | slice | tuple[str, int | slice]) -> ChopperReading:
if isinstance(val, int):
val = ('pulse', val)
return replace(self, data=self.data[val])
10 changes: 9 additions & 1 deletion src/tof/detector.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, replace

import scipp as sc

Expand Down Expand Up @@ -53,3 +54,10 @@ def __repr__(self) -> str:

def __str__(self) -> str:
return self.__repr__()

def __getitem__(
self, val: int | slice | tuple[str, int | slice]
) -> DetectorReading:
if isinstance(val, int):
val = ('pulse', val)
return replace(self, data=self.data[val])
3 changes: 3 additions & 0 deletions src/tof/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ def run(self):
container[c.name]['data'] = self.source.data.copy(deep=False)
t = birth_time + (c.distance / speed).to(unit=birth_time.unit, copy=False)
container[c.name]['data'].coords['toa'] = t
container[c.name]['data'].coords['eto'] = t % (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is t ever negative?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess in principle, since the user is free to define their own time distribution for a pulse, t could be negative?
It is also possible to edit the data in the source before running the model.
E.g.

source = tof.Source(facility='ESS', neutrons=1_000_000)
source.data.coords['birth_time'] -= sc.scalar(1000, unit='us')

makes a source that starts at -1000 us instead of 0.

Should the eto then be (t - birth_time.min()) % period?
How do we solve the potential case where, if we have low statistics, the first pulse may not have any neutrons at the very start (because of sampling), but the second pulse does. This would attribute the early neutrons in the second pulse to the wrong period?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but my point was that mod will give a negative result in those cases, which is not what you want, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow the negative result part... -3 % 10 gives 7.
The period will always be positive.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was mixing things up!

1 / self.source.frequency
).to(unit=t.unit, copy=False)
container[c.name]['data'].coords['distance'] = c.distance
# TODO: remove 'tof' coordinate once deprecation period is over
container[c.name]['data'].coords['tof'] = t
Expand Down
25 changes: 24 additions & 1 deletion src/tof/reading.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)

from __future__ import annotations

from dataclasses import dataclass

import plopp as pp
Expand Down Expand Up @@ -57,7 +59,9 @@ def __repr__(self) -> str:
def __str__(self) -> str:
return self.__repr__()

def __getitem__(self, val):
def __getitem__(self, val: int | slice | tuple[str, int | slice]) -> ReadingField:
if isinstance(val, int):
val = ('pulse', val)
return self.__class__(data=self.data[val], dim=self.dim)


Expand All @@ -80,18 +84,37 @@ class ComponentReading:

@property
def toa(self) -> ReadingField:
"""
Time of arrival of the neutrons at the component.
"""
return _make_reading_field(self.data, dim="toa")

@property
def eto(self) -> ReadingField:
"""
Event time offset of the neutrons at the component (= toa modulo pulse period).
"""
return _make_reading_field(self.data, dim="eto")

@property
def wavelength(self) -> ReadingField:
"""
Wavelength of the neutrons at the component.
"""
return _make_reading_field(self.data, dim="wavelength")

@property
def birth_time(self) -> ReadingField:
"""
Birth time of the neutrons at the source.
"""
return _make_reading_field(self.data, dim="birth_time")

@property
def speed(self) -> ReadingField:
"""
Speed of the neutrons at the component.
"""
return _make_reading_field(self.data, dim="speed")

def plot(self, bins: int = 300) -> Plot:
Expand Down
45 changes: 45 additions & 0 deletions tests/result_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,51 @@ def test_component_results_data_slice_step(chopper, detector, multi_pulse_source
assert 'event' in toas.dims


def test_component_results_slice_all_results(chopper, detector, multi_pulse_source):
model = tof.Model(
source=multi_pulse_source, choppers=[chopper], detectors=[detector]
)
res = model.run()
ch = res.choppers['chopper']

reading = ch['pulse', 0]
toas = reading.toa.data
assert 'pulse' not in toas.dims
assert 'event' in toas.dims
wavelengths = reading.wavelength.data
assert 'pulse' not in wavelengths.dims
assert 'event' in wavelengths.dims


def test_component_results_slice_no_pulse_dim(chopper, detector, multi_pulse_source):
model = tof.Model(
source=multi_pulse_source, choppers=[chopper], detectors=[detector]
)
res = model.run()
ch = res.choppers['chopper']

reading = ch[0] # No pulse dim
data = reading.data
assert 'pulse' not in data.dims
assert 'event' in data.dims

toas = ch.toa[1].data
assert 'pulse' not in toas.dims
assert 'event' in toas.dims


def test_component_results_eto(chopper, detector, multi_pulse_source):
model = tof.Model(
source=multi_pulse_source, choppers=[chopper], detectors=[detector]
)
res = model.run()
data = res['detector'].data
assert 'eto' in data.coords
assert data.coords['eto'].max() < (1 / model.source.frequency).to(
unit=data.coords['eto'].unit
)


def test_result_plot_does_not_raise():
model = make_ess_model()
res = model.run()
Expand Down
Loading