Model outputs
After every successful solve, the HiGHS solution is handed back to FlexTool in-memory together with the polars FlexData bundle that produced the LP. flextool.process_outputs.write_outputs then turns that pair into the result tables documented on this page.
The result tables are stored as parquet (the canonical store) and a configurable mix of derived artefacts:
| Location | Format | When written | Notes |
|---|---|---|---|
output_parquet/<scenario>/*.parquet |
parquet | always (canonical) | one file per result table; read by the GUI, the scenario-comparison tools, and read_parquet_dir=True re-runs |
output_csv/<scenario>/*.csv |
CSV | when output-csv is enabled |
one CSV per parquet, plus summary_solve.csv (diagnostic overview) |
output_excel/output_<scenario>.xlsx |
Excel | when output-excel is enabled |
one workbook per scenario; alphabetically ordered sheets |
output_plots/<scenario>/*.png (or svg/pdf) |
image | when output-plot is enabled |
driven by templates/default_plots.yaml |
<output-location>/results.sqlite (or --results-db-url) |
SpineDB | when output-spinedb is enabled (--write-methods spinedb) |
the processed result tables written into a SpineDB using the FlexTool results schema (flextool/schemas/spinedb_results_schema.json); one Spine alternative per run (named after the scenario) so multiple runs coexist in one file. Time series land as nested Spine Map values on the model entity classes (node / unit / connection / group / model). |
The CSV / Excel / plot files are derived from the parquets, so deleting or regenerating them is safe. summary_solve.csv is a diagnostic file aimed at a quick overview of a solve — it lists objective decomposition, period weighting, CO2 total, and any non-zero slack uses.
All annualized numbers are scaled to a full year of operation by dividing through by complete_period_share_of_year. All values are returned in user units — auto-scaling (see LP scaling pipeline) and time-block aggregation under flex-temporal decomposition are unwound before they reach the parquets, so users always read MW / MWh / [CUR] in the original problem space.
For stochastic models the parquets carry only the realised branch of each solve by default. Setting the model parameter output_horizon = yes adds the forecast-branch rows to the time-series tables (*_t, *_dt) so the unrealised futures are visible for debugging — note that cost aggregates in that mode mix realised + unrealised contributions and should not be used as final results (see also How to use stochastics).
- Costs
- Cost by entity
- Prices
- Energy flows
- Capacity factors
- Energy balance in nodes
- Unit online and startup
- Unit curtailment and VRE potential
- Node group results
- Flow group results
- Capacity and investment results
- CO2 emissions
- Reserves
- Inertia and non-synchronous generation
- Ramps
- DC power flow
- Slack and penalty values
Costs
modelentitycost_annualizedparameter — M[CUR] (millions of user-chosen currency); annualized total cost broken down by category:- unit investment & retirement — M[CUR] cost of investing in unit capacity or salvage from retiring it
- connection investment & retirement — M[CUR] same for connections
- storage investment & retirement — M[CUR] same for node states (storages)
- fixed cost pre-existing — M[CUR] fixed O&M for pre-existing capacity
- fixed cost invested — M[CUR] fixed O&M for newly invested capacity
- fixed cost reduction of divestments — M[CUR] fixed O&M removed by divestments
- commodity cost / commodity sales — M[CUR] cost of buying / revenue from selling commodities
- co2 — M[CUR] cost of CO2 emissions caused by commodities with CO2 content
- other operational — M[CUR] other variable O&M costs
- starts — M[CUR] start-up costs
- upward / downward slack penalty — M[CUR] cost of involuntary demand reduction / increase
- inertia slack penalty — M[CUR] cost of not meeting the inertia constraint
- non-synchronous slack penalty — M[CUR] cost of not meeting the non-synchronous constraint
- capacity margin penalty — M[CUR] cost of not meeting the capacity margin constraint
- upward / downward reserve slack penalty — M[CUR] cost of not meeting the reserve constraint
modelentitycost_tparameter — M[CUR] same categories as above but per timestep (no investment / fixed terms)modelentitycost_discounted_solveparameter — M[CUR] costs for the solve considering discounting and years represented (NPV currency)modelentitycost_discounted_totalparameter — M[CUR] same, totalled over all realised periods
Cost by entity
The system Costs above are also broken down per entity at the period level, so you can see which unit, connection or node drives each cost category. Six result tables are produced — one for each entity type (unit, connection, node) in each of two flavours:
- annualized — M[CUR/a], scaled to a full year of operation (the same basis as
cost_annualized) - discounted — M[CUR], net present value over the horizon (the same basis as
cost_discounted_total)
Each table is indexed by (period, entity) — the entity level is named after its type (unit, connection or node) — and carries one column per cost category:
unitentitycost_annualized/cost_discountedparameters — per-unit cost break-downconnectionentitycost_annualized/cost_discountedparameters — per-connection cost break-downnodeentitycost_annualized/cost_discountedparameters — per-node cost break-down
The categories mirror the system Costs decomposition, restricted to those that apply to the entity type:
- commodity cost / commodity sales — M[CUR] cost of buying / revenue from selling commodities
- co2 — M[CUR] cost of CO2 emissions caused by the entity's flows
- other operational — M[CUR] other variable O&M costs
- starts — M[CUR] start-up costs (units)
- investment / retirement — M[CUR] cost of investing in unit / connection capacity or salvage from retiring it (named storage investment / storage retirement in the
nodetable, where the capacity is node-state storage energy) - fixed cost pre-existing / fixed cost invested / fixed cost reduction of divestments — M[CUR] fixed O&M for pre-existing, newly invested and divested capacity
- upward slack penalty / downward slack penalty (
nodetable only) — M[CUR] cost of involuntary demand reduction / increase at the node
Attribution rules (matching the LP objective):
- Fuel / commodity cost is attributed to the consuming process — the unit or connection that draws the commodity — not to the source node.
- CO2 cost for a process is the sum over every priced group the process touches, so a flow that contributes emissions to several priced groups carries the full cost of each.
By construction these tables are purely additive collapses of the same per-entity intermediates the system summary uses, so summing any category over all entities reproduces the system summary: the unit + connection + node contributions for a given category and period add up to the matching cost_annualized / cost_discounted_total category value (annualized_costs_d_p / costs_discounted_d_p in the parquet store).
These are period-level tables only — there are no per-timestep per-entity cost tables.
Prices
nodeentityprice_tparameter — [CUR/MWh] dual of the balance constraint for every node that maintains an energy balance (nominal currency at periodd, un-discounted from NPV)group__reserve__upDownentityreserve_price_tparameter — [CUR/MWh] dual of the reserve balance constraintgroupentityco2_price_periodparameter — [CUR/tCO2] shadow price of the per-period CO2 cap (nominal currency, sign-flipped so a binding cap shows positive)groupentityco2_price_totalparameter — [CUR/tCO2] shadow price of the cumulative CO2 cap (NPV currency, broadcast across periods)
Energy flows
unit__nodeentityflow_annualizedparameter — [MWh] cumulative flow from the node (if node is input) or to the node (if node is output), annualized to a full yearunit__nodeentityflow_tparameter — [MW] flow at each timestepconnection__node__nodeentityflow_annualizedparameter — [MWh] annualized cumulative flow through the connection (left-to-right is positive)connection__node__nodeentityflow_tparameter — [MW] flow through the connection at each timestepconnection__node__nodeentityconnection_losses_annualizedparameter — [MWh] annualized losses on the connectionconnection__node__nodeentityconnection_losses_tparameter — [MW] losses on the connection at each timestep
Optional output: output_connection_flows_separate
connection__node__nodeentityflow_to_first_node_annualizedparameter — [MWh] annualized cumulative flow only to the left (first) nodeconnection__node__nodeentityflow_to_second_node_annualizedparameter — [MWh] annualized cumulative flow only to the right (second) nodeconnection__node__nodeentityflow_to_first_node_tparameter — [MW] flow to the left (first) node at each timestepconnection__node__nodeentityflow_to_second_node_tparameter — [MW] flow to the right (second) node at each timestep
Capacity factors
unit__nodeentitycfparameter — [per unit] average capacity factor of the flow: time-average of flow [MWh/h] divided by capacity [MW] of the unit input or output. One table per direction (unit_cf__inputNode,unit_cf__outputNode).connectionentitycfparameter — [per unit] average capacity factor of the absolute flow (flows in either direction count as utilization), divided by connection capacity
Energy balance in nodes
nodeentitybalanceparameter — [MWh] annualized period sum of all balance contributions: From units, From connections, To units, To connections, Self discharge, Loss of load (upward slack), Excess load (downward slack), Inflownodeentitybalance_tparameter — [MW] same categories per timestepnodeentitystate_tparameter — [MWh] storage state of the node at each timestep (state nodes only; multiplied byentity_unitsizeso the value is in storage MWh, not the scaled LP variable)
Unit online and startup
unitentityonline_tparameter — [count] number of units online at each timestepunitentityonline_averageparameter — [count] average online status over the period (weighted by step duration)unitentitystartup_annualizedparameter — [count] startups during the period scaled to a full year (weighted byrp_cost_weightto handle representative periods correctly)
Unit curtailment and VRE potential
For VRE units (units that use an upper_limit profile) the model also reports curtailment and potential generation against each output node:
unit__nodeentityVRE_potential_tparameter — [MW] potential VRE generation at the timestep (capacity × profile)unit__nodeentityVRE_potential_annualizedparameter — [MWh] annualized potential VRE generation in the periodunit__nodeentitycurtailment_tparameter — [MW] potential minus actual generation at the timestepunit__nodeentitycurtailment_annualizedparameter — [MWh] annualized curtailed energy in the periodunit__nodeentitycurtailment_share_tparameter — [0–1] timestep curtailment / potentialunit__nodeentitycurtailment_shareparameter — [0–1] period curtailment / period potential
Node group results
group entities with print_indicators = yes produce indicator tables aggregating flows / inflows / slacks over their member nodes:
groupentityindicator(period table) — gives a set of indicators for allnodemembers:- Loss of load share — [0–1] upward slack relative to inflow
- VRE share of demand — [0–1] share of inflow served by VRE sources
- Excess load share — [0–1] downward slack relative to inflow
- Curtailed VRE of demand — [0–1] curtailed VRE relative to inflow
- Annualized inflow — [MWh] sum of
inflowto member nodes scaled to a year - Curtailed VRE of potential VRE — [0–1] curtailed share of potential VRE
groupentityindicator_t(timestep table) — exposes the timestep-level building blocks of the above: Loss of load [MWh/step], VRE generation [MWh/step], Excess load [MWh/step], Curtailed VRE [MWh/step], Timestep inflow [MWh/step], Curtailed VRE of potential VRE [0–1], Annualized inflow [MWh], VRE share of demand [0–1]groupentityVRE_share_tparameter — [0–1] share of inflow served by VRE sources at each timestep (one column per node group)groupentityVRE_shareparameter — [0–1] period-average VRE sharegroupentitytotal_inflow_annualizedparameter — [MWh] annualized sum ofinflowto member nodesgroupentitytotal_inflow_tparameter — [MWh/step] timestep inflow to member nodes
group entities with print_dispatch = yes produce a multi-column dispatch table over the group:
groupentityflows_tparameter — [MWh/step] dispatch decomposed by(type, item)wheretypeis one of: slack (upward/downward), from_unit, from_unitGroup, to_unit, to_unitGroup, from_connection, from_connectionGroup, to_connection, to_connectionGroup, inflow, internal_losses (units/connections/storages)groupentityflows_annualizedparameter — [MWh] same decomposition, annualized period totals
Flow group results
flowGroup entities whose flow_aggregator is standalone_aggregator_only or both produce aggregate flow statistics over their member (process, node) legs (listed via flowGroup__unit__node / flowGroup__connection__node):
group_flow__d.csv— per-(flowGroup, period) totals:cumulative_flowparameter — [MWh] sum of |flow| over the period for all member legsaverage_flowparameter — [MW] average power equivalent (cumulative_flow/ period hours)
group_flow__dt.csv— per-(flowGroup, period, time) signed net flow [MW] (sign convention: flow into the group's nodes is positive, flow out of them is negative). Intended for spreadsheet post-processing; because the values are signed they do not stack, so this series is not meant for stacked dispatch plots.
(For member flows shown as aggregated bands inside a node group's dispatch table, set flow_aggregator to dispatch_plots_only or both instead — see Node group results and the reference.)
Capacity and investment results
-
unit,connection,nodeentitiescapacityparameter — [MW or MWh] decomposed into:- existing — capacity assumed at the start of the period
- invested — capacity the model decided to invest in for the period
- divested — capacity the model decided to retire at the start of the period
- total —
existing + invested − divested
Period axis covers every period in which an investment or a divestment decision occurs (both the invest-eligible and divest-eligible periods feed into
d_realize_invest), so divest-only periods are visible even when no new capacity is added. -unit,connection,nodeentitiesinvest_marginalparameter — [CUR/MW or MWh] effective dual of the investment decision: zero means the model is at the unconstrained optimum; positive means an active upper bound (per-entity, per-group, period, total or cumulative caps are summed automatically); negative is not expected and indicates a numerical artefact worth investigating
CO2 emissions
modelentityCO2parameter — [Mt] horizon-total CO2 across all groups and units (usesyears_represented_dto convert annualized emissions to horizon totals)groupentityCO2_annualizedparameter — [Mt/yr] annualized CO2 emissions caused (or removed) by units and connections in the groupunit__source__sinkentityCO2_annualizedparameter — [Mt/yr] annualized CO2 emissions per flow leg
Reserves
unit__reserve__upDown__nodeandconnection__reserve__upDown__nodereservation_tparameter — [MW] reserve provision at each timestepunit__reserve__upDown__nodeandconnection__reserve__upDown__nodereservation_averageparameter — [MW] period-average reserve provision
Inertia and non-synchronous generation
groupentityinertia_tparameter — [MWs] total inertia in the group of nodes at each timestepgroupentityinertia_largest_flow_tparameter — [MW] largest individual flow coming into the group of nodes withhas_inertiagroupentityinertia_unit_node_tparameter — [MWs] inertia contribution per (unit, node) at each timestep, one column per contributing(unit, node)
Ramps
unit__nodeentityramp_tparameter — [MW] ramp of the unit input or output flow at each timestep (one table per direction:unit_ramp__inputNode__dt,unit_ramp__outputNode__dt)
The "ramp room" envelope on the node entity (additional headroom for upward / downward ramps from non-VRE units, VRE units, and connections) is currently not emitted by the new pipeline — it is on the roadmap for re-exposure once the corresponding post-processing is ported.
DC power flow
When the network contains DC-power-flow connections / nodes (is_dc_power_flow = yes):
nodeentitydc_angle_tparameter — [rad] voltage angle at each timestepconnectionentitydc_angle_diff_tparameter — [rad] angle difference across the connection at each timestep
Slack and penalty values
Slack uses are listed in the Energy balance in nodes section (Loss of load, Excess load and per-node slack_up / slack_down parameters with _t and annualized variants) and aggregated in Costs. Group-level slack tables:
groupentityslack_capacity_marginparameter — [MW] capacity-margin shortfall in investment periodsgroupentityslack_inertia_t/slack_inertia— [MWs] inertia shortfall, timestep / annualizedgroupentityslack_nonsync_t/slack_nonsync— [MW] non-synchronous-share shortfall, timestep / annualizedgroup__reserve__upDownentityslack_reserve_t/slack_reserve— [MW] reserve shortfall, timestep / annualized
For the sign / direction conventions of slacks, see Slack convention. For how block-aware solves (flex-temporal decomposition) and spatial Benders decomposition feed into these tables, see Decomposition — the result writers always operate on the fine-timeline, full-spatial solution, so the parquet tables look identical to a monolithic run.