From 19220d799d1fec3238c50d15d35884b9cf8354bf Mon Sep 17 00:00:00 2001 From: lukelowry Date: Fri, 1 May 2026 15:09:26 -0500 Subject: [PATCH] Design of events --- GridKit/Model/PhasorDynamics/Branch/README.md | 53 ++++++-- GridKit/Model/PhasorDynamics/Bus/README.md | 26 +++- GridKit/Model/PhasorDynamics/EVENTS.md | 115 ++++++++++++++++++ application/PhasorDynamics/README.md | 101 ++++++++++++--- 4 files changed, 265 insertions(+), 30 deletions(-) create mode 100644 GridKit/Model/PhasorDynamics/EVENTS.md diff --git a/GridKit/Model/PhasorDynamics/Branch/README.md b/GridKit/Model/PhasorDynamics/Branch/README.md index 2f6ed7e1a..4b1608e7e 100644 --- a/GridKit/Model/PhasorDynamics/Branch/README.md +++ b/GridKit/Model/PhasorDynamics/Branch/README.md @@ -20,10 +20,10 @@ provides more flexibility for modeling. Symbol | Units | Description | Note ------------|---------|---------------------------------| ------ -$R$ | [p.u.] | Branch series resistance | -$X$ | [p.u.] | Branch series reactance | -$G$ | [p.u.] | Branch shunt conductance | -$B$ | [p.u.] | Branch shunt susceptance | +$R$ | [p.u.] | Branch series resistance | +$X$ | [p.u.] | Branch series reactance | +$G$ | [p.u.] | Branch shunt conductance | +$B$ | [p.u.] | Branch shunt susceptance | ### Model Derived Parameters Note the difference between little-g and big-G, little-b, big-B in these equations. @@ -81,10 +81,43 @@ None. \end{aligned} ``` -# Model Outputs +## Actions + +This component accepts the following runtime events via `apply(Action)`. See +[EVENTS.md](../EVENTS.md) for the dispatch model and JSON schema. + +JSON `"action"` | Params | Effect +----------------|---------------------|---------------------------------------------------------------------- +`"open"` | none | Sets line status $S = 0$ (line tripped). +`"close"` | none | Sets line status $S = 1$ (line in service). +`"fault"` | `R`, `X`, `percent` | Stores the fault impedance and fractional position $f = \text{percent}/100$ along the line from `bus1`, and engages the fault ($U = 1$). +`"clear"` | none | Disengages the fault ($U = 0$). Stored impedance is unchanged. + +The line status $S$ is implemented as a mask (0 or 1) on every +branch residual contribution, so the Jacobian sparsity pattern is fixed +across `Open`/`Close` cycles. + +While the fault is engaged, the line is split at the fault point into two +series segments with admittances + +``` math +y_{1m} = \dfrac{g + jb}{f}, \quad y_{m2} = \dfrac{g + jb}{1 - f} +``` + +connected by a shunt fault admittance $y_f = 1/(R_f + jX_f)$ to ground at +the fault point, where $g$, $b$ are the unfaulted line series admittance +parameters and $R_f$, $X_f$ are the fault impedance from the action payload. +The Norton-equivalent two-port admittance of this augmented network replaces +the unfaulted line series contribution to the residuals at `bus1` and `bus2`. +The line charging admittances $G/2$, $B/2$ at both terminals are unchanged. + +The fault contribution is itself multiplied by the line status $S$: an open +line carries no fault current regardless of $U$. + +## Model Outputs Real and imaginary current at the branch's two buses -are model variables of the branch model: $I_{r1}$, $I_{i1}$, $I_{r2}$, +are model variables of the branch model: $I_{r1}$, $I_{i1}$, $I_{r2}$, and $I_{i2}$. Current is oriented leaving the branch (i.e. entering the bus). @@ -96,7 +129,7 @@ Current magnitude $I_{m1}$ and $I_{m2}$ are the phasor magnitude of the current. \end{aligned} ``` -Active and reactive power ($P_1$, $Q_1$, $P_2$, and $Q_2$) +Active and reactive power ($P_1$, $Q_1$, $P_2$, and $Q_2$) are the real and imaginary parts of the complex power at each end of the branch, where the complex power is defined as $S=VI^{\ast}=(V_r + j V_i)(I_r - jI_i)$ ``` math @@ -112,7 +145,7 @@ Real and reactive power are oriented leaving the branch (i.e. entering the bus). # Transformer Branch Model -**Note: Transformer model not yet implemented** +> **Note: Transformer model not yet implemented** The branch model can be created by adding the ideal transformer in series with the $`\pi`$ circuit as shown in Figure 2 where $`\tau`$ is a tap ratio @@ -121,8 +154,8 @@ $`N = \tau e^{j \theta}`$.
- - + + Figure 2: Branch equivalent circuit
diff --git a/GridKit/Model/PhasorDynamics/Bus/README.md b/GridKit/Model/PhasorDynamics/Bus/README.md index 2d61eed2f..c26a2986e 100644 --- a/GridKit/Model/PhasorDynamics/Bus/README.md +++ b/GridKit/Model/PhasorDynamics/Bus/README.md @@ -17,7 +17,7 @@ sign.
- + Figure 1: Needs to be changed to represent current balance instead of power balance.
@@ -28,3 +28,27 @@ sign. **Other Parameters** Buses are uniquely defined by their ID (number or name). Besides, each bus should have associated Nominal Voltage value. + +## Actions + +This component accepts the following runtime events via `apply(Action)`. See +[EVENTS.md](../EVENTS.md) for the dispatch model and JSON schema. + +JSON | Params | Effect +----------------|----------|-------------------------------------------------------------------- +`"fault"` | `R`, `X` | Stores the fault impedance and engages the fault ($U = 1$). +`"clear"` | none | Disengages the fault ($U = 0$). Stored impedance is unchanged. + +While the fault is engaged, the bus current balance gains the contributions: + +``` math +\begin{aligned} + G_f &= \dfrac{R}{R^2 + X^2}, \quad B_f = -\dfrac{X}{R^2 + X^2} \\ + \Delta I_{rk} &= U(-G_f V_{rk} + B_f V_{ik}) \\ + \Delta I_{ik} &= U(-B_f V_{rk} - G_f V_{ik}) +\end{aligned} +``` + +The `percent` parameter of `Fault` is ignored by `Bus`. The fault gate $U$ +is implemented as a mask (0 or 1) on the fault residual term, +so the Jacobian sparsity pattern is fixed across `Fault`/`Clear` cycles. diff --git a/GridKit/Model/PhasorDynamics/EVENTS.md b/GridKit/Model/PhasorDynamics/EVENTS.md new file mode 100644 index 000000000..91dec8518 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/EVENTS.md @@ -0,0 +1,115 @@ +# Phasor Dynamics Events + +## Overview + +This document describes the runtime event system for PhasorDynamics +components. A runtime event mutates the state of a single component during simulation. Events are scheduled in a solver file +(see [PDSim](../../../application/PhasorDynamics/README.md)) and delivered to components by `SystemModel` at their scheduled times. Buses are addressed for events by their existing `number`; devices by their +existing `id`. + +## Event + +An event is a triple of fields: + +Name | Description +---------|------------------------------------------------------ +`time` | Simulation time at which the event fires, in seconds +`target` | Routing key for the component receiving the event +`action` | The mutation to perform, drawn from the action vocabulary + +For buses, the `target` is the bus's `number` rendered as a string +(e.g. `"1001"`). For devices, it is the device's `id` field from the case +file (e.g. `"BR_1001_1064_1"`). Buses and devices share a single routing +namespace; collisions between a bus number and a device id are an error at registration. + + +```cpp +struct Event { + double time; + std::string target; + Action action; +}; +``` + +## Action vocabulary + +`Action` is a `std::variant` of action structs in +`GridKit::PhasorDynamics::Actions`. Each struct has a canonical name string +exposed as `static constexpr std::string_view name`. + +C++ type | JSON name | Params | Applies to +-----------------|-----------|---------------------|---------------- +`Actions::Open` | `open` | none | `Branch` +`Actions::Close` | `close` | none | `Branch` +`Actions::Fault` | `fault` | `R`, `X`, `percent` | `Bus`, `Branch` +`Actions::Clear` | `clear` | none | `Bus`, `Branch` + +The semantics of each action are documented in the receiving component's +README. For `fault`, the `percent` parameter (position along the line as a +percent in `[0, 100]`) is used by `Branch` and ignored by `Bus`. + +## Dispatch + +`Component::apply(const Action&)` is virtual on the base class. The default +throws. Components that handle events override it with a `std::visit` over +the variant, dispatching to per-action handlers and falling through to a +generic catch-all for unhandled action types: + +```cpp +void Bus::apply(const Action& a) override { + std::visit(overloaded{ + [this](const Actions::Fault& f) {}, + [this](const Actions::Clear&) {}, + [this](const auto& other) { + using A = std::decay_t; + throw std::runtime_error("Bus does not handle action '" + std::string(A::name) + "'"); + } + }, a); +} +``` + +`SystemModel::apply(const Event&)` looks up the target by string id and +forwards the action to the component. + +## Schedule + +The `schedule` field of the solver file is an array of events. The parser +stable-sorts the schedule by `time` on read. If the input was not already +sorted, an info line is logged and parsing continues. + +Multiple events at the same time are applied sequentially in listing order in a single re-init of the integrator (`IDACalcIC`). When two same-time events conflict on a single target, the last-listed event wins. + +The same-time grouping uses exact `double` equality on `time`. Both sides of the comparison are read from the parsed event records, so events with the same JSON literal compare equal. + +## Example schedule + +A schedule that faults bus `2` at `t=1.0`, clears the fault at `t=1.1`, and +simultaneously opens line `L23` at `t=1.1`: + +```json +"schedule": [ + { "time": 1.0, "target": "2", "action": "fault", "params": { "R": 0.0, "X": 1.0e-5 } }, + { "time": 1.1, "target": "2", "action": "clear" }, + { "time": 1.1, "target": "L23", "action": "open" } +] +``` + +## Errors + +Failure | Source | Message +-------------------------------|---------------------|-------- +Unknown target id | `SystemModel` | `No event target with id ''` +Unhandled action for component | `Component::apply` | `Component '' does not handle action ''` +Unknown action string | parser | `unknown action '' (valid: open, close, fault, clear)` +Missing required `params` | parser | `action '' requires params field with ` +Schedule entry past `tmax` | parser | `schedule entry at t= exceeds tmax=` + +## Adding a new action + +1. Add a struct to `GridKit::PhasorDynamics::Actions` with a + `static constexpr std::string_view name` and any payload fields. +2. Add the struct to the `Action` variant alias. +3. Add a parser case mapping the JSON name to the struct. +4. Override `apply` in the receiving component(s) to handle the new action. +5. Add a row to the action vocabulary table above and an entry in each + receiving component's README Actions section. diff --git a/application/PhasorDynamics/README.md b/application/PhasorDynamics/README.md index c3da80264..8178146f9 100644 --- a/application/PhasorDynamics/README.md +++ b/application/PhasorDynamics/README.md @@ -1,25 +1,88 @@ -# Input file for GridKit phasor dynamics application +# PDSim (Phasor Dynamics Simulation) -## Root elements - Name | Value - ---------------------|------------------------------------------------------- - `system_model_file` | Path to the system model file[^1] - `dt` | A floating point value for time step size - `tmax` | A floating point value for max time - `events` | An array of event groups (see [Events](#events) below) - `output_file` | Path to output (CSV) file - `reference_file` | A string containing the name of the case - `error_tolerance` | A string containing the name of the case +PDSim runs a phasor-dynamics simulation defined by a *solver file* +(`*.solver.json`) and a *case file* (`*.case.json`). The case file specifies +the system model, and the solver file specifies the simulation enviornment, including +any runtime events scheduled during the simulation. -[^1]: See system model [case format](../../Model/PhasorDynamics/INPUT_FORMAT.md) +The runtime event system (action vocabulary, dispatch model, schedule +semantics) is documented in [`Model/PhasorDynamics/EVENTS.md`](../../GridKit/Model/PhasorDynamics/EVENTS.md). -## Events +## Usage -Each event group describes a system event that occurs at a given time point +``` +pdsim +``` - Name | Value - --------------------|------------------------------------------------------- - `time` | A floating point value for time event occurs - `type` | Event type (one of { "fault_on", "fault_off" }) - `element_id` | An integer value referencing the element associated with the event (e.g., bus fault id) +Relative paths in `case_file`, `output_file`, and `reference_file` are +resolved against the solver file's directory. Absolute paths are used as +given. + +## Solver file format + +### Top-level fields + +Name | Required | Description +------------------|----------|------------------------------------------------------ +`format_version` | no | Format version integer. Default `1`. Parser rejects unknown versions. +`case_file` | yes | Path to the case file +`dt` | yes | Reporting time step in seconds +`tmax` | yes | End time in seconds +`schedule` | no | Ordered array of events (see below). May be empty or omitted. +`output_file` | no | Path to monitor output (CSV) +`reference_file` | no | Path to reference CSV for validation +`error_tolerance` | no | Maximum allowed error vs reference. Default `1e-4`. + +### Schedule + +The `schedule` field is an array of events. Each event has the shape: + +Name | Required | Description +---------|--------------------|------------------------------------------------------ +`time` | yes | Simulation time at which the event fires, in seconds. Must be `≤ tmax`. +`target` | yes | Routing key of the component receiving the event (bus `number` or device `id`) +`action` | yes | Action name; one of `open`, `close`, `fault`, `clear` +`params` | iff action carries | Action payload (e.g. `{ R, X, percent }` for `fault`) + +The action vocabulary, dispatch model, schedule canonicalization (stable +sort by time, last-listed wins on conflicts), and same-time semantics are +documented in +[`EVENTS.md`](../../GridKit/Model/PhasorDynamics/EVENTS.md). + +## Output and validation + +If `output_file` is given, it sets the destination for the case file's +default CSV monitor sink. Other monitor sinks declared in the case file +are unaffected. Output formatting (channels, delimiter) is controlled by +the case file's monitor declarations. + +If both `output_file` and `reference_file` are given, PDSim compares the +output against the reference and exits with status `0` if the maximum +error is within `error_tolerance`, `1` otherwise. + +## Example + +```json +{ + "format_version": 1, + "case_file": "texas.json", + "dt": 0.00416666666666, + "tmax": 20.0, + "schedule": [ + { "time": 10.0, "target": "1001", "action": "fault", + "params": { "R": 0.0, "X": 1.0e-5 } }, + { "time": 10.15, "target": "1001", "action": "clear" }, + { "time": 10.15, "target": "BR_1001_1064_1", "action": "open" } + ], + "output_file": "texas.csv", + "reference_file": "texas.ref.csv", + "error_tolerance": 1.0e-4 +} +``` + +This run faults bus `1001` at `t=10`, clears the fault and trips line +`BR_1001_1064_1` simultaneously at `t=10.15` (a 9-cycle fault followed by +isolation of the faulted equipment), and integrates to `t=20`. The two +events at `t=10.15` batch into one IDA re-init. Output is compared against +the reference file with tolerance `1e-4`.