Per-Unit Systems
Everything a PowerDynamics model computes is in per unit: voltages, currents, powers, impedances and time constants. The bases exist for the two moments where that is not enough β entering data that comes in SI units, and reading results back in SI units β and for attaching devices that bring their own per-unit system.
There are three primitive bases. Everything else is derived from them.
| base | unit | scope | |
|---|---|---|---|
Sbase | MVA | global | one power base for the whole network |
Οbase | rad/s | global | the frequency at which reactances were evaluated; converts SI time β pu |
Vbase | kV | per bus | the voltage level the bus' busbar belongs to |
Derived and never set directly: Ibase = Sbase/(β3Β·Vbase) [kA], Zbase = VbaseΒ²/Sbase [Ξ©] and Ybase = Sbase/VbaseΒ² [S] β the units fall out of the kV/MVA choice, since MVA/kV = kA and kVΒ²/MVA = Ξ©. fbase = Οbase/2Ο [Hz] is derived from Οbase too, but unlike those three it has a convenience setter of its own, because data sheets quote Hz.
Sbase is a three-phase power and Vbase a line-to-line voltage β the convention every data set and every power flow case you are likely to import uses. The derived bases follow from it: Ibase is the line current, so Sbase = β3Β·VbaseΒ·Ibase, and Zbase is the per-phase (star-equivalent) impedance that transmission line data is given in, so Zbase = (Vbase/β3)/Ibase. The β3 appears only in Ibase; Zbase and Ybase happen to take the same value whether you read the pair as three-phase/line-to-line or as per-phase/line-to-neutral.
Where the bases live
The bases live in the model, not in a global configuration. Every bus and every line carries a SystemBase component named systembase, holding Sbase, Οbase and the frame speed Οframe. It is added by MTKBus/MTKLine. Note that it is duplicated per bus and per line rather than shared β which is why the values have to be checked for agreement, see Consistency below.
Vbase is not on it. It belongs to the bus' BusBar, and each of a line's two LineEnds carries its own.
bus 1 line 1β2 bus 2
ββββββββββββββββββββββββ βββββββββββββββββββββββββββββ ββββββββββββββββββββββββ
β SystemBase β β SystemBase β β SystemBase β
β Sbase Οbase Οframe β β Sbase Οbase Οframe β β Sbase Οbase Οframe β
β (reads global) β β (reads global) β β (reads global) β
ββββββββββββββββββββββββ€ βββββββββββββββ¬ββββββββββββββ€ ββββββββββββββββββββββββ€
β BusBar β β LineEnd src β LineEnd dst β β BusBar β
β Vbase βββββββββββββββββββ Vbase β΅ Vbase ββββββββββ Vbase β
β (manual) β β (inherited) β β (manual) β
ββββββββββββββββββββββββ€ βββββββββββββββββββββββββββββ ββββββββββββββββββββββββ€
β Machine β β Load β
β Sn Vn (manual) β β (no local base) β
ββββββββββββββββββββββββ ββββββββββββββββββββββββThose are the names a model refers to: :systembaseβSbase, :busbarβVbase, :srcβVbase. The arrows are the inheritance covered below; Sn and Vn on the machine are a device-local system, not a base of the bus. The "Internals" section of Modeling Concepts describes each of the three components in full.
Setting the bases
Sbase and Οbase are set through module-level globals, before you build anything:
using PowerDynamics
using PowerDynamics.Library
set_Sbase!(300) # MVA
set_fbase!(60) # Hz β same as set_Οbase!(2Ο*60)Every component copies the globals into its own defaults when it is constructed. Setting a base afterwards does not reach a model that already exists, and there is no network-level setter. Set the bases at the top of a script, before the first model.
For the same reason the globals leak between models built in the same session. Every setter restores its default when called without an argument, so a script that changes a base should undo that at the end β a bare set_fbase!() is "back to 50 Hz", a bare set_Sbase!() "back to 100 MVA".
Vbase is per bus, so it is a keyword rather than a global:
bus = MTKBus(machine; Vbase=16.5) # on the symbolic model
vertex = compile_bus(bus; Vbase=16.5) # or when compiling
set_default!(vertex, :busbarβVbase, 16.5) # or afterwardsset_Vbase! exists too, but it is only the fallback for buses that were given nothing β useful when the whole network sits at one voltage level, pointless otherwise. Its default is 1.0. That keeps a model which never sets a voltage base fully simulable β the pu physics is untouched β while making the SI observables self-announcing rather than plausible fictions: u_kV degenerates to the pu value and i_kA comes out implausibly large (Sbase/β3, so 173 kA on the 300 MVA base above). If you intend to read the SI observables at all, set Vbase per bus.
Inheritance: who takes Vbase from whom
Only buses own a voltage base. Their neighbours inherit it:
- line ends take it from the bus they are attached to β per end, so a transformer is simply a line whose two ends resolve to different values, with the turns ratio falling out of the two bases;
- a satellite bus of a current injector takes it from its hub (see Current Injector Bus).
Both use NetworkDynamics' parameter sharing and both are weak: a value you set yourself always wins over the inherited one.
Device-local per-unit systems: Sn and Vn
A device is usually rated differently from the network it sits in, and its data comes on its own rating: a machine's reactances and time constants are given on the machine's MVA rating, not on the system base. So a device may open a local per-unit system and convert at exactly one boundary β its terminal. The convention for spelling it is a nominal power and voltage:
@parameters begin
# shadows of the bases that surround the device
Sbase, [bound_to = :systembaseβSbase]
Vbase, [bound_to = :busbarβVbase]
# the device's own ratings, weakly initialized from those shadows
Sn, [initf_weak = Sbase] # machine power rating [MVA]
Vn, [initf_weak = Vbase] # machine voltage rating [kV]
endFirst the device declares local copies of the bus' Vbase and the system's Sbase with bound_to, which essentially inherit their values from the surrounding bus. The local ratings are then declared initf_weak on those shadows, which reads as "if nobody gives me a rating, take the surrounding base".
Because that initialization is weak, a rating you do set wins over it. And because it is the surrounding base it falls back to, leaving Sn/Vn out is a useful default rather than a special case: the local and the system per-unit systems become identical, every ratio is exactly 1, and the device ends up on the system base.
Everything behind the terminal boundary is in the local system β not just the internal states, but also the measurements the device publishes (e.g. v_mag, P, Q) and the control inputs it accepts (e.g. Ο_m, vf). The AVR and the governor wired to those ports therefore need no base of their own; they work in the machine's per unit, which is why the control blocks in the library carry no Sbase or Vbase at all.
Sn/Vn are the device's nominal ratings β what is written on its nameplate, and what a pu value can be on. They are not setpoints: a machine's nominal voltage Vn and its voltage setpoint Vset are different quantities and may legitimately differ.
The two names are a convention, not a requirement. Models ported from elsewhere keep their source's spelling (the PSS/E machines call the same quantity M_b), and nothing stops a device from opening more than one local system β a turbine rated separately from the alternator is the classical example.
Frequency: base, frame, setpoint, state
Four quantities in a power system model look like a frequency. Three of them are in pu, and in an undisturbed system at default settings all three read 1: the frame does not rotate, nothing commands anything but nominal, and the machines settle there. That coincidence is what makes them so easy to conflate β and it ends as soon as any one of them moves. The fourth is the unit the other three are measured in:
| symbol | unit | kind | |
|---|---|---|---|
Οbase | rad/s | unit | converts SI time β pu; global, on systembase |
Οframe | pu | gauge | speed of the global dq frame, pinned to 1 |
Οset | pu | setpoint | commanded frequency of a droop, a damping term, β¦ |
Ο | pu | state | rotor or PLL speed |
Οframe is what makes the reference frame explicit in equations that would otherwise carry a bare literal 1, as in Dt(Ξ΄) ~ Οbase*(Ο - Οframe). It is a settable parameter, but in PowerDynamics 5.0 it should stay at 1.0; it exists to make the frame speed explicit, so that if we ever introduce simulation in a COI or reference-machine frame, the models are already written for it.
Note that the frame speed is spelled Οframe rather than the sometimes-used Οref, to avoid confusion with the ref of a reference value, i.e. a setpoint.
Reading results in SI units
Every busbar and line end carries four SI observables β the pu quantity times the matching base, and therefore exactly as meaningful as the bases you set. They inherit the convention of the bases they are built from:
| observable | is |
|---|---|
u_kV | voltage magnitude, line-to-line, RMS |
i_kA | current magnitude, line current, RMS |
P_MW, Q_MVAr | three-phase totals |
slack = compile_bus(SlackAlgebraic(; name=:slack); vidx=1, Vbase=230)
gen = compile_bus(Swing(; name=:machine); vidx=2, pf=pfPV(P=0.8, V=1.02), Vbase=18.0)
line = compile_line(PiLine(; name=:piline, R=0.02, X=0.1); src=1, dst=2)
nw = Network([slack, gen], [line])
s0 = initialize_from_pf!(nw)
(; u_kV = s0[VIndex(2, :busbarβu_kV)],
P_MW = s0[VIndex(2, :busbarβP_MW)])(u_kV = 18.36000000000016, P_MW = 239.99999999998818)0.8 pu on a 300 MVA base is 240 MW, and 1.02 pu on an 18 kV bus is 18.36 kV. The two buses sit at different levels, so the PiLine between them is a transformer in the sense of the previous section β its two ends resolve to different Vbase values.
Consistency
Bases that disagree do not fail loudly on their own β they quietly produce wrong numbers. So check_base_consistency runs on every initialize_from_pf! and verifies that
- all components agree on
Sbase, - all components agree on
Οbase, - all components agree on
Οframe, - each line end matches the
Vbaseof the bus it is attached to, - each satellite bus matches the
Vbaseof its hub.
Components that carry none of these symbols β a hand-rolled pure-pu model without a systembase β are skipped rather than failed; mixing based and baseless components is legitimate. All findings are collected into a single message. Pass check=:warn or check=:none to initialize_from_pf! if you have a good reason to allow a mismatch.
What each of those mismatches actually costs:
| situation | consequence |
|---|---|
Vbase wrong on one line end | wrong implied transformer ratio, wrong SI values at that end |
Vbase of a satellite β its hub | same, at the injector's own terminal |
Sbase disagrees | power silently mis-converted at every device boundary |
Οbase disagrees | time constants silently rescaled per component |
Οframe disagrees | frames rotate apart, no steady state exists |
The voltage side degrades softly, the power and frequency side does not.
One failure mode the check cannot see is setting no Vbase at all: every component then agrees on the fallback 1.0, so the network is consistent by construction and passes. The pu physics stays exact and only the SI observables are meaningless β the tool degrades to pure pu, which is why this is a default rather than an error.
Finally, this page changed two globals, so it puts them back β the rule from the top applied to the page itself:
set_Sbase!()
set_fbase!()