Skip to content

feat: FreqResponsePointwise — batch sweep with guaranteed single-point arithmetic - #169

Merged
jamestjsp merged 1 commit into
jamestjsp:mainfrom
jamestjat:feat/freqresponse-pointwise
Aug 25, 2026
Merged

feat: FreqResponsePointwise — batch sweep with guaranteed single-point arithmetic#169
jamestjsp merged 1 commit into
jamestjsp:mainfrom
jamestjat:feat/freqresponse-pointwise

Conversation

@jamestjat

Copy link
Copy Markdown
Contributor

Why

FreqResponse switches long sweeps of delay-free state-space models to a transfer-function conversion (directFrequencySweepWorkLimit = 80) whose values are close but not bit-identical to the single-point path. Downstream consumers that need sweep results to reproduce single-point evaluations exactly have to mirror the internal work limit and chunk their sweeps — a coupling to an unexported constant that a dependency bump can silently break.

Concretely: internal/csadapter in APC.Exp.AspenAPC-Go carries directSweepWorkLimit = 80 plus a chunking loop, pinned by TestChunkedFreqResponseBitIdentical. This PR removes the need for both (monorepo task CM2QFO, found by DFDQAZ wave 48).

What

New exported method:

func (sys *System) FreqResponsePointwise(omega []float64) (*FreqResponseMatrix, error)

Contract: the value at each omega[k] is bit-identical to FreqResponse([]float64{omega[k]}), regardless of len(omega).

  • Delay-free / IO-delay state-space path: per-ω direct solve (evalFrSSInto) with the delay phase applied per point — the exact single-point arithmetic.
  • Per-point transfer-function fallback when the state-space solve fails at an isolated frequency (e.g. a pure integrator at ω=0), matching what a one-element FreqResponse call does for that point; the TF conversion is computed once and reused (deterministic).
  • Internal-delay (LFT) and descriptor paths already evaluate one frequency at a time with batch-size-independent arithmetic and delegate to the existing evaluator unchanged.

Also adds a README API-table row.

Tests

frequency_pointwise_test.go pins the bit-identity guarantee (Float64bits comparison) across:

  • sweep lengths 1 / 40 / 137 / 500 (below and above the work limit) for first-order, second-order + input delay, and static-gain systems
  • discrete systems, MIMO with output delay, internal delay (LFT)
  • the mixed per-point TF fallback (pure integrator with ω=0 in the grid)
  • empty omega (nil result, matching FreqResponse)

Local validation: go fix ./... clean, go vet ./... clean, go test -count=1 -race ./... green.

Please review; do not merge/tag on my behalf — the monorepo adapter switch is staged and lands after a tagged release.

🤖 Generated with Claude Code

…hmetic

FreqResponse switches long sweeps of delay-free state-space models to a
transfer-function conversion (directFrequencySweepWorkLimit) whose values
are close but not bit-identical to the single-point path. Downstream
consumers that need sweep results to reproduce single-point evaluations
exactly had to mirror the internal work limit and chunk their sweeps.

FreqResponsePointwise contracts per-frequency single-point arithmetic:
the value at each omega[k] is bit-identical to
FreqResponse([]float64{omega[k]}) regardless of sweep length, including
the per-point transfer-function fallback when the state-space solve
fails at an isolated frequency. Internal-delay (LFT) and descriptor
paths already evaluate one frequency at a time and delegate unchanged.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
@jamestjsp
jamestjsp merged commit 65b248b into jamestjsp:main Aug 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants