Sampler and results#

mergen.sampler#

Space-filling design via the Stochastic Coordinate Exchange (SCE) engine.

This module exposes four objects:

  • Sampler — configure and run the design construction.

  • SamplingResult — container for design + validation + extras.

  • FocusPoint — denser sampling around a critical region.

  • ExclusionPoint — repel sampling away from a region.

Quick start#

from mergen.space   import ParameterSpace
from mergen.sampler import Sampler

space   = ParameterSpace({'temperature': range(100, 400, 10),
                          'pressure'   : ('continuous', 0.5, 5.0)})
sampler = Sampler(space)
sampler.add_prescribed([[200, 2.5]], in_design=True,  in_optim=False)
sampler.add_focus    ([350, 4.5], spread=1.5,         in_design=True, in_optim=True)
sampler.add_exclusion([100, 0.5], spread=1.0)
sampler.set_design(n_samples=30)
sampler.set_sce(n_restarts=5)
result  = sampler.run(criteria='umaxpro', seed=44)
result.summary()

Algorithm#

The optimisation is a Stochastic Coordinate Exchange (SCE) outer-inner loop:

  • Inner loop — for each restart, repeatedly pick one row of the design and propose new values along one coordinate at a time, accepting changes that improve the criterion. The 1D nature makes the proposal cost O(n) (see mergen.criteria) and reduces multi-dimensional feasibility constraints to one-dimensional box constraints, which is the key advantage demonstrated by Kang (2019).

  • Outer loop — Iterated Local Search (Lourenço, Martin & Stützle 2003): when the inner loop stalls, perturb the current best design and run the inner loop again. The best design across all restarts is returned.

The first restart is seeded by a greedy maximin scheme (Morris & Mitchell 1995); subsequent restarts are seeded by ILS kicks from the current best (rather than random restart) for variance reduction across seeds.

References

Meyer, R. K. & Nachtsheim, C. J. (1995).

The coordinate-exchange algorithm for constructing exact optimal experimental designs. Technometrics, 37(1), 60–69.

Kang, L. (2019).

Stochastic coordinate-exchange optimal designs with complex constraints. Quality Engineering, 31(3), 401–416.

You, Y., Jin, G., Pan, Z. & Guo, R. (2021).

MP-CE method for space-filling design in constrained space with multiple types of factors. Mathematics, 9(24), 3314.

Lourenço, H. R., Martin, O. C. & Stützle, T. (2003).

Iterated Local Search. In Handbook of Metaheuristics, Springer, 320–353.

Morris, M. D. & Mitchell, T. J. (1995).

Exploratory designs for computational experiments. J. Statist. Plan. Infer., 43, 381–402.

Loeppky, J. L., Sacks, J. & Welch, W. J. (2009).

Choosing the sample size of a computer experiment: A practical guide. Technometrics, 51(4), 366–376.

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

Computer aided design of experiments. Technometrics, 11(1), 137–148.

class mergen.sampler.FocusPoint(point, spread=1.0, n_samples=None, include_center=None, in_design=True, in_optim=True)[source]#

Bases: object

A critical region where denser sampling is desired.

Parameters:
  • point (array-like, shape (n_parameters,)) – Grid coordinates of the focus centre.

  • spread (float) – Gaussian kernel width in normalised grid steps.

  • n_samples (int or None) – Number of focus samples to draw. None → auto: max(1, int((2 * d + 1) * spread)).

  • include_center (bool or None) – True → the centre is guaranteed to appear in the design. False → the centre is excluded from the candidate pool. None → stochastic (the centre competes with its neighbours through the Gaussian-weighted draw).

  • in_design (bool) – True → focus samples count toward the n_samples budget. False → focus samples are added on top of n_samples and the total design size grows.

  • in_optim (bool) – True → the optimiser sees these points and pushes optimised points away. False → the optimiser ignores these points (they are only reserved against re-selection).

resolve_n_samples(n_dims)[source]#

Set n_samples from the auto formula if not user-supplied.

Parameters:

n_dims (int)

Return type:

None

class mergen.sampler.ExclusionPoint(point, spread=1.0)[source]#

Bases: object

A region to avoid: the centre is hard-excluded from the candidate pool and its neighbourhood receives a Gaussian soft-repulsion penalty during coordinate-exchange candidate selection.

Parameters:
  • point (array-like, shape (n_parameters,)) – Grid coordinates of the exclusion centre.

  • spread (float) – Gaussian repulsion kernel width in normalised grid steps.

class mergen.sampler.SamplingResult(samples, validation, space)[source]#

Bases: object

Container returned by Sampler.run().

Variables:
  • samples (pd.DataFrame) – Main design (prescribed + focus + optimised points).

  • validation (pd.DataFrame) – Kennard-Stone validation set.

  • sets (dict[str, pd.DataFrame]) – Extra named sets (e.g. {'test': df, 'holdout': df}).

  • designs (dict[str, pd.DataFrame]) – Designs per criterion when multiple criteria are run.

  • space (ParameterSpace)

Parameters:
  • samples (pd.DataFrame)

  • validation (pd.DataFrame)

  • space (ParameterSpace)

PRESCRIBED = 'Prescribed'#
FOCUS = 'Focus'#
OPTIMISED = 'Optimised'#
VALIDATION = 'Validation'#
property best_design: DataFrame#

Alias for samples — the design from the best algorithm.

property best_score: float | None#

Criterion score of the best algorithm’s design, or None.

summary()[source]#

Print a concise design summary to stdout.

Return type:

None

quality_report(metrics='default', criteria_metrics=None, mc_samples=300, verbose=True)[source]#

Compute and print design quality metrics.

Delegates to mergen.metrics.quality_report().

Each metric is reported both as a raw value and as a percentile rank against a Monte Carlo baseline of random designs of the same size, so the table shows not just the value but whether it is good. Pass mc_samples=0 to skip the baseline (values only).

Parameters:
  • metrics ('default' or list of metric names)

  • criteria_metrics (list of criterion names to evaluate post-hoc)

  • mc_samples (number of random designs for the Monte Carlo) – baseline (0 disables it; default 300)

  • verbose (print the metrics table (default True))

Returns:

dict of metric values and (optionally) baseline statistics

Return type:

dict

comparison()[source]#

Compare quality metrics across all criteria in self.designs. Returns a DataFrame (criteria × metrics).

Return type:

DataFrame

plot(kind='pairplot', **kwargs)[source]#

Visualise the design. Delegates to mergen.output.

Parameters:

kind ('pairplot' | '1d' | '2d' | 'distances') –

'quality' | 'all'

Return type:

None

to_csv(filename='design.csv')[source]#
Parameters:

filename (str)

Return type:

None

to_json(filename='design.json')[source]#
Parameters:

filename (str)

Return type:

None

to_markdown(filename='design.md')[source]#
Parameters:

filename (str)

Return type:

None

to_latex(filename='design.tex')[source]#
Parameters:

filename (str)

Return type:

None

to_html(filename='design.html')[source]#
Parameters:

filename (str)

Return type:

None

to_excel(filename='design.xlsx')[source]#
Parameters:

filename (str)

Return type:

None

class mergen.sampler.ComparisonResult(table, results, best, priority)[source]#

Bases: object

Outcome of Sampler.compare().

Variables:
  • table (pandas.DataFrame) – One row per (criterion, algorithm) combination, sorted by the priority metrics; columns hold percentile ranks (0-100, higher is better) against the shared Monte Carlo baseline. The winner is flagged in the best column.

  • results (dict) – Maps (criterion, algorithm) to the full SamplingResult, so any candidate design can be inspected or exported, not only the winner.

  • best (tuple of (str, str)) – The winning (criterion, algorithm) pair.

  • priority (tuple of str) – The metrics that defined the ranking, in order.

property best_result: SamplingResult#

The full SamplingResult of the winning combination, identical to calling run() for that combination with the same seed and n_repeats.

summary()[source]#

Print the ranked comparison table.

Return type:

None

plot(save=False, filename=None, show=True, **kwargs)[source]#

Heat map of the percentile-rank table.

Rows are (criterion, algorithm) combinations, columns are the quality metrics, cell colour encodes the percentile rank; the winning row is starred. Saved under outputs/ when save=True.

Parameters:
Return type:

None

to_markdown(filename)[source]#

Save the ranked comparison table as a Markdown file under the output directory.

Unlike calling comparison.table.to_markdown(...) directly, this writes into outputs/ (creating it if needed), matching where the plots and design exports are saved.

Parameters:

filename (str)

Return type:

None

class mergen.sampler.Sampler(space)[source]#

Bases: object

Space-filling design sampler using Stochastic Coordinate Exchange (SCE).

The sampler is configured fluently — add_* methods register prescribed/focus/exclusion points, set_* methods set sizes and optimiser hyperparameters, and run() produces the design.

Parameters:

space (ParameterSpace)

Examples

>>> space   = ParameterSpace({'x': range(1, 21), 'y': range(1, 21)})
>>> sampler = Sampler(space)
>>> sampler.set_design(n_samples=20)
>>> result  = sampler.run(seed=44)
>>> result.summary()
add_prescribed(points, in_design=True, in_optim=False)[source]#

Add one or more prescribed (fixed) points.

Prescribed points are always present in the final design and are never moved by the optimiser.

Parameters:
  • points (array-like) – Single point [x1, x2, ...] or list of points. All points must lie on the parameter grid.

  • in_design (bool) – True → counted within the n_samples budget. False → added on top of n_samples (the total design size grows accordingly).

  • in_optim (bool) – True → the optimiser sees these points and pushes optimised points away from them. False → the optimiser ignores them (the points are only reserved against being re-selected).

Returns:

self

Return type:

Sampler

add_set(name, points, color=None)[source]#

Add a user-supplied named point set (e.g. an external test set).

The points are validated against the parameter grid, reserved so the optimiser cannot re-select them, and carried into the result as result.sets[name]. They appear in plots under name and in exports as a separate table.

Parameters:
  • name (str) – Label of the set (e.g. 'test'). Must not collide with the built-in labels 'Optimised', 'Validation', 'Prescribed' or 'Focus', nor with a generated extra set or a previously added set.

  • points (array-like) – Single point [x1, x2, ...] or list of points. All points must lie on the parameter grid.

  • color (str, optional) – Matplotlib colour used for this set in plots (e.g. '#3a86ff'). If omitted, a neutral fallback colour is assigned by the plotting layer.

Returns:

self

Return type:

Sampler

load_design(points, name='Existing', color=None)[source]#

Load an existing design instead of optimising a new one.

The supplied points become the design itself: run() skips optimisation entirely and only generates the requested validation set and any extra sets (Kennard-Stone) around them. Use mergen.sequential.extend() instead if you want to grow the design with new optimised points.

Parameters:
  • points (array-like or pandas.DataFrame) – The existing design. A DataFrame is matched to the parameter space by column names; an array must have one column per parameter in space order. All points must lie on the parameter grid.

  • name (str) – Label under which the points appear in plots, summaries and exports (default 'Existing').

  • color (str, optional) – Matplotlib colour for this label in plots. Defaults to the Optimised blue ('#3a86ff').

Returns:

self

Return type:

Sampler

add_focus(point, spread=1.0, n_samples=None, include_center=None, in_design=True, in_optim=True)[source]#

Add a focus point — denser sampling near a critical region.

Parameters:
  • point (grid coordinates of the focus centre)

  • spread (Gaussian kernel width in normalised grid steps)

  • n_samples (number of focus samples (None → auto))

  • include_center (True / False / None) – (guaranteed / excluded / stochastic)

  • in_design (True → within budget; False → extra)

  • in_optim (True → the optimiser sees these points)

Returns:

self

Return type:

Sampler

add_exclusion(point, spread=1.0)[source]#

Add an exclusion point — sampling avoids this region.

The centre is hard-excluded from the candidate pool. Its neighbourhood receives a Gaussian soft-repulsion weight that downweights candidate values during coordinate-exchange selection.

Parameters:
  • point (grid coordinates of the exclusion centre)

  • spread (Gaussian repulsion kernel width)

Returns:

self

Return type:

Sampler

set_design(n_samples=None, n_validation=None, extra_sets=None)[source]#

Configure design size and holdout sets.

Parameters:
  • n_samples (total design size (None → 10 × n_parameters,) – Loeppky, Sacks & Welch 2009).

  • n_validation (validation set size (None → 20% of) – n_samples).

  • extra_sets (dict of {name: size} for additional) – Kennard-Stone holdout sets, e.g. {'test': 10, 'holdout': 5}.

Returns:

self

Return type:

Sampler

set_optimizer(name, **kwargs)[source]#

Configure hyperparameters for an optimisation algorithm.

The named algorithm must be registered in mergen.algorithms (e.g. 'sa', 'sce', 'ese'). Hyperparameters are validated against the algorithm’s get_default_params() schema; unknown keys raise ValueError.

Calling this method does not run the optimiser — it only stores the parameters for later use by run(). You can call it multiple times to configure different algorithms.

Parameters:
  • name (str) – The registered name of the optimiser (e.g. 'sa').

  • **kwargs – Hyperparameters specific to the chosen algorithm. See each algorithm’s documentation for the full list.

Returns:

self – Enables fluent chaining.

Raises:
  • KeyError – If name is not a registered optimiser.

  • ValueError – If any kwargs key is not a valid hyperparameter for the algorithm.

Return type:

Sampler

Examples

>>> sampler.set_optimizer('sa', n_restarts=5, max_iter=10000)
>>> sampler.set_optimizer('sce', n_complexes=4)
>>> sampler.set_optimizer('ese', n_outer=20, n_inner=100)
set_dimension_weights(weights)[source]#

Set per-dimension importance weights for greedy maximin seeding.

Parameters:

weights (dict {name: weight} or list of floats) – (one weight per parameter, in parameter order).

Returns:

self

Return type:

Sampler

run(criteria='umaxpro', algorithm='sa', seed=44, n_repeats=1, priority=None, n_jobs=None, verbose=True)[source]#

Generate the space-filling design.

Parameters:
  • criteria (str, list of str, or BaseCriterion instance) – Optimisation criterion (or list of criteria). Available built-in names: 'umaxpro', 'maxpro', 'phi_p', 'cd2', 'stratified'. Default 'umaxpro'.

  • algorithm (str or list of str, optional) – Name of the optimiser (or list of names) to run. Each name must be a registered optimiser (see mergen.list_optimizers()). When a list is given, every algorithm is run independently and the results are collected in SamplingResult.algorithm_results. Default 'sa'.

  • seed (int or None) – Random seed for reproducibility (default 44).

  • n_repeats (int, default 1) – Number of independent optimisation repeats. Repeat seeds are derived from seed via NumPy’s SeedSequence.spawn; the representative repeat is returned (see priority).

  • priority (tuple of str, optional) – Quality-metric names used to pick the representative repeat when n_repeats > 1. None (default) keeps the repeat with the lowest criterion score (classic best-of-k restarts). When given — e.g. ('min_distance', 'max_abs_correlation') — the repeat whose design lies closest to the Utopia point of these metrics is returned instead (optimise-by-proxy, select-by-objective), which keeps the delivered design stable when near-optimal criterion scores correspond to different coverage trade-offs. Pass the same tuple used in compare() to reproduce its best_result exactly.

  • n_jobs (int or None, optional) – Number of parallel workers for the multi-algorithm dispatcher. None (default) and 1 run sequentially. Positive integers request that many workers (up to the number of CPUs). Negative integers follow the joblib convention: -1 for all CPUs, -2 for all but one, and so on. Parallelism applies only across distinct algorithms; the per-algorithm restart loop is ILS-based and therefore intentionally serial. When n_jobs is greater than the number of algorithms, the extra workers are unused.

  • verbose (bool, optional) – Print progress information. Default True.

Returns:

SamplingResult – Contains the (per-algorithm) optimised designs and quality metadata. When a single algorithm is requested, SamplingResult.samples exposes its design directly; when multiple algorithms are requested, use SamplingResult.algorithm_results to access each individually and SamplingResult.comparison() to tabulate them.

Return type:

SamplingResult

compare(criteria=None, algorithms=None, priority=('min_distance', 'max_abs_correlation'), mc_samples=300, seed=44, n_repeats=5, n_jobs=None, verbose=True)[source]#

Run a criterion/algorithm sweep and rank the resulting designs.

Every (criterion, algorithm) combination is optimised with the current sampler configuration. Because raw criterion scores are on incomparable scales, designs are ranked on criterion-agnostic quality metrics expressed as percentile ranks against a single shared Monte Carlo baseline of random designs of the same size (Joseph 2016; Pronzato & Mueller 2012).

Parameters:
  • criteria (list of str, optional) – Criteria to sweep. If None, all criteria compatible with the space are used: nominal-supporting criteria when the space contains a nominal factor, all remaining criteria otherwise. An explicit list is used as given (with a warning if it mixes in criteria that do not match the factor types).

  • algorithms (list of str, optional) – Optimisers to sweep. If None, ['sa'].

  • priority (tuple of str) – Quality metrics treated as competing objectives when selecting the best design. All are on a common 0-100 percentile scale (higher is better) and are honoured jointly via a Pareto/Utopia rule (Lu, Anderson-Cook & Robinson 2011): the non-dominated designs are kept, and the one closest to the Utopia point (every metric = 100) is chosen. No weights are needed and no single metric dominates. Available metrics: min_distance, mean_distance, cv_distances, minimax, max_abs_correlation, projection_cd2.

  • mc_samples (int, default 300) – Size of the shared Monte Carlo baseline.

  • seed (int, default 44) – Base seed. All randomness (the repeats and the Monte Carlo baseline) is derived from it, so the whole comparison reproduces exactly from this one number.

  • n_repeats (int, default 5) – Number of independent optimiser runs per combination. Each run uses a distinct seed derived from seed via NumPy’s SeedSequence.spawn (independent, not consecutive integers); the best-scoring repeat represents the combination, exactly as run(seed, n_repeats) would deliver it, and the table reports that design’s metric percentiles. More repeats therefore improve every combination’s delivered design rather than averaging over seed luck. Use n_repeats=1 for a single run.

  • n_jobs (int or None, default None) – Number of parallel workers for the combination x repeat runs, following the joblib convention (None or 1 -> one worker, -1 -> all cores). The runs are independent, so this scales well; results are identical regardless of n_jobs.

  • verbose (bool, default True) – Print progress and the final ranking table.

Returns:

ComparisonResult.table (ranked DataFrame of percentile ranks), .results (dict mapping (criterion, algorithm) to the full SamplingResult), .best (winning key) and .priority.

Return type:

ComparisonResult