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.

baseunitscope
SbaseMVAglobalone power base for the whole network
Ο‰baserad/sglobalthe frequency at which reactances were evaluated; converts SI time ↔ pu
VbasekVper busthe 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)
The setters affect future construction only

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 afterwards

set_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]
end

First 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.

`n` is for *nominal*

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:

symbolunitkind
Ο‰baserad/sunitconverts SI time ↔ pu; global, on systembase
Ο‰framepugaugespeed of the global dq frame, pinned to 1
Ο‰setpusetpointcommanded frequency of a droop, a damping term, …
Ο‰pustaterotor 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:

observableis
u_kVvoltage magnitude, line-to-line, RMS
i_kAcurrent magnitude, line current, RMS
P_MW, Q_MVArthree-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

  1. all components agree on Sbase,
  2. all components agree on Ο‰base,
  3. all components agree on Ο‰frame,
  4. each line end matches the Vbase of the bus it is attached to,
  5. each satellite bus matches the Vbase of 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:

situationconsequence
Vbase wrong on one line endwrong implied transformer ratio, wrong SI values at that end
Vbase of a satellite β‰  its hubsame, at the injector's own terminal
Sbase disagreespower silently mis-converted at every device boundary
Ο‰base disagreestime constants silently rescaled per component
Ο‰frame disagreesframes 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!()