CaltechExperimentalGravity / CaltechExperimentalGravity/system_ident

Stage D: CDSBackend.read() invariants — never synthesise X, never GPS-align

Open
#14 1 comment 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Python
Stars
0
Forks
1
PR merge metrics
No merged PRs in 30d

Description

**Stage D (spec §2.1, §4.2).** The most important correctness decision in the campaign, and it
is the *opposite* of the obvious design.

[`src/system_ident/loop.py:383`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L383) `_estimate_tf_periodic` reshapes on the assumption that sample 0 is a
period boundary (`P = len(x) // nperseg`, [`src/system_ident/loop.py:408`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L408)). The obvious conclusion —
that `read()` must GPS-align the buffer — is **wrong**. The estimator is a ratio of averages,
`H = mean_p(Y_p) / mean_p(X_p)` ([`src/system_ident/loop.py:432`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L432)), so a common time shift multiplies
X and Y by the same phasor and **cancels identically**.

Measured max in-band relative FRF error:

| offset | X = real readback | X = stashed drive array |
|---|---|---|
| aligned | 7.4e-12 | — |
| 1 sample | 7.3e-12 | **1.96e-1** |
| 37 samples | 1.7e-12 | **2.00e+0** |
| 511 samples | 5.4e-12 | **2.00e+0** |
| 4096 (= 4·nperseg) | 9.9e-13 | 9.9e-13 |

Offset is irrelevant with a readback. Synthesising X destroys the FRF — **200% error** — for any offset
that is not an exact multiple of `nperseg`. A constant *fractional*-sample offset behaves the same way and
also cancels with a readback X; only time-*varying* drift bites. And per the coherence-blindness issue,
none of this shows up in `H_err` or coherence.

### The invariants

1. **Never synthesise X.** For the excitation / drive-monitor channel, return real `getdata` samples from
the **same buffer as Y**, and **raise** if a requested channel is absent from the result. (`_EXC` is a
testpoint rather than a recorded channel at many sites; the sibling project read it successfully at the
40m, but that is a site fact, not a guarantee.)
2. **No rolling, trimming or GPS alignment.** Return the raw buffer. Computing the offset from
`((start_time - start_gps) * fs) mod nperseg` is not even reliably possible: `gpstime.now().gps()` is
truncated to `int` at the injection site, `getdata`'s `start_time` is the NDS buffer's own boundary
(integer-second aligned in practice, but that is an implementation detail, not a contract), the AWG's
start latency relative to `start_gps` is unspecified, and resampling introduces the rate ratio. It
would be a correction whose failure mode is a silent 200% error.
3. **Nothing staged → the quiet/background read**, matching the sibling project's `excitation is None`
early return. This is what [`src/system_ident/loop.py:145`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L145) does for `Pyy`.
4. **Assert, loudly:** `duration` rounds to an integer second (`getdata` takes ints, but
`total_dur = T_perseg * n_seg` at [`src/system_ident/loop.py:97`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L97) is a float); the returned length is
`round(duration * fs_hw)` **per channel** (NDS live reads can come back short or gapped and nothing at
[`src/system_ident/loop.py:214`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L214) checks); and the length is an exact multiple of `nperseg_hw`.
Silence here becomes a 200%-class error downstream.

### Companion change

`channels.drive` must become **mandatory** for this backend — see the `--cds` wiring issue.
[`src/system_ident/loop.py:90`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L90) currently falls back to `exc[d]` silently and
`_warn_open_drive_monitor` ([`src/system_ident/loop.py:274`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/loop.py#L274)) only warns. "X falls back to the
excitation channel" plus "the excitation channel returns the stashed drive" *is* the 200%-error
configuration.

Also correct the docstring at [`src/system_ident/backends/cds.py:18`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/backends/cds.py#L18): "a constant integer-sample
offset cancels ... but a fractional-sample clock drift would re-introduce leakage" is only half right,
and as written it pushes an implementer toward exactly the alignment code this issue rules out.

---
**Campaign:** CDS hardware backend · branch [`feat/cds-hardware-backend`](https://github.com/CaltechExperimentalGravity/system_ident/tree/feat/cds-hardware-backend)
· [spec](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/docs/superpowers/specs/2026-08-03-cds-hardware-backend-design.md) · [plan](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/docs/superpowers/plans/2026-08-03-cds-hardware-backend.md) · [handoff](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/notes/cds-hardware-bringup-2026-08.md)
*Code is deferred until the plan and issues have been reviewed.*

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with src/system_ident/loop.py, especially the read path around lines 90, 145, 214, and 274, then inspect src/system_ident/backends/cds.py and the linked CDS specification and plan. Confirm that CDSBackend.read() uses real channels from the shared buffer, performs no alignment or synthesis, enforces the stated duration and length invariants, and that channels.drive is mandatory with the corrected docstring.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.