Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 21 additions & 24 deletions docs/user-guide/beer/beer_modulation_mcstas.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"from ess.beer import BeerModMcStasWorkflow, BeerModMcStasWorkflowKnownPeaks\n",
"from ess.beer.data import mcstas_silicon_medium_resolution, mcstas_duplex, duplex_peaks_array, silicon_peaks_array\n",
"from ess.beer.types import *\n",
"from ess.powder.types import *\n",
"\n",
"# Default bin edges for our d_hkl histograms\n",
"dspacing = sc.linspace('dspacing', 0.8, 2.2, 2000, unit='angstrom')\n",
Expand Down Expand Up @@ -163,7 +164,8 @@
"wf = BeerModMcStasWorkflowKnownPeaks()\n",
"wf[DetectorBank] = DetectorBank.north\n",
"wf[Filename[SampleRun]] = mcstas_silicon_medium_resolution()\n",
"da = wf.compute(RawDetector[SampleRun])\n",
"wf[DHKLList] = silicon_peaks_array()\n",
"da = wf.compute(WavelengthDetector[SampleRun])\n",
"da.masks.clear()\n",
"da.hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-3)"
]
Expand All @@ -183,13 +185,12 @@
"metadata": {},
"outputs": [],
"source": [
"wf[DHKLList] = silicon_peaks_array()\n",
"wf[Filename[SampleRun]] = mcstas_silicon_medium_resolution()\n",
"\n",
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -220,7 +221,7 @@
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -279,7 +280,8 @@
"wf = BeerModMcStasWorkflowKnownPeaks()\n",
"wf[DetectorBank] = DetectorBank.south\n",
"wf[Filename[SampleRun]] = mcstas_duplex(8)\n",
"wf.compute(RawDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-2)"
"wf[DHKLList] = duplex_peaks_array()\n",
"wf.compute(WavelengthDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-2)"
]
},
{
Expand All @@ -297,12 +299,10 @@
"metadata": {},
"outputs": [],
"source": [
"wf[DHKLList] = duplex_peaks_array()\n",
"\n",
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -333,7 +333,7 @@
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -392,7 +392,8 @@
"wf = BeerModMcStasWorkflowKnownPeaks()\n",
"wf[DetectorBank] = DetectorBank.south\n",
"wf[Filename[SampleRun]] = mcstas_duplex(9)\n",
"wf.compute(RawDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-3)"
"wf[DHKLList] = duplex_peaks_array()\n",
"wf.compute(WavelengthDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-3)"
]
},
{
Expand All @@ -410,12 +411,10 @@
"metadata": {},
"outputs": [],
"source": [
"wf[DHKLList] = duplex_peaks_array()\n",
"\n",
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -446,7 +445,7 @@
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -505,7 +504,8 @@
"wf = BeerModMcStasWorkflowKnownPeaks()\n",
"wf[DetectorBank] = DetectorBank.south\n",
"wf[Filename[SampleRun]] = mcstas_duplex(10)\n",
"wf.compute(RawDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-3)"
"wf[DHKLList] = duplex_peaks_array()\n",
"wf.compute(WavelengthDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-3)"
]
},
{
Expand All @@ -523,12 +523,10 @@
"metadata": {},
"outputs": [],
"source": [
"wf[DHKLList] = duplex_peaks_array()\n",
"\n",
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -559,7 +557,7 @@
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -618,7 +616,8 @@
"wf = BeerModMcStasWorkflowKnownPeaks()\n",
"wf[DetectorBank] = DetectorBank.south\n",
"wf[Filename[SampleRun]] = mcstas_duplex(16)\n",
"wf.compute(RawDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-3)"
"wf[DHKLList] = duplex_peaks_array()\n",
"wf.compute(WavelengthDetector[SampleRun]).hist(two_theta=400, event_time_offset=1000).plot(norm='log', cmin=1.0e-3)"
]
},
{
Expand All @@ -636,12 +635,10 @@
"metadata": {},
"outputs": [],
"source": [
"wf[DHKLList] = duplex_peaks_array()\n",
"\n",
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down Expand Up @@ -672,7 +669,7 @@
"results = {}\n",
"for bank in DetectorBank:\n",
" wf[DetectorBank] = bank\n",
" da = wf.compute(TofDetector[SampleRun])\n",
" da = wf.compute(WavelengthDetector[SampleRun])\n",
" results[bank] = (\n",
" da\n",
" .transform_coords(('dspacing',), graph=scn.conversion.graph.tof.elastic('tof'),)\n",
Expand Down
5 changes: 3 additions & 2 deletions src/ess/beer/clustering.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import scipp as sc
from scipy.signal import find_peaks, medfilt

from ess.powder.types import RunType

from .conversions import tof_from_t0_estimate_graph
from .types import (
GeometryCoordTransformGraph,
RawDetector,
RunType,
StreakClusteredData,
)


def cluster_events_by_streak(
da: RawDetector[RunType], gg: GeometryCoordTransformGraph
) -> StreakClusteredData[RunType]:
graph = tof_from_t0_estimate_graph(gg)
graph = tof_from_t0_estimate_graph(da, gg)

da = da.transform_coords(['dspacing'], graph=graph)
da.bins.coords['coarse_d'] = da.bins.coords.pop('dspacing').to(unit='angstrom')
Expand Down
103 changes: 51 additions & 52 deletions src/ess/beer/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@
import scipp.constants
from scippneutron.conversion import graph

from ess.powder.types import ElasticCoordTransformGraph, RunType

from .types import (
DHKLList,
GeometryCoordTransformGraph,
ModulationPeriod,
PulseLength,
RawDetector,
RunType,
StreakClusteredData,
TofCoordTransformGraph,
TofDetector,
WavelengthDefinitionChopperDelay,
WavelengthDetector,
)


def compute_tof_in_each_cluster(
def compute_wavelength_in_each_cluster(
da: StreakClusteredData[RunType],
chopper_delay: WavelengthDefinitionChopperDelay,
mod_period: ModulationPeriod,
) -> TofDetector[RunType]:
graph: GeometryCoordTransformGraph,
) -> WavelengthDetector[RunType]:
"""Fits a line through each cluster, the intercept of the line is t0.
The line is fitted using linear regression with an outlier removal procedure.

Expand All @@ -37,7 +38,10 @@ def compute_tof_in_each_cluster(
"""
if isinstance(da, sc.DataGroup):
return sc.DataGroup(
{k: compute_tof_in_each_cluster(v, mod_period) for k, v in da.items()}
{
k: compute_wavelength_in_each_cluster(v, mod_period)
for k, v in da.items()
}
)

max_distance_from_streak_line = mod_period / 3
Expand Down Expand Up @@ -185,17 +189,48 @@ def _tof_from_dhkl(
return out


def t0_estimate(
wavelength_estimate: sc.Variable,
L0: sc.Variable,
Ltotal: sc.Variable,
) -> sc.Variable:
"""Estimates the time-at-chopper by assuming the wavelength."""
Comment on lines +192 to +197
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 clear to me what L0 is without context. Can you explain in the docstrings? Either here or module-level?
  • Could a helper from scippneutron be used for this conversion?

return (
sc.constants.m_n
/ sc.constants.h
* wavelength_estimate
* (L0 - Ltotal).to(unit=wavelength_estimate.unit)
).to(unit='s')


def tof_from_t0_estimate_graph(
da: RawDetector[RunType],
gg: GeometryCoordTransformGraph,
) -> ElasticCoordTransformGraph[RunType]:
"""Graph for computing ``wavelength`` in pulse shaping chopper modes."""
return {
**gg,
't0': t0_estimate,
'tof': lambda time_of_arrival, t0: time_of_arrival - t0,
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.

Can you clarify why we still have tof here despite the switch to use the new wavelength-LUT in other code above?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beer currently does not use the LUT approach to compute wavelengths. Eventually we will move to that approach for the pulse shaping modes (the modulation modes are incompatible with the LUT approach).

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.

But I saw a lot of changes that replaced Tof with Wavelength?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the workflow is still using the generic unwrap workflow which does not have any public Tof... types.

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.

So where does TOF come into play? Is it TOA->TOF->Wavelength, or TOA->Wavelength->TOF as for DREAM final result, or something else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's toa->tof->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.

So what is GenericUnwrapWorkflow being used for? As far as I understand it does not support TOF anymore.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used so that the interface of the workflow matches what it will look like once we have lookup tables for the pulse skipping modes. That the wavelengths are currently not computed using lookup tables is an implementation detail, it does not need to be reflected in the public interface.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually we will replace this mechanism for computing wavelengths in beer pulse shaping modes with the lookup table approach to make it consistent with the other techniques.

'time_of_arrival': time_of_arrival,
}


def geometry_graph() -> GeometryCoordTransformGraph:
return graph.beamline.beamline(scatter=True)
return {
**graph.beamline.beamline(scatter=True),
**graph.tof.elastic("tof"),
}


def tof_from_known_dhkl_graph(
da: RawDetector[RunType],
mod_period: ModulationPeriod,
pulse_length: PulseLength,
chopper_delay: WavelengthDefinitionChopperDelay,
dhkl_list: DHKLList,
gg: GeometryCoordTransformGraph,
) -> TofCoordTransformGraph:
) -> ElasticCoordTransformGraph[RunType]:
"""Graph computing ``tof`` in modulation chopper modes using
list of peak positions."""

Expand All @@ -221,7 +256,6 @@ def _compute_coarse_dspacing(

return {
**gg,
**graph.tof.elastic("tof"),
'pulse_length': lambda: pulse_length,
'mod_period': lambda: mod_period,
'chopper_delay': lambda: chopper_delay,
Expand All @@ -232,56 +266,21 @@ def _compute_coarse_dspacing(
}


def t0_estimate(
wavelength_estimate: sc.Variable,
L0: sc.Variable,
Ltotal: sc.Variable,
) -> sc.Variable:
"""Estimates the time-at-chopper by assuming the wavelength."""
return (
sc.constants.m_n
/ sc.constants.h
* wavelength_estimate
* (L0 - Ltotal).to(unit=wavelength_estimate.unit)
).to(unit='s')


def _tof_from_t0(
time_of_arrival: sc.Variable,
t0: sc.Variable,
) -> sc.Variable:
"""Computes time-of-flight by subtracting a start time."""
return time_of_arrival - t0


def tof_from_t0_estimate_graph(
gg: GeometryCoordTransformGraph,
) -> TofCoordTransformGraph:
"""Graph for computing ``tof`` in pulse shaping chopper modes."""
return {
**gg,
**graph.tof.elastic("tof"),
't0': t0_estimate,
'tof': _tof_from_t0,
'time_of_arrival': time_of_arrival,
}


def compute_tof(
da: RawDetector[RunType], graph: TofCoordTransformGraph
) -> TofDetector[RunType]:
"""Uses the transformation graph to compute ``tof``."""
return da.transform_coords(('tof',), graph=graph)
def wavelength_detector(
da: RawDetector[RunType], graph: ElasticCoordTransformGraph[RunType]
) -> WavelengthDetector[RunType]:
"""Applies the transformation graph to compute ``wavelength``."""
return da.transform_coords(('wavelength',), graph=graph)


convert_from_known_peaks_providers = (
geometry_graph,
tof_from_known_dhkl_graph,
compute_tof,
wavelength_detector,
)
convert_pulse_shaping = (
geometry_graph,
tof_from_t0_estimate_graph,
compute_tof,
wavelength_detector,
)
providers = (compute_tof_in_each_cluster, geometry_graph)
providers = (compute_wavelength_in_each_cluster, geometry_graph)
Loading
Loading