Parameter space#
mergen.space#
ParameterSpace : defines the parameter grid for the sampling design. GridSampler : memory-efficient bijective grid representation.
Supported parameter types#
- Discrete (explicit values):
[100, 200, 300] list / tuple range(10, 110, 10) Python range np.arange(0.1, 1.1, 0.1) numpy array np.linspace(0, 1, 10) any 1-D iterable
Continuous (Mergen builds the grid):
('continuous', 0.5, 5.0) linear grid
('continuous', 1e-4, 1e-1, 'log') log-spaced grid
Integer (Mergen builds the grid):
('integer', 2, 10) all integers in [min, max]
('integer', 8, 256, 'log') log-spaced integers (rounded, unique)
Usage#
# Dict — all at once
space = ParameterSpace({
'voltage': [100, 200, 300, 400],
'pressure': ('continuous', 0.5, 5.0),
'lr': ('continuous', 1e-4, 1e-1, 'log'),
'n_layers': ('integer', 2, 10),
'batch': ('integer', 8, 256, 'log'),
})
# Fluent — one at a time
space = ParameterSpace()
space.add_parameter('voltage', [100, 200, 300])
space.add_parameter('pressure', ('continuous', 0.5, 5.0))
# Constrained
space.add_constraint(lambda p: p['x'] + p['y'] <= 10)
References
Morris & Mitchell (1995), J. Statist. Plan. Infer. 43. [greedy maximin seed]
- class mergen.space.ParameterSpace(parameters=None, resolution=100)[source]#
Bases:
objectN-dimensional discrete parameter space for space-filling design.
Each parameter is internally represented as a sorted 1-D float array (the grid). Continuous and integer parameters are automatically discretised to a grid; SA operates on this grid via coordinate swap.
- Parameters:
Examples
>>> from mergen.space import ParameterSpace >>> space = ParameterSpace({ ... 'voltage': [100, 200, 300, 400], ... 'pressure': ('continuous', 0.5, 5.0), ... 'lr': ('continuous', 1e-4, 1e-1, 'log'), ... }) >>> space.n_parameters 3
- add_parameter(name, spec, resolution=None)[source]#
Add a parameter axis to the space.
- Parameters:
name (str) – Parameter name — used as column header in output DataFrames. May contain spaces, Greek letters, LaTeX, or any Unicode.
spec (array-like or tuple) –
Supported formats:
[100, 200, 300] # discrete explicit range(10, 110, 10) # discrete range np.arange(0.1, 1.1, 0.1) # discrete numpy ('continuous', 0.5, 5.0) # linear grid ('continuous', 1e-4, 1e-1, 'log') # log grid ('integer', 2, 10) # integer grid ('integer', 8, 256, 'log') # log-integer grid ('continuous', 0.5, 5.0, {'resolution': 500}) # custom res ('ordinal', ['low', 'med', 'high']) # ordered categories ('nominal', ['A', 'B', 'C']) # unordered categories
Ordinal and nominal parameters are stored internally as integer level indices
[0, 1, ..., k-1]; the string labels are kept for input/output round-tripping. Which criteria can be used with each type is enforced bySampler.run().resolution (int, optional) – Grid size for continuous / integer-log parameters. Overrides the space-level resolution for this parameter only. Default: space-level resolution (set at construction).
- Returns:
self — enables fluent chaining.
- Return type:
- add_constraint(fn)[source]#
Add a feasibility constraint.
The callable receives a single dict mapping parameter names to their current values and must return
Truefor feasible points.- Parameters:
fn (callable) –
Signature:
fn(p: dict) -> boolExamples:
lambda p: p['x'] + p['y'] <= 10 lambda p: p['pressure'] * p['temperature'] < 1000 lambda p: p['lr'] < 1e-2 or p['n_layers'] <= 3
- Returns:
self
- Return type:
- set_resolution(resolution)[source]#
Update the default grid resolution for continuous / integer-log parameters and rebuild affected grids.
Note: parameters added with an explicit
resolutionoverride are not affected.- Parameters:
resolution (int) – New default resolution (>= 2).
- Return type:
- property param_types: Dict[str, str]#
Dict mapping parameter name -> type. Possible values are
'discrete','continuous','integer','ordinal', or'nominal'.
- category_labels(name)[source]#
Return the level labels of a nominal or ordinal parameter.
Returns an empty list for numeric parameters. The order matches the internal integer level indices
[0, 1, ..., k-1], socategory_labels(name)[i]is the label for leveli.
- property is_mask: ndarray#
Boolean mask, one entry per parameter (in
namesorder), marking which parameters are nominal. Useful for downstream code that needs to switch metrics on a per-column basis (Wilson & Martinez 1997 HEOM).
- property candidate_pool: ndarray#
Full Cartesian product of all parameter grids, filtered by constraints. Shape: (n_candidates, n_parameters).
The result is cached; invalidated automatically when parameters or constraints are added.
- property granges: ndarray#
Per-dimension range of the feasible candidate pool. Never zero (degenerate dimensions get range = 1e-9).
- property centroid: ndarray#
Nearest feasible grid point to the centroid of the candidate pool.
Computed as the mean of all feasible candidates, then snapped to the closest grid point.
- Returns:
np.ndarray, shape (n_parameters,)
Example
>>> sampler.add_prescribed([space.centroid])
- property corners: ndarray#
Feasible corner points of the parameter space.
Returns all combinations of per-parameter min/max values that satisfy the feasibility constraints. Infeasible corners are silently excluded.
- Returns:
np.ndarray, shape (n_feasible_corners, n_parameters)
Example
>>> sampler.add_prescribed(space.corners)
- property bounds_as_dict: Dict[str, tuple]#
{name: (min, max)}.- Returns:
dict[str, tuple]
Example
>>> space.bounds_as_dict {'temperature': (100.0, 400.0), 'pressure': (0.5, 5.0)}
- Type:
Parameter bounds as a dict
- random_point(seed=44)[source]#
Return a single random feasible point from the candidate pool.
- Parameters:
seed (int, default 44) – Random seed for reproducibility. Pass
seed=Nonefor a different point each call.- Returns:
np.ndarray, shape (n_parameters,)
- Return type:
Example
>>> sampler.add_prescribed([space.random_point()]) >>> space.random_point(seed=None) # different each call
- normalise(points)[source]#
Map points to [0, 1]^d using the feasible pool’s min/range.
- Parameters:
points (array-like, shape (n,) or (n, d))
- Returns:
np.ndarray, same shape as input
- Return type:
- denormalise(points)[source]#
Inverse of
normalise().
- distance(x, y)[source]#
Normalised Euclidean distance between two points in [0, 1]^d.
Both points are normalised before distance computation so that all parameter axes contribute equally regardless of scale.
- Parameters:
x (array-like, shape (d,))
y (array-like, shape (d,))
- Returns:
float in [0, sqrt(d)]
- Return type:
- is_valid()[source]#
Return True if the space has at least one parameter and one candidate.
- Return type:
- on_grid(point)[source]#
Return the row index of point in the candidate pool, or -1.
- Parameters:
point (array-like, shape (n_parameters,))
- Return type:
- validate_point(point, label='Point')[source]#
Assert that point lies on the grid.
- Parameters:
point (array-like, shape (n_parameters,))
label (str, prefix for the error message)
- Returns:
pt (np.ndarray, shape (n_parameters,))
- Raises:
ValueError if the point is not on the grid. –
- Return type:
- grid_sampler()[source]#
Return a
GridSamplerfor this space.- Return type:
- class mergen.space.GridSampler(space)[source]#
Bases:
objectBijective mapping between integer indices and grid points.
Represents the full Cartesian product grid without materialising it in memory. Uses a mixed-radix number system:
Mathematical basis#
For a grid with n_1 × n_2 × … × n_d points, define strides:
s_k = prod(n_{k+1}, ..., n_d)
Then for index i:
level_k = (i // s_k) mod n_k x_k = values_k[level_k]
This is the standard mixed-radix decomposition — exact and reversible.
Memory#
O(d × max_levels) for the value arrays only. A 10^10-point grid uses ~48 bytes vs ~800 GB for a full array.
- param space:
- type space:
ParameterSpace
References
Morris & Mitchell (1995), J. Statist. Plan. Infer. 43.
- FULL_GREEDY_THRESHOLD: int = 500000#
Grids with fewer candidates than this are iterated exactly in greedy. Above the threshold, random sampling is used (unbiased in expectation).
- index_to_point(idx)[source]#
O(d) bijection: integer index → grid point.
- Parameters:
idx (int in [0, n_candidates))
- Returns:
np.ndarray, shape (d,)
- Return type:
- point_to_index(point)[source]#
O(d) bijection: grid point → integer index.
- Parameters:
point (array-like, shape (d,))
- Returns:
int — index in [0, n_candidates), or -1 if off-grid.
- Return type:
- random_point_excluding(reserved, max_tries=100000, rng=None)[source]#
Draw a uniformly random feasible grid point not in reserved.
For large grids where
len(reserved) << n_candidatesthe rejection rate is negligible and this terminates in O(1) expected tries.- Parameters:
max_tries (int, default 100000) – Upper bound on rejection sampling attempts.
rng (numpy.random.Generator, optional) – Random source. If
None(default), Python’s globalrandommodule is used for backward compatibility with existing callers. Pass an explicit generator to make the call reproducible independently of the global random state — essential when running under joblib workers, where each process has its own global state that is not synchronised with the parent process.
- Returns:
(point, index) ((np.ndarray, int) or (None, None) if exhausted)
- greedy_maximin_seed(selected, budget, reserved, weights=None)[source]#
Greedy farthest-point seeding for SA initialisation.
Adds budget points to selected by iteratively choosing the candidate that maximises the minimum distance to already-selected points (weighted maximin criterion).
- Small grids (≤
FULL_GREEDY_THRESHOLD): Exact — iterates every non-reserved index. O(N × n × d).
- Large grids (>
FULL_GREEDY_THRESHOLD): Samples
K = min(N, max(10_000, 50 × n_selected))random candidates per step. Unbiased in expectation; every point has positive probability of selection.
- Parameters:
selected (np.ndarray, shape (n, d) — anchor points)
budget (int — number of additional points to add)
reserved (set of int — indices already in use (updated in-place))
weights (array-like, shape (d,), optional) – Per-dimension importance weights for distance computation. None → uniform weights.
- Returns:
selected (np.ndarray, shape (n + budget, d))
reserved (set (updated))
References
Morris & Mitchell (1995), J. Statist. Plan. Infer. 43.
- Small grids (≤
- balanced_lhs_seed(selected, budget, reserved, rng=None, max_repair_attempts=50)[source]#
Build a balanced Latin-Hypercube initial design.
Adds
budgetrows on top of the anchor rows inselectedso that the resulting full design (anchors + new rows) is balanced on every parameter axis: each level appears eitherfloor(n_total / L_v)orceil(n_total / L_v)times, whereL_vis the number of levels on axis v.When the anchor rows over-use a level (frequent in user scenarios with many prescribed points at the same value), the excess is redistributed to under-used levels so the whole design stays as balanced as possible.
This is the level-collapsing approach of Joseph, Gul & Ba (2018) adapted to a fixed grid.
- Parameters:
selected (np.ndarray, shape (k, d)) – Anchor rows that must appear in the output (e.g. prescribed and focus points). Pass
np.empty((0, d))if none.budget (int) – Number of additional rows to add.
len(selected) + budgetequals the final design sizen_total.reserved (set) – Grid indices already in use; updated in place to include the new rows’ indices.
rng (numpy.random.Generator, optional) – RNG for the level-pool shuffles.
None→ freshdefault_rng(44).max_repair_attempts (int, default 50) – Number of attempts to repair constraint violations via in-axis swaps before giving up.
- Returns:
design (np.ndarray, shape (k + budget, d)) – Anchor rows stacked on top of the new balanced LHS rows.
reserved (set (updated))
References
- Joseph, V. R., Gul, E. & Ba, S. (2018). Designing computer
experiments with multiple types of factors: the MaxPro approach. Journal of Quality Technology, 50(4).
- Morris, M. D. & Mitchell, T. J. (1995).
J. Statist. Plan. Infer., 43, 381–402.
- McKay, M. D., Conover, W. J. & Beckman, R. J. (1979).
Technometrics, 21(2), 239–245.
- Parameters:
space (ParameterSpace)