Skip to content

Releases: imewei/xpcsjax

v0.1.8

Choose a tag to compare

@imewei imewei released this 16 Sep 23:39

codebase-review hardening: data-integrity fixes, covariance-contract unification, dead-code cleanup

  • Mode-agnostic fit-quality metric nrmse surfaced in nlsq_diagnostics["fit_quality"] / nlsq_result.json.
  • Homodyne and heterodyne uncertainties now follow one covariance contract on every solve path — placeholder/singular covariance reports NaN with covariance_is_placeholder instead of a fabricated zero, identity, or floored value.
  • Data-integrity fixes: APS-U bin-skip index mislabeling, NPZ cache keyed to its source HDF5 file, homodyne angle-stratified fits no longer fit the c2 diagonal.
  • ~4K lines of dead code removed (performance_engine.py, memory_manager dead methods, unreachable ImportError fallback shims) with no numerical or CLI-facing behavior change.

Full details in CHANGELOG.md.

v0.1.7

Choose a tag to compare

@imewei imewei released this 05 Sep 14:05

xpcsjax v0.1.7

NLSQ floor raised to nlsq>=0.7.6 across every pin site (pyproject.toml,
.pre-commit-config.yaml, the runtime dependency check, both conda
recipes, and the docs that quote it). conda-recipe/nlsq updated to the
0.7.6 sdist; upstream changed neither the dependency set nor the
entry points, so the recipe's run deps are unchanged.

make verify now reports zero warnings (was 10): three non-actionable
upstream nlsq warnings are filtered at the narrowest honest scope.

Verified: make verify (2670 passed), strict sphinx-build -W, and
make verify-nlsq all clean at 0.1.7.

v0.1.6

Choose a tag to compare

@imewei imewei released this 31 Aug 00:09

[0.1.6] - 2026-08-30

Fixed

  • Negative-dominant D_offset now detected in physics validators. The
    D_offset/D0 overfitting checks only compared ratio > 0.5, missing the
    symmetric case where a large-magnitude negative offset dominates D0
    (e.g. D_offset_ref/D0_ref = -0.503 in a real C045 two_component fit,
    implying a negative diffusion coefficient at early times). Both checks now
    compare abs(ratio) > 0.5 (#75).
  • Resolved mypy hard-gate failures on main (CI run 33300386957): explicit
    type annotations for union-typed locals in fallback_chain.py,
    recovery.py, and validation.py, plus a new AngleGroup TypedDict in
    chunking.py replacing an overly-loose dict[str, Any] (#73).
  • Duplicate HeterodyneModel class name. Two unrelated classes shared
    the name — core/heterodyne_model.py's PhysicsModelBase adapter and
    core/heterodyne_model_stateful.py's public stateful model — so
    from xpcsjax.core.heterodyne_model import HeterodyneModel and
    from xpcsjax.core import HeterodyneModel silently returned different
    classes. Renamed the adapter to HeterodynePhysicsAdapter across every
    real call site; the public xpcsjax.core.HeterodyneModel export is
    unaffected (#71).
  • Stratified-LS (>=1M point) diagonal-exclusion mask compared indices
    into two independently-built np.unique() arrays instead of the actual
    t1/t2 values, wrongly zeroing real off-diagonal points and keeping
    true diagonal ones (#71).
  • Hybrid-streaming per_angle_mode="constant" fallback index
    misalignment.
    In the quantile-estimation-failure fallback, L3's
    regularization group indices and L4's gradient-collapse-monitor watched
    indices were resolved via the literal per_angle_mode_actual string
    instead of the real 2-param [contrast_mean, offset_mean, *physical]
    layout the fallback actually uses, watching the scaling head as if it
    were physics (#71).
  • coerce_finite_float no longer silently coerces a stray YAML boolean
    (e.g. a typo'd max: true) to 1.0/0.0 at the shared bounds/values
    validation boundary (#71).
  • Degenerate 1-frame Siegert-ceiling check no longer reads the excluded
    tau=0 diagonal spike.
    The matrix.shape[1] <= 1 fallback branch in
    data/filtering_utils.py and data/validation.py read diagonal[0]
    into the check instead of skipping it, reintroducing the anti-pattern the
    surrounding code otherwise avoids (#71).
  • 19 correctness gaps closed in the NLSQ fitting workflow and 6 more across
    config/core/viz, found via independent step-by-step audits that traced the
    NLSQ workflow and docs/diagrams/architecture.md against the real code.

Removed

  • ~7800 lines of confirmed-dead code removed repo-wide (ponytail-audit).

Documentation

  • Config examples across the documentation now match the shipped YAML
    templates exactly — bounds format, key nesting, and the heterodyne
    physics/scaling parameter layout — instead of several invented shapes
    that a reader could not actually load (#70).
  • Theory prose terminology and notation cleaned up: dropped the "diffusion
    integral" label for D(t1,t2) (referred to by symbol only, alongside
    J(t)), and aligned Sphinx theory notation with the README (#69, #70).

Dependencies

  • nlsq bumped 0.7.1 → 0.7.4 and evosax bumped 0.2.0 → 0.3.1 in
    uv.lock and the conda recipe (conda-recipe/xpcsjax/meta.yaml); host
    setuptools floor raised to >=77 to match evosax 0.3.1's build
    requirement (#74).

v0.1.5

Choose a tag to compare

@imewei imewei released this 25 Aug 08:26

[0.1.5] - 2026-08-25

Added

  • fixed_parameters / active_parameters support across every NLSQ execution
    tier
    (#55, #57, #59). A user can now freeze named physical parameters or
    restrict the optimized set for NLSQWrapper.fit(), NLSQAdapter.fit(), the
    CMA-ES escape, and the sequential, out-of-core, stratified-LS, and
    hybrid-streaming tiers alike — resolve_optimized_physical_parameters computes
    a single mask-based strip/restore descriptor threaded through all of them, so
    the free-parameter subset stays consistent regardless of which tier a fit
    dispatches to. Unknown names in either config key raise ValueError instead of
    silently narrowing or ignoring the request.
  • tied_parameters is now homodyne-invalid, not silently ignored. Homodyne's
    ParameterSpace/ParameterManager have no tie/DOF-reduction mechanism (it is
    a two_component-only feature); configuring tied_parameters on
    static_anisotropic/static_isotropic/laminar_flow now raises ValueError
    at construction instead of silently producing two fully independent free
    parameters.
  • Heterodyne hybrid-streaming L2 now reports a real Hessian-based covariance
    (2*sigma^2*H^-1, pseudo-inverse fallback on a singular Hessian) in place of
    the previous identity placeholder, mirroring the laminar_flow fix. Falls back
    to identity with covariance_is_placeholder=True only when the Hessian itself
    is non-finite.

Fixed

  • tied_parameters alias/canonical name collisions are now rejected, not
    silently last-writer-wins
    , across six config-load sites: the heterodyne
    _apply_tied_parameters, _apply_initial_parameters, _apply_fixed_parameters,
    _apply_parameter_space_bounds, and the homodyne ParameterManager's bounds
    loader (#67). A config that names both a parameter's public alias (e.g.
    v_beta) and its internal canonical name (beta) as separate entries — for
    example tied_parameters: {beta: v_beta, v_beta: D0_ref} — previously
    collapsed to whichever entry a dict comprehension processed last, dropping one
    tie/bound/fixed-value with no diagnostic; all now raise ValueError on
    collision.
  • active_parameters typos now raise, matching fixed_parameters.
    resolve_optimized_physical_parameters validated fixed_parameters names
    strictly but had no equivalent check for active_parameters, so a typo'd name
    silently narrowed the optimized set instead of erroring.
  • Heterodyne stratified-LS no longer attempts a doomed OOM allocation before
    falling back to the Layer-2 escape.
    At per_angle_mode=individual with
    enough angles, the unconditional baseline trf solve's dense Jacobian sizing
    didn't account for the per-angle scaling tail, so a fit that was always going
    to need the hierarchical escape first tried (and failed) a multi-tens-of-GB
    allocation, wasting time and logging a misleading error. A pre-flight memory
    estimate now skips straight to the escape when the baseline solve would exceed
    budget and the escape can pick up from p0_full anyway.
  • Cached t1/t2 arrays are now always recomputed from the current dt
    convention
    instead of trusted verbatim from a .npz cache. The dt
    fingerprint check only fired when the cache carried dt metadata, so a
    cache built by an older xpcsjax version or an external converter (no dt key)
    passed silently and could desync frame-0 alignment against a cache-free run.
  • Scalar per_angle_scaling contrast/offset values are no longer dropped.
    get_initial_parameters() only injected list-typed contrast/offset values,
    so a two_component config writing a bare scalar (documented as equivalent to
    a length-1 list) hit a KeyError in simulated-data plot rendering; scalars are
    now normalized to length-1 lists at both injection sites.
  • CI stability: retry workflow hardened against starvation and
    false-positive cancellations, the runner-cancellation label mismatch (GitHub
    reports a runner-killed step as failure, not cancelled) is now handled,
    and the Ubuntu test matrix is sharded to cut per-shard wall time (#65, #66).
  • NLSQAdapter's real-solve path (fit_nlsq_jax(use_adapter=True)) is
    repaired.
    _flatten_xpcs_data didn't handle the standard raw-loader data
    shape (shared 1D marginal t1/t2 time vectors plus a phi array of
    unique angles), and a separate mismatch between nlsq's own internal input
    validators over 2-D xdata orientation made every real solve through this
    path fail and silently fall back to NLSQWrapper. Both are fixed; the
    adapter path now completes a genuine solve.

v0.1.4

Choose a tag to compare

@imewei imewei released this 13 Aug 10:24
fa47ad1

Fixed

  • nlsq_result.npz now carries c2_exp/c2_fitted/residuals/t1/t2/phi_angles/wavevector_q.
    save_results_npz previously wrote only scalars, parameters, and
    covariance — the raw (n_phi, n_t1, n_t2) correlation surfaces (and the
    scattering wavevector needed to interpret them) a user needs for
    downstream re-analysis were only ever written to
    plots/simulated_data/c2_fitted_data.npz, and only when plotting was
    enabled. service/persist.py::merge_fitted_c2() now folds those
    already-computed arrays into the primary NPZ (best-effort, mtime-guarded
    against stale leftovers from a previous run in the same output directory)
    after plotting runs, for both the CLI and the GUI worker.
  • Whole-codebase module-review sweep: 18 findings fixed across 12 modules
    (2 confirmed HIGH blockers + 16 advisory). Highlights: ParameterSpace.from_config
    crashed on a blank parameter_space: YAML section; public xpcsjax.HeterodyneModel
    resolved to the no-arg PhysicsModelBase adapter instead of the documented
    stateful model used by every production heterodyne path and the test suite.
    Advisory fixes span CLI persistence logging, config parameter-name aliasing,
    path-traversal-check de-duplication, an OOM-risk unbounded residuals scatter
    plot, and more.

v0.1.3

Choose a tag to compare

@imewei imewei released this 10 Aug 05:43
11279ca

Fixed

  • Deep-RCA whole-codebase debug audit: 21 confirmed bugs across 24 files (#49, #50). Highlights:
    • wrapper.py / strategies/stratified_ls.py: a DOF-correction truthiness bug understated reduced-chi2/quality_flag when the anti_degeneracy config section is absent but averaged/individual scaling is still active.
    • heterodyne_core.py: the CMA-ES warm-start auto-skip compared raw (non noise-normalized) SSR/dof against a threshold meant for noise-normalized reduced chi2, silently defeating the global escape on genuinely poor basins; added a missing weights/data shape guard in _fit_cmaes.
    • jax_backend.py: validate_backend()'s self-test used jax.grad on a matrix-output function, so gradient_support was always False even on a working JAX install; fixed to use jacobian, resolving the downstream always-ImportError fallout in model_mixins.py.
    • anti_degeneracy_controller.py: Layer 2 (HierarchicalOptimizer) was constructed mode-blind, reporting active even in averaged/constant mode where its hard-coded individual-mode layout is unusable; construction is now gated on the resolved per-angle mode.
    • hierarchical.py: outer-loop convergence was gated on absolute parameter change instead of the already-computed relative change, making convergence unreachable for parameters spanning 6+ orders of magnitude.
    • adaptive_regularization.py: the CV-safe-divide fix sanitized the numerator as well as the denominator, silently changing the near-zero-mean fallback value and killing the L3 regularization penalty exactly where it is needed; corrected to sanitize only the denominator (applied to all three CV sites).
    • strategies/sequential.py: combine_angle_results used a scalar per-angle weight for combined_params while combined_cov used an unmasked per-parameter inverse-variance weight, an internally inconsistent pairing; both now share the same masked/floored weight matrix.
    • data/performance_engine.py: MultiLevelCache disk writes were not serialized per key (unsynchronized "wb" opens race); fixed via atomic temp-file + os.replace.
    • data/memory_manager.py: cleanup_virtual_memory() deleted files by a shared filename-prefix scan instead of tracking self-created files, risking deletion of another live instance's mmap-backed file.
    • config.py: NLSQConfig.from_dict() crashed (instead of degrading to default) on an explicit YAML null for non-Optional numeric fields.
    • GUI: project_panel.py / result_presenter.py rendered a NaN parameter with the same "—" sentinel as "run has no such parameter," silently defeating the comparison-view diff marker; both now render "NaN" distinctly.
    • Smaller fixes: an unguarded end_frame KeyError in HeterodyneModel.from_config, an unbounded lscpu subprocess call (hang risk) in device/cpu.py, an uncommented empty except FileNotFoundError flagged by CodeQL, and unlogged fast_chi2_mode subsampling in out_of_core.py.
    • Verification: ruff clean, mypy clean (0 issues, 198 files), full suite green (2628 passed, +6 new regression tests, 0 failures).

Full Changelog: v0.1.2...v0.1.3

v0.1.2

Choose a tag to compare

@imewei imewei released this 06 Aug 13:47
de8c773

What's Changed

  • docs: modernization assessment for xpcsjax by @imewei in #6
  • build: repo-level write-time Python quality gate (Plankton-equivalent) by @imewei in #7
  • docs: add module map to root CLAUDE.md by @imewei in #9
  • fix: bundle Pillow in the PyInstaller spec, close drift-guard gap by @imewei in #10
  • docs: scaffold Matt Pocock engineering-skills config by @imewei in #8
  • fix(gui): address design-critique findings on the workbench by @imewei in #11
  • docs(graphify): update knowledge graph (66 files changed) by @imewei in #12
  • fix(gui): resolve 6 click-path-audit state-conflict bugs + doc sync by @imewei in #13
  • fix: whole-codebase debug audit — 30 confirmed bugs across 11 modules by @imewei in #14
  • fix: 6 remaining debug-audit findings (NaN-q, diagonal-correction skip, fallback escalation, sigma-weighting, cmaes_seed, negative-correlation repair) by @imewei in #15
  • release: bump version to 0.1.2 by @imewei in #16
  • docs: add missing API pages for device/io/utils modules by @imewei in #18
  • fix: codex+agy multi-tool audit — 145 confirmed bugs across all 12 modules by @imewei in #19
  • test: close 4 of 5 remaining test-coverage gaps from debug-audit review by @imewei in #17
  • test: close heterodyne streaming auto_tune_lambda test-coverage gap by @imewei in #21
  • fix: drop extra solver-space arg from adjust_covariance_for_transforms call by @imewei in #22
  • fix: silence redundant JAX_PLATFORMS warning in device.cpu by @imewei in #23
  • fix: seed heterodyne multistart candidates into the frozen initial-values snapshot by @imewei in #24
  • fix: don't report converged/good for a CMA-ES escape kept via refinement-only success by @imewei in #25
  • chore(deps): drop scikit-learn, cloudpickle, interpax — never imported by @imewei in #26
  • feat(nlsq): tied_parameters equality-constraint support for heterodyne two_component by @imewei in #27
  • refactor(heterodyne): consolidate _decompose_chi2_per_angle import (TODO(C3)) by @imewei in #28
  • fix: resolve CodeQL alerts #352-357 by @imewei in #29
  • docs: document heterodyne tied_parameters config feature by @imewei in #30
  • fix(heterodyne): correct chi2/scaling/config bugs in two_component fit path by @imewei in #31
  • refactor: remove 17 dead-code symbols (graphify-guided sweep) by @imewei in #32
  • fix(data): break AdvancedMemoryManager<->MemoryPressureMonitor ref cycle by @imewei in #33
  • fix(core): correctness sweep across xpcsjax/core/ (6 bugs) by @imewei in #34
  • fix(data): deep-RCA multi-agent audit — 12 confirmed production bugs by @imewei in #35
  • fix(silent-failures): close 4 High + 9 mechanical findings from full-codebase review-pr sweep by @imewei in #36
  • fix(optimization): deep-RCA multi-agent audit — 29 confirmed bugs in xpcsjax/optimization/ by @imewei in #37
  • fix(optimization): close PR #37 review findings (4-agent review-pr sweep) by @imewei in #38
  • fix(review): close 15 confirmed bugs from whole-codebase code-reviewer sweep by @imewei in #39
  • fix(data): use with-statement for XPCSDataLoader (CodeQL #360) by @imewei in #40
  • docs: add missing make test-viz to commands reference by @imewei in #41
  • fix(test): skip killpg-race test on non-POSIX platforms by @imewei in #42
  • fix: deep RCA sweep across optimization, io, core, data, device by @imewei in #43
  • build: add conda-forge-style recipes for imewei anaconda.org channel by @imewei in #44
  • docs: add conda/mamba channel install instructions by @imewei in #45
  • chore: bump config-schema version to 0.1.2 by @imewei in #46
  • Fix GUI fit-time warnings: param labeling, XLA flag timing by @imewei in #47

Full Changelog: v0.1.1...v0.1.2

v0.1.1

Choose a tag to compare

@imewei imewei released this 27 Jun 01:16

Maintenance release. No user-facing behavioural or API changes — fitting results, the public API, and config formats are identical to v0.1.0.

Changed

  • System memory detection in the optimization memory-strategy layer unified and simplified into a single detection path (behaviour-preserving).
  • Removed obsolete adapter metadata methods that were no longer referenced by any live path.

Internal / CI

  • GitHub→GitLab mirror workflow (wchen/xpcsjax) via .github/workflows/mirror.yml.
  • Repinned pypa/gh-action-pypi-publish to a valid v1.14.0 commit SHA in the release workflow.
  • Rebuilt the graphify knowledge graph and refreshed README badges.

Publishing this release uploads the sdist + wheel to PyPI via Trusted Publishing (OIDC).

Full changelog: https://github.com/imewei/xpcsjax/blob/v0.1.1/CHANGELOG.md

v0.1.0

Choose a tag to compare

@imewei imewei released this 23 Jun 16:18
35346d5

xpcsjax v0.1.0 — unified homodyne + heterodyne NLSQ fitting (JAX-native, NLSQ-only, float64, CPU).

Added

  • Initial consolidated release. xpcsjax v0.1 ports the homodyne and
    heterodyne NLSQ pipelines into a single JAX-native package.
  • Unified public API — seven lazy-loaded symbols:
    xpcsjax.data.xpcs_loader.load_xpcs_data,
    xpcsjax.optimization.nlsq.fit_nlsq,
    xpcsjax.config.ConfigManager,
    xpcsjax.core.HomodyneModel,
    xpcsjax.core.HeterodyneModel,
    xpcsjax.optimization.nlsq.results.OptimizationResult,
    xpcsjax.viz.nlsq_plots.generate_nlsq_plots.
  • JAX-first with float64. JAX_ENABLE_X64=1 is set at package
    import time; parameters span 6+ orders of magnitude and float32 is
    unsafe.
  • Homodyne parity oracle. Characterisation tests pin xpcsjax's
    homodyne output to upstream homodyne results at rtol=1e-10.
  • Heterodyne multi-angle. Joint fitting across φ angles with
    χ²-exact residuals using the per-angle scaling layouts constant /
    individual / auto (auto resolves to averaged at n_phi ≥ 3,
    else individual); returns a single OptimizationResult.
  • NLSQ engine split. xpcsjax owns strategy routing, the 5-layer
    anti-degeneracy controller, CMA-ES escape, LHS multistart,
    angle-stratified chunking, and shear weighting. NLSQ owns the
    CurveFit JIT cache and the trust-region solve.
  • Anti-degeneracy controller with five composable layers: per-angle
    reparameterisation, hierarchical optimisation, adaptive
    cross-validation regularisation, gradient-collapse monitoring, and
    shear-sensitivity weighting.
  • Memory-aware strategy selection via
    xpcsjax.optimization.nlsq.select_nlsq_strategy — picks between
    in-memory, hybrid-streaming, and out-of-core paths based on dataset
    size and available RAM. (Angle-stratified least squares is a separate
    ≥1M-point dispatch path.)
  • Visualization module (xpcsjax.viz) — three public plot
    functions (plot_nlsq_fit 3-panel comparison, plot_residual_map
    4-panel diagnostic, plot_simulated_data single-panel theoretical
    heatmap), orchestrated by generate_nlsq_plots. Artifacts are
    serialised as LZMA-compressed NPZ + JSON under
    output_dir/simulated_data/. Optional Datashader fast path (5–10×
    per-call speedup; install via pip install 'xpcsjax[viz-fast]')
    with transparent matplotlib fallback. Parallel multi-process
    rendering via multiprocessing.Pool(spawn).
    xpcsjax.viz.diagnostics.compute_diagonal_overlay_stats extracts
    the t₁ = t₂ diagonal from experimental and fitted c² surfaces.
  • Desktop analysis workbench (GUI) (xpcsjax/gui/). A PySide6 graphical
    front-end registered as the xpcsjax-gui / xj-gui console script
    (xpcsjax.gui.app:main; recognises --help / --version, forwards the rest
    to Qt). Config tab (form + raw-YAML + live JAX-free validation), Data tab
    (h5py-only HDF5 browser + two-time C₂ preview), Fit tab, Inspector dock
    (params / uncertainties / diagnostics), live-diagnostics view (SSR curve,
    L1–L5 layer chips, banner log), interactive PyQtGraph plots, and a
    datasets→runs project sidebar with side-by-side comparison. Sessions persist
    to .xpcsproj JSON (atomic writes, per-run output dirs).
    Architectural invariant: the GUI process never imports JAX — every fit
    runs in a separate spawn worker (xpcsjax/gui/ipc/) that lazily imports
    JAX + the service layer and streams structured FitEvents back to the UI.
    Optional install: pip install -e ".[gui]". PyInstaller freeze support via
    .[packaging] + packaging/xpcsjax-gui.spec (one-dir bundle,
    multiprocessing.freeze_support() first). Documentation:
    docs/source/user_guide/gui.rst.
  • Headless core-service layer (xpcsjax/service/). The argparse-free,
    Qt-free orchestration seam shared by the CLI and the GUI worker:
    service.config (JAX-free load_config / validate_config / available_modes
    / template_dict), service.events (JAX-free streamed FitEvent schema),
    and the worker-side service.data / service.fit / service.plots, plus
    service.persist (result serialisation, relocated from cli.result_saving).
    Documentation: docs/source/api/service.rst.
  • Command-line interface (xpcsjax/cli/). Console scripts registered in
    pyproject.toml, each with an xj short alias:
    • xpcsjax / xj — single flat flag-driven command for NLSQ fits;
      --plot-experimental-data / --plot-simulated-data switch to a
      standalone-plot path that skips optimisation.
    • xpcsjax-config / xj-config — generate, --show-template,
      --validate, or --interactively build a YAML config from the four
      mode templates.
    • xpcsjax-config-xla / xj-config-xla — inspect/print the CPU
      XLA_FLAGS.
    • xpcsjax-validate / xj-validate — validate the installation.
    • xpcsjax-post-install / xpcsjax-cleanup (+ xj- aliases) — install
      and remove shell completion + XLA activation scripts.
    • xjexp / xjsim — plotting shortcuts (experimental QC / simulated C₂),
      mirroring upstream heterodyne's hexp / hsim.
  • Runtime utilities (xpcsjax/runtime/). System validator
    (xpcsjax.runtime.utils.system_validator) checking environment, dependency
    versions, JAX/float64 config, and template / public-API integrity
    (NLSQ-only — no Bayesian/MCMC probes), plus the bash/zsh/fish completion and
    XLA activation shell assets under xpcsjax.runtime.shell.
  • Documentation: docs/source/user_guide/cli.rst, docs/source/api/cli.rst,
    and docs/source/api/runtime.rst.
  • Heterodyne joint global escapes (parity gap C closed). The joint
    CMA-ES (enable_cmaes=True) and joint multistart (multistart=True)
    escapes in heterodyne_core.py are now real global escapes over the full
    [physics | scaling] vector — seed-pinned, keep-better vs. the plain NLSQ
    joint fit, and best-effort fall back on failure (reusing the shared
    fit_with_cmaes / run_multistart_nlsq). Escape results are tagged
    nlsq_diagnostics["global_escape"] and carry NaN covariance /
    uncertainties with n_iterations=0 by construction. See
    docs/source/theory/heterodyne_anti_degeneracy.rst.
  • Dedicated heterodyne logging module
    (optimization/nlsq/heterodyne_logging.py). The two_component core and
    stratified-LS paths now route progress/banner logging through it for
    structured-logging parity with the laminar_flow pipeline (resolves the
    multi-minute stratified-LS silence).

Changed

  • Heterodyne streaming anti-degeneracy (parity gap D closed). The
    two_component STREAMING tier no longer freezes the quantile-estimated
    per-angle scaling. It now optimizes the scaling tail (contrast + offset)
    and runs L1–L4, reaching mechanism parity with laminar_flow streaming.
    The scaling treatment is selected by anti_degeneracy_config.per_angle_mode,
    with "auto" as the default — including when anti_degeneracy_config is
    absent/None (no "freeze when unconfigured" special case). "auto"
    resolves to auto_averaged at n_phi ≥ constant_scaling_threshold
    (default 3), else individual; per_angle_mode="constant" is the explicit
    frozen-scaling opt-out. See
    docs/source/theory/heterodyne_memory_strategy.rst.

  • Symmetric anti-degeneracy diagnostics. Both laminar_flow and
    two_component now emit the same top-level nlsq_diagnostics activation
    keys (hierarchical_active, regularization_active, shear_weighting,
    plus gradient_monitor when L4 ran) via the shared
    assemble_anti_degeneracy_diagnostics across every dataset-size path.
    shear_weighting is reported inactive for heterodyne by design (L5 is
    laminar_flow-only).

  • Deprecation — analysis_mode taxonomy. The bare value
    analysis_mode: static is deprecated. It was ambiguous between
    static_isotropic (angle-collapsed) and static_anisotropic
    (angle-resolved) and silently collapsed downstream. The canonical set
    is now exactly four modes:

    • static_isotropic
    • static_anisotropic
    • laminar_flow
    • two_component (with heterodyne accepted as a case-insensitive
      synonym, normalised to two_component at config load time)

    ConfigManager._normalize_analysis_mode still accepts the legacy bare
    value but rewrites it to static_anisotropic (the drop-in replacement
    that preserves angle resolution) and emits a deprecation warning at
    config-load time. Migrate old configs to one of the canonical modes to
    silence the warning.

    See docs/source/user_guide/analysis_modes.rst for the full mode
    reference.

Removed

  • Per-angle-mode unification (Phase 7) — the fourier per-angle scaling
    mode is gone.
    Both laminar_flow and two_component now expose a single
    canonical vocabulary of three per-angle scaling layouts — constant,
    averaged, and individual (resolved from the auto default) — across all
    eight execution paths. The Fourier-reparameterized scaling tail and its
    independent sibling were deleted along with the
    optimization/nlsq/fourier_reparam.py module, the FourierReparameterizer
    class, the fourier_order / fourier_auto_threshold config keys (all four
    mode templates plus the homodyne and heterodyne config dataclasses), and the
    fourier_basis_dim diagnostics key. Breaking: configs that set
    per_angle_mode: fourier (or independent) are rejected at resolve time —
    use individual for free per-angle scaling or averaged for the
    angularly-averaged two-DOF tail. Verified no-worse-SSR on the laminar
    CMA-ES / stratified / streaming paths; the homodyne rtol=1e-10 parity
    baselines were regenerated against explicit individual.
  • Dead-code cleanup — removed code that was unreachable, superseded, or
    never wired into the ...
Read more