-
Notifications
You must be signed in to change notification settings - Fork 2
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
Improve handling of XGM data recording the wrong number of pulses #161
base: master
Are you sure you want to change the base?
Changes from all commits
0a58000
260adcd
53d15d4
abc5cff
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 |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from enum import Enum | ||
from warnings import warn | ||
from textwrap import dedent | ||
|
||
import numpy as np | ||
|
@@ -192,7 +193,8 @@ def __init__(self, run, device=None, default_sase=None): | |
self._pulse_energy = { } | ||
self._slow_train_energy = { } | ||
self._npulses = { } | ||
self._npulses_by_train = { } | ||
self._pulse_counts = { } | ||
self._slow_pulse_counts = { } | ||
self._max_pulses = { } | ||
|
||
self._proposal = None | ||
|
@@ -410,15 +412,7 @@ def _get_main_nbunches_key(self): | |
def npulses(self, sase=None) -> int: | ||
"""The nominal number of pulses. | ||
|
||
This calls | ||
[KeyData.as_single_value()][extra_data.KeyData.as_single_value] | ||
internally, which means it will throw an exception if the number of | ||
pulses is not constant. | ||
|
||
Note that this returns the number of pulses recorded by the XGM, which | ||
can be unreliable. Use something like the | ||
[XrayPulses][extra.components.XrayPulses] component to find the real | ||
number of pulses from the bunch pattern table. | ||
This will throw an exception if the number of pulses is not constant. | ||
|
||
Args: | ||
sase (int): Same meaning as in | ||
|
@@ -429,40 +423,64 @@ def npulses(self, sase=None) -> int: | |
""" | ||
pg = self._check_sase_arg(sase) | ||
if pg not in self._npulses: | ||
if pg == PropertyGroup.MAIN: | ||
key = self._get_main_nbunches_key() | ||
else: | ||
key = f"pulseEnergy.numberOfSa{pg.value}BunchesActual" | ||
self._npulses[pg] = int(self.control_source[key].as_single_value()) | ||
pulse_counts = self.pulse_counts(sase=sase) | ||
if not np.allclose(pulse_counts[0], pulse_counts): | ||
raise ValueError("Number of pulses is changing, there is no nominal number.") | ||
|
||
self._npulses[pg] = int(pulse_counts[0]) | ||
|
||
return self._npulses[pg] | ||
|
||
def pulse_counts(self, sase=None): | ||
def pulse_counts(self, sase=None, force_slow_data=False): | ||
"""Return a 1D [DataArray][xarray.DataArray] of the number of pulses in each train. | ||
|
||
See the docs for [XGM.npulses()][extra.components.XGM.npulses] | ||
for information on retrieving the true number of pulses. | ||
Because the slow data `pulseEnergy.numberOf[SAx]BunchesActual` property | ||
can be unreliable this will always check the slow data counts against | ||
the counts in the fast data from | ||
[XGM.pulse_energy()][extra.components.XGM.pulse_energy] and return the | ||
fast data counts if there is a difference. This can be overridden by | ||
passing `force_slow_data=True`. | ||
|
||
Warning: | ||
Using `force_slow_data=True` can give unreliable results, only use | ||
it if you specifically want to find what numbers the XGM | ||
recorded. In general, prefer using something like the | ||
[XrayPulses][extra.components.XrayPulses] component to find the real | ||
number of pulses from the bunch pattern table. | ||
|
||
Args: | ||
sase (int): Same meaning as in | ||
[XGM.pulse_energy()][extra.components.XGM.pulse_energy]. | ||
""" | ||
import xarray as xr | ||
|
||
pg = self._check_sase_arg(sase) | ||
if pg not in self._npulses_by_train: | ||
if pg not in self._pulse_counts: | ||
if pg == PropertyGroup.MAIN: | ||
key = self._get_main_nbunches_key() | ||
else: | ||
key = f"pulseEnergy.numberOfSa{pg.value}BunchesActual" | ||
self._npulses_by_train[pg] = self._control_source[key].xarray() | ||
slow_counts = self._control_source[key].xarray() | ||
self._slow_pulse_counts[pg] = slow_counts | ||
|
||
pulse_energy = self.pulse_energy(sase=sase) | ||
common_tids = np.intersect1d(pulse_energy.trainId, slow_counts.trainId) | ||
fast_counts = np.count_nonzero(pulse_energy.sel(trainId=common_tids), axis=1) | ||
fast_counts = xr.DataArray(fast_counts, dims=("trainId",), | ||
coords={"trainId": pulse_energy.trainId}) | ||
|
||
counts_match = np.allclose(fast_counts, slow_counts.sel(trainId=common_tids)) | ||
if not counts_match: | ||
warn(f"Slow data pulse counts ({key}) don't match the counts from fast data (data.intensityTD), data may be invalid!") | ||
Comment on lines
+473
to
+474
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. If we know the slow data counts aren't always reliable and the difference from the fast data is a useful signal of that, does that imply the fast data are more reliable? Should we be using that instead? 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. Yes, in my experience the fast data is more reliable and in some ways more useful than what we get from the pulses components because it will show the real number of pulses that are getting delivered. I don't know why but sometimes the number of pulses will change for... reasons... and for whatever reason that doesn't seem to be reflected in the bunch pattern table. As for whether we should be using that instead of the slow data, I'm not sure. I am a little hesitant to make it do something other than the obvious 'just return whatever was saved', but as you say that's probably not very useful. 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.
Hu, do you have a concrete example? That should not be possible, as they both are looking at exactly the same data - the XGM DOOCS server takes the bunch pattern to slice out the correct values from the train signal for 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. 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. 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. Ah ok, makes sense. |
||
self._pulse_counts[pg] = fast_counts | ||
else: | ||
self._pulse_counts[pg] = slow_counts | ||
|
||
return self._npulses_by_train[pg] | ||
return self._pulse_counts[pg] if not force_slow_data else self._slow_pulse_counts[pg] | ||
|
||
def max_npulses(self, sase=None) -> int: | ||
"""The maximum number of pulses. | ||
|
||
See the docs for [XGM.npulses()][extra.components.XGM.npulses] | ||
for information on retrieving the true number of pulses. | ||
|
||
Args: | ||
sase (int): Same meaning as in | ||
[XGM.pulse_energy()][extra.components.XGM.pulse_energy]. | ||
|
@@ -476,9 +494,6 @@ def max_npulses(self, sase=None) -> int: | |
def is_constant_pulse_count(self, sase=None) -> bool: | ||
"""Return whether or not the number of pulses is constant. | ||
|
||
See the docs for [XGM.npulses()][extra.components.XGM.npulses] | ||
for information on retrieving the true number of pulses. | ||
|
||
Args: | ||
sase (int): Same meaning as in | ||
[XGM.pulse_energy()][extra.components.XGM.pulse_energy]. | ||
|
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'm sorry I didn't notice that earlier, but it is unfortunate we're using
xarray.DataArray
here andpd.Series
forPulsePattern
-derived components. We should avoid mixing these types arbitrarily between components in the future.Out of curiosity, any particular reason or just preference?
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.
No reason, it's just a preference since I tend to use DataArrays/Datasets a lot.