Convert an instrument¶
An instrument is a tree, and every target reads the same walk over it. This is what that looks like from the outside.
Say what the instrument is¶
An Instrument names its pieces and says what each hangs from.
from niess.bifrost import Primary, Tank
from niess.bifrost.parameters import primary_parameters, tank_parameters
from niess.instrument import Instrument, Mount
bifrost = Instrument(
name='bifrost',
origin='sample_origin',
parts=(
Mount(name='primary', content=Primary.from_calibration(primary_parameters())),
# the tank is described in coordinates about the sample, so it hangs there
Mount(name='tank', content=Tank.from_calibration(tank_parameters()),
relative_to='sample_origin'),
),
)
BIFROST's primary is described in global coordinates, so it hangs from nothing. Its tank
is described about the sample — the first analyzer sits at [1.189, 0, 0], not 162 m
down the guide — so it hangs from sample_origin, which the primary provides.
origin names the component everything is measured against. Targets need it, and
saying it once here is better than each of them guessing.
A piece can be turned at run time
Mount(..., rotation=(0, a4, 0)) turns a piece about the frame it hangs from, in
degrees. The three angles may be numbers or InstrumentParameters, because the
interesting ones are set per run: a BIFROST run turns the sample by
sample_rotation (a3) and the detector tank by detector_tank_angle (a4). McStas emits an Arm turned by the named parameter; NeXus
emits a transformation linking to that parameter's NXlog.
Convert it¶
from niess.nexus import to_nexus_structure
from niess.nexus.bifrost import BIFROST_REGISTRY
structure = to_nexus_structure(bifrost, registry=BIFROST_REGISTRY)
Instrument-specific translators are opt-in per conversion. BIFROST_REGISTRY gives
its analyzers and detectors their ICD pixel numbering; without it they fall back to
the generic classification, which places them correctly but does not classify them.
origin is where the tof.Source sits, which belongs to the model rather than the
instrument: an ESS source in tof carries a 0.05 m facility offset while the niess
moderator is at the instrument origin.
Ask the instrument about itself¶
The tree answers questions an emitted instrument cannot.
Where the beam branches. McCode describes an instrument as a list, so the only flow it can express is declaration order. BIFROST's tank branches:
Ten paths leave the sample — nine channels and the elastic monitor — and a neutron takes
one. That is what NeXus states through each group's inputs and outputs datasets.
What a thing will be called, and what it is measured from.
from niess.walk import visits
where = {v.id: v for v in visits(bifrost)}
analyzer = where['tank/channels[2]/pairs[0]/analyzer']
analyzer.emit_name('monochromator') is channel_3_1_monochromator, and its frame is
the arm's own analyzer_point. A translator asks rather than rebuilding the name from
an f-string, which is how the same name used to get written in two places and drift.
Converting an instrument niess did not build¶
You cannot, and that is deliberate. Every target reads the tree, and a .instr file has
none.
There used to be a second route per target, recovering what it could from an assembled
instrument — placement resolved back out of the emitted text, run-time values recovered
by constant-folding DECLARE blocks, a detector's arc and triplet read off a generated
WHEN clause with a regular expression. It worked, and it meant every target carried two
implementations of itself forever, one of which could only ever be a reconstruction.
Reading a .instr is still supported and still useful: niess.io.mccode.load_instr
parses one so its placements can be inspected, which is what
Translate a McStas .instr does to check a niess submodule
against the hand-written file it replaces. That is the migration story, and it survives.
Converting one is not.