Skip to content

[BUG]: McXtrace: code written for 2.x in-place RESTORE_XRAY; EXTEND 'if (!SCATTERED) ABSORB' is a no-op for restored rays #2940

Open

Description

@willend

Background

In McCode 2.x, RESTORE_XRAY(index, x, y, z, kx, ky, kz, phi, t, Ex, Ey, Ez, p) restored the photon state in place, immediately. Since 3.0 it only sets a flag (cogen.c.in: #define RESTORE_XRAY(_index, ...) _particle->_restore = _index;). The generated code then restores the component's entry state after TRACE and EXTEND, and particle_restore() also clears _absorbed:

class_X_trace(&_X_var, _particle);
if (_particle->_restore)
  particle_restore(_particle, &_particle_save);   // ... p->_absorbed=0; p->_restore=0;

Code written for the old semantics therefore behaves differently in 3.x:

  1. An ABSORB after a RESTORE_XRAY, in TRACE or in the instrument's EXTEND, is cancelled. The ray continues downstream.
  2. Code that keeps going after RESTORE_XRAY works on the un-restored state. Its effect is discarded at the end of the component, but SCATTERED stays set.
  3. The index argument is ignored.

The widespread idiom EXTEND %{ if (!SCATTERED) ABSORB; %} after a mirror or crystal therefore does nothing for rays the component restores. Those are exactly the rays it targets.

Survey of origin/main (e2d7eb1), McXtrace side. The McStas side is a separate issue.

Reproducer

Restore_absorb_x.instr
DEFINE INSTRUMENT Restore_absorb_x(int mode=1)
TRACE
COMPONENT src = Source_flat(radius=0.01, dist=1, focus_xw=0.02, focus_yh=0.02, E0=10, dE=1)
AT (0,0,0) ABSOLUTE
COMPONENT mir = Mirror(xwidth=0.01, zdepth=0.01, R0=1)
WHEN (mode==1) AT (0.5,0,0.5) RELATIVE src
EXTEND %{
  if (!SCATTERED) ABSORB;
%}
COMPONENT mnd = Monitor_nD(xwidth=0.001, yheight=0.001, options="x y")
WHEN (mode==2) AT (0.5,0,0.6) RELATIVE src
EXTEND %{
  ABSORB;
%}
COMPONENT mnda = Monitor_nD(xwidth=0.001, yheight=0.001, options="absorb x y")
WHEN (mode==3) AT (0.5,0,0.65) RELATIVE src
COMPONENT emon = E_monitor(xwidth=0.1, yheight=0.1, Emin=5, Emax=15, restore_xray=1)
WHEN (mode==4) AT (0,0,0.7) RELATIVE src
EXTEND %{
  ABSORB;
%}
COMPONENT arm = Arm()
AT (0,0,0.8) RELATIVE src
EXTEND %{
  if (INSTRUMENT_GETPAR(mode)==5) ABSORB;
%}
COMPONENT det = PSD_monitor(xwidth=0.1, yheight=0.1, filename="det")
AT (0,0,1) RELATIVE src
END

Each case is set up so that det should get 0. Results with 1e5 rays (mcxtrace 3.99.99, macOS arm64):

mode case events at det
1 Mirror missed, EXTEND if (!SCATTERED) ABSORB 71009 (all the restored ones)
2 Monitor_nD missed, EXTEND ABSORB 100000
3 Monitor_nD missed, options="absorb" 100000
4 E_monitor(restore_xray=1), EXTEND ABSORB 100000
5 control: Arm, EXTEND ABSORB 0

Affected: ABSORB after RESTORE in a component's TRACE

  • monitors/Monitor_nD.comp:580: RESTORE_XRAY(...); ABSORB; when absorb is set and the ray misses. The absorb option has no effect on misses (mode 3).

Affected: code that relies on the restore happening in place

  • optics/Mirror_curved.comp:99: when both roots are negative, it calls RESTORE_XRAY and then falls through to the bounds check and SCATTER. This is fixed by Fix curved mirror #2936 together with its follow-up. The follow-up also replaces the miss-RESTORE with a manual undo, so that an EXTEND ABSORB wins. Without that, 61% of the rays on the XFEL_SPB KB mirrors went straight through.

Affected: instruments with EXTEND ABSORB after a component that RESTOREs on a miss

  • Mirror_curved (27 instances): JJ_SAXS, SOLEIL_CRISTAL, DIFFABS, DISCO, GALAXIES, NANOSCOPIUM, PUMA, ROCK, SAMBA, SIRIUS, SIXS, Test_KB, XFEL_SPB. The in-component fix for Fix curved mirror #2936 covers these.
  • Mirror_elliptic (6): BARC Czerny_Turner mirror_m1 and mirror_m2, SOLEIL_DISCO DISCO_M51, DISCO_mirror_m1 and DISCO_mirror_m2, Test_mirror_elliptic
  • Mirror (3): SOLEIL_DISCO DISCO_M4, SOLEIL_ROCK ROCK_mirror_m2a, SOLEIL_SIRIUS SIRIUS_def_mirror
  • Bragg_crystal_bent_BC (1): Template_Johann_spec bc
  • E_monitor(restore_xray=1) + if (INSTRUMENT_GETPAR(stop_at_energy)) ABSORB: NIST/PBD_BT incoming_spectrum_mon. stop_at_energy can never stop a ray (mode 4).

Other components that RESTORE on a miss would hit the same problem as soon as an instrument uses the idiom: Mirror_parabolic, Multilayer_elliptic, Grating_reflect, Grating_trans, Bragg_crystal_bent, Beamstop, ZonePlate, contrib Bragg_crystal_BC and Laue_crystal_BC. (Bragg_crystal is fine: its RESTORE is commented out.)

I have not quantified the impact per instrument, apart from the Mirror_curved ones in #2936.

Possible directions (for discussion)

  • Per component, without touching the code generator: on a miss, undo the propagation by hand instead of calling RESTORE_XRAY, as done for Mirror_curved in the Fix curved mirror #2936 follow-up. Monitor_nD: drop the RESTORE before ABSORB.
  • Centrally: skip particle_restore() when ABSORBED is set. That is one line in the code generator (shared with McStas) and changes numbers.

%Example targets of the listed instruments need re-checking after either fix.

Related: #1227.

Survey and reproducers made with Claude Code.

Activity

  1. willend commented on Oct 30, 2026

    @willend
    ContributorAuthor

    McStas counterpart: #2939

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions