laurel.scenario_framework package
Submodules
laurel.scenario_framework.build module
Abstract base classes for Kedro scenario configuration generation.
Provides ScenarioBuilder, the abstract interface that concrete scenario
builders must implement to generate a Kedro configuration file tree for a
batch of scenario runs (States of the World). Each concrete builder maps a
parameter grid to a directory layout that is compatible with SLURM job arrays
and Kedro’s PartitionedDataset format.
The generated layout follows the pattern:
<scenario_name>/<intermediate_dirs>/task_<N>/parameters.yml
<scenario_name>/<intermediate_dirs>/task_<N>/catalog.yml
where N is the zero-based SLURM array task index. The per-task
catalog.yml overrides selected PartitionedDataset entries from
conf/base/catalog.yml with direct single-file or single-directory entries
that point at the task-specific path. This lets Kedro determine whether an
individual task has already run (via its standard “skip if output exists”
logic) without touching the base catalog.
Which entries are overridden is declared in conf/base/catalog.yml itself,
using the metadata.scenario_override field on each relevant entry. The
base catalog therefore remains the single source of truth: adding a new
scenario-specific dataset only requires annotating it there.
Key design decisions
``task_N`` directory layout: giving each task its own subdirectory avoids filesystem conflicts when SLURM array jobs run concurrently, and makes it trivial for
ScenarioReaderto filter completed vs. pending tasks by path prefix.Per-task catalog overrides: generated alongside each
parameters.ymlso Kedro can check individual task completion. Entries withflatten: truecollapse aPartitionedDatasetinto the inner dataset type (e.g.pandas.FeatherDataset) pointing at the task file; entries withoutflattenredirectpathto the task subdirectory (used for debug partitions whose nodes return dicts).``n_tasks_generated`` side-effect:
ScenarioBuilder.build_configs()sets this attribute so thatScenarioBashWritercan generate the correct--array=0-NSLURM range without a separate counting step.
- class laurel.scenario_framework.build.ScenarioBuilder(scen_params, all_params, catalog)[source]
Bases:
ABCAbstract base class for scenario configuration builders.
Concrete subclasses define how a parameter grid maps to Kedro configuration partitions. The framework uses the
SLURM_ARRAY_TASK_IDenvironment variable to select the correct partition at runtime, or a localforloop for serial execution.- Class attributes:
- _registry: Maps each concrete subclass name to the class itself.
Populated automatically by
__init_subclass__()when a subclass is defined (i.e. when its module is imported).- n_tasks_generated: Set to the total number of tasks by
build_configs(); initiallyNone.
- __init__(scen_params, all_params, catalog)[source]
Initialise a scenario builder.
- Parameters:
scen_params (
dict) – Scenario-specific parameters dict. Must contain the key"display_name"(a human-readable scenario identifier used as the root directory name). May contain additional keys consumed by_build_param_dicts()in concrete subclasses (e.g."builder","n_scenarios","seed").all_params (
dict) – The full Kedroparametersdict for the run, forwarded to concrete builders that need pipeline-level parameters such as base paths or default energy rates.catalog (
dict) – The rawconf/base/catalog.ymldict, loaded as plain YAML (no OmegaConf interpolation)._build_single_partition()emits acatalog.ymlalongside eachparameters.ymlby calling_build_single_catalog().
- Raises:
RuntimeError – If
"display_name"is absent fromscen_params.
- build_configs()[source]
Generate the full set of Kedro configuration partition dicts.
Calls
_build_param_dicts(), then wraps each(path, params)pair with_build_single_partition(). Setsn_tasks_generatedas a side-effect so the bash writer can generate the correct SLURM array range.- Return type:
dict[Path,dict]- Returns:
Dict mapping each task’s config file path to its content dict. Suitable for saving as a Kedro
PartitionedDataset.
-
n_tasks_generated:
int|None= None
- abstract property partition_level_names: tuple[str]
Ordered tuple of path-component names that describe each partition level.
These names correspond to the path components between the scenario root and the
task_Nleaf directory (e.g.("run_name", "task_id")).ScenarioReaderuses them to extract structured metadata from partition paths.
- class laurel.scenario_framework.build.TestScenarioBuilder(scen_params, all_params, catalog)[source]
Bases:
ScenarioBuilderMinimal concrete builder that produces a single-task scenario partition.
Used in tests and as a reference implementation for
ScenarioBuilder. Generates one partition whose output directory is<display_name>/task_0/with no extra parameters.- partition_level_names = ('run_name', 'task_id')
laurel.scenario_framework.cmd module
Bash and SLURM script generation for batch scenario execution.
Provides ScenarioBashWriter for generating shell scripts that launch
Kedro scenario arrays either locally or via SLURM, and a thin pipeline-facing
wrapper generate_bash_script() used from the build_scenarios pipeline.
Key design decisions
Three command modes:
sbatchgenerates a self-contained SLURM batch script with#SBATCHdirectives and dynamic config-directory discovery;sallocgenerates an interactive allocation command (the--arrayoption is stripped sincesallocdoes not support job arrays);localwraps the Kedro call in a shellforloop that iterates overSLURM_ARRAY_TASK_IDvalues, mimicking SLURM array behaviour for local testing.Runtime config discovery: rather than hardcoding task paths, the generated script uses
findto locate thetask_$SLURM_ARRAY_TASK_IDdirectory underconf/scenarios/<name>/at runtime. This keeps the script independent of the absolute project path on the HPC cluster.Multiple sequential Kedro calls:
cmd_line_calls.kedroin the parameters YAML accepts either a single dict or a list of dicts. Each dict produces onekedro runline; multiple calls are emitted as consecutive lines so each runs independently. The config-directory discovery block is emitted once per task regardless of how manykedro runcalls follow.Option building via string template:
ScenarioBashWriter.build_opts()uses a simpleKEY/VALUEplaceholder template rather than a CLI-parsing library, keeping the output predictable and easy to inspect.
- class laurel.scenario_framework.cmd.ScenarioBashWriter(name, command)[source]
Bases:
objectGenerates shell scripts that launch Kedro scenario arrays.
Supports three execution modes (
sbatch,salloc,local) controlled at construction time. Usecompile()to obtain the complete script string, then write it to disk or pass it to thebuild_scenariospipeline for storage.- Parameters:
name (
str) – Human-readable scenario name (matchesdisplay_name). Used as the SLURM job name and the config search path.command (
str) – Execution mode — one of"sbatch","salloc", or"local".
- build_kedro_run(kedro_calls, prefix='', n_tasks=1)[source]
Build the shell commands that discover the config dir and invoke Kedro.
For
sbatchmode, produces afindcommand that setsconf_dirfromSLURM_ARRAY_TASK_ID, followed by onekedro runline per entry inkedro_calls. Forlocalmode, wraps the entire block in aforloop overseq 0 <n_tasks-1>that simulates a SLURM array, with each line individually indented.The config-directory discovery block is emitted exactly once per task regardless of how many
kedro runcalls are requested.- Parameters:
kedro_calls (
list[dict] |dict) – A list of dicts, each mapping Kedro CLI option names to values (e.g.[{"pipeline": "electrify_trips"}, {"pipeline": "evaluate_impacts"}]). A bare dict is accepted for backward compatibility and is treated as a single-element list. The"env"key is injected automatically into each call and should not be included. At least one entry is required.prefix (
str) – Shell command prefix inserted before eachkedro runinvocation (e.g."uv run"or"srun"). Defaults to"".n_tasks (
int) – Total number of tasks in the array; used only forlocalmode to set theforloop range. Defaults to1.
- Return type:
str- Returns:
Multi-line shell string containing the config-discovery block and one or more
kedro runinvocations.- Raises:
ValueError – If
kedro_callsis empty.
- static build_opts(d, template)[source]
Render a dict of key-value pairs into a shell option string.
Substitutes each
(key, value)pair intotemplateby replacing the literal stringsKEYandVALUE. Concatenates all rendered lines into a single string.- Parameters:
d (
dict[slice(<class ‘str’>, str | int | float, None)]) – Ordered dict of option names to values (e.g.{"ntasks": 4, "mem": "64G"}).template (
str) – Format string containing the placeholdersKEYandVALUE(e.g." --KEY=VALUE"or"#SBATCH --KEY=VALUE\n").
- Return type:
str- Returns:
Concatenated option string (e.g.
" --ntasks=4 --mem=64G").
- build_slurm_request(resources, reporting=None)[source]
Build the SLURM resource-request block for
sbatchorsallocmode.For
sbatch, produces#SBATCH --key=valueheader lines (one per resource option) and injectsjob-nameautomatically. Forsalloc, produces a singlesalloc --key=value ...command string and strips thearrayoption, whichsallocdoes not support.- Parameters:
resources (
dict) – Dict of SLURM resource options (e.g.{"ntasks": 4, "mem": "64G", "array": "0-511"}). Modified in-place to addjob-nameforsbatchmode.reporting (
dict) – Optional dict of additional SLURM options (e.g. email and output-file settings) merged intoresourcesforsbatchmode only.
- Return type:
str- Returns:
Multi-line string of
#SBATCHdirectives (sbatchmode) or a singlesalloc ...command line (sallocmode).- Raises:
RuntimeError – If
resourcesisNone.
- compile(params, resources=None, reporting=None, n_tasks=None)[source]
Compile all script sections into a complete shell script string.
Assembles the shebang line, optional SLURM directives, and the Kedro run block into a single multi-line string suitable for writing to a
.shfile.- Parameters:
params (
dict) –Dict with two required keys:
"prefix": Shell command prefix forwarded tobuild_kedro_run()(e.g."srun"or"")."kedro": Dict of Kedro CLI options forwarded tobuild_kedro_run().
resources (
dict) – SLURM resource dict forwarded tobuild_slurm_request(). Required whencommandis"sbatch"or"salloc"; ignored for"local".reporting (
dict) – Optional SLURM reporting options (email, output paths) forwarded tobuild_slurm_request().n_tasks (
int) – Total number of array tasks; forwarded tobuild_kedro_run()forlocalmode loop sizing.
- Return type:
str- Returns:
Complete shell script string starting with
#!/bin/bash.
- laurel.scenario_framework.cmd.generate_bash_script(command, builder, cmd_params=None, resources=None, reporting=None)[source]
Generate a Bash script for running a full scenario array.
Constructs a
ScenarioBashWriterfrom the builder’s display name and compiles the complete script. Intended to be called as a Kedro node in thebuild_scenariospipeline afterScenarioBuilder.build_configs()has been run (so thatn_tasks_generatedis set).- Parameters:
command (
str) – Execution mode passed toScenarioBashWriter— one of"sbatch","salloc", or"local".builder (
ScenarioBuilder) – ConfiguredScenarioBuilderinstance whosedisplay_nameandn_tasks_generatedare used.cmd_params (
dict) – Dict with keys"prefix"and"kedro"forwarded toScenarioBashWriter.compile().resources (
dict) – SLURM resource dict forwarded toScenarioBashWriter.compile().reporting (
dict) – Optional SLURM reporting dict forwarded toScenarioBashWriter.compile().
- Return type:
dict[slice(<class ‘pathlib.Path’>, <class ‘str’>, None)]- Returns:
Single-entry dict
{builder.display_name: script_string}suitable for saving as a KedroPartitionedDataset.
laurel.scenario_framework.io module
Kedro pipeline node functions for reading and writing scenario-partitioned datasets.
Deprecated since version :func:`write_scenario_partition`: and read_scenario_partition() are
no longer used by the electrify_trips or evaluate_impacts pipelines.
Per-task I/O is now handled by per-task catalog.yml overrides generated
by _build_single_catalog().
These functions are retained for backwards compatibility with
ScenarioReader analysis scripts and
any external callers.
Provides two thin wrapper nodes that bridge the generic Kedro
PartitionedDataset API and the scenario directory layout defined by
ScenarioBuilder:
write_scenario_partition()— packages an arbitrary object for Kedro to save under the current task’s output directory.read_scenario_partition()— loads a single partition from that directory, enabling within-pipeline re-reads of just-written data.
- laurel.scenario_framework.io.read_scenario_partition(partitions, params, client=None)[source]
Load a single partition from the current scenario’s output directory.
Deprecated since version Use: per-task
catalog.ymloverrides generated by_build_single_catalog()instead of this wrapper node.Filters the full
PartitionedDatasetdict to the one entry whose path matchesparams["dir"], calls its loader function, and returns the result. Raises if zero or more than one matching partition is found.For reading multiple partitions across scenarios, use
read_partitions()instead.- Parameters:
partitions (
dict) – Full KedroPartitionedDatasetdict mapping partition path strings to zero-argument loader callables.params (
dict) – The"results_partition"sub-dict from the task’s Kedro parameter config. Must contain"dir"(str) — the path of the partition to load.client (
Client) – Unused DaskClientargument retained for pipeline compatibility (ensures Dask is started before this node runs).
- Return type:
object- Returns:
The loaded dataset object returned by the partition’s loader callable.
- Raises:
RuntimeError – If more than one partition matches
params["dir"].
- laurel.scenario_framework.io.write_scenario_partition(obj, params)[source]
Package an object for Kedro to save to the current scenario’s output directory.
Deprecated since version Use: per-task
catalog.ymloverrides generated by_build_single_catalog()instead of this wrapper node.Wraps
objin a single-entry partition dict keyed by the task directory path. Kedro’sPartitionedDatasetmachinery uses this dict to determine where and how to serialise the object (format is controlled bycatalog.yml).- Parameters:
obj (
object) – The dataset to save (e.g. apd.DataFrame,dict, or any object supported by the catalog entry).params (
dict) –The
"results_partition"sub-dict from the task’s Kedro parameter config. Must contain:"dir"(str): the task output directory path."level_names"(list[str]): partition level name metadata (not used by this function but present in the dict).
- Return type:
dict[str,object]- Returns:
Single-entry dict
{params["dir"]: obj}ready for Kedro to persist.
laurel.scenario_framework.nodes module
Kedro node for dynamic scenario configuration dispatch.
Provides a single node function generate_scenario_configs() that looks
up the requested ScenarioBuilder subclass in the class-level registry,
instantiates it, and delegates to its
build_configs() method.
Usage
Import this function into any Kedro pipeline’s nodes.py alongside the
builder imports that should be available for that pipeline:
# In your pipeline's nodes.py:
import laurel.scenario_builders # noqa: F401 — registers all bundled builders
from laurel.scenario_framework.nodes import generate_scenario_configs # noqa: F401
Any ScenarioBuilder subclass imported (anywhere) before
generate_scenario_configs() is called becomes discoverable — importing
the module is the registration step.
Key design decisions
``__init_subclass__`` registry: builders self-register when their module is imported via
__init_subclass__(). This replaces the previousglobals()/inspectapproach, which required the function and all builder imports to live in the same module.Import-as-registration: callers signal intent by importing builder modules. A plain
import laurel.scenario_buildersis enough to register all bundled builders; custom builders only need to be imported once anywhere before the node runs.
- laurel.scenario_framework.nodes.generate_scenario_configs(scen_params, all_params, catalog)[source]
Dispatch to the named ScenarioBuilder and return configuration partitions.
Looks up
scen_params["builder"]in_registry, instantiates the matching class, callsbuild_configs(), and returns the resulting partitions dict together with the builder instance.The registry is populated automatically when a
ScenarioBuildersubclass module is imported. Callers are responsible for ensuring the desired builders are imported before this function is called (typically via a module-levelimport laurel.scenario_buildersin the pipeline’snodes.py).- Parameters:
scen_params (
dict) –Scenario-specific parameter dict. Must contain:
"builder"(str): class name of the concreteScenarioBuildersubclass to use."display_name"(str): human-readable name forwarded to the builder constructor.Any additional keys consumed by the chosen builder’s
_build_param_dictsimplementation.
all_params (
dict) – The complete Kedroparametersdict for the run, forwarded unchanged to the builder constructor.catalog (
dict) – The rawconf/base/catalog.ymldict, loaded as plain YAML by thebase_catalogcatalog entry (no OmegaConf interpolation). Each task’s partition will include acatalog.ymlthat overrides scenario-specific entries with task-scoped paths.
- Return type:
tuple- Returns:
Two-tuple
(partitions, builder)wherepartitionsis the dict of{config_file_path: content_dict}entries returned bybuild_configs(), andbuilderis the instantiated builder (whosen_tasks_generatedis set and available for downstream nodes such asgenerate_bash_script()).- Raises:
NotImplementedError – If no builder matching
scen_params["builder"]is found in_registry.
laurel.scenario_framework.read module
Abstract base classes for reading scenario-partitioned datasets into analysis DataFrames.
Provides ScenarioReader, the abstract interface that concrete readers
must implement to collate Kedro PartitionedDataset partitions across
scenario runs (States of the World) into a single combined DataFrame.
Typical post-hoc analysis workflow
Instantiate a concrete reader with a list of scenario directories (or
Noneto read all available partitions).Call
ScenarioReader.read_partitions()with the Kedro partition dict to obtain a combined DataFrame labelled by scenario metadata.Use
ScenarioReader.list_completed_partitions()to audit which tasks have finished before reading.
Key design decisions
Lazy/eager dual path:
ScenarioReader.read_partitions()supportslazy=True(Daskfrom_map) for large post-hoc analyses that would exhaust memory if loaded eagerly, andlazy=False(pandasconcat) for small result sets or interactive exploration.Multi-level column handling:
ScenarioReader._collate_partition_df()pads metadata column names with empty strings when the DataFrame has a multi-level column index, so that metadata columns appear at the correct level without restructuring the DataFrame.``metadata_level_names ⊆ partition_level_names`` validation: the constructor checks this invariant at instantiation time, surfacing misconfiguration before any I/O is attempted.
Path-based metadata extraction: scenario metadata (e.g. adoption-rate bin, energy-rate value) is encoded in the partition path by
ScenarioBuilder; concrete readers parse it viaextract_metadata()rather than storing it in the data files, keeping the files themselves schema-free.
- class laurel.scenario_framework.read.ScenarioReader(dirs=None)[source]
Bases:
ABCAbstract base class for collating scenario-partitioned Kedro datasets.
Concrete subclasses must implement three abstract members:
builder— theScenarioBuilderclass whosepartition_level_namesdefines the path structure.metadata_level_names— the subset ofpartition_level_namesto extract as metadata columns in the combined DataFrame.extract_metadata()— parse a partition path to a metadata tuple.name_scenario()— convert a partition path to a human-readable label.
- Class attributes:
- builder: The
ScenarioBuilderclass (not instance) associated with this reader.
- metadata_level_names: Tuple of path-level names to extract as DataFrame
metadata columns; must be a subset of
builder.partition_level_names.- scenario_name: Column name for the human-readable scenario label added
by
_collate_partition_df(). Defaults to"scenario".
- builder: The
- __init__(dirs=None)[source]
Initialise the reader and validate metadata/partition name consistency.
- Parameters:
dirs (
list[Path] |list[str] |None) – One or more directories to restrict reading to. If a single string orPathis given, it is wrapped in a list. PassNoneto read all available partitions.- Raises:
RuntimeError – If any name in
metadata_level_namesis not present inbuilder.partition_level_names.
- abstract property builder: ScenarioBuilder
The ScenarioBuilder class whose partition layout this reader expects.
- concat_name_components(*args, sep=', ')[source]
Join path-component strings into a formatted scenario name.
Replaces underscores with spaces in each component, then joins with
sep.- Parameters:
*args (
str) – Path component strings (e.g."high_adoption","task_42").sep (
str) – Separator inserted between components. Defaults to", ".
- Return type:
str- Returns:
Formatted scenario name string (e.g.
"high adoption, task 42").
- abstract extract_metadata(path)[source]
Parse scenario-identifying metadata from a partition path.
Return values must correspond in order with
metadata_level_names.- Parameters:
path (
Path) –Pathobject for a completed partition file.- Return type:
tuple- Returns:
Tuple of metadata values (strings or scalars) in the same order as
metadata_level_names.
- get_metadata_values(path)[source]
Extract the metadata-level path components from a partition path.
Uses the positional indices of
metadata_level_nameswithinbuilder.partition_level_namesto select the correspondingpath.partscomponents.- Parameters:
path (
Path) –Pathobject for a completed partition file.- Return type:
dict[str,str]- Returns:
Dict mapping each metadata level name to its value from
path.parts.
- list_completed_partitions(data_partitions, config_partitions=None, incomplete=False, report_type='scenario')[source]
List completed (or incomplete) scenario partitions within
self.dirs.Compares the set of data partitions that already exist on disk against the full set of config partitions to determine which tasks have finished. When
incomplete=True, returns the complement — the tasks that are still pending.- Parameters:
data_partitions (
dict[str,object]) – KedroPartitionedDatasetdict for the output dataset (e.g.dwells_with_charging_partition). Only partitions withinself.dirsare considered.config_partitions (
dict[str,object] |None) – KedroPartitionedDatasetdict for the generated configs (used only whenincomplete=Trueto determine the full target set). May beNonewhenincomplete=False.incomplete (
bool) – IfTrue, return tasks that are inconfig_partitionsbut not yet indata_partitions. Defaults toFalse(return completed tasks).report_type (
str) – Format of the return values."scenario"returns human-readable scenario names (vianame_scenario());"task"returns integer task IDs parsed from thetask_Npath component.
- Return type:
list[str] |list[int]- Returns:
Sorted list of scenario name strings (
report_type="scenario") or integer task IDs (report_type="task").- Raises:
RuntimeError – If no partitions are found within
self.dirs.NotImplementedError – If
report_typeis not"scenario"or"task".
- abstract property metadata_level_names: tuple[str]
Ordered subset of
builder.partition_level_namesto use as metadata columns.Names must exactly match entries in
partition_level_namesof the associated builder.
- abstract name_scenario(path)[source]
Convert a partition path to a human-readable scenario label.
The helper
concat_name_components()is typically useful for formatting the label from path components.- Parameters:
path (
Path) –Pathobject for a completed partition file.- Return type:
str- Returns:
Human-readable scenario name string added to the combined DataFrame under the
scenario_namecolumn.
- read_partitions(partitions, lazy=False)[source]
Load and collate scenario partitions into a single combined dataset.
Filters the partition dict to the directories in
self.dirs, then for each matching partition calls the Kedro loader function, attaches metadata and scenario-name columns, and concatenates all results.For
pd.DataFramepartitions, supports both eager (pandas.concat) and lazy (dask.dataframe.from_map) execution. Fordictpartitions, only eager loading is supported.- Parameters:
partitions (
dict[str,object]) – Full KedroPartitionedDatasetdict mapping path strings to zero-argument loader callables.lazy (
bool) – IfTrue, return a Dask DataFrame constructed viafrom_mapso that loading is deferred until.compute()is called. Only valid forpd.DataFramepartitions. Defaults toFalse.
- Returns:
a
pd.DataFrame(lazy=False) ordd.DataFrame(lazy=True) with additional columns for eachmetadata_level_namesentry and forscenario_name. Fordictpartitions: a dict keyed by scenario name.- Return type:
Combined dataset. For
pd.DataFramepartitions- Raises:
RuntimeError – If no partitions are found within
self.dirs.NotImplementedError – If
lazy=Trueis requested for dict partitions, or if the partition data type is not supported.
-
scenario_name:
str= 'scenario'
- select_partitions(partitions)[source]
Filter a Kedro partition dict to entries under
self.dirs.Delegates to
select_partitions_static()withself.dirs.- Parameters:
partitions (
dict[Path,object]) – Full KedroPartitionedDatasetdict mapping path strings to loader callables.- Return type:
dict[Path,object]- Returns:
Filtered dict containing only the partitions whose paths are relative to one of the directories in
self.dirs.- Raises:
RuntimeError – If no matching partitions are found.
- static select_partitions_static(partitions, dirs=None)[source]
Filter a partition dict to entries whose paths are under any of
dirs.If
dirsisNone, all partitions are returned unchanged. UsesPath.is_relative_toto test containment so both absolute and relative paths are handled consistently.- Parameters:
partitions (
dict[Path|str,object]) – KedroPartitionedDatasetdict (path → loader).dirs (
list[Path] |None) – List of directories to restrict to, orNoneto return all partitions.
- Return type:
dict[Path,object]- Returns:
Filtered dict of matching
{Path: loader}entries.- Raises:
RuntimeError – If no partitions match any of the given directories.
- class laurel.scenario_framework.read.TestScenarioReader(dirs=None)[source]
Bases:
ScenarioReaderMinimal concrete reader for the single-partition test scenario.
Pairs with
TestScenarioBuilder. Extracts no metadata and labels every partition"Test".- builder
alias of
TestScenarioBuilder
- extract_metadata(path)[source]
Return an empty tuple (no metadata levels defined for test scenarios).
- Parameters:
path (
Path) – Partition path (unused).- Return type:
tuple- Returns:
Empty tuple
().
-
metadata_level_names:
tuple[str] = ()