Skip to content

Batch case studies: DuPont batch PCA, SBR batch PLS, FMC multiblock batch PLS (#154, #155, #156) - #537

Merged
kgdunn merged 36 commits into
mainfrom
claude/issues-154-156-process-3cu4iq
Sep 6, 2026
Merged

kgdunn merged 36 commits into
mainfrom
claude/issues-154-156-process-3cu4iq

Conversation

@kgdunn

@kgdunn kgdunn commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Re-implements the three legacy batch case studies from issues Case study: FMC batch process — multiblock batch PLS #154 (FMC batch dryer, multiblock batch PLS), Case study: DuPont Nylon batch process — batch PCA outlier diagnosis #155 (DuPont nylon batch, batch PCA outlier diagnosis) and Case study: SBR rubber batch process — batch PLS fault diagnosis (simulated) #156 (SBR reactor, batch PLS fault diagnosis) as plain Python scripts with narrative pages under docs/user_guide/case_studies/batch/. No notebooks: each script is a ladder of small functions that return the fitted models, and each page quotes the script section by section and states what it prints. The FMC script carries the clock time at each aligned sample as the eleventh trajectory of every batch model, as the original study did, and ends with a block-score reading of the four batches whose trajectories say off-specification while the product was on-specification.
  • Adds remote loaders batch.load_dupont, batch.load_fmc and batch.load_sbr (data served from openmv.net: polymerization.csv, batch-dryer.xlsx and sbr-batch-reactor.xlsx, all three live), a shared bounded-timeout reader process_improve._remote_data with an Excel variant (the experiments loaders now use it, unchanged in behaviour), one plot helper batch.unfolded_contribution_plot, and a small additive convenience layer: BatchPCA.score_contributions / unfold_and_scale, and on BatchPLS the plot, limit and contribution forwards, predictions_, r2_per_variable_ and predictions_vs_observed_plot. time_varying_loading_plot now also accepts a BatchPLS or any multivariate model fitted on a dict_to_wide matrix.
  • Brings the on-line (batch-so-far) API to BatchPLS: predict_online (scores, final-quality prediction, T2, cumulative and instantaneous SPE, residuals and the trajectory forecast of Wold et al. 2009 from a batch observed for upto_k samples, using the missing-data score estimators the library already had), predict_online_trace (the same at every sample of a complete batch) and online_rmse (the per-sample RMSE of the evolving prediction over a set of batches). BatchPCA.predict_online returns the same new keys. BatchMonitor now accepts a BatchPLS, standardises Hotelling's T2 by the reference batches' score scatter at each sample (the time-varying covariance Nomikos and MacGregor 1995 note the T2 chart needs, computed as Garcia-Munoz, Kourti and MacGregor 2004 do; the fixed end-of-batch covariance gave a normal batch a T2 of 220 after four samples), takes spe_statistic="instantaneous" for the per-interval SPE, and spe_window to pool the reference values of neighbouring samples into each per-sample SPE limit (default 0, the per-sample fit). The shared row-building and pattern-stacking code moved into a private batch/_online.py, with the BatchPCA traces bitwise unchanged.
  • Contributions for rows with missing cells: spe_contributions, score_contributions and t2_contributions (and the PCA, PLS, BatchPCA and BatchPLS methods that forward to them) used to return an all-NaN row for any observation with a missing cell. The scores of such a row are now estimated from its observed cells with the same missing-data operators as project (method="scp" by default, which reproduces the NIPALS fit; "tsr" and "pmp" available), and each contribution is that row-specific operator times the cell, so the contributions are defined at every observed cell and NaN only at the missing ones. Complete rows take the exact complete-data path and are bitwise unchanged; a row with no observed feature raises. The FMC page uses it for batch 20, the batch above both limits of the batch PCA, whose record has 205 missing cells.
  • Fixes PCA.r2_per_variable_ on the NIPALS path (taken whenever X has missing cells): it divided each column's residual by its sum of squares after the previous component, so column a held the share of what was left that component a explained rather than the cumulative R2 the docstring promises, and only the first column was right; the TSR path never filled the attribute and returned zeros. Found when the book's loading panels drew the R2 per cell (the collector-tank cells read 0.07 where the cumulative value is 0.78). A test checks the three PCA paths and PLS against the direct definition. The complete-data PCA path and PLS were already correct.
  • Fixes the "scp" score estimator for PLS models. Running every missing-data path on data without missing values showed that single-component projection projected onto the observed part of each loading vector, where NIPALS computes a score by projecting onto the weight vector and deflating with the loading: with nothing missing the SCP estimate differed from the model's own scores by up to a score standard deviation, and an incomplete row did not reproduce the score the fit had stored for it. operator_for_pattern and project_rows take an optional x_weights, passed by PLS.project, projection_matrix, the contribution helpers and the mid-course corrector; PCA is unchanged (its weights are its loadings) and TSR, the default, already reduced to the score map exactly. PMP is the least-squares fit onto the loading plane and, for PLS, does not reduce to the model's scores even with nothing missing; the docstrings now say so. tests/test_missing_data_paths_on_complete_data.py runs the NIPALS and TSR fit paths of PCA and the three operators on complete data (the bundled dryer batches, the DuPont batches, the SBR batches) against the direct computation.
  • Pins every number the legacy narratives quote in reference tests (tests/batch/test_case_studies.py), fills in the skipped TestFMCReference placeholder, vendors the legacy MATLAB batch-PCA scores and loadings on the SBR data as a fixture (tests/fixtures/sbr_batch_pca/) with a sign-agnostic cross-check, and adds tests/batch/test_online_prediction.py (exact identities of the on-line API on a thinned dryer set), tests/test_contributions_missing_cells.py (incomplete rows reproduce the stored diagnostics and the project estimates; complete rows bit-identical) and tests/test_r2_per_variable_cumulative.py, plus SBR alarm pins.

Closes #154, closes #155, closes #156.

Reference numbers reproduced (two components, tag-major unfolding, MCUV scaling):

Case Model Result
DuPont batch PCA, 55 batches R2 38.3% and 17.6% per component, 55.9% cumulative (narrative: 38.3, 17.6, 55.9); batch 49 largest in SPE; batches 50-55 in the scores; batch 49 residual in the heating, cooling and pressure tags at samples 57-63
DuPont rebuilt models second cluster 37, 39, 43-48 on t2-t3, read as a group against the model centre (TempC-1 and Press-3 lead; samples 0 to 25 carry 66% and 90% of the contribution); final 40-batch model leaves batches 38, 40, 41, 42 inside both limits (observability)
SBR batch PLS, six tags R2X 24.5% and 12.7%, R2Y 65.3% and 6.9% (narrative: 24.5, 12.7, 65.3, 6.9); batches 37 and 34 flagged in the scores, not in the SPE; batch 37 from conversion and latex density throughout, batch 34 from the heat balance from mid-batch (sustained departure of the temperatures and the energy released at samples 103-105, conversion and latex density at 123 and 129)
SBR on-line, reference model on the 51 normal batches batch 37 trips the per-sample T2 limit (10.5 at 99%) after 23 samples; batch 34 trips the instantaneous SPE after 105 samples, five after the impurity enters, with the reactor temperature carrying 40% of the residual at that sample; 0.2% of the reference batches' T2 values and 1.2% of their SPE values above the limits, unchanged in substance when neighbouring samples are pooled into the limits (spe_window=2: 1.3%, batch 34 still at 105); particle-size RMSEE / sd falls from 2.84 after 10 samples to 0.60 at the end (leave-one-batch-out RMSEP 3.10 to 0.78)
SBR batch PCA, nine tags (legacy unit_tests.m) R2 17.085% and 10.053%; scores and loadings match SBR-expected.mat sign-agnostically
FMC the eight-model ladder, 46 batches, clock time as the eleventh trajectory PCA on Y 0.500/0.703; PLS Zchem 0.163/0.222; PLS Zop 0.207/0.262; MBPLS Z 0.292/0.364; batch PCA 0.231/0.376; batch PLS 0.266/0.410; batch MBPLS 0.370/0.472 with super VIP Zchem 0.86, Zop 1.07, X 1.06 (regression pins; the FMC notes quote no numbers). Batch 20, above both limits of the batch PCA: 49% of its SPE in the dryer pressure, 58% in the first phase, its 205 missing cells (gaps between samples 34 and 109) blank. Batches 2, 3, 6 and 7, classed good, are placed with the abnormal batches by the trajectory block and with the good ones by both initial-condition blocks; against their nearest abnormal neighbours (42, 43, 44, 47, 50) the operating-condition contribution is carried by a longer cool-down, a shorter and steeper ramp and a shorter high-speed agitation, with the same peak set point
All three the missing-data paths on complete data PCA by NIPALS agrees with SVD to 1e-6 and by TSR to 1e-13 (scores, loadings, R2, R2 per variable, SPE, T2; also the DuPont batch PCA); with every cell observed the TSR, SCP and PMP operators return the PCA scores to 1e-13, and TSR and SCP the PLS scores (the SBR batch PLS included); project on complete rows equals the fit for every method; SCP reproduces the NIPALS scores of incomplete rows for PLS as it did for PCA

Consistency with the Nomikos and MacGregor papers. The SBR pages now state the simulated faults as the 1994 AIChE paper describes them (30% more impurity from the start of one batch, 50% more from halfway through the other) and that the feed tags carry the simulation's noise rather than being constant; the per-sample T2 covariance is credited to the 1995 Technometrics paper as a need it states, and to the 2004 paper for computing it; the DuPont page says the original reference set also left out the four poor-quality batches (36 batches, three components).

SBR hosting. The workbook sbr-batch-reactor.xlsx (sheets X_batch, Y_quality, the layout of batch-dryer.xlsx) is produced by tests/fixtures/sbr_batch_pca/prepare_fixture.py and is live at https://openmv.net/file/sbr-batch-reactor.xlsx, byte-identical to the fixture's output, with its description at https://openmv.net/info/sbr-batch-reactor. load_sbr() reads it in about two seconds, and the nine SBR reference tests, which skipped without a PROCESS_IMPROVE_SBR_URL override while the upload was pending, now run against it (the override stays for offline work).

Companion pull requests. The book chapter is kgdunn/pid-book#273 (three pages in the Applications chapter, each figure preceded by the equivalent Plotly code, with the two on-line sections on the SBR page and the block-score section on the FMC page), and its matplotlib figures are kgdunn/figures#84 (merge the figures PR before the book PR). The figures repository's script checks fail on the three batch scripts until this PR is merged (they import load_dupont, load_fmc, load_sbr and BatchMonitor from it) and, for the PyPI job, until a release carries it. The FMC loading-panel figure and the book's R2 echoes depend on the r2_per_variable_ fix.

Versioning note. Main took 1.79.0 (#539), 1.79.1 (#547) and 1.80.0 (#540) while this branch was open, so this branch's changelog section is 1.81.0, above main's 1.80.0. PR #541 also carries a 1.80.0 bump; if it merges before this one it needs a re-bump too.

Follow-ups, not in this PR. Converting the five existing case-study notebooks to the same script-plus-page form; MCUVScaler and PLS(scale=True) dropping multi-level column labels (the scripts re-attach wide.columns after scaling).

Test plan

  • uv run ruff check . and uv run ruff format --check . pass
  • uv run mypy src/process_improve passes (163 files)
  • Full suite after the SCP fix, with the SBR workbook served locally: 3208 passed, 4 skipped; tests/batch tests/test_multiblock_reference.py after each merge of main and after the clock-time change (299 passed), plus the coverage gate
  • Reference tests run online: DuPont and FMC against openmv.net; SBR against the local workbook while the upload was pending, then the nine SBR tests against the live openmv.net workbook with no override (9 passed)
  • Each case-study script executed end to end and its printed numbers compared with the pages and the pin table; the BatchPCA on-line traces compared bitwise with the pre-refactor baseline; complete-row contributions compared bitwise with the previous implementation
  • uv run sphinx-build docs docs/_build/html -b html -W succeeds after this round (pandoc installed for the existing notebooks)

Checklist

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

🤖 Generated with Claude Code

https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
… data

The scores and loadings that the 2011 toolkit's unit test asserts, relabelled
onto a (tag, sequence) layout, plus the script that regenerates them and writes
the two-sheet workbook hosted on openmv.net.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The urlopen-with-timeout helper moves from experiments.datasets into the
private process_improve._remote_data module so the batch loaders can reuse it;
read_remote_excel parses a workbook into one frame per sheet. Behaviour,
timeouts and error messages are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
load_dupont, load_fmc and load_sbr download the openmv.net copies through the
shared bounded reader, with a url override for mirrors and local copies. The
FMC and SBR loaders return the multiblock layout (trajectories plus quality
and initial-condition tables) as a Bunch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Comment thread tests/batch/test_datasets.py Fixed
Forward the score, SPE and T2 plots, the limits and the three contribution
functions to the inner PLS; expose predictions_ in quality units and
r2_per_variable_ on the unfolded index; add predictions_vs_observed_plot
aligned by batch id. Both batch classes gain unfold_and_scale, the public
scaled matrix the contribution methods take, and BatchPCA gains
score_contributions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…read any unfolded model

The new plot draws one batch's whole contribution vector over the (tag, time)
axis, one bar trace per tag with the tag names centred under their blocks, or
summed into one bar per tag. Row validation is shared with
contribution_at_time_plot. time_varying_loading_plot now accepts a BatchPLS
(weights) or any multivariate model fitted on a dict_to_wide matrix, reading
the tag and time order from the index when the model does not record them.

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

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

…ive pages

Three plain Python scripts under docs/user_guide/case_studies/batch, each a
ladder of small functions that return the fitted models so tests can pin the
numbers, with an RST page that quotes the script section by section and states
what each section prints. DuPont: batch PCA and the exclude-and-rebuild outlier
hunt (#155). SBR: batch PLS, the same injected fault at two onset times (#156).
FMC: the eight-model multiblock ladder on the batch dryer, with the missing
cells handled by the multivariate estimators (#154).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The reference tests import the case-study scripts by path and assert on the
objects their functions return, so the documented and tested numbers share
one code path. TestFMCReference replaces the skipped placeholder. The SBR
tests read the PROCESS_IMPROVE_SBR_URL override so they can run on a local
copy of the workbook until it is hosted, and skip offline otherwise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Comment thread tests/_case_study_scripts.py Fixed
CodeQL read the pytest.skip inside the except branch as an implicit
fall-through; assigning first and returning at the end says the same thing
with one explicit return.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Comment thread tests/_case_study_scripts.py Fixed
Comment thread tests/batch/test_datasets.py Fixed
…t when the SBR fault appears

CodeQL still read a pytest.skip call as a fall-through, so the download-or-skip
helpers now raise pytest.skip.Exception explicitly. The loaders are exercised
on local file:// copies of synthetic data, the openpyxl-missing branch and the
plot figure-reuse branches are tested, and BatchPLS's initial-condition checks
are covered. The SBR script reports per-fifth contribution shares and the first
sample at which each tag leaves the band of the other batches, which shows the
batch 34 fault appearing around samples 33 to 37, not midway.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Share one forwarding helper between BatchPCA and BatchPLS; refuse a quality
frame that lacks training batches in the parity plot; draw contiguous tag
blocks in unfolded_contribution_plot for the (sequence, tag) layout; probe
openpyxl before downloading a workbook; state the real DuPont tag order;
qualify the r2_per_variable_ sentence for scale=False; use a Python 3.10
compatible timestamp in the fixture script; return the worst complete batch
from the FMC script instead of recomputing it; make the DuPont and FMC
end-to-end tests skip offline; tag the network tests slow; share the skip
helper; restore the Simca-P placeholder that was dropped by mistake.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…sing

The "first sample more than 2 sd away" diagnostic on the SBR page reported
samples 33 to 37 for the temperatures of batch 34, which are single noisy
excursions; the temperatures and the energy released leave the band of the
normal batches for good at samples 103 to 105, the middle of the batch, and
conversion and latex density at 123 and 129. The helper now requires the tag
to stay outside the band for twenty consecutive samples, the page quotes the
resulting onsets, and the fault of batch 34 is described as starting midway,
as the original course notes do.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The NaN-padded scaled row of a running batch was built by hand in the
mid-course corrector and, in a second form, in BatchPCA.predict_online_trace;
the derivation of which unfolded column is an initial condition and which time
sample each trajectory column belongs to existed in both as well. One private
module, batch/_online.py, now holds those pieces and both callers use it, so
the PLS mirror that follows is not a third copy.

BatchPCA's online path is bitwise unchanged (checked against a saved baseline
of scores, T2, SPE, condition numbers and monitor limits on the aligned nylon
batches). predict_online gains three outputs the monitoring chapter needs:
the per-cell residuals of the observed part, the SPE of the newest sample
alone (the per-interval statistic of Nomikos and MacGregor), and the forecast
of the remaining trajectories from the score estimate (Wold et al. 2009,
Eq. 4), in engineering units. predict_online_trace gains the per-sample SPE.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
BatchPLS had no way to use a batch before it finished: unfold_and_scale
rejects a truncated batch, and the missing-data projection the library already
has for PCA was reachable for PLS only through the mid-course corrector. Three
methods close that gap, mirroring BatchPCA.

predict_online(batch, upto_k) scales the observed part of the batch, estimates
the scores from those cells alone (trimmed score regression by default, or
SCP or PMP) and maps them to a quality prediction in the original units; it
also returns the residuals, the newest sample's SPE and the forecast of the
rest of the batch. It accepts a truncated batch-so-far or a complete one. At
upto_k equal to the batch length it reproduces predict() exactly, since the
complete row takes the ordinary projection path.

predict_online_trace(batch) does the same at every sample in one call, giving
the evolving prediction of a batch as it would have looked in real time.
online_rmse(X, Y) turns those traces into one error curve per quality
attribute: the estimation error on the training batches, or the prediction
error when the model was fitted without the batch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
BatchMonitor compared each sample's T2 against a limit built from the
end-of-batch score covariance. Score estimates early in a batch are shrunk and
noisy compared with those near its end, so that yardstick was wrong where it
matters most: a normal SBR batch read T2 = 195 after four samples against a
limit of 10. The T2 at sample k now uses the covariance of the reference
batches' score estimates at that sample, with the F limit for the number of
reference batches; the same normal batch reads 1.2. The reference batches'
mean T2 is then A(N-1)/N at every sample, which the tests pin.

The monitor also accepts a BatchPLS model, since both classes now expose
predict_online_trace, and offers the SPE of the newest sample alone
(spe_statistic="instantaneous") beside the cumulative statistic it charted
before, which stays the default. The instantaneous statistic reacts in the
sample a fault begins; on the SBR data it flags batch 34 after 105 samples,
the cumulative one after 112.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Follow-ups from an adversarial read of the new on-line API. The reference
score scatter in BatchMonitor is now taken about zero, the centre of the
training scores, so the quadratic form and its normalisation agree and the
reference batches' mean T2 is A (N - 1) / N at every sample; a rank check
explains the case where fewer cells than components have been observed.
observed_series matches tags by name rather than by position, rejects a
batch that lacks a training tag, and handles the (sequence, tag) layout of
group_by_batch=True. upto_k is coerced with operator.index; online_rmse
rejects an empty X, a non-unique Y index, NaN quality rows and missing Z
rows; the monitor forwards its ridge to every projection. The initial
condition bookkeeping moved into its own helper to keep observed_series
within the complexity budget.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
test_online_prediction.py checks exact identities on a thinned dryer set:
predict_online agrees with predict at the end of a batch, a truncated
batch-so-far equals the full batch cut at upto_k, the forecast reproduces
the observed part, online_rmse ends at the fitted RMSE, both SPE
statistics coincide at the first sample, and a model unfolded with
group_by_batch=True projects the same batch to the same prediction, SPE,
T2 and forecast. BatchMonitor is checked for the A (N - 1) / N mean-T2
invariant of a monitor fitted on its own training batches. The SBR
case-study tests pin the first sustained alarm of each faulty batch
(batch 37 on T2 after 23 samples, batch 34 on the instantaneous SPE after
105) and the reference batches' false-alarm rates.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Two sections on the SBR case-study page: the evolving prediction of the
final quality from a batch-so-far (RMSEE per sample, and batch 4's
particle size against its final prediction and its measured value), and
the on-line monitoring of the two faulty batches against a reference
model on the 51 normal batches, with the trajectory forecast of Wold et
al. (2009). The companion script gains online_monitoring and the
plotting helpers the tests call.

The DuPont page's TempH-1 range now reports what the per-member
contributions show on each component (-3.8 to +6.3 on t2, -3.1 to +2.1
on t3) instead of a range that reproduced on neither.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Minor bump for the new BatchPLS.predict_online, predict_online_trace and
online_rmse methods, the residuals, spe_instantaneous and forecast keys on
both batch models, BatchMonitor on BatchPLS with the spe_statistic option,
and the per-sample T2 normalisation in BatchMonitor. CITATION.cff carries
the same version and today's date.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…low-up), keeping 1.80.0

Main took 1.79.0 for the select_n_components change (#539) and 1.79.1 for
the cross-validation guide (#547) while this branch carried its own 1.79.0
entry for the case studies. The case-study entry is folded into this
branch's 1.80.0 section, which now sits above main's 1.79.1, and the
compare links follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
"""The fitted attributes a batch model must expose for the online helpers."""

@property
def feature_columns_(self) -> pd.Index: ...
def feature_columns_(self) -> pd.Index: ...

@property
def center_(self) -> pd.Series: ...
def center_(self) -> pd.Series: ...

@property
def scale_(self) -> pd.Series: ...
def scale_(self) -> pd.Series: ...

@property
def tag_names_(self) -> Sequence[Hashable]: ...
def tag_names_(self) -> Sequence[Hashable]: ...

@property
def initial_condition_names_(self) -> Sequence[Hashable]: ...
def initial_condition_names_(self) -> Sequence[Hashable]: ...

@property
def n_initial_conditions_(self) -> int: ...
def n_initial_conditions_(self) -> int: ...

@property
def n_timesteps_(self) -> int: ...
spe_window pools the reference SPE values of the samples on either side
of sample k before that sample's chi-squared limit is fitted, the usual
way to steady per-sample limits when few reference batches are available;
the default of 0 keeps the per-sample fit and every existing limit. The
mean trace is never pooled. On the SBR reference set the pooled limits
leave every alarm sample unchanged: the unpooled limit moves by under 2%
between neighbouring samples, so the reference batches' short alarm runs
come from the autocorrelation of their SPE, not from a rough limit, and
the case-study page says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Main took 1.80.0 for the consistency fixes surfaced by executing the
book against the library, so this branch's section moves to 1.81.0,
above main's 1.80.0 entry, with the compare links to match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The case-study script projects the 15 batches removed before model C
onto it (the on-line projection at the last sample of a complete batch)
and reports which lie above its 95% limits: all 15 above the SPE limit,
the six score outliers and batch 37 above the T2 limit as well. The
observability check prints one line instead of a table, the page's
group-contribution passage is shorter and no longer repeats numbers the
figure shows, and a test pins the projection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The SBR case-study page attributed the estimator to Garcia-Munoz, Kourti
and MacGregor (2004), who compared it; the reference is added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…reading of the four anomalous batches

The clock time at each aligned sample, which the workbook carries and the
original study modelled, now enters the batch PCA, the batch PLS and the
batch multiblock PLS beside the ten process tags, so the pace of each batch
is part of what the models see. The pins move with it (batch PCA 0.231 /
0.376, batch PLS 0.266 / 0.410, batch MBPLS 0.370 / 0.472; the complete batch
with the largest SPE is now 41), and the clock time joins the collector level
as the leading contribution of batch 13.

A new section places every batch, block by block, with the group whose
average point is nearer in that block's score plot. Four batches classed good
(2, 3, 6, 7) are placed with the abnormal batches by the trajectory block and
with the good ones by both initial-condition blocks; the contribution from
their nearest abnormal neighbours' average to theirs in the operating-condition
block is carried by a longer cool-down, a shorter and steeper ramp and a
shorter high-speed agitation, with the same peak set point.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The simulated faults are the ones the 1994 AIChE paper describes: 30% more
impurity from the start of one batch and 50% more from halfway through the
other, not 30% in both; the feed flows and the feed temperature carry the
simulation's noise rather than being constant. The per-sample T2 covariance
is credited as the 1995 Technometrics paper states it (a need they note and
set the individual score-chart limits by) and as Garcia-Munoz, Kourti and
MacGregor (2004) compute it. The DuPont page says that the original reference
set also left out the four poor-quality batches and had 36 batches with three
components.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
spe_contributions, score_contributions and t2_contributions (and the PCA,
PLS, BatchPCA and BatchPLS methods that forward to them) used to return an
all-NaN row for any observation with a missing cell, because the scores
were taken as X @ R. A batch with a gap in its record therefore had no
contribution plot at all, even though NIPALS fitted it from its observed
cells.

The scores of an incomplete row are now estimated from its observed cells
with the same missing-data operators as `project` (SCP by default, which
reproduces the NIPALS fit; TSR and PMP available), and each contribution
is that row-specific operator times the cell. Contributions are then
defined at every observed cell and NaN only at the missing ones; complete
rows take the exact complete-data path as before, so their results are
bitwise unchanged. A row with no observed feature raises.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Batch 20 is the one batch above both limits of the batch PCA, and it is
also one of the ten with missing samples, so its contribution plot could
not be drawn and the page fell back to batch 41, the complete batch with
the largest SPE. With contributions now defined for rows with missing
cells, the script reports batch 20's share of the SPE per tag (half of it
in the dryer pressure), the reference test pins that reading, and the page
and changelog describe the change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The attribute is documented as the cumulative per-variable R2 after each
component (it replaced the old R2X_cum), and the complete-data path and
PLS compute it that way. The NIPALS path, taken whenever X has missing
cells, divided each column's residual by its sum of squares after the
previous component instead of the original, so column a held the share
of what was left that component a explained; only the first column was
right. The TSR path never filled the attribute and returned zeros.

Both now use the original column sums of squares. The FMC batch PCA of
the case study exposed it: the collector-tank cells carry the largest
first loading, and their second column read 0.07 where the cumulative
value is 0.78. A test checks the three PCA paths and PLS against the
direct definition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Its missing cells are not a contiguous block of samples: five tags lack
samples 95 to 109 and five more lack cells between samples 34 and 109.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Running every missing-data path on data without missing values showed
one inconsistency: for a PLS model the single-component projection
estimator projected each component onto the observed part of the
loading vector, where NIPALS computes a score by projecting onto the
weight vector and deflating with the loading. With nothing missing the
SCP estimate differed from the model's own scores by up to a score
standard deviation, and an incomplete row did not reproduce the score
the fit had stored for it. PCA was unaffected (its weights are its
loadings), and TSR, the default, reduces to the score map exactly.

operator_for_pattern and project_rows take an optional x_weights, which
PLS.project, projection_matrix, the contribution helpers and the
mid-course corrector now pass. PMP is the least-squares fit onto the
loading plane and, for PLS, does not reduce to the model's scores even
with nothing missing; the docstrings say so.

A new test module runs the NIPALS and TSR fit paths of PCA and the
three score operators on complete data (the bundled dryer batches, the
DuPont batches and the SBR batches) and checks each against the direct
computation, and checks that SCP reproduces the NIPALS scores of
incomplete rows for PLS as it already did for PCA.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Every paper cited on the three case-study pages and in the projection
module's references has an entry in the maintainer's literature database,
so the links point there (the entries carry the DOI and an abstract)
instead of at the DOI resolver, and the references that had no link get
one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
… offline work only

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
@kgdunn
kgdunn merged commit 80d98c2 into main Sep 6, 2026
14 checks passed
@kgdunn
kgdunn deleted the claude/issues-154-156-process-3cu4iq branch September 6, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants