Technical manual — chapter list

Protections

A generic protective-device layer: RoCoF / Loss-of-Mains, under/over-frequency, df/dt, low-frequency demand disconnection (LFDD), under/over-voltage, thermal-overload, and topology auto-reclosers. It is deliberately not a protection simulator — real relay settings are unobtainable — so settings are generic and [VERIFY]-tagged, grounded only where the GDA event-detector data supports it. See docs/manual/18-extending-gridsim.md for the authoring recipe and docs/technical/13-asset-dynamics-data-grounding.md for the grounding ledger.

The abstraction (src/GridSim.Core/Assets/Protection/)

A protection is a generalisation of the inverter self-protection block in GridFollowingInverter (RoCoF dwell + sub-cycle uplift, vector-shift, LVRT). It reuses the asset-dynamics seam wholesale.

  • ProtectionStage — one definite-time stage: a MonitoredQuantity (Frequency, Rocof, Voltage, LoadingFraction, PhaseShift) crossing a Threshold in a ThresholdSense (Above/Below), held for DwellSeconds, drives a ProtectionAction. RoCoF-LoM is {Rocof, Above, rocofTripHzPerS, rocofTripConfirmS, TripInfeed}.
  • ProtectionAction — maps onto the channels AssetActuation already exposes, so no integrator change: TripInfeedTrippedInfeedMw (lost infeed folded into the swing deficit); ShedDemand → a positive ActivePowerMw step (LFDD: load removed reduces the deficit); InjectStep → a signed swing-power step; OpenBranch/RestoreBranch → a topology request (the recloser; §Topology).
  • ProtectionEvaluator — the extracted, quantity-agnostic dwell/edge state machine: accumulate time over threshold, emit Fired once when the dwell is met, and (if a finite AutoRestoreDelayS) Restored after it. Reuses the inverter's MeasurementDelay for relay latency and the phase-window vector-shift proxy.
  • ProtectionController : IActiveAssetDynamics — builds a stage per present class parameter, drives one evaluator each, and aggregates the result. Contributes exactly 0 MW until a stage operates. Tripped reflects an infeed-trip or branch-open only (a demand-shed operates but is not a "tripped unit").

Placement and JSON

Device description is an AssetClass of kind: "protection" in asset-classes.json (reusing the [VERIFY] provenance validation). Placement is a separate protections.json{name, class, monitorBus, capacityMw, targetBranch?} — because a protection needs an explicit location and the MW it acts on, which fuel-type binding cannot express. Both are optional and null-gated via GridModel.Protections (ProtectionModel = library + placed ProtectionInstances). The fleet builds a ProtectionController per instance in AssetDynamicsFleet.Build.

Canonical stage parameters (AssetParams): rocofTripHzPerS+rocofTripConfirmS+localRocofUpliftFactor, ufTripHz/ofTripHz (+dwell), dfDtTripHzPerS, uvTripPu/ovTripPu (+dwell), overloadTripFraction, lfddStageHz+lfddBlockMw+lfddDwellS, vectorShiftTripDeg, lvrtVoltagePu, autoRecloseDelayS.

Two integration tiers

Tier A — frequency/injection domain (grounded). RoCoF/LoM, U/O-frequency, df/dt, and dynamic LFDD plug into the existing AssetDynamicsFleet as extra active controllers — the integrators (AssetFrequencyDynamics, AssetAwareFrequencyIntegrator) are untouched. Dynamic LFDD is the headline: a ShedDemand stage fires when the simulated frequency crosses 48.8 Hz and injects +lfddBlockMw, turning the previously-scripted LFDD RecordedAction into an endogenous, closed-loop protection (it must replace the scripted step, not run alongside it — the ExogenousLossEvents split avoids double-counting).

Tier B — voltage/loading domain. U/O-voltage and thermal-overload read AssetStepContext.LocalVoltagePu / LoadingFraction, which the pure lumped COI model does not carry (defaults 1.0 / 0.0) — so they are inert without a network solve (a guard test codifies this). LiveSim feeds the solved per-bus voltage and per-bus max-incident-branch loading via AssetAwareFrequencyIntegrator.SetNetworkState (held constant across the fast sub-steps within a tick). StepCoi's optional voltageOf/loadingOf suppliers default to null, preserving the batch reduction gate.

Topology recloser and islanding (the network work)

A recloser class (has autoRecloseDelayS) turns its trip stages into OpenBranch actions targeting targetBranch; the controller tracks its open branches and AssetDynamicsFleet.OpenBranches() aggregates them. The network layer:

  1. Branch.InService (default true) — YBus.Build/BuildSparse, BackwardForwardSweep, and NewtonShell.ComputeLineFlows skip an out-of-service branch. With every branch in service the admittance accumulation order is unchanged, so the solve is bit-identical to before (pinned by the power-flow suite before any topology feature is exposed).
  2. IslandPartition.Prepare — union-find over in-service branches finds the connected components; per island: keep it if it has a slack; elect the largest-generator bus as reference if it has generation but no slack; black it out (drop its buses/branches, book the lost load/gen) if it has no generation. A single connected component with a slack (everything in service) is a pure no-op.
  3. TopologyOps.Apply — marks the recloser-opened branches out of service and runs the partition.
  4. LiveSim — applies the fleet's open branches before solving, feeds the blackout net-load (LostLoadMw − LostGenMw) to the swing model as a one-off perturbation (load lost = surplus = frequency up), and reclose reverses it. Auto-reclose restores the branch after the delay and the next tick re-solves.

Grounding

Grounded from the GDA event-detector layer (Events/{rocof_events,lfdd_risk}): RoCoF/LoM grades (0.0625/0.125/0.25/0.5 Hz/s; modern ALoMCP 1.0 Hz/s + 500 ms delay) DerivedFromRecorded/Recorded; LFDD first stage 48.8 Hz (Recorded) and the 632 MW 2019 block (DerivedFromRecorded); statutory U/O-frequency band. Ungrounded → [VERIFY]: recloser/auto-reclose/breaker operation, measured voltage, thermal loading, and fault level — none exist in the lake. Emitter: GDA NetworkModel/build_protections.py, wired into assemble/run.py.

Invariants (pinned by tests)

  • Frequency reduction gate — no protections.json (or a never-operating fleet) ⇒ byte-identical swing trajectory (ProtectionTests).
  • Power-flow reduction gate — all branches in service ⇒ bit-identical solve (IslandPartition no-op; the power-flow suite).
  • Voltage/thermal inertness — provably inert in the pure lumped model until the context feed supplies the quantity (ProtectionVoltageTests).
  • [VERIFY] discipline — every unmeasurable setting is CuratedPlaceholder with the literal token.

Key files

Assets/Protection/{ProtectionElement,ProtectionEvaluator,ProtectionController,ProtectionModel}.cs, IO/Json/ProtectionDto.cs, PowerFlow/{IslandPartition,TopologyOps}.cs, Model/Model.cs (Branch.InService, GridModel.Protections), Simulation/LiveSim.cs (context feed + topology), PowerFlow/YBus.cs + NewtonShell.cs + BackwardForwardSweep.cs (InService skip). Tests: ProtectionTests, ProtectionEvaluatorTests, ProtectionVoltageTests, IslandPartitionTests, ProtectionRecloserTests.