-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add notebook for computing WFM time-of-flight #529
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small comments, looks good otherwise:
cls, | ||
disk_chopper: DiskChopper, | ||
pulse_frequency: sc.Variable, | ||
npulses: int = 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use a different (larger) default. I think there is probably no harm in making this larger than necessary. Alternatively, remove the default, since 1
is probably harmful, at least for ESS instrument.
src/scippneutron/tof/unwrap.py
Outdated
@@ -404,6 +413,38 @@ def time_offset(unwrapped: UnwrappedData) -> TimeOffset: | |||
return TimeOffset(unwrapped.coords['time_offset']) | |||
|
|||
|
|||
def time_of_flight_origin_wfm( | |||
distance: TimeOfFlightOriginDistance, | |||
time_open: TimeOfFlightOriginTime, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bad name, since it is not the time when the slits open? How about subframe_origin_time
?
src/scippneutron/tof/unwrap.py
Outdated
# TODO: add a helper provider that can make time of flight origin from a pair of WFM | ||
# choppers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose then either the above or below functions goes way, since it is split into two as per our discussion?
"id": "360be644-4c0d-4a0c-9497-af5a603cbfe1", | ||
"metadata": {}, | ||
"source": [ | ||
"## Creating some neutron events\n", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note a lot of overlap above and below with the fakes that were being introduced in https://github.com/scipp/scippneutron/blob/main/src/scippneutron/tof/fakes.py. Can we extend and use that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to use FakeSource
and FakeBeamline
to create multiple pulses of neutrons that span different wavelength ranges, and then just merge all the events in a single data array.
Something like:
# Make the ESS source with tof
N = 500_000 # Number of neutrons in a pulse
source = tof.Source(facility='ess', neutrons=N)
# Histogram the wavelengths in coarse bins, to make 20 pulses below
neutrons_per_wavelength = source.data.squeeze().hist(wavelength=sc.linspace('wavelength', 0.05, 11.0, 21, unit='angstrom'))
choppers = {
key: cc.Chopper.from_disk_chopper(
chop,
pulse_frequency=source.frequency,
npulses=1,
)
for key, chop in disk_choppers.items()
}
events = None
for n in neutrons_per_wavelength:
s = fakes.FakeSource(
frequency=sc.scalar(14.0, unit='Hz'), run_length=sc.scalar(10.0, unit='s'),
events_per_pulse=int(n.value)
)
p = fakes.FakePulse(
time_min=sc.scalar(0.0, unit='ms'),
time_max=sc.scalar(3.0, unit='ms'),
wavelength_min=n.coords['wavelength'].min(),
wavelength_max=n.coords['wavelength'].max(),
)
beamline = fakes.FakeBeamline(
source=s,
pulse=p,
choppers=choppers,
monitors={'monitor': Ltotal},
detectors={},
)
# Need try/except as some wavelength ranges have 0 neutrons that make it through
try:
mon, ref = beamline.get_monitor('monitor')
if events is None:
events = mon
else:
events = events.bins.concatenate(mon)
except (ValueError, NotImplementedError):
pass
but the final events don't look great. The different pulses have sharp edges, and some overlap where we see spikes in the histogrammed plot:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"source": [ | ||
"## Using the chopper cascade to chop the pulse\n", | ||
"\n", | ||
"The `chopper_cascade` module (abbreviated here as `cc`) can now be used to chop a pulse of neutrons using the choppers created above.\n", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd avoid importing under an alias unless a module is used many times, since it just adds to the cognitive overhead. Spell it out!
"# Use provider to set time of flight origin manually\n", | ||
"pl.insert(uw.time_of_flight_origin_wfm)\n", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think we should just have a single provider, I don't think there is need to modify the pipeline? Just set the origin by hand, which will replace the provider which computed it?
- Remember that we are moving away from the "pipeline" terminology, use "workflow", don't mention "provider". See https://scipp.github.io/essreduce/user-guide/reduction-workflow-guidelines.html#c-5-use-a-fixed-pattern-for-creating-manipulating-and-running-workflows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to replace the old provider with one that uses a pair of WFM choppers.
I've also left the possibility of setting the time of flight origin and distance manually (I hope!).
See update.
src/scippneutron/tof/unwrap.py
Outdated
times = [] | ||
for ch in wfm_choppers: | ||
times.append(ch.time_open) | ||
times.append(ch.time_close) | ||
return TimeOfFlightOriginTime(sc.concat(times, dim='dummy').mean('dummy')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine, but not also https://scipp.github.io/generated/functions/scipp.reduce.html#scipp-reduce.
src/scippneutron/tof/unwrap.py
Outdated
""" | ||
Compute the time-of-flight origin from a source chopper in the WFM case. | ||
|
||
For WFM there is not a single time-of-flight "origin", but one for each subframe. | ||
For each subframe, the time-of-flight origin may be defined as the center of the | ||
respective chopper slit opening of the WFM chopper. In some cases there is a pair | ||
WFM choppers, in which case the time-of-flight origin may be defined using some | ||
combination of the two choppers. This is not supported yet, as we only support | ||
a single source chopper input. | ||
""" | ||
if len(source_chopper.time_open) != subframe_bounds['time'].sizes['subframe']: | ||
raise NotImplementedError( | ||
"Source chopper openings do not match the subframe count, this is ." | ||
"not supported yet." | ||
) | ||
""" """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstring removed by accident?
src/scippneutron/tof/unwrap.py
Outdated
# Select the first n time origins to match subframe bounds | ||
subframe_origin_time = subframe_origin_time[: subframe_bounds.sizes['subframe']] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Um, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what else to do here? The subframe_origin_time
has maybe a length of e.g. 30, but we know the choppers only have 6 openings. The dimensions wouldn't agree further down without the slicing, I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but why the first 6? It could just as well be the last 6, or some in the middle?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the last thing I have not yet addressed. But I am unsure how to proceed. Do you have a suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, unfortunately. Require that only the relevant times and/or chopper openings are passed in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See if the update is reasonable.
src/scippneutron/tof/unwrap.py
Outdated
shift = ( | ||
sc.concat([nans, subframe_origin_time], dim=uuid4().hex) | ||
.transpose() | ||
.flatten(to='subframe') | ||
) | ||
shift = sc.concat([shift, nans[0]], 'subframe') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused shift
from above? Also, why not use
full_liketo make
shift, then use setitem with
[1::2]` to set the times?
…and take the first set of time origins that are greater than that time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Fingers crossed that this is one of the last few iterations on the WFM work.
tof
for neutron events from WFM beamlineDraft because no unit test yet, but still worth reviewing the notebook and changes in providers.