Repository navigation
Polish and flag sfm() fits that stop short of a maximum (A62) - #60
Conversation
optim() can return convergence 0 with an invertible Hessian at a point that is not a maximum. On 2 of 180 NTN fits it did, 1.53 and 3.60 log-likelihood units below the sigma_v-collapse supremum, and sfm() printed standard errors for both. Before reporting SEs, sfm() now computes the Newton decrement from a Richardson gradient. Above 1e-4 (sound fits were all below 7e-9), it polishes with nlminb on the log scale for positive parameters and keeps the result only if the log-likelihood rises. If the point is still not a maximum and no boundary flag explains it, it warns. The result is recorded in $stationarity. The NTN sigma_v boundary now sets SEs to NA explicitly, because a polished Hessian can invert numerically there. Re-run on the 180 fits: only the two target fits changed, none got worse, and the check costs ~1% of fit time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A typo in the A62 edit to the wrong-skew/boundary \value entry: the
markup opened as `de{sfm}`, which renders as "desfm" rather than as the
function name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The A62 check differenced the objective two-sided at the point optim() returned. numDeriv's stencil takes an ABSOLUTE 1e-4 step once |x| falls below 1.8e-5, so a parameter nearer its bound than that is differenced across the likelihood's own domain penalty and the gradient becomes the penalty's. On sfm(model_name = "NHN_Z") fitted to the package's own cs_small(N = 200) fixture, where sigma_v collapses to 1.47e-06, that gave grad_max 1.8e+18 and a Newton decrement of 2.4e+21 -- and the fit then warned that a Newton step predicted a gain of 2.4e+21 log-likelihood units, which is not a quantity. Such a coordinate is now differenced FORWARD, left out of the decrement, and judged by the sign of its one-sided slope: at a lower bound a minimum of the negative log-likelihood needs that slope non-negative. $stationarity records at_bound, n_at_bound and bound_ok. On that NHN_Z fit the decrement over the free coordinates is -886 and the bound is where a minimum belongs, so the message now says what is actually wrong: the Hessian over those coordinates is not positive definite, i.e. the point is a saddle. A negative decrement is no longer reported as a predicted gain. bound_ok is recorded but deliberately does NOT trigger a polish: widening the trigger would change what the 180-fit study measured. Measured, same machine and same data, pushed branch vs this commit over all 180 stored NTN fits: all 180 log-likelihoods identical to 0, every decrement identical, the same 11 fits flagged at the sigma_v boundary with NA standard errors. No coordinate in those fits sits at a lower bound, so the new branch is inert there and changes only fits that have one. Worth recording from the same run: on macOS none of the 180 fits polishes or warns. N=150 seed 31 and N=300 seed 56, the two fits that motivated A62 on Windows, here reach the sigma_v boundary flag with NA SEs directly, so where the optimizer stops on these ridges is platform-dependent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Second commit: the check must not be differenced across a boundRunning the full suite against this branch locally surfaced a defect in the check itself. It fired on four
which is not a quantity. The mechanism, measured on the package's own
A coordinate nearer its bound than the stencil reaches is now differenced forward, left out of the decrement, and judged by the sign of its one-sided slope: at a lower bound a minimum of the negative log-likelihood needs that slope non-negative.
Regression evidence, same machine and same data
So the new branch is inert on the fits the study measured and changes only fits that actually have a parameter against a bound. One thing the re-run says that the study could notOn macOS none of the 180 fits polishes or warns. N = 150 seed 31 and N = 300 seed 56 — the two fits that motivated A62 on Windows — here reach the Tests: 🤖 Generated with Claude Code |
The defect
optim()can stop withconvergence = 0and an invertible Hessian at a point that is not a maximum.sfm()then prints standard errors as if the fit were sound.Found by the RTMB scoping study on
sfm(model_name = "NTN")(180 fits,y_pcs_tn). The AD gradient and Hessian are exact, so they show where finite differences stopped short:convIn both, the supremum is the
sigma_vcollapse (lambda -> Inf). So these are A59 fits that A59'sntn_sigma_v_at_boundflag never saw.The fix
.stationarity_polish()(R/matrix_utils.R), called insfm()whenever standard errors are about to be reported:0.5 g'H^-1 g, the log-likelihood gain one Newton step predicts. The gradient is Richardson (numDeriv::grad, already imported), which matched the AD gradient to 1e-8;His the fit's own Hessian.|decrement| > 1e-4, it polishes withnlminb(). The positive parameters are on the log scale, so a ridge running to a scale's infinity can be walked. It uses the gradient only, restarts up to 3 times, and keeps the result only if the log-likelihood rises.$stationarityrecords the decrement, the largest gradient element, whether the fit was polished, the gain, and the decrement afterwards.Also: the NTN
sigma_vboundary now sets its standard errors toNAexplicitly. It used to rely on the Hessian being singular there, and a polished point'soptimHess()can invert numerically, which printed finite SEs beside a warning saying they areNA.Why these choices (measured on seeds 31 and 56)
The 1e-4 threshold: on the 180 fits, the 167 sound interior fits all have decrements ≤ 7e-9.
Measured: all 180 fits re-run on this branch
main.sigma_vcollapse, withNASEs and the existing warning.Not covered
sfm()model in the shared likelihood block, including the simulated-ML ones. On a tHN fit (quadrature) it cost 0.5 s of a 411 s fit.Tests
tests/testthat/test-a62-stationarity.R(21 assertions) drives the helper on synthetic objectives: a quadratic at its minimum and off it, a ridge to a positive parameter's infinity, a singular Hessian, and a wrong reported Hessian that must not move a true minimum. There is also one loose integration check on an interior NHN fit. These are deliberately not degenerate fits: those sit on flat ridges, which do not reproduce across platforms (#54, #57, #59).During local testing,
test-influence.Rcaught a crash ontHN(resultswas used before tHN builds it). That is fixed in this branch before the push.Also in this PR
One typo fix in
man/sfm.Rd: the A62 edit opened the markup asde{sfm},which renders as "desfm" rather than as the function name.
The PR itself was opened from the Mac —
ghis not installed on the machinethe work was done on, and CI triggers only on pull requests, so the
five-platform gate had not run on this branch until now.
Verified again here before opening, on pinned builds outside Dropbox:
test-a62-stationarity.RgivesFAIL 0 | PASS 21against this branch's build,and against
origin/main's build the same file fails 8 times — the helper doesnot exist, and
$stationaritycomes backNULLon an interior NHN fit.🤖 Generated with Claude Code