Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Segmented ArbitraryStream excitation with a cosine on/off envelope; ArbitraryLoop as a peer mode

Open
#31 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
38/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
python
Domain
backend, testing

Research direction

Start with backends/base.py, the shipped configs/rtsfreerun_hsts.yml, and the linked work items #5, #11, #13, #14, #16, #19, and #20. Check the existing fake harness and stream/loop entry points before defining the segmented stream path, analysed window, stop handling, and config wiring. Done means both modes remain supported and the specified tests and underrun checks cover their distinct envelope behavior.

Written by the indexing model from the issue text.

Description

enhancement

Supersedes the design consequence of #9. Design detail: spec §2.3 (new), with the two corrections it rests on boxed in §2.2 and §8b.

Why

_soft_start_stop (backends/base.py) is scipy.signal.windows.tukey — a cosine taper, C¹, slope starts at zero. ArbitraryLoop.start(ramptime) is set_gain(0) then set_gain(g, ramptime=…) → awgSetGain — a linear gain ramp with a slope discontinuity at both ends. The 2026-08-04 operator run measured that linearity to five decimals (#27).

A cosine taper is gentler on the actuator and is therefore preferred. So "use the transport's own ramp", which #9's diagnosis implied, is an envelope-quality downgrade, not a neutral swap. It was only acceptable because a looped array leaves no alternative: tapering the staged array turns the taper into periodic AM and the seam re-excites the plant every cycle — measured 2.81e-1 FRF error (spec §2.2).

What

Build the entire excitation as one array carrying a cosine on/off envelope and inject it once with awg.ArbitraryStream, in four temporal segments:

# segment duration envelope analysed?
1 ramp-on t_ramp cosine, rising no
2 settle — the system under test's transient decays warmup_s flat, full amplitude no, discarded
3 main excitation — read back, FRF measured segment_duration × n_segments flat, full amplitude yes
4 ramp-off t_ramp cosine, falling no

Segments 1, 2 and 4 sit outside the analysed window, so the taper never touches the periods entering the FRF and segment 3 stays exactly periodic. read() must window segment 3 only (#14), and a config check should enforce that t_ramp fits the lead it tapers into.

No new config keys. The four durations derive from keys that already exist and already carry this meaning in the twin and rtsfreerun backends: t_ramp, warmup_s, segment_duration, n_segments. One meaning per knob across all backends. Shipped configs/rtsfreerun_hsts.yml gives 3 + 24 + 96 + 3 = 126 s.

Two findings that make this viable

Both verified read-only against awg 4.1.4 on the deployment machine:

  1. ArbitraryStream exists, and ArbitraryLoop subclasses it — class ArbitraryLoop(ArbitraryStream). Available: open(), append(data, scale), close(), send(), abort() → SIStrAbort, set_gain(gain, ramptime, wait) → awgSetGain.
  2. A blocking claim in §2.2 is false. It argued against the one-shot construction because "ramp_down would have to rewrite an already-queued array, which is impossible." Not so: set_gain and abort are independent of the queued data. So ramp_down(channel, secs) → set_gain(0, ramptime=secs) maps exactly (#16), and #19's mandatory out-of-band STOP gets abort().

Note §2.2's own table already scored one-shot lead+record+tail at 8.8e-12 — as good as the ramptime path (1.4e-11). The preferred construction was measured all along, and it is the RTSfreerunBackend construction, so twin and hardware share one envelope construction instead of two.

ArbitraryLoop stays a first-class peer — cds.exc_mode: stream | loop

Not a deprecated path. Both modes supported, both tested, both in the #5 fake harness. exc_mode defaults to stream; choosing loop is a deliberate config choice and emits a warning naming the consequence — linear envelope, harsher on the actuator.

loop's four roles:

  1. Fallback if stream injection misbehaves on hardware.
  2. Development pipeline — simpler to drive.
  3. Valid on request, or wherever a linear ramp carries no disadvantage for the use case.
  4. Very long excitations — with the justification corrected below.
Role 4, computed rather than asserted

Per the feasibility gate in CLAUDE.md — no claimed limit without the number:

  • Shipped config, one experiment = 126 s. At fs_hw = 16384, float64: 126 × 16384 × 8 ≈ 16.5 MB. Memory is a non-issue.
  • Q-limited physics-sized record: T ≥ ~Q/f0 gives T_perseg ≈ 1493 s for Q = 1000 at 0.67 Hz, so 6 periods ≈ 8955 s ≈ 2.5 h → ≈1.17 GB per channel, ≈3.5 GB for 3 DoF simultaneous. Large, still tractable, and campaign estimates already run to hours.
  • But append can be fed in chunks. The segmented envelope needs only the total length up front, not the samples, so the array need never be resident — it can be generated period by period. The memory rationale dissolves.
  • Stream underrun does not dissolve. append pushes data in real time from Python; a stall (GC, NFS, network) starves the stream and leaves a gap in the excitation. ArbitraryLoop is structurally immune — the front end repeats a staged buffer with no further Python involvement.

So role 4 is justified on underrun-immunity, not memory. Stronger argument, and it scales with record duration rather than being a remote edge case.

Underrun must be detected explicitly — it is a #8-class fault

A gap in the excitation is exactly what H_err and coherence cannot see (spec §3.3: they are period-to-period scatter, so a corrupted FRF can still report coherence ≈ 1). Reuse the two detections that already exist rather than inventing a third:

  1. #8's independent check — measured Xbar phase vs the known injected multisine phase.
  2. Compare the _EXC readback against the commanded array. Newly known feasible: §8b measured the excitation readback as NDS-readable at the model rate, round-tripping amplitude and shape exactly. A gap shows up directly.

Work items

  • #5 — add FakeArbitraryStream recording append sizes/order so chunking and underrun are testable; keep FakeArbitraryLoop.
  • #11 — transport seam expresses both modes.
  • #13 — stream mode builds the segmented enveloped array; the _soft_start_stop pass-through override applies to loop mode only.
  • #14 — analysed read window = segment 3 only.
  • #15 — segment 2 is the settle.
  • #16 — set_gain(0, ramptime=…) graceful, abort() hard; underrun handling in the finally.
  • #19 — abort() as the STOP path on the stream.
  • #20 — exc_mode in the config schema and --cds wiring, with the loop-mode warning.
  • base.py ramp contract — keep it two-branch, but each branch must state which envelope shape it produces and that cosine is preferred. That omission is what let the two be treated as equivalent.
  • Rework spec §4.1 / §4.2, which still specify ArbitraryLoop as the only transport.

Deferred — planned, not implemented

  • Shrinking segment 2, via #1 (Local Polynomial Method / GraFIT). LPM estimates the transient/leakage term instead of waiting it out, so the discarded settle — the dominant fixed cost per experiment — could shrink. Defer planning.
  • MIMO. Segments 1–4 constitute one 'experiment'; a MIMO measurement contains one experiment per input, so n_experiments = n_inputs. Defer planning.

Not in scope

Any hardware injection. ArbitraryStream has never been driven at the site — see #27 for the stream-specific unknowns. Every injection needs its own separate operator approval regardless of what came before.

Dominant language
Python
Stars
0
Forks
1
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from CaltechExperimentalGravity/system_ident

All issues in CaltechExperimentalGravity/system_ident

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.