Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c90a53e
Out-of-the-box auto-config: capture design (requirements + open quest…
DLWoodruff Jun 28, 2026
9df05b1
Out-of-the-box: resolve mechanism, add first dated policy file
DLWoodruff Jun 28, 2026
3ea768b
Out-of-the-box: add effort tiers (instantiation depth) to design
DLWoodruff Jun 28, 2026
d02ee49
Out-of-the-box: internal-consistency pass on the design doc
DLWoodruff Jun 28, 2026
0a49efb
Out-of-the-box: policy selection is an optional path on each flag
DLWoodruff Jun 28, 2026
c43db0e
Out-of-the-box: rename advisories -> suggestions, emit after the run
DLWoodruff Jun 28, 2026
90c5a11
Out-of-the-box: bundle sizing via a shared effort-scaling model
DLWoodruff Jun 28, 2026
c364aec
Out-of-the-box: add --inspect-only (dry run) to the design
DLWoodruff Jun 28, 2026
f19a44f
Out-of-the-box: policy additional_options + computed suggestions
DLWoodruff Jun 28, 2026
6948a23
Out-of-the-box: the minus tier cannot bundle
DLWoodruff Jun 28, 2026
53d22e7
Out-of-the-box: EF gate reuses the bundle effort model + an EF budget
DLWoodruff Jun 28, 2026
e559f37
Out-of-the-box: per-concern option categories with superseded_by
DLWoodruff Jun 28, 2026
d84692e
Out-of-the-box: user-forced decomposition overrides EF gate; validati…
DLWoodruff Jun 28, 2026
a1e561a
Out-of-the-box: rank allocation -- small core, widened, unbalanced ra…
DLWoodruff Jun 28, 2026
e1ba4bc
Out-of-the-box: --inspect-only takes an optional assumed rank count
DLWoodruff Jun 28, 2026
07a6eff
Out-of-the-box: validator runs decomposition + produces a report
DLWoodruff Jun 28, 2026
03c7cc7
Out-of-the-box: only a small, solver-free part of the validator gates CI
DLWoodruff Jun 28, 2026
b5b49a2
Out-of-the-box: validator report details all tests, flags maxed-out i…
DLWoodruff Jun 28, 2026
879e0a2
Out-of-the-box: validator is a PR1 deliverable (not TBD); nail down i…
DLWoodruff Jun 28, 2026
821bad1
Out-of-the-box: full validator (incl. run-tier) in PR1 -- it's the tr…
DLWoodruff Jun 28, 2026
411e278
Out-of-the-box: internal-consistency pass (and reconcile with the pol…
DLWoodruff Jun 28, 2026
585bfa8
Out-of-the-box: validator flags problems, does not assert expected re…
DLWoodruff Jun 28, 2026
a655ae7
Out-of-the-box: add effort-calibration tool (sec.9, future)
DLWoodruff Jun 28, 2026
073a7f0
Out-of-the-box: calibration tool moves into PR1 (policy numbers are i…
DLWoodruff Jun 28, 2026
c9ca7ef
Out-of-the-box: note that --out-of-the-box-plus is not a tuning tool
DLWoodruff Jun 28, 2026
c3718a1
Out-of-the-box: commit the interpreter sketch (decision logic complet…
DLWoodruff Jun 28, 2026
21ced1b
Out-of-the-box: status header reflects the now-committed sketch
DLWoodruff Jun 28, 2026
ba3f0a5
Out-of-the-box: wire the interpreter -- minus + base tiers run end-to…
DLWoodruff Jun 28, 2026
6921626
Out-of-the-box: policy-file validator (design sec. 8)
DLWoodruff Jun 29, 2026
6a4b3d7
Out-of-the-box: effort-calibration tool (design sec. 9)
DLWoodruff Jun 29, 2026
0a16078
Out-of-the-box: ship calibrated numbers in the policy
DLWoodruff Jun 29, 2026
763dd2c
Out-of-the-box: quick-start docs and a dedicated page
DLWoodruff Jun 29, 2026
edba427
Out-of-the-box: raise CI coverage of the new modules (codecov)
DLWoodruff Jun 29, 2026
3ccb1da
Out-of-the-box: route solver choice by problem class (LP/MIP/QP/MIQP/…
DLWoodruff Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/test_pr_and_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ jobs:
run: |
coverage run $COV_ARGS -m pytest mpisppy/tests/test_generic_cylinders.py -v

- name: Test out-of-the-box interpreter wiring
run: |
coverage run $COV_ARGS -m pytest mpisppy/tests/test_out_of_the_box.py -v

- name: Test out-of-the-box policy validator (layers 1 + 2-synthetic)
run: |
coverage run $COV_ARGS -m pytest mpisppy/tests/test_ootb_validate.py -v

- name: Test out-of-the-box effort calibrator (pure fit)
run: |
coverage run $COV_ARGS -m pytest mpisppy/tests/test_ootb_calibrate.py -v

- name: Test xhat from file
run: |
coverage run $COV_ARGS -m pytest mpisppy/tests/test_xhat_from_file.py -v
Expand Down
577 changes: 577 additions & 0 deletions doc/designs/out_of_the_box_design.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions doc/src/generic_cylinders.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ run mpi-sppy. It provides command-line access to the hub-and-spoke
system, the extensive form solver, confidence intervals, and many
other features without requiring you to write a driver program.

.. tip::
If you are new to mpi-sppy, add ``--out-of-the-box`` and let the driver
pick a sensible configuration automatically, then read the equivalent
command line it prints to learn the explicit options. See
:ref:`out_of_the_box`.

Your Model File (Module)
------------------------

Expand Down
1 change: 1 addition & 0 deletions doc/src/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ MPI is used.
:caption: Running with generic_cylinders

generic_cylinders.rst
out_of_the_box.rst
examples.rst
ef.rst
chance_constraints.rst
Expand Down
254 changes: 254 additions & 0 deletions doc/src/out_of_the_box.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
.. _out_of_the_box:

Out-of-the-box auto-configuration
=================================

The ``--out-of-the-box`` option lets a relatively new user obtain a *sensible*
mpi-sppy run with almost no knowledge of the library's internals. You supply a
model module (and its scenario data); mpi-sppy introspects the environment and
the model and assembles a defensible configuration -- algorithm, solver, spokes,
flexible rank split, and proper bundling -- instead of requiring a hand-crafted
hub/spoke command line.

The spirit is *"here is my model, go,"* followed by a clear explanation of what
was chosen and how to do better.

.. note::
Out-of-the-box (OOTB) only *fills gaps*. **Any option you set explicitly
always wins** -- OOTB never overrides it. So you can start from
``--out-of-the-box`` and override individual choices as you learn.

Basic usage
-----------

Add ``--out-of-the-box`` to an otherwise minimal ``generic_cylinders`` command
line (your model still needs its scenario count -- ``--num-scens`` for two-stage
problems, ``--branching-factors`` for multistage):

.. code-block:: bash

# serial -> too few ranks for cylinders, so OOTB solves the EF
python -m mpisppy.generic_cylinders --module-name farmer --num-scens 3 \
--out-of-the-box

# 3+ ranks available; OOTB decomposes when the problem is big/hard enough
mpiexec -np 3 python -m mpi4py -m mpisppy.generic_cylinders \
--module-name farmer --num-scens 6 --out-of-the-box

OOTB prints the configuration it chose, the **equivalent explicit command line**
(so you can reproduce, learn from, and tweak it), runs the model, and then prints
a prioritized **Suggestions** list.

.. note::
OOTB decomposes only when it expects the decomposition to pay off. The base
tier estimates how long the monolithic EF would take and, if that is within
budget, solves the EF even when several ranks are available -- because for a
small or fast-solving model the EF *is* the right call. On a fast machine with
a commercial solver, the bundled examples (farmer, sizes, aircond) are cheap
enough that ``--out-of-the-box`` chooses the EF for all of them. To exercise
the cylinder path on a small problem, either request a spoke (any
decomposition flag, e.g. ``--lagrangian``, forces a decomposition) or use the
``--out-of-the-box-minus`` tier, which has no size estimate and decomposes
whenever there are more than a couple of scenarios:

.. code-block:: bash

mpiexec -np 3 python -m mpi4py -m mpisppy.generic_cylinders \
--module-name farmer --num-scens 6 --out-of-the-box-minus

What OOTB decides
-----------------

In order, OOTB chooses:

#. **Solver.** The first installed solver in a preference order (persistent
commercial, then commercial, then a free QP-capable solver, then LP/MIP-only).
An LP/MIP-only solver (cbc, glpk) automatically adds
``--linearize-proximal-terms`` because it cannot take the quadratic PH prox.
If you pass ``--solver-name`` it is used as-is (and carried over to
``--EF-solver-name`` if OOTB ends up solving the EF).
#. **Extensive form vs. decomposition.** With fewer than three ranks there is no
useful cylinder configuration (hub + at least two spokes), so OOTB solves the
**EF**. Above the rank floor, OOTB still solves the EF when the whole problem
is small enough to expect a quick monolithic solve (see *Effort and the EF
gate* below). Otherwise it decomposes. If you explicitly request a
decomposition (any spoke or a non-default hub) and have enough ranks, OOTB
never substitutes the EF.
#. **Spokes (a small, widened core).** Starting from a minimal core of one outer
bound (``--lagrangian``) and one inner/incumbent spoke (``--xhatshuffle``),
OOTB adds further spokes only while every cylinder would still keep at least a
couple of ranks. The preference is to give a few cylinders width rather than
pile on many weak single-rank spokes. So six ranks become three cylinders,
widened -- not six single-rank cylinders.
#. **Flexible rank split.** Ranks are split across cylinders unevenly: cheaper
cylinders (the xhat family) get a smaller share via per-spoke
``--*-rank-ratio`` flags. (This is a crude cold-start split; the right split
depends on relative subproblem solve cost.)
#. **Proper bundling.** When there are many scenarios, OOTB forms proper bundles,
choosing the largest ``--scenarios-per-bundle`` that divides the scenario
count, leaves at least as many bundles as ranks, and keeps a bundle's modeled
solve effort within budget.
#. **A few extra defaults**, each backed off if you addressed the same concern:
``--default-rho 1`` and the ``--grad-rho`` rho setter, ``--rel-gap 0.01``,
``--max-iterations 100``, and ``--dynamic-rho-primal-crit``.

Transparency
------------

OOTB prints the choices and the equivalent command line up front, and a
**Suggestions** list after the run (so the suggestions can reflect how the run
went). For example, a serial farmer run reports::

[out-of-the-box] tier 'base', policy 2026-06-28
- solver: gurobi_persistent (first available in preference order)
- --EF: only 1 ranks; decomposition needs >= 3
- --EF-solver-name gurobi_persistent: EF solver (gurobi_persistent)
[out-of-the-box] equivalent command line:
mpiexec -np 1 python -m mpi4py -m mpisppy.generic_cylinders \
--module-name farmer --num-scens 3 --EF --EF-solver-name gurobi_persistent
...
[out-of-the-box] Suggestions:
* Ran the monolithic EF because only 1 MPI rank(s) were available; with
>= 3 ranks OOTB would decompose (hub + bound spokes).

The equivalent command line is anchored with the module and scenario
specification and lists every flag OOTB added, so you can paste it (dropping
``--out-of-the-box``) to reproduce or modify the run.

Effort tiers (how deeply OOTB inspects the model)
-------------------------------------------------

Three mutually-exclusive flags select how deeply OOTB looks at the model; they
share one interpreter and one policy file and differ only in how much they
inspect. Every decision uses the best fact available and degrades gracefully to
a suggestion when a fact is missing.

.. list-table::
:header-rows: 1
:widths: 22 14 64

* - Flag
- Instantiates
- What it can decide
* - ``--out-of-the-box-minus``
- nothing
- EF gate by scenario *count*; solver by availability. Cannot size proper
bundles (no model size information).
* - ``--out-of-the-box`` *(default)*
- one probe scenario
- Size-aware EF gate; integrality- and size-aware bundling. The recommended
tier.
* - ``--out-of-the-box-plus``
- *(reserved)*
- Planned: instantiate all scenarios and do a brief timed solve for
solve-time / gap information. Currently behaves like the base tier.

``--out-of-the-box-plus`` is **not** an autotuner: it is "out-of-the-box with
more information," making the same one-shot decisions as the base tier. (It is
reserved for a future release; today it is equivalent to ``--out-of-the-box``.)

Effort and the EF gate
^^^^^^^^^^^^^^^^^^^^^^^

The base tier instantiates one scenario to read its size profile (continuous /
integer variable counts and nonant counts) and models solve *effort* from it.
The numbers in the policy file are **calibrated to roughly seconds**, so the EF
budget reads as a wall-clock target: OOTB solves the EF when the whole problem's
modeled effort is within ``ef_effort_budget`` (about that many seconds), and
sizes bundles so a bundle is at most a small multiple as hard as a single
scenario. Integer content scales superlinearly, so an integer-heavy model
decomposes at a far smaller scenario count than a continuous one.

``--inspect-only`` (dry run)
----------------------------

``--inspect-only`` does the inspection, prints the configuration, the equivalent
command line, and config-time suggestions, then **stops before the production
run**. It is independent of OOTB (on its own it just verifies that one scenario
instantiates), but pairs naturally with it:

.. code-block:: bash

# plan the run, print the equivalent command line, do not solve
python -m mpisppy.generic_cylinders --module-name farmer --num-scens 3 \
--out-of-the-box --inspect-only

``--inspect-only`` takes an optional **assumed rank count** for HPC planning:
``--inspect-only 512`` plans as if 512 ranks were available -- so you can get the
recommended command line for a large job *from a login node, without launching
it*. Everything else (installed solvers, model size) still comes from the real
session; only the rank count is hypothetical.

Policy files
------------

OOTB's choices are driven by a dated, declarative **policy file** under
``mpisppy/generic/ootb_policies/`` -- data, not code, interpreted by a thin
Python routine so every decision is explainable. A bare ``--out-of-the-box``
uses the newest shipped default policy; ``--out-of-the-box PATH`` uses the policy
file at ``PATH`` (the optional value of the flag *is* the path). Policy files
with different *foci* may ship side by side, distinguished by filename; you
select one by passing its path. The run logs which policy and ``policy_version``
it used.

The policy holds the solver preference order, the EF budget, the spoke ladder and
rank ratios, the bundle-effort model, and the extra-option defaults. Its numbers
are produced by the calibration tool (below), not hand-guessed.

.. _ootb_validator:

Validating a policy file
------------------------

A policy file is checked by a validator that confirms it is well-formed and that
its recommendations make sense -- and, on demand, actually run:

.. code-block:: bash

# static schema + decision checks on the default policy
python -m mpisppy.generic.ootb_validate

# also exercise the real example models (needs a solver to instantiate)
python -m mpisppy.generic.ootb_validate --examples

# also actually run the recommended configs and flag problem cases
python -m mpisppy.generic.ootb_validate --run --json report.json

The validator has three layers: static schema checks (every referenced flag is
real, keys/types are right); decision checks (the EF gate fires when it should,
forced decomposition wins, bundling is valid, no conflicting rho setters); and
run checks that execute the recommended configurations and **flag** two cases for
a human to review -- an EF that misses a 1% gap in ten minutes, and cylinders
that max out on iterations. It produces a human-readable and a machine-readable
(``--json``) report. The fast, solver-free layers gate continuous integration;
the run tier is for nightly / local use.

.. _ootb_calibrator:

Calibrating the effort numbers
------------------------------

The effort coefficients and budget are produced by a calibration tool from timed
solves on the example models, so they track measured wall-clock time on a
reference machine rather than being guesses:

.. code-block:: bash

python -m mpisppy.generic.ootb_calibrate --solver-name gurobi \
--output mpisppy/generic/ootb_policies/ootb_policy_<date>.json

The tool times extensive-form solves over a spread of bundle sizes, fits the
continuous / integer / nonant coefficients (choosing the integer exponent by best
fit), keeps the coefficients in seconds units so the budgets read as seconds, and
writes a new dated policy with provenance. Solve time is machine- and
solver-dependent (and noisy for MIPs), so a calibrated policy is per reference
machine and approximate; re-run the tool to recalibrate for your environment.

Limitations
-----------

OOTB aims for a *defensible* configuration, not an *optimal* one. It does not
tune convergence parameters to optimality and does not search a parameter space.
For full control, write an explicit hub/spoke command line (the rest of
:ref:`generic_cylinders` documents every option) -- and remember that OOTB emits
exactly such a command line for you to start from.
18 changes: 18 additions & 0 deletions doc/src/quick_start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,24 @@ guidance and HPC-specific tips, see :ref:`Install mpi4py`.
Running the Farmer Example
---------------------------

**Recommended first run: let mpi-sppy configure itself.** Add
``--out-of-the-box`` and the driver introspects the environment and the model and
picks a sensible configuration (solver, EF vs. decomposition, spokes, bundling),
prints the equivalent explicit command line, and runs it:

.. code-block:: bash

python -m mpisppy.generic_cylinders --module-name farmer --num-scens 3 \
--out-of-the-box

Any option you set explicitly always wins, so this is a good starting point you
can refine. (For a small, fast-solving model like farmer, OOTB will sensibly
choose the extensive form; it decomposes for larger or harder problems.) See
:ref:`out_of_the_box` for the full description -- effort tiers,
``--inspect-only``, policy files, and the validation and calibration tools.

The explicit forms below show what such a run is equivalent to.

**Solve the EF** (does not use MPI):

.. code-block:: bash
Expand Down
Loading