Repository navigation
Name the standard errors the other six entry points could not compute (A65) - #66
Conversation
…e (A65)
zsfm(), lcsfm(), copsfm(), ivsfm() and selsfm() each took their standard
errors as suppressWarnings(sqrt(diag(solve(opt$hessian)))). On an
indefinite Hessian the negative-variance coordinates come back NaN, and
suppressWarnings() discarded R's own "NaNs produced" -- the only signal
there was. Nothing set a flag, and the convergence code read 0.
Reproduced on zsfm("ZISF"), data_gen_cs(N = 25, rand = 8, sig_u = 1,
sig_v = .3, cons = .5, beta1 = .5, beta2 = .5, mu = .5, a = 5), 0.5s to
fit: eigenvalues 3.48e6 ... -1474, two of six standard errors NaN -- one
of them the slope x2, whose true value is 0.5 -- conv 0, no warnings, no
flags, and print()/summary() said nothing beyond printing NaN.
All five now call .se_missing_report(), the model-agnostic helper #63
added for ttsfm(), on the REPORTED standard errors and names, so
copsfm()'s and ivsfm()'s delta-method scales carry through correctly.
$se_unresolved is appended to the results list rather than re-listed
against its name vector. solve() is wrapped too: the
sum(colMeans(hessian)) == 0 test caught only an all-zero Hessian, so a
singular but non-zero one errored and discarded a finished fit.
The test file fails on main (3 failures: no warning, no flag) and passes
17 of 17 here, and includes a false-positive guard -- the same design at
N = 300 must stay silent with no flag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Scope note, found after opening this PR: A65 was scoped with I have not folded it in here: this branch was cut before #62 landed, so its Also worth noting: |
The test hard-coded the landing point of a single fit on macOS arm64: the names of the two parameters that lose a standard error, and the count 2 of 6. Which point the optimizer stops at, and the sign of a small eigenvalue there, is BLAS-dependent -- exactly what made #62 fail on all five CI platforms a few days ago, and exactly what notes from that round said not to do again. Every expectation is now derived from the fit in front of it: the reported parameters must be the ones whose standard error is non-finite, n_missing must be that count, n_par the length of the vector, and "indefinite" must hold iff a negative eigenvalue was read. The regime is a skip condition, not an assertion, in both directions -- the first test skips where every standard error is finite, since there is then nothing for the report to say, and the false-positive guard skips where the N = 300 fit is not healthy, since its claim is about a healthy fit. expect_silent() is gone too: it demanded total silence from a fit, which is a stronger claim than the test needs. 12 of 12 pass against the branch build and 2 of 4 fail against main, so it still discriminates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The entry reported "conv 0, and zero warnings" as the headline. The zero warnings is the defect and holds; conv 0 does not travel. On all five CI platforms the same fit reports convergence 52, so there the convergence code carries a signal and only the missing standard errors are unexplained. On this Mac it reports 0, which is the fully silent case. The CI run also establishes something the local run could not: the warning fired on macOS, ubuntu devel, ubuntu release and windows -- the only failing expectation there was the convergence code -- so the indefinite-Hessian regime is reached on four of the five platforms. ubuntu oldrel-1 does not reach it, which is why the rewritten test skips rather than asserts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
CI caught this, and it was a platform-dependent landing point again — the fix is pushed as What actually failed. Four of five platforms failed on one expectation, So the run established something the local run could not. On macOS, ubuntu devel, ubuntu release and windows the warning fired — the only failing expectation was the convergence code — so the regime is reached, and the fix works, on four of the five platforms. Only oldrel-1 misses it. The fix. Every expectation is now derived from the fit in front of it: the reported parameters must be exactly those whose standard error is non-finite, One claim corrected in NEWS ( This is the same mistake #62 made a few days ago, and I had written the rule down after it. Noted again, less abstractly: it is not enough to avoid pinning the parameter values — the convergence code is part of the landing point too. The scope note above still stands: |
psfm() carries the same two defects at seven Hessian-diagonal sites,
covering GTRE and TRE (which share one), GTRE_Z, TRE_Z, GTRE_FML,
TFE_WMLE, TFE and FD. Four of the seven left the inverse unguarded, so a
singular but non-zero Hessian discarded a finished PANEL fit -- the most
expensive kind this package produces. Their guard tests
any(hessian == 0), which catches a zero ENTRY rather than singularity.
The count recorded two days ago was six, and it was wrong. FD spells the
same quantity sqrt(diag(qr.solve(opt$hessian))) -- qr.solve, and
unclamped -- which makes it invisible to A64's sweep, to A65's first
pass, and to the .sfa_se_from_var(diag(solve(...))) pattern that found
the other six. It surfaced only because a replace-all asserted 4
occurrences and got 5; the sweep was then redone on $hessian itself,
spelling-agnostically, which is what should have happened first.
Deliberately excluded after that sweep: psfm()'s two other Hessian
inversions (the sandwich H_inv, the delta-method vc), which already
guard solve() and take their standard errors through a Jacobian, so the
report's eigenvalue narrative does not map onto them as directly.
$se_unresolved is appended at eight assemblies -- GTRE and TRE share a
site but keep separate results lists -- and the insertion was scoped to
each enclosing if (model_name ...) block so it could not leak into
GTRE_SEQ1/SSFE/PL80/GTRE_SEQ2, where .sm does not exist.
Two instances came out of the existing suite, which is why the local
warning count moved:
* psfm("FD") at test-psfm.R:23 is GENUINE -- 3 of 5 standard errors
missing, smallest eigenvalue -0.00119 against a largest of 17400. So
the site every sweep missed is reachable in the package's own tests,
and had been reporting three unexplained NAs there all along.
* lcsfm("LCM_CN") at test-a52-opt-shape.R:91 was a FALSE POSITIVE of
mine. That call passes optHessian = FALSE, so there is no Hessian by
request; LCM_CN is the one branch whose standard-error test does not
mention optHessian, and so the one place the guard was missing. Now
guarded.
Test uses A64's own design, psfm("GTRE_FML") on data_gen_p(t = 5,
N = 40, rand = 1, ...) -- 3 seconds, three of seven parameters with a
negative variance estimate -- gated on the regime from the start, since
A64's end-to-end case had to be gated after all five CI platforms
disagreed with this Mac.
Full local suite on the final code: pass 5556, fail 0, warn 49, skip 9,
against a 48-warning baseline -- the one addition being the genuine FD
report.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six entry points reported a
NaNstandard error with no signal at allzsfm(),lcsfm(),copsfm(),ivsfm(),selsfm()andpsfm()each tooktheir standard errors as one line:
Two defects sit in it.
NaNstandard error is reported with nothing said about it. On anindefinite Hessian the negative-variance coordinates give
NaN, andsuppressWarnings()discards R's own "NaNs produced" — the only signalthere was. Nothing sets a flag,
print()andsummary()say nothing beyondprinting
NaNin the cells, and the convergence code reads0.solve()was unguarded. Thesum(colMeans(h)) == 0test catches only anall-zero Hessian. A Hessian that is singular but not zero made
solve()error outright, throwing away a fit that had already run — minutes of one for
copsfm()andselsfm(), which are simulated ML.This is exactly the state
ttsfm()was in before #63; that PR fixed one entrypoint and left these five.
Reachable, and cheap to reach
zsfm(model_name = "ZISF")ondata_gen_cs(N = 25, rand = 8, sig_u = 1, sig_v = .3, cons = .5, beta1 = .5, beta2 = .5, mu = .5, a = 5)— half a secondto fit:
One of the two parameters that lost its standard error is the slope
x2,whose true value is 0.5. Over 80 fits of that design (
Nin 25, 40, 60, 100× seeds 1–20) two show this; the rest are healthy, with min eigenvalues of 37–65.
The change
All five now call
.se_missing_report()— the model-agnostic helper #63 added,which needed nothing TTHN-specific — on the reported standard errors and
names, so
copsfm()'s andivsfm()'s delta-method scales carry throughcorrectly and
ivsfm("IVLIML")'s count is of what it reports rather than whatit estimates. The result is named in a
warning()and recorded in$se_unresolved, whichprint()andsummary()already carry for anysfaregobject via.sfa_report_boundary()— so no change was needed there,and the flag survives
saveRDS()where a warning does not.$se_unresolvedis appended to each results list rather than re-listedagainst its name vector: re-listing twenty-one values against twenty-one names
to add one field is how a misalignment gets introduced.
solve()is wrapped intryCatchat all five sites.lcsfm()has two such sites (LCM_CN, andLCM/LCM_Z) and both are done.Tests
tests/testthat/test-a65-missing-se-everywhere.R. Onmainit gives 3failures — no warning is emitted, and
$se_unresolvedisNULL; on thisbranch 17 of 17 pass. It asserts the parameter names, that
min_eigenvalue < 0 < max_eigenvalue, that the message declines to choosebetween "not a maximum" and "boundary solution", and that
print()carries thenote. It also carries a false-positive guard: the same design at
N = 300must stay silent, set no flag, and report six finite standard errors.
What has NOT been run, and why
The full local suite was not run. This Mac is at load 92 with several
multi-worker R jobs of David's, so a suite run would be neither quick nor
trustworthy. Per the project's own practice when the Mac is saturated, CI's
five platforms are the gate here rather than a local run. What was run
locally: the new test file against both builds (17/17 on the branch, 3 failures
on
main),parse()on all five changed files, and the 80-fit scan above.Of the five entry points, only
zsfm()has a reproduction. The other four arewired identically and are covered by the existing suite only for not
regressing on healthy fits — finding a design that drives each of them to an
indefinite Hessian is its own piece of work, and A65's entry says so rather
than assuming the TTHN or ZISF seeds carry over.
psfm()was added after review began — it has seven sites and a third spellingThe scope note below said
psfm()was a sixth site this PR did not cover. It isnow covered, and the count in that note was wrong:
psfm()has sevenHessian-diagonal sites, covering
GTREandTRE(which share one),GTRE_Z,TRE_Z,GTRE_FML,TFE_WMLE,TFEandFD. Four of the seven left theinverse unguarded, so a singular but non-zero Hessian discarded a finished
panel fit — the most expensive kind this package produces.
The seventh is
FD, which spells the same quantityso it is invisible to A64's sweep (
sqrt(pmax(diag(solve(...)), 0))), to thisPR's first pass (
sqrt(diag(solve(...)))) and to the.sfa_se_from_var(diag(solve(...)))pattern that found the other six. Itsurfaced only because a replace-all asserted 4 occurrences and got 5. The sweep
was then redone on
$hessianitself, spelling-agnostically, which is whatshould have happened first.
Deliberately excluded after that sweep:
psfm()'s two other Hessian inversions(the sandwich
H_inv, the delta-methodvc). Both already guardsolve()andtake their standard errors through a Jacobian, so the report's eigenvalue
narrative does not map onto them as directly.
$se_unresolvedis appended at eight assemblies —GTREandTREshare asite but keep separate results lists — and the insertion was scoped to each
enclosing
if (model_name ...)block so it could not leak intoGTRE_SEQ1/SSFE/PL80/GTRE_SEQ2, where.smdoes not exist and the linewould have thrown at runtime.
The existing suite produced two more instances, and one was mine
The full local run's warning count moved 48 → 50, and the two were different
things:
psfm("FD")attest-psfm.R:23is genuine — 3 of 5 standard errorsmissing, smallest Hessian eigenvalue −0.00119 against a largest of 17400.
The site every sweep missed is reachable in the package's own test suite,
and had been reporting three unexplained
NAs there all along.lcsfm("LCM_CN")attest-a52-opt-shape.R:91was a false positive Iintroduced. That call passes
optHessian = FALSE: no Hessian by request,and my report fired claiming none could be read, for 6 of 6 parameters.
LCM_CNis the one branch of the seven whose standard-error test does notmention
optHessian, so it was the single place the guard was missing. Fixed.Testing
psfm("GTRE_FML")on A64's own design —data_gen_p(t = 5, N = 40, rand = 1, ...), 3 seconds, three of seven parameters with a negative variance estimate —so no search was needed. Gated on the regime from the start this time.
Full local suite on the final code, which the earlier revision of this PR did
not have: pass 5556, fail 0, warn 49, skip 9, against a
48-warning baseline — the single addition being the genuine
FDreport. Thefive-platform CI run is still the gate.
🤖 Generated with Claude Code