Repository navigation
[BUG]: McXtrace: code written for 2.x in-place RESTORE_XRAY; EXTEND 'if (!SCATTERED) ABSORB' is a no-op for restored rays #2940
Description
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:
- An
ABSORBafter aRESTORE_XRAY, in TRACE or in the instrument'sEXTEND, is cancelled. The ray continues downstream. - Code that keeps going after
RESTORE_XRAYworks on the un-restored state. Its effect is discarded at the end of the component, butSCATTEREDstays set. - The
indexargument 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;whenabsorbis set and the ray misses. Theabsorboption 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 callsRESTORE_XRAYand then falls through to the bounds check andSCATTER. 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 EXTENDABSORBwins. 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_Turnermirror_m1andmirror_m2, SOLEIL_DISCODISCO_M51,DISCO_mirror_m1andDISCO_mirror_m2, Test_mirror_ellipticMirror(3): SOLEIL_DISCODISCO_M4, SOLEIL_ROCKROCK_mirror_m2a, SOLEIL_SIRIUSSIRIUS_def_mirrorBragg_crystal_bent_BC(1): Template_Johann_specbcE_monitor(restore_xray=1)+if (INSTRUMENT_GETPAR(stop_at_energy)) ABSORB: NIST/PBD_BTincoming_spectrum_mon.stop_at_energycan 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 forMirror_curvedin the Fix curved mirror #2936 follow-up. Monitor_nD: drop the RESTORE beforeABSORB. - Centrally: skip
particle_restore()whenABSORBEDis 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
willend commentedon Oct 30, 2026
McStas counterpart: #2939