Market Scores#

Market-output scoring functions, including PTDF construction and node-level scores from dispatch, prices, and congestion duals.

Score Definitions#

The table below introduces the notation used throughout the market-based scores.

Symbol

Meaning

\(g\)

Generator

\(v\)

Node

\(G(v)\)

Set of generators connected to node \(v\)

\(d_g\)

Dispatch of generator \(g\)

\(c_g\)

Marginal cost of generator \(g\)

\(\lambda_v\)

Nodal price at node \(v\)

\(\gamma_g\)

Generator-capacity dual value

\(P_g^{\max}\)

Maximum capacity of generator \(g\)

\(L_v\)

Load at node \(v\)

\(\mu_{vw}^{+}, \mu_{vw}^{-}\)

Congestion duals on line \((v,w)\)

\(F_{vw}^{\max}\)

Capacity of line \((v,w)\)

\(\delta(v)\)

Set of lines incident to node \(v\)

\(\mathrm{PTDF}_{\ell,v}\)

PTDF coefficient of line \(\ell\) with respect to node \(v\)

\(m_\ell\)

Residual margin on line \(\ell\)

\(\varepsilon\)

Small positive constant to avoid division by zero

The main market-based scores are summarized below.

Score

Definition

Interpretation

Rent-Weighted Dispatch

\(s_g = \max(0, \lambda_{n(g)} - c_g)d_g,\quad S_v = \sum_{g \in G(v)} s_g\)

Rewards nodes whose dispatched generators earn positive margin at the baseline prices.

Dispatch Volume

\(S_v = \sum_{g \in G(v)} d_g\)

Measures how much generation is dispatched at each node.

Gamma-Capacity

\(s_g = \gamma_g P_g^{\max},\quad S_v = \sum_{g \in G(v)} s_g\)

Emphasizes nodes with generators that are scarce in the baseline solution.

Gamma-Capacity-Congestion

\(S_v = \sum_{g \in G(v)} \gamma_g P_g^{\max} + \sum_{(v,w) \in \delta(v)} F_{vw}^{\max}(\mu_{vw}^{+} + \mu_{vw}^{-})\)

Combines generator scarcity with congestion on adjacent lines.

Load-Weighted LMP

\(S_v = \lambda_v L_v\), or \(S_v = \min(\lambda_v, \mathrm{VOLL})L_v\) when capping is used

Highlights nodes where high prices coincide with high demand.

PTDF Stress

\(S_v = \left(\sum_{g \in G(v)} P_g^{\max}\right)\sum_{\ell}\frac{|\mathrm{PTDF}_{\ell,v}|}{m_\ell + \varepsilon}\)

Captures how strongly a node’s installed capacity is exposed to tight transmission constraints.

API path: node_ranking.market_scores

Market-based score functions and PTDF utilities for node ranking.

This module contains:

  • PTDF construction helpers for DC network representations

  • node-level scores based on baseline dispatch outputs

  • score formulations that use dispatch, nodal prices, scarcity duals, and congestion duals

build_B_matrix(G, b_attr='B')[source]#

Build the nodal susceptance (Laplacian) matrix for DC power flow.

Parameters:
  • G (nx.Graph) – Undirected graph with edge attribute b_attr = susceptance.

  • b_attr (str) – Edge attribute name for susceptance.

Returns:

  • B ((n, n) np.ndarray) – Nodal susceptance matrix.

  • nodes (list) – Node labels in the order used for B.

  • node_index (dict) – Map node label -> row/col index in B.

Return type:

tuple[ndarray, list[Hashable], dict[Hashable, int]]

Notes

For each edge (u, v), the edge susceptance is added to the diagonal entries of u and v and subtracted from the corresponding off-diagonal entries.

compute_bus_angle_basis(Binv, n, slack_idx, mask)[source]#

Compute bus-angle responses for unit injections at non-slack buses.

Parameters:
  • Binv (np.ndarray) – Inverse of the reduced susceptance matrix.

  • n (int) – Number of buses in the original full system.

  • slack_idx (int) – Index of the slack bus in the full system. This argument is included for interface clarity; the slack angle is fixed implicitly by reinserting a zero row.

  • mask (list[int]) – Full-system indices of the non-slack buses.

Returns:

theta_full – Voltage-angle responses with slack angle fixed at zero.

Return type:

(n, n-1) np.ndarray

compute_ptdf(G, slack=None, b_attr='B')[source]#

Compute the PTDF matrix (1 MW injection, slack withdrawal convention).

Parameters:
  • G (nx.Graph) – Network with line susceptance stored in b_attr.

  • slack (hashable or None, default=None) – Label of the slack node. If omitted, the first node in G.nodes() is used.

  • b_attr (str, default="B") – Edge attribute containing line susceptance.

Returns:

  • ptdf ((m, n-1) np.ndarray) – Rows = lines in edges, cols = non-slack buses (order = mask).

  • edges (list[tuple]) – Edge list in the row order of ptdf.

  • nodes (list) – Node labels in column/angle order used to build B.

  • mask (list[int]) – Indices of non-slack buses (maps ptdf columns -> nodes[mask[c]]).

  • slack_node (hashable) – The chosen slack node label.

Return type:

tuple[ndarray, list[tuple[Hashable, Hashable, dict[str, Any]]], list[Hashable], list[int], Hashable]

Notes

The returned PTDF has one column per non-slack bus. Column c corresponds to node nodes[mask[c]].

dispatch_volume_score(nodes, generators, baseline_result)[source]#

Compute node dispatch-volume scores from a baseline dispatch.

This score is the total dispatched generation connected to each node.

Score definition:

\[S_v = \sum_{g \in G(v)} d_g\]
Parameters:
  • nodes (dict) – Node data keyed by node label.

  • generators (dict) – Generator data keyed by generator id. Each generator must provide a node entry.

  • baseline_result (dict) – Baseline dispatch outputs containing dispatch.

Returns:

Node-level dispatch volume scores.

Return type:

dict[str, float]

gamma_capacity_congestion_score(nodes, generators, lines, baseline_result)[source]#

Compute gamma-capacity-congestion score (GCCS) per node.

This score adds two components at each node:

  • a generator-scarcity term based on gamma_g P_g^{max}

  • a congestion term based on the dual values of incident line limits

Score definition:

\[S_v = \sum_{g \in G(v)} \gamma_g P_g^{\max} + \sum_{(v,w) \in \delta(v)} F_{vw}^{\max}\left(\mu_{vw}^{+} + \mu_{vw}^{-}\right)\]
Parameters:
  • nodes (dict) – Node data keyed by node label.

  • generators (dict) – Generator data keyed by generator id.

  • lines (dict) – Line data keyed by line id. Each line must provide ends and capacity.

  • baseline_result (dict) – Baseline dispatch outputs containing gamma, mu_plus, and mu_minus.

Returns:

Node-level gamma-capacity-congestion scores.

Return type:

dict[str, float]

gamma_capacity_score(nodes, generators, baseline_result)[source]#

Compute gamma-capacity scores from a baseline dispatch.

This score combines the generator-capacity dual gamma with installed capacity and then aggregates the resulting generator-level scarcity signal to nodes.

Score definition:

\[s_g = \gamma_g P_g^{\max}\]
\[S_v = \sum_{g \in G(v)} s_g\]
Parameters:
  • nodes (dict) – Node data keyed by node label.

  • generators (dict) – Generator data keyed by generator id. Each generator must provide node and p_max.

  • baseline_result (dict) – Baseline dispatch outputs containing gamma.

Returns:

(node_scores, gen_scores) with node-level and generator-level gamma-capacity scores.

Return type:

tuple[dict[str, float], dict[str, float]]

invert_reduced_B(B, slack_idx)[source]#

Invert the reduced B matrix after removing the slack row/column.

Parameters:
  • B (np.ndarray) – Full nodal susceptance matrix.

  • slack_idx (int) – Index of the slack bus in B.

Returns:

  • Binv ((n-1, n-1) np.ndarray) – Inverse of the reduced B matrix.

  • mask (list[int]) – Indices of non-slack buses kept in the reduced system.

  • full2red (np.ndarray) – Map full-bus index -> reduced index (or -1 for slack).

Return type:

tuple[ndarray, list[int], ndarray]

load_weighted_lmp_score(nodes, baseline_result, VOLL=500.0, cap_lambda=True)[source]#

Compute load-weighted LMP scores from baseline nodal prices and load.

This score highlights nodes where high prices coincide with high load. Optionally, nodal prices can be capped at VOLL before weighting.

Score definition:

\[S_v = \lambda_v L_v\]

When cap_lambda is enabled, the score becomes:

\[S_v = \min(\lambda_v, \mathrm{VOLL}) L_v\]
Parameters:
  • nodes (dict) – Node data keyed by node label. Each node must provide load.

  • baseline_result (dict) – Baseline dispatch outputs containing lambdas.

  • VOLL (float, default=500.0) – Price cap applied when cap_lambda is enabled.

  • cap_lambda (bool, default=True) – Whether to cap nodal prices at VOLL before scoring.

Returns:

Node-level load-weighted LMP scores.

Return type:

dict[str, float]

ptdf_stress_score(ptdf, nodes, mask, generators, line_margins, epsilon=1e-06)[source]#

Compute PTDF stress score (PTDFS) per node.

This score combines installed capacity at a node with the node’s PTDF exposure to lines that have little residual margin. Higher values indicate nodes whose injections are both large and strongly coupled to tight lines.

For each non-slack node \(v\):

\[S_v = \left(\sum_{g \in G(v)} P_g^{\max}\right)\sum_{\ell}\frac{\left|\mathrm{PTDF}_{\ell,v}\right|}{m_{\ell} + \varepsilon}\]

where \(m_{\ell}\) is the residual line margin and \(\varepsilon\) avoids division by zero. Slack node score is set to 0 because PTDF is provided only for non-slack columns (given by mask).

Parameters:
  • ptdf (np.ndarray) – PTDF matrix with rows as lines and columns as non-slack buses.

  • nodes (list[hashable]) – Node labels in the full network order.

  • mask (list[int]) – Indices of non-slack buses corresponding to PTDF columns.

  • generators (dict) – Generator data keyed by generator id. Each generator must provide node and p_max.

  • line_margins (np.ndarray or list[float]) – Residual margin for each PTDF row / line.

  • epsilon (float, default=1e-6) – Small positive stabilizer in the denominator.

Returns:

PTDF stress score for each node in the full node set.

Return type:

dict[hashable, float]

rent_weighted_dispatch_score(nodes, generators, baseline_result)[source]#

Compute rent-weighted dispatch scores from a baseline dispatch.

This score rewards dispatched generators that earn a positive gross margin at the baseline nodal price, then aggregates those generator-level scores to nodes.

Score definition:

\[s_g = \max(0, \lambda_{n(g)} - c_g)\, d_g\]
\[S_v = \sum_{g \in G(v)} s_g\]

where \(d_g\) is dispatch, \(c_g\) is generator cost, and \(\lambda_{n(g)}\) is the nodal price at the generator’s node.

Parameters:
  • nodes (dict) – Node data keyed by node label. Used to initialize the node-score map.

  • generators (dict) – Generator data keyed by generator id. Each generator must provide at least node and cost.

  • baseline_result (dict) – Baseline dispatch outputs containing dispatch and lambdas.

Returns:

(node_scores, gen_scores) with node-level and generator-level rent-weighted dispatch scores.

Return type:

tuple[dict[str, float], dict[str, float]]

Reference#

M. Bichler and T. Dobos. A market-based analysis of critical nodes in power systems. In International Conference on the European Energy Market, EEM 2026, June 2026.