Releases: gcol33/tulpaObs
Release list
tulpaObs 0.0.233
AGENTS.mdadded, the Codex-facing counterpart ofCLAUDE.md. Same
contract: rules and interfaces here, the measurements they rest on in
NOTES_measurements.md. Listed in.RbuildignorealongsideCLAUDE.md, so
R CMD checkdoes not report a non-standard file at top level.
tulpaObs 0.0.232
- The
sbc()registry test readsms_abun()'s ranked quantity, not its
community mean..sbc_reg_means()carries one branch per community
family because a community fit'sfit$meansis the community MEAN alone
(P-length) while its SBC design ranks the per-species REALIZED coefficient
mu + b_s(S x P, species-major).ms_abun()was registered without its
branch, so it fell through to thefit$meansshort-circuit and returned a
2-length vector against the registry's 40 -- the exact failure the comment
above that helper describes. Branch added, mirroringms_distance()'s:
as.vector(t(cbind(coef_lambda, coef_p)))against
.tobs_sbc_ms_abun_names()$cols. All eight community families now have one.
tulpaObs 0.0.231
-
The
sbc()registry's end-to-end composition test slices the pooled
response at whatever rank the family brings. Itsslice_site()helper
enumerated ranks -- 3D array, else 2D matrix -- soms_dyn_occu(), whose
yis 4D[site x visit x season x species], fell to the 2D branch and
errored with "incorrect number of dimensions"..tobs_sbc_pool_4d_species()
was right: every registered family pools on the first axis and leaves the
rest alone, so the helper now indexes axis 1 at any rank and the
rank-enumeration cannot go stale again the next time a family arrives with
a new response shape. -
man/dot-tobs_community_em.Rdregenerated, clearing anR CMD check
codoc WARNING..tobs_community_em()gainedre_aghq/n_quad/
re_aghq_maxdim(with their roxygen) when the AGHQ variance debias was
generalized onto it, butdocument()was not re-run, so the Rd's\usage
had been three arguments behind the function since then.
Tagged before CI ran green. The run on this commit cleared the codoc WARNING
and the rank error, and reported one remaining failure: ms_abun() was
registered without its .sbc_reg_means() branch, so the sbc() registry
test read its community mean where the registry ranks the per-species
realized coefficient. That is fixed in v0.0.232, the first version in this
sequence with smoke (4936 assertions, 0 failures, 0 errors) and
R CMD check (Status: OK) both passing.
tulpaObs 0.0.230
-
ms_abun()registered forsbc()(#220, the 27th and
final family of the original registration scope). Needed TWO upstream
tulpaengine changes before it could even be attempted:tulpa#398(blup_cross): exposestulpa_re_aghq()'s per-group
mode/theta cross-Hessian.- A second gap found while wiring this family:
tulpa_re_aghq()also
only ever exposed the per-RE-TERM DIAGONAL of a group's posterior
covariance (blup_var).ms_abun()'s lambda (abundance) and p
(detection) arms share a per-species grouping factor with real
cross-arm posterior covariance -- the same lambda/p identifiability
ridgetobs()'s penalized-EM exists to break for occupancy psi/p --
so drawing a species' abundance and detection deviations
independently would repeat the#226bug one level deeper (inside a
species instead of between the community mean and a species). Fixed
upstream by addingblup_cov_g/blup_cross_g(the full per-group
joint covariance/cross-Hessian, unsliced by RE term), validated
against a closed-form joint-Hessian construction on a toy model with
deliberately collinear RE terms.
ms_community$Cinv/Bfnow thread offblup_cov_g/blup_cross_g
(R/ms_abun.R), matching the convention every othersbc()-registered
community family uses. Only reachable via the AGHQ/joint_fd engine
(control = list(optimizer = "joint_fd", n.quad > 1)) -- the default
n.quad = 1Laplace-EM path (a different, faster engine entirely) does
not exposeCinv/Bfat all, and errors with a pointer rather than
silently drawing an independent(mu, b_s).Registered at reduced
n.sim = 15(distsamp_open's own precedent for the
identical cost problem, see0.0.227below): an.sim = 100run at
standard settings was projected to take >12h; the actualn.sim = 15run
completed in 39.9 min. Posterior calibrates cleanly (min p_unif 3.9e-3,
0/40 quantities below 1e-3); narrow control's minimum p_unif is 2.9e-12
(comfortably below the 1e-3 pass bar every sibling family uses), though
one narrow-arm quantity did not fail as comprehensively as siblings'
(max p_unif 1.3e-2 there, vs ~1e-6 to 1e-15 elsewhere) -- not
re-investigated given the ~40min cost of a re-run, and does not weaken
the primary (posterior) calibration signal.All 27 originally-scoped families are now registered for
sbc(). -
Engine pin raised to tulpa 0.0.198 (Imports floor and
Remotestag
together, as.github/scripts/check-engine-pin.Rrequires). That release
carries both engine changes thems_abun()registration above depends on:
blup_cross(gcol33/tulpa#398) and the full per-group joint covariance
blup_cov_g/blup_cross_g. The floor and the tag had also drifted apart
from each other (0.0.197 vs v0.0.196), which the pin check fails on.
Tagged before CI ran green. Every workflow run from 2026-08-11 to 2026-08-15
stopped at pak dependency resolution, so the test suite had not executed
against this code. Three defects were sitting behind that, all of them
test-side or in generated documentation: the sbc() registry test's
rank-enumerating slice helper, that test's missing ms_abun()
.sbc_reg_means() branch, and a stale man/dot-tobs_community_em.Rd.
Package behaviour is unaffected.
Use v0.0.232, the first version in this sequence with smoke (4936
assertions, 0 failures, 0 errors) and R CMD check (Status: OK) both
passing.
tulpaObs 0.0.197
0.0.197 (2026-08-11)
-
Every diagnostic is now one verb, dispatched on the fit (breaking). The
package exported a paralleltobs_*spelling for diagnostics that already had
an owner elsewhere, so the same concept carried two names and neither could
dispatch. Each is now an S3 method on the generic of whichever package owns
the concept, and the generic is re-exported here, so attaching tulpaObs is
enough to reach it:was now generic from tobs_waic(fit)waic(fit)loo -- loo(fit)loo tobs_cpo(fit)cpo(fit)tulpa tobs_dic(fit)dic(fit)tulpa tobs_sbc(fit)sbc(fit)tulpa tobs_pit_residuals(fit)pit_residuals(fit)tulpa tobs_test_uniformity(pit)test_uniformity(pit)tulpa tobs_test_dispersion(fit)test_dispersion(fit)tulpa tobs_test_outliers(fit)test_outliers(fit)tulpa tobs_test_zero_inflation(fit)test_zero_inflation(fit)tulpa tobs_ppc(fit)ppc(fit)tulpaObs tobs_check(fit)check_model(fit)tulpa The old names are removed rather than aliased. Results are unchanged: each
method is the previous function body, andtest_uniformity()runs the same
Kolmogorov-Smirnov test on the same PIT vector. -
loo(fit)is new, and returns a genuinepsis_looobject, so
[loo::loo_compare()] and the model-weight machinery read atobs_fit
directly. It builds the same pointwise log-likelihood matrix the other
criteria use and hands it to PSIS with relative effective sample sizes off the
chain layout -- the buildertobs_stack()already used per member. -
tobs_check()is nowcheck_model(fit), and it draws the panel its
generic promises. [tulpa::check_model()] errored on atobs_fitbefore
this: its default method builds the panel fromfitted()andresiduals()
as numeric vectors, and a latent-state fit has neither (fitted()returns
list(psi, p, z)), so the response could not be resolved. The method reads
the same quantities through the family's own doors instead --pit_residuals(),
ppc(),test_dispersion(),moran_i()-- printing the roll-up report as
before and drawing a QQ-uniform, PPC, dispersion and (givencoords)
correlogram panel beside it.plot = FALSEprints the report alone. Nothing
is simulated twice: the panel plots what the report already computed, which is
whytest_dispersion()now also returns the simulated variances in$sim. -
ppc()deliberately did not fold intotulpa::pp_check(). That generic
draws a graphical check whileppc()returns a Bayesian p-value, and a method
has to honour its generic's contract, so the discrepancy check became its own
generic. -
loomoves from Suggests to Imports (it now owns two of the doors), and
requires tulpa >= 0.0.196 for the generics.
tulpaObs 0.0.196
0.0.196 (2026-08-11)
-
The information criteria score a random effect the fit carried
(#215). On the grid-integrated (nested_laplace) route,
occu_cover()scored every observation-arm random effect at the population
mean in WAIC, LOO, CPO, PPC and PIT, and an occupancy-arm one likewise. Both
now reach the criteria through the same per-visit and per-site offset path the
sampled route uses. This moves existing numbers: elpd_waic -17.939 ->
-6.406 on a detection-arm RE atsigma_re_p1.10 over 216 visits, -125.94 ->
-64.41 on a cover-arm RE over 400 visits, and -209.525 -> -200.180 on an
occupancy-arm RE atsigma_re1.57. A fit carrying no random effect is
identical to the bit. The occupancy term also carries its grouping variable
and factor levels now, soranef()labels it like the observation arms and
predict(newdata = )matches on it. -
A bare areal term on
occu_cover()loads on occurrence only undernuts,
as it already did undernested_laplace(#217). The same
input previously fit two different models: the deterministic backend decoupled
the cover arm while the sampler estimated a copy amplitude off the default
axis (mean 0.78 on a 36-cell fixture). A term's process is the formula it sits
in, andcopy()is what couples it to the other arm. This changes fitted
values for existing no-copy spatial NUTS fits. Declaringcopy(spatial())
reproduces the previous numbers to every printed digit on all three coupled
recovery fixtures. -
occu_cover()samples a second (spatially-varying-coefficient) areal field
undernuts(#214). The sampler carried one field block, so a
trend surface was confined to the grid-integrated path. Each block now carries
its own basis, site-to-node map, per-site design weight and its own sampled
(sigma, rho, alpha). Reported asfit$trend_field/trend_fields, with
per-block suffixed hyperparameters inhyper_draws. Both surfaces recover with
zero divergences. -
A per-source detection formula can carry a structured term
(#216). An integrated detection-armspde()field was wired in
the fitter and unreachable fromtobs(). Per-source field correlation with
truth 0.87-0.93; the fields are reported infit$spatial_field_det. Gating
this uncovered a silent misplacement: undermethod = "nested_laplace"a
detection-arm term was fit against the occupancy arm, on the single-season path
as well as the integrated one. Both now error with a pointer. -
Simulation-based calibration runs on eight more families
(#207):occu,count,abun,royle_nichols,occu_ttd,
fp_occu,removalanddistance, each verified end to end against a
deliberately mis-scaled control. A structured term and a visit-level
observation design are refused rather than approximated. -
The SBC replicate field is drawn at the width the joint engine fits
(#213). The generator normalised its ICAR draw to geo-mean
marginal SD 1 while the joint nested-Laplace engine carries the amplitude
against the rawQ = D - W, so both reported field SDs were off by one common
factor while their ratio stayed clean. Ranks return inside the band. Note that
the joint path reportssigmaas a raw amplitude while the sampled path
reports a geo-mean marginal SD; compare a fit against a simulator on the field,
not on the reported scalar.
tulpaObs 0.0.188
0.0.188 (2026-08-07)
- Pinned to
tulpa (>= 0.0.136), which makes a joint nested-Laplace fit
return the same numbers twice.test-occu-cover-parallel-coupling.R
asserts that two identical paralleloccu_cover()fits are bit-identical,
and it failed once under a loaded parallel suite while passing 200+ paired
fits in isolation. The cause was upstream: the coupled-cell scatter's chunk
count, which sets its summation order, was derived from a count of solves in
flight, and the outer width was clamped against a live free-memory reading,
so both moved with whatever else the machine was doing (gcol33/tulpa#300).
Measured from here: forcing the chunked path movesmeansby 1.4e-15 and
sdsby 1.19e-07, and the fixture's default path was bit-identical to the
forced serial scatter -- so the chunk count demonstrably changes the answer,
and whether it changed was race-decided. Both inputs are now read from the
problem rather than the machine, with no parallelism given up. Nothing in
this package changed; the engine's exported headers are unchanged from
0.0.135, so no rebuild is needed.
tulpaObs 0.0.187
0.0.187 (2026-08-07)
- Pinned to
tulpa (>= 0.0.135). The engine's exported headers
(inst/include/tulpa/) are unchanged from 0.0.132, so this package's
compiled backend links against the same ABI and needs no rebuild; the
bump carries the engine's own nested-Laplace and diagnostics work
(gcol33/tulpa#294 through #299), including the per-block declaration of
required fields checked at dispatch. TheImportsfloor, theRemotes
tag, and the installed engine are checked against each other by
.github/scripts/check-engine-pin.Ron every CI job, so the three
cannot drift apart silently.
tulpaObs 0.0.185
0.0.185 (2026-08-07)
-
Pinned to
tulpa (>= 0.0.131)so the auto-recentering outer grid also
engages at this package's own defaults (gcol33/tulpa#291, #292). The
0.0.130 rescue that 0.0.183 pinned for readpareto_k_mode_u/cov_u,
fields only the full outer Pareto-k diagnostic populates -- and
control$diagnose.kdefaults toFALSEon every route reaching the joint
driver --occu_cover(),occu_multiscale_cover(), andoccu()'s
SVC-bar joint path (#101) -- so the driver's rescue never fired on a
default fit. It now computes the same (mode, covariance) itself
whenever the grid has collapsed onto an edge, independent of the
diagnostic. A collapsed defaultoccu_cover()fit therefore re-centers and
refits where before it stayed railed.The companion upstream change adds the same rescue to
fit_st_nested()'s
tau_spatial x tau_temporal [x rho]tensor grid, which reaches the
observation families carrying an areal + temporal field on one arm
(removal(),distance(),fp_occu(),dyn_abun()).Both are behaviour-preserving where the grid was not collapsed, and both
decline whenever a grid-construction knob was set explicitly. The
Remotes: gcol33/tulpa@v0.0.131tag moves with the floor, as
check-engine-pin.Rasserts: r-universe builds default-branch HEAD on its
own poll schedule and currently serves 0.0.130, so the tag is what resolves
the pinned engine.
0.0.184 (2026-08-07)
-
tobs_cpo(),tobs_ppc()andtobs_pit_residuals()now work on a compact
(ragged)occu_cover()fit (#185).method = "nested_laplace"defaults
compact = TRUE, so the ragged carrier is the standard representation for the
joint engine, and a compact fit stores no padded[n_sites x max_visits]
y/validgrid. The pointwise-log-likelihood path already read the ragged
carrier, but the PPC and the PIT / LOO-PIT CDF limits still derived their
per-site detection summary asrowSums(model$y * model$valid), which errored
with'x' must be an array of at least two dimensions. LOO / CPO / LOO-PIT and
the posterior predictive check were therefore unavailable for any real-scale
occu_cover()fit whiletobs_waic()worked.All three now read one
.occu_cover_visit_view(): the one-row-per-valid-visit
structure a compact fit stores, and the flattening of a dense fit's padded grid
(.occu_cover_dense_ragged(), the site-major visit-ascending order the
pointwise kernel already used).n_validandany_detare derived there, so
there is one definition of each. Because both layouts now feed one kernel per
diagnostic, the dense and compact builds of the same data agree to the bit:
measured 0.000e+00 acrosselpd_waic,elpd_loo, the LOO-PIT, the PIT
residuals, and the PPCfit.y/fit.y.rep/bayesian.p. -
tobs_ppc()no longer returnsNAwhen a detected visit has a missing
cover (#185). The likelihood gates the cover density ondetected AND finite(missing-at-random cover), but the PPC kernel scored theNAinto the
Freeman-Tukey / chi-squared sum, sofit.ycame backNAfor every draw and
bayesian.pwith it. Verified directly against the pre-change kernel: oneNA
cover at one detected visit tookfit.yfrom1.788toNAon all draws.
The replicate is now drawn at every valid visit (so the RNG stream does not
depend on which visits happened to be detected) and scored only where a cover
was observed. -
Internal: the three ragged
occu_cover()kernels -- pointwise
log-likelihood, PPC, CDF limits -- assemble their per-draw predictors from one
sharedArmsview (src/occu_cover_ragged.h) instead of each writing out the
column-major design / draw index arithmetic. The pointwise kernel is
byte-identical after the change (test-occu-cover-ploglik-cpp.Rscores it
against its R oracle). The PPC kernel now takes the arm designs and the
coefficient draws rather than precomputed[n_sites x (S * max_visits)]
predictor blocks, so its memory is O(V) in the observations rather than
O(padded grid x draws).
tulpaObs 0.0.183
Pins the tulpa v0.0.130 engine.
Auto-recentering outer grid (gcol33/tulpa#289, #290)
Every nested-Laplace family built its outer hyperparameter grid from a fixed default axis, so a fit whose field-SD posterior mode sat above the top node collapsed every outer weight onto that boundary node and reported it only as pareto_k_regime = "collapsed_edge". The motivating case was a tulpaObs one: on the 78 real EVA occu_cover() fits, 10 railed the 5.0 sigma ceiling (measurement upstream, in gcol33/tulpa#289).
The fixed grid is now a starting axis rather than a ceiling. A collapsed fit re-centers using the mode-Hessian its own outer Pareto-k diagnostic already computes, and refits.
It reaches this package through the joint driver occu_cover() and cover() already fit against (single-block icar/bym2 backends and multi-block copy blocks) and through the standalone .NL_REGISTRY path. An explicit sigma_grid / tau_grid still wins; auto-recenter engages only where the grid was left at its default, and is a no-op when the mode already sits inside the old axis. car_proper (its rho axis is unguessable) and MCAR are out of scope upstream, so the correlated cover-field routes keep the old behaviour.
Restored the exact Remotes tag pin
be6b2de dropped the tag on the expectation that r-universe would serve the floor. r-universe builds default-branch HEAD on its own poll schedule, not tags or releases, and currently serves tulpa 0.0.125 -- so an Imports floor above that resolves an engine below the floor and check-engine-pin.R fails, which is the #150 skew reopening. Remotes: gcol33/tulpa@v0.0.130 installs the pinned engine directly.
Cost, as before: the Imports floor and the Remotes tag now have to move together, which check-engine-pin.R asserts.
Also folds in the unreleased 0.0.182, which moved the floor to tulpa (>= 0.0.129) for the outer-grid regime split (gcol33/tulpa#276).
Verification
check-engine-pin.R:Imports >= 0.0.130 / Remotes @v0.0.130 / installed 0.0.130, with pak resolvinggithub::gcol33/tulpa@708ddc2- Smoke tier against the installed package on tulpa 0.0.130: 3703 passed, 0 failed, 0 errors, 649 skipped
- Cover recovery files were not run for this release