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.