McStas Readout Master 0.3.3
Loading...
Searching...
No Matches
Examples

Complete runnable example

The repository ships a full collect → combine → replay walkthrough in examples/:

  • examples/readout_example.instr — a small instrument with a TTL beam monitor and a bank of CAEN-read tubes, both storing records into one collector file,
  • examples/run_example.sh — runs two simulation points, validates and concatenates the per-point files, and replays the combined file.
/*******************************************************************************
* Complete collect -> combine -> replay example for mcstas-readout-master.
*
* A small continuous source illuminates a TTL beam monitor and a bank of ten
* position-sensitive tubes read out by CAEN digitizers. Both detectors store
* weighted-ray records into one HDF5 file through Collector components; no
* Event Formation Unit is needed while this instrument runs.
*
* Run repeatedly (or scan `lambda`), then combine the per-run files with
* `readout-combine` and send the events to EFUs with `readout-replay` — see
* run_example.sh next to this file.
*******************************************************************************/
DEFINE INSTRUMENT readout_example(lambda=4.0, string filename="example")
USERVARS
%{
int ring_id;
int fen_id;
int tube_id;
int amp_a;
int amp_b;
int monitor_signal;
int detected;
%}
TRACE
SEARCH SHELL "readout-config --show compdir"
COMPONENT origin = Progress_bar() AT (0, 0, 0) ABSOLUTE
COMPONENT source = Source_simple(
radius = 0.02, dist = 2, focus_xw = 0.10, focus_yh = 0.10,
lambda0 = lambda, dlambda = 0.1 * lambda)
AT (0, 0, 0) RELATIVE origin
/* A 0-D beam monitor: every transmitted neutron leaves a digitized signal. */
COMPONENT beam_monitor = Monitor(xwidth = 0.10, yheight = 0.10, restore_neutron = 1)
AT (0, 0, 2) RELATIVE origin
EXTEND %{
ring_id = 0;
fen_id = 0;
monitor_signal = SCATTERED ? 500 + (int)(rand01() * 1024) : 0;
%}
COMPONENT collect_monitor = CollectorTTLMonitor(
ring = "ring_id", fen = "fen_id", value = "monitor_signal",
identity_value = 0, filename = filename, dataset_name = "monitor")
WHEN (monitor_signal > 0)
AT (0, 0, 2) RELATIVE origin
/* A plane standing in for ten position-sensitive tubes, 0.10 m wide: the
* tube index follows from x, and charge division along the tube (y) gives
* the two CAEN amplitudes. */
COMPONENT detector_plane = Monitor(xwidth = 0.10, yheight = 0.10, restore_neutron = 1)
AT (0, 0, 4) RELATIVE origin
EXTEND %{
detected = SCATTERED ? 1 : 0;
if (detected) {
double frac = (y / 0.10) + 0.5; /* 0 at bottom, 1 at top */
ring_id = 1;
fen_id = 0;
tube_id = (int)(((x / 0.10) + 0.5) * 10); /* 0 .. 9 across the bank */
amp_a = (int)(2048 * frac);
amp_b = (int)(2048 * (1.0 - frac));
}
%}
COMPONENT collect_detector = CollectorCAEN(
ring = "ring_id", fen = "fen_id", tube = "tube_id",
a_name = "amp_a", b_name = "amp_b",
filename = filename, dataset_name = "detector")
WHEN (detected)
AT (0, 0, 4) RELATIVE origin
END

The walkthrough:

#!/usr/bin/env bash
# Collect -> combine -> replay walkthrough for readout_example.instr.
#
# Requires: McStas 3.3+ (mcrun) and an installed mcstas-readout-master
# (readout-config, readout-combine, readout-replay on PATH).
#
# Replay sends UDP packets; without a listening EFU they are simply dropped,
# so the walkthrough is safe to run anywhere. Point --addr/--port at a real
# EFU to feed the full ESS pipeline.
#
# replay_publisher.py in this directory is the Python twin of step 3, driving
# the same replay through the mcstas_readout package instead of the CLI.
set -euo pipefail
cd "$(dirname "$0")"
command -v mcrun >/dev/null || { echo "mcrun (McStas 3.3+) not found on PATH" >&2; exit 1; }
command -v readout-config >/dev/null || { echo "readout-config not found on PATH; install mcstas-readout-master first" >&2; exit 1; }
workdir=$(mktemp -d readout-example.XXXXXX)
echo "Working in $workdir"
# 1. Collect: run two simulation points with different wavelengths.
# Each run writes its collector groups ('monitor', 'detector') into one
# HDF5 file in the run directory.
mcrun readout_example.instr -d "$workdir/point_0" -n 1e5 lambda=4.0 filename=point_0
mcrun readout_example.instr -d "$workdir/point_1" -n 1e5 lambda=5.0 filename=point_1
# 2. Validate and combine the per-point files into one multi-point file.
readout-combine validate "$workdir"/point_*/point_*.h5
readout-combine concatenate --output "$workdir/scan.h5" \
"$workdir/point_0/point_0.h5" "$workdir/point_1/point_1.h5"
readout-combine validate "$workdir/scan.h5"
# 3. Replay: step through the two points, publish their parameters, and send
# n ~ Poisson(weight * 2.0 s) events per stored readout.
readout-replay --verbose --sequential --time 2.0 --seed 42 \
--addr 127.0.0.1 --port 9000 "$workdir/scan.h5"
echo "Done. Combined file: $workdir/scan.h5"

Replay sends UDP packets; without a listening EFU they are simply dropped, so the walkthrough is safe to run anywhere.

Example A: scan points, combine, replay

Run your instrument repeatedly (or as a scan), producing one collector file per point:

./MyInstrument.out -n 1000000 filename=point_000
./MyInstrument.out -n 1000000 filename=point_001
./MyInstrument.out -n 1000000 filename=point_002

Concatenate into one multi-point file:

readout-combine concatenate --output scan_all.h5 point_000.h5 point_001.h5 point_002.h5

Replay with parameter publication and Poisson sampling:

readout-replay --time 2.0 --seed 42 --config senders.json scan_all.h5

Example B: explicit sender routing

senders.json (see the CLI reference for the full schema):

{
"senders": [
{
"detector_type": "BIFROST",
"readout_type": "CAEN",
"ip_address": "efu-caen.example.org",
"udp_port": 9001,
"tcp_port": 10800
},
{
"detector_type": "TTLMonitor",
"readout_type": "TTLMonitor",
"ip_address": "efu-ttl.example.org",
"udp_port": 9002,
"tcp_port": 10800
}
]
}

Run, with command-line defaults for any group not matched by the configuration or by file-embedded attributes:

readout-replay --config senders.json --addr efu-default.example.org --port 9000 scan_all.h5

Example C: instrument TRACE fragment for a collector

TRACE
SEARCH SHELL "readout-config --show compdir"
...
COMPONENT collect = CollectorTTLMonitor(
filename="monitor_run",
dataset_name="beam_monitor",
ring="ring_id",
fen="fen_id",
identity="channel",
value="adc",
tof="t"
) AT (0,0,0) RELATIVE PREVIOUS
...

Example D: driving replay from Python

The same replay as Example A, controlled in-process through the Python API with a custom parameter publisher:

import mcstas_readout as ro
class Printer(ro.ParameterPublisher):
def publish(self, point, name, value, unit):
print(f"point {point}: {name} = {value}" + (f" [{unit}]" if unit else ""))
config = ro.ReplayConfig(counting_time=2.0, seed=42,
default_address="127.0.0.1", default_port=9000)
ro.replay("scan_all.h5", config, Printer())

examples/replay_publisher.py is the runnable version, including timed cancellation from a second thread:

#!/usr/bin/env python3
"""Python twin of run_example.sh step 3: replay a collector file with a
stdout parameter publisher, equivalent to
readout-replay --verbose --sequential --time 2.0 --seed 42 \
--addr 127.0.0.1 --port 9000 <file>
but driven through the mcstas_readout package, the route an external tool
(e.g. mccode-plumber) takes to publish parameters to EPICS instead of stdout.
Usage: replay_publisher.py <collector-file.h5> [--cancel-after SECONDS]
Replay sends UDP packets; without a listening EFU they are simply dropped, so
this is safe to run anywhere. --cancel-after demonstrates thread-safe
cooperative cancellation: run() returns False instead of True when the timer
fires first.
"""
import argparse
import threading
import mcstas_readout as ro
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("filename", help="multi-point collector file, e.g. scan.h5")
parser.add_argument("--cancel-after", type=float, default=None, metavar="SECONDS",
help="cancel the replay from a timer thread after this many seconds")
args = parser.parse_args()
points = ro.validate_collector_file(args.filename)
print(f"{args.filename}: {points} point(s)")
config = ro.ReplayConfig(counting_time=2.0, seed=42,
default_address="127.0.0.1", default_port=9000)
job = ro.Replay(args.filename, config, ro.StreamParameterPublisher())
timer = None
if args.cancel_after is not None:
timer = threading.Timer(args.cancel_after, job.cancel)
timer.start()
with job:
completed = job.run()
if timer is not None:
timer.cancel()
print("replay completed" if completed else "replay cancelled")
return 0 if completed else 1
if __name__ == "__main__":
raise SystemExit(main())