Skip to content

refactor(experiments): make the trade-off API names consistent - #491

Merged
kgdunn merged 8 commits into
mainfrom
claude/factorial-table-omars-addition-ufpj8j
Aug 10, 2026
Merged

refactor(experiments): make the trade-off API names consistent#491
kgdunn merged 8 commits into
mainfrom
claude/factorial-table-omars-addition-ufpj8j

Conversation

@kgdunn

@kgdunn kgdunn commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Why

The four trade-off functions had drifted into four separate naming problems:

  1. The same word was spelled two ways in sibling functions: tradeoff against trade_off_table.
  2. runs was an int in tradeoff() and a tuple in trade_off_table(). Same parameter name, different type, no cue at the call site.
  3. The OMARS pair changed parameter names between its own two functions (n_runs/n_factors against runs/factors).
  4. trade_off_table was the only one of the four without a display argument.

On top of that, omars_tradeoff was both a module and a function inside it.

Rules this establishes

Meant to apply to any future pair, not just this one:

  • One spelling: trade_off, never tradeoff.
  • The entry accessor is named get_<table_name>_entry.
  • Scalar counts take the n_ prefix; sequences stay plural bare nouns. The parameter type is then visible at the call site.
  • Every reporting function takes display.

What changed

Old New
tradeoff(runs, factors) get_trade_off_table_entry(n_runs, n_factors)
omars_tradeoff(n_runs, n_factors) get_omars_trade_off_table_entry(n_runs, n_factors)
TradeoffResult TradeOffTableEntry
OmarsTradeoffResult OmarsTradeOffTableEntry
experiments.tradeoff (module) experiments.trade_off
experiments.omars_tradeoff (module) experiments.omars_trade_off

TradeOffTableEntry.runs and .factors become .n_runs and .n_factors, matching OmarsTradeOffTableEntry, which already had them. trade_off_table gains display, defaulting to True like the other three, so a bare trade_off_table() now prints as well as returns.

Renaming the modules also removes the module/function collision.

No deprecation shims. The old names are gone.

MCP surface: deliberately untouched

Same tool name (trade_off_table), same runs and factors schema keys, same output. Only the internal call site moved, so no MCP client breaks. Verified by calling the tool after the rename.

Consistency scan

A scan of the public API (99 modules, 287 public functions) confirms no compound word is now spelled two ways anywhere in the library. It also surfaced two things left alone pending a decision, since generate_design is itself an MCP tool and renaming its parameters would be a schema change:

  • generate_design(replicates: int) and generate_design(blocks: int | None): scalar counts without the n_ prefix. replicates also appears in designs_utils.build_design_result and replicate_design.
  • center_points is in the same category and was not flagged by the scan; consistency would pull it in too.

generate_omars(n_runs_range: tuple[int, int]) was flagged as n_-prefixed but sequence-valued. It reads as "the range for n_runs", so I judged it fine.

Verification

  • ruff check . clean
  • ruff format --check . clean
  • mypy src/process_improve clean, 145 source files
  • pytest: 2475 passed, 5 skipped, coverage 94.08%
  • trade_off_table MCP tool called after the rename: unchanged schema and output

Open question: version number

Not bumped yet, and the changelog entry sits under [Unreleased].

An incompatible rename without deprecation is semantically 2.0.0. But docs/development/deprecation_policy.rst permits MAJOR only after its announce and warn schedule has run, which was deliberately skipped here. So the options are 2.0.0 (correct semantics, contradicts the written policy) or 1.67.0 (understates a breaking change). CLAUDE.md says to ask rather than pick when unsure, so this is left for the maintainer.

Downstream

pid-book and kgdunn/figures both call the renamed functions and need matching updates before this merges:

  • pid-book: design-analysis-experiments/omars-designs.rst, design-analysis-experiments/fractional-factorial-designs/design-resolution.rst
  • figures: doe/omars-capability-staircase.py

Generated by Claude Code

The four trade-off functions had drifted into four separate naming problems:
the same word was spelled two ways in sibling functions (tradeoff against
trade_off_table); `runs` was an int in one function and a tuple in another, with
no cue at the call site; the OMARS pair changed parameter names between its own
two functions; and trade_off_table was the only one of the four without a
`display` argument. On top of that, `omars_tradeoff` was both a module and a
function in it.

Three rules now govern the group, and they are meant to apply to any future pair:
one spelling, `trade_off`; the entry accessor is named get_<table_name>_entry;
and scalar counts take the n_ prefix while sequences stay plural bare nouns, so
the parameter type is visible at the call site.

  tradeoff(runs, factors)             -> get_trade_off_table_entry(n_runs, n_factors)
  omars_tradeoff(n_runs, n_factors)   -> get_omars_trade_off_table_entry(n_runs, n_factors)
  TradeoffResult                      -> TradeOffTableEntry
  OmarsTradeoffResult                 -> OmarsTradeOffTableEntry
  experiments.tradeoff                -> experiments.trade_off
  experiments.omars_tradeoff          -> experiments.omars_trade_off

TradeOffTableEntry.runs and .factors become .n_runs and .n_factors, matching
OmarsTradeOffTableEntry, which already had them. trade_off_table gains `display`,
defaulting to True like the other three.

No deprecation shims: the old names are gone.

The trade_off_table MCP tool is deliberately untouched. Same tool name, same
runs and factors schema keys, same output; only its internal call site moved, so
no MCP client breaks.

A scan of the public API (99 modules, 287 public functions) confirms no compound
word is now spelled two ways anywhere. It also surfaced scalar counts without the
n_ prefix in generate_design (`replicates`, `blocks`) and its helpers; those are
left alone pending a decision, since generate_design is itself an MCP tool.

Version bump deferred: an incompatible rename without deprecation is semantically
MAJOR, but the deprecation policy permits MAJOR only after its announce and warn
schedule has run. The changelog entry sits under [Unreleased] until that is
settled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

kgdunn and others added 4 commits August 9, 2026 20:44
…ay argument

Codecov flagged one partial branch on the PR: every existing test called
trade_off_table() with display left at its default, so the quiet path added in
the previous commit was never taken.

Adds a test that asserts both directions, and switches the three existing table
tests to display=False so the suite stops printing the whole table to stdout
now that True is the default. trade_off.py is back to 100% branch coverage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
…ters

Completes the naming sweep started with the trade-off functions. The rule is that
a scalar count takes the n_ prefix while a sequence keeps a plural bare noun, so
the parameter's type is visible at the call site.

  center_points -> n_center_points
  replicates    -> n_replicates
  blocks        -> n_blocks   (the COUNT only)

Touches generate_design, build_design_result, replicate_design, dispatch_ccd,
dispatch_box_behnken, allocate_budget, estimate_rsm_runs, and
TradeOffTableEntry.n_replicates.

Deliberately NOT renamed:

  DesignResult.blocks stays as it is. It holds per-run block assignments as a
  list[int], and sequences keep the plural bare noun. Renaming the count actually
  removes a collision here, because build_design_result took a blocks count and
  returned a blocks list from the same function.

  blocks throughout the multivariate package means a sequence of data blocks in
  multiblock PCA and PLS. Different concept, already correct, left alone.

  generate_omars(n_runs_range) is n_-prefixed and tuple-valued, but it reads as
  "the range for n_runs" rather than a sequence of counts, so it stays.

Two things worth calling out. The generate_design MCP tool schema changes, since
center_points and replicates are input keys. And the per-key DoS caps in
tool_safety.py had to be re-keyed to match: they are keyed by tool input name, so
without that change the caps for these two inputs would have stopped applying
silently. tests/test_sec19_dos_caps.py caught it.

A re-run of the API scan now reports no scalar count anywhere in the library
missing the n_ prefix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
…epair prose

Two things, both in the DOE code.

1. analyze_experiment now reports estimability.

A rank-deficient model matrix is still "fitted" by the pseudo-inverse: a
coefficient is returned for every requested term, with a standard error and a
p-value, while only `model_rank` of them are determined by the data. The rest are
one arbitrary solution out of infinitely many. Economical designs that carry
structured aliasing (definitive screening, OMARS, foldovers generally) land in
that state routinely, so the caller was being handed confident-looking output
with nothing to flag it.

model_summary gains n_terms, model_rank and rank_deficient, and a RuntimeWarning
fires when the model is deficient, naming how many terms are not estimable and
saying what is and is not affected: predictions at the design points are fine,
individual coefficients and predictions elsewhere are not.

Demonstrated on the JMP definitive-screening example (6 factors, 17 runs): the
full second-order model asks for 28 terms and gets rank 15, which previously came
back silently. The main-effects-plus-quadratics model on the same data is full
rank and stays silent.

2. Prose damaged by the parameter rename.

The n_ prefix sweep in the previous commit was applied by pattern, and it reached
23 places where "replicates", "blocks" or "center points" was the English word
rather than an identifier: docstring descriptions, comments, an error message
("Number of n_blocks must be at least 2."), a strategy purpose string ("Run
n_replicates at the predicted optimum"), and two MCP schema descriptions visible
to tool callers ("Number of center point n_replicates"). All restored.

The lesson is recorded rather than just fixed: a rename by pattern needs a
separate prose pass, because tests and type checks pass either way. Nothing here
was caught by ruff, mypy or pytest; it was found by reading the diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
Codecov flagged one uncovered line on the PR: the fractional-factorial branch of
_screening_design_params. The rename touched that line, so it surfaced as a patch
gap, but the function had no direct test at all: three of its four branches were
only ever reached incidentally, and one never.

Adds a test per branch, including that definitive screening asks for a fake factor
only when the factor count is even. engine.py goes from 94 to 95 percent.

The remaining uncovered lines in that module predate this branch and are left
alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
kgdunn and others added 3 commits August 10, 2026 05:40
#485 landed the doe-designer skill and minimum moment aberration. Merging it in
exposes call sites that this branch's renames break, none of which git flags.

CHANGELOG was the only textual conflict: both sides had added to [Unreleased].
Entries from both are kept, main's first in each subsection.

Three schema-key call sites in the skill. The n_ prefix sweep renamed
generate_design's `center_points` and `replicates` inputs, and the skill passes
those keys as live JSON:

  examples/design_spec.json          "center_points": 0, invoked by SKILL.md,
                                     examples/README.md and worked-example.md
  references/worked-example.md:59    the prose describing that call
  references/worked-example.md:199   a runnable CCD command

Left alone, the skill's flagship example would fail input validation rather than
degrade. All three now use n_center_points, and all three documented commands
were run to confirm it.

The English words are untouched: "center points or replicates", "extra
replicates", "3 replicates at the optimum". Only keys in payloads changed.

Three tests in test_moment_aberration.py called generate_design with
center_points=; updated to n_center_points=.

Also corrected, unrelated to the rename: the CHANGELOG entry for the skill said
it exposes "the ten designed-experiments tools". trade_off_table (#487) made it
eleven, which SKILL.md already reflected after the #485 merge.

Citation narrowed in three places. SKILL.md, references/verification.md and
scripts/verify_design.py each stated that the Vazquez, Rother and
Charles-Gonzalez study (arXiv:2512.17113) ran GPT-5.1 and Gemini 2.5 Flash over
36 tasks with ten replicates, and that the failures were resolution 1 or 2,
non-regular arrays offered as regular fractions, and tables with missing cells.
The paper's abstract supports the run sizes, the factor range, the GPT and
Gemini families, and reliability up to about eight factors; it does not name
model versions, task or replicate counts, or those specific failure modes. The
text now claims only what is verifiable, and the authors are named in full.

Verification after the merge:

  ruff check . and ruff format --check . clean
  mypy src/process_improve clean, 146 source files
  pytest: 2538 passed, 5 skipped
  API naming scan over the merged code: no scalar count missing the n_ prefix,
    including everything #485 added
  all 44 numeric checks behind the pid-book sections still pass
  MCP surface as intended: trade_off_table still keyed on runs and factors,
    generate_design now on n_center_points and n_replicates
  skill scripts run end to end, including verify_design.py --require-resolution 5
    exiting 3, and the CCD command from worked-example.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
…inventory

Three accuracy problems found in a final sweep of the documentation.

user_guide/omars_designs.rst said the frontier is strictly more than the
parameter count "from four factors up". It is more at every factor count: the
gap is exactly k(k-1)/2, so it is 3 runs at three factors, not zero. The phrase
described where the old sizing bug showed up, not where the gap exists. The text
now states the gap and says the parameter count is never a sufficient test.

doe/tools.md documented generate_design's schema with center_points, replicates
and blocks, which the n_ prefix rename has since changed. These are the input
keys an agent sends, so a stale table here is a wrong contract.

doe/README.md and doe/tools.md both claimed eight tools, with two not started.
There are eleven, all implemented. The coverage percentages in that table were
measured against the 162-question bank for the original eight; the three added
since have not been scored, and the text now says so rather than implying the
figures cover them.

Also checked and correct as they stand: the performance table's half-pool column
against (3^k - 1)/2, its run-size and error-df columns against k^2+k+1 and the
parameter count, and the solver timings against the prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
The tool count appeared in five places and had to be bumped every time a tool
was added, which is how it came to read 'eight tools, two not started' long
after there were eleven and all of them worked. The count is also not the useful
fact: what matters is what each tool does.

Removed from doc/doe/README.md (both the At a Glance line and the '8-tool
architecture' file description), docs/doe/tools.md, SKILL.md, the CHANGELOG entry
for this branch, and the simulation tools module docstring.

Where a reader needs to know what exists, the text now points at the registry
instead: get_tool_specs() in the library, and doe_tool.py list in the skill.
Both enumerate whatever is actually registered, so they cannot drift.

The summary table in docs/doe/tools.md is completed with create_factorial_design,
fit_linear_model and trade_off_table. Their coverage columns carry a dash: the
question-bank percentages were measured before those tools existed, and scoring
them retrospectively would mean inventing numbers. The table now matches the
registry exactly, checked programmatically.

Historical CHANGELOG entries that state a count are left alone. They describe
what a past release did and are correct as a record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1daHPaXLs7PPRsq8CmMMm
@kgdunn
kgdunn merged commit 46410b6 into main Aug 10, 2026
14 checks passed
@kgdunn
kgdunn deleted the claude/factorial-table-omars-addition-ufpj8j branch August 10, 2026 06:56
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