-
Notifications
You must be signed in to change notification settings - Fork 1
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
Time binning reduction and export method. #21
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
2a4b00b
Time binning reduction and export method.
YooSunYoung 69a238c
Split shared and separate fields and add docstring to each field.
YooSunYoung c41dd0f
Load and export crystal rotation angle.
YooSunYoung ef0cf2f
Reshape detector counts.
YooSunYoung efaf78f
Replace h5 data group/dataset creation functions with helper functions.
YooSunYoung 6862bf5
Merge branch 'main' into export-mcstas-result
YooSunYoung b3cba5d
Apply automatic formatting
YooSunYoung 9353ee6
Fix unit of weights to be counts.
YooSunYoung ba23828
Refactorize functions.
YooSunYoung 9e9de47
Rename functions.
YooSunYoung 9c4a154
Extract functions for better documentation.
YooSunYoung e753113
Reorganize tests and update type hints of properties.
YooSunYoung f5d76c5
Update copyright year.
YooSunYoung d77a7e7
Export method tests draft.
YooSunYoung 2945fb3
Add fake group and fake create_dataset method.
YooSunYoung 255a95f
Add tests for exporting method and add in-memory byte stream IO optio…
YooSunYoung 5c3e52c
Fix workflow example page.
YooSunYoung 36e6789
Add new types into API references.
YooSunYoung d63fdb0
Fix docstring of exporting method.
YooSunYoung 4d17bf2
Add McStas specific field manipulation functions as parameters.
YooSunYoung fb163cd
Merge branch 'main' into export-mcstas-result
YooSunYoung c5338d1
Update new type object, rename type alias and add default values in t…
YooSunYoung dab4242
Rename type alias.
YooSunYoung 7e29fca
Merge branch 'main' into export-mcstas-result
YooSunYoung File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,9 @@ | |
:recursive: | ||
|
||
NMXData | ||
NMXReducedData | ||
InputFilepath | ||
TimeBinSteps | ||
|
||
``` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,33 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) | ||
from typing import Iterable, NewType, Optional | ||
from typing import Callable, Iterable, NewType, Optional | ||
|
||
import scipp as sc | ||
import scippnexus as snx | ||
|
||
from .reduction import NMXData | ||
|
||
PixelIDs = NewType("PixelIDs", sc.Variable) | ||
InputFilepath = NewType("InputFilepath", str) | ||
NMXData = NewType("NMXData", sc.DataGroup) | ||
|
||
# McStas Configurations | ||
MaximumProbability = NewType("MaximumProbability", int) | ||
DefaultMaximumProbability = MaximumProbability(100_000) | ||
|
||
McStasEventProbabilities = NewType("McStasEventProbabilities", sc.Variable) | ||
EventWeights = NewType("EventWeights", sc.Variable) | ||
EventWeightsConverter = NewType( | ||
"EventWeightsConverter", | ||
Callable[[MaximumProbability, McStasEventProbabilities], EventWeights], | ||
) | ||
"""A function that converts McStas probability to event weights.""" | ||
|
||
ProtonCharge = NewType("ProtonCharge", sc.Variable) | ||
ProtonChargeConverter = NewType( | ||
"ProtonChargeConverter", Callable[[EventWeights], ProtonCharge] | ||
) | ||
"""A function that derives arbitrary proton charge based on event weights.""" | ||
|
||
|
||
def _retrieve_event_list_name(keys: Iterable[str]) -> str: | ||
prefix = "bank01_events_dat_list" | ||
|
@@ -27,54 +42,172 @@ def _retrieve_event_list_name(keys: Iterable[str]) -> str: | |
raise ValueError("Can not find event list name.") | ||
|
||
|
||
def _retrieve_raw_event_data(file: snx.File) -> sc.Variable: | ||
"""Retrieve events from the nexus file.""" | ||
bank_name = _retrieve_event_list_name(file["entry1/data"].keys()) | ||
# ``dim_0``: event index, ``dim_1``: property index. | ||
return file["entry1/data/" + bank_name]["events"][()].rename_dims( | ||
{'dim_0': 'event'} | ||
) | ||
|
||
|
||
def _copy_partial_var( | ||
var: sc.Variable, idx: int, unit: Optional[str] = None, dtype: Optional[str] = None | ||
) -> sc.Variable: | ||
"""Retrieve a property from a variable.""" | ||
var = var['dim_1', idx].astype(dtype or var.dtype, copy=True) | ||
if unit: | ||
if unit is not None: | ||
var.unit = sc.Unit(unit) | ||
return var | ||
|
||
|
||
def _retrieve_crystal_rotation(file: snx.File, unit: str) -> sc.Variable: | ||
"""Retrieve crystal rotation from the file.""" | ||
|
||
return sc.vector( | ||
value=[file[f"entry1/simulation/Param/XtalPhi{key}"][...] for key in "XYZ"], | ||
unit=unit, | ||
) | ||
|
||
|
||
def event_weights_from_probability( | ||
max_probability: MaximumProbability, probabilities: McStasEventProbabilities | ||
) -> EventWeights: | ||
"""Create event weights by scaling probability data. | ||
|
||
event_weights = max_probability * (probabilities / max(probabilities)) | ||
|
||
Parameters | ||
---------- | ||
probabilities: | ||
The probabilities of the events. | ||
|
||
max_probability: | ||
The maximum probability to scale the weights. | ||
|
||
""" | ||
maximum_probability = sc.scalar(max_probability, unit='counts') | ||
|
||
return EventWeights(maximum_probability * (probabilities / probabilities.max())) | ||
|
||
|
||
def _compose_event_data_array( | ||
*, | ||
weights: sc.Variable, | ||
id_list: sc.Variable, | ||
t_list: sc.Variable, | ||
pixel_ids: sc.Variable, | ||
num_panels: int, | ||
) -> sc.DataArray: | ||
"""Combine data with coordinates loaded from the nexus file. | ||
|
||
Parameters | ||
---------- | ||
weights: | ||
The weights of the events. | ||
|
||
id_list: | ||
The pixel IDs of the events. | ||
|
||
t_list: | ||
The time of arrival of the events. | ||
|
||
pixel_ids: | ||
All possible pixel IDs of the detector. | ||
|
||
num_panels: | ||
The number of (detector) panels used in the experiment. | ||
|
||
""" | ||
|
||
events = sc.DataArray(data=weights, coords={'t': t_list, 'id': id_list}) | ||
grouped: sc.DataArray = events.group(pixel_ids) | ||
return grouped.fold(dim='id', sizes={'panel': num_panels, 'id': -1}) | ||
|
||
|
||
def proton_charge_from_event_data(event_da: sc.DataArray) -> ProtonCharge: | ||
"""Make up the proton charge from the event data array. | ||
|
||
Proton charge is proportional to the number of neutrons, | ||
which is proportional to the number of events. | ||
The scale factor is manually chosen based on previous results | ||
to be convenient for data manipulation in the next steps. | ||
It is derived this way since | ||
the protons are not part of McStas simulation, | ||
and the number of neutrons is not included in the result. | ||
|
||
Parameters | ||
---------- | ||
event_da: | ||
The event data binned in detector panel and pixel id dimensions. | ||
|
||
""" | ||
# Arbitrary number to scale the proton charge | ||
_proton_charge_scale_factor = sc.scalar(1 / 10_000, unit=None) | ||
Comment on lines
+145
to
+146
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. Shouldn't there be more protons than neutrons? 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... I don't remember... |
||
|
||
return ProtonCharge(_proton_charge_scale_factor * event_da.bins.size().sum().data) | ||
|
||
|
||
def load_mcstas_nexus( | ||
file_path: InputFilepath, | ||
event_weights_converter: EventWeightsConverter = event_weights_from_probability, | ||
proton_charge_converter: ProtonChargeConverter = proton_charge_from_event_data, | ||
max_probability: Optional[MaximumProbability] = None, | ||
) -> NMXData: | ||
"""Load McStas simulation result from h5(nexus) file. | ||
|
||
See :func:`~event_weights_from_probability` and | ||
:func:`~proton_charge_from_event_data` for details. | ||
|
||
Parameters | ||
---------- | ||
file_path: | ||
File name to load. | ||
|
||
event_weights_converter: | ||
A function to convert probabilities to event weights. | ||
The function should accept the probabilities as the first argument, | ||
and return the converted event weights. | ||
|
||
proton_charge_converter: | ||
A function to convert the event weights to proton charge. | ||
The function should accept the event weights as the first argument, | ||
and return the proton charge. | ||
|
||
max_probability: | ||
The maximum probability to scale the weights. | ||
If not provided, ``DefaultMaximumProbability`` is used. | ||
|
||
""" | ||
|
||
from .mcstas_xml import read_mcstas_geometry_xml | ||
|
||
geometry = read_mcstas_geometry_xml(file_path) | ||
probability = max_probability or DefaultMaximumProbability | ||
coords = geometry.to_coords() | ||
|
||
with snx.File(file_path) as file: | ||
bank_name = _retrieve_event_list_name(file["entry1/data"].keys()) | ||
var: sc.Variable | ||
var = file["entry1/data/" + bank_name]["events"][()].rename_dims( | ||
{'dim_0': 'event'} | ||
raw_data = _retrieve_raw_event_data(file) | ||
weights = event_weights_converter( | ||
max_probability or DefaultMaximumProbability, | ||
McStasEventProbabilities( | ||
_copy_partial_var(raw_data, idx=0, unit='counts') | ||
), # p | ||
) | ||
event_da = _compose_event_data_array( | ||
weights=weights, | ||
id_list=_copy_partial_var(raw_data, idx=4, dtype='int64'), # id | ||
t_list=_copy_partial_var(raw_data, idx=5, unit='s'), # t | ||
pixel_ids=coords.pop('pixel_id'), | ||
num_panels=len(geometry.detectors), | ||
) | ||
proton_charge = proton_charge_converter(event_da) | ||
crystal_rotation = _retrieve_crystal_rotation( | ||
file, geometry.simulation_settings.angle_unit | ||
) | ||
|
||
weights = _copy_partial_var(var, idx=0, unit='counts') # p | ||
id_list = _copy_partial_var(var, idx=4, dtype='int64') # id | ||
t_list = _copy_partial_var(var, idx=5, unit='s') # t | ||
|
||
weights = (probability / weights.max()) * weights | ||
|
||
loaded = sc.DataArray(data=weights, coords={'t': t_list, 'id': id_list}) | ||
coords = geometry.to_coords() | ||
grouped = loaded.group(coords.pop('pixel_id')) | ||
da = grouped.fold(dim='id', sizes={'panel': len(geometry.detectors), 'id': -1}) | ||
da.coords.update(coords) | ||
|
||
return NMXData(da) | ||
return NMXData( | ||
weights=event_da, | ||
proton_charge=proton_charge, | ||
crystal_rotation=crystal_rotation, | ||
**coords, | ||
) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 which
event_da
is passed here, but shouldn't it be a monitor instead of detector data? Otherwise we include the scattering effects, which may be problematic?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.
Yes, I asked about this before.
Justin said there should be easier/more precise ways to integrate number of protons during the measurements in the real experiments...
So, for simulation data, this
proton_charge
just need to be proportional to the number of events.And the Monitor is not included in the simulation data... so it was the only way...?
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.
Ok!