axfluxmdo.optimize¶
Requires the [opt] extra for the pymoo / OpenMDAO / scikit-learn backends;
DesignProblem and DesignDataset work with the core install.
axfluxmdo.optimize.problem ¶
Backend-agnostic optimization problem specification.
This module is the single source of truth for parsing the SPEC-style
optimization grammar against the stable AnalyticalResult.to_dict() keys:
- variables:
{"outer_radius": (0.05, 0.12), "pole_pairs": [8, 10, 12]}— 2-tuples are continuous bounds (or integer bounds when the motor field is an int), lists are discrete choices; dotted paths reach nested fields ("tolerances.runout_m"). - objectives:
"maximize_torque_density"/"minimize_mass"— the suffix resolves through the alias map to ato_dict()key. - constraints:
"winding_temp_c < 140"— parsed to the pymoo/OpenMDAOg <= 0convention. The model's built-in ConstraintRecords are enforced in addition by default, so optimizer output is alwaysresult.feasible.
Design vectors that violate geometric validation (frozen-dataclass
__post_init__ ValueError, e.g. inner_radius >= outer_radius) are
penalized with large finite objective/constraint values rather than raising —
GA constraint-domination buries them and gradient drivers stay numeric.
Only numpy is required here; pymoo/OpenMDAO backends import this module, not the other way around.
UnknownKeyError ¶
Bases: ValueError
A user-facing name did not resolve to any result key or alias.
UserConstraint
dataclass
¶
UserConstraint(key: str, op: str, bound: float, label: str)
violation ¶
violation(value: float) -> float
g <= 0 convention: positive means violated.
Source code in src/axfluxmdo/optimize/problem.py
91 92 93 94 95 96 97 | |
EvalRecord
dataclass
¶
EvalRecord(f_min: ndarray, g: ndarray, result: AnalyticalResult | None, motor: AxialFluxMotor | None)
One design evaluation in optimizer space.
DesignProblem ¶
DesignProblem(motor: AxialFluxMotor, op: OperatingPoint, *, variables: Mapping[str, object], objectives: Sequence[str], constraints: Sequence[str] = (), model: Model | None = None, enforce_model_constraints: bool = True)
Validated design variables / objectives / constraints around a seed motor.
Source code in src/axfluxmdo/optimize/problem.py
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | |
apply ¶
apply(x: Mapping[str, object]) -> AxialFluxMotor
Build the motor variant for a design vector; may raise ValueError.
Source code in src/axfluxmdo/optimize/problem.py
233 234 235 236 237 238 239 240 241 242 | |
objective_values ¶
objective_values(result: AnalyticalResult) -> np.ndarray
Human-readable (un-negated) objective values.
Source code in src/axfluxmdo/optimize/problem.py
244 245 246 247 | |
resolve_key ¶
resolve_key(name: str, available: Collection[str]) -> str
Resolve a user-facing name (alias or canonical) to a to_dict() key.
Source code in src/axfluxmdo/optimize/problem.py
68 69 70 71 72 73 74 | |
parse_objective ¶
parse_objective(spec: str, available: Collection[str]) -> Objective
Parse 'maximize_
Source code in src/axfluxmdo/optimize/problem.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
parse_constraint ¶
parse_constraint(spec: str, available: Collection[str]) -> UserConstraint
Parse '
Source code in src/axfluxmdo/optimize/problem.py
117 118 119 120 121 122 123 124 125 126 127 | |
axfluxmdo.optimize.pymoo_runner ¶
Pareto-front optimization via pymoo's mixed-variable GA.
pymoo is imported lazily so the base package works without the [opt]
extra. Mixed continuous/integer/choice design variables use
MixedVariableGA with rank-and-crowding survival (the multi-objective
configuration recommended by pymoo 0.6.x for mixed search spaces) — never
NSGA2-with-rounding, which breaks Choice semantics and duplicate
elimination.
ParetoStudy
dataclass
¶
ParetoStudy(variables: list[str], objectives: list[Objective], X: list[dict], F: ndarray, results: list[AnalyticalResult], motors: list[AxialFluxMotor], seed: int, pop_size: int, n_gen: int, problem: DesignProblem)
Feasible non-dominated designs from one optimize_pareto run.
F holds human-readable objective values (maximize objectives are NOT
negated here; the sign convention lives only inside the optimizer).
Every point satisfies the user constraints and result.feasible.
to_records ¶
to_records() -> list[dict]
One flat dict per point: design variables merged with result.to_dict().
Source code in src/axfluxmdo/optimize/pymoo_runner.py
57 58 59 | |
to_arrays ¶
to_arrays(*fields_: str) -> dict[str, np.ndarray]
Extract named fields (aliases, to_dict keys, or design variables) as arrays.
Source code in src/axfluxmdo/optimize/pymoo_runner.py
61 62 63 64 65 66 67 68 69 | |
best ¶
best(objective: str) -> int
Index of the best point for one objective (alias-aware).
'Best' respects the study's sense for that key if it is an objective (max for maximize_*), otherwise defaults to maximum.
Source code in src/axfluxmdo/optimize/pymoo_runner.py
71 72 73 74 75 76 77 78 79 80 81 | |
optimize_pareto ¶
optimize_pareto(motor: AxialFluxMotor, op: OperatingPoint, *, variables: Mapping[str, object], objectives: Sequence[str], constraints: Sequence[str] = (), model: Model | None = None, algorithm=None, pop_size: int = 60, n_gen: int = 40, seed: int = 1, verbose: bool = False, enforce_model_constraints: bool = True) -> ParetoStudy
Multi-objective Pareto optimization of the motor design (SPEC flagship API).
Note: unlike the SPEC sketch, the operating point is an explicit argument — every objective (torque, efficiency, winding temperature) depends on it.
Source code in src/axfluxmdo/optimize/pymoo_runner.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
axfluxmdo.optimize.sensitivity ¶
One-at-a-time design sensitivities for tornado charts.
compute_sensitivities ¶
compute_sensitivities(motor: AxialFluxMotor, op: OperatingPoint, variables: Sequence[str] | Mapping[str, object], *, output: str = 'torque_density_nm_kg', rel_step: float = 0.05, model: Model | None = None) -> SensitivityResult
One-at-a-time low/high perturbations of each variable (tornado-chart input).
variables may be a sequence of field names (perturbed by ±rel_step of
the baseline value) or an optimize-style dict: tuple bounds clamp the
perturbations, choice lists step one option below/above the baseline's
nearest choice. Integer fields are rounded and de-duplicated. A
perturbation that produces invalid geometry is skipped with a warning
(the baseline output stands in for that side).
Source code in src/axfluxmdo/optimize/sensitivity.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | |
axfluxmdo.optimize.dataset ¶
Dataset of evaluated designs (numpy-only; usable without the [opt] extra).
Each record pairs a design vector x (dict over the declared variables)
with the flat outputs dict of its evaluation (result.to_dict(), plus
any extra keys such as an expensive-FEA objective).
Feature encoding for surrogates is ordinal-as-float: continuous and
integer variables are cast to float; Choice variables use the option VALUE
when numeric (e.g. pole_pairs — an ordered physical quantity) and the
option INDEX otherwise. One-hot encoding for genuinely unordered categoricals
is documented future work; the GP's per-dimension ARD length scales absorb
the differing column scales.
Persistence is JSON Lines with a versioned header
(axfluxmdo-dataset-v1) — dependency-free, append-friendly, git-diffable;
these datasets are O(10^2..10^3) rows, so columnar formats buy nothing.
DesignDataset ¶
DesignDataset(variable_names: Sequence[str], *, choices: Mapping[str, list] | None = None)
Ordered collection of (design vector, evaluation outputs) records.
Source code in src/axfluxmdo/optimize/dataset.py
40 41 42 43 44 45 46 47 48 | |
from_study
classmethod
¶
from_study(study: ParetoStudy) -> DesignDataset
Wrap a ParetoStudy's points without re-evaluating anything.
Source code in src/axfluxmdo/optimize/dataset.py
52 53 54 55 56 57 58 | |
from_evaluations
classmethod
¶
from_evaluations(problem: DesignProblem, xs: Iterable[Mapping[str, object]]) -> DesignDataset
Evaluate each design with the problem's model and record it.
Source code in src/axfluxmdo/optimize/dataset.py
60 61 62 63 64 65 66 67 68 69 70 | |
encode ¶
encode(x: Mapping[str, object]) -> np.ndarray
One design dict -> feature row (fixed variable order).
Source code in src/axfluxmdo/optimize/dataset.py
92 93 94 95 96 97 98 99 100 | |
feature_matrix ¶
feature_matrix() -> np.ndarray
(n, d) float matrix, columns in variable_names order.
Source code in src/axfluxmdo/optimize/dataset.py
102 103 104 105 106 | |
to_arrays ¶
to_arrays(y: str) -> tuple[np.ndarray, np.ndarray]
(X, y) for surrogate fitting; y accepts aliases or output keys.
Source code in src/axfluxmdo/optimize/dataset.py
108 109 110 111 112 113 114 | |
dedupe ¶
dedupe(*, tol: float = 1e-12) -> DesignDataset
Drop records whose feature rows duplicate an earlier one (keep first).
Source code in src/axfluxmdo/optimize/dataset.py
116 117 118 119 120 121 122 123 124 125 126 | |
axfluxmdo.optimize.surrogate ¶
Gaussian-process and ensemble surrogates over design datasets.
scikit-learn is imported at module level here, but this module is only
reachable lazily from axfluxmdo.optimize (PEP 562), so the base package
and import axfluxmdo.optimize stay sklearn-free (test-enforced).
The GP is the primary surrogate: Matern(nu=2.5) with PER-DIMENSION (ARD)
length scales — mandatory for the mixed ordinal/continuous feature encoding —
plus a WhiteKernel jitter. Trustworthiness is judged by cross-validation
(cv_rmse/cv_r2), not by sklearn's ConvergenceWarning (suppressed: it
fires routinely on small-n fits).
GPSurrogate ¶
GPSurrogate(*, nu: float = 2.5, n_restarts: int = 5, random_state: int = 0, noise_level: float = 1e-06)
Gaussian-process surrogate with ARD Matern kernel and CV diagnostics.
Source code in src/axfluxmdo/optimize/surrogate.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
predict_dict ¶
predict_dict(x: Mapping[str, object], dataset: DesignDataset) -> tuple[float, float]
Predict at one design dict using the dataset's feature encoding.
Source code in src/axfluxmdo/optimize/surrogate.py
100 101 102 103 | |
cv_rmse ¶
cv_rmse(k: int = 5) -> float
Seeded k-fold RMSE on the training data — the honest trust signal.
Source code in src/axfluxmdo/optimize/surrogate.py
105 106 107 | |
RandomForestSurrogate ¶
RandomForestSurrogate(*, n_estimators: int = 200, random_state: int = 0)
Ensemble surrogate: per-tree spread as the uncertainty estimate.
The documented fallback for non-smooth responses; satisfies the same Surrogate protocol as the GP.
Source code in src/axfluxmdo/optimize/surrogate.py
143 144 | |
axfluxmdo.optimize.bayesopt ¶
Bayesian optimization for expensive design evaluations.
The loop reuses the Phase-3 :class:~axfluxmdo.optimize.problem.DesignProblem
wholesale (search space, objective/constraint parsing, geometry penalization).
The intended use case is an EXPENSIVE objective — e.g. a GetDP solve via
expensive_fn — but the default path evaluates the cheap analytical model
so tests and examples run anywhere.
Mechanics (v1, documented tradeoffs):
- Initial design: scipy Latin hypercube over continuous/integer variables (integers rounded, deduped, topped up), seeded random draws for choices.
- Acquisition: closed-form expected improvement in MINIMIZE space, maximized over a seeded candidate pool (half uniform, half perturbations of the incumbent) — derivative-free and correct for mixed spaces; gradient multistart is future work.
- Constraints: evaluated by the cheap model's g-vector at every evaluated point. The incumbent is the best FEASIBLE point. The surrogate trains on all points, with infeasible ones assigned a SOFT penalty (worst feasible + 10% of the feasible range) — never the 1e9 geometry penalty, which would destroy GP smoothness. Probability-of-feasibility classifiers are future work.
Human-sign convention: BOStudy reports objective values with their
natural sign (same invariant as ParetoStudy.F); minimize-space values
live only inside this module.
BOStudy
dataclass
¶
BOStudy(objective: Objective, dataset: DesignDataset, X: list[dict], y: ndarray, feasible: ndarray, history: ndarray, best_x: dict, best_value: float, best_result: AnalyticalResult | None, best_motor: AxialFluxMotor | None, surrogate: Surrogate, n_initial: int, n_iterations: int, seed: int, problem: DesignProblem)
Result of one Bayesian-optimization run (human-sign objective values).
recommend ¶
recommend(k: int = 5, *, risk_aversion: float = 1.0) -> list[dict]
Top-k feasible evaluated designs ranked by surrogate-pessimistic value.
Score = mean − risk_aversion·σ for maximize objectives (the reverse for minimize). Ranks only EVALUATED (verified-feasible) designs — uncertainty-aware without certifying unevaluated space.
Source code in src/axfluxmdo/optimize/bayesopt.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
bayesian_optimize ¶
bayesian_optimize(motor: AxialFluxMotor, op: OperatingPoint, *, variables: Mapping[str, object], objective: str, constraints: Sequence[str] = (), model: Model | None = None, expensive_fn: Callable[[AxialFluxMotor, OperatingPoint], float | dict] | None = None, n_initial: int = 10, n_iterations: int = 25, seed: int = 1, xi: float = 0.01, n_candidates: int = 2048, surrogate: Surrogate | None = None, enforce_model_constraints: bool = True) -> BOStudy
Single-objective Bayesian optimization over the motor design space.
Source code in src/axfluxmdo/optimize/bayesopt.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | |
axfluxmdo.optimize.openmdao_components ¶
OpenMDAO integration: the motor model as an ExplicitComponent.
pymoo handles multi-objective Pareto exploration; this layer exposes the
motor model to OpenMDAO's gradient-based drivers and to larger coupled MDO
groups (the SPEC's system-integration story). Discrete design variables
(integer fields, choice lists) are frozen at their baseline values here —
gradient drivers cannot move them; use optimize_pareto for those.
This module imports openmdao.api at import time (the component must
subclass om.ExplicitComponent), so it is exposed from
axfluxmdo.optimize only through lazy PEP 562 __getattr__.
MotorComponent ¶
Bases: ExplicitComponent
Motor model wrapped for OpenMDAO with finite-difference partials.
Inputs: the DesignProblem's continuous variables (baseline values as
defaults). Outputs: the requested result keys plus one g_<name>
violation output per constraint (feasible when <= 0).
build_motor_group ¶
build_motor_group(problem: DesignProblem, *, objective_index: int = 0) -> om.Problem
An om.Problem with the motor component, SLSQP driver, and constraints wired.
The objective is the DesignProblem objective at objective_index
(negated internally when it is a maximize objective — OpenMDAO minimizes).
Source code in src/axfluxmdo/optimize/openmdao_components.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
run_openmdao_demo ¶
run_openmdao_demo(motor: AxialFluxMotor, op: OperatingPoint, *, variables: dict, objective: str, constraints: tuple = (), model: Model | None = None) -> dict
Single-objective gradient refinement over the continuous variables.
Returns {'x': optimal design dict, 'result': final to_dict(), 'success': bool}.
Source code in src/axfluxmdo/optimize/openmdao_components.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |