diff --git a/docs/ess/dream.ipynb b/docs/ess/dream.ipynb
index 07d6a6b..7e1a9fb 100644
--- a/docs/ess/dream.ipynb
+++ b/docs/ess/dream.ipynb
@@ -20,12 +20,7 @@
"source": [
"import scipp as sc\n",
"import plopp as pp\n",
- "import tof\n",
- "\n",
- "Hz = sc.Unit('Hz')\n",
- "deg = sc.Unit('deg')\n",
- "meter = sc.Unit('m')\n",
- "AA = sc.Unit('angstrom')"
+ "import tof"
]
},
{
@@ -56,144 +51,35 @@
"source": [
"## Component set-up\n",
"\n",
- "## Choppers\n",
- "\n",
"The DREAM chopper cascade consists of:\n",
"\n",
"- Two counter-rotating pulse-shaping choppers (PSC) that are very close to each other, located ~6m from the source\n",
"- An overlap chopper placed right after the two PSCs\n",
"- A band control chopper\n",
- "- A T0 chopper"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "5",
- "metadata": {},
- "outputs": [],
- "source": [
- "choppers = [\n",
- " tof.Chopper(\n",
- " frequency=14 * Hz,\n",
- " direction=tof.AntiClockwise,\n",
- " centers=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[0, 72, 86.4, 115.2, 172.8, 273.6, 288.0, 302.4],\n",
- " unit='deg',\n",
- " ),\n",
- " widths=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[2.46, 3.02, 3.27, 3.27, 5.02, 3.93, 3.93, 2.46],\n",
- " unit='deg',\n",
- " ),\n",
- " phase=(286 - 180) * deg,\n",
- " distance=6.145 * meter,\n",
- " name=\"PSC1\",\n",
- " ),\n",
- " tof.Chopper(\n",
- " frequency=14 * Hz,\n",
- " direction=tof.Clockwise,\n",
- " centers=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[0, 28.8, 57.6, 144, 158.4, 216, 259.2, 316.8],\n",
- " unit='deg',\n",
- " ),\n",
- " widths=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[2.46, 3.60, 3.60, 3.23, 3.27, 3.77, 3.94, 2.62],\n",
- " unit='deg',\n",
- " ),\n",
- " phase=236 * deg,\n",
- " distance=6.155 * meter,\n",
- " name=\"PSC2\",\n",
- " ),\n",
- " tof.Chopper(\n",
- " frequency=14 * Hz,\n",
- " direction=tof.AntiClockwise,\n",
- " centers=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[0.0],\n",
- " unit='deg',\n",
- " ),\n",
- " widths=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[27.6],\n",
- " unit='deg',\n",
- " ),\n",
- " phase=(297 - 180 - 90) * deg,\n",
- " distance=6.174 * meter,\n",
- " name=\"OC\",\n",
- " ),\n",
- " tof.Chopper(\n",
- " frequency=112 * Hz,\n",
- " direction=tof.AntiClockwise,\n",
- " centers=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[0.0, 180.0],\n",
- " unit='deg',\n",
- " ),\n",
- " widths=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[73.75, 73.75],\n",
- " unit='deg',\n",
- " ),\n",
- " phase=(240 - 180) * deg,\n",
- " distance=9.78 * meter,\n",
- " name=\"BC\",\n",
- " ),\n",
- " tof.Chopper(\n",
- " frequency=28 * Hz,\n",
- " direction=tof.AntiClockwise,\n",
- " centers=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[0.0],\n",
- " unit='deg',\n",
- " ),\n",
- " widths=sc.array(\n",
- " dims=['cutout'],\n",
- " values=[314.9],\n",
- " unit='deg',\n",
- " ),\n",
- " phase=(280 - 180) * deg,\n",
- " distance=13.05 * meter,\n",
- " name=\"T0\",\n",
- " ),\n",
- "]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6",
- "metadata": {},
- "source": [
- "### Detector banks and monitors\n",
+ "- A T0 chopper\n",
"\n",
"DREAM has 5 detector banks: the Mantle, two End-caps, a High-resolution detector and a SANS detector.\n",
"\n",
- "For each detector bank, we use a single mean distance (in practice, one could have a different distance for each pixel)."
+ "For each detector bank, we use a single mean distance (in practice, one could have a different distance for each pixel).\n",
+ "\n",
+ "These component parameters are pre-configured in `tof.facilities.ess`.\n",
+ "Here we select the configuration in high-flux mode:"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "7",
+ "id": "5",
"metadata": {},
"outputs": [],
"source": [
- "sample_position = 76.55 * meter\n",
- "\n",
- "detectors = [\n",
- " tof.Detector(distance=sample_position + 1.125 * meter, name='mantle'),\n",
- " tof.Detector(distance=sample_position + 1.125 * meter, name='end-cap'),\n",
- " tof.Detector(distance=sample_position + 2.5 * meter, name='high-resolution'),\n",
- " tof.Detector(distance=sample_position + 2.5 * meter, name='sans'),\n",
- "]"
+ "dream_params = tof.facilities.ess.dream(high_flux=True)\n",
+ "dream_params"
]
},
{
"cell_type": "markdown",
- "id": "8",
+ "id": "6",
"metadata": {},
"source": [
"## Run the simulation\n",
@@ -204,18 +90,18 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "9",
+ "id": "7",
"metadata": {},
"outputs": [],
"source": [
- "model = tof.Model(source=source, choppers=choppers, detectors=detectors)\n",
+ "model = tof.Model(source=source, **dream_params)\n",
"results = model.run()\n",
"results.plot(blocked_rays=5000)"
]
},
{
"cell_type": "markdown",
- "id": "10",
+ "id": "8",
"metadata": {},
"source": [
"## Wavelength as a function of time-of-arrival\n",
@@ -231,7 +117,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "11",
+ "id": "9",
"metadata": {},
"outputs": [],
"source": [
@@ -246,7 +132,7 @@
},
{
"cell_type": "markdown",
- "id": "12",
+ "id": "10",
"metadata": {},
"source": [
"### Defining a conversion from `toa` to `wavelength`\n",
@@ -259,7 +145,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "13",
+ "id": "11",
"metadata": {},
"outputs": [],
"source": [
@@ -278,7 +164,7 @@
},
{
"cell_type": "markdown",
- "id": "14",
+ "id": "12",
"metadata": {},
"source": [
"## Computing wavelengths\n",
@@ -289,7 +175,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "15",
+ "id": "13",
"metadata": {},
"outputs": [],
"source": [
@@ -314,7 +200,7 @@
},
{
"cell_type": "markdown",
- "id": "16",
+ "id": "14",
"metadata": {},
"source": [
"We can now compare our computed wavelengths to the true wavelengths of the neutrons."
@@ -323,7 +209,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "17",
+ "id": "15",
"metadata": {},
"outputs": [],
"source": [
@@ -351,7 +237,8 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
- "pygments_lexer": "ipython3"
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
}
},
"nbformat": 4,
diff --git a/docs/ess/odin.ipynb b/docs/ess/odin.ipynb
index 71cb681..1501e04 100644
--- a/docs/ess/odin.ipynb
+++ b/docs/ess/odin.ipynb
@@ -20,11 +20,7 @@
"source": [
"import scipp as sc\n",
"import plopp as pp\n",
- "import tof\n",
- "\n",
- "Hz = sc.Unit(\"Hz\")\n",
- "deg = sc.Unit(\"deg\")\n",
- "meter = sc.Unit(\"m\")"
+ "import tof"
]
},
{
@@ -34,7 +30,7 @@
"source": [
"## Create a source pulse\n",
"\n",
- "We first create a source with 4 pulses containing 800,000 neutrons each,\n",
+ "We first create a source with 4 pulses containing 500,000 neutrons each,\n",
"and whose distribution follows the ESS time and wavelength profiles (both thermal and cold neutrons are included)."
]
},
@@ -56,142 +52,33 @@
"source": [
"## Component set-up\n",
"\n",
- "### Choppers\n",
- "\n",
"The ODIN chopper cascade consists of:\n",
"\n",
"- 2 WFM choppers\n",
"- 5 frame-overlap choppers\n",
"- 2 band-control choppers\n",
- "- 1 T0 chopper"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "5",
- "metadata": {},
- "outputs": [],
- "source": [
- "parameters = {\n",
- " \"WFMC_1\": {\n",
- " \"frequency\": 56.0,\n",
- " \"phase\": 93.244,\n",
- " \"distance\": 6.85,\n",
- " \"open\": [-1.9419, 49.5756, 98.9315, 146.2165, 191.5176, 234.9179],\n",
- " \"close\": [1.9419, 55.7157, 107.2332, 156.5891, 203.8741, 249.1752],\n",
- " },\n",
- " \"WFMC_2\": {\n",
- " \"frequency\": 56.0,\n",
- " \"phase\": 97.128,\n",
- " \"distance\": 7.15,\n",
- " \"open\": [-1.9419, 51.8318, 103.3493, 152.7052, 199.9903, 245.2914],\n",
- " \"close\": [1.9419, 57.9719, 111.6510, 163.0778, 212.3468, 259.5486],\n",
- " },\n",
- " \"FOC_1\": {\n",
- " \"frequency\": 42.0,\n",
- " \"phase\": 81.303297,\n",
- " \"distance\": 8.4,\n",
- " \"open\": [-5.1362, 42.5536, 88.2425, 132.0144, 173.9497, 216.7867],\n",
- " \"close\": [5.1362, 54.2095, 101.2237, 146.2653, 189.417, 230.7582],\n",
- " },\n",
- " \"BP_1\": {\n",
- " \"frequency\": 7.0,\n",
- " \"phase\": 31.080,\n",
- " \"distance\": 8.45,\n",
- " \"open\": [-23.6029],\n",
- " \"close\": [23.6029],\n",
- " },\n",
- " \"FOC_2\": {\n",
- " \"frequency\": 42.0,\n",
- " \"phase\": 107.013442,\n",
- " \"distance\": 12.2,\n",
- " \"open\": [-16.3227, 53.7401, 120.8633, 185.1701, 246.7787, 307.0165],\n",
- " \"close\": [16.3227, 86.8303, 154.3794, 218.7551, 280.7508, 340.3188],\n",
- " },\n",
- " \"BP_2\": {\n",
- " \"frequency\": 7.0,\n",
- " \"phase\": 44.224,\n",
- " \"distance\": 12.25,\n",
- " \"open\": [-34.4663],\n",
- " \"close\": [34.4663],\n",
- " },\n",
- " \"T0_alpha\": {\n",
- " \"frequency\": 14.0,\n",
- " \"phase\": 179.672,\n",
- " \"distance\": 13.5,\n",
- " \"open\": [-167.8986],\n",
- " \"close\": [167.8986],\n",
- " },\n",
- " \"T0_beta\": {\n",
- " \"frequency\": 14.0,\n",
- " \"phase\": 179.672,\n",
- " \"distance\": 13.7,\n",
- " \"open\": [-167.8986],\n",
- " \"close\": [167.8986],\n",
- " },\n",
- " \"FOC_3\": {\n",
- " \"frequency\": 28.0,\n",
- " \"phase\": 92.993,\n",
- " \"distance\": 17.0,\n",
- " \"open\": [-20.302, 45.247, 108.0457, 168.2095, 225.8489, 282.2199],\n",
- " \"close\": [20.302, 85.357, 147.6824, 207.3927, 264.5977, 319.4024],\n",
- " },\n",
- " \"FOC_4\": {\n",
- " \"frequency\": 14.0,\n",
- " \"phase\": 61.584,\n",
- " \"distance\": 23.69,\n",
- " \"open\": [-16.7157, 29.1882, 73.1661, 115.2988, 155.6636, 195.5254],\n",
- " \"close\": [16.7157, 61.8217, 105.0352, 146.4355, 186.0987, 224.0978],\n",
- " },\n",
- " \"FOC_5\": {\n",
- " \"frequency\": 14.0,\n",
- " \"phase\": 82.581,\n",
- " \"distance\": 33.0,\n",
- " \"open\": [-25.8514, 38.3239, 99.8064, 160.1254, 217.4321, 272.5426],\n",
- " \"close\": [25.8514, 88.4621, 147.4729, 204.0245, 257.7603, 313.7139],\n",
- " },\n",
- "}\n",
+ "- 1 T0 chopper\n",
"\n",
- "choppers = [\n",
- " tof.Chopper(\n",
- " frequency=ch[\"frequency\"] * Hz,\n",
- " direction=tof.Clockwise,\n",
- " open=sc.array(dims=[\"cutout\"], values=ch[\"open\"], unit=\"deg\"),\n",
- " close=sc.array(dims=[\"cutout\"], values=ch[\"close\"], unit=\"deg\"),\n",
- " phase=ch[\"phase\"] * deg,\n",
- " distance=ch[\"distance\"] * meter,\n",
- " name=key,\n",
- " )\n",
- " for key, ch in parameters.items()\n",
- "]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6",
- "metadata": {},
- "source": [
- "### Detector\n",
+ "It also has a single detector panel 60.5 meters from the source.\n",
"\n",
- "ODIN has a single detector panel 60.5 meters from the source."
+ "These component parameters are pre-configured in `tof.facilities.ess`.\n",
+ "Here we select the configuration in pulse-skipping mode:"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "7",
+ "id": "5",
"metadata": {},
"outputs": [],
"source": [
- "detectors = [\n",
- " tof.Detector(distance=60.5 * meter, name=\"detector\"),\n",
- "]"
+ "odin_params = tof.facilities.ess.odin(pulse_skipping=True)\n",
+ "odin_params"
]
},
{
"cell_type": "markdown",
- "id": "8",
+ "id": "6",
"metadata": {},
"source": [
"## Run the simulation\n",
@@ -202,18 +89,18 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "9",
+ "id": "7",
"metadata": {},
"outputs": [],
"source": [
- "model = tof.Model(source=source, choppers=choppers, detectors=detectors)\n",
+ "model = tof.Model(source=source, **odin_params)\n",
"results = model.run()\n",
"results.plot(blocked_rays=5000)"
]
},
{
"cell_type": "markdown",
- "id": "10",
+ "id": "8",
"metadata": {},
"source": [
"We can see that the chopper cascade is implementing WFM and pulse-skipping at the same time!\n",
@@ -231,7 +118,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "11",
+ "id": "9",
"metadata": {},
"outputs": [],
"source": [
@@ -245,7 +132,7 @@
},
{
"cell_type": "markdown",
- "id": "12",
+ "id": "10",
"metadata": {},
"source": [
"### Defining a conversion from `toa` to `wavelength`\n",
@@ -258,7 +145,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "13",
+ "id": "11",
"metadata": {},
"outputs": [],
"source": [
@@ -277,7 +164,7 @@
},
{
"cell_type": "markdown",
- "id": "14",
+ "id": "12",
"metadata": {},
"source": [
"We can now overlay our mean wavelength function on the image above:"
@@ -286,7 +173,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "15",
+ "id": "13",
"metadata": {},
"outputs": [],
"source": [
@@ -306,7 +193,7 @@
},
{
"cell_type": "markdown",
- "id": "16",
+ "id": "14",
"metadata": {},
"source": [
"## Computing wavelengths\n",
@@ -317,7 +204,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "17",
+ "id": "15",
"metadata": {},
"outputs": [],
"source": [
@@ -338,7 +225,7 @@
},
{
"cell_type": "markdown",
- "id": "18",
+ "id": "16",
"metadata": {},
"source": [
"We can now compare our computed wavelengths to the true wavelengths of the neutrons:"
@@ -347,7 +234,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "19",
+ "id": "17",
"metadata": {},
"outputs": [],
"source": [
@@ -375,7 +262,8 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
- "pygments_lexer": "ipython3"
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
}
},
"nbformat": 4,
diff --git a/src/tof/__init__.py b/src/tof/__init__.py
index 00623db..d3a75af 100644
--- a/src/tof/__init__.py
+++ b/src/tof/__init__.py
@@ -14,7 +14,7 @@
__getattr__, __dir__, __all__ = lazy.attach(
__name__,
- submodules=[],
+ submodules=['facilities'],
submod_attrs={
'chopper': ['AntiClockwise', 'Chopper', 'ChopperReading', 'Clockwise'],
'dashboard': ['Dashboard'],
diff --git a/src/tof/dashboard.py b/src/tof/dashboard.py
index ceab4af..37c5164 100644
--- a/src/tof/dashboard.py
+++ b/src/tof/dashboard.py
@@ -12,6 +12,7 @@
from matplotlib.backend_bases import PickEvent
from mpl_toolkits.axes_grid1 import make_axes_locatable
+from . import facilities
from .chopper import AntiClockwise, Chopper, Clockwise
from .detector import Detector
from .model import Model
@@ -113,13 +114,29 @@ def continuous_update(self, callback: Callable):
self.enabled_widget.observe(callback, names="value")
-class SourceWidget(ipw.VBox):
+class SetupWidget(ipw.VBox):
def __init__(self):
self.facility_widget = ipw.Dropdown(options=["ess"], description="Facility")
self.neutrons_widget = ipw.IntText(value=100_000, description="Neutrons")
self.pulses_widget = ipw.IntText(value=1, description="Pulses")
+ self.instrument_widget = ipw.Dropdown(
+ options=[
+ "ESS: Odin",
+ "ESS: Odin (pulse-skipping)",
+ "ESS: Dream (high-flux)",
+ ],
+ description="Instrument",
+ value=None,
+ )
super().__init__(
- [self.facility_widget, self.neutrons_widget, self.pulses_widget]
+ [
+ ipw.Label(value="Source:"),
+ self.facility_widget,
+ self.neutrons_widget,
+ self.pulses_widget,
+ ipw.HTML("
"),
+ self.instrument_widget,
+ ]
)
def continuous_update(self, callback: Callable):
@@ -128,6 +145,13 @@ def continuous_update(self, callback: Callable):
self.pulses_widget.observe(callback, names="value")
+INSTRUMENT_LIBRARY = {
+ "ESS: Odin": facilities.ess.odin(pulse_skipping=False),
+ "ESS: Odin (pulse-skipping)": facilities.ess.odin(pulse_skipping=True),
+ "ESS: Dream (high-flux)": facilities.ess.dream(high_flux=True),
+}
+
+
class TofWidget:
def __init__(self):
self.top_bar = ipw.HBox()
@@ -165,8 +189,11 @@ def __init__(self):
("Logy", "Toggle log scale", "arrows-alt-v", "toggle_yscale"),
]
- self.source_widget = SourceWidget()
- self.source_widget.continuous_update(self.maybe_update)
+ self.setup_widget = SetupWidget()
+ self.setup_widget.continuous_update(self.maybe_update)
+ self.setup_widget.instrument_widget.observe(
+ self.populate_from_instrument, names="value"
+ )
self.choppers_container = ipw.Accordion()
self.add_chopper_button = ipw.Button(description="Add chopper")
@@ -182,8 +209,8 @@ def __init__(self):
[self.add_detector_button, self.detectors_container]
)
- tab_contents = ["Source", "Choppers", "Detectors"]
- children = [self.source_widget, self.choppers_widget, self.detectors_widget]
+ tab_contents = ["Setup", "Choppers", "Detectors"]
+ children = [self.setup_widget, self.choppers_widget, self.detectors_widget]
self.tab = ipw.Tab(layout={"height": "650px", "width": "374px"})
self.tab.children = children
self.tab.titles = tab_contents
@@ -212,6 +239,35 @@ def __init__(self):
self.main_widget = ipw.VBox([self.top_bar, self.toa_wav_fig.canvas])
+ def populate_from_instrument(self, change):
+ cont_update_value = self.continuous_update.value
+ self.continuous_update.value = False
+ for ch in self.choppers_container.children:
+ self.remove_chopper(None, uid=ch._uid)
+ for det in self.detectors_container.children:
+ self.remove_detector(None, uid=det._uid)
+ params = INSTRUMENT_LIBRARY[change["new"]]
+ for ch in params["choppers"]:
+ self.add_chopper(None)
+ chop = self.choppers_container.children[-1]
+ chop.frequency_widget.value = ch.frequency.to(unit='Hz').value
+ chop.open_widget.value = ", ".join(
+ str(x) for x in ch.open.to(unit='deg').values
+ )
+ chop.close_widget.value = ", ".join(
+ str(x) for x in ch.close.to(unit='deg').values
+ )
+ chop.phase_widget.value = ch.phase.to(unit='deg').value
+ chop.distance_widget.value = ch.distance.to(unit='m').value
+ chop.name_widget.value = ch.name
+ for d in params["detectors"]:
+ self.add_detector(None)
+ det = self.detectors_container.children[-1]
+ det.distance_widget.value = d.distance.to(unit='m').value
+ det.name_widget.value = d.name
+ self.run(None)
+ self.continuous_update.value = cont_update_value
+
def toggle_yscale(self):
scale = "log" if self.toa_wav_ax[0].get_yscale() == "linear" else "linear"
self.toa_wav_ax[0].set_yscale(scale)
@@ -297,9 +353,9 @@ def plot_time_distance(self, _: Any | None = None):
def run(self, _: Any):
source = Source(
- facility=self.source_widget.facility_widget.value,
- neutrons=int(self.source_widget.neutrons_widget.value),
- pulses=int(self.source_widget.pulses_widget.value),
+ facility=self.setup_widget.facility_widget.value,
+ neutrons=int(self.setup_widget.neutrons_widget.value),
+ pulses=int(self.setup_widget.pulses_widget.value),
)
choppers = [
Chopper(
@@ -425,16 +481,5 @@ def Dashboard():
dashboard.
"""
w = TofWidget()
- w.add_chopper_button.click()
- ch = w.choppers_container.children[0]
- ch.frequency_widget.value = 48.0
- ch.open_widget.value = "10, 50, 150, 270"
- ch.close_widget.value = "50, 90, 190, 320"
- ch.distance_widget.value = 10.0
- ch.name_widget.value = "ChopChop"
- w.add_detector_button.click()
- d = w.detectors_container.children[0]
- d.distance_widget.value = 35.0
- d.name_widget.value = "Monitor"
- w.run(None)
+ w.setup_widget.instrument_widget.value = "ESS: Odin (pulse-skipping)"
return w.main_widget
diff --git a/src/tof/facilities/__init__.py b/src/tof/facilities/__init__.py
index 2d643f8..83ef51f 100644
--- a/src/tof/facilities/__init__.py
+++ b/src/tof/facilities/__init__.py
@@ -1,2 +1,6 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
+
+from . import ess
+
+__all__ = ["ess"]
diff --git a/src/tof/facilities/common.py b/src/tof/facilities/common.py
new file mode 100644
index 0000000..25efbe7
--- /dev/null
+++ b/src/tof/facilities/common.py
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
+
+import scipp as sc
+
+from ..chopper import AntiClockwise, Chopper, Clockwise
+from ..detector import Detector
+
+
+def _array_or_none(container, key):
+ return (
+ sc.array(dims=["cutout"], values=container[key], unit="deg")
+ if key in container
+ else None
+ )
+
+
+def make_beamline(instrument) -> dict[str, list[Chopper] | list[Detector]]:
+ choppers = [
+ Chopper(
+ frequency=ch["frequency"] * sc.Unit("Hz"),
+ direction={"clockwise": Clockwise, "anti-clockwise": AntiClockwise}[
+ ch["direction"]
+ ],
+ open=_array_or_none(ch, "open"),
+ close=_array_or_none(ch, "close"),
+ centers=_array_or_none(ch, "centers"),
+ widths=_array_or_none(ch, "widths"),
+ phase=ch["phase"] * sc.Unit("deg"),
+ distance=ch["distance"] * sc.Unit("m"),
+ name=key,
+ )
+ for key, ch in instrument["choppers"].items()
+ ]
+ detectors = [
+ Detector(distance=det["distance"] * sc.Unit("m"), name=key)
+ for key, det in instrument["detectors"].items()
+ ]
+ return {"choppers": choppers, "detectors": detectors}
diff --git a/src/tof/facilities/ess/__init__.py b/src/tof/facilities/ess/__init__.py
new file mode 100644
index 0000000..8275b51
--- /dev/null
+++ b/src/tof/facilities/ess/__init__.py
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
+
+from .dream import dream
+from .odin import odin
+from .pulse_profile import pulse
+
+__all__ = ["dream", "odin", "pulse"]
diff --git a/src/tof/facilities/ess/dream.py b/src/tof/facilities/ess/dream.py
new file mode 100644
index 0000000..0822f89
--- /dev/null
+++ b/src/tof/facilities/ess/dream.py
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
+"""
+Pre-configured chopper and detector parameters for Dream.
+"""
+
+from ...chopper import Chopper
+from ...detector import Detector
+from ..common import make_beamline
+
+dream_high_flux = {
+ "choppers": {
+ "PSC1": {
+ "frequency": 14.0,
+ "phase": 286.0 - 180.0,
+ "distance": 6.145,
+ "centers": [0, 72, 86.4, 115.2, 172.8, 273.6, 288.0, 302.4],
+ "widths": [2.46, 3.02, 3.27, 3.27, 5.02, 3.93, 3.93, 2.46],
+ "direction": "anti-clockwise",
+ },
+ "PSC2": {
+ "frequency": 14.0,
+ "phase": 236.0,
+ "distance": 6.155,
+ "centers": [0, 28.8, 57.6, 144.0, 158.4, 216.0, 259.2, 316.8],
+ "widths": [2.46, 3.60, 3.60, 3.23, 3.27, 3.77, 3.94, 2.62],
+ "direction": "clockwise",
+ },
+ "OC": {
+ "frequency": 14.0,
+ "phase": 297.0 - 180.0 - 90.0,
+ "distance": 6.174,
+ "centers": [0.0],
+ "widths": [27.6],
+ "direction": "anti-clockwise",
+ },
+ "BC": {
+ "frequency": 112.0,
+ "phase": 240.0 - 180.0,
+ "distance": 9.78,
+ "centers": [0.0, 180.0],
+ "widths": [73.75, 73.75],
+ "direction": "anti-clockwise",
+ },
+ "T0": {
+ "frequency": 28.0,
+ "phase": 280.0 - 180.0,
+ "distance": 13.05,
+ "centers": [0.0],
+ "widths": [314.9],
+ "direction": "anti-clockwise",
+ },
+ },
+ "detectors": {
+ "mantle": {"distance": 77.675},
+ "end-cap": {"distance": 77.675},
+ "high-resolution": {"distance": 79.05},
+ "sans": {"distance": 79.05},
+ },
+}
+
+
+def dream(
+ high_flux=False, high_resolution=False
+) -> dict[str, list[Chopper] | list[Detector]]:
+ if high_flux and high_resolution:
+ raise ValueError("Select either high flux or high resolution, not both.")
+ if high_flux:
+ return make_beamline(dream_high_flux)
+ if high_resolution:
+ # TODO: Add high-resolution configuration
+ raise NotImplementedError("High-resolution configuration not yet implemented.")
+ raise ValueError("Either high_flux or high_resolution must be True.")
diff --git a/src/tof/facilities/ess/odin.py b/src/tof/facilities/ess/odin.py
new file mode 100644
index 0000000..acc9cd2
--- /dev/null
+++ b/src/tof/facilities/ess/odin.py
@@ -0,0 +1,129 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
+"""
+Pre-configured chopper and detector parameters for Odin.
+"""
+
+import copy
+
+from ...chopper import Chopper
+from ...detector import Detector
+from ..common import make_beamline
+
+odin_non_pulse_skipping = {
+ "choppers": {
+ "WFMC_1": {
+ "frequency": 56.0,
+ "phase": 93.244,
+ "distance": 6.85,
+ "open": [-1.9419, 49.5756, 98.9315, 146.2165, 191.5176, 234.9179],
+ "close": [1.9419, 55.7157, 107.2332, 156.5891, 203.8741, 249.1752],
+ "direction": "clockwise",
+ },
+ "WFMC_2": {
+ "frequency": 56.0,
+ "phase": 97.128,
+ "distance": 7.15,
+ "open": [-1.9419, 51.8318, 103.3493, 152.7052, 199.9903, 245.2914],
+ "close": [1.9419, 57.9719, 111.6510, 163.0778, 212.3468, 259.5486],
+ "direction": "clockwise",
+ },
+ "FOC_1": {
+ "frequency": 42.0,
+ "phase": 81.303297,
+ "distance": 8.4,
+ "open": [-5.1362, 42.5536, 88.2425, 132.0144, 173.9497, 216.7867],
+ "close": [5.1362, 54.2095, 101.2237, 146.2653, 189.417, 230.7582],
+ "direction": "clockwise",
+ },
+ "BP_1": {
+ "frequency": 14.0,
+ "phase": 31.080 + 14.0,
+ "distance": 8.45,
+ "open": [-23.6029],
+ "close": [23.6029],
+ "direction": "clockwise",
+ },
+ "FOC_2": {
+ "frequency": 42.0,
+ "phase": 107.013442,
+ "distance": 12.2,
+ "open": [-16.3227, 53.7401, 120.8633, 185.1701, 246.7787, 307.0165],
+ "close": [16.3227, 86.8303, 154.3794, 218.7551, 280.7508, 340.3188],
+ "direction": "clockwise",
+ },
+ "BP_2": {
+ "frequency": 14.0,
+ "phase": 44.224 + 14.0,
+ "distance": 12.25,
+ "open": [-34.4663],
+ "close": [34.4663],
+ "direction": "clockwise",
+ },
+ "T0_alpha": {
+ "frequency": 14.0,
+ "phase": 179.672,
+ "distance": 13.5,
+ "open": [-167.8986],
+ "close": [167.8986],
+ "direction": "clockwise",
+ },
+ "T0_beta": {
+ "frequency": 14.0,
+ "phase": 179.672,
+ "distance": 13.7,
+ "open": [-167.8986],
+ "close": [167.8986],
+ "direction": "clockwise",
+ },
+ "FOC_3": {
+ "frequency": 28.0,
+ "phase": 92.993,
+ "distance": 17.0,
+ "open": [-20.302, 45.247, 108.0457, 168.2095, 225.8489, 282.2199],
+ "close": [20.302, 85.357, 147.6824, 207.3927, 264.5977, 319.4024],
+ "direction": "clockwise",
+ },
+ "FOC_4": {
+ "frequency": 14.0,
+ "phase": 61.584,
+ "distance": 23.69,
+ "open": [-16.7157, 29.1882, 73.1661, 115.2988, 155.6636, 195.5254],
+ "close": [16.7157, 61.8217, 105.0352, 146.4355, 186.0987, 224.0978],
+ "direction": "clockwise",
+ },
+ "FOC_5": {
+ "frequency": 14.0,
+ "phase": 82.581,
+ "distance": 33.0,
+ "open": [-25.8514, 38.3239, 99.8064, 160.1254, 217.4321, 272.5426],
+ "close": [25.8514, 88.4621, 147.4729, 204.0245, 257.7603, 313.7139],
+ "direction": "clockwise",
+ },
+ },
+ "detectors": {"detector": {"distance": 60.5}},
+}
+
+
+odin_pulse_skipping = copy.deepcopy(odin_non_pulse_skipping)
+odin_pulse_skipping["choppers"]["BP_1"]["frequency"] = 7.0
+odin_pulse_skipping["choppers"]["BP_1"]["phase"] = 31.080
+odin_pulse_skipping["choppers"]["BP_2"]["frequency"] = 7.0
+odin_pulse_skipping["choppers"]["BP_2"]["phase"] = 44.224
+
+
+def odin(
+ pulse_skipping: bool = False,
+) -> dict[str, list[Chopper] | list[Detector]]:
+ """
+ Return a pre-configured Odin beamline containing choppers and a detector.
+
+ Parameters
+ ----------
+ pulse_skipping:
+ If True, configure the beamline for pulse skipping mode (band-pass choppers will
+ rotate at 7 Hz instead of 14 Hz).
+ """
+ return make_beamline(
+ odin_pulse_skipping if pulse_skipping else odin_non_pulse_skipping
+ )
diff --git a/src/tof/facilities/ess/pulse_profile.py b/src/tof/facilities/ess/pulse_profile.py
new file mode 100644
index 0000000..fe64d73
--- /dev/null
+++ b/src/tof/facilities/ess/pulse_profile.py
@@ -0,0 +1,649 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
+
+"""
+The data here was obtained from the McStas model of the ESS moderator.
+See https://www2.mcstas.org/download/components/3.4_current/sources/ESS_butterfly.html.
+"""
+
+import scipp as sc
+
+from ...utils import PulseProfile
+
+pulse = PulseProfile(
+ birth_time=sc.DataArray(
+ data=sc.array(
+ dims=['birth_time'],
+ values=[
+ 3.35837383e-04,
+ 7.11462527e-04,
+ 1.19279849e-03,
+ 1.75844384e-03,
+ 2.30312776e-03,
+ 2.81888175e-03,
+ 3.29973291e-03,
+ 3.74342320e-03,
+ 4.12944139e-03,
+ 4.50699573e-03,
+ 4.84309044e-03,
+ 5.14929712e-03,
+ 5.45855067e-03,
+ 5.69936363e-03,
+ 5.93094105e-03,
+ 6.17477200e-03,
+ 6.37362606e-03,
+ 6.53680137e-03,
+ 6.73889852e-03,
+ 6.89076164e-03,
+ 7.03303086e-03,
+ 7.16725029e-03,
+ 7.28150493e-03,
+ 7.38097004e-03,
+ 7.47766769e-03,
+ 7.58743639e-03,
+ 7.68332632e-03,
+ 7.76191703e-03,
+ 7.81004156e-03,
+ 7.89307474e-03,
+ 7.99204102e-03,
+ 8.00486001e-03,
+ 8.06517551e-03,
+ 8.12961675e-03,
+ 8.15018882e-03,
+ 8.20005390e-03,
+ 8.24155829e-03,
+ 8.29947221e-03,
+ 8.32367988e-03,
+ 8.35031681e-03,
+ 8.38183866e-03,
+ 8.40430754e-03,
+ 8.41246895e-03,
+ 8.47906647e-03,
+ 8.48321568e-03,
+ 8.51947204e-03,
+ 8.50648386e-03,
+ 8.50133270e-03,
+ 8.52613383e-03,
+ 8.54115343e-03,
+ 8.57264133e-03,
+ 8.56943450e-03,
+ 8.55472574e-03,
+ 8.59122615e-03,
+ 8.62517530e-03,
+ 8.60719002e-03,
+ 8.60040235e-03,
+ 8.63208702e-03,
+ 8.62533445e-03,
+ 8.64678861e-03,
+ 8.64471591e-03,
+ 8.64182350e-03,
+ 8.63327922e-03,
+ 8.66950735e-03,
+ 8.65129688e-03,
+ 8.64664447e-03,
+ 8.64770277e-03,
+ 8.68294600e-03,
+ 8.67019185e-03,
+ 8.66156150e-03,
+ 8.67539908e-03,
+ 8.69230509e-03,
+ 8.66817676e-03,
+ 8.67640973e-03,
+ 8.67257086e-03,
+ 8.71476117e-03,
+ 8.69738903e-03,
+ 8.70019388e-03,
+ 8.68198365e-03,
+ 8.70585101e-03,
+ 8.69709781e-03,
+ 8.70056388e-03,
+ 8.70854571e-03,
+ 8.67909089e-03,
+ 8.68674970e-03,
+ 8.68150346e-03,
+ 8.70191501e-03,
+ 8.69301298e-03,
+ 8.69085698e-03,
+ 8.71649943e-03,
+ 8.67369456e-03,
+ 8.69969514e-03,
+ 8.72322806e-03,
+ 8.70185411e-03,
+ 8.68939648e-03,
+ 8.71606837e-03,
+ 8.68724841e-03,
+ 8.70218613e-03,
+ 8.72040120e-03,
+ 8.69293491e-03,
+ 8.69351789e-03,
+ 8.73419233e-03,
+ 8.69075919e-03,
+ 8.70522638e-03,
+ 8.71477349e-03,
+ 8.70507452e-03,
+ 8.70227301e-03,
+ 8.71420193e-03,
+ 8.71897903e-03,
+ 8.70425294e-03,
+ 8.68891152e-03,
+ 8.68402639e-03,
+ 8.70486831e-03,
+ 8.69707245e-03,
+ 8.70321249e-03,
+ 8.27644038e-03,
+ 7.95684870e-03,
+ 7.43411585e-03,
+ 6.84769508e-03,
+ 6.31981817e-03,
+ 5.79771415e-03,
+ 5.33668341e-03,
+ 4.90722076e-03,
+ 4.51723727e-03,
+ 4.14674875e-03,
+ 3.81695136e-03,
+ 3.51640474e-03,
+ 3.22513571e-03,
+ 2.95911995e-03,
+ 2.73056190e-03,
+ 2.51716771e-03,
+ 2.31576802e-03,
+ 2.12938206e-03,
+ 1.96285779e-03,
+ 1.80590850e-03,
+ 1.66301586e-03,
+ 1.52912399e-03,
+ 1.41144832e-03,
+ 1.29986696e-03,
+ 1.20219471e-03,
+ 1.10136923e-03,
+ 1.01839892e-03,
+ 9.39143113e-04,
+ 8.64814579e-04,
+ 7.96703989e-04,
+ 7.35063247e-04,
+ 6.78739795e-04,
+ 6.24226207e-04,
+ 5.76731522e-04,
+ 5.31877313e-04,
+ 4.91432490e-04,
+ 4.53724222e-04,
+ 4.17097140e-04,
+ 3.84883668e-04,
+ 3.56053519e-04,
+ 3.28615077e-04,
+ 3.03171707e-04,
+ 2.80212744e-04,
+ 2.58806676e-04,
+ 2.39233542e-04,
+ 2.20830164e-04,
+ 2.03703468e-04,
+ 1.88784094e-04,
+ 1.74177494e-04,
+ 1.61290974e-04,
+ 1.48847234e-04,
+ 1.37594011e-04,
+ 1.27396903e-04,
+ 1.17512734e-04,
+ 1.08817617e-04,
+ 1.00626721e-04,
+ 9.30799915e-05,
+ 8.58308478e-05,
+ 7.97150769e-05,
+ 7.36523610e-05,
+ 6.84294010e-05,
+ 6.32182918e-05,
+ 5.85268482e-05,
+ 5.40528453e-05,
+ 5.02368808e-05,
+ 4.65180897e-05,
+ 4.30575884e-05,
+ 3.98835225e-05,
+ 3.70504980e-05,
+ 3.42857719e-05,
+ 3.18132215e-05,
+ 2.95287409e-05,
+ 2.73577012e-05,
+ 2.53724318e-05,
+ 2.35572592e-05,
+ 2.18844303e-05,
+ 2.02605458e-05,
+ 1.88596686e-05,
+ 1.75580663e-05,
+ 1.62935792e-05,
+ 1.51211989e-05,
+ 1.40303609e-05,
+ 1.30832864e-05,
+ 1.21382107e-05,
+ 1.12829800e-05,
+ 1.05060755e-05,
+ ],
+ ),
+ coords={
+ 'birth_time': sc.array(
+ dims=['birth_time'],
+ values=[
+ 1.24378109e-05,
+ 3.73134328e-05,
+ 6.21890547e-05,
+ 8.70646766e-05,
+ 1.11940299e-04,
+ 1.36815920e-04,
+ 1.61691542e-04,
+ 1.86567164e-04,
+ 2.11442786e-04,
+ 2.36318408e-04,
+ 2.61194030e-04,
+ 2.86069652e-04,
+ 3.10945274e-04,
+ 3.35820896e-04,
+ 3.60696517e-04,
+ 3.85572139e-04,
+ 4.10447761e-04,
+ 4.35323383e-04,
+ 4.60199005e-04,
+ 4.85074627e-04,
+ 5.09950249e-04,
+ 5.34825871e-04,
+ 5.59701493e-04,
+ 5.84577114e-04,
+ 6.09452736e-04,
+ 6.34328358e-04,
+ 6.59203980e-04,
+ 6.84079602e-04,
+ 7.08955224e-04,
+ 7.33830846e-04,
+ 7.58706468e-04,
+ 7.83582090e-04,
+ 8.08457711e-04,
+ 8.33333333e-04,
+ 8.58208955e-04,
+ 8.83084577e-04,
+ 9.07960199e-04,
+ 9.32835821e-04,
+ 9.57711443e-04,
+ 9.82587065e-04,
+ 1.00746269e-03,
+ 1.03233831e-03,
+ 1.05721393e-03,
+ 1.08208955e-03,
+ 1.10696517e-03,
+ 1.13184080e-03,
+ 1.15671642e-03,
+ 1.18159204e-03,
+ 1.20646766e-03,
+ 1.23134328e-03,
+ 1.25621891e-03,
+ 1.28109453e-03,
+ 1.30597015e-03,
+ 1.33084577e-03,
+ 1.35572139e-03,
+ 1.38059701e-03,
+ 1.40547264e-03,
+ 1.43034826e-03,
+ 1.45522388e-03,
+ 1.48009950e-03,
+ 1.50497512e-03,
+ 1.52985075e-03,
+ 1.55472637e-03,
+ 1.57960199e-03,
+ 1.60447761e-03,
+ 1.62935323e-03,
+ 1.65422886e-03,
+ 1.67910448e-03,
+ 1.70398010e-03,
+ 1.72885572e-03,
+ 1.75373134e-03,
+ 1.77860697e-03,
+ 1.80348259e-03,
+ 1.82835821e-03,
+ 1.85323383e-03,
+ 1.87810945e-03,
+ 1.90298507e-03,
+ 1.92786070e-03,
+ 1.95273632e-03,
+ 1.97761194e-03,
+ 2.00248756e-03,
+ 2.02736318e-03,
+ 2.05223881e-03,
+ 2.07711443e-03,
+ 2.10199005e-03,
+ 2.12686567e-03,
+ 2.15174129e-03,
+ 2.17661692e-03,
+ 2.20149254e-03,
+ 2.22636816e-03,
+ 2.25124378e-03,
+ 2.27611940e-03,
+ 2.30099502e-03,
+ 2.32587065e-03,
+ 2.35074627e-03,
+ 2.37562189e-03,
+ 2.40049751e-03,
+ 2.42537313e-03,
+ 2.45024876e-03,
+ 2.47512438e-03,
+ 2.50000000e-03,
+ 2.52487562e-03,
+ 2.54975124e-03,
+ 2.57462687e-03,
+ 2.59950249e-03,
+ 2.62437811e-03,
+ 2.64925373e-03,
+ 2.67412935e-03,
+ 2.69900498e-03,
+ 2.72388060e-03,
+ 2.74875622e-03,
+ 2.77363184e-03,
+ 2.79850746e-03,
+ 2.82338308e-03,
+ 2.84825871e-03,
+ 2.87313433e-03,
+ 2.89800995e-03,
+ 2.92288557e-03,
+ 2.94776119e-03,
+ 2.97263682e-03,
+ 2.99751244e-03,
+ 3.02238806e-03,
+ 3.04726368e-03,
+ 3.07213930e-03,
+ 3.09701493e-03,
+ 3.12189055e-03,
+ 3.14676617e-03,
+ 3.17164179e-03,
+ 3.19651741e-03,
+ 3.22139303e-03,
+ 3.24626866e-03,
+ 3.27114428e-03,
+ 3.29601990e-03,
+ 3.32089552e-03,
+ 3.34577114e-03,
+ 3.37064677e-03,
+ 3.39552239e-03,
+ 3.42039801e-03,
+ 3.44527363e-03,
+ 3.47014925e-03,
+ 3.49502488e-03,
+ 3.51990050e-03,
+ 3.54477612e-03,
+ 3.56965174e-03,
+ 3.59452736e-03,
+ 3.61940299e-03,
+ 3.64427861e-03,
+ 3.66915423e-03,
+ 3.69402985e-03,
+ 3.71890547e-03,
+ 3.74378109e-03,
+ 3.76865672e-03,
+ 3.79353234e-03,
+ 3.81840796e-03,
+ 3.84328358e-03,
+ 3.86815920e-03,
+ 3.89303483e-03,
+ 3.91791045e-03,
+ 3.94278607e-03,
+ 3.96766169e-03,
+ 3.99253731e-03,
+ 4.01741294e-03,
+ 4.04228856e-03,
+ 4.06716418e-03,
+ 4.09203980e-03,
+ 4.11691542e-03,
+ 4.14179104e-03,
+ 4.16666667e-03,
+ 4.19154229e-03,
+ 4.21641791e-03,
+ 4.24129353e-03,
+ 4.26616915e-03,
+ 4.29104478e-03,
+ 4.31592040e-03,
+ 4.34079602e-03,
+ 4.36567164e-03,
+ 4.39054726e-03,
+ 4.41542289e-03,
+ 4.44029851e-03,
+ 4.46517413e-03,
+ 4.49004975e-03,
+ 4.51492537e-03,
+ 4.53980100e-03,
+ 4.56467662e-03,
+ 4.58955224e-03,
+ 4.61442786e-03,
+ 4.63930348e-03,
+ 4.66417910e-03,
+ 4.68905473e-03,
+ 4.71393035e-03,
+ 4.73880597e-03,
+ 4.76368159e-03,
+ 4.78855721e-03,
+ 4.81343284e-03,
+ 4.83830846e-03,
+ 4.86318408e-03,
+ 4.88805970e-03,
+ 4.91293532e-03,
+ 4.93781095e-03,
+ 4.96268657e-03,
+ 4.98756219e-03,
+ ],
+ unit='s',
+ )
+ },
+ ),
+ wavelength=sc.DataArray(
+ data=sc.array(
+ dims=['wavelength'],
+ values=[
+ 2.47910521e-02,
+ 3.15394565e-02,
+ 2.46673832e-02,
+ 4.24700432e-02,
+ 6.22005065e-02,
+ 6.41430908e-02,
+ 5.63970675e-02,
+ 4.79885540e-02,
+ 4.28010531e-02,
+ 4.17366412e-02,
+ 4.51519899e-02,
+ 5.35348380e-02,
+ 6.07649341e-02,
+ 5.33537571e-02,
+ 4.52415054e-02,
+ 3.85305979e-02,
+ 3.29632299e-02,
+ 2.82808267e-02,
+ 2.43136951e-02,
+ 2.09961870e-02,
+ 1.81474939e-02,
+ 1.57517036e-02,
+ 1.36839501e-02,
+ 1.19127479e-02,
+ 1.04094236e-02,
+ 9.09390901e-03,
+ 7.97376111e-03,
+ 7.03987735e-03,
+ 6.19641665e-03,
+ 5.48516928e-03,
+ 4.84954606e-03,
+ 4.31560357e-03,
+ 3.84683395e-03,
+ 3.43486380e-03,
+ 3.07473495e-03,
+ 2.75943204e-03,
+ 2.48705338e-03,
+ 2.24190868e-03,
+ 2.02792878e-03,
+ 1.83647522e-03,
+ 1.66751194e-03,
+ 1.52037927e-03,
+ 1.38568580e-03,
+ 1.26625902e-03,
+ 1.15985092e-03,
+ 1.06326666e-03,
+ 9.77402425e-04,
+ 8.97152751e-04,
+ 8.28473264e-04,
+ 7.65517629e-04,
+ 7.06753047e-04,
+ 6.53870845e-04,
+ 6.05626744e-04,
+ 5.61775168e-04,
+ 5.21407086e-04,
+ 4.83706064e-04,
+ 4.50096590e-04,
+ 4.18595081e-04,
+ 3.89942860e-04,
+ 3.63082390e-04,
+ 3.38196875e-04,
+ 3.16116766e-04,
+ 2.94433976e-04,
+ 2.74611217e-04,
+ 2.56804279e-04,
+ 2.39866263e-04,
+ 2.24103151e-04,
+ 2.09428428e-04,
+ 1.96024411e-04,
+ 1.83244400e-04,
+ 1.71452766e-04,
+ 1.60372237e-04,
+ 1.50247463e-04,
+ 1.40620774e-04,
+ 1.31338349e-04,
+ 1.23209023e-04,
+ 1.15219833e-04,
+ 1.07938245e-04,
+ 1.01217712e-04,
+ 9.47807301e-05,
+ 8.87686984e-05,
+ 8.31031332e-05,
+ 7.78786212e-05,
+ 7.29971694e-05,
+ 6.83232132e-05,
+ 6.40614634e-05,
+ 6.00522902e-05,
+ 5.63009205e-05,
+ 5.26437650e-05,
+ 4.93458816e-05,
+ 4.62942251e-05,
+ 4.33867986e-05,
+ 4.06646952e-05,
+ 3.80409296e-05,
+ 3.57000207e-05,
+ 3.34181377e-05,
+ 3.13558896e-05,
+ 2.93784366e-05,
+ 2.75483925e-05,
+ 2.57593945e-05,
+ 2.41535573e-05,
+ ],
+ ),
+ coords={
+ 'wavelength': sc.array(
+ dims=['wavelength'],
+ values=[
+ 1.98514851e-01,
+ 3.95544554e-01,
+ 5.92574257e-01,
+ 7.89603960e-01,
+ 9.86633663e-01,
+ 1.18366337e00,
+ 1.38069307e00,
+ 1.57772277e00,
+ 1.77475248e00,
+ 1.97178218e00,
+ 2.16881188e00,
+ 2.36584158e00,
+ 2.56287129e00,
+ 2.75990099e00,
+ 2.95693069e00,
+ 3.15396040e00,
+ 3.35099010e00,
+ 3.54801980e00,
+ 3.74504950e00,
+ 3.94207921e00,
+ 4.13910891e00,
+ 4.33613861e00,
+ 4.53316832e00,
+ 4.73019802e00,
+ 4.92722772e00,
+ 5.12425743e00,
+ 5.32128713e00,
+ 5.51831683e00,
+ 5.71534653e00,
+ 5.91237624e00,
+ 6.10940594e00,
+ 6.30643564e00,
+ 6.50346535e00,
+ 6.70049505e00,
+ 6.89752475e00,
+ 7.09455446e00,
+ 7.29158416e00,
+ 7.48861386e00,
+ 7.68564356e00,
+ 7.88267327e00,
+ 8.07970297e00,
+ 8.27673267e00,
+ 8.47376238e00,
+ 8.67079208e00,
+ 8.86782178e00,
+ 9.06485149e00,
+ 9.26188119e00,
+ 9.45891089e00,
+ 9.65594059e00,
+ 9.85297030e00,
+ 1.00500000e01,
+ 1.02470297e01,
+ 1.04440594e01,
+ 1.06410891e01,
+ 1.08381188e01,
+ 1.10351485e01,
+ 1.12321782e01,
+ 1.14292079e01,
+ 1.16262376e01,
+ 1.18232673e01,
+ 1.20202970e01,
+ 1.22173267e01,
+ 1.24143564e01,
+ 1.26113861e01,
+ 1.28084158e01,
+ 1.30054455e01,
+ 1.32024752e01,
+ 1.33995050e01,
+ 1.35965347e01,
+ 1.37935644e01,
+ 1.39905941e01,
+ 1.41876238e01,
+ 1.43846535e01,
+ 1.45816832e01,
+ 1.47787129e01,
+ 1.49757426e01,
+ 1.51727723e01,
+ 1.53698020e01,
+ 1.55668317e01,
+ 1.57638614e01,
+ 1.59608911e01,
+ 1.61579208e01,
+ 1.63549505e01,
+ 1.65519802e01,
+ 1.67490099e01,
+ 1.69460396e01,
+ 1.71430693e01,
+ 1.73400990e01,
+ 1.75371287e01,
+ 1.77341584e01,
+ 1.79311881e01,
+ 1.81282178e01,
+ 1.83252475e01,
+ 1.85222772e01,
+ 1.87193069e01,
+ 1.89163366e01,
+ 1.91133663e01,
+ 1.93103960e01,
+ 1.95074257e01,
+ 1.97044554e01,
+ 1.99014851e01,
+ ],
+ unit='angstrom',
+ )
+ },
+ ),
+ frequency=14.0 * sc.Unit('Hz'),
+)
diff --git a/src/tof/facilities/ess_pulse.py b/src/tof/facilities/ess_pulse.py
deleted file mode 100644
index 48f5b24..0000000
--- a/src/tof/facilities/ess_pulse.py
+++ /dev/null
@@ -1,647 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
-
-"""
-The data here was obtained from the McStas model of the ESS moderator.
-See https://www2.mcstas.org/download/components/3.4_current/sources/ESS_butterfly.html.
-"""
-
-import scipp as sc
-
-birth_time = sc.DataArray(
- data=sc.array(
- dims=['birth_time'],
- values=[
- 3.35837383e-04,
- 7.11462527e-04,
- 1.19279849e-03,
- 1.75844384e-03,
- 2.30312776e-03,
- 2.81888175e-03,
- 3.29973291e-03,
- 3.74342320e-03,
- 4.12944139e-03,
- 4.50699573e-03,
- 4.84309044e-03,
- 5.14929712e-03,
- 5.45855067e-03,
- 5.69936363e-03,
- 5.93094105e-03,
- 6.17477200e-03,
- 6.37362606e-03,
- 6.53680137e-03,
- 6.73889852e-03,
- 6.89076164e-03,
- 7.03303086e-03,
- 7.16725029e-03,
- 7.28150493e-03,
- 7.38097004e-03,
- 7.47766769e-03,
- 7.58743639e-03,
- 7.68332632e-03,
- 7.76191703e-03,
- 7.81004156e-03,
- 7.89307474e-03,
- 7.99204102e-03,
- 8.00486001e-03,
- 8.06517551e-03,
- 8.12961675e-03,
- 8.15018882e-03,
- 8.20005390e-03,
- 8.24155829e-03,
- 8.29947221e-03,
- 8.32367988e-03,
- 8.35031681e-03,
- 8.38183866e-03,
- 8.40430754e-03,
- 8.41246895e-03,
- 8.47906647e-03,
- 8.48321568e-03,
- 8.51947204e-03,
- 8.50648386e-03,
- 8.50133270e-03,
- 8.52613383e-03,
- 8.54115343e-03,
- 8.57264133e-03,
- 8.56943450e-03,
- 8.55472574e-03,
- 8.59122615e-03,
- 8.62517530e-03,
- 8.60719002e-03,
- 8.60040235e-03,
- 8.63208702e-03,
- 8.62533445e-03,
- 8.64678861e-03,
- 8.64471591e-03,
- 8.64182350e-03,
- 8.63327922e-03,
- 8.66950735e-03,
- 8.65129688e-03,
- 8.64664447e-03,
- 8.64770277e-03,
- 8.68294600e-03,
- 8.67019185e-03,
- 8.66156150e-03,
- 8.67539908e-03,
- 8.69230509e-03,
- 8.66817676e-03,
- 8.67640973e-03,
- 8.67257086e-03,
- 8.71476117e-03,
- 8.69738903e-03,
- 8.70019388e-03,
- 8.68198365e-03,
- 8.70585101e-03,
- 8.69709781e-03,
- 8.70056388e-03,
- 8.70854571e-03,
- 8.67909089e-03,
- 8.68674970e-03,
- 8.68150346e-03,
- 8.70191501e-03,
- 8.69301298e-03,
- 8.69085698e-03,
- 8.71649943e-03,
- 8.67369456e-03,
- 8.69969514e-03,
- 8.72322806e-03,
- 8.70185411e-03,
- 8.68939648e-03,
- 8.71606837e-03,
- 8.68724841e-03,
- 8.70218613e-03,
- 8.72040120e-03,
- 8.69293491e-03,
- 8.69351789e-03,
- 8.73419233e-03,
- 8.69075919e-03,
- 8.70522638e-03,
- 8.71477349e-03,
- 8.70507452e-03,
- 8.70227301e-03,
- 8.71420193e-03,
- 8.71897903e-03,
- 8.70425294e-03,
- 8.68891152e-03,
- 8.68402639e-03,
- 8.70486831e-03,
- 8.69707245e-03,
- 8.70321249e-03,
- 8.27644038e-03,
- 7.95684870e-03,
- 7.43411585e-03,
- 6.84769508e-03,
- 6.31981817e-03,
- 5.79771415e-03,
- 5.33668341e-03,
- 4.90722076e-03,
- 4.51723727e-03,
- 4.14674875e-03,
- 3.81695136e-03,
- 3.51640474e-03,
- 3.22513571e-03,
- 2.95911995e-03,
- 2.73056190e-03,
- 2.51716771e-03,
- 2.31576802e-03,
- 2.12938206e-03,
- 1.96285779e-03,
- 1.80590850e-03,
- 1.66301586e-03,
- 1.52912399e-03,
- 1.41144832e-03,
- 1.29986696e-03,
- 1.20219471e-03,
- 1.10136923e-03,
- 1.01839892e-03,
- 9.39143113e-04,
- 8.64814579e-04,
- 7.96703989e-04,
- 7.35063247e-04,
- 6.78739795e-04,
- 6.24226207e-04,
- 5.76731522e-04,
- 5.31877313e-04,
- 4.91432490e-04,
- 4.53724222e-04,
- 4.17097140e-04,
- 3.84883668e-04,
- 3.56053519e-04,
- 3.28615077e-04,
- 3.03171707e-04,
- 2.80212744e-04,
- 2.58806676e-04,
- 2.39233542e-04,
- 2.20830164e-04,
- 2.03703468e-04,
- 1.88784094e-04,
- 1.74177494e-04,
- 1.61290974e-04,
- 1.48847234e-04,
- 1.37594011e-04,
- 1.27396903e-04,
- 1.17512734e-04,
- 1.08817617e-04,
- 1.00626721e-04,
- 9.30799915e-05,
- 8.58308478e-05,
- 7.97150769e-05,
- 7.36523610e-05,
- 6.84294010e-05,
- 6.32182918e-05,
- 5.85268482e-05,
- 5.40528453e-05,
- 5.02368808e-05,
- 4.65180897e-05,
- 4.30575884e-05,
- 3.98835225e-05,
- 3.70504980e-05,
- 3.42857719e-05,
- 3.18132215e-05,
- 2.95287409e-05,
- 2.73577012e-05,
- 2.53724318e-05,
- 2.35572592e-05,
- 2.18844303e-05,
- 2.02605458e-05,
- 1.88596686e-05,
- 1.75580663e-05,
- 1.62935792e-05,
- 1.51211989e-05,
- 1.40303609e-05,
- 1.30832864e-05,
- 1.21382107e-05,
- 1.12829800e-05,
- 1.05060755e-05,
- ],
- ),
- coords={
- 'birth_time': sc.array(
- dims=['birth_time'],
- values=[
- 1.24378109e-05,
- 3.73134328e-05,
- 6.21890547e-05,
- 8.70646766e-05,
- 1.11940299e-04,
- 1.36815920e-04,
- 1.61691542e-04,
- 1.86567164e-04,
- 2.11442786e-04,
- 2.36318408e-04,
- 2.61194030e-04,
- 2.86069652e-04,
- 3.10945274e-04,
- 3.35820896e-04,
- 3.60696517e-04,
- 3.85572139e-04,
- 4.10447761e-04,
- 4.35323383e-04,
- 4.60199005e-04,
- 4.85074627e-04,
- 5.09950249e-04,
- 5.34825871e-04,
- 5.59701493e-04,
- 5.84577114e-04,
- 6.09452736e-04,
- 6.34328358e-04,
- 6.59203980e-04,
- 6.84079602e-04,
- 7.08955224e-04,
- 7.33830846e-04,
- 7.58706468e-04,
- 7.83582090e-04,
- 8.08457711e-04,
- 8.33333333e-04,
- 8.58208955e-04,
- 8.83084577e-04,
- 9.07960199e-04,
- 9.32835821e-04,
- 9.57711443e-04,
- 9.82587065e-04,
- 1.00746269e-03,
- 1.03233831e-03,
- 1.05721393e-03,
- 1.08208955e-03,
- 1.10696517e-03,
- 1.13184080e-03,
- 1.15671642e-03,
- 1.18159204e-03,
- 1.20646766e-03,
- 1.23134328e-03,
- 1.25621891e-03,
- 1.28109453e-03,
- 1.30597015e-03,
- 1.33084577e-03,
- 1.35572139e-03,
- 1.38059701e-03,
- 1.40547264e-03,
- 1.43034826e-03,
- 1.45522388e-03,
- 1.48009950e-03,
- 1.50497512e-03,
- 1.52985075e-03,
- 1.55472637e-03,
- 1.57960199e-03,
- 1.60447761e-03,
- 1.62935323e-03,
- 1.65422886e-03,
- 1.67910448e-03,
- 1.70398010e-03,
- 1.72885572e-03,
- 1.75373134e-03,
- 1.77860697e-03,
- 1.80348259e-03,
- 1.82835821e-03,
- 1.85323383e-03,
- 1.87810945e-03,
- 1.90298507e-03,
- 1.92786070e-03,
- 1.95273632e-03,
- 1.97761194e-03,
- 2.00248756e-03,
- 2.02736318e-03,
- 2.05223881e-03,
- 2.07711443e-03,
- 2.10199005e-03,
- 2.12686567e-03,
- 2.15174129e-03,
- 2.17661692e-03,
- 2.20149254e-03,
- 2.22636816e-03,
- 2.25124378e-03,
- 2.27611940e-03,
- 2.30099502e-03,
- 2.32587065e-03,
- 2.35074627e-03,
- 2.37562189e-03,
- 2.40049751e-03,
- 2.42537313e-03,
- 2.45024876e-03,
- 2.47512438e-03,
- 2.50000000e-03,
- 2.52487562e-03,
- 2.54975124e-03,
- 2.57462687e-03,
- 2.59950249e-03,
- 2.62437811e-03,
- 2.64925373e-03,
- 2.67412935e-03,
- 2.69900498e-03,
- 2.72388060e-03,
- 2.74875622e-03,
- 2.77363184e-03,
- 2.79850746e-03,
- 2.82338308e-03,
- 2.84825871e-03,
- 2.87313433e-03,
- 2.89800995e-03,
- 2.92288557e-03,
- 2.94776119e-03,
- 2.97263682e-03,
- 2.99751244e-03,
- 3.02238806e-03,
- 3.04726368e-03,
- 3.07213930e-03,
- 3.09701493e-03,
- 3.12189055e-03,
- 3.14676617e-03,
- 3.17164179e-03,
- 3.19651741e-03,
- 3.22139303e-03,
- 3.24626866e-03,
- 3.27114428e-03,
- 3.29601990e-03,
- 3.32089552e-03,
- 3.34577114e-03,
- 3.37064677e-03,
- 3.39552239e-03,
- 3.42039801e-03,
- 3.44527363e-03,
- 3.47014925e-03,
- 3.49502488e-03,
- 3.51990050e-03,
- 3.54477612e-03,
- 3.56965174e-03,
- 3.59452736e-03,
- 3.61940299e-03,
- 3.64427861e-03,
- 3.66915423e-03,
- 3.69402985e-03,
- 3.71890547e-03,
- 3.74378109e-03,
- 3.76865672e-03,
- 3.79353234e-03,
- 3.81840796e-03,
- 3.84328358e-03,
- 3.86815920e-03,
- 3.89303483e-03,
- 3.91791045e-03,
- 3.94278607e-03,
- 3.96766169e-03,
- 3.99253731e-03,
- 4.01741294e-03,
- 4.04228856e-03,
- 4.06716418e-03,
- 4.09203980e-03,
- 4.11691542e-03,
- 4.14179104e-03,
- 4.16666667e-03,
- 4.19154229e-03,
- 4.21641791e-03,
- 4.24129353e-03,
- 4.26616915e-03,
- 4.29104478e-03,
- 4.31592040e-03,
- 4.34079602e-03,
- 4.36567164e-03,
- 4.39054726e-03,
- 4.41542289e-03,
- 4.44029851e-03,
- 4.46517413e-03,
- 4.49004975e-03,
- 4.51492537e-03,
- 4.53980100e-03,
- 4.56467662e-03,
- 4.58955224e-03,
- 4.61442786e-03,
- 4.63930348e-03,
- 4.66417910e-03,
- 4.68905473e-03,
- 4.71393035e-03,
- 4.73880597e-03,
- 4.76368159e-03,
- 4.78855721e-03,
- 4.81343284e-03,
- 4.83830846e-03,
- 4.86318408e-03,
- 4.88805970e-03,
- 4.91293532e-03,
- 4.93781095e-03,
- 4.96268657e-03,
- 4.98756219e-03,
- ],
- unit='s',
- )
- },
-)
-
-wavelength = sc.DataArray(
- data=sc.array(
- dims=['wavelength'],
- values=[
- 2.47910521e-02,
- 3.15394565e-02,
- 2.46673832e-02,
- 4.24700432e-02,
- 6.22005065e-02,
- 6.41430908e-02,
- 5.63970675e-02,
- 4.79885540e-02,
- 4.28010531e-02,
- 4.17366412e-02,
- 4.51519899e-02,
- 5.35348380e-02,
- 6.07649341e-02,
- 5.33537571e-02,
- 4.52415054e-02,
- 3.85305979e-02,
- 3.29632299e-02,
- 2.82808267e-02,
- 2.43136951e-02,
- 2.09961870e-02,
- 1.81474939e-02,
- 1.57517036e-02,
- 1.36839501e-02,
- 1.19127479e-02,
- 1.04094236e-02,
- 9.09390901e-03,
- 7.97376111e-03,
- 7.03987735e-03,
- 6.19641665e-03,
- 5.48516928e-03,
- 4.84954606e-03,
- 4.31560357e-03,
- 3.84683395e-03,
- 3.43486380e-03,
- 3.07473495e-03,
- 2.75943204e-03,
- 2.48705338e-03,
- 2.24190868e-03,
- 2.02792878e-03,
- 1.83647522e-03,
- 1.66751194e-03,
- 1.52037927e-03,
- 1.38568580e-03,
- 1.26625902e-03,
- 1.15985092e-03,
- 1.06326666e-03,
- 9.77402425e-04,
- 8.97152751e-04,
- 8.28473264e-04,
- 7.65517629e-04,
- 7.06753047e-04,
- 6.53870845e-04,
- 6.05626744e-04,
- 5.61775168e-04,
- 5.21407086e-04,
- 4.83706064e-04,
- 4.50096590e-04,
- 4.18595081e-04,
- 3.89942860e-04,
- 3.63082390e-04,
- 3.38196875e-04,
- 3.16116766e-04,
- 2.94433976e-04,
- 2.74611217e-04,
- 2.56804279e-04,
- 2.39866263e-04,
- 2.24103151e-04,
- 2.09428428e-04,
- 1.96024411e-04,
- 1.83244400e-04,
- 1.71452766e-04,
- 1.60372237e-04,
- 1.50247463e-04,
- 1.40620774e-04,
- 1.31338349e-04,
- 1.23209023e-04,
- 1.15219833e-04,
- 1.07938245e-04,
- 1.01217712e-04,
- 9.47807301e-05,
- 8.87686984e-05,
- 8.31031332e-05,
- 7.78786212e-05,
- 7.29971694e-05,
- 6.83232132e-05,
- 6.40614634e-05,
- 6.00522902e-05,
- 5.63009205e-05,
- 5.26437650e-05,
- 4.93458816e-05,
- 4.62942251e-05,
- 4.33867986e-05,
- 4.06646952e-05,
- 3.80409296e-05,
- 3.57000207e-05,
- 3.34181377e-05,
- 3.13558896e-05,
- 2.93784366e-05,
- 2.75483925e-05,
- 2.57593945e-05,
- 2.41535573e-05,
- ],
- ),
- coords={
- 'wavelength': sc.array(
- dims=['wavelength'],
- values=[
- 1.98514851e-01,
- 3.95544554e-01,
- 5.92574257e-01,
- 7.89603960e-01,
- 9.86633663e-01,
- 1.18366337e00,
- 1.38069307e00,
- 1.57772277e00,
- 1.77475248e00,
- 1.97178218e00,
- 2.16881188e00,
- 2.36584158e00,
- 2.56287129e00,
- 2.75990099e00,
- 2.95693069e00,
- 3.15396040e00,
- 3.35099010e00,
- 3.54801980e00,
- 3.74504950e00,
- 3.94207921e00,
- 4.13910891e00,
- 4.33613861e00,
- 4.53316832e00,
- 4.73019802e00,
- 4.92722772e00,
- 5.12425743e00,
- 5.32128713e00,
- 5.51831683e00,
- 5.71534653e00,
- 5.91237624e00,
- 6.10940594e00,
- 6.30643564e00,
- 6.50346535e00,
- 6.70049505e00,
- 6.89752475e00,
- 7.09455446e00,
- 7.29158416e00,
- 7.48861386e00,
- 7.68564356e00,
- 7.88267327e00,
- 8.07970297e00,
- 8.27673267e00,
- 8.47376238e00,
- 8.67079208e00,
- 8.86782178e00,
- 9.06485149e00,
- 9.26188119e00,
- 9.45891089e00,
- 9.65594059e00,
- 9.85297030e00,
- 1.00500000e01,
- 1.02470297e01,
- 1.04440594e01,
- 1.06410891e01,
- 1.08381188e01,
- 1.10351485e01,
- 1.12321782e01,
- 1.14292079e01,
- 1.16262376e01,
- 1.18232673e01,
- 1.20202970e01,
- 1.22173267e01,
- 1.24143564e01,
- 1.26113861e01,
- 1.28084158e01,
- 1.30054455e01,
- 1.32024752e01,
- 1.33995050e01,
- 1.35965347e01,
- 1.37935644e01,
- 1.39905941e01,
- 1.41876238e01,
- 1.43846535e01,
- 1.45816832e01,
- 1.47787129e01,
- 1.49757426e01,
- 1.51727723e01,
- 1.53698020e01,
- 1.55668317e01,
- 1.57638614e01,
- 1.59608911e01,
- 1.61579208e01,
- 1.63549505e01,
- 1.65519802e01,
- 1.67490099e01,
- 1.69460396e01,
- 1.71430693e01,
- 1.73400990e01,
- 1.75371287e01,
- 1.77341584e01,
- 1.79311881e01,
- 1.81282178e01,
- 1.83252475e01,
- 1.85222772e01,
- 1.87193069e01,
- 1.89163366e01,
- 1.91133663e01,
- 1.93103960e01,
- 1.95074257e01,
- 1.97044554e01,
- 1.99014851e01,
- ],
- unit='angstrom',
- )
- },
-)
-
-frequency = 14.0 * sc.Unit('Hz')
diff --git a/src/tof/facilities/ess_pulse/__init__.py b/src/tof/facilities/ess_pulse/__init__.py
new file mode 100644
index 0000000..8db86ae
--- /dev/null
+++ b/src/tof/facilities/ess_pulse/__init__.py
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
+
+"""
+This module exists only to provide retro-compatibility for `essreduce` and
+potentially other downstream packages that used to import pulse parameters from the old
+`tof.facilities.ess_pulse` module.
+
+This should be phased out in future releases.
+"""
+
+from ..ess.pulse_profile import pulse
+
+frequency = pulse.frequency
+birth_time = pulse.birth_time
+wavelength = pulse.wavelength
+
+__all__ = ["birth_time", "frequency", "wavelength"]
+
+del pulse
diff --git a/src/tof/source.py b/src/tof/source.py
index af7d848..33cdfe8 100644
--- a/src/tof/source.py
+++ b/src/tof/source.py
@@ -205,16 +205,16 @@ def __init__(
wmax: sc.Variable | None = None,
seed: int | None = None,
):
- self.facility = facility
+ self.facility = facility.lower() if facility is not None else None
self.neutrons = int(neutrons)
self.pulses = int(pulses)
self.data = None
- if facility is not None:
+ if self.facility is not None:
try:
- facility_params = import_module(f"tof.facilities.{facility}_pulse")
+ facility_params = import_module(f"tof.facilities.{self.facility}").pulse
except ModuleNotFoundError as e:
- raise ValueError(f"Facility '{facility}' not found.") from e
+ raise ValueError(f"Facility '{self.facility}' not found.") from e
self.frequency = facility_params.frequency
pulse_params = _make_pulses(
neutrons=self.neutrons,
diff --git a/src/tof/utils.py b/src/tof/utils.py
index c1e51f5..45eaf46 100644
--- a/src/tof/utils.py
+++ b/src/tof/utils.py
@@ -91,3 +91,23 @@ def one_mask(
class Plot:
ax: plt.Axes
fig: plt.Figure
+
+
+@dataclass
+class PulseProfile:
+ """
+ Parameters of a neutron pulse (typically from a neutron facility).
+
+ Parameters
+ ----------
+ frequency:
+ Frequency of the pulse in Hz.
+ birth_time:
+ Probability distribution of neutrons in time within a single pulse.
+ wavelength:
+ Probability distribution of neutrons in wavelength within a single pulse.
+ """
+
+ frequency: sc.Variable
+ birth_time: sc.DataArray
+ wavelength: sc.DataArray
diff --git a/tests/source_test.py b/tests/source_test.py
index 5c8d23c..2d6fb7a 100644
--- a/tests/source_test.py
+++ b/tests/source_test.py
@@ -22,6 +22,15 @@ def test_ess_pulse():
assert (wavs[:150].sum() > 1.5 * wavs[150:].sum()).value
+@pytest.mark.parametrize('facility', ['ESS', 'Ess'])
+def test_ess_pulse_uppercase(facility):
+ source = tof.Source(facility=facility, neutrons=100_000)
+ assert source.neutrons == 100_000
+ assert sc.identical(source.frequency, sc.scalar(14.0, unit="Hz"))
+ assert source.pulses == 1
+ assert source.facility == 'ess'
+
+
def test_creation_from_neutrons():
birth_times = sc.array(dims=['event'], values=[1000.0, 1500.0, 2000.0], unit='us')
wavelengths = sc.array(dims=['event'], values=[1.0, 5.0, 10.0], unit='angstrom')