Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Учебники / 0841558_16EA1_federico_milano_power_system_modelling_and_scripting

.pdf
Скачиваний:
82
Добавлен:
08.06.2015
Размер:
6.72 Mб
Скачать

3.5 Python Scripting Language

45

self.params = {’a’:0.0, ’b’:0.0, ’c’:0.0} self.setup()

def fcall(self, x):

fvec = self.c + x*(self.b + x*self.a) return sum(fvec)

def dfcall(self, x):

dfvec = self.b + 2.0*x*self.a return sum(dfvec)

class sine(base):

"""sine function class"""

def init (self):

base. init (self)

self.params = {’A’:0.0, ’omega’:0.0, ’phi’:0.0} self.setup()

def fcall(self, x):

fvec = mul(self.A, sin(self.omega*x + self.phi)) return sum(fvec)

def dfcall(self, x):

dfvec = mul(mul(self.A, self.omega), cos(self.omega*x + self.phi))

return sum(dfvec)

As discussed above, the two classes poly and sine inherit the methods of the class base. Thus, to complete these classes it su ces to define function parameters and the methods fcall and fxcall. It is important to note that any other function fi(x) can be defined using the basic structure of the classes poly and sine. The functions mul and cos imported from the cvxopt package compute the element-by-element products and cosine, respectively, of double-precision arrays (see also Appendix A).

Figure 2.3 of Chapter 2 shows a dotted line for indicating an interface that takes care of the communication between the function fi(x) classes and the procedures. This interface is not strictly necessary but allows further generalizing the code and simplifies adding new fi(x) classes. In this example, the interface is implemented as the following class function.

class function():

"""interface for all specific function classes"""

def init (self, flist):

46

 

 

 

 

3 Power System Scripting

 

self.flist = flist

 

 

for item in self.flist:

 

self.

dict [item] = eval(item + ’()’)

def

setup(self):

 

 

for item in self.flist:

 

if self. dict

[item].n:

 

self. dict

[item].list2matrix()

def

fcall(self, x):

 

 

f = 0

 

 

 

 

 

for item in self.flist:

 

if self. dict

[item].n:

 

f += self. dict [item].fcall(x)

 

return f

 

 

 

 

def

dfcall(self, x):

 

 

df = 0

 

 

 

 

 

for item in self.flist:

 

if self. dict

[item].n:

 

df += self. dict [item].dfcall(x)

 

return df

 

 

 

 

flist = [’poly’, ’sine’]

 

Function = function(flist)

 

The constructor

 

init

 

of the class function requires as input the list

(flist) of all defined function classes. In this case, the list is composed of only two items, but can be easily extended. The meta-dictionary dict and the meta-function eval allow using a general approach for handling all fi(x). By means of dict and eval, adding a new function fi(x) requires only including a new item in the function list flist apart from the definition of the new fi(x) class. But no procedure has to be modified. Thus, metaprogramming and dynamical typing simplify the expansion of a project at the cost of an higher level of abstraction. The last line defines an instance Function of the class function. This instance is used by all procedures.

The kernel of the application is a routine run that collects all settings, calls the data parser, sets up the functions fi(x), launches the solver and, if required, executes the 2D plot.

def run(datafile, x0=0.0, plot=True, imax=20, tol=1e-5):

"""initialize function and run appropriate routines"""

if not datafile:

print ’* Error: A data file must be defined!’ print ’* Type "dome -h" for help.’ sys.exit(1)

3.5 Python Scripting Language

47

read(datafile)

Function.setup()

solve(x0, imax, tol)

if plot: fplot(x0)

The settings are kept minimal for simplicity but one can easily include any adjustments in the form of input parameters. For example one could allow choosing among di erent solvers. In this case, available settings are the initial guess x0, the maximum number of iterations imax, the tolerance tol for the Newton’s method, and a Boolean parameter plot that enforces the graphical output. Finally, observe the use of default values for the settings.

The parser of the data file is implemented in the routine read. The input data is a plain text ascii file, as follows:

Poly

1.0

4.0

-12.0

Sine

3.0

5.0

-1.57079632679

Sine

5.0

3.0

0.0

The parser for this format is given below.

def read(datafile):

"""parse input data in plain text format"""

fid = open(datafile, ’rt’)

for line in fid:

data = line.split()

if not len(data): continue if data[0] == ’Poly’:

Function.poly.add(a = float(data[1]), b = float(data[2]), c = float(data[3]))

elif data[0] == ’Sine’: Function.sine.add(A = float(data[1]),

omega = float(data[2]), phi = float(data[3]))

fid.close()

The parser directly calls the methods of the classes poly and sine. Thus, the parser has to know the parameters of these classes. Parsers are thus a fragile part of the application, since a change in the classes that implements functions fi(x) can make the parser inconsistent.

The solver solve implements a standard Newton’s method. The solver only interacts with the instance Function of the interface class and does not know anything about the specific functions fi(x).

48

3 Power System Scripting

def solve(x0 = 0.0, imax = 20, tol = 1e-5):

"""simple Newton’s method"""

f = 1.0 iteration = 0 x = x0

while abs(f) > tol:

if iteration > imax: break f = Function.fcall(x)

df = Function.dfcall(x) inc = f/df

print ’Convergence error: %.8f’ % inc x -= inc

iteration += 1

if iteration <= imax:

print ’The solution is x = %.5f’ % x else:

print ’Reached maximum number of iterations’

The routine fplot takes care of graphical operations. In particular, fplot generates a 2D plot of the complete function f (x) in a given interval around the initial guess x0. This can be useful to double-check the solution obtained using the Newton’s method. Most lines in the routine fplot are needed for defining the format of the 2D plot and are strictly dependent on the graphical module (Matplotlib in this case).

def fplot(x0):

"""plot f(x) in the neighborhood of the initial guess"""

#build x and f vectors points = 200

xmin = x0 - 5.0 xmax = x0 + 5.0

xvec = linspace(xmin, xmax, num = points, endpoint = True) fvec = matrix(0, (points, 1), ’d’)

for item, x in enumerate(xvec): fvec[item] = Function.fcall(x)

#graphical commands

fig = pyplot.figure() pyplot.hold(True) pyplot.plot(xvec, fvec, ’k’)

pyplot.axhline(linestyle = ’:’, color = ’k’) pyplot.axvline(linestyle = ’:’, color = ’k’) pyplot.xlabel(’$x$’)

pyplot.ylabel(’$f(x)$’) pyplot.savefig(’zeroplot.eps’, format=’eps’) pyplot.show()

3.5 Python Scripting Language

49

Finally, any script requires some command line interface to properly interact with the user. The following routine main accomplishes this task.

def main():

"""parse settings and launch solver"""

parser = OptionParser(version=’ ’) parser.add option(’-x’, ’--x0’, dest=’x0’,

default=0.0, help=’Initial guess’) parser.add option(’-p’, ’--plot’, dest=’plot’,

action=’store true’, default=False, help=’Plot f(x) around x0.’)

parser.add option(’-n’, ’--iterations’, dest=’imax’, help=’Maximum number of iterations.’, default=20)

parser.add option(’-t’, ’--tolerance’, dest=’tol’, help=’Convergence tolerance.’, default=1e-5)

options, args = parser.parse args(sys.argv[1:])

datafile = args[0]

run(datafile,

x0 = float(options.x0), plot = options.plot, imax = int(options.imax),

tol = float(options.tol))

# command line usage

if name == " main ": main()

The routine main uses the package optparse for providing a simple yet powerful parser of command line options. optparse is a good example of module that simplifies the e ort of programming in Python.

Combining all code pieces depicted above in an unique file zero.py provides the complete Python script. Assuming that the input data are saved in the file test.txt, calling zero.py produces Figure 3.3 and the following output:

>>> python zero.py -p test.txt Convergence error: -0.78947368 Convergence error: 0.16580881 Convergence error: -1.15519109 Convergence error: -0.13338833 Convergence error: -0.01723270 Convergence error: -0.00041200 Convergence error: -0.00000024 The solution is x = 1.92989

Although much more complex, a software package for power system analysis is very similar to this example. The main di erence is the multi-dimensionality

50

3 Power System Scripting

of the set of DAE. This implies that a particular care has to be devoted to index each variable so that each device operates on the correct element of the vector of DAE and the corresponding Jacobian matrices. The variable indexing issue is further discussed in Chapter 9.

Fig. 3.3 Plot of the function around the initial guess point x0 = 0.0

Script 3.2 Basis of a Power System Analysis Program

The main parts of a general script for power system analysis are the same as those described in the previous Script 3.1. Only, there are much more pieces that compose the puzzle. Listing all the code would take much more than the pages of this book. Thus, this example draws only a few outlines.

Following the logical order of the previous example and the synoptic scheme given in Figure 2.5 of Chapter 2, the following basic elements are needed:

1.External modules required by the program. This topic is discussed in Appendix E.

2.A structure whose attributes are all classes used by the program.

3.A main script that handles user inputs and options.

4.A general interface class that takes care of making routines to call device methods.

5.A general device class from which specific device classes inherit basic methods. This is the topic of Chapter 9.

3.5 Python Scripting Language

51

6.A set of scripts that read input data.

7.A set of scripts that implement solvers. This is the topic of Part II.

8.A set of scripts that implement devices. This is the topic of Part III.

9.A set of scripts that process results (e.g., creating reports and drawing plots). This is the topic of Chapter 22.

Neglecting the issues that are treated in details in dedicated chapters and parts, only three topics remain to be discussed, namely the “system structure”, the “main script” and the “interface class”. The principal function of these three elements is to provide a common environment for all other parts of the software package. These are further described in the following items.

Structure System

The scheme of Figure 2.5 of Chapter 2 states that the number of classes and data types required by a complex program such as power system analysis software package is potentially huge. Each class and data type has at least one instance. Since most instances are shared by several methods and functions, it would be particularly lengthy to import instances one by one.

For example, if there are 100 device instances, one has to write 100 import statements in each solver. Furthermore, if a new device is added to the program, one has to revise all the program code an add the new component in the import list occurrences. This is clearly a weak and error-prone approach.

A more robust approach is to define a global structure which can be imported using simply one line of code. This structure is thus a “store” containing all instances of classes and data types required by the program. Thus, if a new class is added to this structure, all other parts of the program automatically inherit also the new class instance.

Assume that this global structure is called system. This name will be used hereinafter in all script examples. In Python, the easiest way to implement such structure is to define a module (i.e., a file), called for example system.py. A simple example of the contents of this module is given below.

# Settings

from settings.settings import settings from settings.sssa import sssa

from settings.cpf import cpf from settings.opf import opf

# Variables

from variables.dae import dae

from variables.device import device from variables.varout import varout

# Devices

from devices.bus import bus from devices.line import line

52 3 Power System Scripting

from devices.pv import pv, slack from devices.pq import pq

from devices.shunt import shunt from devices.fault import fault from devices.breaker import breaker from devices.zone import zone

from devices.synchronous import syn2, syn3, syn4, syn5a, syn5b from devices.synchronous import syn5c, syn5d, syn6a, syn6b from devices.avr import avr1, avr2, avr3

from devices.turbine import tg1, tg2 from devices.pss import pss1, pss2, pss3

# list of all active devices

device list = [’Bus’, ’Area’, ’Region’, ’System’, ’Line’,

’Shunt’,

’Breaker’, ’Fault’, ’PV’, ’SW’, ’Syn2’,

’Syn3’, ’Syn4’, ’Syn5a’, ’Syn5b’, ’Syn5c’, ’Syn5d’,

’Syn6a’,

’Syn6b’, ’Avr1’, ’Avr2’, ’Avr3’, ’Tg1’,

’Tg2’,

’Pss1’, ’Pss2’, ’Pss3’]

# settings

 

 

Settings = settings()

#

power flow and time domain settings

SSSA = sssa()

# eigenvalue analysis settings

CPF = cpf()

# continuation power flow settings

OPF = opf()

# optimal power flow settings

#variables DAE = dae()

Varname = varname() Varout = varout()

Device = device(device list)

#D E V I C E S

#basic power flow devices Bus = bus()

Line = line() SW = slack() PV = pv()

PQ = pq()

Shunt = shunt() Area = zone(’Area’)

Region = zone(’Region’) System = zone(’System’)

#switches

Fault = fault()

Breaker = breaker()

# synchronous machines

Syn2 = syn2()

# machine models

Syn3

= syn3()

 

Syn4

= syn4()

 

Syn5a

= syn5a()

 

Syn5b

= syn5b()

 

3.5 Python Scripting Language

53

Syn5c = syn5c()

Syn5d = syn5d()

Syn6a = syn6a()

Syn6b = syn6b()

# synchronous

machines controls

Avr1 = avr1()

# automatic voltage regulators

Avr2 = avr2()

 

Avr3 = avr3()

 

Tg1 = tg1()

# turbine governors

Tg2 = tg2()

 

Pss1 = pss1()

# power system stabilizers

Pss2 = pss2()

 

Pss3 = pss3()

 

The script is organized into two parts. In the first part, all modules are imported; in the second one, an instance of all classes is assigned to a variable with a meaningful name. Hence, any script importing the module system has access to any of these instances. For example system.Settings allows accessing methods and attributes of the instance of the class settings that, surprisingly enough, contains general settings for the system including power flow and numerical integration settings. The class device and its associated instance Device functions as the class function described in the Script 3.1 is further discussed at the end of this section. Other variable names should be self-explicative. The reader is invited to familiarize with the variable names of the code above because these will be used systematically throughout this book.

Another important point is the definition of a list of all devices implemented in the program (device list). This list is used by the class device discussed below. Adding a new device is as simple as (i) importing the device module, (ii) adding a item to the list device list and (iii) adding an instance of the new device. After these simple operations, the whole program treats the new device as any other pre-existing devices.

Main Script

The functions of the main script are:

1.Initializing all elements of the package.

2.Collecting the information about input data and custom options.

3.Checking the consistency of the options.

4.Calling the adequate parser for reading the input data.

5.Calling the power flow routine and checking the solution.

6.If required, initializing dynamic devices.

7.If required, calling a solver for further analysis (e.g. numerical integration, optimal power flow, etc.).

54

3 Power System Scripting

8.If required, calling the post-processing scripts for generating suitable output report and/or plots.

9.Terminating the execution and, if required, producing a log file.

No one of the operations above is per se particularly complicated. The main script is more a “secretary” that organizes the work of other routines. No “real” task should be solved in the main script rather those described above. This rule allows separating the administrative work for the technical one, which is carried out by specific solvers, parsers, device models, postprocessing routines, etc.

The Python language is particularly well-suited for organizing the material in such tidy way. The main tool o ered by Python is the possibility of wrapping all scripts (or modules) of the same kind within folders, that works as meta-modules. The duty of the the main script is thus only to launch the correct module for each task.

An example is probably easier to understand than several abstract explanations. Let us consider the task of choosing the correct parser for the input data. With this aim, suppose that the user can specify the format of the input data as a command line option and that this information is assigned to the variable input format. Assume also that the set of all parsers is contained in the folder filters. Within this folder, there is a set of files (i.e., one per parser) and a special file init .py that is a kind of dedicated “secretary” for that module. The init .py script provides general methods for handling all defined parsers. Thus, the main script only needs to call the methods defined in the init .py script. For example, a code fragment that implements the concept above is as follows:

import filters

# parse data file using a suitable filter

if not input format or input format == ’all’: input format = filters.guess(datafile, path)

filters.read(input format, datafile, path, addfile)

The code above takes into account the possibility that the user does not define any format for the input data. If this is the case, an heuristic procedure, namely filters.guess(), is called and tries to determine the input data format. Then, the main script call the method filters.read() (in the programming slang, this method is a wrapper ) that tries to parse the input data files datafile and the optional additional file addfile located at the path path using the parser input format. This code fragment remains valid regardless the kind of the data format and regardless the implementation of the init .py script. Changes are needed only if the input options of the method filters.read change.

The main di culty when writing the main script is to find a general syntax for the modules to be called so that the arguments of wrapper methods are su ciently general to be able to take into account any possible future modification of the modules. For example, in the case of the wrapper