Examples

For your convenience, several ESA usage examples are provided in this section. Some examples may also be presented earlier in the documentation. If you would like to share your own examples with us, please file an issue on GitHub. If you’d like even more examples (though in a much less reader friendly format), check out ESA’s tests, specifically the test_saw.py file.

ESA Quick Start

The following example is also presented in the Quick Start section.

This “quick start” example has several purposes:

  • Illustrate how ESA is used with a simple power system model.

  • Demonstrate how to perform common tasks (e.g. solving the power flow, retrieving simulation data such as bus voltages and power injections).

  • Show the usefulness of some of ESA’s high level helper functions which do more than simply wrap SimAuto functions.

Before running the example below, define a CASE_PATH constant (the file path to a PowerWorld .pwb case file) like so (adapt the path as needed for your system):

CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\ieee_14\IEEE 14 bus_pws_version_21.pwb"

On to the quick start!

Start by Importing the SimAuto Wrapper (SAW) class:

>>> from esa import SAW

Initialize SAW instance using 14 bus test case:

>>> saw = SAW(FileName=CASE_PATH)

Solve the power flow:

>>> saw.SolvePowerFlow()

Retrieve power flow results for buses. This will return a Pandas DataFrame to make your life easier.

>>> bus_data = saw.get_power_flow_results('bus')
>>> bus_data
    BusNum BusName  BusPUVolt   BusAngle    BusNetMW  BusNetMVR
0        1   Bus 1   1.060000   0.000000  232.391691 -16.549389
1        2   Bus 2   1.045000  -4.982553   18.300001  30.855957
2        3   Bus 3   1.010000 -12.725027  -94.199997   6.074852
3        4   Bus 4   1.017672 -10.312829  -47.799999   3.900000
4        5   Bus 5   1.019515  -8.773799   -7.600000  -1.600000
5        6   Bus 6   1.070000 -14.220869  -11.200000   5.229700
6        7   Bus 7   1.061520 -13.359558    0.000000   0.000000
7        8   Bus 8   1.090000 -13.359571    0.000000  17.623067
8        9   Bus 9   1.055933 -14.938458  -29.499999   4.584888
9       10  Bus 10   1.050986 -15.097221   -9.000000  -5.800000
10      11  Bus 11   1.056907 -14.790552   -3.500000  -1.800000
11      12  Bus 12   1.055189 -15.075512   -6.100000  -1.600000
12      13  Bus 13   1.050383 -15.156196  -13.500001  -5.800000
13      14  Bus 14   1.035531 -16.033565  -14.900000  -5.000000

Retrieve power flow results for generators:

>>> gen_data = saw.get_power_flow_results('gen')
>>> gen_data
   BusNum GenID       GenMW     GenMVR
0       1     1  232.391691 -16.549389
1       2     1   40.000001  43.555957
2       3     1    0.000000  25.074852
3       6     1    0.000000  12.729700
4       8     1    0.000000  17.623067

To learn more about variables such as GenMW, see PowerWorld Variables.

Let’s change generator injections! But first, we need to know which fields PowerWorld needs in order to identify generators. These fields are known as key fields.

>>> gen_key_fields = saw.get_key_field_list('gen')
>>> gen_key_fields
['BusNum', 'GenID']

Change generator active power injection at buses 3 and 8 via SimAuto function:

>>> params = gen_key_fields + ['GenMW']
>>> values = [[3, '1', 30], [8, '1', 50]]
>>> saw.ChangeParametersMultipleElement(ObjectType='gen', ParamList=params, ValueList=values)

Did changing generator active power injections work? Let’s confirm:

>>> new_gen_data = saw.GetParametersMultipleElement(ObjectType='gen', ParamList=params)
>>> new_gen_data
   BusNum GenID       GenMW
0       1     1  232.391691
1       2     1   40.000001
2       3     1   30.000001
3       6     1    0.000000
4       8     1   50.000000

It would seem the generator active power injections have changed. Let’s re-run the power flow and see if bus voltages and angles change. Spoiler: they do.

>>> saw.SolvePowerFlow()
>>> new_bus_data = saw.get_power_flow_results('bus')
>>> cols = ['BusPUVolt', 'BusAngle']
>>> diff = bus_data[cols] - new_bus_data[cols]
>>> diff
       BusPUVolt   BusAngle
0   0.000000e+00   0.000000
1  -1.100000e-07  -2.015596
2  -5.700000e-07  -4.813164
3  -8.650700e-03  -3.920185
4  -7.207540e-03  -3.238592
5  -5.900000e-07  -4.586528
6  -4.628790e-03  -7.309167
7  -3.190000e-06 -11.655362
8  -7.189370e-03  -6.284631
9  -6.256150e-03  -5.987861
10 -3.514030e-03  -5.297895
11 -2.400800e-04  -4.709888
12 -1.351040e-03  -4.827348
13 -4.736110e-03  -5.662158

Wouldn’t it be easier if we could change parameters with a DataFrame? Wouldn’t it be nice if we didn’t have to manually check if our updates were respected? You’re in luck!

Create a copy of the gen_data DataFrame so that we can modify its values and use it to update parameters in PowerWorld. Then, change the generation for the generators at buses 2, 3, and 6.

>>> gen_copy = gen_data.copy(deep=True)
>>> gen_copy.loc[gen_copy['BusNum'].isin([2, 3, 6]), 'GenMW'] = [0.0, 100.0, 100.0]
>>> gen_copy
   BusNum GenID       GenMW     GenMVR
0       1     1  232.391691 -16.549389
1       2     1    0.000000  43.555957
2       3     1  100.000000  25.074852
3       6     1  100.000000  12.729700
4       8     1    0.000000  17.623067

Use helper function change_and_confirm_params_multiple_element to both command the generators and to confirm that PowerWorld respected the command. This is incredibly useful because if you directly use ChangeParametersMultipleElements, PowerWorld may unexpectedly not update the parameter you tried to change! If the following does not raise an exception, we’re in good shape (it doesn’t)!

>>> saw.change_and_confirm_params_multiple_element(ObjectType='gen', command_df=gen_copy.drop('GenMVR', axis=1))

Run the power flow and observe the change in generation at the slack bus (bus 1):

>>> saw.SolvePowerFlow()
>>> new_gen_data = saw.get_power_flow_results('gen')
>>> new_gen_data
   BusNum GenID       GenMW     GenMVR
0       1     1   62.128144  14.986289
1       2     1    0.000000  10.385347
2       3     1  100.000000   0.000000
3       6     1  100.000000  -3.893420
4       8     1    0.000000  17.399502

What if we try to change generator voltage set points? Start by getting a DataFrame with the current settings. Remember to always access the key fields so that when we want to update parameters later PowerWorld knows how to find the generators.

>>> gen_v = saw.GetParametersMultipleElement('gen', gen_key_fields + ['GenRegPUVolt'])
>>> gen_v
   BusNum GenID  GenRegPUVolt
0       1     1      1.060000
1       2     1      1.045000
2       3     1      1.025425
3       6     1      1.070000
4       8     1      1.090000

Now, change all voltage set points to 1 per unit:

>>> gen_v['GenRegPUVolt'] = 1.0
>>> gen_v
   BusNum GenID  GenRegPUVolt
0       1     1           1.0
1       2     1           1.0
2       3     1           1.0
3       6     1           1.0
4       8     1           1.0

>>> saw.change_and_confirm_params_multiple_element('gen', gen_v)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\myuser\git\ESA\esa\saw.py", line 199, in change_and_confirm_params_multiple_element
    raise CommandNotRespectedError(m)
esa.saw.CommandNotRespectedError: After calling ChangeParametersMultipleElement, not all parameters were actually changed within PowerWorld. Try again with a different parameter (e.g. use GenVoltSet instead of GenRegPUVolt).

So, PowerWorld didn’t respect that command, but we’ve been saved from future confusion by the change_and_confirm_params_multiple_element helper function.

Let’s call the LoadState SimAuto function:

>>> saw.LoadState()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\myuser\git\ESA\esa\saw.py", line 967, in LoadState
    return self._call_simauto('LoadState')
  File "C:\Users\myuser\git\ESA\esa\saw.py", line 1227, in _call_simauto
    raise PowerWorldError(output[0])
esa.saw.PowerWorldError: LoadState: State hasn't been previously stored.

This behavior is expected - it is not valid to call LoadState if SaveState has not yet been called. In the exception above, not that a PowerWorldError is raised. This empowers users to handle exceptions in whatever manner they see fit:

>>> from esa import PowerWorldError
>>> try:
...     saw.LoadState()
... except PowerWorldError:
...     print("Oh my, we've encountered a PowerWorldError!")
...
Oh my, we've encountered a PowerWorldError!

Finally, make sure to clean up after yourself so you don’t have COM objects hanging around.

>>> saw.exit()

After walking through this quick start, you should be ready to start using ESA to improve your simulation and analysis work flows!

Increase Loading in Case

The following example is also presented in the What Is ESA? section.

This simple example uniformly increases the loading in a power system model by 50%.

If you want to follow along, you’ll first need to define your own CASE_PATH constant (the file path to a PowerWorld .pwb case file), like so (adapt the path for your system):

CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\ieee_14\IEEE 14 bus_pws_version_21.pwb"

Then, import the SimAuto wrapper (SAW) class and initialize an instance:

>>> from esa import SAW
>>> saw = SAW(CASE_PATH)

Retrieve key fields for loads:

>>> kf = saw.get_key_field_list('load')
>>> kf
['BusNum', 'LoadID']

Pull load data including active and reactive power demand:

>>> load_frame = saw.GetParametersMultipleElement('load', kf + ['LoadSMW', 'LoadSMVR'])
>>> load_frame
    BusNum LoadID    LoadSMW   LoadSMVR
0        2      1  21.699999  12.700000
1        3      1  94.199997  19.000000
2        4      1  47.799999  -3.900000
3        5      1   7.600000   1.600000
4        6      1  11.200000   7.500000
5        9      1  29.499999  16.599999
6       10      1   9.000000   5.800000
7       11      1   3.500000   1.800000
8       12      1   6.100000   1.600000
9       13      1  13.500001   5.800000
10      14      1  14.900000   5.000000

To learn more about variables such as LoadSMW, see PowerWorld Variables.

Uniformly increase loading by 50% and solve the power flow:

>>> load_frame[['LoadSMW', 'LoadSMVR']] *= 1.5
>>> saw.change_parameters_multiple_element_df('load', load_frame)
>>> saw.SolvePowerFlow()

Let’s confirm that the loading did indeed increase:

>>> new_loads = saw.GetParametersMultipleElement('load', kf + ['LoadSMW', 'LoadSMVR'])
>>> new_loads
    BusNum LoadID     LoadSMW   LoadSMVR
0        2      1   32.549998  19.050001
1        3      1  141.299999  28.500000
2        4      1   71.699995  -5.850000
3        5      1   11.400000   2.400000
4        6      1   16.800001  11.250000
5        9      1   44.250000  24.900000
6       10      1   13.500001   8.700000
7       11      1    5.250000   2.700000
8       12      1    9.150000   2.400000
9       13      1   20.250002   8.700000
10      14      1   22.350000   7.500000

Clean up when done:

>>> saw.exit()

Easy, isn’t it?

Add Lines to Case

This example shows how to add transmission lines to a model.

Before starting the example, please define the constants CASE_PATH (the file path to a PowerWorld .pwb case file) and CANDIDATE_LINES (file path to a .csv file with data related to lines we’d like to add to the model) like the following, adapting paths to your system. You can find the case and .csv file referenced in the tests directory of the ESA repository.

CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\tx2000\tx2000_base_pws_version_21.pwb"
CANDIDATE_LINES = r"C:\Users\myuser\git\ESA\tests\data\CandidateLines.csv"

Import packages/classes and read the CANDIDATE_LINES .csv file.

>>> from esa import SAW
>>> import pandas as pd
>>> line_df = pd.read_csv(CANDIDATE_LINES)
>>> line_df
   From Number  To Number  Ckt        R        X        B  Lim MVA A
0         8155       5358    3  0.00037  0.00750  0.52342       2768
1         8154       8135    3  0.00895  0.03991  0.00585        149
2         8153       8108    3  0.01300  0.05400  0.02700        186
3         8152       8160    3  0.00538  0.03751  0.00613        221
4         8155       8057    3  0.00037  0.00750  0.52342       2768
5         8154       8153    3  0.01300  0.05400  0.02700        186
6         8155       8135    3  0.00538  0.03751  0.00613        221

Instantiate a SAW object. Set CreateIfNotFound to True so that new lines can be added:

>>> saw=SAW(FileName=CASE_PATH, CreateIfNotFound=True, early_bind=True)

Rename columns in the line_df to match PowerWorld variables. We are renaming variables from the “Concise Variable Name” convention to the “Variable Name” convention. See power_world_object_fields.xlsx. Also note this issue is also relevant. To learn more about PowerWorld variables, see PowerWorld Variables.

>>> line_df.rename(
... columns={
... 'From Number': 'BusNum',
... 'To Number': 'BusNum:1',
... 'Ckt': 'LineCircuit',
... 'R': 'LineR',
... 'X': 'LineX',
... 'B': 'LineC',
... 'Lim MVA A': 'LineAMVA'
... },
... inplace=True)
>>> line_df.columns
Index(['BusNum', 'BusNum:1', 'LineCircuit', 'LineR', 'LineX', 'LineC',
       'LineAMVA'],
      dtype='object')

Secondary and tertiary limits are required fields that we must add manually, since they were not present in the .csv file:

>>> line_df['LineAMVA:1'] = 0.0
>>> line_df['LineAMVA:2'] = 0.0

Check to see if the first line is actually present. An error will indicate that it’s not.

>>> line_key_fields = saw.get_key_field_list('branch')
>>> line_key_fields
['BusNum', 'BusNum:1', 'LineCircuit']
>>> first_line = saw.GetParametersSingleElement('branch', line_key_fields, line_df.loc[0, line_key_fields].tolist())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\myuser\git\ESA\esa\saw.py", line 693, in GetParametersSingleElement
    output = self._call_simauto('GetParametersSingleElement', ObjectType,
  File "C:\Users\myuser\git\ESA\esa\saw.py", line 1227, in _call_simauto
    raise PowerWorldError(output[0])
esa.saw.PowerWorldError: GetParameters: Object not found

Enter edit mode to enable the creation of new devices, and use the change_and_confirm_params_multiple_element helper function to easily create the lines. This function will automagically confirm that the lines will be created.

>>> saw.RunScriptCommand("EnterMode(EDIT);")
>>> saw.change_and_confirm_params_multiple_element('branch', line_df)

Now, we should be able to find that first line without error:

>>> first_line = saw.GetParametersSingleElement('branch', line_key_fields, line_df.loc[0, line_key_fields].tolist())
>>> first_line
BusNum         8152
BusNum:1       8160
LineCircuit       3
dtype: object

Always clean up:

>>> saw.exit()

Transient Stability Analysis

This example illustrates the procedure to perform transient stability(TS) analysis and obtain the TS result. To retrieve the result, the most convenient way is to create a plot object and connect the object/fields pairs to it, then you will be able to query it using the TSGetCongencyResults function.

CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\il200\ACTIVSg200.pwb"

Load the case first and then solve a PF (optional):

>>> from esa import SAW
>>> saw = SAW(CASE_PATH)
>>> saw.SolvePowerFlow()

Then perform TS analysis (make sure you already have a desired plot object)

>>> t1 = 0.0
>>> t2 = 15.0
>>> stepsize = 0.01

    # Solve.
>>> cmd = 'TSSolve("{}",[{},{},{},NO])'.format(
        self.ctg_name, t1, t2, stepsize
    )
>>> saw.RunScriptCommand(cmd)

Once it is done, you could retrieve (and visualize) the results:

>>> objFieldList = ['Plot ''Area_Avg Bus Hz''']  # "Area_Avg Bus Hz" is the plot name
>>> result = sa.TSGetContingencyResults("My Transient Contingency", objFieldList, 0, 12)  # "My Transient Contingency" is the contingency name
>>> df = result[1]  #result[0] is meta data
>>> df.columns = ['Time (s)', 'Area_Avg Bus Hz']
>>> df.plot(x='Time (s)', y='Area_Avg Bus Hz')
https://github.com/mzy2240/ESA/raw/develop/docs/rst/snippets/ts_result.png

The whole process, including setting up plots and creating contingencies, could be fully automated, but it might be easier for most users to pre-define the plots and contingencies in the case and then load the case using ESA. GetParametersMultipleElement cannot be used here to retrieve the TS datapoints (which is a very rare situation).

10/26/2022 Update You could access and modify the TS-related objects and fields using ESA. In other words, no need to create and use the aux file for those tasks. If you encounter errors when accessing some of the objects, setting pw_order (a SAW property) to true might solve the issue.

Fast Contingency Analysis

This example shows how to do fast contingency analysis (N-1 & N-2) using ESA. The fast contingency analysis is a slightly improved implementation of this paper. It is generally much faster than the built-in CA that simulator provides (which, by the way, could also be invoked from ESA).

The initialization procedure is the same as others.

>>> CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\tx2000\tx2000_base_pws_version_21.pwb"
>>> from esa import SAW
>>> saw = SAW(CASE_PATH)

Make sure your case already has a valid operating states. If not, run power flow first:

>>> saw.SolvePowerFlow()

Then let’s run N-1 first.

>>> saw.run_contingency_analysis('N-1')
The size of N-1 islanding set is 451.0
Fast N-1 analysis was performed, 156 dangerous N-1 contigencies were found, 138 lines are violated
Grid is not N-1 secure. Invoke n1_protect function to automatically increasing limits through lines.
Out: (False, array([0, 0, 0, ..., 1, 0, 0]), None)

So the test system (TX2000) is not N-1 secured. In this case, when running N-2, the line limit will be automatically adjusted to ensure no N-1 violations. Based on the use case you have, you could adjust the line limits manually as well.

>>> saw.run_contingency_analysis('N-2')
https://github.com/mzy2240/ESA/raw/develop/docs/rst/snippets/n-2.gif

You could also validate the fast CA result with the built-in CA result by simply set the argument validate=True when calling run_contingency_analysis function.

Contingency Analysis using PW Built-in capability

This example shows how to perform the contingency analysis via PW’s built-in capability. We assume you already have the aux file that contains all the contingencies.

The initialization procedure is the same as others.

>>> CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\tx2000\tx2000_base_pws_version_21.pwb"
>>> from esa import SAW
>>> saw = SAW(CASE_PATH, CreateIfNotFound=True)
>>> saw.pw_order = True

Make sure your case already has a valid operating states. If not, run power flow first:

>>> saw.SolvePowerFlow()

Then load the auxiliary file into Powerworld.

>>> filepath_aux = r"C:\Users\myuser\git\ESA\tests\cases\tx2000\tx2000_contingency_auxfile.aux"
>>> saw.ProcessAuxFile(filepath_aux)

Run the powerworld script command to solve all the contingencies that are not set to skip in the loaded auxiliary file.

>>> cmd_solve = 'CTGSolveAll({},{})'.format('NO','YES')
>>> saw.RunScriptCommand(cmd_solve)

Use ESA to obtain the CA result

>>> result = saw.GetParametersMultipleElement('Contingency', ['CTGLabel', 'CTGSolved', 'CTGProc', 'CTGCustMonViol', 'CTGViol'])

The result is presented in a Pandas DataFrame.

Create Simple Graph Model

This example shows how to easily transform a grid model into a graph supported by NetworkX. NetworkX is a popular Python package for analyzing graph structure, building network models and designing new network algorithms. You’ll first need to install NetworkX into your virtual environment (which should be activated!), which is most easily done by:

python -m pip install networkx

Before following along with the example, define the CASE_PATH constant (the file path to a PowerWorld .pwb case file) like so, adapting the path to your system:

CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\tx2000\tx2000_base_pws_version_21.pwb"

On to the example!

Perform imports, initialize a SAW instance:

>>> from esa import SAW
>>> import pandas as pd
>>> import networkx as nx
>>> saw = SAW(CASE_PATH, early_bind=True)

Get a DataFrame with all branches (lines, transformers, etc.):

>>> kf = saw.get_key_field_list('branch')
>>> kf
['BusNum', 'BusNum:1', 'LineCircuit']
>>> branch_df = saw.GetParametersMultipleElement('branch', kf)
>>> branch_df
      BusNum  BusNum:1 LineCircuit
0       1001      1064           1
1       1001      1064           2
2       1001      1071           1
3       1001      1071           2
4       1002      1007           1
...      ...       ...         ...
3199    8157      5124           1
3200    8157      8156           1
3201    8158      8030           1
3202    8159      8158           1
3203    8160      8159           1

[3204 rows x 3 columns]

To learn more about variables such as LineCircuit, see PowerWorld Variables.

Create the graph from the DataFrame. Yes, it is this simple. Use Graph instead of MultiGraph if there are no parallel branches.

>>> graph = nx.from_pandas_edgelist(branch_df, "BusNum", "BusNum:1", create_using=nx.MultiGraph)
>>> graph.number_of_nodes()
2000
>>> graph.number_of_edges()
3204

Clean up:

saw.exit()

Created Graph Model with Edges Weighted by Branch Impedance

This example shows how one can create a weighted graph using branch impedance values from a PowerWorld grid model as weights. You’ll need to have the NetworkX Python package installed into your virtual environment in order to execute this example on your machine (python -m pip install networkx).

Please note that this example does NOT work with Simulator version 17 ( and possibly other versions of Simulator older than version 21). For an unknown reason, PowerWorld itself throws an exception when trying to run the SaveYbusInMatlabFormat script command. If you have a solution to this problem, please file an issue.

Before following along with the example, define the CASE_PATH constant (the file path to a PowerWorld .pwb case file) like so, adapting the path to your system:

CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\ieee_14\IEEE 14 bus_pws_version_21.pwb"

Onward!

Imports and initialization:

>>> import networkx as nx
>>> from esa import SAW
>>> import re
>>> import os
>>> saw = SAW(CASE_PATH, early_bind=True)
>>> g = nx.Graph()

Save YBus matrix to file:

>>> ybus_file = CASE_PATH.replace('pwb', 'mat')
>>> cmd = 'SaveYbusInMatlabFormat("{}", NO)'.format(ybus_file)
>>> saw.RunScriptCommand(cmd)

Read YBus matrix file into memory. The first two lines are skipped via the readline method because they aren’t needed.

>>> with open(ybus_file, 'r') as f:
...     f.readline()
...     f.readline()
...     mat_str = f.read()
...
'j = sqrt(-1);\n'
'Ybus = sparse(14);\n'

We’re done with the file itself now. Remove it:

>>> os.remove(ybus_file)

Remove all white space, split by semicolons, and define a couple regular expressions (ie –> integer expression, fe –> float expression):

>>> mat_str = re.sub(r'\s', '', mat_str)
>>> lines = re.split(';', mat_str)
>>> ie = r'[0-9]+'
>>> fe = r'-*[0-9]+\.[0-9]+'
>>> exp = re.compile(r'(?:Ybus\()({ie}),({ie})(?:\)=)({fe})(?:\+j\*)(?:\()({fe})'.format(ie=ie, fe=fe))

Loop over the lines from the file and build up the graph. Ignore diagonal Y bus matrix entries and buses which are not connected (have 0 admittance between them).

>>> for line in lines:
...     match = exp.match(line)
...     if match is None:
...         continue
...     idx1, idx2, real, imag = match.groups()
...     if idx1 == idx2:
...         continue
...     neg_admittance = float(real) + 1j * float(imag)
...     try:
...         impedance = -1 / neg_admittance
...     except ZeroDivisionError:
...         continue
...     g.add_edge(int(idx1), int(idx2), r=impedance.real, x=impedance.imag)
...

Explore some graph properties to ensure it worked:

>>> g.number_of_nodes()
14
>>> g.number_of_edges()
20
>>> data_1_2 = g.get_edge_data(1, 2)
>>> data_1_2['r']
0.01937987032338931
>>> data_1_2['x']
0.05917003035204804

As always, clean up when done:

>>> saw.exit()

Plot Histogram of Line Flows with Matplotlib

This examples shows how to make a histogram of percent line loading in a power system model using SimAuto, ESA, and Matplotlib.

Matplotlib is a “comprehensive library for creating static, animated, and interactive visualizations in Python.” You’ll first need to install Matplotlib into your virtual environment (which should be activated!), which is most easily done by:

python -m pip install -U matplotlib

Before following along with the example, define the CASE_PATH constant (the file path to a PowerWorld .pwb case file) like so, adapting the path to your system.

CASE_PATH = r"C:\Users\myuser\git\ESA\tests\cases\tx2000\tx2000_base_pws_version_21.pwb"

Now let’s get started!

Perform imports, initialize a SAW instance:

>>> from esa import SAW
>>> import matplotlib.pyplot as plt

Initialize SAW instance using 2000 bus test case:

>>> saw = SAW(FileName=CASE_PATH)

Solve the power flow:

>>> saw.SolvePowerFlow()

Let’s obtain line loading percentages. But first, we need to know which fields PowerWorld needs in order to identify branches. These fields are known as key fields.

>>> branch_key_fields = saw.get_key_field_list('Branch')
>>> branch_key_fields
['BusNum', 'BusNum:1', 'LineCircuit']

Get line loading percentage at all buses via SimAuto function:

>>> params = branch_key_fields + ['LinePercent']
>>> branch_data = saw.GetParametersMultipleElement(ObjectType='Branch', ParamList=params)
>>> branch_data
      BusNum  BusNum:1 LineCircuit  LinePercent
0       1001      1064           1    30.879348
1       1001      1064           2    30.879348
2       1001      1071           1    35.731801
3       1001      1071           2    35.731801
4       1002      1007           1     5.342946
...      ...       ...         ...          ...
3199    8157      5124           1    36.371236
3200    8157      8156           1    46.769588
3201    8158      8030           1    25.982494
3202    8159      8158           1    43.641971
3203    8160      8159           1    57.452701

[3204 rows x 4 columns]

To learn more about variables such as LinePercent, see PowerWorld Variables.

Then let’s start to plot with Matplotlib!

>>> axes = branch_data.plot(kind='hist', y='LinePercent')
>>> axes.set_xlabel('Line Percent Loading')
Text(0.5, 0, 'Line Percent Loading')
>>> axes.set_ylabel('Number of Lines')
Text(0, 0.5, 'Number of Lines')
>>> axes.set_title('Histogram of Line Loading')
Text(0.5, 1.0, 'Histogram of Line Loading')
>>> plt.show(block=False)

The results should look like:

https://github.com/mzy2240/ESA/raw/develop/docs/rst/snippets/line_loading_histogram.png