Library for conducting global sensitivity analyses and stress tests. Interoperable and model agnostic API. HPC suitability approved.
Project description
ETA Stress
eta-stress is the standalone stress-test orchestration package for ETA workflows.
It integrates with eta-incerto through a thin adapter boundary and provides a
clean Tier 1 + Tier 2 + GSA pipeline with explicit contracts and artifact persistence.
Key Features
- Tier 1 local sensitivity analysis on a solved baseline
- Tier 2 scenario generation, perturbation, and scorecard aggregation
- Tier 3 global sensitivity analysis (PAWN / Sobol) with HDF5 artifacts
- Thin adapter boundary to
eta-incertowith explicit run contracts - CLI-driven orchestration and optional plotting summaries
Overview
flowchart TD
bootstrap[bootstrap] --> preflight[preflight]
preflight --> variant_registry[variant_registry]
variant_registry --> baseline[baseline solve]
baseline --> tier1[tier1 analyze]
tier1 --> tier2[tier2 scenarios]
tier2 --> aggregation[tier2 aggregation]
aggregation --> gsa[tier3 gsa]
gsa --> provenance[provenance]
What This Package Runs
The orchestrator executes the full stress-test pipeline:
- bootstrap: create namespace folders under
run_dir. - preflight: validate config, mode, scenario files, series files, and variant layout.
- variant_registry: import variant runtime module and validate registry availability.
- baseline resolution: solve baseline through
EtaBackendor import an existing baseline. - baseline artifacts: persist
baseline.jsonandincumbent.npz. - tier1 analyze: local sensitivity analysis over solved baseline.
- tier2 source: resolve scenarios from
generateorexisting. - tier2 runner: run per-scenario context -> perturb -> incumbent_eval -> metrics -> persist.
- tier2 aggregation: aggregate scenario results into scorecard.
- tier3 gsa: build factor/response data (X/Z), run PAWN/Sobol, persist
gsa.h5. - provenance: persist final run provenance and artifact links.
Installation
poetry install
Compatibility:
- Python:
>=3.11,<3.13 eta-incerto:>=1.3.5,<2.0.0(current package constraint:^1.3.5)
See Solver and third-party requirements for MILP solver notes (via eta-incerto / eta-components).
CLI Usage
The CLI takes required runtime arguments plus an optional stress-config overrides file.
poetry run python -m eta_stress.cli.main \
--run-dir /path/to/run_dir \
--config-name config \
--mode stochastic \
--variant variant_one \
--variant-import-root examples.conventional \
--scenario-source generate \
--stress-config /path/to/stress_test_config.json \
--plot \
--plot-heatmap
Required CLI arguments:
--run-dir--config-name--mode--variant--variant-import-root--scenario-source(generate|existing)
Optional:
--stress-config(policy/tolerance overrides)--stress-evaluation-mode(fixed_first_stage|full_reopt) overrides evaluation mode from config--strict-antifragile/--no-strict-antifragiletoggle strict antifragile execution (no synthetic fallback when strict)--antifragile-pareto-selection(weighted_sum|min_first_objective) controls antifragile Pareto point selection for stress baseline--antifragile-objective-weightscomma-separated objective weights forweighted_sum(default1/3,1/3,1/3)--chapter8-price-only-uncertainty/--no-chapter8-price-only-uncertaintyenforce Chapter-8 uncertainty scope--plot(render plotting summaries after pipeline completion)--plot-preset(style preset for plotting)--plot-heatmap(enable heatmap panel in per-scenario plotting)
Supported config file formats:
.json.yml.yaml.ymal
Stress-Test Config Contract
Main model: eta_stress.orchestrator.stress_test_config.StressTestConfig.
Required fields:
- none (all fields are optional overrides now)
Important behavior:
sensitivity_namespaceis auto-generated by orchestrator using timestamp + incremental index.- If
generation_policy.scenario_probability_candidatesis provided,scenario_probability_deltasis ignored. - If neither
scenario_probability_candidatesnorscenario_probability_deltasis provided, probability deltas are auto-generated. generation_policy.max_scenariosis applied during scenario generation (early cap), so only up to that many candidates are built.- Solver option path for MIP gap is normalized to
pyomo.solver_settings.options.mipgap. stress_evaluation_modecontrols scenario solve semantics:fixed_first_stage: evaluate baseline first-stage decisions under stress. This is the default production path.full_reopt: re-optimize first- and second-stage decisions under stress. This is kept for compatibility/debugging and explicit re-optimization studies.
strict_antifragilecontrols antifragile fallback behavior:true: fail fast if antifragile runtime/h5/incumbent requirements are not met.false: allow synthetic placeholder fallback behavior for compatibility/debugging.
antifragile_pareto_selectioncontrols antifragile Pareto scalarization for stress baseline:weighted_sum(default) usesantifragile_objective_weights(defaults to equal weights1/3,1/3,1/3).min_first_objectivereproduces legacy behavior.
chapter8_price_only_uncertaintydefaults totrueand enforces uncertainty channels to gas/electricity yearlyunit_priceonly.rulebook_preset.allowed_carriers/rulebook_preset.allowed_fieldsdefault to["gas","electricity"]and["unit_price"].
Complete example config:
Variant Registry Import Rule
variant_registry imports:
<variant_import_root>.variants.<variant>.system
Then it validates runtime registration via eta_incerto.registry.get_system(variant).
Example:
variant_import_root = "examples.conventional"variant = "variant_one"- imported module =
examples.conventional.variants.variant_one.system
Output Layout
For each run, a namespace directory is created under run_dir:
run_dir/
sensitivity_YYYY-MM-DD_HH-MM-SSZ_XXXX/
baseline/
baseline.json
incumbent.npz
tier1_analysis.json
scenarios/
scenarios.json
results/
<scenario_id>.h5
aggregate/
scorecard.h5
gsa/
gsa.h5
plots/
conventional_stress_scorecard_summary.png
conventional_stress_scorecard_summary.pdf
conventional_stress_scorecard_evaluation.md
conventional_stress_per_scenario_summary.png
conventional_stress_per_scenario_summary.pdf
conventional_stress_per_scenario_evaluation.md
provenance.json
gsa/gsa.h5 payload contract (payload_json) is:
gsa.factor_valuesscenario_ids[]factor_names[]matrix[][]
gsa.validation_zmetric_keyvalues[]
gsa.pawn(flatten metrics map, keysgsa.pawn.*)gsa.sobol(flatten metrics map, keysgsa.sobol.*)
Runtime Model
eta-stress runs one sensitivity namespace per execution:
- bootstrap
- preflight
- variant registry
- baseline resolution + baseline artifacts
- Tier 1 analysis when a solved baseline model is available
- Tier 2 source (generate/existing)
- Tier 2 scenario runner
- Tier 2 aggregation
- GSA (X/Z + PAWN/Sobol payload)
- optional plotting (
--plot) - provenance persistence
Chapter 8 Evidence Protocol
- Pilot stress run:
generation_policy.max_scenarios = 200for ranking/interval stability check. - Final stress run:
generation_policy.max_scenarios = 500once pilot is stable. - Keep Chapter-8 uncertainty scope price-only (
chapter8_price_only_uncertainty=trueand price-only rulebook preset).
Package Structure (High Level)
eta_stress/orchestrator: top-level pipeline and run config loading.eta_stress/services/tier1: baseline local sensitivity analysis.eta_stress/services/tier2: scenario source, runner, and aggregation runners.eta_stress/services/gsa: Tier 3 GSA encoding/reports/runner.eta_stress/services/artifacts.py: artifact persistence (idempotent writes).eta_stress/adapters/eta_backend/: boundary toeta-incertoruntime operations.backend.py: publicEtaBackendfacade.baseline.py: baseline solve dispatch for deterministic, stochastic, robust, regret, and antifragile modes.runtime.py: incumbent evaluation and optional full re-optimization runtime calls.perturbations.py: local stress perturbation injection.antifragile.py: antifragile Pareto H5 extraction and selection.status.py: solver/status normalization.
eta_stress/io: JSON/YAML/H5/path utilities.eta_stress/core: shared domain types, statuses, provenance, and numeric helpers.
Testing and Validation
See CONTRIBUTING.md for setup, tests, and merge request workflow.
Run tests (environment with dev dependencies):
poetry run pytest
Quick syntax check:
find eta_stress -name '*.py' -print0 | xargs -0 python3 -m py_compile
Notes
tools/extract_stress_from_incerto.pyis a migration utility and not part of normal runtime execution.- The current implementation prioritizes strict contracts and simple stage boundaries over legacy report shape parity.
- A successful end-to-end run typically persists:
baseline.json,tier1_analysis.json,scenarios/scenarios.json,aggregate/scorecard.h5,gsa/gsa.h5, andprovenance.json(plusplots/*if--plotis enabled). - HDF5 writer format tags are
eta_stress.*(eta_stress.scenario_result,eta_stress.scorecard,eta_stress.gsa). - HDF5 readers currently accept payloads from both old and new format tags as long as
payload_jsonis valid. - E2E artifact checks are available via
eta_stress.e2e.contract.assert_e2e_artifacts. - Current status: Tier 1–2 (including GSA) is integrated in
eta-stress; remaining epic closure items are mainly cross-repo cutover and real-config E2E/harness alignment. - Detailed Slurm/venv split patterns (optimization stage vs stress stage, array indexing, worker/thread tuning) are intentionally tracked in a separate follow-up issue and are not finalized in this README yet.
Recent Backend and Tier 2 Changes
Eta Backend Adapter
eta_stress.adapters.eta_backend is now a package instead of a single adapter file. The public import remains stable:
from eta_stress.adapters.eta_backend import BackendRequest, EtaBackend, RuntimeVersions
The public EtaBackend facade intentionally stays small:
solve_baseline(request)evaluate_incumbent(request)solve_scenario(request)get_runtime_versions()
The implementation is split by responsibility:
baseline.pyhandles baseline solves fordeterministic,stochastic,robust,regret, andantifragile.runtime.pyhandles per-scenario runtime evaluation.perturbations.pyapplies local model, solver, scenario probability, and scenario CSV perturbations.antifragile.pyreads antifragile Pareto H5 outputs and selects a point viaweighted_sumormin_first_objective.incumbent.pyextracts first-stage incumbent maps from solved frameworks, bundles, or antifragile Pareto H5 files.status.pynormalizes solver status and feasibility flags.
The new adapter no longer imports eta_incerto.stress_test.*. It may still import normal eta_incerto runtime modules, evaluators, config builders, and model utilities.
Baseline Resolution
There are two baseline paths:
baseline_resolution=solve: eta-stress solves the baseline, persistsbaseline.jsonandincumbent.npz, and keeps the live solved system object for Tier 1.baseline_resolution=import: eta-stress imports the baseline objective and incumbent from an existing optimization result H5, persistsbaseline.jsonandincumbent.npz, and skips Tier 1 because no live solved Linopy model is available.
Tier 1 currently requires the solved model object, specifically access to solved_system._backend.model, model constraints with lhs.solution, rhs, sign, and variables with solution, lower, upper. Existing H5 outputs are not a uniform replacement for that object across all modes.
Observed H5 shape by mode:
stochastic,robust,regret: expose conventional RODMO-style expected groups such as__expected__/investment,__expected__/unit_dispatch, and__expected__/carriers.deterministic: writes scenario-level result groups and dispatch data rather than the same expected-result contract.antifragile: exposes Pareto data such aspareto/X,pareto/F,pareto/variable_names, andpareto/objective_names.
Because of that, import mode is currently Tier-2-oriented: it imports the incumbent design and evaluates that fixed design under stress.
Incumbent Evaluation Flow
evaluate_incumbent evaluates the existing first-stage design under one stress scenario:
- Read
incumbent.npzfrom the namespace baseline artifacts. - Load the eta-incerto experiment and the scenario/system collection for the requested variant.
- Apply the scenario perturbations to model parameters, solver options, scenario probabilities, and/or scenario CSV values.
- Convert the incumbent vector into first-stage investment decisions.
- Fix those first-stage investment variables on each perturbed scenario system.
- Run eta-incerto's
DeterministicEvaluatoron the perturbed scenario system with the first-stage variables already fixed. At this point the scenario is concrete and the investment design is fixed, so the remaining problem is a deterministic operational evaluation rather than a stochastic/robust/regret investment optimization. - Return
objective_value, solver status, feasibility, fixed-design metadata, and diagnostics for scenario metrics.
The important distinction is that incumbent_eval does not choose a new investment design. It fixes the baseline/imported first-stage design first, then evaluates how that design performs under the perturbed scenario. This matches the production Tier 2 goal: post-hoc stress evaluation of an existing design.
full_reopt remains available only when explicitly requested:
fixed_first_stage:scenario_runnercallsevaluate_incumbentand persistsresolveas skipped for compatibility.full_reopt:scenario_runnercalls bothevaluate_incumbentandsolve_scenario.
The decision to run or skip resolve lives in scenario_runner, not in the backend facade.
Scenario Metrics
Scenario-level metrics always include incumbent metrics:
objective.incumbent_eval.costloss.incumbent_vs_baseline.absloss.incumbent_vs_baseline.relflag.incumbent_feasibledecision.hamming_ratefragility.F_s
fragility.F_s is based on incumbent stress loss and incumbent feasibility. It does not depend on resolve metrics in the default production path.
When stress_evaluation_mode=full_reopt produces a real resolve result, scenario metrics may also include resolve extras:
objective.resolve.costloss.resolve_vs_baseline.absloss.resolve_vs_baseline.relloss.resolve_vs_incumbent.absloss.resolve_vs_incumbent.relflag.resolve_feasible
Aggregation Metrics
Aggregation always computes incumbent-based summaries:
- core objective spread uses
objective.incumbent_eval.*; - threshold and tail metrics use
objective.incumbent_eval.cost; - pair/asymmetry metrics use
loss.incumbent_vs_baseline.abs; - fragility CVaR uses
loss.incumbent_vs_baseline.rel.
When resolve values exist, aggregation also emits resolve summaries:
objective.resolve.*- resolve-vs-baseline loss summaries;
- resolve tail/CVaR summaries;
- resolve-enabled flatness/count diagnostics.
Those resolve summaries are optional and are not required for fixed-first-stage production runs.
Plotting
Plotting now prefers incumbent metrics:
- scorecard summaries use
objective.incumbent_eval.*as the primary objective family; - per-scenario summaries use incumbent stress cost and incumbent-vs-baseline loss;
- all-method sensitivity plots use incumbent objective and feasibility signals.
When full_reopt is used and resolve metrics exist, plotting can include resolve information as an additional comparison layer. Fixed-first-stage plots do not require resolve metrics.
Provenance and Summaries
Run summaries now report incumbent objective flatness/count diagnostics independently of resolve:
incumbent_objective_countincumbent_objective_unique_countincumbent_objective_missing_countincumbent_objective_flat_detected
Resolve diagnostics are only emitted when resolve is enabled:
resolve_enabledresolve_skipped_countresolve_objective_countresolve_objective_unique_countresolve_objective_missing_countresolve_objective_flat_detected
In fixed_first_stage, scenario payloads keep a compatibility block:
{
"resolve": {
"status": "skipped",
"skipped": true,
"reason": "fixed_first_stage"
}
}
This keeps older readers stable while making the production semantics explicit.
Documentation
Project overview and usage: this README on GitLab (README.md).
Related software
- eta-incerto — required dependency; provides optimization runtime and variant registry
Solver and third-party requirements
Stress tests solve MILP models through eta-incerto and eta-components (Linopy). Baseline and scenario runs inherit solver requirements from those packages:
- Gurobi (
gurobipy) — required for many default eta-incerto paths; commercial license required - CPLEX — optional; see eta-incerto scripts/README_CPLEX.md
- HiGHS — open-source alternative where supported
This package is licensed under BSD-2-Clause. Solver products are third-party software with their own licenses and terms.
Citation
For academic use, cite this repository using CITATION.cff. See AUTHORS.rst for further contributors.
License
BSD-2-Clause — see LICENSE. See also CHANGELOG.md and SECURITY.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eta_stress-1.0.7.tar.gz.
File metadata
- Download URL: eta_stress-1.0.7.tar.gz
- Upload date:
- Size: 87.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.13.2 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
944f959d0602e8f2bb48d09419087f75df4dbc41a47717fedfda9d12334522f3
|
|
| MD5 |
dc54a136f3083054f7300a2ece1edf22
|
|
| BLAKE2b-256 |
ac0cca98fcbf4aa520c4d1d970417e9a385447ea46082779f580d894aeabd1ea
|
File details
Details for the file eta_stress-1.0.7-py3-none-any.whl.
File metadata
- Download URL: eta_stress-1.0.7-py3-none-any.whl
- Upload date:
- Size: 121.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.13.2 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27d929966e0953dd38647da79085a56d17e74ad92a47b3872f7e5a9a4fb9f1e5
|
|
| MD5 |
de5bc4c7522bd2923c950580adefe87a
|
|
| BLAKE2b-256 |
c6adc278d9ff969db4205a8d9ddf492631eec3dc20e24e0a2378190ba7e28dfb
|