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: DataFrame = <factory>, storage_df: DataFrame = <factory>, thermal_generation_df: DataFrame = <factory>, installed_plants_df: DataFrame = <factory>, summary_df: 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: DataFrame = <factory>, attribute_units: dict[str, str]=<factory>, marginal_prices_df: DataFrame = <factory>, line_congestion_duals_df: DataFrame = <factory>, system_generation_df: 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
- attribute_units
Display units for public result-attribute dictionaries. This metadata is optional for directly constructed and legacy deserialized results.
- generation_df: DataFrame
- storage_df: DataFrame
- thermal_generation_df: DataFrame
- installed_plants_df: DataFrame
- summary_df: DataFrame
- interregional_exchanges_df: DataFrame
- marginal_prices_df: DataFrame
- line_congestion_duals_df: DataFrame
- system_generation_df: DataFrame
- get_attribute_unit(name: str) str | None[source]
Return the display unit associated with a public result attribute.
- 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_system_generation_dataframe() DataFrame[source]
Return hourly generation dispatch aggregated to the system level.
- Returns:
One row per hour for zonal results, or the copperplate generation DataFrame unchanged. Zonal net load is derived from aggregate fixed-generation quantities rather than summed across areas.
- 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_marginal_prices_dataframe() DataFrame[source]
Return hourly marginal prices from the fixed-decision pricing LP.
- Returns:
Price rows keyed by hour and area. Numeric price fields are NaN whenever
pricing_statusreports unavailable duals or pricing.- Return type:
pd.DataFrame
- get_line_congestion_duals_dataframe() DataFrame[source]
Return directional zonal line-capacity duals for price auditing.
- Returns:
Rows keyed by line and hour with upper/lower capacity duals and their sum. Empty for copper-plate runs.
- Return type:
pd.DataFrame
- get_problem_info() dict[source]
Return solver problem-size information.
- Returns:
Dictionary with counts for constraints, variables, binary variables, objectives, and nonzeros.
- Return type:
- __init__(termination_condition: str = '', solver_status: str = '', total_cost: float = 0.0, gen_mix_target: float = 0.0, generation_df: DataFrame = <factory>, storage_df: DataFrame = <factory>, thermal_generation_df: DataFrame = <factory>, installed_plants_df: DataFrame = <factory>, summary_df: 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: DataFrame = <factory>, attribute_units: dict[str, str]=<factory>, marginal_prices_df: DataFrame = <factory>, line_congestion_duals_df: DataFrame = <factory>, system_generation_df: 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
Under the zonal path,
summary_dfcontains system-level aggregates andOptimizationResults.area_summary_dfcontains per-area summaries.interregional_exchanges_dfis available for zonal CSV export.
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()
# Get fixed-decision LP marginal prices and zonal line-dual audit values
marginal_prices_df = results.get_marginal_prices_dataframe()
line_congestion_duals_df = results.get_line_congestion_duals_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 |
|
pd.DataFrame |
Fixed-decision LP LMP rows with price components and pricing method/status metadata. Use |
|
pd.DataFrame |
Zonal directional line-capacity dual audit rows. Empty for copperplate runs; use |
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. |
Marginal prices are calculated after an optimal planning solve by fixing the incumbent investment, capacity, and binary decisions on a cloned model and solving that clone as an LP with the same configured solver. In zonal runs, the generation component is the LMP of the lexicographically first area in each connected component; the congestion component is the local LMP less that reference price. Raw imported supply-balance duals and directional line-capacity duals remain available for audit.
Collector dispatch behavior:
collect_results_from_model(...)uses the zonal collector when bothmodel.Aandmodel.areaexist.In zonal mode,
summary_dfcontains aggregate system-level metrics andarea_summary_dfcontains per-area summary DataFrames.