Skip to content

Support multiple pulses in chopper cascade#700

Merged
nvaytet merged 3 commits into
mainfrom
multi-pulse-chopper-cascade
May 8, 2026
Merged

Support multiple pulses in chopper cascade#700
nvaytet merged 3 commits into
mainfrom
multi-pulse-chopper-cascade

Conversation

@nvaytet
Copy link
Copy Markdown
Member

@nvaytet nvaytet commented May 7, 2026

Example:

from scippneutron.tof import chopper_cascade
from ess.odin.beamline import choppers
import scipp as sc

source_position = sc.vector([0, 0, 0], unit='m')
disk_choppers = choppers(source_position)

pulse_frequency = sc.scalar(14.0, unit='Hz')
pulse_period = 1 / pulse_frequency

chops = {
    key: chopper_cascade.Chopper(
        distance=(ch.axle_position - source_position).fields.z,
        time_open=ch.time_offset_open(pulse_frequency=pulse_frequency / 4),
        time_close=ch.time_offset_close(pulse_frequency=pulse_frequency / 4),
    )
    for key, ch in disk_choppers.items()
}

frames = chopper_cascade.FrameSequence.from_source_pulse(
    time_min=sc.scalar(0.0, unit='ms'),
    time_max=sc.scalar(4.0, unit='ms'),
    wavelength_min=sc.scalar(0.0, unit='angstrom'),
    wavelength_max=sc.scalar(10.0, unit='angstrom'),
    npulses=3,
    pulse_period=pulse_period,
)
frames = frames.chop(chops.values())

at_sample = frames.propagate_to(sc.scalar(65.0, unit="m"))
at_sample.draw()
Figure 3 (1)

Comment on lines +322 to +325
subframes = [
Subframe(time=time + i * pulse_period, wavelength=wavelength)
for i in range(npulses)
]
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.

Should there be source pulses also before the "pulse of interest? Or is that handled by some wrapping you do afterwards?

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 do some wrapping manually afterwards in essreduce. I thought I would keep it simple here, so that it behaves a little like tof. But I don't mind either way.

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.

Its fine as it is, probably. But I do wonder if one could formulate this on a cyclic coord system instead?

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.

Maybe something like making the period boundary act like a chopper and chop the polygons as well?

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.

What I mean: After every time propagation, do a mod 71 ms or (142 ms if pulse-skipping, etc.). But the polygon math might not be easy to port.

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.

Yes I got that. You'd have to do the mod, but as you say you would run into cases where some polygons span across the period boundary.
I was thinking one easy way to split those polygons would be to use the chop mechanism?
You would then of course have to duplicate the pulses to the frame before the current frame.

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.

I don't think pulse duplication would be necessary any more, since periodicity takes care of that? Splitting the polygons that cross the boundary is a good suggestion, provided it does not interfere with other code that tries to look at subframes?

Comment on lines +515 to +519
for i in range(3):
expected_subframe = chopper_cascade.Subframe(
time=expected_time + i * pulse_period.to(unit='ms'),
wavelength=expected_wavelength,
)
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 a fan of repeating the implementation-math in the test, but not sure what a better test would be.

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 could check that the min of frame.bounds().time is zero, and the max is 2 * period + 1ms?
And remove the looping over the frames. Not sure it's better, but it's different.

@nvaytet nvaytet merged commit d8a03d5 into main May 8, 2026
5 checks passed
@nvaytet nvaytet deleted the multi-pulse-chopper-cascade branch May 8, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants