Results Module
The results module provides structured data classes for storing and accessing optimization results.
OptimizationResults Class
The OptimizationResults dataclass is the primary container for all optimization results returned by run_solver().
- class sdom.results.OptimizationResults(termination_condition: str = '', solver_status: str = '', total_cost: float = 0.0, gen_mix_target: float = 0.0, generation_df: ~pandas.core.frame.DataFrame = <factory>, storage_df: ~pandas.core.frame.DataFrame = <factory>, thermal_generation_df: ~pandas.core.frame.DataFrame = <factory>, installed_plants_df: ~pandas.core.frame.DataFrame = <factory>, summary_df: ~pandas.core.frame.DataFrame = <factory>, problem_info: dict = <factory>, capacity: dict = <factory>, storage_capacity: dict = <factory>, generation_totals: dict = <factory>, cost_breakdown: dict = <factory>, is_zonal: bool = False, areas: list = <factory>, lines: list = <factory>, area_capacity: dict = <factory>, area_storage_capacity: dict = <factory>, area_generation_totals: dict = <factory>, area_cost_breakdown: dict = <factory>, area_generation_df: dict = <factory>, area_storage_df: dict = <factory>, area_thermal_generation_df: dict = <factory>, area_installed_plants_df: dict = <factory>, area_summary_df: dict = <factory>, interregional_exchanges_df: ~pandas.core.frame.DataFrame = <factory>)[source]
Data class containing all optimization results from SDOM.
This class stores the complete results from an SDOM optimization run, organized into DataFrames for different result categories (generation, storage, summary) and provides convenient accessors for specific metrics.
- generation_df
Hourly generation dispatch results for all technologies.
- Type:
pd.DataFrame
- storage_df
Hourly storage operation results (charge, discharge, SOC).
- Type:
pd.DataFrame
- thermal_generation_df
Disaggregated hourly thermal generation by plant.
- Type:
pd.DataFrame
- installed_plants_df
Installed capacity for each individual power plant (solar, wind, thermal).
- Type:
pd.DataFrame
- summary_df
Summary metrics including capacities, costs, and totals.
- Type:
pd.DataFrame
- generation_df: DataFrame
- storage_df: DataFrame
- thermal_generation_df: DataFrame
- installed_plants_df: DataFrame
- summary_df: DataFrame
- interregional_exchanges_df: DataFrame
- get_generation_dataframe() DataFrame[source]
Get the hourly generation dispatch DataFrame.
- Returns:
DataFrame with columns: Scenario, Hour, Solar PV Generation (MW), Solar PV Curtailment (MW), Wind Generation (MW), Wind Curtailment (MW), All Thermal Generation (MW), Hydro Generation (MW), Nuclear Generation (MW), Other Renewables Generation (MW), Imports (MW), Storage Charge/Discharge (MW), Exports (MW), Load (MW).
- Return type:
pd.DataFrame
- get_storage_dataframe() DataFrame[source]
Get the hourly storage operation DataFrame.
- Returns:
DataFrame with columns: Hour, Technology, Charging power (MW), Discharging power (MW), State of charge (MWh).
- Return type:
pd.DataFrame
- get_thermal_generation_dataframe() DataFrame[source]
Get the disaggregated hourly thermal generation DataFrame.
- Returns:
DataFrame with columns: Hour, and one column per thermal plant.
- Return type:
pd.DataFrame
- get_summary_dataframe() DataFrame[source]
Get the summary metrics DataFrame.
- Returns:
DataFrame with columns: Metric, Technology, Run, Optimal Value, Unit.
- Return type:
pd.DataFrame
- get_installed_plants_dataframe() DataFrame[source]
Get the installed power plants capacity DataFrame.
- Returns:
DataFrame with columns: Plant ID, Technology, Installed Capacity (MW), Max Capacity (MW), Capacity Fraction.
- Return type:
pd.DataFrame
- get_problem_info() dict[source]
Get solver problem information.
- Returns:
Dictionary with keys: Number of constraints, Number of variables, Number of binary variables, Number of objectives, Number of nonzeros.
- Return type:
- __init__(termination_condition: str = '', solver_status: str = '', total_cost: float = 0.0, gen_mix_target: float = 0.0, generation_df: ~pandas.core.frame.DataFrame = <factory>, storage_df: ~pandas.core.frame.DataFrame = <factory>, thermal_generation_df: ~pandas.core.frame.DataFrame = <factory>, installed_plants_df: ~pandas.core.frame.DataFrame = <factory>, summary_df: ~pandas.core.frame.DataFrame = <factory>, problem_info: dict = <factory>, capacity: dict = <factory>, storage_capacity: dict = <factory>, generation_totals: dict = <factory>, cost_breakdown: dict = <factory>, is_zonal: bool = False, areas: list = <factory>, lines: list = <factory>, area_capacity: dict = <factory>, area_storage_capacity: dict = <factory>, area_generation_totals: dict = <factory>, area_cost_breakdown: dict = <factory>, area_generation_df: dict = <factory>, area_storage_df: dict = <factory>, area_thermal_generation_df: dict = <factory>, area_installed_plants_df: dict = <factory>, area_summary_df: dict = <factory>, interregional_exchanges_df: ~pandas.core.frame.DataFrame = <factory>) None
Result Collection Function
- sdom.results.collect_results_from_model(model, solver_result, case_name: str = 'run') OptimizationResults[source]
Collect all optimization results from a solved Pyomo model.
Dispatches to the legacy single-area collector or the zonal collector based on whether
modelexposes a top-level area setmodel.Aand a per-areaBlockmodel.area(the convention established bysdom.optimization_main._initialize_model_zonal()).On the legacy path the returned
OptimizationResultsmatches today’s schema bit-identically (locked bytests/test_zonal_legacy_regression.py). On the zonal path the same top-level DataFrames (generation_df,storage_df,thermal_generation_df,installed_plants_df) are populated as the concatenation of per-area frames with a leadingAreacolumn, and a battery of new per-area dict fields plusOptimizationResults.interregional_exchanges_dfare filled in. See PRD §2.4 / §6.1.- Parameters:
model (pyomo.core.base.PyomoModel.ConcreteModel) – The solved Pyomo model instance.
solver_result (pyomo.opt.SolverResults) – The solver results object from
solver.solve().case_name (str, optional) – Case identifier for the scenario column. Defaults to
"run".
- Returns:
A dataclass containing all optimization results.
- Return type:
Notes
Top-level
summary_dfis left empty under the zonal path; per-area summaries are populated inOptimizationResults.area_summary_dfinstead. A system-level zonal summary is a follow-up. CSV emission ofinterregional_exchanges_dfis also a follow-up (commit #11).
Example Usage
Basic Result Access
from sdom import load_data, initialize_model, run_solver, get_default_solver_config_dict
# Load and solve model
data = load_data('./Data/scenario/')
model = initialize_model(data, n_hours=168)
solver_config = get_default_solver_config_dict(solver_name="highs")
# Run optimization - returns OptimizationResults
results = run_solver(model, solver_config)
# Check solution status
if results.is_optimal:
print(f"Optimization successful!")
print(f"Total Cost: ${results.total_cost:,.2f}")
else:
print(f"Solver terminated with: {results.termination_condition}")
Accessing Capacities
# Generation capacities
print(f"Solar PV Capacity: {results.total_cap_pv:.2f} MW")
print(f"Wind Capacity: {results.total_cap_wind:.2f} MW")
print(f"Thermal Capacity: {results.total_cap_thermal:.2f} MW")
# Storage capacities by technology
for tech, cap in results.total_cap_storage_charge.items():
print(f"{tech} Charge Capacity: {cap:.2f} MW")
for tech, energy in results.total_cap_storage_energy.items():
print(f"{tech} Energy Capacity: {energy:.2f} MWh")
Accessing DataFrames
# Get hourly generation dispatch
gen_df = results.get_generation_dataframe()
print(gen_df.head())
# Get storage operation details
storage_df = results.get_storage_dataframe()
# Get summary metrics
summary_df = results.get_summary_dataframe()
# Get disaggregated thermal generation (if multiple plants)
thermal_df = results.get_thermal_generation_dataframe()
Accessing Cost Breakdown
# CAPEX by technology
capex = results.cost_breakdown["capex"]
print(f"Solar CAPEX: ${capex['Solar PV']:,.2f}")
print(f"Wind CAPEX: ${capex['Wind']:,.2f}")
# Storage costs
power_capex = results.cost_breakdown["power_capex"]
energy_capex = results.cost_breakdown["energy_capex"]
# Operating costs
fom = results.cost_breakdown["fom"]
vom = results.cost_breakdown["vom"]
# Import/export costs
print(f"Import Cost: ${results.cost_breakdown['imports_cost']:,.2f}")
print(f"Export Revenue: ${results.cost_breakdown['exports_revenue']:,.2f}")
Accessing Problem Information
# Solver problem statistics
problem_info = results.get_problem_info()
print(f"Constraints: {problem_info['Number of constraints']}")
print(f"Variables: {problem_info['Number of variables']}")
print(f"Binary Variables: {problem_info['Number of binary variables']}")
OptimizationResults Attributes
Solver Information
Attribute |
Type |
Description |
|---|---|---|
|
str |
Solver termination status (e.g., ‘optimal’, ‘infeasible’) |
|
str |
Overall solver status (e.g., ‘ok’, ‘warning’) |
|
bool |
Property: True if solution is optimal |
Core Results
Attribute |
Type |
Description |
|---|---|---|
|
float |
Total objective value ($) |
|
float |
Generation mix target used |
Capacity Results
Attribute |
Type |
Description |
|---|---|---|
|
dict |
Generation capacities by technology (MW) |
|
dict |
Nested dict with ‘charge’, ‘discharge’, ‘energy’ by technology |
|
float |
Property: Solar PV capacity (MW) |
|
float |
Property: Wind capacity (MW) |
|
float |
Property: Thermal capacity (MW) |
|
dict |
Property: Storage charge capacity by tech (MW) |
|
dict |
Property: Storage discharge capacity by tech (MW) |
|
dict |
Property: Storage energy capacity by tech (MWh) |
Generation Results
Attribute |
Type |
Description |
|---|---|---|
|
dict |
Total generation by technology (MWh) |
|
float |
Property: Total PV generation (MWh) |
|
float |
Property: Total wind generation (MWh) |
|
float |
Property: Total thermal generation (MWh) |
Cost Breakdown
Attribute |
Type |
Description |
|---|---|---|
|
dict |
Nested dict with ‘capex’, ‘power_capex’, ‘energy_capex’, ‘fom’, ‘vom’, ‘fuel_cost’, ‘imports_cost’, ‘exports_revenue’ |
DataFrames
Attribute |
Type |
Description |
|---|---|---|
|
pd.DataFrame |
Hourly generation dispatch |
|
pd.DataFrame |
Hourly storage operation |
|
pd.DataFrame |
Disaggregated thermal generation |
|
pd.DataFrame |
Summary metrics |
Zonal Fields
Attribute |
Type |
Description |
|---|---|---|
|
bool |
True when collected from a zonal model ( |
|
list |
Area IDs in the solved zonal model. |
|
list |
Line metadata ( |
|
dict |
Per-area installed capacities. |
|
dict |
Per-area storage capacities ( |
|
dict |
Per-area generation totals. |
|
dict |
Per-area cost breakdown. |
|
dict[str, pd.DataFrame] |
Hourly generation by area. |
|
dict[str, pd.DataFrame] |
Hourly storage operation by area. |
|
dict[str, pd.DataFrame] |
Thermal generation by area. |
|
dict[str, pd.DataFrame] |
Installed plants by area. |
|
dict[str, pd.DataFrame] |
Per-area summary DataFrames. |
|
pd.DataFrame |
Per-line/per-hour flow, directional capacity, and utilization. |
Collector dispatch behavior:
collect_results_from_model(...)uses the zonal collector when bothmodel.Aandmodel.areaexist.In zonal mode,
summary_dfis intentionally left empty and replaced byarea_summary_df.