Skip to content

Core Base API

Extracted documentation from src/mdotoolbox/core/base.py.

DoE

Attributes

x (list| np.ndarray): Input samples as a matrix (n_samples x n_vars)
        or vector (for 1D problems). Each row represents one sample point.
y (dict| list, np.ndarray): Output evaluations. Can be:
    - dict: Multiple outputs with named keys {name: array of values}
    - list/array: Single output (converted to dict with key 'y')
constraint_violation (np.ndarray| None): Optional array of constraint
    violation values for each sample. None indicates no constraints or
    all points are feasible. Default is None.

Examples

>>> # Single output

>>> doe = DoE(x=[[1, 2], [3, 4]], y=[0.5, 1.2])
>>>
>>> # Multiple outputs with constraints
>>> doe = DoE(
...     x=[[1, 2], [3, 4]],
...     y={'obj': [0.5, 1.2], 'c1': [0.1, -0.2]},
...     constraint_violation=[0.0, 0.2]
... )

Raises:
    TypeError: If x or y are not of the correct type.
    ValueError: If dimensions of x, y, and constraint_violation don't match.

update_DoE

Examples

>>> doe = DoE(x=[[1, 2]], y=[0.5])
>>> doe.update_DoE(x_n=[3, 4], y_n=1.2)
>>> print(len(doe.x))  # Now has 2 samples
2

to_dict

Examples

>>> doe = DoE(x=[[1, 2]], y={'obj': [0.5]}, constraint_violation=[0.1])
>>> data = doe.to_dict()
>>> print(data.keys())
dict_keys(['x', 'obj', 'constraint_violation'])

get_feasible_indices

Examples

>>> doe = DoE(x=[[1], [2], [3]], y=[1, 2, 3],
...           constraint_violation=[0, 0.1, 0.0001])
>>> feasible_idx = doe.get_feasible_indices(tol=0.01)
>>> print(feasible_idx)  # Points 0 and 2
[0 2]

get_best_feasible

Examples

>>> doe = DoE(
...     x=[[1], [2], [3]],
...     y={'obj': [10, 5, 8]},
...     constraint_violation=[0.5, 0.0, 0.1]
... )
>>> x_best, f_best, cv_best = doe.get_best_feasible(tol=0.2)
>>> print(f"Best: x={x_best}, f={f_best}, cv={cv_best}")
Best: x=[3], f=8, cv=0.1

get_f_min

Examples

>>> doe = DoE(x=[[1], [2]], y={'obj': [10, 5]})
>>> f_min = doe.get_f_min()
>>> print(f_min)
5

Function

Attributes

func (Callable): The actual function to evaluate. Should accept
    individual arguments matching the variable names in x.
x (Union[str, list[str], np.ndarray[str]]): Variable names that the
    function depends on. Can be a single variable name or a list of names.
name (str, optional): Descriptive name for the function (e.g., "drag",
    "lift", "constraint_1"). Defaults to empty string.

Examples

>>> # Simple quadratic function

>>> def quad(x, y):
...     return x**2 + y**2
>>> f = Function(func=quad, x=['x', 'y'], name='quadratic')
>>>
>>> # Constraint function
>>> def constraint(x):
...     return x - 5
>>> c = Function(func=constraint, x='x', name='lower_bound')

Raises:
    TypeError: If func is not callable, x is not string-like, or name is not a string.

Notes

The function is called with positional arguments in the order specified by x. For example, if x=['a', 'b', 'c'], func will be called as func(a_val, b_val, c_val).

Constraint

Attributes

func (Function): The constraint function wrapped in a Function object.
ctype (str, optional): Constraint type. Must be one of:
    - 'ge': Greater than or equal (func(x) >= value)
    - 'le': Less than or equal (func(x) <= value)
    - 'eq': Equal to (func(x) = value)
    Defaults to 'ge'.
value (Union[int, float], optional): Right-hand side value of the
    constraint. Defaults to 0.0.

Examples

>>> # Box constraint: x >= 0

>>> def x_func(x):
...     return x
>>> f = Function(func=x_func, x='x', name='x_value')
>>> c = Constraint(func=f, ctype='ge', value=0.0)
>>>
>>> # Nonlinear constraint: x^2 + y^2 <= 1 (unit circle)
>>> def circle(x, y):
...     return x**2 + y**2
>>> f = Function(func=circle, x=['x', 'y'], name='circle')
>>> c = Constraint(func=f, ctype='le', value=1.0)

Raises:
    TypeError: If func is not a Function object, ctype is not a string,
        or value is not numeric.
    ValueError: If ctype is not one of 'ge', 'le', or 'eq'.

Notes

  • Internally, constraints are converted to the form func(x) OP value
  • For inequality constraints, violation is measured as the amount by which the constraint is not satisfied

Problem

Attributes

objective (Function): Objective function to minimize (or maximize if
    maximize=True).
constraints (Iterable): Collection of Constraint objects defining the
    feasible region. Can be empty for unconstrained problems.
ubounds (ArrayLike): Upper bounds for each variable. Must have same
    length as objective.x. Use np.inf for unbounded variables.
lbounds (ArrayLike): Lower bounds for each variable. Must have same
    length as objective.x. Use -np.inf for unbounded variables.
maximize (bool, optional): If True, maximize the objective instead of
    minimizing. Internally converted to minimization. Defaults to False.
tol (float, optional): Tolerance for numerical comparisons and
    convergence checks. Defaults to 1e-6.
name (str, optional): Descriptive name for the problem. Defaults to "".

Examples

>>> # Unconstrained problem: minimize (x-2)^2

>>> def obj(x):
...     return (x - 2)**2
>>> f = Function(func=obj, x='x', name='quadratic')
>>> problem = Problem(
...     objective=f,
...     constraints=[],
...     lbounds=[-10],
...     ubounds=[10],
...     name='simple_quadratic'
... )
>>>
>>> # Constrained problem: minimize f(x,y) subject to x+y >= 1
>>> def obj(x, y):
...     return x**2 + y**2
>>> def con(x, y):
...     return x + y
>>> f = Function(func=obj, x=['x', 'y'])
>>> c = Constraint(func=Function(func=con, x=['x', 'y']), ctype='ge', value=1.0)
>>> problem = Problem(
...     objective=f,
...     constraints=[c],
...     lbounds=[0, 0],
...     ubounds=[10, 10]
... )

Raises:
    TypeError: If inputs are not of the correct type.
    ValueError: If bounds length doesn't match number of variables or
        constraint variables don't match objective variables.

Notes

  • If maximize=True, the objective is internally negated for minimization
  • Bounds are stored as self.bounds (n_vars x 2) array
  • Constraint names are auto-generated if not provided

evaluate

Examples

>>> # Evaluate objective only

>>> f_val, _ = problem.evaluate(x=[1.0, 2.0], f=True, c=False)
>>>
>>> # Evaluate constraints only
>>> _, c_vals = problem.evaluate(x=[1.0, 2.0], f=False, c=True)
>>>
>>> # Evaluate both
>>> f_val, c_vals = problem.evaluate(x=[1.0, 2.0], f=True, c=True)

Notes

The objective is called with unpacked x values as positional arguments.

initial_DoE

Examples

>>> # Fixed number of samples

>>> doe = problem.initial_DoE(n=20)
>>>
>>> # Adaptive number based on problem dimension
>>> doe = problem.initial_DoE(n=lambda n: 3*n + 1)

Notes

  • For infinite bounds, uses +/-100 or +/-(bound + 50) as limits
  • Evaluates objective as 'obj' and constraints as 'c{i}-{type}-{value}'
  • Automatically computes constraint violations

compute_constraint_violation

Examples

>>> # For constraints c1(x) >= 0 and c2(x) <= 5

>>> constraint_vals = {
...     'c0-ge-0.0': np.array([1.0, -0.5, 0.1]),  # c1 values
...     'c1-le-5.0': np.array([3.0, 6.0, 4.9])    # c2 values
... }
>>> violations = problem.compute_constraint_violation(constraint_vals)
>>> # violations = [0, 0.25+1.0, 0] = [0, 1.25, 0]

Notes

  • For 'ge' constraints: violation = max(0, value - c_val)^2
  • For 'le' constraints: violation = max(0, c_val - value)^2
  • For 'eq' constraints: violation = |c_val - value|^2
  • Total violation is the sum across all constraints

ParetoEntry

Attributes

f:     Objective value.
h:     Total constraint violation h_total.
J_i:   Total coupling discrepancy J_i.
z_bar: Shared design variables at this iterate.
x_bar: Local design variables at this iterate.
y_bar: Coupling variables at this iterate.
code:  Integer in {1,...,7}. Bit 1 set if non-dominated in (f,h);
       bit 2 if non-dominated in (f,J); bit 4 if non-dominated in (h,J).

BestSolution

Attributes

z_bar:     Shared design variables at the best iterate.
x_bar:     Local design variables at the best iterate.
y_bar:     Coupling variables at the best iterate.
f:         Objective value at the best iterate.
h:         Running minimum of h_total (used in strict improvement check).
J_i:       Coupling discrepancy J_i at the best iterate.
h_history: All observed h_total values in order of observation.

Results

Attributes

best:         Best iterate found under the lexicographic criterion.
converged:    True if termination criteria were met.
iterations:   Number of system-level iterations performed.
evaluations:  Total number of discipline evaluations performed.
elapsed_time: Wall-clock time in seconds.
history:      DataFrame with one row per iteration.
pareto:       Pareto set at termination.  DataFrame with columns
              [f_sys, J_i, h_total, z_bar, x_bar, y_bar, code].  *code* is an
              integer bitmask (1-7) encoding membership in bi-objective
              spaces (f,h), (f,J), (h,J).

BudgetManager

Attributes

mode (Literal["shared", "weighted", "fixed"]): Budget allocation strategy:
    - "shared": Single shared pool, first-come-first-served
    - "weighted": Proportional split based on ratios
    - "fixed": User-specified exact allocations
    Defaults to "weighted".
total_budget (int, optional): Total evaluation budget. Required for
    "shared" and "weighted" modes.
system_ratio (float, optional): Fraction of budget for system-level
    (weighted mode only). Must be in (0, 1). Defaults to 0.5.
subsystem_weights (list[float], optional): Relative weights for each
    subsystem (weighted mode). Must sum to ~1.0.
subsystem_budgets (list[int], optional): Exact budgets for each
    subsystem (fixed mode). Required if mode="fixed".
system_budget (int, optional): Exact budget for system-level (fixed mode).
    Required if mode="fixed".
iteration_ratio (float, optional): Fraction of allocated budget to use
    per iteration (0, 1). If None, use full budget per iteration.
    Defaults to None.

Examples

>>> # Mode 1: Shared pool (400 total, use until exhausted)

>>> budget = BudgetManager(mode="shared", total_budget=400)
>>>
>>> # Mode 2: Weighted split (50% system, 50% subsystems)
>>> budget = BudgetManager(
...     mode="weighted",
...     total_budget=400,
...     system_ratio=0.5,
...     subsystem_weights=[0.3, 0.5, 0.2]
... )
>>>
>>> # Mode 3: Weighted with iteration ratio (5% per iteration)
>>> budget = BudgetManager(
...     mode="weighted",
...     total_budget=400,
...     system_ratio=0.5,
...     subsystem_weights=[0.3, 0.5, 0.2],
...     iteration_ratio=0.05
... )
>>>
>>> # Mode 4: Fixed budgets with iteration ratio
>>> budget = BudgetManager(
...     mode="fixed",
...     subsystem_budgets=[100, 70],
...     system_budget=120,
...     iteration_ratio=0.05
... )

Notes

  • Call initialize(n_subsystems) before using the budget manager
  • Use get_subsystem_max_iter() and get_system_max_iter() to get available evaluations for each call
  • Record actual usage with record_subsystem_evals() and record_system_evals()
  • Check exhaustion with is_subsystem_exhausted(), is_system_exhausted(), is_total_exhausted()
    Raises:
        ValueError: If mode is invalid, weights don't sum to 1, or required
            parameters are missing for the selected mode.