Create and Explore a Power System

Welcome to PowerSystems.jl!

In this tutorial, we will create a power system and add some components to it, including some nodes, a transmission line, load, and both renewable and fossil fuel generators. Then we will retrieve data from the system and explore the system settings.

Setup

To get started, ensure you have followed the installation instructions.

Start Julia from the command line if you haven't already:

$ julia

Load the PowerSystems.jl package:

julia> using PowerSystems

Creating a Power System

In PowerSystems.jl, data is held in a System that holds all of the individual components along with some metadata about the power system itself.

There are many ways to define a System, but let's start with an empty system. All we need to define is a base power of 100 MVA for per-unitization.

julia> sys = System(100.0)System
┌───────────────────┬─────────────┐
│ Property          │ Value       │
├───────────────────┼─────────────┤
│ Name              │             │
│ Description       │             │
│ System Units Base │ SYSTEM_BASE │
│ Base Power        │ 100.0       │
│ Base Frequency    │ 60.0        │
│ Num Components    │ 0           │
└───────────────────┴─────────────┘

Notice that this system is a 60 Hz system with a base power of 100 MVA.

Now, let's add some components to our system.

Adding Buses

We'll start by creating some buses. By referring to the documentation for ACBus, notice that we need define some basic data, including the bus's unique identifier and name, base voltage, and whether it's a load, generator, or reference bus.

Let's start with a reference bus:

julia> bus1 = ACBus(
               number = 1,
               name = "bus1",
               bustype = ACBusTypes.REF,
               angle = 0.0,
               magnitude = 1.0,
               voltage_limits = (min = 0.9, max = 1.05),
               base_voltage = 230.0
               );

This bus is on a 230 kV AC transmission network, with an allowable voltage range of 0.9 to 1.05 p.u. We are assuming it is currently operating at 1.0 p.u. voltage and an angle of 0 radians.

Let's add this bus to our System with add_component!:

julia> add_component!(sys, bus1)

We can see the impact this has on the System simply by printing it:

julia> sysSystem
┌───────────────────┬─────────────┐
│ Property          │ Value       │
├───────────────────┼─────────────┤
│ Name              │             │
│ Description       │             │
│ System Units Base │ SYSTEM_BASE │
│ Base Power        │ 100.0       │
│ Base Frequency    │ 60.0        │
│ Num Components    │ 1           │
└───────────────────┴─────────────┘

Static Components
┌───────┬───────┐
│ Type  │ Count │
├───────┼───────┤
│ ACBus │ 1     │
└───────┴───────┘

Notice that System now shows a summary of components in the system.

Let's create a second bus:

julia> bus2 = ACBus(
               number = 2,
               name = "bus2",
               bustype = ACBusTypes.PV,
               angle = 0.0,
               magnitude = 1.0,
               voltage_limits = (min = 0.9, max = 1.05),
               base_voltage = 230.0
               );

Notice that we've defined this bus with power and voltage variables, suitable for power flow studies.

Let's also add this to our System:

julia> add_component!(sys, bus2)

Now, let's use show_components to quickly see some basic information about the buses:

julia> show_components(sys, ACBus)ACBus
┌──────┐
│ name │
├──────┤
│ bus1 │
│ bus2 │
└──────┘

Adding a Transmission Line

Let's connect our buses. We'll add a transmission Line between bus1 and bus2.

Warning

When defining a line that isn't attached to a System yet, you must define the thermal rating of the transmission line in per-unit using the base power of the System you plan to connect it to – in this case, 100 MVA.

julia> line = Line(
               name = "line1",
               available = true,
               active_power_flow = 0.0,
               reactive_power_flow = 0.0,
               arc = Arc(from = bus1, to = bus2),
               r = 0.00281, # Per-unit
               x = 0.0281, # Per-unit
               b = (from = 0.00356, to = 0.00356), # Per-unit
               rating = 2.0, # Line rating of 200 MVA / System base of 100 MVA
               angle_limits = (min = -0.7, max = 0.7),
           );

Note that we also had to define an Arc in the process to define the connection between the two buses.

Let's also add this to our System:

julia> add_component!(sys, line)

Finally, let's check our System summary to see all the network topology components we have added are attached:

julia> sysSystem
┌───────────────────┬─────────────┐
│ Property          │ Value       │
├───────────────────┼─────────────┤
│ Name              │             │
│ Description       │             │
│ System Units Base │ SYSTEM_BASE │
│ Base Power        │ 100.0       │
│ Base Frequency    │ 60.0        │
│ Num Components    │ 4           │
└───────────────────┴─────────────┘

Static Components
┌───────┬───────┐
│ Type  │ Count │
├───────┼───────┤
│ ACBus │ 2     │
│ Arc   │ 1     │
│ Line  │ 1     │
└───────┴───────┘

Adding Loads and Generators

Now that our network topology is complete, we'll start adding components that inject or withdraw power from the network.

Warning

When you define components that aren't attached to a System yet, you must define all fields related to power (with units such as MW, MVA, MVAR, or MW/min) in per-unit using the base_power of the component.

We'll start with defining a 10 MW load to bus1:

julia> load = PowerLoad(
               name = "load1",
               available = true,
               bus = bus1,
               active_power = 0.0, # Per-unitized by device base_power
               reactive_power = 0.0, # Per-unitized by device base_power
               base_power = 10.0, # MVA
               max_active_power = 1.0, # 10 MW per-unitized by device base_power
               max_reactive_power = 0.0
               );

Notice that we defined the max_active_power, which is 10 MW, as 1.0 in per-unit using the base_power of 10 MVA. We've also used the bus1 component itself to define where this load is located in the network.

Now add the load to the system:

julia> add_component!(sys, load)

Finally, we'll add two generators: one renewable and one thermal.

We'll add a 5 MW solar power plant to bus2:

julia> solar = RenewableDispatch(
               name = "solar1",
               available = true,
               bus = bus2,
               active_power = 0.0, # Per-unitized by device base_power
               reactive_power = 0.0, # Per-unitized by device base_power
               rating = 1.0, # 5 MW per-unitized by device base_power
               prime_mover_type = PrimeMovers.PVe,
               reactive_power_limits = (min=0.0, max=0.05), # 0 MVAR to 0.25 MVAR per-unitized by device base_power
               power_factor = 1.0,
               operation_cost = RenewableGenerationCost(nothing),
               base_power = 5.0 # MVA
               );

Note that we've used a generic renewable generator to model solar, but we can specify that it is solar through the prime mover.

Finally, we'll also add a 30 MW gas thermal generator:

julia> gas = ThermalStandard(
               name = "gas1",
               available = true,
               status = true,
               bus = bus2,
               active_power = 0.0, # Per-unitized by device base_power
               reactive_power = 0.0, # Per-unitized by device base_power
               rating = 1.0, # 30 MW per-unitized by device base_power
               active_power_limits = (min=0.2, max=1.0), # 6 MW to 30 MW per-unitized by device base_power
               reactive_power_limits = nothing, # Per-unitized by device base_power
               ramp_limits = (up=0.2, down=0.2), # 6 MW/min up or down, per-unitized by device base_power
               operation_cost = ThermalGenerationCost(nothing),
               base_power = 30.0, # MVA
               time_limits = (up=8.0, down=8.0), # Hours
               must_run = false,
               prime_mover_type = PrimeMovers.CC,
               fuel = ThermalFuels.NATURAL_GAS
               );

This time, let's add these components to our System using add_components! to add them both at the same time:

julia> add_components!(sys, [solar, gas])

Explore the System and its Components

Congratulations! You have built a power system including buses, a transmission line, a load, and different types of generators. Now let's take a look around.

Remember that we can see a summary of our System using the print statement:

julia> sysSystem
┌───────────────────┬─────────────┐
│ Property          │ Value       │
├───────────────────┼─────────────┤
│ Name              │             │
│ Description       │             │
│ System Units Base │ SYSTEM_BASE │
│ Base Power        │ 100.0       │
│ Base Frequency    │ 60.0        │
│ Num Components    │ 7           │
└───────────────────┴─────────────┘

Static Components
┌───────────────────┬───────┐
│ Type              │ Count │
├───────────────────┼───────┤
│ ACBus             │ 2     │
│ Arc               │ 1     │
│ Line              │ 1     │
│ PowerLoad         │ 1     │
│ RenewableDispatch │ 1     │
│ ThermalStandard   │ 1     │
└───────────────────┴───────┘

Now, let's double-check some of our data by retrieving it from the System. Let's use show_components again to get an overview of our renewable generators:

julia> show_components(sys, RenewableDispatch)RenewableDispatch
┌────────┬───────────┐
│ name   │ available │
├────────┼───────────┤
│ solar1 │ true      │
└────────┴───────────┘

We just have the one renewable generator named solar1. Use get_component to retrieve it by name:

julia> retrieved_component = get_component(RenewableDispatch, sys, "solar1");

Let's double-check what type of renewable generator this is using a get_ function:

julia> get_prime_mover_type(retrieved_component)PrimeMovers.PVe = 21

Verify that this a PVe, or solar photovoltaic, generator.

Let's also use a get_ function to double-check where this generator is connected in the transmission network:

julia> get_bus(retrieved_component)ACBus: bus2:
   number: 2
   name: bus2
   bustype: ACBusTypes.PV = 2
   angle: 0.0
   magnitude: 1.0
   voltage_limits: (min = 0.9, max = 1.05)
   base_voltage: 230.0
   area: nothing
   load_zone: nothing
   ext: Dict{String, Any}()
   InfrastructureSystems.SystemUnitsSettings:
      base_value: 100.0
      unit_system: UnitSystem.SYSTEM_BASE = 0
   has_supplemental_attributes: false
   has_time_series: false

See that the generator's bus is linked to the actual bus2 component in our System.

These "getter" functions are available for all the data fields in a component.

Tip

Always use the get_* functions to retrieve the data within a component. While in Julia a user can use . to access the fields of a component, we make no guarantees on the stability of field names and locations. We do however promise to keep the getter functions stable. PowerSystems.jl also does many internal data calculations that the getter functions will properly handle for you, as you'll see below.

Changing System Per-Unit Settings

Now, let's use a getter function to look up the solar generator's rating:

julia> get_rating(retrieved_component)0.05
Important

When we defined the solar generator, we defined the rating as 1.0 per-unit with a device base_power of 5.0 MVA. Notice that the rating now reads 0.05. After we attached this component to our System, its power data is being returned to us in the System's units base.

Let's double-check the System's units base:

julia> get_units_base(sys)"SYSTEM_BASE"

SYSTEM_BASE means all power-related (MW, MVA, MVAR, MW/min) component data in the System, except for each component's base_power, is per-unitized by the system base power for consistency.

Check the System's base_power again:

julia> get_base_power(sys)100.0

Notice that when we called get_rating above, the solar generator's rating, 5.0 MW, is being returned as 0.05 = (5 MVA)/(100 MVA) using the system base power.

Instead of using the System base power, let's view everything in MW or MVA – or what we call "NATURAL_UNITS" in PowerSystems.

Change the System's unit system:

julia> set_units_base_system!(sys, "NATURAL_UNITS")[ Info: Unit System changed to UnitSystem.NATURAL_UNITS = 2

Now retrieve the solar generator's rating again:

julia> get_rating(retrieved_component)5.0

Notice that the value is now its "natural" value, 5.0 MVA.

Finally, let's change the System's unit system to the final option, "DEVICE_BASE":

julia> set_units_base_system!(sys, "DEVICE_BASE")[ Info: Unit System changed to UnitSystem.DEVICE_BASE = 1

And retrieve the solar generator's rating once more:

julia> get_rating(retrieved_component)1.0

See that now the data is now 1.0 (5.0 MVA per-unitized by the generator (i.e., the device's) base_power of 5.0 MVA), which is the format we used to originally define the device.

Recall that if you ever need to check a System's settings, including the unit system being used by all the getter functions, you can always just print the System:

julia> sysSystem
┌───────────────────┬─────────────┐
│ Property          │ Value       │
├───────────────────┼─────────────┤
│ Name              │             │
│ Description       │             │
│ System Units Base │ DEVICE_BASE │
│ Base Power        │ 100.0       │
│ Base Frequency    │ 60.0        │
│ Num Components    │ 7           │
└───────────────────┴─────────────┘

Static Components
┌───────────────────┬───────┐
│ Type              │ Count │
├───────────────────┼───────┤
│ ACBus             │ 2     │
│ Arc               │ 1     │
│ Line              │ 1     │
│ PowerLoad         │ 1     │
│ RenewableDispatch │ 1     │
│ ThermalStandard   │ 1     │
└───────────────────┴───────┘

See the units base is printed as one of the System properties.

Next Steps

In this tutorial, you manually created a power System, added and then retrieved its components, and modified the System per-unit settings.

Next, you might want to: