-
Notifications
You must be signed in to change notification settings - Fork 2
Counter rotating choppers #44
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
Changes from all commits
1b17177
7998929
588d571
4260d20
996c476
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,7 +77,7 @@ | |
| "source": [ | ||
| "choppers = [\n", | ||
| " tof.Chopper(\n", | ||
| " frequency=70.0 * Hz,\n", | ||
| " frequency=-70.0 * Hz,\n", | ||
| " open=sc.array(\n", | ||
| " dims=['cutout'],\n", | ||
| " values=[98.71, 155.49, 208.26, 257.32, 302.91, 345.3],\n", | ||
|
|
@@ -93,7 +93,7 @@ | |
| " name=\"WFM1\",\n", | ||
| " ),\n", | ||
| " tof.Chopper(\n", | ||
| " frequency=70 * Hz,\n", | ||
| " frequency=-70 * Hz,\n", | ||
| " open=sc.array(\n", | ||
| " dims=['cutout'],\n", | ||
| " values=[80.04, 141.1, 197.88, 250.67, 299.73, 345.0],\n", | ||
|
|
@@ -109,7 +109,7 @@ | |
| " name=\"WFM2\",\n", | ||
| " ),\n", | ||
| " tof.Chopper(\n", | ||
| " frequency=56 * Hz,\n", | ||
| " frequency=-56 * Hz,\n", | ||
| " open=sc.array(\n", | ||
| " dims=['cutout'],\n", | ||
| " values=[74.6, 139.6, 194.3, 245.3, 294.8, 347.2],\n", | ||
|
|
@@ -125,7 +125,7 @@ | |
| " name=\"Frame-overlap 1\",\n", | ||
| " ),\n", | ||
| " tof.Chopper(\n", | ||
| " frequency=28 * Hz,\n", | ||
| " frequency=-28 * Hz,\n", | ||
| " open=sc.array(\n", | ||
| " dims=['cutout'],\n", | ||
| " values=[98.0, 154.0, 206.8, 254.0, 299.0, 344.65],\n", | ||
|
|
@@ -141,7 +141,7 @@ | |
| " name=\"Frame-overlap 2\",\n", | ||
| " ),\n", | ||
| " tof.Chopper(\n", | ||
| " frequency=7 * Hz,\n", | ||
| " frequency=-7 * Hz,\n", | ||
|
Comment on lines
143
to
+144
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if relying on a sign convention will lead to too many bugs? Have you considered splitting this into frequency (positive) and |
||
| " open=sc.array(\n", | ||
| " dims=['cutout'],\n", | ||
| " values=[30.0],\n", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| from dataclasses import dataclass | ||
| from typing import Optional, Tuple | ||
|
|
||
| import numpy as np | ||
| import scipp as sc | ||
|
|
||
| from .reading import ComponentReading, ReadingField | ||
|
|
@@ -18,15 +19,19 @@ class Chopper: | |
| Parameters | ||
| ---------- | ||
| frequency: | ||
| The frequency of the chopper. | ||
| The frequency of the chopper. Positive values indicate anti-clockwise rotation, | ||
| negative values indicate clockwise rotation. | ||
| open: | ||
| The opening angles of the chopper cutouts. | ||
| close: | ||
| The closing angles of the chopper cutouts. | ||
| distance: | ||
| The distance from the source to the chopper. | ||
| phase: | ||
| The phase of the chopper. | ||
| The phase of the chopper. Note that the phase is applied opposite to the | ||
| direction of rotation. For example, if the chopper rotates clockwise, a | ||
| phase of 10 degrees will shift all the openings by 10 degrees in the | ||
| anti-clockwise direction. | ||
| name: | ||
| The name of the chopper. | ||
| """ | ||
|
|
@@ -76,19 +81,43 @@ def open_close_times( | |
| """ | ||
| if unit is None: | ||
| unit = time_limit.unit | ||
| nrot = max(int(sc.ceil((time_limit * self.frequency).to(unit='')).value), 1) | ||
| nrot = max( | ||
| int(sc.ceil((time_limit * abs(self.frequency)).to(unit='')).value), 1 | ||
| ) | ||
| # Start at -1 to catch early openings in case the phase or opening angles are | ||
| # large | ||
| phases = sc.arange(uuid.uuid4().hex, -1, nrot) * two_pi + self.phase.to( | ||
| unit='rad' | ||
| ) | ||
| ) * (np.sign(self.frequency.value) * -1.0) | ||
| # Note that the order is important here: we need (phases + open/close) to get | ||
| # the correct dimension order when we flatten below. | ||
| open_times = (phases + self.open.to(unit='rad', copy=False)) / self.omega | ||
| close_times = (phases + self.close.to(unit='rad', copy=False)) / self.omega | ||
| open_times = (phases + self.open.to(unit='rad', copy=False)).flatten( | ||
| to=self.open.dim | ||
| ) | ||
| close_times = (phases + self.close.to(unit='rad', copy=False)).flatten( | ||
| to=self.close.dim | ||
| ) | ||
| # If the chopper is rotating anti-clockwise, we mirror the openings because the | ||
| # first cutout will be the last to open. | ||
| if self.frequency.value > 0: | ||
| open_times, close_times = ( | ||
| sc.array( | ||
| dims=close_times.dims, | ||
| values=(two_pi - close_times).values[::-1], | ||
| unit=close_times.unit, | ||
| ), | ||
| sc.array( | ||
| dims=open_times.dims, | ||
| values=(two_pi - open_times).values[::-1], | ||
| unit=open_times.unit, | ||
| ), | ||
| ) | ||
|
Comment on lines
+100
to
+114
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interface and need to handle this here feels very error-prone. Would it be possible to always specify openings in the order they appear, with positive angles? Any code for converting from different convention could be moved into helpers, such as
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice suggestion, but wouldn't this mean that the So I am wondering if it could me more error-prone in some cases?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how different conventions for different things (rotation, phase, angles) interact, so this was just an idea. I don't mind either solution, but I thought that
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, now that I think a bit more about this, I am wondering if we should do what you suggest, and not have a sign on the frequency, nor a In the case of the dream choppers, there are two counter-rotating choppers, where one is above the beam and the other one is below. There is even a third chopper which is on the side:
I think defining things in terms of "the first window that is listed is the first one that will pass in front of the beam" makes sense. If a user has angles from a chopper diagram, and then they want to place that chopper on the side of the beam, they just have to either add 90deg to all their angles, or add 90deg to their phase. To define the opening angles for a counter-rotating chopper, you have to reverse the order from what is in the NeXus.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean we have to change nothing to the code but instead update the documentation to explain how to make a counter-rotating chopper?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think we probably still need a It will not always be
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. About For McStas, we cannot parse the instrument file, so I am not sure what to do there. I am asking because I thought about either having chopper = Chopper(...)
chopper.counter_rotate() # would modify in-place the open and close anglesThe problem with the latter is that you don't necessarily remember how many times you called How about using classmethods?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 on the classmethods, this is more or less what I had in mind. Yes, the idea was to not change this code here, but instead separate those concerns into independent helpers. |
||
| abs_omg = abs(self.omega) | ||
| open_times /= abs_omg | ||
| close_times /= abs_omg | ||
| return ( | ||
| open_times.flatten(to=self.open.dim).to(unit=unit, copy=False), | ||
| close_times.flatten(to=self.close.dim).to(unit=unit, copy=False), | ||
| open_times.to(unit=unit, copy=False), | ||
| close_times.to(unit=unit, copy=False), | ||
| ) | ||
|
|
||
| def __repr__(self) -> str: | ||
|
|
||


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.
Why is this convention used?
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.
This is the convention from Nexus, but I don't like it either.
McStas also uses the sign of the frequency, and theirs is the opposite (positive for clockwise).
But regarding your comment below, yes I think I prefer using positive values for frequency and then a
directionparameter.