Python for Power Systems

A blog for power systems engineers to learn Python.

Designing an Easier PSSE Subsystem Data Retrieval API

I want to take you through a small function that we have written that will combine the entire PSSE subsystem data retrieval API into a single, easier to use function.

What does the original subsystem data retrieval api look like?

Have you ever looked at the PSSE subsystem data retrieval API (Chapter 8 in the PSSE API guide)? With it you can get information about branches and buses (and other elements like machines) for an entire subsystem. So how would we go about getting a list of all of the bus numbers in subsystem 2?

1
ierr, busnumbers = psspy.abusint(sid=2, string="NUMBER")

Things become tricky when we want the bus names and the bus numbers though.

1
2
ierr, busnumbers = psspy.abusint(sid=2, string="NUMBER")
ierr, busnames = psspy.abuschar(sid=2, string="NAME")

We had to know that the NAME attribute uses a different API call to the NUMBER attribute. If we wanted to get the bus per unit voltage we would need another API call again, and if we wanted the total in service fixed bus shunt in MW and MVar a fourth API call would be required.

four function calls is three too many
1
2
3
4
ierr, busnumbers = psspy.abusint(sid=2, string="NUMBER")
ierr, busnames = psspy.abuschar(sid=2, string="NAME")
ierr, busvoltages = psspy.abusreal(sid=2, string="PU")
ierr, bus_shunts = psspy.abuscplx(sid=2, string="SHUNTACT")

Each of the return values from the API is a nested list. If you wanted to get the name and pu voltage for bus number 340:

getting name and pu voltage for bus number 340
1
2
3
bus_index = busnumbers[0].index(340)
voltage = busvoltages[0][bus_index]
name = busnames[0][bus_index]

The resulting code can be extremely difficult to read, and quite verbose.

A wrapper around the old API

Using the new subsystem_info function is easy. Lets get the bus numbers, names, pu voltages and actual shunt values for subsystem id 2:

1
2
3
4
5
>>> businfo = subsystem_info('bus', ['NUMBER', 'NAME', 'PU', 'SHUNTACT'], sid=2)
>>> print businfo
[(205, 'CATDOG', 1.01, complex(0.4, 0)),
 (203, 'CATDOG2', 0.99, complex(0, 0)),
 ... ]

All of the information we were looking for is organised neatly into rows, rather than separate columns. Here is how we made that function.

How does this work?

The new function relies on some helpful design choices in the original PSSE subsystem data retrieval api.

Each of the functions are named using a regular pattern:

1
2
3
4
abusint,
abuscplx,
amachint,
aloadint

a element type api data type

a bus int

There is a lookup function called abustypes (we’ll call it types) which will return a character string that represents each of the api data types. For example

1
2
>>> psspy.abustypes("NUMBER")
"I"

We ask the types function about each of the attributes the user has requested. So a query like ["NUMBER", "NAME", "PU"] might return ["I", "R", "C"], being int, real and char respectively.

Use a dictionary to store functions

Ok, so we can find a character "I", "R", "C" that represents the API type. Translating that character into the correct retrieval function to use is the clever part.

There is a Python dictionary to look up the corresponding API call for the attribute requested. So asking for "NUMBER" which returns "I" from the types function will retrieve the psspy.abusint function from the dictionary. Using a dictionary to look up a function like this is called the ‘dispatch table pattern’ (example 4.16 in the Python Cookbook if you have a copy)

Grouping related calls to the API together

The difficult part is grouping and returning the API calls in rows and in the order they were requested. The itertools groupby function is used to group related API calls together so if we requested ["NUMBER", "TYPE", "NAME"] we might get ["I", "I", "C"] from abustypes.

The groupby will group the two consecutive “I” api calls together so we can make one function call:

1
abusint(string=["NUMBER", "TYPE"])

instead of two function calls:

1
2
abusint(string="NUMBER") # and
abusint(string="TYPE")

Transpose columns to rows

Finally, we use the built in zip function to transpose a list of columns into a list of rows

1
2
>>> zip(*[[1,2,3], [4,5,6]])
[(1,4), (2, 5), (3,6)]