Skip to content

Repair control architecture and numerical contracts#163

Merged
jamestjsp merged 10 commits into
mainfrom
codex/architectural-correctness
Jul 18, 2026
Merged

Repair control architecture and numerical contracts#163
jamestjsp merged 10 commits into
mainfrom
codex/architectural-correctness

Conversation

@jamestjsp

@jamestjsp jamestjsp commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • correct ModelArray stacking to preserve array rank and add explicit flattening concatenation
  • distinguish sampled passivity evidence from analytic certification, including DC/Nyquist-aware grids and MIMO Hermitian checks
  • replace state-index modal slicing with real-Schur ordering, conjugate-block preservation, Sylvester separation, and projection bases
  • update the Gonum fork dependency from v0.17.6-fork to the latest published v0.17.7-fork
  • assemble physical across/through constraints into descriptor dynamics with explicit channel bindings and grounded reaction variables
  • bind generalized analysis points to plant-input or plant-output loop topology
  • evaluate tuning goals against their intended response, frequency band, dynamic weights, and MIMO induced 2-norm
  • expose the bounded Cartesian engine as GridTune, with Systune and Looptune retained as compatibility wrappers
  • synchronize the API audit and generated architecture diagrams

Why

Several APIs exposed shallow or ambiguous contracts: stacking silently flattened model grids, passivity sampling looked like certification, modal truncation depended on the current state basis, physical connections only combined metadata, and all named loop points and gain goals collapsed onto the same response. Those behaviors could produce plausible but mathematically incorrect results.

The revised modules own their invariants and workflows: model arrays own shape semantics, passivity results own their evidence status and grid, modal reduction owns Schur-block selection and projection, physical assembly owns topology and constraints, and tuning goals own point/response/grid/weight selection.

Compatibility and impact

  • Passive remains as a deprecated compatibility alias for SampledPassive.
  • ConcatModelArrays provides the former flatten-and-append behavior explicitly.
  • the original generalized-loop analysis point remains a plant-output break, preserving the prior P*C convention
  • Systune and Looptune remain available and delegate to GridTune
  • descriptor frequency responses now evaluate C(sE-A)^{-1}B+D
  • correctness-oriented modal, physical-assembly, and MIMO tuning paths perform more work than the former simplified implementations
  • passivity frequency grids (explicit or FRD) must now be strictly increasing, finite, non-negative, and at or below the Nyquist frequency for sampled models
  • ModalTruncate now reports an explicit error when a requested order splits a complex-conjugate pair or a MaxRealPart threshold selects no modes, instead of silently falling back
  • review follow-up: grid-search candidates no longer share the analysis-point map with the source model, and weighted-gain evaluation reuses buffers across the frequency loop (-30% allocations on the dynamic-weight path)

Validation

  • go fix ./...
  • go vet ./...
  • go test -v -count=1 ./...
  • go test -race -count=1 ./...
  • go test -run '^$' -bench=. -benchmem -count=1 ./... (315.1 s, PASS)
  • clean workspace diagnostics
  • persistent non-symmetric, similarity-invariance, conjugate-pair, descriptor-oracle, physical-network, point-topology, MIMO singular-value, weighted-band, and search-limit regression tests

Focused tuning benchmarks show the true point-aware induced-norm checks increase runtime while reducing allocations; the Cartesian search now has an explicit MaxEvaluations bound.

CI follow-up

The initial Linux/amd64 run exposed an architecture-dependent logarithmic-grid endpoint rounding difference. Commit 9040de6 makes the generated passivity grid explicitly own its lower and Nyquist endpoints. Native arm64, Darwin amd64, full local race tests, both GitHub test jobs, and both downstream-consumer jobs now pass.

jamestjsp and others added 9 commits July 18, 2026 15:11
- untrack .ergo/plans.jsonl and ignore .ergo/
- explain order rejection when it would split a conjugate Schur pair
- reject MaxRealPart thresholds that select no modes instead of falling
  back to the half-order heuristic
- give GridTune candidates their own analysis-point map via
  withSampledController instead of a shallow model copy
- reuse weight-response and product buffers across the frequency loop in
  frequencyGainRange (-30% allocs on the dynamic-weight path)
- require strictly increasing passivity grids, matching tuning-goal
  grid validation, and document the FRDPassive grid contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jamestjsp
jamestjsp marked this pull request as ready for review July 18, 2026 21:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14029acd0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread frequency.go Outdated
Comment on lines +174 to +176
for k, w := range omega {
s := e.sAt(w)
if err := e.evalStateSpaceInto(s, data[k*pm:(k+1)*pm]); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reuse the descriptor evaluation workspace

For every frequency in a descriptor-model sweep, evalStateSpaceInto constructs a new ssEvalWorkspace, allocating several O(n²) complex buffers. Large frequency grids therefore generate substantial avoidable allocation and GC pressure, contrary to AGENTS.md's requirement to pre-allocate buffers outside loops and reuse LAPACK work arrays. Allocate one workspace before this loop and call evalFrSSInto with it for each frequency.

Useful? React with 👍 / 👎.

Comment thread physical_assembly.go Outdated
Comment on lines +155 to +159
if len(inputs) == 0 && len(outputsForPort) == 0 {
inputs = integerRange(inputCursor, port.Dimension)
outputsForPort = integerRange(outputCursor, port.Dimension)
inputCursor += port.Dimension
outputCursor += port.Dimension

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip explicitly occupied channels for implicit ports

When a component mixes explicit and implicit port bindings, the cursors advance only for implicit ports and do not account for channels already claimed explicitly. For example, an explicit first port bound to channel 0 followed by an implicit port is assigned channel 0 again and rejected as reused even when channel 1 is available; later implicit ports can similarly collide. Select the next unused input/output channels, or advance the cursors past explicit assignments.

Useful? React with 👍 / 👎.

@jamestjsp
jamestjsp merged commit 2b45f1d into main Jul 18, 2026
4 checks passed
@jamestjsp
jamestjsp deleted the codex/architectural-correctness branch July 18, 2026 22:25
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.

1 participant