Assigns bus types (Generator, Load, Connection) to a raw power grid
topology using an Artificial Immune System (AIS) optimization algorithm.
The method, from Elyas and Wang (2016), exploits the observed non-trivial
correlations between bus types and topology metrics (node degree, clustering
coefficient) in realistic grids. A bus type entropy measure quantifies
these correlations, and a target entropy \(W^*\) is derived from a
scaling property fitted to real-world systems. The AIS then searches for
an assignment whose entropy matches \(W^*\).
The pipeline is:
Determine target bus type ratios \((r_G, r_L, r_C)\) from network
size.
Estimate \(W^*\) via Monte Carlo sampling of random assignments
and the scaling relation \(W^* = \mu + \sigma \cdot d(N)\).
Run AIS optimization (clonal selection, hypermutation, receptor editing)
to find an assignment \(\mathbb{T}\) such that
\(|W(\mathbb{T}) - W^*| < \epsilon\).
Two entropy definitions are supported:
Model 0 (\(W_0\)): standard entropy of bus/link type ratios.
\(\mu\) is stable across network sizes.
Model 1 (\(W_1\)): generalized entropy weighted by \(N\)
and \(M\). \(\mu\) grows with network size, giving better
discrimination in large grids.
Parameters:
graph (nx.Graph) – NetworkX graph representing the grid topology (nodes and edges only;
no bus type attributes required yet).
entropy_model (int) – Selects the entropy definition: 0 for \(W_0\), 1 for \(W_1\).
bus_type_ratio (list of float or None) – Optional target ratios [Gen,Load,Conn]. Values are normalized
to sum to 1. If None, default ratios are chosen based on N.
Compute ratios of the 6 link (edge) types for a given assignment.
Each edge is classified by the sorted pair of its endpoint types:
GG (1,1), LL (2,2), CC (3,3), GL (1,2), GC (1,3), LC (2,3).
The ratio is \(R_{ij} = M_{ij} / M\).
Parameters:
assignment (np.ndarray) – Bus type vector of shape (N,).
Estimate the target entropy \(W^*\) via Monte Carlo sampling.
Generates monte_carlo_iters random bus type assignments (with
fixed target ratios), computes their entropies, and fits a normal
distribution \(\mathcal{N}(\mu, \sigma^2)\). The target entropy
is then:
\[W^* = \mu + \sigma \cdot d(N)\]
where \(d(N)\) is the piecewise scaling function from
_get_d_parameter().
Parameters:
monte_carlo_iters (int, optional) – Number of random samples (default 2000).
Returns:
w_star (float) – Target entropy value.
std_w (float) – Standard deviation of the Monte Carlo entropy samples.
Generate a random bus type assignment vector of length \(N\).
Types: 1 = Generator, 2 = Load, 3 = Connection. The counts are
determined by self.ratio_types. Connection buses are preferentially
placed on non-leaf nodes (degree > 1) to reflect the hub-like role of
connection buses in realistic grids.
Returns:
Integer array of shape (N,) with values in {1,2,3}.
Compute the normalized distance parameter \(d(N)\) for estimating
the target entropy \(W^*\).
The piecewise scaling functions were fitted to realistic grids by
Elyas and Wang (2016). For entropy model 0:
\[\begin{split}d_0(N) = \begin{cases}
-1.39 \ln N + 6.79 & \text{if } \ln N \le 8 \\
-6.003 \times 10^{-14} (\ln N)^{15.48} & \text{if } \ln N > 8
\end{cases}\end{split}\]
For entropy model 1:
\[\begin{split}d_1(N) = \begin{cases}
-1.748 \ln N + 8.576 & \text{if } \ln N \le 8 \\
-6.053 \times 10^{-22} (\ln N)^{24.1} & \text{if } \ln N > 8
\end{cases}\end{split}\]
Note
The \(d_0\) linear-segment coefficients here (-1.39,
6.79) follow the MATLAB SynGrid implementation and differ
from the values in the paper (-1.721, 8).
Assigns generation capacities (PgMax) to generator buses in the grid.
Implements the three-stage methodology from Elyas et al. (2017):
Total generation: Compute the aggregate generation capacity from a
scaling law fitted to realistic grids:
logPg_tot(N)=-0.21*log^2(N)+2.06*log(N)+0.66.
Individual capacities: Sample N_g individual capacities from an
exponential distribution (with ~1% super-large outliers to capture the
observed heavy tail).
Correlated assignment: Assign capacities to generator buses using a
14x14 empirical 2D probability table (Tab_2D_Pgmax) that encodes
the joint distribution of normalized capacity and normalized nodal
degree. This preserves the observed Pearson correlation
rho(P_bar, k_bar) in [0.25, 0.5] between capacity and degree.
Reference systems with pre-computed Tab_2D_Pgmax tables are available
for NYISO-2935 (id=1), WECC-16944 (id=2), and a third system (id=3).
A heuristic diagonal-bias table (id=0) is provided as a fallback.
Parameters:
graph (nx.Graph) – NetworkX graph with 'bus_type' node attributes already set
(by BusTypeAllocator).
ref_sys_id (int) – Reference system for statistical tables (0=heuristic, 1=NYISO-2935,
2=WECC-16944, 3=additional reference).
Assign normalized capacities to buses via 2D-binning (Stage 3).
This implements the correlated assignment algorithm from Elyas et al.
(2017) that preserves the empirical joint distribution of normalized
capacity and normalized nodal degree. The steps are:
Scale 2D table to counts: multiply tab_2d by N_g and
round to integer targets n_rc for each (capacity-class r,
degree-class c) cell. Adjust rounding so that the total equals
N_g exactly.
Compute marginals: column sums give degree-bin targets;
row sums give capacity-bin targets.
Bin by degree: sort generators by normalized degree, partition
into 14 degree bins according to column-sum targets.
Bin by capacity: sort normalized capacities, partition into 14
capacity bins according to row-sum targets.
Nested assignment loop: for each degree bin c = 1..14 and
each capacity bin r = 14..1 (high to low): randomly sample
n_rc capacities from capacity bin r (without replacement)
and assign them to unassigned generators in degree bin c.
Reassemble all bins into the output array.
Parameters:
norm_deg_pairs (np.ndarray) – Shape (N_g,2) — columns are [NodeID,NormalizedDegree].
Generate a heuristic 14x14 joint-probability table as a fallback.
When no reference system data is selected (ref_sys_id=0), this
method produces a synthetic table using a Gaussian-decay diagonal
bias: weight(r,c)=exp(-0.5*|r-c|), where rows represent
capacity classes and columns represent degree classes (both ordered
low-to-high). The matrix is normalized to sum to 1.
This simulates the positive correlation between nodal degree and
generation capacity observed in realistic grids, without relying on
empirical data from a specific reference system.
Return the Tab_2D_Pgmax table for the selected reference system.
The table is a 14x14 matrix representing the empirical joint PDF
Pr((P_bar_gn_max,k_bar_n)inA) discretized into 14 capacity
classes (rows, low-to-high) and 14 nodal-degree classes (columns,
low-to-high). Tables for real reference systems are loaded from
reference_data.get_reference_stats() (ported from the MATLAB
SynGrid file sg_refsys_stat.m).
Sample individual generation capacities from the empirical distribution.
Following Elyas et al. (2017), more than 99% of generation units in
realistic grids follow an exponential distribution. About 1% have
extremely large capacities that fall outside the exponential range.
The procedure is:
Draw N_g samples from Exp(mu) where mu=total_gen/N_g.
Replace ~1% of the samples with “super-large” values drawn
uniformly from [max(P),3*max(P)].
If the sum deviates more than 5% above or 10% below
total_gen, rescale all values proportionally.
Normalize by the maximum capacity.
Parameters:
total_gen (float) – Target total generation capacity (MW).
Returns:
p_caps (np.ndarray) – Raw (possibly rescaled) capacity values, shape (N_g,).
max_r_pgmax (float) – Maximum capacity value, used for normalization.
normalized_r_pgmax (np.ndarray) – Capacities normalized to [0, 1] by dividing by max_r_pgmax.
Run the full generation-capacity allocation pipeline.
Executes the three-stage methodology from Elyas et al. (2017):
Compute total generation capacity from the scaling law:
Pg_tot=10^(-0.21*log10(N)^2+2.06*log10(N)+0.66).
Sample individual capacities, normalize them and the generator
nodal degrees by their respective maxima so that both lie in
[0, 1]: P_bar=P/max(P), k_bar=k/max(k).
Assign normalized capacities to generator buses via 2D binning
using Tab_2D_Pgmax.
Denormalize: Pg_max=P_bar*max(P).
Parameters:
tab_2d (np.ndarray or None, optional) – Custom 14x14 probability matrix. If None, the default table
for the selected ref_sys_id is used.
Returns:
Mapping from generator node ID to its assigned capacity
Pg_max (MW).
Uncommitted units (10–20 %): \(\alpha = 0\), selected via
targets drawn from Uniform[0, 0.6].
Partially committed units (40–50 %): selected via exponential
distribution on capacity; dispatch factors assigned through a
2-D bin-matching table Tab_2D_Pg (\(14 \times 10\)).
Fully committed units (remainder): \(\alpha = 1\).
Balancing loop: iteratively adjusts dispatch to match total
load within 1 % tolerance.
Parameters:
graph (networkx.Graph) – Power grid graph. Generator nodes must have 'bus_type'=='Gen'
and 'pg_max' (MW) attributes. Load nodes must have 'pl' (MW).
ref_sys_id (int, optional) – Reference system for statistical tables (1 = NYISO-2935,
2 = WECC-16994, 3 = additional reference). Default is 1.
Loading-level flag from the reference system. When 0 all alphas
are Uniform[0, 1]; otherwise 0.5 % receive negative dispatch
(e.g., pumped-storage hydro).
Assign dispatch factors to committed units via 2-D bin matching.
Units are sorted by normalised capacity and alphas by value, then
distributed into bins defined by Tab_2D_Pg (14 capacity bins
\(\times\) 10 alpha bins). Within each bin, units and alphas
are paired randomly (high-to-low bin traversal). Any leftovers
are paired sequentially as a fallback.
This reproduces the empirical joint distribution
\(f(\bar{P}_{g}^{\max}, \alpha)\) from the reference system
(Sadeghian et al., 2018, Table I).
Parameters:
units (numpy.ndarray, shape (n, 2)) – [bus_id,normalised_capacity].
alphas (numpy.ndarray, shape (n, 1)) – Dispatch-factor values from _generate_alphas().
Generate dispatch factors for partially committed units.
When alpha_mod==0 (e.g. NYISO), all \(\alpha\) values are
drawn from Uniform[0, 1]. When alpha_mod!=0 (e.g. WECC),
99.5 % are Uniform[0, 1] and 0.5 % are negative, representing
reverse dispatch such as pumped-storage hydro.
Parameters:
n_comm (int) – Number of committed units requiring \(\alpha\) values.
Select generators to be partially committed (\(0 < \alpha < 1\)).
Selects 40–50 % of total generator count. 99 % of these are
chosen by matching to targets drawn from an exponential distribution
with parameter \(\mu_{\text{committed}}\); the remaining 1 %
are drawn from the extreme tail Uniform[0.5, 1.0], capturing
super-large units (Sadeghian et al., 2018, Sec. III-A).
Parameters:
norm_pg_max (numpy.ndarray, shape (n, 2)) – Remaining units after uncommitted selection:
[bus_id,normalised_capacity].
total_units_count (int) – Original total number of generator units (before any selection).
Select generators to be uncommitted (\(\alpha = 0\)).
Randomly selects 10–20 % of total generator units. Target
capacities are drawn from Uniform[0, 0.6] and the unit whose
normalised capacity is closest to each target is selected.
This reproduces the empirical observation that uncommitted units
tend to be small or medium-size (Sadeghian et al., 2018, Sec. III).
Parameters:
norm_pg_max (numpy.ndarray, shape (n, 2)) – Array with columns [bus_id,normalised_capacity].
Implements the algorithm of Sadeghian et al. (2018):
Collect generator buses and normalise capacities by
\(P^{\max}_{g_{\max}}\).
Partition generators into uncommitted
(\(\alpha = 0\)), partially committed
(\(0 < \alpha < 1\)), and fully committed
(\(\alpha = 1\)).
Assign dispatch factors to partially committed units via
the 2-D bin-matching table Tab_2D_Pg.
Iteratively balance total generation against total load
(1 % tolerance, up to 50 iterations) by scaling committed
\(\alpha\) values and toggling uncommitted / full-load
units on or off.
Convert normalised dispatch back to MW:
\(P_{g_i} = \alpha_i \cdot \bar{P}_{g_i}^{\max} \cdot P^{\max}_{g_{\max}}\).
Returns:
Mapping of generator bus ID to dispatched active power (MW).
Generate detailed input sequences for PowerGridGenerator from
high-level parameters.
This is “operation mode II”, where the user specifies only the number of
nodes, average degree, diameter, and distribution type for each voltage
level, rather than providing explicit degree sequences. The configurator
uses DegreeDistributionOptimizer to fit distribution parameters
and then samples degree sequences.
See Section 6 of Aksoy et al. (2018) for the synthetic input
generation guidelines.
Parameters:
seed (int or None, optional) – Random seed for reproducibility. Default is None.
Generate a degree sequence by optimizing DGLN or DPL parameters.
First calls DegreeDistributionOptimizer.optimize() to find the
distribution parameters matching avg_degree and max_degree,
then samples n_nodes degrees from the resulting PDF.
Parameters:
n_nodes (int) – Number of nodes in the same-voltage subgraph.
avg_degree (float) – Target average degree \(\bar{d}\).
max_degree (int) – Maximum degree \(d_{\max}\) (PDF support is 1..max_degree).
dist_type (str) – 'dgln' for generalized log-normal or 'dpl' for power law.
'max_k' (int, optional): maximum degree (default:
min(n-1,50)). For 'dpl' the paper suggests
\(d_{\max} \approx 1.517\,n^{1/4}\); for 'dgln'\(\bar{d} \approx 2.425 \pm 0.185\) is consistent across
subgraphs (Section 6.1 of Aksoy et al., 2018).
inter_connections (dict) –
Mapping (i,j)->config for transformer connections.
Config is either:
Assigns active power loads (PL) to load buses in the grid.
Implements the load-setting methodology from Elyas et al. (2017), which
mirrors the generation-capacity approach in CapacityAllocator:
Total load: Compute an aggregate load target, either from a
deterministic scaling formula or as a fraction (light / medium / heavy)
of the total installed generation capacity.
Individual loads: Sample N_l individual loads from an exponential
distribution (with ~1% super-large outliers).
Correlated assignment: Assign loads to load buses using a 14x14
empirical 2D probability table (Tab_2D_load) that encodes the
joint distribution of normalized load demand and normalized nodal
degree.
Reference systems with pre-computed Tab_2D_load tables are available
for NYISO-2935 (id=1), WECC-16944 (id=2), and a third system (id=3).
A heuristic diagonal-bias table (id=0) is provided as a fallback.
Parameters:
graph (nx.Graph) – NetworkX graph with 'bus_type' and (for non-deterministic loading
levels) 'pg_max' node attributes already set.
ref_sys_id (int) – Reference system for statistical tables (0=heuristic, 1=NYISO-2935,
2=WECC-16944, 3=additional reference).
Return the Tab_2D_load table for the selected reference system.
The table is a 14x14 matrix representing the empirical joint PDF
Pr((P_bar_ln,k_bar_n)inA) discretized into 14 load-demand
classes (rows, low-to-high) and 14 nodal-degree classes (columns,
low-to-high). When ref_sys_id=0, a heuristic table with
Gaussian-decay diagonal bias exp(-0.5*|r-c|) is generated
instead.
Sample individual load demands from the empirical distribution.
Mirrors CapacityAllocator._initial_generation_distribution().
More than 99% of load demands follow an exponential distribution;
~1% are replaced by super-large outliers drawn uniformly from
[max(P),3*max(P)]. If the sum deviates more than 5% above
or 10% below total_load, all values are rescaled proportionally.
Parameters:
total_load (float) – Target aggregate load (MW).
Returns:
p_loads (np.ndarray) – Raw (possibly rescaled) load values, shape (N_l,).
max_r_pl (float) – Maximum load value, used for normalization.
normalized_r_pl (np.ndarray) – Loads normalized to [0, 1] by dividing by max_r_pl.
Generative model for an entire power grid graph on k voltage levels.
Implements Algorithm 4 (CLCStars) from Aksoy et al. (2018).
Phase 1 generates each same-voltage subgraph via the CLC model,
and Phase 2 inserts transformer edges via the random-star model.
Parameters:
seed (int or None, optional) – Random seed for reproducibility. Default is None.
Generate same-voltage subgraphs (Phase 1) for all k levels.
For each voltage level, runs Preprocessor.run_setup() followed
by EdgeCreator.generate_edges(), converting local node IDs to
global IDs using cumulative offsets.
Parameters:
k (int) – Number of voltage levels.
degrees_by_level (list of list of int) – Desired degree sequences, one per voltage level.
diameters_by_level (list of int) – Desired diameters, one per voltage level.
level_offsets (list of int) – Mutated in place — filled with the global node-ID offset for
each level.
level_node_counts (list of int) – Mutated in place — filled with the actual node count (after
degree-sequence inflation) for each level.
all_edges (dict) – Mutated in place — {(u,v):{'type':'line'}} entries are
added for each generated edge.
Example
1# Level 0 generated (5 nodes) -> Local IDs: 0, 1, 2, 3, 42level_offsets[0]=03current_global_offsetbecomes545# Level 1 generated (3 nodes) -> Local IDs: 0, 1, 26# We shift them by current_global_offset (5):7# Global IDs: 5, 6, 78level_offsets[1]=59current_global_offsetbecomes8
Generate a multi-level power grid graph (CLCStars, Algorithm 4).
Runs Phase 1 (CLC for each voltage level) and Phase 2 (random-star
transformer edges for each pair of levels), then combines results.
Parameters:
degrees_by_level (list of list of int) – Desired degree sequences \(\mathbf{d}^{X_1},\dots,\mathbf{d}^{X_k}\),
one per voltage level.
diameters_by_level (list of int) – Desired diameters \(\delta^{X_1},\dots,\delta^{X_k}\),
one per voltage level.
transformer_degrees (dict) – Mapping (i,j)->(t_i_j,t_j_i) where t_i_j is the
transformer degree list for level-i nodes toward level j,
and t_j_i is the reverse.
keep_lcc (bool, optional) – If True, return only the largest connected component with
contiguous node IDs. Default is False.
Returns:
The generated grid graph with voltage_level node attributes
and type ('line' or 'transformer') edge attributes.
Allocate impedance and capacity limits to transmission lines.
The algorithm follows Sadeghian et al. (2018) and the SynGrid
MATLAB toolbox (sg_line.m, sg_flow_lim.m):
Impedance generation — magnitudes \(Z\) from
LogNormal(\(\mu\), \(\sigma\)), angles \(\varphi\)
from a Lévy stable distribution
\(S(\alpha_s, \beta_s, \gamma_s, \delta_s)\). Then
\(X = Z \sin\varphi\), \(R = Z \cos\varphi\).
DCPF-based swapping — sort impedances ascending and flows
descending, then randomly swap ~20–30 % of assignments to
introduce variance while preserving the negative correlation
between impedance and flow.
(Optional) Topology refinement — iteratively add
low-impedance lines between max angle-difference bus pairs
and remove weak high-\(X\) lines until the angle spread
is below a size-dependent threshold.
Capacity assignment — gauge ratios
\(\beta_l = F_l / F_l^{\max}\) from
Exponential(\(\mu_\beta\)) with overload injection;
assigned via 2-D table Tab_2D_FlBeta.
Capacity limits: \(F_l^{\max} = F_l / \beta_l\).
Parameters:
graph (networkx.Graph) – Power grid graph with nodal generation/load attributes.
Assign gauge ratios to lines via 2-D bin matching.
Uses the empirical PMF Tab_2D_FlBeta to reproduce the joint
distribution \(f(\bar{F}_l, \beta_l)\) from the reference
system. Lines are sorted by normalised flow and betas by value,
then paired randomly within matching bins (high-to-low traversal).
Generate gauge ratios from an exponential distribution.
Draws \(\beta \sim \mathrm{Exp}(\mu_\beta)\) and resamples
values exceeding 1.0. A fraction overload_b of lines are then
injected with \(\beta \in (1.0, 1.2]\) to model bottleneck
/ overloaded lines (Sadeghian et al., 2018, Sec. IV).
Generate line angles from a Lévy stable distribution.
Draws \(\varphi \sim S(\alpha_s, \beta_s, \gamma_s, \delta_s)\)
and clips to \([0.01, 89.99]\) degrees. Out-of-range samples
are resampled up to 20 times before a hard clip.
Refine grid topology to reduce phase-angle spread.
Iteratively tightens the electrical diameter of the network
(ported from sg_flow_lim.m):
Compute the DCPF and measure
\(\Delta\theta_{\max} = \max(\theta) - \min(\theta)\).
If \(\Delta\theta_{\max} > TT + 2\) with
\(TT = 10^{0.3196 \log_{10} N + 0.8324}\),
add a low-impedance edge between the bus pair with the
largest angle difference.
Remove a random high-\(X\) edge (top 20 %) whose
end-point degrees are both \(\ge 3\), preserving
graph connectivity.