Formulas: from the equation in the source to the function in the code¶
Every operation in the package that appears in a paper or a textbook gets one named function,
even when it is called once, so a reader can go from an equation to the code and back. This
catalogue is the index: one row per formula, the function that implements it, the equation in
plain text, the source with a key from REFERENCES.md, and where it is used.
A formula is a pure function in formulas/, cited here once, and the rest of the package calls it
rather than repeating the expression. A new formula function gets its row here in the same change.
How a formula function is written¶
def swing_zscore(nx: np.ndarray, prev: np.ndarray, scale_t: np.ndarray, metered: np.ndarray) -> np.ndarray:
"""The one-step injection change as a z-score of the bus's typical recent change [FED26]:
spikes (Aq, Al) read large, the slow ramp and benign scans stay near 1.
swing[b] = ([P_inj, Q_inj](t) - [P_inj, Q_inj](t-1)) / scale_t[b]
nx, prev : [N, 4] current and previous scan; scale_t : [N, 2] from recent_change_scale
metered : [N] bool, buses with an injection meter
returns : [N, 2] float32, dimensionless
"""
sw = np.zeros((nx.shape[0], 2), np.float32)
sw[metered, 0] = (nx[metered, 1] - prev[metered, 1]) / scale_t[metered, 0]
sw[metered, 1] = (nx[metered, 2] - prev[metered, 2]) / scale_t[metered, 1]
return sw
The rules: the name is the operation, not the caller; the first line of the docstring names the quantity and cites the key with its equation number; the equation follows in plain text; every argument has its shape and unit; the function is pure (numpy in, numpy out, no I/O, no dataset objects); one test checks it on a case small enough to verify by hand.
Catalogue¶
| formula | function | equation | source | used by |
|---|---|---|---|---|
| regional subspace prior | formulas.federated.block_diagonal_basis, through federated.RegionalPrior |
V[cols_k, block k] = V_k from each client's own whitened_svd_basis, zero elsewhere; orthonormal for disjoint column sets |
[EST26], [FED26] | the federated fit of the estimator's prior |
| federated average | formulas.federated.fedavg |
θ = Σ_k (n_k / n) θ_k, in float64, cast back; one client returns its own tensor | [MCM17] | the federated localizers |
| the buses of each client | formulas.federated.interior_boundary, cut_edge_count, attackable_affinity, hop_distance, halo_nodes, through federated.spectral_partition and compute_nodes |
interior: every neighbour in the same client; cut edges: ends in different clients; W = A ⊙ √(m mᵀ), m = heavy at attackable buses; halo: other clients' buses within h hops, own buses first | [VLX07], [FED26] | the federated partition |
| pooled channel moments | formulas.federated.channel_moments, pool_moments, through localization.learned.standardization |
f_a = n_a / n, f_b = n_b / n, mean = mean_a + (mean_b − mean_a) f_b, var = f_a var_a + f_b var_b + (mean_b − mean_a)² f_a f_b; one part returns unchanged | [CGL79] | the learned localizers' standardization, pooled across federated clients |
| per-bus rates and average precision | formulas.metrics.perbus_rates, average_precision, through LocalizerBase.score_perbus and LearnedLocalizer.score_grid |
F1 = 2TP / (2TP + FP + FN), DR = TP / (TP + FN), FR = FP / (FP + TN); AP = Σ (R_n − R_{n−1}) P_n over distinct thresholds, highest first | [KEC25], [DG06] | the federated paper's node-wise table and grid detection |
| per-bus confusion counts and the papers' threshold | formulas.metrics.perbus_counts, perbus_f1_from_counts, tau_from_counts, through LearnedLocalizer.tune_threshold |
F1 = 2TP / (2TP + FP + FN); tau = argmax over the grid of the mean per-bus F1 on the active buses | [KEC25], [FED26] | the learned localizers' validation threshold, summable across clients |
| swing z-score and temporal delta | formulas.temporal.temporal_delta, formulas.temporal.swing_zscore, through timeline.write_temporal_layers (each observed frame against the previous emitted frame, after the walk) |
delta_t = z_t − z_{t−1} at injection-metered buses; swing = delta_t / scale_t | [FED26] | the swing and temporal_delta features, SwingThreshold, DeltaThreshold |
| recent-change scale | formulas.temporal.recent_change_scale (over the observed frames, through timeline.write_temporal_layers) |
scale_t = std over [t − W, t) of the one-step change, plus 1e-3 | [FED26] | the swing feature |
| bus voltage phasors | formulas.network.complex_voltages |
V = |V| e^{jθ} | [AE04, eq. 2.1] | every AC evaluation |
| bus injections | formulas.network.bus_injections (numpy); se.base.SEBase._h_t is its torch twin, kept for callers that differentiate through it and pinned by tests/test_formulas.py |
S = V ∘ conj(Y V) | [AE04, eq. 2.6] | the estimator's h(x) |
| AC measurement function | formulas.network.ac_measurement, called by SEBase._h |
h = [|V|, −Re S, −Im S, θ, Re S_f, Im S_f] | [AE04, eqs. 2.6, 2.8] | every estimator, JacobianFeatures.delta_z |
| AC measurement Jacobian | formulas.network.ac_jacobian (pandapower's dSbus_dV and dSbr_dV), called by SEBase._jacobian (the chord Jacobian in fit), pinned to the autograd Jacobian by tests/test_formulas.py |
∂S/∂θ = j diag(V) conj(diag(I) − Y diag(V)); ∂S/∂|V| = diag(V) conj(Y diag(V/|V|)) + conj(diag(I)) diag(V/|V|); the same for S_f with C_f and Y_f | [AE04, ch. 2], [MP19, dSbus_dV, dSbr_dV] | every estimator |
| branch flows | formulas.network.branch_flows; called by engine.measurement.emit_from_state, clean_flows_from_states and dataset._clean_flows_full |
S_f = V_from ∘ conj(Y_f V) | [AE04, eq. 2.8] | every emitted flow meter, the clean flow layers, the estimator's h(x) |
| branch admittances | formulas.network.branch_admittances; called by dataset._admittances (ybus, yf, yt) |
y_tt = y_s + (g + jb)/2, y_ff = y_tt / (t conj t), y_ft = −y_s / conj t, y_tf = −y_s / t; shunts on the diagonal | [MP19, makeYbus] | the loader's admittance matrices |
| series admittance | formulas.network.series_admittance, called by FdiaGenerator._branch_physics and branch_admittances |
y_s = 1 / (r + jx), zero when the branch has no impedance | [MP19, branch model] | the static edge physics |
| accuracy-class error split | formulas.noise.bias_jitter_split, called by FdiaGenerator.__init__ |
bias² + jitter² = SD², jitter = 0.25 SD | [ASP14], the jitter fraction set in this package | every emitted measurement |
| ramp profile | formulas.attacks.ramp_profile, through engine.attacks.episodes.ramp_dev |
dev(i) = rate_up·i for i < rise; peak on the hold; max(0, peak − rate_down·(i − rise − hold)) after | [DAT26] | the At family (the overload Am follows its own goal, overload.overload_goal) |
| attacker's subnetwork | formulas.network.subnetwork, engine.attacks.area.AreaMixin.local_region |
the buses within hops branches of the seeds (never the slack, grown over zero-injection boundary buses) and their boundary |
[WU26] | every stealthy family |
| local false state | formulas.network.local_ac_solve, engine.attacks.false_state.FalseStateMixin.solve_local |
S_i(V) = V_i conj(Σ_j Y_ij V_j) = S_target_i on the interior, every other V held true; Newton on [θ_I, |V|_I], each step halved until the mismatch drops (the full step first, so a plain-Newton iteration is unchanged) | [WU26], [AE04, ch. 2] | Aq, At, Al, Am |
| a scan's load at a generator bus | formulas.attacks.bus_load, through engine.physics.PhysicsMixin.true_load and scan_generation |
P_load = P_inj + s P_gen,base = s P_load,base with s = P_inj / (P_load,base − P_gen,base), the pools scaling a bus's load and generation by one factor | [DAT26] | Aq, At, Al, Am, contingency re-solves |
| a bus's scan load over its load elements | formulas.attacks.element_loads, through true_load |
P_e = P_bus(e) · P0_e / Σ P0 over the bus's elements, the pools scaling every load at a bus by one factor | [DAT26] | Aq, At, Al, Am, contingency re-solves |
| operating limits of a false state | formulas.attacks.operating_limits, generator_output, within_limits, checked by FalseStateMixin.stealthy_state |
per bus V_i^min, V_i^max and the generator limits from the case; every generator's implied output P_gen − (ΔP_inj − ΔP_load), Q_gen − ΔQ_inj within its limits, the true output recovered from the pool's common load and generation scale, ΔP_load the load change the attacker pretends; a true value already outside a limit is its own bound (the false state may not make it worse); a false state outside is rejected and its step halved | [WU26, eqs. 21-23] | Aq, At, Al, Am |
| attack vector | engine.attacks.false_state.FalseStateMixin._attack_vector, called by _stealthy_frame |
a = h(x_false) - h(x_true), the noiseless reading of the local false state minus that of the true state, added to the true scan so every meter keeps its own noise draw | [WU26] | Aq, At, Al, Am |
| open attack subspace | formulas.trust.attack_subspace, sparse_basis |
{H c : H_S c = 0} = H · null(H_S), its basis in reduced row echelon form | [WU26], [AE04, ch. 5] | the trusted-meter selectors |
| attack cost | formulas.trust.attack_cost |
the fewest meters the cheapest open attack touches: the sparsest row of the echelon basis (an upper bound on the sparsest vector of the subspace, NP-hard) | [WU26] | TrustedMeters, the DQN reward |
| sparsest attack by row reduction | formulas.trust.sparsest_rows, called by engine.attacks.rref.RrefMixin.rref_support |
reduce the transposed Jacobian to RREF, move the sparsest row's nonzero columns to the end, reduce again until the sparsest row stops getting sparser, map the rows back; eligible restricts the chased row (an attack that moves a target line) |
[WU26] Sec. IV-D1, [YAN17] | Am with support_method="rref" |
| greedy trusted meters | formulas.trust.greedy_trusted_meters, called by trust.TrustedMeters |
secure, on the cheapest open attack, the meter whose protection leaves the costliest cheapest attack; ties to the meter the most basis attacks pass through | [WU26], the row-reduction selection | TrustedMeters |
| tamper set of a stealthy frame | engine.attacks.false_state.changed_meters, called by _stealthy_frame |
the metered channels the attack vector moves: the interior's and boundary's injections and voltages, the flows on branches touching the interior | [WU26] | Aq, At, Al, Am |
| per-scan jitter sigma | formulas.noise.jitter_sigma, called by MeasurementMixin.emit_from_state |
σ_jit = jitter_v on |V|, degrees(jitter_va) on the angle, |P| jitter_pi + floor and |Q| jitter_qi + floor on injections, |P_f| jitter_pf + floor and |Q_f| jitter_qf + floor on flows | [ASP14], the rule set in this package | emission only: the per-scan noise every measurement draws |
| accuracy-class sigma of a scan | formulas.noise.accuracy_sigma, through formulas.estimation.accuracy_class_sigma (the measured calibration's rule) |
σ_acc = class_v on |V|, degrees(class_va) on the angle, class_x |reading| + floor on every power channel | [ASP14] | what a change must exceed to count as tampered, and the most a channel may move between snapshots, in the fewest-tamper search |
| tampered channels | formulas.attacks.tampered_channels, called by the fewest-tamper search |
tampered_m = metered_m and |a_m| > σ_acc,m | [WU26, eq. 12] with sub-noise changes excluded | At with min_tamper |
| tampered devices | formulas.attacks.tampered_devices, called by the fewest-tamper search |
a channel counts toward its device: a bus's SCADA terminal (injections, the flows metered at the bus, a voltage meter without PMU) or its PMU (|V|, angle, and the branch currents read at the bus) | [WU26], the device count of its results | At with min_tamper |
| pool line rating | OverloadMixin.use_line_ratings with rating_source="pool" (the default; generate_timeline(..., am_attack={"rating_margin": ...})) |
S_max_l = rating_margin × max over the pool of |S_f,l|, the noiseless from-end apparent flow of every branch (MeasurementMixin.all_flows_from_states), rating_margin = 1.25 |
the plan's D15 | Am |
| branch rating | formulas.attacks.branch_ratings, called by OverloadMixin.line_ratings (am_attack={"rating_source": "pglib"}) |
the PGLib-OPF rate_a of the row with the branch's two end buses, the smallest over parallel rows; the 9,900 MVA placeholder is no rating |
[WU26, eq. 25]; PGLib-OPF v23.07 | Am |
| flow-goal false state | formulas.network.local_flow_solve, called by FalseStateMixin.solve_flow_local |
S_i(V) = S_fixed_i at the held buses of the interior and |V_f conj(y_f · V)| = target_l on each goal branch l, the free injections (attackable loads and generators) free, least-norm Gauss-Newton steps halved until the residual drops; an active set pins a generator component at the limit (22)-(23) it would pass (in the support or on its edge), a load bus's active change at load_cap times its true load (|ΔD_i| ≤ τ D_i [YUA11]), and holds a bus voltage at its limit (21), then solves again |
[WU26, eqs. 13-14, 21-25], [YUA11] | Am |
| branch currents | formulas.network.branch_currents, called by MeasurementMixin._emit_currents and currents_from_states |
I_f = Y_f V, I_t = Y_t V; columns Re I_f, Im I_f, Re I_t, Im I_t | [WU26, eqs. 19-20], [MP19] | the hybrid meters' PMU current channels |
| PMU current sigma | formulas.noise.current_sigma, PMU_CURRENT_CLASS; paper_current_sigma for Am |
σ_re = σ_im = rel |I_end| + 1e-5 pu, rel = 0.01/3 (the whole class) or its jitter part; Am: 0.01 pu | [C37118]; [WU26, case studies] | the emitter's jitter, the fewest-tamper search (D7, D8) |
| PMU pseudo voltage | formulas.estimation.pmu_pseudo_links, pmu_pseudo_voltages, through SEBase(pmu_pseudo=True) |
V_f = (I_n − y_nn V_n) / y_nf, the mean over the links reaching f; dV_f = R(1/y_nf) dI_n − R(y_nn/y_nf) P_n (d|V_n|, dθ_n), var|V_f| = g_r C g_rᵀ, var θ_f = g_θ C g_θᵀ | [WU26, eq. (3)] | the estimators' pmu_pseudo option |
| paper noise | formulas.noise.paper_sigma, WU26_NOISE, called by the fewest-tamper search for a flow goal |
σ = 0.03 pu × base MVA on P, Q injections and flows, 0.01 pu on a PMU bus's |V| (0.03 on a SCADA voltmeter), degrees(0.01) on the angle | [WU26, case studies] | Am (D8) |
| overload target sets | OverloadMixin._target_sets, _partner, called by am_overload_design |
from one random order of the eligible lines, each line a with the first other eligible line b whose ends lie inside a's attack area (the buses within hops of a's ends), each pair once, at most AM_LINE_TRIES |
[WU26, case studies]; the plan's D17 | Am |
| overload goal | engine.attacks.overload.OverloadMixin.overload_goal |
S_{l,t} = S_true_{l,t} + (t − κ)/T (S_max − S_true_{l,κ+T}) on the noiseless reading, the rating at the last snapshot | [WU26, eqs. 24-25]; the plan's D9 | Am |
| fewest-tamper support | engine.attacks.minimize.MinimizeMixin.min_tamper, called by EpisodeDesignMixin.ramp_design and OverloadMixin.am_overload_design |
minimize over supports S in the attacker's area the devices tampered at some snapshot of the window, S held for the window and feasible at every snapshot (local solve, the goal, the operating limits, for At's load goal no metered channel moving more than its σ from one snapshot to the next, the first from the frame before the episode; Am's flow goal has no such bound, D11) and moving at least one device beyond σ, where σ is σ_acc for At's load goal (D7) and [WU26]'s own noise paper_sigma for Am's flow goal (D8, the count's threshold only, D11); proven only among converged local solves, closed over zero-injection boundary buses; smallest first from the region, proven when exhausted or at the devices the goal forces above noise |
[WU26, eq. 12] | At with min_tamper, Am with am_attack="overload" |
| WLS step | formulas.estimation.wls_step (shared weights) and wls_step_batched (per-record weights), called by SEBase._solve_plain and _w_solve |
Δc = (BᵀWB)⁻¹BᵀW (z − h(x)) | [SCH70, part II] | every estimator |
| gain matrix | formulas.estimation.normal_matrix; per record formulas.linalg.batched_normal_matrices |
G = HᵀWH | [SCH70, part II] | every estimator, the observability guard |
| weighted objective | formulas.estimation.weighted_objective, the divergence guard of SEBase._w_solve |
J = Σ w_i (z_i − h_i(x))² | [SCH70] | the robust arms |
| residual covariance | formulas.estimation.residual_covariance_diag, critical_measurements, floored_covariance, called by SEBase.fit |
Ω = R − H G⁻¹ Hᵀ; critical when Ω_ii < 1e-6 R_ii | [HAN75] | normalized residuals, ResidualRemoval |
| accuracy-class sigma | formulas.estimation.accuracy_class_sigma, called by SEBase._class_sigma (fit(calibrate="measured")) |
σ_i = c_i |z̄_i| + floor (relative), σ_i = c_i (absolute) | [ASP14] | the measurement-only calibration of the detector paths |
| normalized residual | formulas.estimation.normalized_residual, called by SEBase._nres |
r_N,i = |z_i − h_i(x̂)| / √Ω_ii | [HAN75] | Huber, residual removal, ResidualLocalizer |
| Huber weight | formulas.estimation.huber_weights, called by AdaptiveWeighting, SubspacePrior._huber_passes, JacobianWeighting |
a_i = min(1, c / |r_N,i|) | [HUB64] | AdaptiveWeighting, SubspacePrior, JacobianWeighting |
| operating-point prior | formulas.estimation.whitened_svd_basis, called by SubspacePrior._fit_states |
whiten X per coordinate, SVD, keep the leading rank_frac directions, un-whiten, QR | [EST26] | SubspacePrior, GatedPrior |
| localization gate | formulas.estimation.gate_weights, called by GatedPrior.gated_weights |
w_i ← factor · w_i for every meter incident to a flagged bus | [EST26] | GatedPrior |
| guarded inverse, condition number | formulas.linalg.guarded_inverse, condition_number (Cholesky, power and inverse iteration) |
inverse when PD and cond < 1/(100 eps), else pseudo-inverse; cond = λ_max / λ_min | numerical guards, no source | every estimator, the observability guard |
| weighted pseudo-inverse | formulas.projection.weighted_pseudoinverse |
H_W⁺ = G⁻¹HᵀW | [SCH70, part II] | Jacobian features |
| explained / unexplained split | formulas.projection.explained_unexplained, called by JacobianFeatures.transform |
dx̂ = H_W⁺ Δz, r∥ = H dx̂, r⊥ = Δz − r∥ | [JAC26] | Jacobian features, JacobianWeighting |
| leverage | formulas.projection.leverage |
diag of P = Hw G⁻¹ Hwᵀ, Hw = W^{1/2} H | [HAN75] | the leverage-weighted change feature |
| weak directions | formulas.projection.weak_directions, direction_coefficients, weak_move |
SVD of Hw; α = Uᵀ W^{1/2} Δz; V_weak V_weakᵀ dx̂ | [JAC26] | the α_weak and weak-move features |
| meter-to-bus aggregation | formulas.projection.bus_incidence, meters_to_buses |
a bus's own V, P, Q, θ channels plus the flows of its incident branches; sum for energies, max for changes | [JAC26] | the per-bus features, GatedPrior, ResidualLocalizer |
| [WU26] | states | implemented in | tested by |
|---|---|---|---|
| eq. 3 | PMU pseudo-measurements of the neighbouring voltages, from the branch currents (KKT form) | formulas.estimation.pmu_pseudo_linksformulas.estimation.pmu_pseudo_phasorsformulas.estimation.pmu_pseudo_voltagesmodels.config.SolveConfigmodels.grid.PseudoLinksmodels.grid.PseudoVoltagesse.base.SEBase.__init__se.base.SEBase._build_pseudo |
tests.test_pmu_channels.test_eq3_is_exact_without_noise |
| eq. 12 | the attack: fewest tampered measurements over the window (l0) | engine.attacksengine.attacks.episodes.RampDesigner.designengine.attacks.minimizeengine.attacks.minimize.MinimizeMixinengine.attacks.minimize.SearchSupportformulas.attacks.tampered_channelsmodels.choices.SupportMethodmodels.config.SearchSettingsmodels.config.TimelineKnobsmodels.frames.FrameKnobsmodels.frames.MinimizerResulttimeline.generate_timelinetrust.wu26trust.wu26.solve_window |
tests.test_attack_area.test_every_attack_the_search_returns_is_validtests.test_minimizetests.test_overload.test_the_search_on_a_flow_goal_equals_brute_forcetests.test_trusted_pmus.test_an_empty_schedule_costs_what_no_schedule_costs |
| eq. 13 | SCADA active injection under attack | engine.attacks.false_state.FalseStateMixin._attack_vectorengine.attacks.false_state.FalseStateMixin.generator_busesengine.attacks.minimize.MinimizeMixin.goal_statetrust.wu26 |
tests.test_overload.test_a_generator_only_support_is_valid_and_its_output_stays_in_limitstests.test_search_speed.test_the_support_attack_vector_equals_the_full_onetests.test_timeline.test_stealthy_families_pass_the_residual_test |
| eq. 14 | SCADA reactive injection under attack | engine.attacks.false_state.FalseStateMixin._attack_vectorengine.attacks.false_state.FalseStateMixin.generator_busesengine.attacks.minimize.MinimizeMixin.goal_statetrust.wu26 |
tests.test_overload.test_a_generator_only_support_is_valid_and_its_output_stays_in_limitstests.test_search_speed.test_the_support_attack_vector_equals_the_full_onetests.test_timeline.test_stealthy_families_pass_the_residual_test |
| eq. 15 | SCADA active branch flow under attack | engine.attacks.false_state.FalseStateMixin._attack_vectorengine.attacks.minimize.MinimizeMixin.goal_statetrust.wu26 |
tests.test_search_speed.test_the_support_attack_vector_equals_the_full_onetests.test_timeline.test_stealthy_families_pass_the_residual_test |
| eq. 16 | SCADA reactive branch flow under attack | engine.attacks.false_state.FalseStateMixin._attack_vectorengine.attacks.minimize.MinimizeMixin.goal_statetrust.wu26 |
tests.test_search_speed.test_the_support_attack_vector_equals_the_full_onetests.test_timeline.test_stealthy_families_pass_the_residual_test |
| eq. 17 | PMU voltage magnitude under attack | engine.attacks.false_state.FalseStateMixin._attack_vectorengine.attacks.minimize.MinimizeMixin.goal_stateengine.core.FdiaGenerator.__init__models.choices.MeterModeltimeline.generate_timelinetrust.wu26 |
tests.test_timeline.test_stealthy_families_pass_the_residual_testtests.test_trusted_pmus.test_a_trusted_pmu_keeps_its_bus_true_from_its_slot_on |
| eq. 18 | PMU voltage angle under attack | engine.attacks.false_state.FalseStateMixin._attack_vectorengine.attacks.minimize.MinimizeMixin.goal_stateengine.core.FdiaGenerator.__init__models.choices.MeterModeltimeline.generate_timelinetrust.wu26 |
tests.test_timeline.test_stealthy_families_pass_the_residual_testtests.test_trusted_pmus.test_a_trusted_pmu_keeps_its_bus_true_from_its_slot_on |
| eq. 19 | PMU branch current, real part, under attack | engine.attacks.false_state.FalseStateMixin._current_attackengine.attacks.false_state.FalseStateMixin._currents_with_attackengine.attacks.minimize.MinimizeMixin.goal_stateengine.attacks.minimize._Window.__init__engine.core.FdiaGenerator.__init__engine.measurement.MeasurementMixin.current_maskformulas.attacks.tampered_devicesformulas.network.branch_currentsformulas.noise.jitter_sigmamodels.choices.MeterModelmodels.fields.PmuCurrentFieldsmodels.grid.CurrentColumnsmodels.grid.MeterPlantimeline.generate_timelinetrust.wu26 |
tests.test_pmu_channelstests.test_pmu_channels.test_a_false_state_writes_its_currentstests.test_timeline.test_stealthy_families_pass_the_residual_test |
| eq. 20 | PMU branch current, imaginary part, under attack | engine.attacks.false_state.FalseStateMixin._current_attackengine.attacks.false_state.FalseStateMixin._currents_with_attackengine.attacks.minimize.MinimizeMixin.goal_stateengine.attacks.minimize._Window.__init__engine.core.FdiaGenerator.__init__engine.measurement.MeasurementMixin.current_maskformulas.attacks.tampered_devicesformulas.network.branch_currentsformulas.noise.jitter_sigmamodels.choices.MeterModelmodels.fields.PmuCurrentFieldsmodels.grid.CurrentColumnsmodels.grid.MeterPlantimeline.generate_timelinetrust.wu26 |
tests.test_pmu_channelstests.test_pmu_channels.test_a_false_state_writes_its_currentstests.test_timeline.test_stealthy_families_pass_the_residual_test |
| eq. 21 | bus voltage limits | engine.attacks.episodes.OverloadDesigner.designengine.attacks.false_state.FalseStateMixin._within_limitsengine.attacks.false_state.FalseStateMixin.operating_limitsengine.attacks.false_state.FalseStateMixin.stealthy_stateengine.attacks.minimize.MinimizeMixin.goal_stateengine.core.FdiaGenerator._case_limitsformulas.attacks.operating_limitsformulas.attacks.within_limitsmodels.frames.OperatingLimitstimeline._knobstimeline.generate_timelinetrust.wu26 |
tests.test_overload.test_a_voltage_is_held_at_its_limit_while_the_goal_is_met |
| eq. 22 | generator active output limits | engine.attacks.episodes.OverloadDesigner.designengine.attacks.false_state.FalseStateMixin._within_limitsengine.attacks.false_state.FalseStateMixin.operating_limitsengine.attacks.false_state.FalseStateMixin.stealthy_stateengine.attacks.false_state._clip_generatorengine.attacks.minimize.MinimizeMixin.goal_stateengine.core.FdiaGenerator._case_limitsformulas.attacks.operating_limitsformulas.attacks.within_limitsmodels.frames.OperatingLimitstimeline._knobstimeline.generate_timeline |
tests.test_overload.test_a_generator_at_its_p_limit_keeps_its_q_freetests.test_overload.test_a_generator_only_support_is_valid_and_its_output_stays_in_limits |
| eq. 23 | generator reactive output limits | engine.attacks.episodes.OverloadDesigner.designengine.attacks.false_state.FalseStateMixin._within_limitsengine.attacks.false_state.FalseStateMixin.operating_limitsengine.attacks.false_state.FalseStateMixin.stealthy_stateengine.attacks.false_state._clip_generatorengine.attacks.minimize.MinimizeMixin.goal_stateengine.core.FdiaGenerator._case_limitsformulas.attacks.operating_limitsformulas.attacks.within_limitsmodels.frames.OperatingLimitstimeline._knobstimeline.generate_timeline |
tests.test_overload.test_a_generator_at_its_p_limit_keeps_its_q_freetests.test_overload.test_a_generator_only_support_is_valid_and_its_output_stays_in_limits |
| eq. 24 | the target flow grows snapshot by snapshot | engine.attacks.episodes.OverloadDesigner.designengine.attacks.false_state.FalseStateMixin.solve_flow_localengine.attacks.minimize.MinimizeMixin.goal_stateengine.attacks.overloadformulas.network.local_flow_solvemodels.errors.NoLineRatingsmodels.frames.FlowGoalmodels.frames.LoadGoaltrust.wu26 |
tests.test_attack_area.test_delta_ratings_end_each_goal_at_its_flow_plus_the_steptests.test_attack_area.test_every_attack_the_search_returns_is_validtests.test_overload.test_the_goal_rides_on_the_true_flow_and_ends_at_the_rating |
| eq. 25 | the target flow reaches its rating S_max over the window | engine.attacks.episodes.OverloadDesigner.designengine.attacks.false_state.FalseStateMixin.solve_flow_localengine.attacks.minimize.MinimizeMixin.goal_stateengine.attacks.overloadengine.attacks.overload._scheduleformulas.attacks.branch_ratingsformulas.network.local_flow_solvemodels.errors.NoLineRatingsmodels.frames.FlowGoalmodels.frames.LoadGoaltrust.wu26 |
tests.test_attack_area.test_delta_ratings_end_each_goal_at_its_flow_plus_the_steptests.test_attack_area.test_every_attack_the_search_returns_is_validtests.test_overload.test_the_goal_rides_on_the_true_flow_and_ends_at_the_ratingtests.test_split_first.test_every_frame_labelled_attacked_carries_an_attacktests.test_timeline.test_layers_and_tamper_masks_agree |
| eq. 26 | the SE linearization with the secure and nonsecure PMU rows | engine.attacks.minimize.MinimizeMixin.min_tamper |
tests.test_trusted_pmus_solution1.test_the_first_pmu_reads_the_sparsest_attack_on_a_target_line |
| eq. 27 | the secure set h^S: a trusted PMU's own | V | and angle rows |
| eq. 28 | the attack under defense: fewest nonsecure measurements | engine.attacks.minimizeengine.attacks.minimize.MinimizeMixin.min_tamperengine.attacks.minimize._Window.costengine.attacks.rref |
tests.test_trusted_pmus.test_an_empty_schedule_costs_what_no_schedule_costs |
| eq. 29 | the trusted rows' state deviation is zero | engine.attacks.minimizeengine.attacks.minimize.MinimizeMixin.min_tamperengine.attacks.minimize._Window.__init__models.inputs.TrustablePmustrust.wu26trust.wu26.incremental_freeze |
tests.test_attack_area.test_every_attack_the_search_returns_is_validtests.test_trusted_pmus.test_a_trusted_pmu_keeps_its_bus_true_from_its_slot_on |
| eq. 30 | the nonsecure set at t | engine.attacks.minimize.MinimizeMixin.min_tamperengine.attacks.minimize._Window.__init__trust.wu26trust.wu26.incremental_freeze |
tests.test_trusted_pmus.test_trust_accumulates_from_its_slot |
| eq. 31 | trust accumulates: h^S_t = h^S_t-1 + dh^S_t | engine.attacks.minimize.MinimizeMixin.min_tamperengine.attacks.minimize._Window.__init__ |
tests.test_trusted_pmus.test_trust_accumulates_from_its_slot |
| eq. 32 | the rows one trusted PMU adds | engine.attacks.minimize.MinimizeMixin.min_tamper |
tests.test_trusted_pmus.test_a_trusted_pmu_keeps_its_bus_true_from_its_slot_on |
| eq. 33 | the defense effect: the rise in the attack's cost | trust.defensetrust.wu26_defense |
tests.test_wu_defense_env.test_the_reward_is_the_rise_in_the_search_cost |
| eq. 34 | the DQN's greedy action | trust.wu_dqn.TrustedPMUsDQN._act |
tests.test_trusted_pmus_dqn.test_algorithm_1_learns_the_pmu_that_pays |
| eq. 35 | the DQN's loss over a mini-batch | trust.wu_dqn.TrustedPMUsDQN._learn |
tests.test_trusted_pmus_dqn.test_algorithm_1_learns_the_pmu_that_pays |
| eq. 36 | the target Q value | trust.wu_dqn.TrustedPMUsDQN._learn |
tests.test_trusted_pmus_dqn.test_algorithm_1_learns_the_pmu_that_pays |
| eq. 37 | the gradient step | trust.wu_dqn.TrustedPMUsDQN._learn |
tests.test_trusted_pmus_dqn.test_algorithm_1_learns_the_pmu_that_pays |
| Alg. 1 | the DRL-based defense (training loop) | trust.wu_dqn.TrustedPMUsDQN.fit |
tests.test_trusted_pmus_dqn.test_algorithm_1_learns_the_pmu_that_pays |