Skip to content

Floor the optimal-design budget at k + 1 on the pyoptex path (deflakes CI) - #531

Merged
kgdunn merged 13 commits into
mainfrom
claude/process-improve-library-issues-hr7xxk-doeclamp
Aug 29, 2026
Merged

Floor the optimal-design budget at k + 1 on the pyoptex path (deflakes CI)#531
kgdunn merged 13 commits into
mainfrom
claude/process-improve-library-issues-hr7xxk-doeclamp

Conversation

@kgdunn

@kgdunn kgdunn commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

Test plan

  • uv run pytest tests/test_designs_screening_optimal.py --no-cov: 21 passed, 5 consecutive runs (previously intermittent)
  • uv run pytest tests/test_designs_optimal_pyoptex.py tests/test_design_generation.py tests/test_augment_design.py tests/test_designs_screening_optimal.py --no-cov: 200 passed
  • ruff check ., ruff format --check ., mypy src/process_improve clean

Checklist

  • Version bumped in pyproject.toml (PATCH for fixes/docs/config, MINOR for new features)
  • Tests added or updated where relevant
  • ruff check . passes
  • CHANGELOG.md updated

Generated by Claude Code

claude added 2 commits August 29, 2026 01:11
…ptex path

The point-exchange fallback already raises a budget below k + 1 to k + 1
(the smallest run count that can estimate the main-effects model), and
the tests document that clamp as the contract. The pyoptex path passed
the budget through unclamped, and pyoptex handles an infeasible run
count unpredictably, which made
test_budget_clamped_to_minimum_model_size fail intermittently across
unrelated PRs. Apply the same floor in the d-, i-, and a-optimal
dispatchers, except when the requested budget cannot even hold the
fixed_runs, so that validation still raises on the requested value.

Part of the DOE cluster tracked in #513.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
…floor

The mcp floor accompanies the mcp 2.x server port from #528 included in
the previous commit; both no-op once #528 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
kgdunn pushed a commit that referenced this pull request Aug 29, 2026
…p test is deterministic

test_budget_clamped_to_minimum_model_size fails intermittently because
the pyoptex path never implemented the documented k + 1 budget floor;
ported verbatim from #531, no-ops once it merges.

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

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The exchange loop grows the design only when an addition improves the
criterion, and a candidate consumed by a replacement is never considered
for addition, so an unlucky shuffle ended below number_points on roughly
1 to 2% of unseeded runs; this is the actual mechanism behind the
intermittent 'assert 3 >= 4' in the D-optimal dispatch test (the
dispatcher-level budget floor in the previous commit is necessary for the
contract but not sufficient, because the shortfall happened inside
point_exchange itself). Complete the design greedily with the remaining
candidates that best preserve D-optimality; measured 500/500 correct
sizes after the fix versus 492/500 before.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
kgdunn pushed a commit that referenced this pull request Aug 29, 2026
The dispatcher budget floor alone was not sufficient: the shortfall
happens inside point_exchange, which could end below number_points on
1 to 2% of unseeded runs. Ported verbatim from #531; no-ops once it
merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
claude added 2 commits August 29, 2026 01:27
Seeds 122, 146, 199, 319, and 338 ended with 3 rows under the pre-fix
algorithm, so they cover the greedy completion loop that codecov flagged
as untested; the low seeds keep the ordinary path covered.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
…in point_exchange

optimization_function guarded only the exactly singular case via
LinAlgError; a numerically singular X'X passes np.linalg.inv and its
garbage inverse can carry an exactly zero determinant, scoring
log|det| = -inf. Under the lower-is-better convention that ranked the
singular design as unbeatable, so no replacement or addition could ever
improve on it: this froze the exchange loop (the underlying mechanism of
the run-count shortfall) and let the new completion step pick a
rank-deficient row (the pinned regression seeds caught it via a
non-finite d_opt). Map any non-finite score to +inf, matching the
existing convention for the exactly singular case. Verified 300/300
unseeded runs and all pinned seeds now return full-rank, correctly sized
designs with finite scores.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
kgdunn pushed a commit that referenced this pull request Aug 29, 2026
Completes the earlier port: non-finite scores from a numerically
singular X'X now read as unacceptable rather than unbeatable, which was
the underlying freeze behind the run-count shortfall. No-ops once #531
merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
claude added 3 commits August 29, 2026 06:29
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
kgdunn pushed a commit that referenced this pull request Aug 29, 2026
test_budget_clamped_to_minimum_model_size failed intermittently on this
PR's CI (macOS, assert 3 >= 4) in code this PR does not touch. Root
cause and fix are in #531 (a k+1 budget floor on the pyoptex path, a
greedy run-count completion in point_exchange, and a singular-design
scoring guard); ported verbatim here so this PR's CI is deterministic.
No-ops once #531 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
kgdunn pushed a commit that referenced this pull request Aug 29, 2026
test_budget_clamped_to_minimum_model_size fails intermittently on any
PR's CI (macOS, assert 3 >= 4) in code this PR does not touch. Root
cause and fix are in #531; ported verbatim so this PR's CI is
deterministic. No-ops once #531 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
kgdunn pushed a commit that referenced this pull request Aug 29, 2026
test_budget_clamped_to_minimum_model_size fails intermittently on any
PR's CI (macOS, assert 3 >= 4) in code this PR does not touch. Root
cause and fix are in #531; ported verbatim so this PR's CI is
deterministic. No-ops once #531 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
kgdunn added a commit that referenced this pull request Aug 29, 2026
* Publish real per-tool JSON Schemas from the MCP server (#506)

The MCP server built every tool from an async (**kwargs) closure and
assigned __annotations__ / __defaults__ afterwards, which cannot add
parameters to a function that has none. Every registered tool was
therefore published to MCP clients with an empty inputSchema: no
parameter names, types, required/optional split, enums, or bounds.

Registration now constructs explicit Tool objects whose `parameters`
field, the schema the server publishes verbatim, is exactly the
registry's spec["input_schema"] from get_tool_specs(). Argument
validation stays where it always lived: execute_tool_call validates
against each tool's real pydantic input model, so the hand-built
FuncMetadata passes arguments through unchanged.

The server now targets the MCP 2.x SDK (mcp.server.mcpserver.MCPServer,
renamed from 1.x's FastMCP); the installed `mcp>=1.0` pin already
resolved to 2.x, under which the old `from mcp.server.fastmcp import
FastMCP` failed at import (and was mypy's one standing error). The
`mcp` extra now pins `mcp>=2.0`, and the module is no longer omitted
from coverage measurement (100% covered by the new tests, which also
compare every published schema against the registry through the real
list_tools() path).

Drive-by from #513: the concurrent-dispatch test asserted on wall-clock
elapsed time and could flake on loaded CI runners; it now proves the
overlap with a barrier both in-flight calls must reach.

Version 1.73.2; CITATION.cff and CHANGELOG.md updated in step.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the point_exchange/pyoptex-budget deflake from #531

test_budget_clamped_to_minimum_model_size failed intermittently on this
PR's CI (macOS, assert 3 >= 4) in code this PR does not touch. Root
cause and fix are in #531 (a k+1 budget floor on the pyoptex path, a
greedy run-count completion in point_exchange, and a singular-design
scoring guard); ported verbatim here so this PR's CI is deterministic.
No-ops once #531 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

---------

Co-authored-by: Claude <noreply@anthropic.com>
kgdunn added a commit that referenced this pull request Aug 29, 2026
…least-privilege jobs (#507) (#525)

* fix(release): close the dispatch, SBOM, and Makefile holes in the publish pipeline (#507)

The ENG-21 gate promised that a typo in pyproject.toml on main cannot
ship a release, but three paths around the gate remained. This closes
them:

- publish.yml: the version guard now also runs on workflow_dispatch,
  which becomes a republish-only path: the checked-out commit must
  already carry the v<version> tag for the version in pyproject.toml.
- publish.yml: the build job additionally asserts CHANGELOG.md has a
  matching "## [X.Y.Z]" heading (mirroring the extraction regex), and
  the notes-extraction step fails loudly instead of silently falling
  back to auto-generated notes.
- publish.yml: gh release create passes --target with the SHA of the
  checked-out commit (HEAD, not GITHUB_SHA, since a dispatch run may
  check out inputs.ref), so the release tag lands on the published
  commit rather than the default branch head.
- publish.yml: the SBOM is generated from a fresh venv containing only
  the built wheel and its runtime closure, with pip/setuptools/wheel
  removed; cyclonedx-bom runs from a separate tooling venv so build
  tooling no longer ships as runtime components.
- publish.yml: permissions are per-job. The build job, which executes
  repo code via PEP 517 hooks, holds only contents: read; id-token,
  contents, and attestations write scopes stay on the publish job.
- publish.yml: pypa/gh-action-pypi-publish is pinned to the commit SHA
  that release/v1 resolves to (v1.14.2) instead of the mutable branch.
- Makefile: the release target no longer builds or publishes; it prints
  the tag-gated release instructions and exits nonzero.

Version 1.73.3; CITATION.cff and CHANGELOG.md updated in step.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the mcp 2.x migration from #528 so typecheck is green

The typecheck gate is red on main itself: the unpinned mcp extra now
resolves to mcp 2.x, where FastMCP was renamed MCPServer, so mypy fails
on mcp_server.py on every branch. This ports the migrated server and the
mcp>=2.0 floor verbatim from #528; the diff no-ops once #528 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the point_exchange/pyoptex-budget deflake from #531

test_budget_clamped_to_minimum_model_size fails intermittently on any
PR's CI (macOS, assert 3 >= 4) in code this PR does not touch. Root
cause and fix are in #531; ported verbatim so this PR's CI is
deterministic. No-ops once #531 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

---------

Co-authored-by: Claude <noreply@anthropic.com>
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
kgdunn added a commit that referenced this pull request Aug 29, 2026
…at can fail (#508, #510, #511) (#529)

* fix(experiments): bound the remote dataset fetch with an explicit timeout

pd.read_csv(url) carries no timeout, so distillateflow() / oildoe()
against a black-holing host blocked the caller indefinitely (#508).
_read_remote_csv now fetches with urllib.request.urlopen(url,
timeout=...) into a buffer and hands that to pandas. The default of
30 s lives on the config settings singleton as dataset_fetch_timeout,
overridable via PROCESS_IMPROVE_DATASET_FETCH_TIMEOUT or a caller
argument. Timeouts and URL errors still surface as the documented
RuntimeError naming the URL. An on-disk cache stays out of scope.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* test(perf): replace non-asserting benchmarks with deterministic ENG-18 assertions

The tests in tests/perf/ contained no assertions, had no stored
baseline, and pytest-benchmark disables itself under the default
-n auto, so a 100x slowdown passed green while the docstrings claimed
regression tracking (#511). Option 2 from the issue: assert the
cost-shape properties the ENG-18 lazy-frame work actually needs
protected. The public DataFrame views are built exactly once and then
cached (identity plus a counted single build), pickling excludes the
cache and the views rebuild correctly, repeated transform / predict /
diagnose calls cause zero lazy-frame rebuilds, and check_random_state
passes a Generator through by identity while resolving an int seed to
a fresh generator with bit-identical draws. Verified the new tests
fail when the _LazyFrame cache write is removed. CONTRIBUTING.md's
performance-regression policy now describes this honestly; the ENG-15
wall-clock benchmark CI job stays planned.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* test(experiments): skip the live openmv.net tests on the wrapped RuntimeError

The loaders now wrap network failures (including timeouts) in the
module's documented RuntimeError, so the offline skip helper must catch
that too instead of failing when openmv.net is unreachable.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the mcp 2.x migration from #528 so typecheck is green

The typecheck gate is red on main itself: the unpinned mcp dependency
resolves to mcp 2.x, where FastMCP was renamed MCPServer, so mypy fails
on src/process_improve/mcp_server.py. This ports the #528 fix verbatim
(mcp_server.py plus the mcp>=2.0 pin in both pyproject.toml
occurrences) so this PR's CI can pass on its own. It becomes a no-op
once #528 merges; this PR's own version bump and CHANGELOG entries for
#508/#510/#511 are in a separate commit.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* test: make the ENG-29 test tiers real with strict markers and measured slow marks

The tiers were documented but unused: -m 'not slow' deselected almost
nothing and -m 'not dataset' still hit the network (#510).

- pytest.ini addopts gains --strict-markers, so a typo'd marker is a
  collection error instead of a silent warning.
- test_pca_foods in tests/test_multivariate.py, the one test fetching
  from openmv.net, now carries @pytest.mark.dataset; a tests/ sweep for
  urlopen / openmv / http found no other unmarked network fetches.
- 42 tests measured at >= 5 s with --durations on this runner now carry
  @pytest.mark.slow (threshold is 2 s per CONTRIBUTING.md; the margin
  keeps the marks honest on faster machines). The two end-to-end tests
  crossing library boundaries (sensory pipeline, sklearn halving grid
  search) also carry @pytest.mark.integration.
- Wall clock: full suite 6:15, -m 'not slow and not dataset' 1:32 on
  the same runner.
- Both CI test jobs run plain pytest with no -m filter, so every tier
  still executes in CI.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* chore: bump version to 1.73.4 with citation and changelog in sync

PATCH bump for the #508/#510/#511 test-hygiene fixes. CITATION.cff
carries the same version and today's date-released; CHANGELOG.md gains
the 1.73.4 section and updated link footer. The version assumes the
earlier queued 1.73.x PRs merge first.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* test: bind both accesses to names in the loadings cache-identity assertion

CodeQL flags a self-comparison; two named reads assert the same
cache-identity property without the identical-values pattern.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the optimal-design budget floor from #531 so the flaky clamp test is deterministic

test_budget_clamped_to_minimum_model_size fails intermittently because
the pyoptex path never implemented the documented k + 1 budget floor;
ported verbatim from #531, no-ops once it merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the point_exchange run-count completion from #531

The dispatcher budget floor alone was not sufficient: the shortfall
happens inside point_exchange, which could end below number_points on
1 to 2% of unseeded runs. Ported verbatim from #531; no-ops once it
merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the point_exchange singular-design scoring guard from #531

Completes the earlier port: non-finite scores from a numerically
singular X'X now read as unacceptable rather than unbeatable, which was
the underlying freeze behind the run-count shortfall. No-ops once #531
merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

---------

Co-authored-by: Claude <noreply@anthropic.com>
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
kgdunn added a commit that referenced this pull request Aug 29, 2026
…ic multiblock starts, one TPLS T2 (#502, #503, #504) (#530)

* Make NIPALS convergence scale-relative across PCA, PLS, MBPCA and MBPLS

terminate_check compared the raw norm of the change between successive
score vectors against an absolute tolerance, so whether the loop
converged depended on the magnitude of the data: large-magnitude X
burned every md_max_iter iteration, tiny X converged instantly. The
criterion is now the relative form already used by TPLS's
_has_converged: the norm of the change divided by the norm of the
current vector, floored via _nz so a fully-deflated all-zero score
vector cannot divide by zero. The signature and the md_tol settings key
are unchanged; md_tol is now interpreted as a relative tolerance.

The MBPCA and MBPLS inner loops get the same relative criterion, and
their absolute default tolerances at machine precision (eps**(9/10) and
eps**(6/7)), which as relative tolerances sit below the floating-point
oscillation floor, become the shared epsqrt default used by PCA, PLS
and TPLS.

In the same loops, the hard-coded np.random.default_rng(0) start for
the MBPCA super-score and the MBPLS u vector is replaced by a
deterministic seed from the column with the largest sum of squares,
the way single-block PCA / PLS already seed (#195): no RNG remains in
these fit paths, satisfying the reproducibility contract (the
determinism tests for #503 follow in the next commit). Fitted
components are unchanged up to the existing sign convention.

Fixes #504.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* Add determinism tests for the multiblock deterministic NIPALS start

MBPCA and MBPLS previously seeded their NIPALS super-score / u
initialisation from a hard-coded np.random.default_rng(0), violating
the reproducibility contract in docs/development/reproducibility.rst,
and exposed no random_state. The previous commit removed the RNG from
both fit paths in favour of a deterministic seed from the column with
the largest sum of squares, matching single-block PCA / PLS (#195), so
no random_state parameter is needed at all. These tests pin the
contract: repeated fits are bit-identical, and no non-convergence
warning fires on well-conditioned data. Fitted components on the
reference and oracle suites are unchanged up to the existing sign
convention.

Fixes #503.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* Compute one Hotelling's T2 in TPLS, shared by fit() and diagnose()

TPLS.fit() computed a full-Mahalanobis T2 over the super-scores with
the covariance divided by N, shape (n_obs, 1), while diagnose()
computed the cumulative diagonal form, shape (n_obs, n_components):
two different statistics under one name, compared against one limit.
Both now use the cumulative per-component form sum_a (t_a / s_a)^2
already used by PCA and PLS, which is what the class docstring
promised, matches the sibling estimators, and extends to new
observations without the training covariance. The score standard
deviations (scaling_factor_for_scores, which also feeds
ellipse_coordinates) use the unbiased N - 1 divisor, the estimator the
F-distribution form in hotellings_t2_limit is derived for; the
previous N divisor inflated T2 by N / (N - 1) and drew score ellipses
slightly too small.

The pinned test values change accordingly: the training super-scores
are orthogonal (normalized off-diagonal cross-products ~1e-17), so the
old full-Mahalanobis values equal the diagonal form with the N
divisor, and the new pins are exactly the old ones times
(N - 1) / N = 104 / 105. A new test asserts fit() and diagnose()
return the same T2, at the same shape, for the same rows.

Fixes #502.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* Bump version to 1.74.0 and add the changelog entry for #502/#503/#504

MINOR bump: all three fixes change numerical results or iteration
behaviour, because the previous values were wrong. CITATION.cff is kept
in sync in the same commit. The version assumes the queued sibling PRs
land first in order.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* Appease ruff: rename a shadowing loop variable and apply formatting

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the mcp 2.x migration from #528 so typecheck is green

The typecheck gate is red on main itself: the unpinned mcp dependency
resolves to mcp 2.x, where FastMCP was renamed MCPServer, so mypy fails
on src/process_improve/mcp_server.py. This ports the #528 fix verbatim
(mcp_server.py plus the two mcp>=2.0 requirement bumps) and no-ops once
#528 merges. The version bump and changelog entries for #528 are
deliberately not taken here.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* Always run the first NIPALS iteration; relax one kernel-vs-NIPALS rtol

Two CI follow-ups on the scale-relative convergence rework (#504).

The PCA / PLS loops prime their convergence check with a seed offset of
+1.0. Under the relative criterion that offset can be negligible for a
large-magnitude seed column (a fuzzed Y entry of ~1.9e154), so the loop
reported convergence before its first iteration: PLS then raised
UnboundLocalError for the never-assigned t_a through the fit_pls tool
boundary, and PCA would have silently returned the unrefined seed as
the score with an all-zero loading. Both loops now force the first
iteration with an explicit itern == 0 check, restoring the pre-rework
behaviour on such inputs, and a deterministic (non-hypothesis)
regression test pins the fuzz payload.

test_adaptive_pls_initial_matches_batch_ldpe compared kernel-PLS and
NIPALS beta coefficients at the assert_allclose default rtol of 1e-7;
the relative stopping rule now ends the batch fit once successive score
iterations agree to epsqrt (~1.49e-8) relative accuracy, and the
agreement on this fixture sits at 1.07e-7 relative on one of 70
elements. Both solvers are converged to their own criteria, so that one
assertion is relaxed to rtol=1e-6 with an explanatory comment; nothing
else is loosened.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

* fix: port the point_exchange/pyoptex-budget deflake from #531

test_budget_clamped_to_minimum_model_size fails intermittently on any
PR's CI (macOS, assert 3 >= 4) in code this PR does not touch. Root
cause and fix are in #531; ported verbatim so this PR's CI is
deterministic. No-ops once #531 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

---------

Co-authored-by: Claude <noreply@anthropic.com>
claude added 3 commits August 29, 2026 08:37
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
…e-library-issues-hr7xxk-doeclamp

# Conflicts:
#	CHANGELOG.md
@kgdunn
kgdunn merged commit 7fb56a9 into main Aug 29, 2026
11 checks passed
@kgdunn
kgdunn deleted the claude/process-improve-library-issues-hr7xxk-doeclamp branch August 29, 2026 09:29
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