Large-Module Audit

Thirty-three modules in mixle exceed 1,500 lines. Size alone is not a defect, and this audit is not a mandate to split them. Its purpose (worklist A1.7) is to record, for each large module, what it owns, the state it carries, the optional dependencies it touches, its hot paths, its serialization hooks, and where a safe extraction boundary lies — so that a future change can be scoped without a blind refactor.

The governing rule, restated from Support Policy: refactor a large module only where it removes a demonstrated defect or enables isolated testing. Do not split stable numerical code merely to satisfy a line-count target — the EM kernels and forward/backward recursions below are load-bearing and bit-checked, and re-slicing them risks a correctness regression for no user-visible gain.

The set of modules audited here is pinned by mixle/tests/large_module_audit_test.py: a new module that crosses 1,500 lines, or an audited entry that is deleted, fails that test until this page is updated.

How to read an entry

  • Responsibilities — what the module owns.

  • Stateful globals — module-level mutable/registry state (the first thing to check before extraction).

  • Optional imports — heavy dependencies reached lazily; base import must not require them.

  • Hot paths — the numerically load-bearing routines; treat as bit-checked, change with parity tests.

  • Serialization — round-trip hooks (to_json / from_json / encoders); a schema-visible surface.

  • Extraction boundary — the safe seam, or “leave as-is” when no clean seam removes a defect.

Latent sequence and mixture families

These follow the canonical six-type contract (Distribution / Sampler / Accumulator / AccumulatorFactory / Estimator / DataEncoder). Their bulk is the vectorized seq_update / seq_log_density EM machinery and, for several, compiled Numba kernels. They share one extraction principle: the compiled kernels and the data encoder are the only clean seams; the distribution/estimator pair is a single numerical unit.

mixle/stats/latent/hidden_markov.py (5,308)
  • Responsibilities: the principal HMM path — distribution, scaled forward/backward, Baum-Welch EM, terminal/terminal-value likelihoods, Dirichlet chain prior.

  • Stateful globals: only TypeVars (T, E1, E2 …); no mutable registry.

  • Optional imports: none at this layer (the compiled kernels live in _hidden_markov_numba_kernels.py, already a separate module).

  • Hot paths: _hmm_forward_ll, seq_log_density, seq_update, kernel, the Numba route.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the Numba kernels are already extracted. The terminal-state and terminal-value likelihood blocks are the next candidate seam if a defect ever localizes there; absent that, leave as-is — this is the most parity-tested numerical code in the tree.

mixle/stats/latent/tree_hidden_markov_model.py (2,990)
  • Responsibilities: tree-structured HMM; Numba forward/backward, Baum-Welch, posteriors, Viterbi.

  • Stateful globals: TypeVars and shape aliases only.

  • Optional imports: none (Numba kernels inline as module functions numba_*).

  • Hot paths: numba_seq_log_density, numba_baum_welch, numba_posteriors, numba_viterbi.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the numba_* kernel group is the clean seam (mirror hidden_markov’s split into a _..._numba_kernels module) — worth doing only alongside a kernel change.

mixle/stats/latent/structured_hmm.py (3,320)
  • Responsibilities: HMM with pluggable transition operators (dense / low-rank / sparse / block-diagonal / banded), 24 classes.

  • Stateful globals: _DENSE_FB_NUMBA (compiled-kernel handle).

  • Optional imports: jax, numba.

  • Hot paths: forward/backward, seq_update, seq_log_density, logsumexp.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the TransitionOperator family (already an internal class hierarchy) is the natural module split — a genuine isolated-testing win, low numerical risk.

mixle/stats/latent/lookback_hidden_markov_model.py (2,096)
  • Responsibilities: the lookback-window HMM — a typed rewrite of a since-removed untyped sibling module — forward/backward and Baum-Welch EM over a bounded lookback horizon, terminal-state/value likelihoods, and (as of the conjugate-prior fix) a Dirichlet chain prior mirroring hidden_markov.py’s own MAP path.

  • Stateful globals: only TypeVars (T, E0, E1); no mutable registry.

  • Optional imports: none at this layer (the compiled kernels live in _hidden_markov_numba_kernels.py, shared with hidden_markov.py).

  • Hot paths: seq_log_density, seq_update / seq_update_engine, and the MAP branch inside estimate (_hmm_map_probs plus Dirichlet posterior propagation, added alongside get_prior / set_prior / model_log_density).

  • Serialization: dist_to_encoder / seq_encode; two backward-compatible aliases (LookbackHiddenMarkovModelAccumulator / ...AccumulatorFactory) at module scope.

  • Extraction boundary: leave as-is; the prior/MAP machinery just added mirrors hidden_markov.py’s own inline placement rather than introducing a separable module — the file crossed 1,500 lines to reach that parity, not through unrelated growth.

mixle/stats/sequences/markov_chain.py (2,755)
  • Responsibilities: Markov chain distribution/estimator, Dirichlet prior, stationary distribution, gradient-fit state.

  • Stateful globals: TypeVars only.

  • Optional imports: none.

  • Hot paths: forward, seq_update, seq_log_density, kernel.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: leave as-is; the gradient-fit state (_MarkovChainGradientFitState) is the only separable piece and is small.

mixle/stats/latent/mixture.py (2,348)
  • Responsibilities: the general finite mixture — responsibilities, weight prior, EM.

  • Stateful globals: TypeVars only.

  • Optional imports: none (Numba referenced through the compute layer).

  • Hot paths: responsibility logsumexp, seq_update, seq_log_density, kernel.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: leave as-is; the prior-construction helpers (mixture_prior and friends) could move to a _priors helper but that is cosmetic, not defect-driven.

mixle/stats/latent/lda.py (1,966) and mixle/stats/latent/labeled_lda.py (1,837)
  • Responsibilities: LDA and label-supervised LDA — variational E-step, alpha updates.

  • Stateful globals: shape aliases only; no registry.

  • Optional imports: none.

  • Hot paths: variational fixed point, seq_update, seq_log_density.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the shared alpha-update math (update_alpha exists in both) is a real duplication seam — a small shared helper would remove copy drift; safe, defect-adjacent.

mixle/stats/combinator/conditional.py (1,965)
  • Responsibilities: conditional-distribution combinator (feature → response) and its accumulation.

  • Stateful globals: shape aliases only.

  • Optional imports: none.

  • Hot paths: seq_update, seq_log_density, kernel.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: leave as-is; the two module-level stat helpers are already factored.

mixle/stats/graphs/temporal_graph_grammar.py (4,424)
  • Responsibilities: temporal graph-grammar family (37 classes: motifs, labeled/unlabeled variants).

  • Stateful globals: _EPS only.

  • Optional imports: none at import (graph backends reached lazily).

  • Hot paths: emission log-likelihood, seq_update, seq_log_density.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the labeled vs unlabeled variant families are separable, but they share the motif and alignment helpers; split only if a variant grows an independent defect surface.

mixle/stats/latent/integer_hidden_association.py (1,843)
  • Responsibilities: integer hidden-association models over grouped-count word sets.

  • Stateful globals: encoding/statistic TypeVars (E0–E4, SS1, SS2) only, plus the engine-kernel registration performed by _register_int_hidden_association_engine_kernel.

  • Optional imports: Numba, reached through the compute layer.

  • Hot paths: seq_log_density, seq_update, seq_update_engine, and the numba_seq_log_density / numba_seq_update pair.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the numba_* kernels are the only clean seam, as elsewhere in this section; the distribution/estimator pair is one numerical unit. Leave as-is absent a defect.

mixle/stats/latent/integer_probabilistic_latent_semantic_indexing.py (1,723)
  • Responsibilities: integer PLSI — the EM fixed point over document/topic/word factors.

  • Stateful globals: T1/SS1 aliases, plus _register_int_plsi_engine_kernel’s registration.

  • Optional imports: Numba, through the compute layer.

  • Hot paths: fast_seq_log_density / fast_seq_update and their seq_* entry points.

  • Serialization: dist_to_encoder / seq_encode; IntegerPLSIEncodedData is the payload.

  • Extraction boundary: IntegerPLSIEncodedData and the fast_* kernels separate cleanly from the estimator; a real seam, worth taking only with a defect that localizes there.

mixle/stats/latent/heterogeneous_pcfg.py (1,828)
  • Responsibilities: PCFG with heterogeneous terminal emissions — inside/outside recursions, termination certification, quantized index construction.

  • Stateful globals: none.

  • Optional imports: none at this layer.

  • Hot paths: seq_log_density, seq_update, backend_seq_log_density.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: PCFGTerminationCertificate and _HeterogeneousPCFGQuantizedIndexBuilder are already self-contained and are the seam if one is ever needed; the recursions themselves are a single unit.

mixle/stats/sequences/integer_markov_chain.py (1,972)
  • Responsibilities: integer Markov chains with optional lagged transitions, including the explicitly non-generative scoring mode (NonGenerativeIntegerMarkovChainError).

  • Stateful globals: E1/E2/SS1/SS2 aliases only.

  • Optional imports: none.

  • Hot paths: seq_log_density, seq_update, seq_update_engine, backend_seq_log_density.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: leave as-is. The lag handling threads through encode, score and update together; separating it would split one contract across two modules.

mixle/stats/multivariate/multivariate_gaussian.py (1,911)
  • Responsibilities: full-covariance Gaussian six-type contract, the covariance ridge and its repair disclosure, the robust Cholesky self-heal, the opt-in conditioning receipt, and the conditioning-gated shift-anchored moment track.

  • Stateful globals: _LAST_REPAIR (a one-slot channel from _robust_cho_factor to the constructor, drained immediately).

  • Optional imports: scipy.linalg (required at this layer), scipy.stats lazily for cdf/quantile.

  • Hot paths: seq_log_density, seq_update, _robust_cho_factor, _anchored_pooled_covariance, backend_stacked_sufficient_statistics.

  • Serialization: __pysp_getstate__/__pysp_setstate__ (parameters only; derived caches recomputed), dist_to_encoder/seq_encode, and MultivariateGaussianSuffStat.__reduce__.

  • Extraction boundary: the anchored-moment helpers (_needs_anchor, _consistent_anchored_moments, _anchored_mean_offset, _anchored_pooled_covariance) are the one clean seam, shared in spirit with the diagonal and univariate Gaussian families.

mixle/stats/multivariate/diagonal_gaussian.py (1,624)
  • Responsibilities: diagonal-covariance Gaussian six-type contract, per-coordinate variance floor and its repair disclosure, and the same conditioning-gated shift-anchored moment track.

  • Stateful globals: none.

  • Optional imports: scipy.stats lazily for cdf/quantile.

  • Hot paths: seq_log_density, seq_update, _anchored_pooled_variances.

  • Serialization: dist_to_encoder/seq_encode and DiagonalGaussianSuffStat.__reduce__.

  • Extraction boundary: the anchored-moment helpers, jointly with the full-covariance module’s.

mixle/stats/multivariate/integer_multinomial.py (1,586)
  • Responsibilities: integer-keyed multinomial over a bounded support.

  • Stateful globals: SS0/D/E0/E aliases only.

  • Optional imports: none.

  • Hot paths: seq_log_density, seq_update, seq_update_engine.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: leave as-is; the six-type contract is already the minimum unit here.

mixle/stats/combinator/sequence.py (1,679)
  • Responsibilities: iid sequence laws plus the explicitly non-generative sequence score modes (NonGenerativeSequenceError), including the length-support reasoning.

  • Stateful globals: T/E1/E2/E aliases and the two proof limits _INFINITE_INTEGER_LENGTH_SUPPORTS, _FINITE_LENGTH_PROOF_LIMIT (constants, not mutable state).

  • Optional imports: none.

  • Hot paths: seq_log_density, seq_update, backend_seq_log_density, backend_stacked_log_density, expected_log_density.

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the length-support proof helpers are the one separable piece; the generative and score-only paths must stay together, since their divergence is the contract.

mixle/stats/bayes/conjugate.py (1,669)
  • Responsibilities: conjugate-prior posteriors derived from the exponential-family map, one class per conjugate pair, plus the _NO_CLOSED_FORM fallback marker.

  • Stateful globals: _NO_CLOSED_FORM (a sentinel).

  • Optional imports: scipy (special functions), reached lazily.

  • Hot paths: posterior updates and point_estimate; per-observation, not per-batch.

  • Serialization: posterior parameters round-trip through the owning distribution.

  • Extraction boundary: one module per conjugate family would be a clean split, but the shared exponential-family derivation is what keeps them consistent — leave as-is.

Probabilistic-programming surface

mixle/ppl/inference.py (4,490)
  • Responsibilities: posterior inference for the PPL — conjugate, Laplace, VI, MCMC routes.

  • Stateful globals: several registries — _CONJUGATE, _CONJ_LOGM, _HIERARCHICAL, _PRIOR_DICT_BUILDERS, _EXPECTED_PRIOR_FAMILY (dispatch tables; the first thing to preserve).

  • Optional imports: torch.

  • Hot paths: seq_log_density, kernel (through the fitted posteriors).

  • Serialization: dist_to_encoder / seq_encode.

  • Extraction boundary: the conjugate-posterior registry (tables + their builders) is a clean module of its own, separable from the Laplace/VI/MCMC numeric routes — a real isolated-testing win. Move the registries as a unit so dispatch stays in one place.

mixle/ppl/core.py (3,317)
  • Responsibilities: the PPL authoring DSL (Field / Group / Net / Conv / Transformer) and the fitter registry.

  • Stateful globals: _ROUTE_CAVEATS, _CMP, _RESIDUAL plus the fitter registry populated by register_fitter.

  • Optional imports: torch.

  • Hot paths: forward, seq_log_density.

  • Extraction boundary: the neural predictors (_NeuralPredictor / Net / Conv / Transformer) are the torch-touching subset and a natural split from the pure-DSL field/group core.

Compiled-kernel code generation

mixle/stats/compute/fused_codegen.py (2,438)
  • Responsibilities: the fused-kernel source generator — per-family LeafTemplates (scalar, vector, matrix, tabulated, categorical, chain), plan analysis (analyze/fusible), source emission for the one-pass scorer and E-step (sequential and chunk-parallel prange variants), the secure disk-cached _njit loader, and the data/parameter marshalling that feeds generated signatures.

  • Stateful globals: the _TEMPLATES registry (append-ordered; matching is first-hit, so registration order is part of dispatch semantics) and the _COMPILED / _ESTEP_COMPILED kernel caches keyed by (plan.signature, parallel) — plus the on-disk module cache under MIXLE_FUSED_CACHE_DIR with its ownership/symlink checks.

  • Optional imports: numba — reached lazily inside _njit; importing this module must stay numba-free (the freeze-rollup resolver depends on that, guarded by HAS_NUMBA at its call site).

  • Hot paths: every generated _fused / _estep kernel; parity is pinned by generated_kernel_parity_test.py, fused_codegen_test.py, fused_parallel_test.py (ULP / bit-stability receipts), and fused_chain_test.py — treat template row/acc fragments as bit-checked and change them only with those suites green.

  • Serialization: none — kernels are runtime artifacts; the disk cache is a per-user performance cache keyed by source digest, never a schema surface.

  • Extraction boundary: the leaf templates (registrations plus their param/table builders) are the clean seam — they are data, not machinery, and moving them to a _templates module would halve the file without touching emission. The emitter + marshalling + cache trio is a single numerical unit; split only against a demonstrated defect.

Infrastructure and facade

mixle/lifecycle.py (1,702)
  • Responsibilities: the Model facade (fit/deploy/load, provenance and integrity checks) and propose — automatic model-family selection and its held-out verified-frontier scoring.

  • Stateful globals: none (module-level constants only).

  • Optional imports: none directly (delegates tabular record handling to mixle.data.sources.pandas_source, reached only when the caller’s data is already pandas).

  • Serialization: Model.deploy / Model.load — the JSON-first, pickle-fallback artifact format, including the model_sha256 integrity check and the _fit_provenance / _numerical_repairs envelope re-attachment on decode.

  • Extraction boundary: Model and propose are two largely independent halves sharing only the tabular-record helpers (_tabular_records et al.); a real seam, but split only if one half’s test surface needs isolating from the other’s.

mixle/inference/estimation.py (2,422)
  • Responsibilities: the optimize / fit / best_of front door — data encoding, model proposal handoff (_maybe_structured_model), the fused/standard EM drivers, and the schedule/objective plumbing.

  • Stateful globals: none (module-level constants only).

  • Optional imports: torch (gradient-fit path), reached lazily.

  • Hot paths: optimize’s fused-step loop (_local_fused_step / _engine_fused_step) — change with LL-parity tests against the standard loop.

  • Serialization: none directly (delegates to the model’s encoders).

  • Extraction boundary: the structure-proposal gate (_maybe_structured_model) and the EM-driver loops are separable seams; extract only to remove a demonstrated defect, per the audit’s own rule.

mixle/inference/glm.py (1,895)
  • Responsibilities: the GLM front door (glm) across the exponential-family link/family set and its robust standard-error variants (HC0-HC3, with their leverage/rank diagnostics), robust_regression (Huber/Tukey IRLS and its degenerate-fit breakdown disclosure), the penalized-regression trio (ridge_regression / elastic_net / lasso), and quantile_regression.

  • Stateful globals: none (module-level constants only – the floors/tolerances/fractions gating the HC2/HC3 leverage guard and the robust-regression breakdown disclosure; none mutable).

  • Optional imports: none.

  • Hot paths: glm’s IRLS loop and its HC2/HC3 leverage computation (rank-masking is parity-sensitive – see the campaign-eight/D-0208 fix to how it handles a rank-deficient design); robust_regression’s own IRLS loop and its breakdown-disclosure signals (_robust_weight_collapse / _robust_response_point_mass, also D-0208).

  • Serialization: none (GLMResult / RegressionFit / PenalizedResult are plain, non-round-tripped dataclasses).

  • Extraction boundary: robust_regression and its breakdown-disclosure helpers (_response_robust_scale through _robust_breakdown_explanation) are already a self-contained seam from glm, the penalized-regression trio, and quantile_regression. The file crossed 1,500 lines through three consecutive rounds of disclosure-heuristic repair on that one function family (D-0208’s own campaign-eight fix wave), not through unrelated growth elsewhere – extract only if that family needs an isolated test surface of its own, not as a line-count exercise.

mixle/stats/__init__.py (2,426)
  • Responsibilities: the mixle.stats facade — lazy __getattr__ re-exports, capability registration, and the load_models / dump_models model-collection serialization entry points.

  • Stateful globals: _INTERNAL_SUFFIXES plus the lazy-export and capability tables.

  • Optional imports: none (that is the point — the facade must import in a bare environment).

  • Serialization: to_json / from_json (load_models / dump_models).

  • Extraction boundary: the builtin-compute-metadata and lazy-capability registration blocks (_register_builtin_compute_metadata / _register_lazy_module_capabilities) can move to a _registration helper, leaving __init__ as thin re-export glue. Low risk, improves import readability; do it when the registration tables next change.

mixle/utils/automatic/profiling.py (3,301)
  • Responsibilities: automatic-modeling data profiling — marginal/pairwise structure detection.

  • Stateful globals: tuning constants _MIXTURE_EM_CAP, _NUMERIC_MODEL_MARGIN_BITS, GOF_ABSTAIN_PVALUE (thresholds, not mutable state).

  • Optional imports: none.

  • Extraction boundary: the goodness-of-fit / entropy scoring helpers are separable from the profile dataclasses; a defect-neutral tidy, not urgent.

mixle/utils/parallel/planner.py (2,345)
  • Responsibilities: device/placement/sharding planning and the encoded-data backend registry.

  • Stateful globals: the encoded-data backend registry (via register_encoded_data_backend).

  • Optional imports: dask, torch.

  • Serialization: to_json / from_json (placement/plan round-trip — schema-visible).

  • Extraction boundary: the encoded-data backend registry and its backends are separable from the placement/calibration solver; a real seam, but touch it only with a distributed-path change.

mixle/stats/compute/declarations.py (2,303)
  • Responsibilities: distribution declarations (parameter/statistic specs, exponential-family specs) and the Numba-lowering validation.

  • Stateful globals: _NUMBA_INFIX_OPS, _NUMBA_FUNC_OPS, _KNOWN_PARAMETER_CONSTRAINTS, _SHARED_STACKED_PARAMETER_CONSTRAINTS (lowering tables).

  • Optional imports: none.

  • Extraction boundary: the Numba-lowering tables and their validation are separable from the spec dataclasses; split when the lowering rules next grow.

mixle/stats/compute/pdist.py (2,068)
  • Responsibilities: the root contracts — the six-type ABCs, DensitySemantics, and the error types (ContractError, EnumerationError, KeyValidationError).

  • Stateful globals: SS alias and _KEY_ATTRS.

  • Optional imports: inspect and mixle submodules, deferred to avoid import cycles.

  • Hot paths: none of its own; it defines the signatures every hot path implements.

  • Serialization: from_json and the dist_to_encoder / seq_encode contract itself.

  • Extraction boundary: do not split. Nearly every module in the tree imports this one, so a split multiplies the import-cycle surface that doe_task_models_clean_import_test guards. The error classes could move if that test ever forces it, and nothing else should.

mixle/stats/compute/fused_kernels.py (1,803)
  • Responsibilities: fused Numba kernels for mixture estimation over heterogeneous data, and the per-leaf builders that generate them.

  • Stateful globals: _BUILDERS (the leaf-builder registry) and _NEG_INF.

  • Optional imports: Numba via the compute layer, scipy, concurrent (parallel build), os.

  • Hot paths: build_kernel, seq_log_density, seq_component_log_density, and the _pair_kernels / _sequence_kernel / _optional_kernel generators.

  • Serialization: CompiledEncoding is the compiled-payload boundary, not a schema surface.

  • Extraction boundary: the _LeafBuilder subclasses are one clean seam per leaf family and the registry makes the split mechanical; the codegen core should stay whole.

mixle/ppl/field.py (1,931)
  • Responsibilities: latent Gaussian fields observed through many proxies — kernels (RBF/Matern/great-circle/anisotropic), the field system, proxy likelihoods, and posterior readout.

  • Stateful globals: _SPARSE_SOLVE_DETECTORS (a detector registry).

  • Optional imports: torch and scipy-backed sparse solves, both reached lazily.

  • Hot paths: kernel assembly and the sparse/dense posterior solve.

  • Serialization: field and proxy parameters round-trip through the owning system.

  • Extraction boundary: the FieldKernel hierarchy is fully separable from FieldSystem and the Proxy family; a genuine three-way seam, and the first place to split if this module grows.

mixle/relations.py (1,825)
  • Responsibilities: relations over structured spaces enumerated by residual — shortest path, assignment, spanning tree, edit distance, Viterbi path, best-subset regression.

  • Stateful globals: none.

  • Optional imports: scipy (sparse/optimization routines), lazily.

  • Hot paths: each relation’s enumeration loop; the residual ordering is the shared contract.

  • Serialization: Solution / Design carry the round-tripped state.

  • Extraction boundary: one module per relation is a clean, obvious split, held together only by the shared Relation base and its ordering guarantee. Defect-neutral; do it when one grows.