beam_center_from_iofq passes its tolerance argument (default 0.1) straight to scipy.optimize.minimize as tol. For the default minimizer, Nelder-Mead, scipy maps tol onto xatol and fatol. xatol = 0.1 means 0.1 m in the search coordinates, which is larger than the region being searched, so the minimizer stops almost immediately and returns a barely-perturbed center-of-mass estimate.
Evidence
SANS2D tutorial data, against the MANTID_BEAM_CENTER reference used by the skipped tests in sans2d_reduction_test.py. Center of mass is [0.09007, -0.08264, 4], Mantid is [0.09288, -0.08195, 4].
tolerance |
cost evaluations |
result |
deviation from Mantid |
vertical shift from center of mass |
0.1 (default) |
14 |
[0.09458, -0.08264, 4] |
1.83 mm |
0.000 mm |
0.01 |
20 |
[0.09445, -0.08184, 4] |
1.57 mm |
0.803 mm |
0.001 |
39 |
[0.09425, -0.08234, 4] |
1.42 mm |
0.302 mm |
0.0001 |
59 |
[0.09423, -0.08230, 4] |
1.40 mm |
0.336 mm |
The last column is the clearest symptom: at the default tolerance the vertical coordinate comes back bit-identical to the center-of-mass estimate. That half of the refinement never runs.
Why it goes unnoticed
The center-of-mass initial guess is good, so the answer is roughly right either way, and the existing 2 mm test tolerance passes without the refinement doing anything.
There is a second, related fragility. Nelder-Mead sizes its initial simplex relative to x0, so the search step is whatever the magnitude of the initial guess happens to be rather than a deliberate choice. A beam center near the nominal axis therefore gets a much smaller search than one far from it, and an initial guess of exactly zero degenerates.
Not simply a different default
scipy maps tol per method, so no single value is correct across the minimizer argument:
- Nelder-Mead:
xatol and fatol, absolute, in metres and in cost units respectively.
- Powell:
xtol and ftol, relative.
The user guide's manual walk-through of the algorithm uses Powell with tol=0.01 and lands on a different value than the workflow does. The notebook attributes this to the different initial guess; the differing meaning of tol is plausibly also involved.
Suggested direction: set the scipy options explicitly for the chosen method, with a search scale derived from the problem (pixel size, or a fraction of the detector extent) instead of inherited from |x0|.
Notes
Present on main; found while reviewing #729 but independent of it. Not a duplicate of #401, which is about the cost of each evaluation rather than how many are performed. Resolving this looks like a prerequisite for un-skipping the beam-center tests in sans2d_reduction_test.py.
The 0.1 predates the current code: it was hard-coded when the module was first imported, and commit 74bbf86 only turned it into an overridable BeamCenterFinderTolerance parameter. That parameter type has since been removed, so the or 0.1 fallback is now the only value, with no way to set it from the workflow.
beam_center_from_iofqpasses itstoleranceargument (default0.1) straight toscipy.optimize.minimizeastol. For the default minimizer, Nelder-Mead, scipy mapstolontoxatolandfatol.xatol = 0.1means 0.1 m in the search coordinates, which is larger than the region being searched, so the minimizer stops almost immediately and returns a barely-perturbed center-of-mass estimate.Evidence
SANS2D tutorial data, against the
MANTID_BEAM_CENTERreference used by the skipped tests insans2d_reduction_test.py. Center of mass is[0.09007, -0.08264, 4], Mantid is[0.09288, -0.08195, 4].tolerance0.1(default)[0.09458, -0.08264, 4]0.01[0.09445, -0.08184, 4]0.001[0.09425, -0.08234, 4]0.0001[0.09423, -0.08230, 4]The last column is the clearest symptom: at the default tolerance the vertical coordinate comes back bit-identical to the center-of-mass estimate. That half of the refinement never runs.
Why it goes unnoticed
The center-of-mass initial guess is good, so the answer is roughly right either way, and the existing 2 mm test tolerance passes without the refinement doing anything.
There is a second, related fragility. Nelder-Mead sizes its initial simplex relative to
x0, so the search step is whatever the magnitude of the initial guess happens to be rather than a deliberate choice. A beam center near the nominal axis therefore gets a much smaller search than one far from it, and an initial guess of exactly zero degenerates.Not simply a different default
scipy maps
tolper method, so no single value is correct across theminimizerargument:xatolandfatol, absolute, in metres and in cost units respectively.xtolandftol, relative.The user guide's manual walk-through of the algorithm uses Powell with
tol=0.01and lands on a different value than the workflow does. The notebook attributes this to the different initial guess; the differing meaning oftolis plausibly also involved.Suggested direction: set the scipy options explicitly for the chosen method, with a search scale derived from the problem (pixel size, or a fraction of the detector extent) instead of inherited from
|x0|.Notes
Present on
main; found while reviewing #729 but independent of it. Not a duplicate of #401, which is about the cost of each evaluation rather than how many are performed. Resolving this looks like a prerequisite for un-skipping the beam-center tests insans2d_reduction_test.py.The
0.1predates the current code: it was hard-coded when the module was first imported, and commit 74bbf86 only turned it into an overridableBeamCenterFinderToleranceparameter. That parameter type has since been removed, so theor 0.1fallback is now the only value, with no way to set it from the workflow.