Skip to content

Feat(dSep): add ncores for parallel d-separation evaluation#329

Open
Lgz-tud wants to merge 4 commits into
jslefche:mainfrom
Lgz-tud:feat/dsep-parallel
Open

Feat(dSep): add ncores for parallel d-separation evaluation#329
Lgz-tud wants to merge 4 commits into
jslefche:mainfrom
Lgz-tud:feat/dsep-parallel

Conversation

@Lgz-tud

@Lgz-tud Lgz-tud commented May 27, 2026

Copy link
Copy Markdown

Summary

Add an opt-in ncores argument to dSep(), summary.psem(), fisherC(), and AIC_psem() so tests of directed separation can be evaluated in parallel. Default ncores = 1L preserves the existing serial behavior byte-identically.

Motivation

For SEMs with many independence claims (especially involving lmer / glmmTMB where each update() is expensive), summary(SEM) can take many minutes because the d-sep loop is sequential despite each claim being fully independent of the others.

Changes

  1. refactor(dSep) — hoist formulaList and the response→model lookup out of the per-iteration testBasisSetElements call. Zero behavior change; small constant-time win.
  2. feat(dSep) — add ncores argument; dispatch to parallel::mclapply (Linux/macOS) or parallel::parLapply on a PSOCK cluster (Windows) when ncores > 1L. Progress bar disabled in parallel mode (workers cannot share txtProgressBar). Input validated; worker errors surfaced via stop().
  3. feat(summary,fisherC,AIC_psem) — propagate ncores through the common entry points so users can write summary(SEM, ncores = 4).
  4. docs: — regenerate Rd from roxygen.

Backward compatibility

ncores = 1L is the default. Output verified byte-identical (identical()) to pre-refactor main on lm / glm / lmer SEMs across all four (conserve, conditioning) combinations (12 / 12 fixtures match). The parallel path (ncores = 2L, fork) is also byte-identical to the serial baseline on the same fixtures.

Benchmark (28-core Linux, microbenchmark times = 10)

Fixture Claims serial (ms) ncores=2 (ms) ncores=4 (ms) Speedup (n=4)
keeley lm 3-link 3 13 19 19 0.70×
keeley lm 5-eq 16 47 51 39 1.21×
shipley lmer 2 1051 798 830 1.27×

Speedup is sublinear for small basis sets (fork overhead dominates) but grows quickly when claims are individually slow (mixed models) or numerous. Users with 30+ claim mixed-model SEMs should see substantial wins; this PR's bundled fixtures undersample that regime because the package's bundled datasets are small.

Lgz-tud added 4 commits May 27, 2026 22:02
testBasisSetElements was recomputing formulaList (and the response-to-model
lookup) once per basis-set element, even though modelList does not change
across iterations of the loop. Compute both once in dSep() and pass into
the inner function.

No behavior change: outputs verified byte-identical (identical()) to
pre-refactor on lm, glm, and lmer fixtures across the 4 combinations of
(conserve, conditioning).
Adds an `ncores` argument to dSep() that evaluates independence claims in
parallel:

  * ncores = 1L (default): byte-identical to the pre-change behavior;
    progress bar still works.
  * ncores > 1L on Linux/macOS: parallel::mclapply (fork).
  * ncores > 1L on Windows: parallel::parLapply on a PSOCK cluster
    (with clusterEvalQ(requireNamespace) + clusterExport of the
    captured loop state).
  * Progress bar automatically disabled when ncores > 1, since
    txtProgressBar cannot be shared across worker processes.
  * Worker errors are surfaced via stop() instead of silently returning
    try-error objects that would later break do.call(rbind, ...).

Adds `parallel` (base R) to DESCRIPTION Imports.

Backward compatibility: existing user code that does not pass `ncores`
continues to use the serial path with the original progress bar; outputs
verified byte-identical on lm, glm, and lmer fixtures.
Parallel path (ncores = 2L) also verified byte-identical to the serial
baseline on the same fixtures (mclapply fork preserves determinism for
these models).
Adds an `ncores` argument to summary.psem(), fisherC(), and AIC_psem(),
so users can opt into parallel d-sep evaluation from the natural entry
points without calling dSep() directly:

    summary(SEM, ncores = 4)

The argument is purely a pass-through; there is no new parallel logic in
these three functions. Each just appends ncores to its downstream call:

  * summary.psem  -> dSep, fisherC, AIC_psem
  * fisherC       -> dSep (only when dTable is a psem; ignored otherwise)
  * AIC_psem      -> fisherC (only relevant for AIC.type = "dsep")

Default is `ncores = 1L`, preserving the existing serial behavior
byte-identically. Verified end-to-end: summary(SEM, ncores=1) and
summary(SEM, ncores=2) produce identical() dTable, Cstat, and AIC slots
on lm/glm/lmer fixtures.
devtools::document() rebuild after the dSep refactor + ncores feature.
Adds @param ncores to dSep, summary.psem, fisherC, and AIC_psem; adds
respMap to the internal testBasisSetElements signature.

No code change.
@Lgz-tud Lgz-tud changed the title Feat/dsep parallel feat(dSep): add ncores for parallel d-separation evaluation May 27, 2026
@Lgz-tud Lgz-tud changed the title feat(dSep): add ncores for parallel d-separation evaluation Feat(dSep): add ncores for parallel d-separation evaluation May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant