generic_cylinders.py
The program mpisppy.generic_cylinders.py is the recommended way to
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.
Your Model File (Module)
Pyomo Models
Pyomo modellers use generic_cylinders.py by creating a Python module that provides
certain functions. The module name is given (without the .py extension)
as the --module-name argument, and it should be the first argument.
It is needed even with --help:
python -m mpisppy.generic_cylinders --module-name farmer/farmer --help
The module must contain:
scenario_creator– see scenario_creator functionscenario_names_creator– see Helper Functions in the Model Filekw_creator– see Helper Functions in the Model Fileinparser_adder– see Helper Functions in the Model Filescenario_denouement(can beNone) – see Helper Functions in the Model File
Optional functions include _rho_setter, id_fix_list_fct,
hub_and_spoke_dict_callback, custom_writer, and
get_mpisppy_helper_object. See Helper Functions in the Model File for details.
non-Pyomo Models
To use mpi-sppy for models not written in Pyomo, scenarios and scenario tree information can be provided in files. See Loose integration for more information.
Standard Formats (SMPS, MPS)
As an exception to the --module-name requirement, the following flags
can be used as the first argument and the appropriate module will be
inferred automatically:
--smps-dir— usesmpisppy.problem_io.smps_module(see SMPS Format Support)--mps-files-directory— usesmpisppy.problem_io.mps_module(see Loose integration)
For example:
python -m mpisppy.generic_cylinders --smps-dir examples/sizes/SMPS \
--solver-name cplex --EF
Using --module-name together with --smps-dir or
--mps-files-directory is an error.
Solving the Extensive Form
To solve the EF directly (no MPI required):
python -m mpisppy.generic_cylinders --module-name farmer --num-scens 3 \
--EF --EF-solver-name gurobi
Note
Most command line options relevant to the EF start with --EF.
Other options are silently ignored when --EF is specified
(one exception is --solution-base-name).
Running PH with Spokes
To run Progressive Hedging with bound-computing spokes, use mpiexec (or mpirun):
mpiexec -np 3 python -m mpi4py mpisppy/generic_cylinders.py \
--module-name farmer --num-scens 3 \
--solver-name gurobi_persistent --max-iterations 10 \
--default-rho 1 --lagrangian --xhatshuffle --rel-gap 0.01
Note
If you are solving the EF directly, you do not need mpiexec.
Only decomposition methods (PH, APH, etc.) require MPI.
Choosing a Hub Algorithm
By default, the hub runs synchronous PH. Alternative hub algorithms are selected with flags:
(default) PH – no flag needed
--APH– Asynchronous PH (see APH)--subgradient-hub– Subgradient method--fwph-hub– Frank-Wolfe PH--ph-primal-hub– PH primal
Choosing Spokes
Spokes provide bounds and heuristic solutions. Enable them with flags:
Outer bound (lower bound for minimization) spokes:
--lagrangian– Lagrangian relaxation bound--lagranger– Lagrangian with reduced-cost fixing--fwph– Frank-Wolfe PH bound--subgradient– Subgradient bound--ph-dual– PH dual bound--relaxed-ph– Relaxed PH bound--reduced-costs– Reduced costs bound
Inner bound (upper bound for minimization) spokes:
--xhatshuffle– Randomly shuffle scenario solutions--xhatxbar– Use xbar as a candidate solution
See Spokes for details on each spoke type.
Multistage Options
For multistage problems (three or more stages), the model’s
inparser_adder should register branching_factors via
cfg.multistage() or cfg.add_branching_factors().
--stage2-ef-solver-nameSolver to use for forming second-stage EFs during xhat evaluation. When set, the
xhatshufflespoke forms an EF for each second-stage node, fixes the first-stage nonants, and solves. The number of ranks allocated to the xhatshuffle spoke must be an integer multiple of the number of second-stage nodes.
Example for the hydro model (three stages, branching factors 3 3):
mpiexec -np 3 python -m mpi4py mpisppy/generic_cylinders.py \
--module-name hydro --solver-name cplex --max-iterations 100 \
--default-rho 1 --lagrangian --xhatshuffle --rel-gap 0.001 \
--branching-factors "3 3" --stage2-ef-solver-name cplex
ADMM Decomposition
generic_cylinders supports ADMM-based decomposition via the --admm
and --stoch-admm flags. See ADMM with generic_cylinders for full details,
including model module requirements, bundling support, and examples.
Rho Settings
The penalty parameter rho is critical for PH performance.
See Rho Setting for a full description of all rho-related options,
including --default-rho, --sep-rho, --coeff-rho,
--sensi-rho, --grad-rho, and adaptive rho updaters.
Extensions via Command Line
Some extensions can be activated directly from the command line:
--fixer– Fix variables that have converged--mipgaps-json <file>– MIP gap schedule from a JSON file--user-defined-extensions <module>– Load a custom extension module--wtracker– Track W (Lagrange-multiplier) values per iteration and write a convergence report at the end of the run (see wtracker_extension)
See Extensions for details on available extensions.
Solution Output
To write solutions, use --solution-base-name:
python -m mpisppy.generic_cylinders --module-name farmer --num-scens 3 \
--EF --EF-solver-name gurobi --solution-base-name farmersol
This writes nonanticipativity variable data to files with the given base
name and full scenario solutions to a directory named
<base-name>_soldir.
See Output Solutions for more details.
MMW Confidence Intervals
MMW confidence intervals can be computed directly via generic_cylinders
flags such as --mmw-num-batches. See MMW confidence intervals:
for details.
Pickled Scenarios and Bundles
The generic_cylinders program supports pickling and unpickling
scenarios and proper bundles, optionally with presolve, a user-supplied
cleanup callback, and an iteration 0 solve baked into the pickle. See
Pickling Scenarios and Bundles for the full workflow, including the
tuning workflow and the
single-run case where pickling iter0
can be faster than not pickling because all ranks are available during
the pickling phase.
Advanced: hub_and_spoke_dict_callback
Advanced users can directly manipulate the hub and spoke dicts
immediately before spin_the_wheel() is called. If the module (or class)
contains a function called hub_and_spoke_dict_callback(), it will be
called immediately before the WheelSpinner object is created. The
hub_dict, list_of_spoke_dict, and cfg object will be passed
to it. See generic_cylinders.py for details.
Advanced: Using a Class in the Module
If you want to have a class in the module to provide helper functions,
your module still needs to have an inparser_adder function and the
module will need to have a function called
get_mpisppy_helper_object(cfg) that returns the object. It is called
by generic_cylinders.py after cfg is populated and can be used to
create a class. Note that inparser_adder cannot make use of the class
because it is called before get_mpisppy_helper_object.
The class definition needs to include all helper functions other than
inparser_adder. The example examples.netdes.netdes_with_class.py
demonstrates this (although in that particular example, there is no
advantage to doing so).
custom_writer
Advanced users can write their own solution output function. If the
module contains a function called custom_writer(), it will be passed
to the solution writer. Up to four functions can be specified in the
module (or the class if you are using a class):
ef_root_nonants_solution_writer(file_name, representative_scenario, bundling_indicator)ef_tree_solution_writer(directory_name, scenario_name, scenario, bundling_indicator)first_stage_solution_writer(file_name, scenario, bundling_indicator)tree_solution_writer(directory_name, scenario_name, scenario, bundling_indicator)
The first two, if present, will be used for the EF and the second two
for hub-and-spoke solutions. For further information, look at the code
in mpisppy.generic_cylinders.py and in mpisppy.utils.sputils for
example functions such as first_stage_nonant_npy_serializer. There is
a simple example in examples.netdes.netdes_with_class.py.
Warning
These functions will only be used if cfg.solution_base_name has been
given a value by the user.
Warning
Misspelled function names will not result in an error message, nor will they be called.
config-file
This specifies a text file that may contain any command line options.
Options on the command line take precedence over values set in the file.
There is an example text file in examples.sizes.sizes_config.txt.
This option gets pulled in with cfg.popular_args and processed by
cfg.parse_command_line.
Note that required arguments such as num_scens must be on the
command line.
solver-options
mpi-sppy passes solver-specific options to the underlying Pyomo
solver plugin via a whitespace-separated string of key=value
pairs. The global flag is --solver-options; every spoke also
takes a per-spoke variant — --lagrangian-solver-options,
--reduced-costs-solver-options, --subgradient-solver-options,
and so on — that overlays on top of the global flag for that
spoke’s solves.
Example:
--solver-options "presolve=2 threads=4"
--lagrangian-solver-options "mipgap=0.01"
With this invocation, the lagrangian spoke’s effective solver
options are {presolve=2, threads=4, mipgap=0.01}: the spoke
flag adds mipgap and leaves the global presolve and
threads in place. The hub and the other spokes see the
global dict {presolve=2, threads=4} unchanged.
Warning
Behavior change in 2026: per-spoke solver-options flags
overlay the global --solver-options dict; previously
they replaced it for that spoke. In the unlikely event
you relied on the spoke flag dropping a global key, the
recipe is to re-spell every key you want in the spoke
options, or to omit the global --solver-options flag
entirely. The new behavior
is a superset of the old in every case where the spoke flag’s
keys are a subset of the global’s, which is the common
pattern (spoke flag tightens mipgap, leaves the rest
alone).
Two option names — mipgap and threads — are translated
to each solver’s native spelling at solve time, so the same CLI
invocation works whether the configured solver is CPLEX, Gurobi,
Xpress, or HiGHS. Other option keys pass through to the solver
unchanged. If you supply a solver-native key directly (e.g.
--solver-options "mip_rel_gap=0.01" for HiGHS), it wins over
any mipgap set elsewhere.
For iteration-aware mipgap, use --iter0-mipgap and
--iterk-mipgap (plus their per-spoke variants), or
--mipgaps-json <path> for a mipgap-only schedule.
--max-solver-threads sets a system-level thread cap that wins
over any inline threads value; use it on shared HPC nodes.
For solver logging, see --solver-log-dir below — do not try
to enable solver logs through --solver-options.
Solver-options file
For richer configurations, --solver-options-file <path> reads
a JSON file with per-iteration and per-spoke sub-blocks. Schema:
{
"default": {"threads": 4, "presolve": 2},
"iter0": {"mipgap": 1e-4},
"iterk": {"mipgap": 1e-3},
"starting_at_iter": {"5": {"mipgap": 1e-5}, "10": {"mipgap": 1e-6}},
"spokes": {
"lagrangian": {
"default": {"mipgap": 0.01},
"starting_at_iter": {"5": {"mipgap": 0.001}}
},
"reduced_costs": {"iter0": {"mipgap": 0.001}}
}
}
Sub-blocks behave per their names:
default— applies to every iteration.iter0— only at iteration 0.iterk— at iteration 1 and beyond.starting_at_iter— keyed by iteration numberN(withN >= 1); applies from iterationNonward (so"5"first fires atk = 5and persists until a laterstarting_at_iterentry overrides it). For options that should apply at every iteration, use thedefaultsub-block instead —starting_at_iterwithN = 0is rejected because it would silently outrankiter0anditerk.spokes— per-spoke overrides keyed by spoke name. Each spoke sub-block has the same shape minusspokes.
Per-spoke files (--lagrangian-solver-options-file <path>, etc.)
are also accepted; they apply only to that spoke and have the same
shape as a global file (the spokes sub-block, if present, is
ignored).
Precedence at the same iteration / predicate, lowest to highest:
--solver-options-fileentries.Inline
--solver-options(defaultpredicate only).--mipgaps-json(starting_at_iteronly — planned for deprecation; superseded by--solver-options-file).--iter0-mipgap/--iterk-mipgap/--max-solver-threads(CLI sugar).
More specific predicates always win for any iteration that matches
both: at k = 7, an starting_at_iter: {"5": …} entry overrides
any iterk entry, even though both apply.
solver-log-dir
This specifies a directory where solver log files for every subproblem solve will be written. This directory will be created for the user and must not exist in advance. File names disambiguate scenario and rank, so concurrent solves do not clobber one another.
If the per-solve log volume is too high, add
--hub-only-solver-logs to write logs only for hub-side solves
(spoke-side subproblem solves are skipped). --hub-only-solver-logs
requires --solver-log-dir to also be set.
Warning
Do not try to enable solver logging by passing a solver-specific
log flag through --solver-options (e.g.
--solver-options "logfile=run.log" for CPLEX, or the equivalent
LogFile / log_file for Gurobi / HiGHS). Because every
subproblem solve across every rank would share that one path, the
file overwrites itself or interleaves output from concurrent solves;
the resulting log is rarely useful. Use --solver-log-dir (with
--hub-only-solver-logs if needed) instead.
warmstart-subproblems
This option causes subproblem solves to be given the previous iteration solution as a warm-start. This is particularly important when using an option to linearize proximal terms.
turn-off-names-check
By default, mpi-sppy verifies that all scenarios list the same
non-anticipative variables in the same order. This catches a common bug
where a scenario_creator provides variables in an inconsistent order
across scenarios. Use --turn-off-names-check to disable this
validation.
Note
This check is automatically disabled when proper bundles are used, because bundled scenarios are EFs that have different names across bundles.
Presolve (FBBT and OBBT)
The --presolve option enables variable bounds tightening before
solving, which can improve solver performance and numerical stability.
Feasibility-Based Bounds Tightening (FBBT): Uses constraint propagation to tighten variable bounds. Fast and always applied when presolve is enabled.
Optimization-Based Bounds Tightening (OBBT): Uses optimization to find the tightest possible bounds by solving auxiliary min/max problems. More expensive but can achieve significantly tighter bounds.
python -m mpisppy.generic_cylinders farmer.farmer --num-scens 3 --presolve
To enable OBBT in addition to FBBT:
python -m mpisppy.generic_cylinders farmer.farmer --num-scens 3 --presolve --obbt
Warning
OBBT may not be compatible with all solver interfaces, particularly persistent solvers.
The OBBT implementation uses Pyomo’s obbt_analysis function. For more
details, see mpisppy.opt.presolve.py.