Skip to content

Batch mid-course correction, step 2: modelling substrate, projection, and the correction QP - #516

Open
kgdunn wants to merge 13 commits into
mainfrom
claude/batch-mid-course-correction-hdnkzp
Open

Batch mid-course correction, step 2: modelling substrate, projection, and the correction QP#516
kgdunn wants to merge 13 commits into
mainfrom
claude/batch-mid-course-correction-hdnkzp

Conversation

@kgdunn

@kgdunn kgdunn commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Phase 2 of the batch mid-course correction work, on top of the simulator merged in #515. This lands as one PR with the phases as clearly separated commit series (the session's git proxy accepts exactly this branch), in this order:

  1. Batch modelling substrate. BatchPCA (batchwise-unfolded PCA with optional Z join), BatchPLS ([Z | X] -> Y), BatchMonitor (time-varying online SPE/T2 limits), the batch plots, dataset loaders and data_input converters. This consolidates and supersedes the stale Batch modernization phase 1: offline BatchPCA, dataset loaders, converter completion #459, Batch phase 2: online Nomikos-MacGregor monitoring #460 and Batch phase 4: BatchPLS to final quality #462 stack, refreshed to the current conventions (lazy frame descriptors, error-message style, reproducibility contract); the Batch phase 3: sklearn transformer facades and f_rupture #461 transformer facades are deliberately dropped for now and can be revived separately if wanted.
  2. Partial-observation projection as public API. PCA.project / PLS.project for rows with a missing future (method in {"tsr", "scp", "pmp"}, TSR primary per Arteaga and Ferrer 2002), exposing the fixed-missingness-pattern linear operator the corrector precomputes per decision point, with a conditioning diagnostic and a documented ridge option.
  3. The mid-course correction QP. batch/control.py: midcourse_correction (pure function) and MidCourseCorrector, solved with osqp behind a new control extra. Penalty-form QP as the workhorse plus an outer loop on the multipliers for hard SPE/T2 caps; per-decision-point limits; a no-correction dead band from the model's prediction interval; target and maximize modes; rate-of-change and seam constraints; optional knot parameterisation.
  4. Executed policy evaluation. evaluate_control_policies on the simulator: replay vs mid-course correction vs the feedforward ceiling vs the true optimum, reporting realised (executed, not predicted) gains, plus agent tools, a recipe, and a user-guide page with every number measured from seeded runs.

Version, changelog and CITATION.cff mechanics land with the final phase.

Companion PRs: the book chapter that walks through this API is kgdunn/pid-book#272, and its nine figures are kgdunn/figures#83. Suggested merge order: this PR, then the figures PR, then the book PR (whose PDF step stays red until the figures are on that repo's main).

🤖 Generated with Claude Code

https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra


Generated by Claude Code

Phase 2 of the batch mid-course correction work, building on the
simulator merged in #515. Landing here as one PR in clearly separated
commit series:

- Batch modelling substrate: BatchPCA, BatchPLS, BatchMonitor,
  batch plots, dataset loaders (supersedes the stale #459-#462 stack,
  refreshed to current conventions).
- Public partial-observation projection (PCA.project / PLS.project
  with TSR, SCP and PMP score estimators).
- The mid-course correction QP (batch/control.py, osqp behind a new
  'control' extra) with hard SPE/T2 cap mode and a no-correction
  dead band.
- Executed policy evaluation on the simulator: replay vs mid-course
  correction vs feedforward ceiling vs true optimum, with realised
  (not predicted) gains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
claude added 6 commits August 21, 2026 22:13
melted_to_wide, wide_to_dict and wide_to_melted were stubs; they now
round-trip the standard batch dictionary, the melted frame and the
one-row-per-batch wide frame with the 2-level (tag, sequence) column
index. Ported from the stale #459 stack onto current main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
…plots, loaders

Consolidates the stale #459 / #460 / #462 stack onto current main, as
one commit series inside the mid-course-correction PR:

- BatchPCA: batchwise-unfolded (Nomikos-MacGregor) PCA with an optional
  initial-conditions (Z) join, composed over multivariate.PCA and
  MCUVScaler; batch-level scores, SPE, T2, contributions and the plot
  and limit conveniences.
- BatchPCA.predict_online: projection to the model plane for a
  partially-observed batch (upgraded to the shared missing-data
  projection API in a later commit series).
- BatchMonitor: time-varying online SPE and T2 limits learned from good
  batches, with per-sample alarms, plus online_monitoring_plot.
- BatchPLS: batchwise-unfolded [Z | X] -> Y regression to final
  quality. Refreshed beyond the original: the class now owns both
  scalers publicly (center_/scale_ for the unfolded block,
  y_center_/y_scale_ for the quality block; the inner PLS runs with
  scale=False), reports beta and RMSE in engineering units, and adds a
  prediction_interval wrapper. The mid-course corrector reads these
  public attributes instead of any PLS internals.
- time_varying_loading_plot and contribution_at_time_plot for the
  unfolded structure; dataset loaders load_nylon, load_dryer and
  load_batch_fake_data for the bundled batch CSVs.
- Integration test: BatchPLS on a simulator historical campaign (80
  batches, 4 components) reaches fit R2 0.83 and out-of-sample
  correlation 0.80 against fresh batches; thresholds pinned below with
  margin.

The #461 transformer facades are deliberately not re-landed; they can
be revived separately if wanted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
…cp, pmp)

The batch-so-far primitive, promoted to the multivariate package where
every model benefits:

- New shared kernel multivariate/_projection.py implementing the score
  estimators of Arteaga and Ferrer (2002): trimmed score regression
  (the default; statistically the strongest in their comparison and in
  Garcia-Munoz, Kourti and MacGregor 2004's batch-so-far study),
  single-component projection, and projection to the model plane. For
  a fixed missingness pattern each estimator is a fixed linear
  operator on the observed columns; operator_for_pattern exposes that
  matrix, and project_rows groups rows by pattern so each operator is
  built once.
- PCA.project / PLS.project estimate scores, SPE (over the observed
  columns), T2, a conditioning diagnostic and the observed count for
  rows containing NaN; complete rows take the standard path, so their
  scores are bitwise identical to transform(). PLS.project also
  returns y_hat on the original quality units.
- PCA.projection_matrix / PLS.projection_matrix (and a BatchPLS
  passthrough) expose the per-pattern operator with its condition
  number and a documented ridge option; nothing degrades silently.
- BatchPCA.predict_online now delegates to the shared kernel (gaining
  method and ridge parameters; TSR replaces the previous PMP-only
  path), and the new BatchPCA.predict_online_trace projects every time
  sample of a batch in one vectorized call. BatchMonitor uses the
  trace, cutting its nylon fit from 279 s to 1.5 s, and passes a
  method parameter through so limits and monitored traces always use
  the same estimator.
- BatchPLS stores its scaled training matrix and exposes y_loadings_
  and explained_variance_, the pieces the mid-course corrector needs
  to build per-decision-point reference distributions.
- New control extra (osqp) declared in pyproject ahead of the
  corrector module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
…he control extra

The core of the phase. midcourse_correction is the pure optimisation:
the unfolded row splits into observed columns (initial conditions and
the past), free columns (the future MV setpoints, the decision
variables) and missing future responses; the score vector is the
affine image t = b + A_F u of the free columns under the fixed-pattern
TSR operator, so quality tracking (or maximisation), movement from the
nominal remaining schedule, SPE and Hotelling's T2 are all convex
quadratics in u. Box bounds, per-tag rate limits (including the seam
to the last implemented sample) and an optional engineering-space
knot parameterisation stay linear, so osqp solves the whole program;
the unconstrained case falls back to the closed-form solution. Hard
SPE/T2 caps run an outer multiplier iteration (geometric escalation
plus per-cap bisection), verified in the tests against a direct SLSQP
solve of the capped problem.

MidCourseCorrector is the decision-point workflow around it: the SPE
validity gate on the batch-so-far (Flores-Cerrillo and MacGregor
2004), the no-correction dead band from the model's prediction
interval (Yabuki and MacGregor 1997) with a target_side option so a
floor-type quality skips batches predicted at or above target, per
decision-point SPE limits and score covariance from re-projecting the
training batches under the decision point's own missingness pattern
(Garcia-Munoz, Kourti and MacGregor 2004), and the assembled schedule
frame ready to implement or hand to the simulator.

The historical campaign policy's deliberate variation changes from
offset-plus-ramp to independent knot offsets: the executed-policy
experiments showed offset-plus-ramp cannot identify partial-batch
moves, which is precisely the data requirement the mid-course
literature states. Executed on the simulator (per-class models, one
decision point at day 4), the corrected batches gain +0.62 g/L on
average with none harmed; the integration test pins that behaviour.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
…; v1.68.0

evaluate_control_policies runs the whole mid-course workflow end to end
on the simulator and reports realised (executed, same-seed
counterfactual) quality per policy. Headline, defaults, seed 0, forty
fresh batches: replay 7.51 +/- 1.20 g/L; mid-course 7.71 +/- 0.78 (a
35% sigma reduction; four batches corrected, all in the poorest feed
class, gaining +1.53 to +2.67 g/L each, none harmed; the worst batch
rose from 3.66 to 5.79). The oracle-from-the-decision-point row (the
remaining schedule optimised against the simulator itself) shows the
data-driven correction captured 63% of what was achievable at that
decision point; the perfect-feedforward row raises the mean but leaves
a worse low tail than the corrected policy, because a schedule fixed
at time zero cannot answer a disturbance that develops mid-batch.
A decision-point sweep (documented in the user guide) shows the
correction window is mid-batch: earlier, projections are too uncertain
and misdirect; later, the remaining schedule has no leverage.

Also: agent tools correct_batch_midcourse and
evaluate_batch_control_policy; the midcourse_correction analysis
recipe; docs/user_guide/batch_control.rst carrying the executed
numbers; version 1.68.0 with CITATION.cff and the changelog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
CI resolves dependencies fresh whenever pyproject.toml changes ahead of
the maintainer's manual lockfile refresh, and the fresh resolution on
Python 3.13 picked scipy 1.18.1, which only shipped cp312 wheels; every
job then failed at the install step. A uv constraint excludes that one
release. Drop it once scipy publishes cp313 wheels for it or a newer
release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
The Sample/Day/Corrected/Mean-gain simple table had column markers
narrower than the widest cells, which docutils rejects and the strict
docs build treats as fatal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra
claude added 5 commits August 22, 2026 06:13
…de error audit (#500)

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

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
Checked against the primary sources, matching the corrections made to the
book chapter that documents this module.

- Flores-Cerrillo and MacGregor optimise an adjustment to the scores and
  recover the trajectories by inverting the PLS model; that inversion is
  what keeps the result consistent with past operation. The module header
  previously credited them for a reconstruction step this module does not
  perform. Both routes are now described, along with why optimising the MV
  columns directly needs the SPE term and the explicit constraints.
- Credit Arteaga and Ferrer for trimmed score regression, which was cited
  only through Golshan et al.
- The SPE check in their paper gates whether a correction is computed at
  all; say so, and keep the candidate-row SPE penalty marked as an addition
  here.
- With hard SPE and T2 caps the program is quadratically constrained rather
  than a quadratic program; the user guide now says so.
- The user guide claimed the literature almost always reports predicted
  gains. Simulation studies do execute their corrections; it is industrial
  case studies that cannot.
- Mean corrected gain is +2.02 g/L, not +2.03, matching the measured value
  and the decision-point sweep table on the same page.

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

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZAyiPT8xURSdi24yFKCra

# Conflicts:
#	CHANGELOG.md
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