Non-Served Demand Output

Contents

Overview | Columns | Segments | Configuration | Assumptions | Examples | See Also

Overview

File: non_served_demand.csv

non_served_demand.csv records unmet demand at every node that has non-served demand (NSD) variables enabled, across all representative time steps. Non-served demand represents the portion of demand that the optimizer chose not to meet — this happens when meeting demand would be more expensive than paying the NSD penalty price.

Non-served demand is a demand-side flexibility tool. It prevents the model from becoming infeasible when supply is insufficient, at the cost of a penalty in the objective function. The penalty price is specified in the node's input data.

Only nodes with max_nsd != [0.0] (i.e., nodes with non-zero maximum non-served demand) in their input configuration produce rows in this file. If no nodes have NSD variables, the file will not be written.

NSD as a soft constraint

Non-served demand effectively turns the demand balance constraint into a soft constraint. The optimizer will serve demand as long as the marginal cost of doing so is below the NSD penalty price. High NSD values in the results may indicate that supply infrastructure is insufficient, or that the NSD penalty price is too low.

Columns

ColumnTypeDescription
commodityStringCommodity type at the node (e.g., Electricity, Hydrogen)
zoneStringZone (location) where the node is located
component_idStringUnique identifier of the node (e.g., elec_SE, h2_MIDAT)
component_typeStringType of the node (e.g., Node{Electricity})
variableStringAlways "non_served_demand"
segmentIntNSD segment index (1-based; see Segments)
timeIntRepresentative time step index (1-based integer, matches time in other output files)
valueFloat64Amount of unmet demand at this node, segment, and time step (same units as demand input)

Segments

Non-served demand is modeled using piecewise-linear segments that allow different quantities of demand to be unmet at different penalty prices. This mirrors the concept of a "value of lost load" that varies with the amount of load shed:

  • Segment 1 — first block of NSD, up to the limit max_nsd[1], penalized at nsd_cost[1]
  • Segment 2 — second block (if defined), up to max_nsd[2], penalized at nsd_cost[2]
  • etc.

In many models a single segment is used (i.e., all unmet demand is penalized at the same price). In that case, only segment = 1 rows appear for each node.

The penalty costs for NSD segments are specified in the node's input data under the nsd_cost field. These costs appear in costs.csv under the NonServedDemand cost category.

Wide format naming

In wide format, the column name for each NSD variable is formed by combining the component_id and segment: {component_id}_seg{segment} (e.g., elec_SE_seg1, elec_SE_seg2). This allows distinguishing multiple segments for the same node when columns are pivoted.

Configuration

SettingFileDefaultEffect
OutputLayout (or OutputLayout.NonServedDemand)macro_settings.json"long"Set to "wide" to pivot time steps into columns with compound column names {component_id}_seg{segment}.
WriteFullTimeseriescase_settings.jsonfalseWhen true and TDR is active, also write full-year NSD to full_time_series/non_served_demand.csv.

Assumptions

  • Nodes with NSD only. Only nodes configured with max_nsd != [0.0] (i.e., nodes with non-zero maximum non-served demand) produce rows. Nodes without NSD variables are excluded from this file.
  • File not written if empty. If no node in the system has NSD variables, non_served_demand.csv will not be created.
  • Units. NSD values are in the same units as the demand input for that commodity. For electricity, this is typically MWh/hour (= MW). For mass commodities, the units follow your input convention.
  • Annual NSD. To compute total annual non-served demand, multiply value(t) × weight(t) from time_weights.csv and sum:
    Annual NSD (MWh) = Σ_{t,seg}  value(t, seg) × weight(t) × hours_per_timestep
  • NSD and infeasibility. If NSD is not enabled on a node (max_nsd = [0.0]) and the optimizer cannot meet demand, the problem will be infeasible.
  • Cost in objective. NSD values incur penalty costs that appear under the NonServedDemand category in costs_by_type.csv and costs_by_zone.csv.

Examples

Default Long Format (example rows — single segment)

commodityzonecomponent_idcomponent_typevariablesegmenttimevalue
ElectricitySEelec_SENode{Electricity}non_served_demand110.0
ElectricitySEelec_SENode{Electricity}non_served_demand120.0
ElectricitySEelec_SENode{Electricity}non_served_demand1312.5

Default Long Format (example rows — two segments)

commodityzonecomponent_idcomponent_typevariablesegmenttimevalue
ElectricitySEelec_SENode{Electricity}non_served_demand13100.0
ElectricitySEelec_SENode{Electricity}non_served_demand2350.0

In this example at time step 3, the first 100 MW of demand is unmet at the segment-1 penalty price, and an additional 50 MW is unmet at the (higher) segment-2 penalty price.

Wide Format (OutputLayout.NonServedDemand = "wide")

timeelec_SE_seg1elec_SE_seg2h2_MIDAT_seg1
10.00.00.0
20.00.00.0
3100.050.00.0

Computing Total Annual NSD

using CSV, DataFrames

nsd = CSV.read("results/non_served_demand.csv", DataFrame)
weights = CSV.read("results/time_weights.csv", DataFrame)

df = leftjoin(nsd, weights, on=:time)
df.annual_MWh = df.value .* df.weight

# Total annual NSD by node and segment
annual = combine(groupby(df, [:component_id, :segment]), :annual_MWh => sum)

See Also