Running SDOM and Understanding Outputs#

This guide covers how to run SDOM optimizations and the outputs/results it provides.

Running an Optimization#

Basic Workflow#

from sdom import (
    configure_logging,
    load_data, 
    initialize_model, 
    run_solver,
    get_default_solver_config_dict,
    export_results
)
import logging

# 1. Configure logging (optional but recommended)
configure_logging(level=logging.INFO)

# 2. Load input data
data = load_data('./Data/my_scenario/')

# 3. Initialize the optimization model
model = initialize_model(
    data=data,
    n_hours=8760,  # Full year
    with_resilience_constraints=False,
    model_name="SDOM_MyScenario"
)

# 4. Configure solver
solver_config = get_default_solver_config_dict(
    solver_name="cbc",  # or "highs"
    executable_path="./Solver/bin/cbc.exe"
)

# 5. Run optimization - returns an OptimizationResults object
results = run_solver(model, solver_config)

# 6. Check results and export
if results.is_optimal:
    export_results(results, case="scenario_1", output_dir="./results_pyomo/")
    
    # 7. Access results directly from the OptimizationResults object
    print(f"Optimization Status: {results.termination_condition}")
    print(f"Total System Cost: ${results.total_cost:,.2f}")
    print(f"Total Wind Capacity: {results.total_cap_wind:.2f} MW")
    print(f"Total Solar Capacity: {results.total_cap_pv:.2f} MW")
    
    # Access detailed DataFrames
    generation_df = results.generation_df
    storage_df = results.storage_df
    summary_df = results.summary_df
else:
    print(f"Optimization failed: {results.termination_condition}")

Tip

The OptimizationResults object provides convenient properties like is_optimal, total_cost, total_cap_wind, total_cap_pv, and dictionaries for storage capacities. See the Results API Reference for full documentation.

Shorter Time Horizons#

For testing or sensitivity analysis, you can run shorter simulations:

# 24-hour test run
model = initialize_model(data, n_hours=24)

# One week (168 hours)
model = initialize_model(data, n_hours=168)

# One month (~730 hours)
model = initialize_model(data, n_hours=730)

Warning

Budget formulations (monthly/daily hydro) require specific hour multiples. SDOM will automatically adjust and log a warning.

Solver Configuration#

Currently SDOM python package has been tested with the following open-source solvers:

CBC Solver (Open-Source)#

This solver does not have a python package to make the interface, so you need to download the executable and indicate the path of such file:

solver_config = get_default_solver_config_dict(
    solver_name="cbc",
    executable_path="./Solver/bin/cbc.exe"  # Windows
    # executable_path="./Solver/bin/cbc"     # Unix/MacOS
)

# Customize solver options
solver_config["options"]["mip_rel_gap"] = 0.01  # 1% MIP gap
solver_config["solve_keywords"]["timelimit"] = 3600  # 1 hour limit

HiGHS Solver (Open-Source)#

solver_config = get_default_solver_config_dict(
    solver_name="highs",
    executable_path=""  # Does not require the path if you import the python package highspy
)

Outputs/Results#

In the path specified by “output_dir”, sdom will writhe the following output csv files:

File name

Description

OutputGeneration_CASENAME.csv

Hourly generation results aggregated by technology, curtailment, imports/exports and Load.

OutputStorage_CASENAME.csv

Hourly storage operation results (charging/discharging and SOC).

OutputSummary_CASENAME.csv

Summary of key simulation results and statistics.

OutputThermalGeneration_CASENAME.csv

Hourly results for thermal generation plants.

OutputInstalledPowerPlants_CASENAME.csv

Installed capacity for each individual power plant (Solar PV, Wind, Thermal).

Troubleshooting#

Solver Performance#

For large problems:

  • Increase MIP gap: solver_config["options"]["mip_rel_gap"] = 0.01

  • Set time limit: solver_config["solve_keywords"]["timelimit"] = 7200

Infeasible Solutions#

… in progress…


Visualising Results#

After running a single optimisation, use plot_results() from the analytic_tools sub-package to generate a standard set of publication-ready figures in one call.

Generated figures#

File

Description

capacity_donut.png

Installed capacity by technology (donut chart)

capacity_generation_donuts.png

Side-by-side capacity and total generation donuts

heatmap_<column>.png

One 365×24 hourly dispatch heatmap per generation technology

Basic usage#

from sdom import load_data, initialize_model, run_solver, get_default_solver_config_dict
from sdom.analytic_tools import plot_results

data = load_data("./Data/no_exchange_run_of_river/")
model = initialize_model(data, n_hours=8760)
solver_config = get_default_solver_config_dict(solver_name="highs", executable_path="")
results = run_solver(model, solver_config)

if results.is_optimal:
    # Save all plots to ./results_pyomo/my_scenario/plots/
    plot_results(results, output_dir="./results_pyomo/my_scenario/")

Plots are saved to <output_dir>/plots/. To override the plots directory explicitly, use the plots_dir parameter instead:

plot_results(results, plots_dir="./my_output_dir/figures/")

Note

plot_results() silently skips the run and logs a warning if the result is not optimal — it never raises on infeasible solutions.

Controlling the output directory#

Parameter

Behaviour

output_dir="./results/"

Plots saved to ./results/plots/

plots_dir="./figures/"

Plots saved directly to ./figures/

Both parameters are optional but at least one must be provided, otherwise a ValueError is raised.

Full workflow example#

from sdom import (
    load_data, initialize_model, run_solver,
    export_results, get_default_solver_config_dict,
)
from sdom.analytic_tools import plot_results

OUTPUT_DIR = "./results_pyomo/base_scenario/"

data        = load_data("./Data/no_exchange_run_of_river/")
model       = initialize_model(data, n_hours=8760)
solver_cfg  = get_default_solver_config_dict(solver_name="highs", executable_path="")
results     = run_solver(model, solver_cfg)

if results.is_optimal:
    # Export CSV tables
    export_results(results, case="base_scenario", output_dir=OUTPUT_DIR)

    # Generate plots alongside the CSV outputs
    plot_results(results, output_dir=OUTPUT_DIR)

    print(f"Total cost : ${results.total_cost:,.0f}")
    print(f"Solar PV   : {results.total_cap_pv:.1f} MW")
    print(f"Wind       : {results.total_cap_wind:.1f} MW")

Running Parametric & Sensitivity Studies#

To run multi-dimensional parameter sweeps in parallel (e.g., sweeping GenMix_Target, storage CAPEX, or load growth factors), use the built-in ParametricStudy API.

See the dedicated guide: Parametric & Sensitivity Analysis

Next Steps#