Sequential utilities#

Sequential and augmented design utilities for Mergen.

This module collects design-time operations that work on existing designs: extending them with new points, filling in around reference locations, sub-selecting space-filling subsets from a pool, and assigning execution orders.

All functions in this module are surrogate-free — they look only at parameter-space geometry, never at simulation outputs. Adaptive sampling, active learning, expected improvement, and batch Bayesian optimisation are intentionally out of scope; they belong in a surrogate-modelling package.

Public API#

extend(sampler, existing_design, n_new, …)

Append n_new space-filling points to an existing design while preserving every input row (Wang 2003; Qian 2009).

fill_around(sampler, reference_points, n_new, …)

Generate n_new points that space-fill around a set of reference points; the reference points are criterion-visible but excluded from the output.

subsample(sampler, pool, n_select, anchor=’center’)

Pick n_select space-filling points from an arbitrary candidate pool via Kennard-Stone selection in normalised coordinates (Kennard & Stone 1969).

run_order(sampler, design, anchor=’center’, column=’run_order’)

Assign an execution rank so that every cumulative prefix of the design remains space-filling — useful for serial execution, early stopping, interim analyses.

Notes

The first argument of every public function is a Sampler. The sampler supplies the parameter-space metadata (axis ranges, parameter names, constraints) but its existing state — prescribed points, focus regions, exclusions, n_samples, n_validation — is preserved across the call via the private snapshot/restore hooks _snapshot_state / _restore_state.

References

Kennard, R. W. & Stone, L. A. (1969). Computer aided design of

experiments. Technometrics, 11(1), 137-148.

Wang, G. G. (2003). Adaptive response surface method using

inherited Latin hypercube design points. Journal of Mechanical Design, 125(2), 210-220.

Qian, P. Z. G. (2009). Nested Latin hypercube designs.

Biometrika, 96(4), 957-970.

mergen.sequential.extend(sampler, existing_design, n_new, criteria='cd2', algorithm='sa', n_validation=0, seed=44, verbose=True)[source]#

Extend an existing design by n_new space-filling points.

Every row of existing_design is treated as a fixed, criterion-visible anchor; n_new additional points are then optimised on top of them. The final design has shape (len(existing_design) + n_new, d) and the first len(existing_design) rows are exactly the input rows.

This is the surrogate-free augmentation strategy of Wang (2003) and Qian (2009): preserve the historical runs, add new runs that maximise space-filling of the combined design.

Parameters:
  • sampler (Sampler) – Parameter space, constraints, and any pre-set design state (focus regions, exclusions, etc.). The sampler is not permanently modified; any state added during this call is rolled back on exit.

  • existing_design (np.ndarray or DataFrame or list) – Points that must appear in the final design unchanged. DataFrame columns must include all parameter names.

  • n_new (int) – Number of new points to add.

  • criteria (str) – Passed through to Sampler.run(). Defaults match the one-shot workflow.

  • algorithm (Union[str, List[str]]) – Passed through to Sampler.run(). Defaults match the one-shot workflow.

  • n_validation (int) – Passed through to Sampler.run(). Defaults match the one-shot workflow.

  • seed (Optional[int]) – Passed through to Sampler.run(). Defaults match the one-shot workflow.

  • verbose (bool) – Passed through to Sampler.run(). Defaults match the one-shot workflow.

Returns:

SamplingResult

Return type:

SamplingResult

References

Wang, G. G. (2003). Journal of Mechanical Design, 125(2). Qian, P. Z. G. (2009). Biometrika, 96(4).

mergen.sequential.fill_around(sampler, reference_points, n_new, criteria='cd2', algorithm='sa', n_validation=0, seed=44, verbose=True)[source]#

Generate n_new points that space-fill the region around a set of reference points.

Reference points are visible to the criterion (so new points keep their distance from them) but are not included in the final design. Use this when you have e.g. literature or pilot points that you do not want to re-run, and you want subsequent runs to cover the rest of the space efficiently.

Parameters:
  • sampler (Sampler)

  • reference_points (np.ndarray or DataFrame or list) – Coordinates of the points to fill around. Not included in the output.

  • n_new (int) – Number of new points to generate.

  • criteria (str) – See Sampler.run().

  • algorithm (Union[str, List[str]]) – See Sampler.run().

  • n_validation (int) – See Sampler.run().

  • seed (Optional[int]) – See Sampler.run().

  • verbose (bool) – See Sampler.run().

Returns:

SamplingResult – Contains exactly n_new design points.

Return type:

SamplingResult

mergen.sequential.subsample(sampler, pool, n_select, anchor='center')[source]#

Pick n_select space-filling points from a candidate pool.

Uses Kennard-Stone selection in normalised parameter coordinates: each pick maximises the minimum distance to the already-picked points.

Parameters:
  • sampler (Sampler) – Used only for parameter-space metadata (axis ranges, names). The sampler is not modified.

  • pool (np.ndarray or DataFrame) – Candidate pool. DataFrame columns must include all parameter names; the DataFrame index and extra columns are preserved on the output. ndarrays return a plain ndarray.

  • n_select (int) – Number of points to select. 1 n_select len(pool).

  • anchor ({'center', 'maximin', int, None}, default 'center') – How to seed the selection. See _kennard_stone_array for details. 'center' is the most common choice; 'maximin' reproduces the original Kennard-Stone (1969) behaviour.

Returns:

np.ndarray or DataFrame – The selected rows, in selection order. Type matches the input.

Return type:

Union[np.ndarray, pd.DataFrame]

References

Kennard, R. W. & Stone, L. A. (1969). Technometrics, 11(1),

137-148.

mergen.sequential.run_order(sampler, design, anchor='center', column='run_order')[source]#

Assign an execution order so that every cumulative prefix of the design remains space-filling.

Implements a sequential Kennard-Stone reordering: row 0 is the chosen anchor; row k (for k > 0) is the design point most distant (in normalised Euclidean distance) from rows 0, …, k-1. Stopping the run after any prefix yields a balanced sub-design rather than a clustered one.

Use this when:

  • Runs are executed serially (wetlab, paid HPC time, single-node simulations)

  • You may stop early or hit a budget cut

  • Interim analyses or progress reporting may happen before completion

Parameters:
  • sampler (Sampler) – Provides parameter-space metadata for normalisation.

  • design (np.ndarray or DataFrame) – Design to reorder. DataFrame columns must include all parameter names; existing DataFrame columns are preserved.

  • anchor ({'center', 'maximin', int, None}, default 'center') – How to pick the first run. See subsample().

  • column (str, default 'run_order') – Name of the column added to the returned DataFrame holding the execution rank (0 = first to run).

Returns:

DataFrame – A copy of design reordered by execution rank, with the new column column. The original DataFrame index is preserved.

Return type:

pd.DataFrame

References

Kennard, R. W. & Stone, L. A. (1969). Technometrics, 11(1),

137-148.

mergen.sequential.k_fold_split(sampler, design, k, anchor='center')[source]#

Split a design into k space-filling folds for cross-validation.

Returns k (train_indices, test_indices) pairs in the same format as sklearn.model_selection.KFold. Within each pair, the test fold and the training set are both space-filling — every fold covers the parameter space rather than clustering in a sub-region, so cross-validation estimates are not biased by an unlucky split.

Algorithm#

The design is first reordered with run_order() (sequential Kennard-Stone). Then a round-robin assignment distributes rows to folds: row 0 → fold 0, row 1 → fold 1, …, row k-1 → fold k-1, row k → fold 0, and so on. Because consecutive rows of the KS-ordering are placed as far apart as possible, each fold ends up with one representative from every region of the space.

param sampler:

Provides parameter-space metadata for the normalisation used inside run_order().

type sampler:

Sampler

param design:

The full design to split. DataFrame columns must include all parameter names.

type design:

np.ndarray or DataFrame

param k:

Number of folds. 2 k len(design). Folds are as balanced as possible — the first len(design) mod k folds receive one extra row each.

type k:

int

param anchor:

First-point strategy for the underlying KS ordering. See subsample().

type anchor:

{‘center’, ‘maximin’, int, None}, default ‘center’

returns:

list of (train_indices, test_indices) – Position-based indices (0 idx < len(design)) into the input design. Compatible with design.iloc[idx] for DataFrames and design[idx] for ndarrays.

References

Kennard, R. W. & Stone, L. A. (1969). Technometrics, 11(1),

137-148.

Qian, P. Z. G. (2012). Sliced Latin hypercube designs.

Journal of the American Statistical Association, 107(497), 393-399. [Related concept of slicing a design into space-filling sub-designs.]

Parameters:
  • sampler (Sampler)

  • design (Union[np.ndarray, pd.DataFrame])

  • k (int)

  • anchor (Union[str, int, None])

Return type:

List[Tuple[np.ndarray, np.ndarray]]

mergen.sequential.nested(sampler, n_outer, n_inner, criteria='cd2', algorithm='sa', seed=44, verbose=True)[source]#

Build a nested pair of designs: a small inner design that is a subset of a larger outer design, both space-filling.

Useful for multi-fidelity computer experiments, hierarchical sensitivity studies, and any setting where two budget tiers run on the same parameter space (e.g. fast/slow simulation, pilot vs. main wetlab study). Every row of inner appears verbatim in outer.

Algorithm#

  1. Build the outer design via Sampler.run() on the current Sampler state (n_samples = n_outer).

  2. Pick the inner design as a maximin Kennard-Stone subsample of the outer (subsample() with anchor='maximin').

The resulting inner design is space-filling within outer, and every outer point is space-filling in the full parameter space.

The classical He & Qian (2011) construction enforces a strict LHS-preserving nesting; this pragmatic implementation does not guarantee that, which is acceptable for the typical use case of multi-fidelity computer experiments (where space-fillingness matters more than the LHS marginals of the inner design). A strict LHS-nested variant is planned for a future release.

param sampler:

type sampler:

Sampler

param n_outer:

Size of the outer (larger) design.

type n_outer:

int

param n_inner:

Size of the inner (smaller) design. 1 n_inner < n_outer.

type n_inner:

int

param criteria:

Passed to Sampler.run() when building the outer design.

param algorithm:

Passed to Sampler.run() when building the outer design.

param seed:

Passed to Sampler.run() when building the outer design.

param verbose:

Passed to Sampler.run() when building the outer design.

returns:
  • outer (DataFrame, shape (n_outer, d + 1)) – The outer design with one extra boolean column 'in_inner' flagging the rows that belong to the inner design.

  • inner (DataFrame, shape (n_inner, d + 1)) – The inner design (subset of outer, in KS selection order). Same columns as outer; the 'in_inner' column is constant True.

References

He, X. & Qian, P. Z. G. (2011). Nested orthogonal array-based

Latin hypercube designs. Biometrika, 98(3), 721-731.

Qian, P. Z. G. (2009). Nested Latin hypercube designs.

Biometrika, 96(4), 957-970.

Parameters:
Return type:

Tuple[pd.DataFrame, pd.DataFrame]