gemseo_box_subdivision.subdivisions.multi_resolution#

A subdivision described by one categorical variable per level of refinement.

A BoxSubdivision spends \(n m\) binaries to reach \(m\) subdivisions per component. This one spends \(n m L\) to reach \(m^L\), by choosing a box with one categorical variable per level instead of one over the whole subdivision: the levels are the digits of the box index in base \(m\).

Classes

MultiResolution(lower_bounds, upper_bounds, ...)

A subdivision of a design space into \(m^L\) boxes per component.

class gemseo_box_subdivision.subdivisions.multi_resolution.MultiResolution(lower_bounds, upper_bounds, branching, levels)[source]#

Bases: object

A subdivision of a design space into \(m^L\) boxes per component.

The lower bound of a component is the sum of the fractions of the design space that its levels select,

\[\ell_j(\alpha) = L_j + \Delta_j \sum_{k=1}^{L} m^{-k} \, d_k(\alpha_j), \qquad u_j(\alpha) = \ell_j(\alpha) + \Delta_j \, m^{-L},\]

with \(\Delta_j = U_j - L_j\) and \(d_k \in \{0, \dots, m-1\}\) the subdivision selected at level \(k\), one-hot encoded.

Two consequences make this usable where a flat subdivision is not.

  • The resolution grows as a power while the binaries grow as a product, so a resolution of \(1024\) over five variables costs \(100\) binaries instead of \(5120\). Since the density a budget can support is bounded by the binaries of the master and not by the boxes, this is what moves that bound.

  • The bounds stay affine in the one-hot variables and the width does not depend on them at all, so the mapping is bilinear in \((\xi, \alpha)\) exactly as the one-level mapping is, and the post-optimal sensitivity of the Benders formulation applies unchanged.

The counterpart is the model class: the cuts of the master are linear in the one-hot variables, so over the digits they are additive. They can represent what a level contributes on its own, and not that the contribution of a fine digit depends on the coarse digit it sits inside.

Parameters:
  • lower_bounds (dict[str, ndarray])

  • upper_bounds (dict[str, ndarray])

  • branching (int)

  • levels (int)

LEVEL_TEMPLATE: str = '{name}_level_{level}'#

The template naming the categorical variable of a level.

NORMALIZED_SUFFIX: str = '_normalized'#

The suffix naming the normalized variable of a design variable.

ONE_HOT_SUFFIX: str = '_box'#

The suffix naming the one-hot variable of a categorical variable.

property variable_names: tuple[str, ...]#

The names of the design variables.

property sizes: dict[str, int]#

The size of each design variable.

property branching: int#

The number of subdivisions of a component at each level.

property levels: int#

The number of levels.

property resolution: int#

The number of subdivisions of a component the levels reach.

property n_binaries: int#

The number of binaries of the master problem.

This is \(n m L\), against the \(n m^L\) a BoxSubdivision of the same resolution would need.

property n_boxes: int#

The number of boxes the levels can select.

property max_step: int#

The radius at which the trust region stops constraining the master.

The distance of the master counts the one-hot groups a candidate changes, and this subdivision has one group per level per component, against the one per component of a BoxSubdivision.

Note

As for a flat subdivision, this is not the radius to use: it marks where the region stops constraining. A radius letting the master move a couple of whole variables, that is 2 * levels, is what the measurements support.

get_level_name(name, level)[source]#

Return the name of the categorical variable of a level.

Parameters:
  • name (str) – The name of the design variable.

  • level (int) – The level, counted from one.

Returns:

The name of the categorical variable.

Return type:

str

get_one_hot_name(name, level)[source]#

Return the name of the one-hot variable of a level.

A categorical variable is registered under the name of its one-hot encoding, as create_box_design_space() does, so this is the name the design space, the master and the mapping all use.

Parameters:
  • name (str) – The name of the design variable.

  • level (int) – The level, counted from one.

Returns:

The name of the one-hot variable.

Return type:

str

get_normalized_name(name)[source]#

Return the name of the normalized variable of a design variable.

Parameters:

name (str) – The name of the design variable.

Returns:

The name of the normalized variable.

Return type:

str

get_digit_values(level)[source]#

Return what each digit of a level is worth, as a fraction of a range.

Parameters:

level (int) – The level, counted from one.

Returns:

The fraction of the range each subdivision of the level selects.

Return type:

ndarray

compute_bounds(name, one_hots)[source]#

Return the bounds of the box the levels select.

Parameters:
  • name (str) – The name of the design variable.

  • one_hots (Iterable[ndarray]) – The one-hot vector of each level, coarsest first.

Returns:

The lower and upper bounds of the box.

Return type:

tuple[ndarray, ndarray]

locate(name, value)[source]#

Return the one-hot vector of each level holding a design value.

Parameters:
  • name (str) – The name of the design variable.

  • value (ndarray) – The design value.

Returns:

The one-hot vector of each level, coarsest first.

Return type:

list[ndarray]

create_design_space(design_space=None, value=None)[source]#

Return the design space of the master and of the sub-problem.

The normalized variables of the sub-problem span \([0, 1]\) whichever box is selected, and each level is a categorical variable weighted by ones, so that the distance of the trust region counts the digits a candidate changes.

Parameters:
  • design_space (DesignSpace | None) – Unused, the bounds being held by this object.

  • value (dict | None) – The design value the levels should start from, by variable name. If None, start from the center of the design space.

Returns:

The design space.

Return type:

CatalogueDesignSpace