How to Simulate an Entire Day using Time Series Simulation in PandaPower

This post will demonstrate how to simulate network power flows using an entire day’s load profile using pandapower. This kind of simulation allows a network operator to see how their network will operate throughout a day as the load varies.

This is called a time series simulation in pandapower and we will use the IEEE 14 bus test system included with panadapower, with some small modifications, for this demonstration.

Load the Relevant Packages in Python

We will be using pandapower and its control, timeseries and datasources libraries in addition to numpy for building the load profile array and pandapower plotting as well as matplotlib for visualization.

import numpy as np
import pandas as pd
import pandapower as pp
import pandapower.control as control
import pandapower.networks as nw
import pandapower.timeseries as timeseries
from pandapower.timeseries.data_sources.frame_data import DFData
import pandapower.plotting as plt
import matplotlib.pyplot as mplt            
        

Load and Display the IEEE 14 Bus Network

As stated before, we will be using the IEEE 14 bus network for this demonstration. Pandapower has it included as a built in network in its networks library imported above. We load and display the network using the following code:

net = nw.case14()
plt.simple_plot(net,  plot_loads=True, plot_gens=True)            
        

This results in the image of the network below

Display the Network Info

We can display a summary of the network by just using the network’s name. This will give us a quick overview of the network and its elements.

net

This pandapower network includes the following parameter tables:
   - bus (14 elements)
   - load (11 elements)
   - gen (4 elements)
   - shunt (1 element)
   - ext_grid (1 element)
   - line (15 elements)
   - trafo (5 elements)
   - poly_cost (5 elements)
   - controller (2 elements)
   - bus_geodata (14 elements)
   - output_writer (1 element)
 and the following results tables:
   - res_bus (14 elements)
   - res_line (15 elements)
   - res_trafo (5 elements)
   - res_ext_grid (1 element)
   - res_load (11 elements)
   - res_shunt (1 element)
   - res_gen (4 elements)
        

Configure the Network

We make some modifications to the network to establish a good starting point for this demonstration. This step is not strictly necessary but it helps to show how you can modify the network to represent what is desired.

We will be using a distributed slack for the powerflow so that each generator will automatically contribute to serving the load throughout the day.

net.gen.slack = True
net.ext_grid['in_service'] = False
net.gen['vm_pu'] = 1.045
        

Set the Number of Time Slices for the Simulation

For this demo we will be simulating a one day load profile with a resolution of one (1) hour, therefore we have 24 hours and so we need 24 time slices.

n_ts =  24
        

Create a Load Scaling Profile to Match the Desired Load Profile

The approach we will take here is to leave all loads with their default values and scale them at each time slice to achieve the desired profile. We will apply th he same scaling factor to all loads. Since we have 11 loads and 24 time slices we need a 24x11 array to store these scaling factors.

lsf = np.zeros([24,11])
l_indx = np.ones([1,11])
lsf_values = np.array([0.863508968609865,
                       0.833239910313901,
                        0.802410313901345,
                        0.74929932735426,
                        0.735566143497758,
                        0.743974215246637,
                        0.781109865470852,
                        0.830297085201794,
                        0.88887331838565,
                        0.964405829596413,
                        0.975756726457399,
                        0.996356502242152,
                        1,
                        0.982763452914798,
                        0.943806053811659,
                        0.916900224215247,
                        0.925308295964125,
                        0.943806053811659,
                        0.928531390134529,
                        0.944226457399103,
                        0.932455156950673,
                        0.876541479820628,
                        0.871776905829596,
                        0.821748878923767
])

for i in range(n_ts):
    lsf[i,:] = l_indx * lsf_values[i]
        

Plot the Desired Load Profile

Plot the load profile scaling factors so we can see that the profile is as desired.

mplt.plot(lsf_values)
        

This creates the image of the load scaling profile shown below.

Create a Data Frame and Corresponding Data Source Object for Load Scaling Profile

In this step we create the pandas dataframe that will hold the scaling profile above for the loads. After the dataframe is created we use it to create a datasource to pass to pandapower to adjust the values on each timestep.

df = pd.DataFrame(lsf, index=list(range(n_ts)), columns=net.load.index)
ds = DFData(df)
        

Create a Controller to Adjust the Load Scaling According to the Desired Profile

Pandapower uses controllers to adjust the desired system parameters during each timestep. Here we will create a controller to adjust the load scaling during each timestep according to the profile above.

const_load = control.ConstControl(net, element='load', element_index=net.load.index,
            variable='scaling', data_source=ds, profile_name=net.load.index)
        

Create a Output Data Writer to Write the Time Series Results to Corresponding Files

In this step we will setup a writer to write the result of the timestep simulation to a file. We set the output patch and the file type. In this case we save results as excel files in a folder called ‘case14bus’.

ow = timeseries.OutputWriter(net, output_path="./case14bus", output_file_type=".xlsx")  
        

Configure which Variables to Save

Now that we have our writer to write the results, we need to tell it what results to write. For this demo we will save the per unit bus voltages, the line loadings in percentage, the output MW of the generators and the sum of the load MW. We do this with the following code:

ow.log_variable('res_bus', 'vm_pu')
ow.log_variable('res_line', 'loading_percent')
ow.log_variable('res_gen', 'p_mw')
ow.log_variable('res_load', 'p_mw', eval_function=lambda x: x.sum(0))
        

Run the Time Series Simulation

Now we can run the actual time series simulation. This will run the time series simulation and save the results in the format and to the folder specified above by the output writer.

timeseries.run_timeseries(net)  
        

Read the Result Files and Plot the Results

In this step we read the saved files and plot them for a quick visualization of how the saved parameters varied throughout the simulation, in our case a day.

total_load = pd.read_excel('case14bus/res_load/p_mw.xlsx', index_col=0)  
total_load.plot(legend=False, title='Total Load')
        

This creates the following plot of the actual load profile for the day:

Now we can also plot the output of the generators for the day as well. We do that with the following code:

gen = pd.read_excel('case14bus/res_gen/p_mw.xlsx', index_col=0)  
ax = gen.plot(title='Generator Output (MW)')
ax.set_xlabel('time')
ax.set_ylabel('output (MW)')
ax.legend(bbox_to_anchor=(1,1))
        

Giving the following plots:

gen-outputs

The same cn be done for the bus voltages and line loadings. For the bus voltages, we use the following code:

bus_vpu = pd.read_excel('case14bus/res_bus/vm_pu.xlsx', index_col=0)  
ax = bus_vpu.plot(title='Bus Voltages (pu)')
ax.set_xlabel('time')
ax.set_ylabel('voltage (pu)')
ax.legend(bbox_to_anchor=(1,1))                  
        

Which produces the following plots:

For the line loadings we will demonstrate how you could also plot the results without reading in the output files but by using the output writer itself. We do this as shown below:

ax = (ow.output['res_line.loading_percent']*100).plot()
ax.set_title('Line Loading (%)')
ax.set_xlabel('time')
ax.set_ylabel('loading')
ax.legend(bbox_to_anchor=(1,1))            
        

That's it guys. Now you can simulate an entire day of power flows using pandapower.

Don't forget to share this article with anyone you think would find it interesting or could benefit from it.

Happy coding :)