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.

termination_condition

The solver termination condition (e.g., ‘optimal’, ‘infeasible’).

Type:

str

solver_status

The solver status (e.g., ‘ok’, ‘warning’).

Type:

str

total_cost

The total objective value (cost) from the optimization.

Type:

float

gen_mix_target

The generation mix target value used in this run.

Type:

float

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

problem_info

Solver problem information (constraints, variables, etc.).

Type:

dict

capacity

Installed capacity by technology.

Type:

dict

storage_capacity

Storage capacity details (charge, discharge, energy).

Type:

dict

generation_totals

Total generation by technology.

Type:

dict

cost_breakdown

Detailed cost breakdown (CAPEX, OPEX, FOM, VOM).

Type:

dict

attribute_units

Display units for public result-attribute dictionaries. This metadata is optional for directly constructed and legacy deserialized results.

Type:

dict[str, str]

termination_condition: str = ''
solver_status: str = ''
total_cost: float = 0.0
gen_mix_target: float = 0.0
generation_df: DataFrame
storage_df: DataFrame
thermal_generation_df: DataFrame
installed_plants_df: DataFrame
summary_df: DataFrame
problem_info: dict
capacity: dict
storage_capacity: dict
generation_totals: dict
cost_breakdown: dict
is_zonal: bool = False
areas: list
lines: list
area_capacity: dict
area_storage_capacity: dict
area_generation_totals: dict
area_cost_breakdown: dict
area_generation_df: dict
area_storage_df: dict
area_thermal_generation_df: dict
area_installed_plants_df: dict
area_summary_df: dict
interregional_exchanges_df: DataFrame
attribute_units: dict[str, str]
marginal_prices_df: DataFrame
line_congestion_duals_df: DataFrame
system_generation_df: DataFrame
property is_optimal: bool

Check if the solution is optimal.

get_attribute_unit(name: str) → str | None[source]

Return the display unit associated with a public result attribute.

Parameters:

name (str) – Result attribute name, including a dot-qualified nested key such as "storage_capacity.energy".

Returns:

The configured display unit, or None when unit metadata is unavailable for the attribute.

Return type:

str or None

property total_cap_thermal: float

Total installed thermal capacity (MW).

property total_cap_pv: float

Total installed solar PV capacity (MW).

property total_cap_wind: float

Total installed wind capacity (MW).

property total_cap_storage_charge: dict

Storage charging power capacity by technology (MW).

property total_cap_storage_discharge: dict

Storage discharging power capacity by technology (MW).

property total_cap_storage_energy: dict

Storage energy capacity by technology (MWh).

property total_gen_pv: float

Total solar PV generation (MWh).

property total_gen_wind: float

Total wind generation (MWh).

property total_gen_thermal: float

Total thermal generation (MWh).

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_status reports 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:

dict

__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 model exposes a top-level area set model.A and a per-area Block model.area (the convention established by sdom.optimization_main._initialize_model_zonal()).

On the legacy path the returned OptimizationResults matches today’s schema bit-identically (locked by tests/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 leading Area column, and a battery of new per-area dict fields plus OptimizationResults.interregional_exchanges_df are 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:

OptimizationResults

Notes

Under the zonal path, summary_df contains system-level aggregates and OptimizationResults.area_summary_df contains per-area summaries. interregional_exchanges_df is 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

termination_condition

str

Solver termination status (e.g., ‘optimal’, ‘infeasible’)

solver_status

str

Overall solver status (e.g., ‘ok’, ‘warning’)

is_optimal

bool

Property: True if solution is optimal

Core Results

Attribute

Type

Description

total_cost

float

Total objective value ($)

gen_mix_target

float

Generation mix target used

Capacity Results

Attribute

Type

Description

capacity

dict

Generation capacities by technology (MW)

storage_capacity

dict

Nested dict with ‘charge’, ‘discharge’, ‘energy’ by technology

total_cap_pv

float

Property: Solar PV capacity (MW)

total_cap_wind

float

Property: Wind capacity (MW)

total_cap_thermal

float

Property: Thermal capacity (MW)

total_cap_storage_charge

dict

Property: Storage charge capacity by tech (MW)

total_cap_storage_discharge

dict

Property: Storage discharge capacity by tech (MW)

total_cap_storage_energy

dict

Property: Storage energy capacity by tech (MWh)

Generation Results

Attribute

Type

Description

generation_totals

dict

Total generation by technology (MWh)

total_gen_pv

float

Property: Total PV generation (MWh)

total_gen_wind

float

Property: Total wind generation (MWh)

total_gen_thermal

float

Property: Total thermal generation (MWh)

Cost Breakdown

Attribute

Type

Description

cost_breakdown

dict

Nested dict with ‘capex’, ‘power_capex’, ‘energy_capex’, ‘fom’, ‘vom’, ‘fuel_cost’, ‘imports_cost’, ‘exports_revenue’

DataFrames

Attribute

Type

Description

generation_df

pd.DataFrame

Hourly generation dispatch

storage_df

pd.DataFrame

Hourly storage operation

thermal_generation_df

pd.DataFrame

Disaggregated thermal generation

summary_df

pd.DataFrame

Summary metrics

marginal_prices_df

pd.DataFrame

Fixed-decision LP LMP rows with price components and pricing method/status metadata. Use get_marginal_prices_dataframe() for a defensive copy.

line_congestion_duals_df

pd.DataFrame

Zonal directional line-capacity dual audit rows. Empty for copperplate runs; use get_line_congestion_duals_dataframe() for a defensive copy.

Zonal Fields

Attribute

Type

Description

is_zonal

bool

True when collected from a zonal model (model.A and model.area present).

areas

list

Area IDs in the solved zonal model.

lines

list

Line metadata (line_id, from_area, to_area).

area_capacity

dict

Per-area installed capacities.

area_storage_capacity

dict

Per-area storage capacities (charge, discharge, energy).

area_generation_totals

dict

Per-area generation totals.

area_cost_breakdown

dict

Per-area cost breakdown.

area_generation_df

dict[str, pd.DataFrame]

Hourly generation by area.

area_storage_df

dict[str, pd.DataFrame]

Hourly storage operation by area.

area_thermal_generation_df

dict[str, pd.DataFrame]

Thermal generation by area.

area_installed_plants_df

dict[str, pd.DataFrame]

Installed plants by area.

area_summary_df

dict[str, pd.DataFrame]

Per-area summary DataFrames.

interregional_exchanges_df

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 both model.A and model.area exist.

  • In zonal mode, summary_df contains aggregate system-level metrics and area_summary_df contains per-area summary DataFrames.