Skip to content

Configurable RMS computation timing within CAVI cycle #55

@jc-macdonald

Description

@jc-macdonald

Summary

RMS (reconstruction error) is currently computed at a single fixed point in the coordinate-ascent cycle: after the scores and loadings updates, but before the noise variance update, using the new A and S but the old μ. This asymmetry is the root cause of the period-2 oscillation documented in _rms_oscillation_trace.py.

This issue adds a configurable option to control where in the CAVI cycle RMS is evaluated, enabling systematic comparison in Tier 1 of the convergence design matrix (Factor 5).


Current behavior (T0)

RMS is computed in _converge.py::_log_and_check_convergence(), which is called from the main iteration loop after scores + loadings + bias + noise updates. However, the RMS value passed to this function is computed earlier — after S and A are updated but before the noise variance V is updated (see _full_update.py). This means:

  • S: current iteration (new)
  • A: current iteration (new)
  • μ: current iteration (new, but computed from previous iteration's err_mx)
  • V: previous iteration (old)

The μ update uses err_mx which was computed with the previous iteration's μ, creating a one-step lag that generates the 2-cycle.


Proposed timing variants

ID When RMS is computed What's new vs. old Notes
T0 After S+A+μ, before V (current) S,A,μ new; V old Asymmetric — causes oscillation
T1 After full cycle (S, A, μ, V) All parameters at iteration-end Symmetric — likely eliminates oscillation
T2 Average of pre- and post-cycle Period-2 average Equivalent to smoothed RMS but computed differently
T3 After A only (before μ and V) S,A new; μ,V old Isolates loadings convergence

What to add

  • New option rms_timing: str with values "default" (T0), "end_of_cycle" (T1), "average" (T2), "after_loadings" (T3). Default: "default" (no behavior change).
  • Modify the RMS computation call site in _full_update.py (or the main loop in _pca_full.py) to compute RMS at the selected point.
  • For T1: move/defer the RMS call to after the noise variance update.
  • For T2: compute RMS both before and after the full update; store the average.
  • For T3: compute RMS after loadings but before bias.
  • Store the timing variant used in lc["_rms_timing"] for trace provenance.

Estimated scope

  • ~80–120 lines in _full_update.py / _pca_full.py (restructure the RMS call sites)
  • ~20 lines in options defaults
  • ~150–200 lines of tests (verify each timing variant produces different traces on the known-oscillating dense case, verify T1 eliminates oscillation)
  • Total: ~250–350 lines

Design matrix references

  • Factor 5: RMS computation timing (T0–T3)
  • Tier 1: Oscillation root cause (24 runs)

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    algorithmAlgorithm implementation or extensionfeatureNew feature or capability

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions