Three independent integrity gaps in the multivariate FPC representation. The MFPCA math itself is correct (verified to 5.6e-15) — these are about preserving the joint-fit metadata under user operations.
1. Arithmetic explodes with an unhelpful internal error
R/tfb-mfpc.R:379-384. mf + 1 first warns "potentially lossy cast" then aborts via the per-component scoring stub: "Can't score data onto a single component…" — a baffling message for a user who typed + 1.
2. Component replacement silently destroys the joint basis
R/accessors-mv.R:110-137. mf$x <- some_other_tfb succeeds, is_tfb_mfpc() then returns FALSE, the joint spec is silently discarded.
3. Concatenation of slices of the same fit drops the spec
R/vctrs-mv.R:24-42. c(mf[1:4], mf[5:8]) of pieces of the identical fit loses the spec. The slice-keeps / concat-drops asymmetry is documented in a code comment but never to users. Any dplyr::bind_rows/summarize round-trip silently downgrades the column.
Fix
- For Math/Ops on
tfb_mfpc: intercept early with a clear cli_warn ("demoting to per-component tfb_fpc representation; rescore with tf_rebase for joint MFPC arithmetic"); actually demote (drop the joint spec attributes) and continue.
- For
$<- on tfb_mfpc: warn that replacement demotes; demote and continue.
- For
tf_mv_ptype2 on tfb_mfpc: keep the spec when all inputs carry identical() specs (i.e. they came from the same fit). Document the "different specs → demote" rule.
Reproduce
library(tf)
set.seed(1)
mf <- tfb_mfpc(tfd_mv(list(x = tf_rgp(20), y = tf_rgp(20))), pve = 0.95)
mf + 1 # baffling error
mf2 <- mf
mf2$x <- mf$x # succeeds — but is_tfb_mfpc(mf2) is now FALSE, no warning
is_tfb_mfpc(c(mf[1:4], mf[5:8])) # FALSE — same fit, lost spec
Regression test (sketch)
test_that("tfb_mfpc protects its joint spec", {
set.seed(1)
mf <- tfb_mfpc(tfd_mv(list(x = tf_rgp(20), y = tf_rgp(20))), pve = 0.95)
# 1. Arithmetic demotes with a clear warning
expect_warning(out <- mf + 1, "demot|mfpc|joint")
expect_false(is_tfb_mfpc(out))
# 2. $<- demotes with a clear warning
expect_warning({mf2 <- mf; mf2$x <- mf$x}, "demot|mfpc|joint")
expect_false(is_tfb_mfpc(mf2))
# 3. c() of slices of the same fit preserves the spec
expect_true(is_tfb_mfpc(c(mf[1:4], mf[5:8])))
})
Found in the June-2026 ground-up review.
Three independent integrity gaps in the multivariate FPC representation. The MFPCA math itself is correct (verified to 5.6e-15) — these are about preserving the joint-fit metadata under user operations.
1. Arithmetic explodes with an unhelpful internal error
R/tfb-mfpc.R:379-384.mf + 1first warns "potentially lossy cast" then aborts via the per-component scoring stub: "Can't score data onto a single component…" — a baffling message for a user who typed+ 1.2. Component replacement silently destroys the joint basis
R/accessors-mv.R:110-137.mf$x <- some_other_tfbsucceeds,is_tfb_mfpc()then returns FALSE, the joint spec is silently discarded.3. Concatenation of slices of the same fit drops the spec
R/vctrs-mv.R:24-42.c(mf[1:4], mf[5:8])of pieces of the identical fit loses the spec. The slice-keeps / concat-drops asymmetry is documented in a code comment but never to users. Anydplyr::bind_rows/summarizeround-trip silently downgrades the column.Fix
tfb_mfpc: intercept early with a clearcli_warn("demoting to per-component tfb_fpc representation; rescore withtf_rebasefor joint MFPC arithmetic"); actually demote (drop the joint spec attributes) and continue.$<-ontfb_mfpc: warn that replacement demotes; demote and continue.tf_mv_ptype2ontfb_mfpc: keep the spec when all inputs carryidentical()specs (i.e. they came from the same fit). Document the "different specs → demote" rule.Reproduce
Regression test (sketch)
Found in the June-2026 ground-up review.