Skip to content

Commit e7f8621

Browse files
author
Elwardi
committed
fix: defer PI computation over window_size
1 parent 6528716 commit e7f8621

3 files changed

Lines changed: 70 additions & 7 deletions

File tree

src/foambo/default_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ def _get_doc_models():
115115
DimensionalityReductionOptions,
116116
)
117117
from .metrics import FoamJob, LocalJobMetric
118+
from .robustness import RobustOptimizationConfig
118119
return [
120+
(RobustOptimizationConfig, "robust_optimization"),
119121
(ExperimentOptions, "experiment"),
120122
(TrialGenerationOptions, "trial_generation"),
121123
(ModelSpecConfig, "trial_generation.generation_nodes[].generator_specs[]"),

src/foambo/docs_concepts.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,57 @@
504504
available via ``GET /api/v1/events``.""",
505505
},
506506

507+
"concept.robust_optimization": {
508+
"category": "Concept",
509+
"content": """\
510+
Robust optimization — designing for performance under context uncertainty.
511+
512+
Instead of optimizing at one nominal operating point, robust mode samples a
513+
distribution of context values (flowRate, temperature, inlet profile, …) at
514+
every candidate design and collapses the resulting outcome distribution via a
515+
risk measure. The optimizer then maximizes the risk-adjusted score, favoring
516+
designs that stay good across contexts rather than designs that peak at one
517+
and tank at others.
518+
519+
**YAML:**
520+
```yaml
521+
robust_optimization:
522+
context_groups: [operating_point] # parameter groups treated as contexts
523+
risk_measure: auto # or: cvar, mars
524+
robustness: 0.8 # alpha; higher = more conservative
525+
context_samples: 4 # samples per candidate
526+
# context_points: auto-generated via Sobol unless given explicitly
527+
```
528+
529+
**Risk measures:**
530+
- ``cvar`` (Conditional Value-at-Risk) — average of the worst ``(1-alpha)``
531+
fraction of context outcomes. Single-objective only.
532+
- ``mars`` (MVaR Approximation via Random Scalarizations) — multi-objective
533+
VaR built from random weight vectors; each scalarization reduces the problem
534+
to single-objective CVaR, then Ax aggregates. Multi-objective only.
535+
- ``auto`` — picks CVaR when the optimization has one objective and MARS when
536+
it has multiple.
537+
538+
**Mechanism:** foamBO's ``SubstituteContextFeatures`` input transform swaps
539+
the candidate's context slot for each Sobol-drawn context point at acquisition
540+
evaluation time. The GP is evaluated once per context sample and the risk
541+
measure reduces the ``n_contexts`` values to a single score that
542+
``optimize_acqf`` maximizes.
543+
544+
**Parallelism:** robust trials require ``context_samples`` design evaluations
545+
each. With ``parallelism: N``, each Ax orchestrator slot still corresponds to
546+
one design point (the context sweep runs inside the acquisition, not the
547+
runner), so the runner budget is unchanged.
548+
549+
**Full details, risk-measure math, MOO suitability matrix, and comparisons
550+
to SAASBO / InputPerturbation:** see ``docs/risk-measures-guide.md``.
551+
552+
**Specialization:** once a robust run converges, pin the context with
553+
``bootstrap: + specialize: {ctx: value}`` to continue optimizing design
554+
variables at a concrete operating point. See
555+
``concept.bootstrap_and_specialize``.""",
556+
},
557+
507558
"concept.bootstrap_and_specialize": {
508559
"category": "Concept",
509560
"content": """\

src/foambo/optimize.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -542,16 +542,26 @@ def callback(sched: Orchestrator):
542542
from .analysis import plot_streaming_metrics
543543
plot_streaming_metrics(raw_cfg, client)
544544

545-
# Log convergence estimate (Part A) after BO trials
545+
# Log convergence estimate (Part A) — throttled to every `window_size`
546+
# completed trials (same cadence the global stopping strategy uses).
546547
try:
547548
gs = client._generation_strategy
548549
if gs is not None and gs.adapter is not None:
549-
from .analysis import compute_convergence_pi, format_convergence_log
550-
_imp_bar = getattr(orch_cfg, 'global_stopping_strategy', None)
551-
_imp_bar = getattr(_imp_bar, 'improvement_bar', 0.1) if _imp_bar else 0.1
552-
conv = compute_convergence_pi(client, gs, improvement_bar=_imp_bar)
553-
if conv.get("estimates"):
554-
log.info("\n%s", format_convergence_log(conv))
550+
from ax.core.base_trial import TrialStatus as _TS
551+
n_completed = sum(
552+
1 for t in client._experiment.trials.values()
553+
if t.status in (_TS.COMPLETED, _TS.EARLY_STOPPED)
554+
)
555+
gss = getattr(orch_cfg, 'global_stopping_strategy', None)
556+
window = getattr(gss, 'window_size', None) or 5
557+
last = getattr(callback, '_last_conv_n', 0)
558+
if n_completed > 0 and n_completed - last >= window:
559+
from .analysis import compute_convergence_pi, format_convergence_log
560+
_imp_bar = getattr(gss, 'improvement_bar', 0.1) if gss else 0.1
561+
conv = compute_convergence_pi(client, gs, improvement_bar=_imp_bar)
562+
if conv.get("estimates"):
563+
log.info("\n%s", format_convergence_log(conv))
564+
callback._last_conv_n = n_completed
555565
except Exception as e:
556566
log.debug("Convergence estimate failed: %s", e)
557567

0 commit comments

Comments
 (0)