All magnetic-field-related polarisation components in McStas share a common run-time library, pol-lib (mccode/nlib/share/pol-lib.c/.h), which provides the spin-precession integrator and a small set of built-in field functions. A separate library, ref-lib, provides the standard supermirror reflectivity function (StdReflecFunc, section A.2.2) used by the polarising mirrors and guides.
Rather than each field-producing component computing precession locally and independently, pol-lib maintains a small per-particle stack of active magnetic fields, _particle->mcMagnet. A component that defines a field region (e.g. Pol_Bfield) pushes a description of its field onto this stack when a particle enters the region, using
mcmagnet_push(_particle, field_type, &rotation, &position, stopbit, params[8])
where field_type selects one of the built-in field functions (section A.3.3), rotation/position record the field region’s placement in the lab frame (so the field can later be evaluated in the region’s own local coordinates regardless of where in the instrument it sits), and params holds up to 8 doubles of field-specific parameters (amplitude, extent, …). A companion Pol_Bfield_stop component calls mcmagnet_pop() to remove the field from the stack once the particle leaves the region.
Because this is a stack rather than a single active field, field regions may be nested or overlap: any component placed between a Pol_Bfield/Pol_Bfield_stop pair – including another, independent field region – experiences the vector sum of every field currently on the stack (evaluated by mcmagnet_get_field(), which walks the whole stack, transforms each field into the lab frame, and adds the contributions). A field region may also be declared closed (concentric=0), in which case it pushes and pops its own field internally and no other components may be placed inside it – this is the simpler, non-nestable mode used when a single self-contained field region is all that is needed.
Whenever a particle is propagated through a region with an active field (technically: whenever PROP_DT is called while _particle->mcMagnet is non-NULL), the propagation macro transparently invokes the precession integrator described next, so that ordinary component code (slits, monitors, samples placed inside a field region) does not need to know anything about polarisation – the spin precesses correctly regardless of what else is happening to the particle during that propagation step.
The general-purpose precession routine, SimpleNumMagnetPrecession(), integrates Eq. A.11 numerically along the particle’s straight-line trajectory (gravity is not accounted for inside the field itself) for a total propagation time \(dt\), using an adaptive-step scheme:
The field \(\mathbf {B}_\mathrm {start}\) is evaluated at the particle’s current position (by querying the whole field stack, or – for the self-contained field components – a single field function).
A trial sub-step of length mc_pol_initial_timestep (default \(10^{-5}\) s, capped at the remaining \(dt\)) is proposed, and the field \(\mathbf {B}_\mathrm {end}\) is evaluated at the particle’s position at the end of that trial step.
If the angle between \(\mathbf {B}_\mathrm {start}\) and \(\mathbf {B}_\mathrm {end}\) exceeds a threshold, mc_pol_angular_accuracy (default \(1^\circ \)), the trial step is halved and re-evaluated; this repeats until the field direction changes by less than the threshold over the sub-step (or the step size underflows to FLT_EPSILON). This is what makes the scheme adaptive: in a slowly-varying or uniform field, the integrator quickly settles on large sub-steps (up to the \(10^{-5}\) s cap), while in a rapidly rotating field (e.g. the rotating or majorana functions of section A.3.3) it automatically refines the step until the field is well approximated as locally uniform.
Once an acceptable sub-step is found, the particle position and time are advanced by that sub-step, and the spin is precessed about the mean field over the sub-step, \(\bar {\mathbf {B}} = \tfrac 12 (\mathbf {B}_\mathrm {start}+\mathbf {B}_\mathrm {end})\), by the angle \[ \phi = \left (|\bar {\mathbf {B}}|\, \delta t\, \omega _L\right ) \bmod 2\pi , \qquad \omega _L = -2\pi \times 29.16~\mathrm {MHz/T}, \] matching Eq. ?? applied over the sub-step, using a generic axis-angle rotation of \((s_x,s_y,s_z)\) about \(\bar {\mathbf {B}}\) (rather than the special-case \(z\)-axis form of Eq. ??, since \(\bar {\mathbf {B}}\) may point in any direction).
This is repeated, consuming the remaining \(dt\) sub-step by sub-step, until the whole requested propagation time has been precessed over.
Positions/velocities/spin are tracked internally in the lab frame (fields are pushed to the stack together with the rotation/position needed to transform into and out of each field region’s local frame) and the final spin is transformed back into the calling component’s local frame before being returned. Pol_tabled_field (section A.4) re-implements this identical algorithm internally (rather than going through the shared field stack), so that it can query its own tabulated/ interpolated field directly.
Two component-specific accuracy parameters, exposed via mc_pol_set_timestep() and mc_pol_set_angular_accuracy(), allow the initial sub-step size and the angular-accuracy threshold to be tuned instrument-wide if the defaults are not adequate for a particular field geometry.
The field pushed onto the stack by Pol_Bfield is one of the following built-in functions, selected by the integer field_type parameter (Pol_FieldBox and Pol_constBfield always use the constant-field case, evaluated in closed form rather than via the stack):
1 – constant
\(\mathbf {B}=(B_x,B_y,B_z)\), uniform throughout the region.
2 – rotating
\(\mathbf {B}\) starts as \((0,B_y,0)\) at the entrance and rotates smoothly to \((B_y,0,0)\) at the far end (over the region’s zdepth), following \(B_x = B\sin (\tfrac {\pi }{2}z/L)\), \(B_y = B\cos (\tfrac {\pi }{2}z/L)\).
3 – majorana
a large component along \(x\) decreases linearly from \(+B_x\) to \(-B_x\) across the region (passing through zero at the centre – the classic “Majorana flip” geometry for studying spin-flip loss at a field zero-crossing), plus a small, constant transverse component along \(y\).
6 – gradient
linear interpolation of the full field vector between two given endpoint vectors \(\mathbf {B}_1\) (at \(z=-L/2\)) and \(\mathbf {B}_2\) (at \(z=+L/2\)) – a generalisation of the majorana case to an arbitrary field-vector gradient.
-1 – tabled
used internally by Pol_tabled_field: the field is not one of the analytic functions above but is instead interpolated (via interpolation-lib, choosing between a kdtree or regular-grid interpolator) from an externally supplied point cloud file of \((x,y,z,B_x,B_y,B_z)\) rows – this is the route to use for a realistic, measured or finite-element-computed field map.
4 (MSF) and 5 (RF)
reserved identifiers for a modulated/RF spin flipper field; not implemented in the current release (a radio-frequency field is more commonly modelled explicitly via a rotating-frame field function or, for an idealised flipper, by simply mirroring the spin vector, see Pol_SF_ideal below).
New field functions can be added by writing a function with the signature of e.g. const_magnetic_field() in pol-lib.c and adding a case to magnetic_field_dispatcher(); per the file header, this requires modifying the shared library rather than being an end-user-facing extension point.