Skip to content

BIFROST: unpin detector geometry once a corrected NeXus file is available #962

Description

@SimonHeybrock

Summary

BIFROST's detector geometry is temporarily pinned to the old geometry artifact (geometry-bifrost-2025-01-01.nxs) while the chopper workflow consumes a newer one (geometry-bifrost-2026-06-08.nxs). Both are in the geometry registry; each consumer selects the file in which its data is valid. This issue tracks collapsing back to a single artifact once a corrected source file is available.

Why the split exists

The new source file coda_bifrost_999999_00006061.hdf has correct, up-to-date chopper geometry (new group names bandwidth_chopper_*, frame_overlap_chopper_*, pulse_shaping_chopper_*; pulse-shaping choppers moved ~7 cm), but a broken detector transformation chain:

  • The detector_tank_angle group lost its numeric prefix (was 117_detector_tank_angle), but detector_tank_angle_r0/value still has depends_on → /entry/instrument/117_detector_tank_angle/transformations/detector_tank_angle_t0 (stale prefix).
  • The static detector_tank_angle_t0 zero-offset node is missing entirely from the file.

Loading the detector geometry therefore raises a dangling-depends_on warning and fails the reduction. The chopper workflow is unaffected — it loads only NXdisk_chopper + NXsource, never the detector chain. (Reported upstream to the file-writer side.)

The missing _t0 value cannot be safely reconstructed: in the old file it was 0.1192°, identical to the choppers' *_t0_r offset; in the new file that offset was re-surveyed to 0.0974°. Grafting the old value would inject a known-stale number; inferring 0.0974° is unverified. So we pin rather than fabricate.

Cleanup when a corrected file lands

A single NeXus file valid for both detectors and choppers (complete detector_tank_angle chain incl. _t0, new chopper names):

  • Regenerate the geometry artifact (ess-livedata-make-geometry-nexus) and streams_parsed.py (nexus_helpers --generate) from it; add one new registry entry in detector_data_handler.py.
  • In bifrost/factories.py:
    • Drop the date= pin on the reduction's get_nexus_geometry_filename('bifrost') call so both consumers use the latest entry.
    • Switch _detector_names from the prefixed form (123_channel_1_1_triplet) to the unprefixed form (channel_1_1_triplet) — the new files drop the prefix (detector_number content is identical).
    • Remove the two-file rationale comments.

Related changes already on the chopper branch

  • Chopper-cascade / wavelength-LUT workflow wired into the BIFROST factory; integration test tests/config/bifrost_wavelength_lut_test.py.
  • log_producer_bifrost.json extended with per-chopper speed-setpoint + delay sliders for dev-mode testing.
  • make_geometry_nexus.py fixed to handle event-only monitors lacking depends_on.

Activity

  1. SimonHeybrock commented on Aug 14, 2026

    @SimonHeybrock
    MemberAuthor

    Tracking update from #1236, which registers geometry-bifrost-repaired-2026-08-11.nxs (regenerated from coda_bifrost_999999_00016610.hdf, plus a hand-patch described below). Both defects this issue's body names are gone in that file, and the numerical check the cleanup was waiting on is now done. Summary first, evidence after.

    Both defects are gone. All 45 detector chains resolve and there are no stale 117_ groups. For contrast, measured on the same code path: the pinned 2025-01-01 file resolves 45/45, 2026-06-08 resolves 0/45 — not just the detectors, every BIFROST chain in it is dangling, which is worth knowing independently of this issue.

    The positions are right, except for one offset. With the tank angle set to 90° (the static value the pinned file bakes in), triplet positions from the new artifact agree with the pinned artifact to 2 nm across all 45 triplets — after applying one extra rotation of +0.119158° about y. Without it they differ by 3.0 mm mean / 4.5 mm max, growing with radius. That angle is exactly the channel_*_t0_r / detector_tank_angle_t0 offset the pinned chain carries and the new chain does not have at all.

    So the survey is faithfully reproduced and the only open numerical question is that single offset: is the ~0.119° zero offset now folded into the live detector_tank_angle readback, or is the writer dropping it? If the latter, unpinning moves every detector by up to 4.5 mm. This is the one thing to settle before dropping the pin; note the body's remark that the choppers' equivalent offset was re-surveyed from 0.1192° to 0.0974°, so "the offset changed" is a live possibility rather than a writer bug.

    Unpinning is not just dropping the date=. The chain shape changed. Pinned, per triplet:

    channel_1_1_triplet_r0    rotation    104.582 deg
    channel_1_1_triplet_t0_r  rotation      0.119 deg
    channel_1_1_triplet_t0_t  translation   1.311 m
    

    New, per triplet — routed through the real instrument hierarchy and terminating at the live tank angle:

    channel_1_1_triplet_t0_z            translation   1.189 m
    channel_1_1_detector_angle_r0       rotation    -55.117 deg
    channel_1_1_monochromator_r0        rotation    -55.117 deg
    channel_1_1_analyzer_point_r0       rotation     90 deg
    channel_1_1_analyzer_point_t0_z     translation   1.100 m
    channel_1_arm_r0                    rotation    -40 deg
    detector_tank_angle_r0/value        rotation    <NXlog, sizes={'time': 0}>
    

    The pinned file's detector positions are static and carry no tank angle. The new file's are tank-angle-dependent, and the terminating node is a length-0 NXlog placeholder. Unpinning therefore also needs a chain-patch ContextBinding for detector_tank_angle_r0 on unified_detector — the mechanism #1236 introduces for elastic_monitor (ADR 0003, config/value_log.py). Without it, essreduce's reject_time_dependent_transform trips at compute time.

    That binding cannot simply replace the existing direct bind: the tank angle is also the InstrumentAngle[SampleRun] coordinate group_by_rotation bins on, and a stream carries one context key per spec. #1236 solves this for the monitor by making the chain patch the binding and deriving the coordinate from the same log with a small provider; the detector side will need the same shape.

    A guard that will not catch this. tests/config/motion_binding_test.py::test_no_orphan_empty_nxlogs exists to flag exactly this failure mode — an empty NXlog on a registered source with no binding covering it. It cannot see BIFROST's detectors, because the registered source name unified_detector is a logical name covering 45 triplet groups, not a NeXus group, so the chain walk returns nothing. Whoever unpins should not read a green suite as confirmation.

    Why the file is called repaired, and what to check later. The BIFROST writer attaches the event-mode monitors' geometry to <name>_backup / <name>_da00 NXnote siblings, while the NXmonitor's own depends_on points at a transformations group it does not have. make_geometry_nexus.py copies the source faithfully, so the generated artifact has the right transformations under the wrong parent and no monitor chain resolves. The registered file copies them back onto the monitors they belong to — six added datasets, nothing else changed. Reported upstream; the tracking item here is to check whether the writer has been fixed, then regenerate cleanly and supersede the hand-patched artifact. The repair deliberately did not go into make_geometry_nexus.py: a "geometry may live on a similarly-named sibling" rule would encode the producer bug and mis-assign silently if the naming shifts again.

    Registering it repointed the unpinned consumers. Instrument.load_factories passes an unpinned get_nexus_geometry_filename(name) to the wavelength-LUT factory, and Instrument.nexus_file is unpinned too, so both moved from 2026-06-08 to the new file. Verified safe: every transformation dataset is identical between the two, and the only value differences are the choppers' rotation_speed* statistics, which are 0 in the new file because the source run had the choppers parked. Harmless here — the LUT workflow overrides rotation_speed_setpoint and delay from the live setpoint streams before to_disk_choppers.

    Related, worth folding into the same cleanup. BIFROST now reads geometry from three places: the pinned artifact (detector ratemeter / RawDetector), the McStas simulation file (Q-cut and detector elastic Q-map workflows, via simulated_elastic_incoherent_with_phonon()), and the unpinned artifact (elastic monitor Q-map, wavelength LUT). Pointing the Q-cut workflows at an artifact currently fails with No NXcrystal found in the inputs of 135_channel_1_4_triplet — the artifacts carry no analyzer geometry.

    Minor: the cleanup checklist in the body refers to detector_data_handler.py; the registry now lives in preprocessors/detector_data.py.

  2. SimonHeybrock commented on Sep 7, 2026

    @SimonHeybrock
    MemberAuthor

    Checked against coda_bifrost_999999_00020173.hdf (run of 2026-09-07), i.e. four weeks after the file behind the repaired artifact.

    The writer has not been fixed. The tracking item above — "check whether the writer has been fixed, then regenerate cleanly and supersede the hand-patched artifact" — is still open. elastic_monitor and normalization_monitor again contain only depends_on plus their event group, with the transformations on the sibling _backup / _da00 NXnotes. A generated artifact resolves the other three monitors and reports these two as MISSING (no position coord), so regenerating from this file would need exactly the same six-dataset repair. geometry-bifrost-repaired-2026-08-11.nxs stays the one to use; every transformation value in it is identical to what this file produces, so there is nothing else to gain by regenerating.

    New evidence on the open numerical question. The file is internally consistent about the offset in a way that is worth knowing before asking controls. Every component upstream of the tank — all six choppers, the psc/overlap/bandwidth monitors, the moderator — carries a *_t0_r rotation of ~0.0974°. Every component on the tank — the 45 triplets via the arms, and elastic_monitor — carries no _t0_r at all and hangs directly off detector_tank_angle_r0/value.

    So the file is not dropping a field it elsewhere writes: its model is "beamline azimuth zero is 0.0974°, tank frame zero is whatever the motor reads". That does not settle whether the readback actually folds in the ~0.119°, which remains the controls question, but it does mean the absence is a deliberate convention rather than a writer bug — and it makes "the offset moved into the readback" the more likely of the two branches.

    For completeness, the measurement reproduces on this file: patching the tank angle to a static 90° and comparing all 45 triplets against the pinned artifact gives 3.016 mm mean / 4.460 mm max, dropping to 9.4 nm once the extra +0.119158° about y is applied. The tank is parked at exactly 0.0° in this run, as it was in the previous one, so the file cannot answer the question by itself.

    Unchanged and confirmed: 45 unprefixed triplet groups, no stale 117_ groups, all detector chains resolve, chopper geometry identical to both earlier files.

  3. SimonHeybrock commented on Oct 6, 2026

    @SimonHeybrock
    MemberAuthor

    Checked against coda_bifrost_999999_00024005.hdf (2026-10-06).

    The writer is still not fixed. elastic_monitor and normalization_monitor again hold only depends_on and their event group. The transformations are on elastic_monitor_backup / normalization_monitor_da00. A generated artifact reports both monitors as MISSING, so the six-dataset repair is still needed.

    The transformations are otherwise unchanged. All 776 transformation datasets shared with geometry-bifrost-repaired-2026-08-11.nxs are identical (value, depends_on, vector). The tank is parked at 0.0° again and carries no _t0_r, so the file still cannot answer the 0.119° offset question.

    One structural change: source labelling. The origin-pinned neutron_prod_info (NXsource) is gone. source, previously an NXmoderator at z = −161.999 m, is now the NXsource. This removes the BIFROST case of #989. An artifact generated from this file, with the same monitor repair, should replace the repaired 2026-08-11 one. The f144 streams formerly under neutron_prod_info/* (same PVs) are now under source/*. Nothing outside streams_parsed.py refers to the old paths.

  4. SimonHeybrock commented on Oct 8, 2026

    @SimonHeybrock
    MemberAuthor

    coda_bifrost_999999_00024005.hdf (2026-10-06) has a new writer defect, in the source. The origin-pinned neutron_prod_info is gone and the moderator group source is now the NXsource (z = −162.0 m, transformations unchanged), but it is labelled name='accelerator', probe='proton'. The lookup-table workflow refuses a group whose probe is not neutron as the beamline source, so an artifact generated from this file fails there.

    #1363 registers geometry-bifrost-2026-10-06.nxs from this file with two hand repairs: probe='neutron' on the source, and the same six-dataset monitor repair as geometry-bifrost-repaired-2026-08-11.nxs (#1236). Both defects are for the file-writer side; once they are fixed, regenerate cleanly and supersede the hand-patched artifact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:workflowsInstrument configs, geometry, reduction and science logic

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions