9. GridPath Architecture
This section describes GridPath’s modular architecture, i.e. how the modules are linked and work together (as opposed to their formulation or the functionality they add).
9.1. GridPath Modules
9.1.1. gridpath.auxiliary.module_list
This module contains:
the list of all GridPath modules;
the modules included in each optional feature;
the ‘cross-feature’ modules;
the method for determining the user-requested features for the scenarios;
the method for loading modules.
- all_modules_list()[source]
- Returns:
list of all GridPath modules in order they are loaded
This is the list of all GridPath modules in the order they would be loaded if all optional features were selected.
- cross_feature_modules_list()[source]
- Returns:
dictionary with a tuple of features as keys and a list of modules to be included if all those features are selected as values
Some modules depend on more than one feature, i.e. they are included only if multiple features are selected. These relationships are described in the ‘cross_modules’ dictionary here.
- determine_modules(features=None, scenario_directory=None, multi_stage=None)[source]
- Parameters:
features – List of requested features. Optional input; if not specified, function will try to load ‘features.csv’ file to determine the requested features.
scenario_directory – the scenario directory, where we will look for the list of requested features. Optional input; if not specified, function will look for the ‘features’ input parameter
multi_stage – Boolean. Optional input that determines whether the modules that fix variables are used (yes if True, no if False); if not specified, this function will check the scenario_directory to determine whether there are stage subdirectories (if there are not, the ‘fix variables’ modules are removed).
- Returns:
the list of modules – a subset of all GridPath modules – needed for a scenario. These are the module names, not the actual modules.
This method determines which modules are needed for a scenario based on the features specified for the scenario. The features can be either directly specified as a list or by providing the directory where a ‘features.csv’ file lists the requested features.
We start with the list of all GridPath modules from all_modules_list() as the list of modules to use in the scenario. We then iterate over all optional features, which we get from the keys of the optional_modules_list() method above; if the feature is in the list of user-requested features, we do nothing; if it is not, we remove all of the feature’s modules from the list of modules to use. Similarly, for the cross feature modules, which we get from the cross_feature_module_list() method, we check if all features they depend on are included and, if not, remove those modules from the list of modules to use.
- load_modules(modules_to_use)[source]
- Parameters:
modules_to_use – a list of the names of the modules to use
- Returns:
list of imported modules (Python <class ‘module’> objects)
Load the requested modules and return them as a list of Python module objects.
- optional_modules_list()[source]
- Returns:
dictionary with the optional feature names as keys and a list of the modules included in each feature as values
These are all of GridPath’s optional modules grouped by features (features as the dictionary keys). Each of these modules belongs to only one feature.
9.1.2. gridpath.auxiliary.dynamic_components
This module creates the DynamicComponents class, which contains the lists and dictionaries of the names of dynamic optimization components. These are components that are populated by GridPath modules based on the selected features and the scenario input data.
- class DynamicComponents[source]
Here we initialize the class object and its components that will contain the dynamic model components, i.e. lists and dictionary with the names of the optimization components that are populated based on whether features are selected (i.e. certain modules are called) and based on the scenario input data.
9.2. Running GridPath
9.2.1. Running a Scenario
gridpath.run_scenario
This script runs a GridPath scenario. It assumes that scenario inputs have already been written.
The main() function of this script can also be called with the gridpath_run command when GridPath is installed.
- create_abstract_model(model, dynamic_components, loaded_modules, scenario_directory, weather_iteration, hydro_iteration, availability_iteration, subproblem, stage)[source]
- Parameters:
model – the Pyomo AbstractModel object
dynamic_components – the populated dynamic model components class
loaded_modules – list of the required modules as Python objects
scenario_directory –
subproblem –
stage –
To create the abstract model, we iterate over all required modules and call their add_model_components method to add components to the Pyomo AbstractModel. Some modules’ add_model_components method also require the dynamic component class as an argument for any dynamic components to be added to the model.
- create_problem(scenario_directory, weather_iteration, hydro_iteration, availability_iteration, subproblem, stage, multi_stage, parsed_arguments, timing_summary_file_path=None)[source]
- Parameters:
scenario_directory – the main scenario directory
subproblem – the horizon subproblem name
stage – the stage subproblem name
parsed_arguments – the user-defined script arguments
timing_summary_file_path – the timing summary file path (None if not logging)
- Returns:
modules_to_use (list of module names used in scenario), loaded_modules (Python objects), dynamic_inputs (the populated dynamic components class), instance (the problem instance), results (the optimization results)
This method creates the problem instance.
To create the problem, we use a Pyomo AbstractModel() class. We will add Pyomo optimization components to this class, will load data into the components, and will then compile the problem.
We first need to determine which GridPath modules we need to use. See determine_modules method (imported from gridpath.auxiilary.module_list) and import those modules (via the load_modules method imported from gridpath.auxiliary.module_list).
We then determine the dynamic model components based on the selected modules and input data. See populate_dynamic_components method.
The next step is to create the abstract model (see create_abstract_model method) and load the input data into its components (see load_scenario_data).
Finally, we compile the problem (see create_problem_instance method). If any variables need to be fixed, this is done as the last step here (see the fix_variables method).
- create_problem_instance(model, loaded_data)[source]
- Parameters:
model – the AbstractModel Pyomo object with components added
loaded_data – the DataPortal object with the data loaded in and linked to the relevant model components
- Returns:
the compiled problem instance
Compile the problem based on the abstract model formulation and the data loaded into the model components.
- fix_variables(instance, dynamic_components, scenario_directory, weather_iteration, hydro_iteration, availability_iteration, subproblem, stage, loaded_modules)[source]
- Parameters:
instance – the compiled problem instance
dynamic_components – the dynamic component class
scenario_directory – str
subproblem – str
stage – str
loaded_modules – list of imported GridPath modules as Python objects
- Returns:
the problem instance with the relevant variables fixed
Iterate over the required GridPath modules and fix variables by calling the modules’ fix_variables, if applicable. Return the modified problem instance with the relevant variables fixed.
- load_scenario_data(model, dynamic_components, loaded_modules, scenario_directory, weather_iteration, hydro_iteration, availability_iteration, subproblem, stage)[source]
- Parameters:
model – the Pyomo abstract model object with components added
dynamic_components – the dynamic components class
loaded_modules – list of the imported GridPath modules as Python objects
scenario_directory – the main scenario directory
subproblem – the horizon subproblem
stage – the stage subproblem
- Returns:
the DataPortal object populated with the input data
Iterate over all required GridPath modules and call their load_model_data method in order to load input data into the relevant model components. Return the resulting DataPortal object with the data loaded in.
- main(args=None)[source]
This is the ‘main’ method that runs a scenario. It takes in and parses the script arguments, determines the scenario structure (i.e. whether it is a single optimization or has subproblems), and runs the scenario. This method also returns the objective function value(s).
- parse_arguments(args)[source]
- Parameters:
args – the script arguments specified by the user
- Returns:
the parsed known argument values (<class ‘argparse.Namespace’> Python object)
Parse the known arguments.
- run_optimization_for_subproblem(scenario_directory, weather_iteration_directory, hydro_iteration_directory, availability_iteration_directory, subproblem_directory, stage_directories, multi_stage, parsed_arguments, objective_values)[source]
Check if there are stages in the subproblem; if not solve subproblem; if, yes, solve each stage sequentially
- run_scenario(scenario_directory, scenario_structure, parsed_arguments)[source]
Check the scenario structure, iterate over all subproblems if they exist, and run the subproblem optimization.
The objective function is returned, but it’s only really used if we are in ‘testing’ mode.
- Parameters:
scenario_directory – scenario directory path
scenario_structure – the subproblem structure object
parsed_arguments –
- Returns:
the objective function value (NPV); only used in ‘testing’ mode.
9.2.2. Database Access
gridpath.get_scenario_inputs
This script iterates over all modules required for a GridPath scenario and calls their write_model_inputs() method, which queries the GridPath database and writes the .tab input files to the scenario directory.
The main() function of this script can also be called with the gridpath_get_inputs command when GridPath is installed.
Two mechanisms can restrict the run to part of the scenario’s structure:
--temporal_structure_csv_overwritewith--temporal_structure_csv_pathREPLACES the database-derived scenario structure with the CSV’s (columns: weather_iteration, hydro_iteration, availability_iteration, subproblem, stage; one row per subproblem/stage). Fully general – any subset of cells, down to individual subproblems/stages. Every listed combination is validated to exist in the scenario’s database-derived structure (combinations outside it have no input data and would otherwise fail late); the directory-layout flags are derived from the CSV’s values (see get_scenario_structure_from_csv).The
--single_draw WEATHER HYDRO AVAILABILITYoption (0 for iteration levels the scenario doesn’t use) SLICES the resolved structure to a single iteration draw. Use, for example, for re-materializing one draw of a scenario directory that was cleaned after import. The draw is checked to exist, and the scenario’s directory-layout flags are preserved. The slice applies to whatever structure was resolved.
With either mechanism, the post-import cleanup marker (if present) is left
in place, since the rest of the tree is still in its cleaned state; solve
the regenerated part with run_scenario’s --ignore_cleanup_marker. Only
a full regeneration of the database-derived structure removes the marker.
gridpath.import_scenario_results
This script iterates over all modules required for a GridPath scenario and calls their import_results_into_database() method, which loads the scenario results files into their respective database table.
The main()_ function of this script can also be called with the gridpath_import_results command when GridPath is installed.
This script always deletes ALL of the scenario’s prior database results
before importing. Note the implication for
--temporal_structure_csv_overwrite: only the CSV’s
iteration/subproblem/stage cells are then imported, so on a scenario that
already has results, the import replaces them with the CSV’s subset only
(intended for scenarios with no prior results for other cells, e.g.
gridpath_iterate’s freshly cloned conditions-only re-runs).
There is deliberately no single-draw mode here: gridpath_run_e2e
--per_draw_lifecycle --single_draw instead runs the per-draw machinery
(run_end_to_end_per_draw), whose importer deletes and re-imports ONLY the
requested draw’s results (via delete_scenario_results_for_draw) and never
calls this script’s delete-all step. Passing –single_draw to this script
is refused rather than silently ignored, since the user would get the
delete-all behavior they were specifically trying to avoid.
The import assigns each (weather iteration, hydro iteration, availability
iteration, subproblem, stage) an import status – see the
IMPORT_STATUS_* constants – and main() returns the statuses as a
dictionary keyed by that tuple. If results were imported for none or only some of the
subproblems/stages, a warning is printed regardless of the –quiet setting:
skipped subproblems are otherwise silent, and, since all prior results for
the scenario are deleted at the start of the import step, an import that
skips everything leaves the scenario with no results in the database while
appearing to have succeeded. Subproblems/stages skipped because the solver
status was not ‘ok’ are reported by their solver termination condition
(e.g. ‘infeasible’), so the warning says WHY there are no results for them.
gridpath.process_results
This script iterates over all modules required for a GridPath scenario and calls their process_results() method, which makes updates to database tables.
The main() function of this script can also be called with the gridpath_process_results command when GridPath is installed.
9.2.3. Running End-to-End
gridpath.run_end_to_end
This script calls the __main__ functions of get_scenario_inputs.py, run scenario.py, import_scenario_results.py, and process_results.py to run a scenario end-to-end, i.e. get the scenario inputs from the database, solve the scenario problem, import the results the database and perform any necessary results-processing.
The main() function of this script can also be called with the gridpath_process_results command when GridPath is installed.
gridpath.run_end_to_end_per_draw
Per-draw end-to-end mode (gridpath_run_e2e --per_draw_lifecycle) for
Monte Carlo scenarios with many iteration draws.
Instead of writing all inputs, solving everything, and then importing everything – which materializes the entire scenario directory (millions of small files for large Monte Carlo cases) before any of it can be reclaimed – this mode pipelines the run one iteration draw (weather iteration, hydro iteration, availability iteration) at a time:
The main loop writes a batch of draws’ inputs (
--n_draws_per_solve_batchdraws per batch, default 1), solves the batch with one run_scenario call – whose--n_parallel_solvepool parallelizes over the batch’s draws x subproblems – and hands the solved batch to the importer queue.A single importer thread – the only database writer – imports queued batches while other draws are still solving, and, with
--cleanup_after_import/--archive_after_import, cleans each draw’s directory as soon as its batch’s import succeeds. The queue is bounded (--max_draws_pending_importbatches), so if importing falls behind, solving pauses and the on-disk footprint stays bounded.The database is switched to WAL journal mode for the duration of the run so the main loop’s input-writing reads can proceed alongside the importer’s writes; the prior journal mode is restored at the end.
Choosing the parallelization settings (peak on-disk footprint is about (1 + –max_draws_pending_import) x –n_draws_per_solve_batch draws):
--n_parallel_solveis the CPU (and memory) knob: each in-flight subproblem occupies roughly one core – assuming single-threaded solver settings; if the solver is configured to use multiple threads, budget cores ~= –n_parallel_solve x solver threads instead – plus the memory for its model and the solver’s workspace. Start at about the machine’s core count minus one (the importer thread and the main loop overlap with solving), and lower it if memory binds first: in-flight subproblems x per-subproblem peak memory must fit in RAM.--n_draws_per_solve_batchis NOT a CPU knob – it only determines how much work the pool can see at once. Actual concurrency is min(–n_parallel_solve, batch size x subproblems per draw), so make the batch just large enough to feed the pool:Many subproblems per draw (e.g. weekly subproblems over a year): the default batch of 1 already offers a full pool of tasks; leave it.
One subproblem per draw: set the batch to –n_parallel_solve (e.g. on a 10-core budget, both 10) – with the default batch of 1, the draws solve sequentially no matter how many cores are available.
In-between shapes: the smallest batch with batch size x subproblems per draw >= –n_parallel_solve.
Batches larger than needed add no speed – the pool caps concurrency – and only raise the disk footprint and delay each batch’s import/cleanup (a batch is imported only once it has fully solved). One exception: if solve times vary a lot across a batch, workers idle while the last tasks finish, so a batch of 2-3x the pool size amortizes that end-of-batch tail at proportionally higher footprint.
Each draw’s import is idempotent: the importer first deletes the draw’s
prior database rows, so a crashed or killed run can simply be re-run.
Completed draws are recognized on re-run by their rows in the cleanup
marker file (with cleanup/archiving on) and are skipped entirely; all other
draws are re-solved by default, exactly like the classic whole-scenario
mode – pass --incomplete_only to skip re-solving subproblems whose
results are already on disk.
Some more notes:
Linked-subproblem scenarios are refused: subproblems then depend on each other’s inputs and the draws cannot be processed independently.
--temporal_structure_csv_overwrite works with this mode: the draws
are then iterated from the CSV’s structure instead of the database’s, so a
per-draw run can be restricted to a subset of the scenario’s draws. The CSV
must list WHOLE draws here (each processed draw’s database results are
deleted in full before its re-import, so a partial draw would lose its
unlisted subproblems’ results – refused with a clear error); sub-draw
subsets belong in the classic pipeline.
gridpath_run_e2e --per_draw_lifecycle --single_draw WEATHER HYDRO
AVAILABILITY (0 for iteration levels the scenario doesn’t use; the two
flags are required together, –single_draw being a selector for this mode)
runs this same machinery for one
requested draw: its inputs are (re)written, it is solved and imported –
deleting only THIS draw’s prior database rows, so the scenario’s other
results are untouched – and it is cleaned/archived if those options are
set. An explicitly requested draw is never skipped as already-completed,
and a scenario directory cleaned after import needs no special handling
(the draw is simply re-materialized). This is the one-command way to re-run
or debug a single draw of a large Monte Carlo case.
gridpath.scenario_directory_cleanup
Optional lifecycle management for the on-disk scenario directory after results have been imported into the database.
For database-driven workflows the scenario directory is a regenerable
intermediate: inputs are written from the database and results are read back
into it. Monte Carlo cases with many iterations accumulate millions of small
files, so gridpath_run_e2e offers --cleanup_after_import (delete) and
--archive_after_import (one tarball per iteration “draw”, then delete)
to reclaim the directory once its contents are safely in the database.
The unit of cleanup is one iteration draw – a (weather iteration, hydro
iteration, availability iteration) directory path, or the scenario
directory’s own contents when the scenario has no iteration levels. A draw
is only cleaned if EVERY one of its subproblems/stages has import status
“imported” (see import_scenario_results); by default, draws with any
skipped or failed subproblem are left fully intact. With
--cleanup_granularity subproblem, the imported subproblems WITHIN such
partially imported draws are cleaned too, retaining only the not-imported
subproblems (useful when a single stuck subproblem would otherwise strand a
large draw on disk); fully imported draws are still cleaned as whole draws,
so re-run resume bookkeeping is the same at both granularities.
Retained in all cases: the scenario-level files
(scenario_description.csv, features.csv, solver_options.csv,
units.csv, multi_stage_flag.txt, linked_subproblems_map.csv) and
the scenario-level logs directory (small, and the only non-regenerable
content).
Cleanup writes a marker file (scenario_directory_cleaned.csv, one row
per cleaned draw) to the scenario directory. Each entry point has a
deliberate, different relationship with the marker:
import_scenario_results refuses to run on a marked directory, with NO override: it deletes all of the scenario’s database results before importing, so importing from a cleaned directory would wipe the results and find nothing to re-import. (
gridpath_run_e2e --per_draw_lifecycle --single_drawis the sanctioned way to re-import one draw: it deletes only that draw’s rows.)run_scenario refuses unless passed
--ignore_cleanup_marker: the scenario structure is inferred from the directory tree, so a partially cleaned tree yields a silently wrong structure – but deliberately solving just what is on disk (a re-materialized subset) is a legitimate, explicit choice.get_scenario_inputs is never blocked – it is the recovery path: a full regeneration of the database-derived structure removes the marker, while a partial regeneration (
--single_drawor a temporal-structure CSV) leaves it in place, since the rest of the tree is still cleaned.
9.2.4. Input Validation
gridpath.validate_inputs
This script iterates over all modules required for a GridPath scenario and calls their validate_inputs() method, which performs various validations of the input data and scenario setup.