IEEE39 Bus Tutorial - Part I: Model Creation

This is the first part of a four-part tutorial series for the IEEE 39-bus test system:

  • Part I: Model Creation (this tutorial) - Build the network structure with buses, lines, and components
  • Part II: Initialization - Perform power flow calculations and dynamic initialization
  • Part III: Dynamic Simulation - Run time-domain simulations and analyze system behavior
  • Part IV: Advanced Modeling & Parameter Optimization - Create custom components and optimize system parameters

In this first part, we'll construct the complete IEEE 39-bus network model using PowerDynamics.jl, including generators, loads, transmission lines, and control systems.

This script can be downloaded as a normal Julia script here.

System Structure

The system consists of 39 buses (with 10 generators and 19 loads) and 46 branches (12 of which are transformers).

The buses fall into the following categories:

  • Junction: pure transient buses without dynamic components
  • Load: buses with loads only
  • Controlled Machine: buses with controlled machines (generators with AVR and GOV)
  • Controlled Machine + Load: buses with controlled machines and loads
  • Uncontrolled Machine + Load: buses with uncontrolled machines and loads

For the power flow solution, we have a slack bus, PV buses and PQ buses.

For the dynamic simulation, we will use the following models:

  • ZIP Load for loads,
  • 6th Order Sauer-Pai Machine and
  • AVR Type I and TGOV1 for controlled machines.

Setup and Data Loading

No Standardized Data Import

As of now, PowerDynamics.jl does not support any advanced import mechanisms for power grids. Therefore, this tutorial loads the data from some custom CSV files.

First, we'll load the required packages and read the system data from CSV files. The IEEE 39-bus system data is organized into separate files for different components.

using PowerDynamics
using PowerDynamics.Library
using ModelingToolkit
using NetworkDynamics
using DataFrames
using CSV

DATA_DIR = joinpath(pkgdir(PowerDynamics), "docs", "examples", "ieee39data")

The system data is stored in CSV files containing:

bus.csv - Bus Configuration Data
ParameterDescription
busBus number (unique identifier)
bus_typePower flow bus type: "PQ" (load), "PV" (generator), "Slack" (reference)
categoryComponent category: "junction", "load", "ctrld_machine", "ctrld_machine_load", "unctrld_machine_load"
PActive power injection [pu] (positive = generation, negative = load)
QReactive power injection [pu] (positive = generation, negative = load)
VVoltage magnitude [pu] (for PV and Slack buses)
base_kvBase voltage level [kV]
has_loadBoolean flag indicating presence of load component
has_genBoolean flag indicating presence of generator component
has_avrBoolean flag indicating presence of automatic voltage regulator
has_govBoolean flag indicating presence of turbine governor
branch.csv - Transmission Line and Transformer Data
ParameterDescription
src_busSource bus number
dst_busDestination bus number
transformerTransformer flag (0 = line, 1 = transformer)
r_srcSource end transformation ratio [pu]
RSeries resistance [pu]
XSeries reactance [pu]
G_srcSource end shunt conductance [pu]
G_dstDestination end shunt conductance [pu]
B_srcSource end shunt susceptance [pu]
B_dstDestination end shunt susceptance [pu]
load.csv - ZIP Load Model Parameters
ParameterDescription
busBus number where load is connected
PsetActive power at operation point [pu]
QsetReactive power at operation point [pu]
KpZActive power constant impedance fraction
KqZReactive power constant impedance fraction
KpIActive power constant current fraction
KqIReactive power constant current fraction
KpCActive power constant power fraction (1-KpZ-KpI)
KqCReactive power constant power fraction (1-KqZ-KqI)

Note: ZIP loads combine constant impedance (Z), constant current (I), and constant power (P) components.

machine.csv - Generator (Sauer-Pai Machine) Parameters
ParameterDescription
busBus number where generator is connected
SnMachine power rating [MVA]
V_bSystem voltage basis [kV]
VnMachine voltage rating [kV]
R_sStator resistance [pu]
X_lsStator leakage reactance [pu]
X_dd-axis synchronous reactance [pu]
X_qq-axis synchronous reactance [pu]
Xโ€ฒ_dd-axis transient reactance [pu]
Xโ€ฒ_qq-axis transient reactance [pu]
Xโ€ณ_dd-axis subtransient reactance [pu]
Xโ€ณ_qq-axis subtransient reactance [pu]
Tโ€ฒ_d0d-axis transient time constant [s]
Tโ€ฒ_q0q-axis transient time constant [s]
Tโ€ณ_d0d-axis subtransient time constant [s]
Tโ€ณ_q0q-axis subtransient time constant [s]
HInertia constant [s]
DDirect shaft damping coefficient
avr.csv - Automatic Voltage Regulator (AVR Type I) Parameters
ParameterDescription
busBus number where AVR-controlled generator is located
KaAmplifier gain
KeField circuit integral deviation
KfStabilizer gain
TaAmplifier time constant [s]
TfStabilizer time constant [s]
TeField circuit time constant [s]
TrMeasurement time constant [s]
vr_minMinimum regulator voltage [pu]
vr_maxMaximum regulator voltage [pu]
E1First ceiling voltage [pu]
Se1First ceiling saturation factor
E2Second ceiling voltage [pu]
Se2Second ceiling saturation factor
gov.csv - Turbine Governor (TGOV1) Parameters
ParameterDescription
busBus number where governor-controlled generator is located
V_minMinimum valve position [pu]
V_maxMaximum valve position [pu]
RGovernor droop [Machine PU]
T1First transient time constant [s]
T2Second transient time constant [s]
T3Third transient time constant [s]
DTTurbine damping coefficient
ฯ‰_refReference frequency [pu]
branch_df = CSV.read(joinpath(DATA_DIR, "branch.csv"), DataFrame)
bus_df = CSV.read(joinpath(DATA_DIR, "bus.csv"), DataFrame)
load_df = CSV.read(joinpath(DATA_DIR, "load.csv"), DataFrame)
machine_df = CSV.read(joinpath(DATA_DIR, "machine.csv"), DataFrame)
avr_df = CSV.read(joinpath(DATA_DIR, "avr.csv"), DataFrame)
gov_df = CSV.read(joinpath(DATA_DIR, "gov.csv"), DataFrame)

System base values follow the IEEE 39-bus standard:

BASE_MVA = 100.0
BASE_FREQ = 60.0

Subcomponent Definition

As stated above, our buses fall into 5 different categories. We will define a "template" for each of those categories and then create the individual buses from those templates. By doing so, we can reach substantial performance improvements, as we do not have to repeatedly compile the same models (the symbolic simplification is quite costly). Instead, we copy the templates and adjust parameters.

However, before we can define the bus templates, we need to define the individual subcomponents. Those subcomponents are MTK models and not yet compiled node models. See Modeling Concepts and the custom bus tutorial.

Load Model

We use the ZIP load model to represent loads. This model satisfies the Injector Interface.

(t) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 oโ”€โ”€โ”ค ZIP Load โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
load = ZIPLoad(;name=:ZIPLoad)

Generator Models

For generators, we use the Sauer-Pai machine model, which is a 6th-order synchronous machine model. We create two variants:

Uncontrolled Machine: No external control inputs for mechanical torque or field voltage. This model satisfies the Injector Interface directly.

(t) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 oโ”€โ”€โ”ค Machine โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
uncontrolled_machine = SauerPaiMachine(;
    ฯ„_m_input=false,  ## No external mechanical torque input
    vf_input=false,   ## No external field voltage input
    name=:machine,
)

Controlled Machine: Includes automatic voltage regulator (AVR) and turbine governor controls.

The controlled machine is modeled as a composite injector. It consists of 3 subcomponents: the machine, the AVR and the governor. The AVR receives the voltage magnitude measurement from the terminal of the machine and sets the field voltage. The governor receives the frequency measurement and sets the mechanical torque. Together, they satisfy the Injector Interface.

      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
      โ”‚ CtrldMachine  u_mag_meas      โ”‚
      โ”‚              โ•ญโ”€โ”€โ”€โ”€โ”€โ†’โ”€โ”€โ”€โ”€โ•ฎ     โ”‚
      โ”‚    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”      โ”Œโ”€โ”ดโ”€โ”€โ”€โ” โ”‚
  (t) โ”‚    โ”‚           โ”œโ”€โ”€โ”€โ†โ”€โ”€โ”ค AVR โ”‚ โ”‚
   oโ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ค Sauer-Pai โ”‚ vf   โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
      โ”‚    โ”‚ Machine   โ”‚ ฯ„_m  โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”‚
      โ”‚    โ”‚           โ”œโ”€โ”€โ”€โ†โ”€โ”€โ”ค Gov โ”‚ โ”‚
      โ”‚    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”˜      โ””โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ”‚
      โ”‚              โ•ฐโ”€โ”€โ”€โ”€โ”€โ†’โ”€โ”€โ”€โ”€โ•ฏ     โ”‚
      โ”‚                 ฯ‰_meas        โ”‚
      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
_machine = SauerPaiMachine(;
    name=:machine,
)
_avr = AVRTypeI(;
    name=:avr,
    ceiling_function=:quadratic,
)
_gov = TGOV1(; name=:gov,)

controlled_machine = CompositeInjector(
    [_machine, _avr, _gov],
    name=:ctrld_gen
)

Bus Template Creation

Now we have all the components (i.e., the MTK models) so we can combine them into full bus models and compile the methods.

Junction Bus

Pure transmission buses with no generation or load

           โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
           โ•‘ Junction (compiled)  โ•‘
 Network   โ•‘  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘
interface  โ•‘  โ”‚MTKBus           โ”‚ โ•‘
 current โ”€โ”€โ”€โ”€โ†’โ”‚โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”‚ โ•‘
           โ•‘  โ”‚โ”‚BusBarโ”‚(nothing)โ”‚ โ•‘
 voltage โ†โ”€โ”€โ”€โ”€โ”‚โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚ โ•‘
           โ•‘  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘
           โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@named junction_bus_template = Bus(MTKBus())
strip_defaults!(junction_bus_template)  ## Clear default parameters for manual setting
VertexModel :junction_bus_template PureStateMap()
 โ”œโ”€ 2 inputs:  [busbarโ‚Ši_r, busbarโ‚Ši_i]
 โ”œโ”€ 2 states:  [busbarโ‚Šu_i, busbarโ‚Šu_r]
 |    with diagonal mass matrix [0, 0]
 โ””โ”€ 2 outputs: [busbarโ‚Šu_r, busbarโ‚Šu_i]

Load Bus

Buses with only load components

           โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
           โ•‘ Load (compiled)     โ•‘
 Network   โ•‘  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘
interface  โ•‘  โ”‚MTKBus          โ”‚ โ•‘
 current โ”€โ”€โ”€โ”€โ†’โ”‚โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ” โ”‚ โ•‘
           โ•‘  โ”‚โ”‚BusBarโ”œoโ”คLoadโ”‚ โ”‚ โ•‘
 voltage โ†โ”€โ”€โ”€โ”€โ”‚โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ•‘
           โ•‘  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘
           โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@named load_bus_template = Bus(MTKBus(load))
strip_defaults!(load_bus_template)
VertexModel :load_bus_template PureStateMap()
 โ”œโ”€ 2 inputs:  [busbarโ‚Ši_r, busbarโ‚Ši_i]
 โ”œโ”€ 2 states:  [busbarโ‚Šu_r, busbarโ‚Šu_i]
 |    with diagonal mass matrix [0, 0]
 โ”œโ”€ 2 outputs: [busbarโ‚Šu_r, busbarโ‚Šu_i]
 โ””โ”€ 9 params:  [ZIPLoadโ‚ŠPset, ZIPLoadโ‚ŠQset, ZIPLoadโ‚ŠVsetโ‰ˆ1, ZIPLoadโ‚ŠKpZ, ZIPLoadโ‚ŠKqZ, ZIPLoadโ‚ŠKpI, ZIPLoadโ‚ŠKqI, ZIPLoadโ‚ŠKpC, ZIPLoadโ‚ŠKqC]

Generator Bus (Controlled)

Buses with controlled generators (machine + AVR + governor)

            โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
            โ•‘ Ctrld Machine Bus (compiled)                   โ•‘
            โ•‘  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘
            โ•‘  โ”‚MTKBus                                     โ”‚ โ•‘
            โ•‘  โ”‚         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ•‘
  Network   โ•‘  โ”‚         โ”‚CtrldMachine  โ•ญโ”€โ”€โ”€โ”€โ”€โ†’โ”€โ”€โ”€โ”€โ•ฎ     โ”‚ โ”‚ โ•‘
 interface  โ•‘  โ”‚         โ”‚    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”      โ”Œโ”€โ”ดโ”€โ”€โ”€โ” โ”‚ โ”‚ โ•‘
  current โ”€โ”€โ”€โ”€โ†’โ”‚โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚    โ”‚           โ”œโ”€โ”€โ”€โ†โ”€โ”€โ”ค AVR โ”‚ โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚โ”‚BusBarโ”œoโ”ผโ”€โ”€โ”€โ”€โ”ค Sauer-Pai โ”‚      โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ•‘
  voltage โ†โ”€โ”€โ”€โ”€โ”‚โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚    โ”‚ Machine   โ”‚      โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚         โ”‚    โ”‚           โ”œโ”€โ”€โ”€โ†โ”€โ”€โ”ค Gov โ”‚ โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚         โ”‚    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”˜      โ””โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚         โ”‚              โ•ฐโ”€โ”€โ”€โ”€โ”€โ†’โ”€โ”€โ”€โ”€โ•ฏ     โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ•‘
            โ•‘  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘
            โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@named ctrld_machine_bus_template = Bus(
    MTKBus(controlled_machine);
)
strip_defaults!(ctrld_machine_bus_template)
# Set system-wide base values for all generators
set_default!(ctrld_machine_bus_template, r"S_b$", BASE_MVA)
set_default!(ctrld_machine_bus_template, r"ฯ‰_b$", 2ฯ€*BASE_FREQ)
VertexModel :ctrld_machine_bus_template PureStateMap()
 โ”œโ”€  2 inputs:  [busbarโ‚Ši_r, busbarโ‚Ši_i]
 โ”œโ”€ 15 states:  [ctrld_genโ‚Šgovโ‚Šฯ„_mโ‚Šuโ‰ˆ0, ctrld_genโ‚Šgovโ‚Šxg1โ‰ˆ1, ctrld_genโ‚Šgovโ‚Šxg2โ‰ˆ0, ctrld_genโ‚Šavrโ‚Švfoutโ‰ˆ1, ctrld_genโ‚Šavrโ‚Šv_fbโ‰ˆ0, ctrld_genโ‚Šavrโ‚Švrโ‰ˆ0, ctrld_genโ‚Šavrโ‚Švmโ‰ˆ1, ctrld_genโ‚Šmachineโ‚Šฯˆโ€ณ_qโ‰ˆ0, ctrld_genโ‚Šmachineโ‚Šฯˆโ€ณ_dโ‰ˆ1, ctrld_genโ‚Šmachineโ‚ŠEโ€ฒ_dโ‰ˆ1, ctrld_genโ‚Šmachineโ‚ŠEโ€ฒ_qโ‰ˆ0, ctrld_genโ‚Šmachineโ‚Šฯ‰โ‰ˆ1, ctrld_genโ‚Šmachineโ‚Šฮดโ‰ˆ0, busbarโ‚Šu_r, busbarโ‚Šu_i]
 |     with diagonal mass matrix [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
 โ”œโ”€  2 outputs: [busbarโ‚Šu_r, busbarโ‚Šu_i]
 โ””โ”€ 42 params:  [ctrld_genโ‚Šmachineโ‚ŠR_s, ctrld_genโ‚Šmachineโ‚ŠX_d, ctrld_genโ‚Šmachineโ‚ŠX_q, ctrld_genโ‚Šmachineโ‚ŠXโ€ฒ_d, ctrld_genโ‚Šmachineโ‚ŠXโ€ฒ_q, ctrld_genโ‚Šmachineโ‚ŠXโ€ณ_d, ctrld_genโ‚Šmachineโ‚ŠXโ€ณ_q, ctrld_genโ‚Šmachineโ‚ŠX_ls, ctrld_genโ‚Šmachineโ‚ŠTโ€ฒ_d0, ctrld_genโ‚Šmachineโ‚ŠTโ€ณ_d0, ctrld_genโ‚Šmachineโ‚ŠTโ€ฒ_q0, ctrld_genโ‚Šmachineโ‚ŠTโ€ณ_q0, ctrld_genโ‚Šmachineโ‚ŠH, ctrld_genโ‚Šmachineโ‚ŠD, ctrld_genโ‚Šmachineโ‚ŠS_b=100, ctrld_genโ‚Šmachineโ‚ŠV_b, ctrld_genโ‚Šmachineโ‚Šฯ‰_b=376.99, ctrld_genโ‚Šmachineโ‚ŠSn, ctrld_genโ‚Šmachineโ‚ŠVn, ctrld_genโ‚Šavrโ‚ŠTr, ctrld_genโ‚Šavrโ‚Švrefโ‰ˆ1, ctrld_genโ‚Šavrโ‚ŠKa, ctrld_genโ‚Šavrโ‚ŠKe, ctrld_genโ‚Šavrโ‚ŠKf, ctrld_genโ‚Šavrโ‚ŠTa, ctrld_genโ‚Šavrโ‚ŠTf, ctrld_genโ‚Šavrโ‚ŠTe, ctrld_genโ‚Šavrโ‚Švr_min, ctrld_genโ‚Šavrโ‚Švr_max, ctrld_genโ‚Šavrโ‚ŠE1, ctrld_genโ‚Šavrโ‚ŠE2, ctrld_genโ‚Šavrโ‚ŠSe1, ctrld_genโ‚Šavrโ‚ŠSe2, ctrld_genโ‚Šgovโ‚Šฯ‰_ref, ctrld_genโ‚Šgovโ‚Šp_refโ‰ˆ1, ctrld_genโ‚Šgovโ‚ŠV_min, ctrld_genโ‚Šgovโ‚ŠV_max, ctrld_genโ‚Šgovโ‚ŠR, ctrld_genโ‚Šgovโ‚ŠT1, ctrld_genโ‚Šgovโ‚ŠT2, ctrld_genโ‚Šgovโ‚ŠT3, ctrld_genโ‚Šgovโ‚ŠDT]

Generator + Load Bus (Controlled)

Buses with both controlled generators and loads

            โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
            โ•‘ Ctrld Machine Load Bus (compiled)               โ•‘
            โ•‘  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘
            โ•‘  โ”‚MTKBus    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ•‘
            โ•‘  โ”‚          โ”‚CtrldMachine  โ•ญโ”€โ”€โ”€โ”€โ”€โ†’โ”€โ”€โ”€โ”€โ•ฎ     โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚          โ”‚    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”      โ”Œโ”€โ”ดโ”€โ”€โ”€โ” โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚          โ”‚    โ”‚           โ”œโ”€โ”€โ”€โ†โ”€โ”€โ”ค AVR โ”‚ โ”‚ โ”‚ โ•‘
  Network   โ•‘  โ”‚        โ”Œโ”€โ”ผโ”€โ”€โ”€โ”€โ”ค Sauer-Pai โ”‚      โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ•‘
 interface  โ•‘  โ”‚        โ”‚ โ”‚    โ”‚ Machine   โ”‚      โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ•‘
  current โ”€โ”€โ”€โ”€โ†’โ”‚โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ โ”‚    โ”‚           โ”œโ”€โ”€โ”€โ†โ”€โ”€โ”ค Gov โ”‚ โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚โ”‚BusBarโ”œo โ”‚    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”˜      โ””โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ•‘
  voltage โ†โ”€โ”€โ”€โ”€โ”‚โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ โ”‚              โ•ฐโ”€โ”€โ”€โ”€โ”€โ†’โ”€โ”€โ”€โ”€โ•ฏ     โ”‚ โ”‚ โ•‘
            โ•‘  โ”‚        โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ•‘
            โ•‘  โ”‚        โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”                          โ”‚ โ•‘
            โ•‘  โ”‚        โ””โ”€โ”ค Load โ”‚                          โ”‚ โ•‘
            โ•‘  โ”‚          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚ โ•‘
            โ•‘  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘
            โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@named ctrld_machine_load_bus_template = Bus(
    MTKBus(controlled_machine, load);
)
strip_defaults!(ctrld_machine_load_bus_template)
set_default!(ctrld_machine_load_bus_template, r"S_b$", BASE_MVA)
set_default!(ctrld_machine_load_bus_template, r"ฯ‰_b$", 2ฯ€*BASE_FREQ)
VertexModel :ctrld_machine_load_bus_template NoFeedForward()
 โ”œโ”€  2 inputs:  [busbarโ‚Ši_r, busbarโ‚Ši_i]
 โ”œโ”€ 15 states:  [ctrld_genโ‚Šgovโ‚Šฯ„_mโ‚Šuโ‰ˆ0, ctrld_genโ‚Šmachineโ‚Šterminalโ‚Ši_rโ‰ˆ0, ctrld_genโ‚Šmachineโ‚Šterminalโ‚Ši_iโ‰ˆ0, ctrld_genโ‚Šgovโ‚Šxg1โ‰ˆ1, ctrld_genโ‚Šgovโ‚Šxg2โ‰ˆ0, ctrld_genโ‚Šavrโ‚Švfoutโ‰ˆ1, ctrld_genโ‚Šavrโ‚Šv_fbโ‰ˆ0, ctrld_genโ‚Šavrโ‚Švrโ‰ˆ0, ctrld_genโ‚Šavrโ‚Švmโ‰ˆ1, ctrld_genโ‚Šmachineโ‚Šฯˆโ€ณ_qโ‰ˆ0, ctrld_genโ‚Šmachineโ‚Šฯˆโ€ณ_dโ‰ˆ1, ctrld_genโ‚Šmachineโ‚ŠEโ€ฒ_dโ‰ˆ1, ctrld_genโ‚Šmachineโ‚ŠEโ€ฒ_qโ‰ˆ0, ctrld_genโ‚Šmachineโ‚Šฯ‰โ‰ˆ1, ctrld_genโ‚Šmachineโ‚Šฮดโ‰ˆ0]
 |     with diagonal mass matrix [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
 โ”œโ”€  2 outputs: [busbarโ‚Šu_r, busbarโ‚Šu_i]
 โ””โ”€ 51 params:  [ctrld_genโ‚Šmachineโ‚ŠR_s, ctrld_genโ‚Šmachineโ‚ŠX_d, ctrld_genโ‚Šmachineโ‚ŠX_q, ctrld_genโ‚Šmachineโ‚ŠXโ€ฒ_d, ctrld_genโ‚Šmachineโ‚ŠXโ€ฒ_q, ctrld_genโ‚Šmachineโ‚ŠXโ€ณ_d, ctrld_genโ‚Šmachineโ‚ŠXโ€ณ_q, ctrld_genโ‚Šmachineโ‚ŠX_ls, ctrld_genโ‚Šmachineโ‚ŠTโ€ฒ_d0, ctrld_genโ‚Šmachineโ‚ŠTโ€ณ_d0, ctrld_genโ‚Šmachineโ‚ŠTโ€ฒ_q0, ctrld_genโ‚Šmachineโ‚ŠTโ€ณ_q0, ctrld_genโ‚Šmachineโ‚ŠH, ctrld_genโ‚Šmachineโ‚ŠD, ctrld_genโ‚Šmachineโ‚ŠS_b=100, ctrld_genโ‚Šmachineโ‚ŠV_b, ctrld_genโ‚Šmachineโ‚Šฯ‰_b=376.99, ctrld_genโ‚Šmachineโ‚ŠSn, ctrld_genโ‚Šmachineโ‚ŠVn, ctrld_genโ‚Šavrโ‚ŠTr, ctrld_genโ‚Šavrโ‚Švrefโ‰ˆ1, ctrld_genโ‚Šavrโ‚ŠKa, ctrld_genโ‚Šavrโ‚ŠKe, ctrld_genโ‚Šavrโ‚ŠKf, ctrld_genโ‚Šavrโ‚ŠTa, ctrld_genโ‚Šavrโ‚ŠTf, ctrld_genโ‚Šavrโ‚ŠTe, ctrld_genโ‚Šavrโ‚Švr_min, ctrld_genโ‚Šavrโ‚Švr_max, ctrld_genโ‚Šavrโ‚ŠE1, ctrld_genโ‚Šavrโ‚ŠE2, ctrld_genโ‚Šavrโ‚ŠSe1, ctrld_genโ‚Šavrโ‚ŠSe2, ctrld_genโ‚Šgovโ‚Šฯ‰_ref, ctrld_genโ‚Šgovโ‚Šp_refโ‰ˆ1, ctrld_genโ‚Šgovโ‚ŠV_min, ctrld_genโ‚Šgovโ‚ŠV_max, ctrld_genโ‚Šgovโ‚ŠR, ctrld_genโ‚Šgovโ‚ŠT1, ctrld_genโ‚Šgovโ‚ŠT2, ctrld_genโ‚Šgovโ‚ŠT3, ctrld_genโ‚Šgovโ‚ŠDT, ZIPLoadโ‚ŠPset, ZIPLoadโ‚ŠQset, ZIPLoadโ‚ŠVsetโ‰ˆ1, ZIPLoadโ‚ŠKpZ, ZIPLoadโ‚ŠKqZ, ZIPLoadโ‚ŠKpI, ZIPLoadโ‚ŠKqI, ZIPLoadโ‚ŠKpC, ZIPLoadโ‚ŠKqC]

Generator + Load Bus (Uncontrolled)

Buses with uncontrolled generators and loads

            โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
            โ•‘ Unctr. Ma. Load Bus (compiled) โ•‘
            โ•‘  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ•‘
  Network   โ•‘  โ”‚MTKBus      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚    โ•‘
 interface  โ•‘  โ”‚          โ”Œโ”€โ”ค Machine โ”‚ โ”‚    โ•‘
  current โ”€โ”€โ”€โ”€โ†’โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚    โ•‘
            โ•‘  โ”‚ โ”‚BusBarโ”œโ”€o             โ”‚    โ•‘
  voltage โ†โ”€โ”€โ”€โ”€โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚    โ•‘
            โ•‘  โ”‚          โ””โ”€โ”ค Load โ”‚    โ”‚    โ•‘
            โ•‘  โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚    โ•‘
            โ•‘  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ•‘
            โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@named unctrld_machine_load_bus_template = Bus(
    MTKBus(uncontrolled_machine, load);
)
strip_defaults!(unctrld_machine_load_bus_template)
set_default!(unctrld_machine_load_bus_template, r"S_b$", BASE_MVA)
set_default!(unctrld_machine_load_bus_template, r"ฯ‰_b$", 2ฯ€*BASE_FREQ)
VertexModel :unctrld_machine_load_bus_template NoFeedForward()
 โ”œโ”€  2 inputs:  [busbarโ‚Ši_r, busbarโ‚Ši_i]
 โ”œโ”€  8 states:  [machineโ‚Šterminalโ‚Ši_iโ‰ˆ0, machineโ‚Šterminalโ‚Ši_rโ‰ˆ0, machineโ‚Šฯˆโ€ณ_qโ‰ˆ0, machineโ‚Šฯˆโ€ณ_dโ‰ˆ1, machineโ‚ŠEโ€ฒ_dโ‰ˆ1, machineโ‚ŠEโ€ฒ_qโ‰ˆ0, machineโ‚Šฯ‰โ‰ˆ1, machineโ‚Šฮดโ‰ˆ0]
 |     with diagonal mass matrix [0, 0, 1, 1, 1, 1, 1, 1]
 โ”œโ”€  2 outputs: [busbarโ‚Šu_r, busbarโ‚Šu_i]
 โ””โ”€ 30 params:  [machineโ‚Švf_setโ‰ˆ1, machineโ‚Šฯ„_m_setโ‰ˆ1, machineโ‚ŠR_s, machineโ‚ŠX_d, machineโ‚ŠX_q, machineโ‚ŠXโ€ฒ_d, machineโ‚ŠXโ€ฒ_q, machineโ‚ŠXโ€ณ_d, machineโ‚ŠXโ€ณ_q, machineโ‚ŠX_ls, machineโ‚ŠTโ€ฒ_d0, machineโ‚ŠTโ€ณ_d0, machineโ‚ŠTโ€ฒ_q0, machineโ‚ŠTโ€ณ_q0, machineโ‚ŠH, machineโ‚ŠD, machineโ‚ŠS_b=100, machineโ‚ŠV_b, machineโ‚Šฯ‰_b=376.99, machineโ‚ŠSn, machineโ‚ŠVn, ZIPLoadโ‚ŠPset, ZIPLoadโ‚ŠQset, ZIPLoadโ‚ŠVsetโ‰ˆ1, ZIPLoadโ‚ŠKpZ, ZIPLoadโ‚ŠKqZ, ZIPLoadโ‚ŠKpI, ZIPLoadโ‚ŠKqI, ZIPLoadโ‚ŠKpC, ZIPLoadโ‚ŠKqC]

Bus Instantiation and Parameter Setting

Now we create the actual bus instances by copying templates and applying specific parameters from the CSV data files.

# Helper function to apply CSV parameters to bus components
function apply_csv_params!(bus, table, bus_index)
    row_idx = findfirst(table.bus .== bus_index)

    # Apply all parameters except "bus" column
    row = table[row_idx, :]
    for col_name in names(table)
        if col_name != "bus"
            set_default!(bus, Regex(col_name*"\$"), row[col_name])
        end
    end
end

For each bus in the system, we:

  1. Select the appropriate template based on its category
  2. Create a bus instance with the correct vertex index and name
  3. Apply component-specific parameters from CSV files
  4. Set the power flow model (PQ, PV, or Slack)
busses = []
for row in eachrow(bus_df)
    i = row.bus

    # Select template based on bus category
    bus = if row.category == "junction"
        Bus(junction_bus_template; vidx=i, name=Symbol("bus$i"))
    elseif row.category == "load"
        Bus(load_bus_template; vidx=i, name=Symbol("bus$i"))
    elseif row.category == "ctrld_machine"
        Bus(ctrld_machine_bus_template; vidx=i, name=Symbol("bus$i"))
    elseif row.category == "ctrld_machine_load"
        Bus(ctrld_machine_load_bus_template; vidx=i, name=Symbol("bus$i"))
    elseif row.category == "unctrld_machine_load"
        Bus(unctrld_machine_load_bus_template; vidx=i, name=Symbol("bus$i"))
    end

    # Apply component parameters from CSV files
    row.has_load && apply_csv_params!(bus, load_df, i)
    row.has_gen && apply_csv_params!(bus, machine_df, i)
    row.has_avr && apply_csv_params!(bus, avr_df, i)
    row.has_gov && apply_csv_params!(bus, gov_df, i)

    # Set power flow model based on bus type
    pf_model = if row.bus_type == "PQ"
        pfPQ(P=row.P, Q=row.Q)  ## Load bus: fixed P and Q
    elseif row.bus_type == "PV"
        pfPV(P=row.P, V=row.V)  ## Generator bus: fixed P and V
    elseif row.bus_type == "Slack"
        pfSlack(V=row.V, ฮด=0)   ## Slack bus: fixed V and angle
    end
    set_pfmodel!(bus, pf_model)

    push!(busses, bus)
end

Transmission Line Creation

The IEEE 39-bus system includes both transmission lines and transformers, all modeled using the ฯ€-line equivalent circuit model.

The model consists of several layers:

  1. The PiModel, which satisfies the Branch Interface as it has two terminals
  2. The MTKLine constructor, which creates a MTK model fulfilling the MTKLine Interface
  3. The compiled EdgeModel created by calling the Line constructor
       โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
       โ•‘ EdgeModel (compiled)             โ•‘
   src โ•‘ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘ dst
vertex โ•‘ โ”‚MTKLine                       โ”‚ โ•‘ vertex
   u โ”€โ”€โ”€โ†’โ”‚โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚โ†โ”€โ”€โ”€ u
       โ•‘ โ”‚โ”‚LineEndโ”œoโ”ค PiLine โ”œoโ”คLineEndโ”‚โ”‚ โ•‘
   i โ†โ”€โ”€โ”€โ”‚โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚โ”€โ”€โ”€โ†’ i
       โ•‘ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘
       โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

(We used the PiLine_fault model since we plan on simulating short circuits later.)

@named piline_template = Line(MTKLine(PiLine_fault(;name=:piline)))
EdgeModel :piline_template PureFeedForward()
 โ”œโ”€ 2/2 inputs:  src=[srcโ‚Šu_r, srcโ‚Šu_i] dst=[dstโ‚Šu_r, dstโ‚Šu_i]
 โ”œโ”€   0 states:  []  
 โ”œโ”€ 2/2 outputs: src=[srcโ‚Ši_r, srcโ‚Ši_i] dst=[dstโ‚Ši_r, dstโ‚Ši_i]
 โ””โ”€  16 params:  [pilineโ‚ŠR, pilineโ‚ŠX, pilineโ‚ŠG_src, pilineโ‚ŠB_src, pilineโ‚ŠG_dst, pilineโ‚ŠB_dst, pilineโ‚Šr_src=1, pilineโ‚Šr_dst=1, pilineโ‚ŠR_fault=0, pilineโ‚ŠX_fault=0, pilineโ‚ŠG_fault=0, pilineโ‚ŠB_fault=0, pilineโ‚Špos=0.5, pilineโ‚Šactive=1, pilineโ‚Šshortcircuit=0, pilineโ‚Šfaultimp=0]

Each transmission element is created by:

  1. Instantiating a line from the template with source and destination buses
  2. Setting electrical parameters (resistance, reactance, susceptance) from CSV data
branches = []
for row in eachrow(branch_df)
    # Create line instance with topology
    line = Line(piline_template; src=row.src_bus, dst=row.dst_bus)

    # Apply electrical parameters from CSV data
    for col_name in names(branch_df)
        if col_name โˆ‰ ["src_bus", "dst_bus", "transformer"]
            set_default!(line, Regex(col_name*"\$"), row[col_name])
        end
    end

    push!(branches, line)
end

Network Assembly

Finally, we combine all buses and transmission lines into a complete network model. This creates the IEEE 39-bus test system ready for initialization and simulation.

nw = Network(busses, branches)
Network with 201 states and 1306 parameters
 โ”œโ”€ 39 vertices (5 unique types)
 โ””โ”€ 46 edges (1 unique type)
Edge-Aggregation using SequentialAggregator(+)

The network nw now contains the complete IEEE 39-bus model structure. In Part 2 of this tutorial series, we'll initialize this network by solving the power flow and setting up the dynamic initial conditions.


This page was generated using Literate.jl.