
Учебники / 0841558_16EA1_federico_milano_power_system_modelling_and_scripting
.pdf
9.2 Devices as Classes |
239 |
system.DAE.nx |
+= 1 |
for item in self. algebs:
self. dict [item][var] = system.DAE.ny system.DAE.ny += 1
Where the scalars system.DAE.ny and system.DAE.nx indicate the number of total algebraic and state variables, respectively. A slightly di erent approach is required for assigning the indexes of ac voltage magnitudes vh
and phases θh as well as of equations gp,hi and gq,hi. The indexes of these variables and equations are assigned by the device system.Bus. Thus, a
device connected to a certain bus has to retrieve the indexes of vh and phases θh from system.Bus itself. This is easily obtained by providing to the device the indexes of the buses to which are connected.
def bus index(self):
for index in self. bus.keys():
for item in self. dict [index]:
if not system.Bus.int(has key(idx): self.message(’Bus index <%s> does not exist’,
data tuple=item, level=self.ERROR)
else:
idx = system.Bus.int[item]
self. dict [self. bus[index][0]].append(system.Bus.a[idx]) self. dict [self. bus[index][1]].append(system.Bus.v[idx])
For example, if self. bus = {’bus’:[’a’, ’v’]}, the previous code assigns to the attributes self.a and self.v the elements self.bus of system.Bus.a and system.Bus.v, respectively. Another post-parsing operation consists in converting to CVXOPT arrays all parameters of the list param, as follows:
def list2matrix(self):
for item in self. params:
self. dict [item] = matrix(self. dict [item])
5.Deletion of an element from the device instance. Removing an element can be useful for internal operations. For example, after solving the power flow analysis and before running a time domain simulation, it could be necessary to remove static generators at the buses where there are synchronous machines (i.e., synchronous machines substitute static generators in dynamic analysis). The code below accepts as input the index of the element that has to be removed. If the index is not defined, then the procedure exits. Otherwise, all parameter arrays and lists are processed and the element at the position item is popped out.
def remove(self, idx=None):
if idx != None:
if not self.int.has key(idx):


9.2 Devices as Classes |
241 |
The built-in method pop is available for both lists and dictionaries and is used in the script for removing the assigned element. Since CVXOPT array dimension cannot be modified and do not provide the method pop, CVXOPT arrays are firstly converted into lists through the method matrix2list which implements the opposite conversion than the method list2matrix.
6.Handling windup and anti-windup limiters. A description of windup and anti-windup limiters as well as the implementation of the correspondent meta-methods is provided in Appendix C and Script C.1, respectively.
9.2.3Specific Device Methods
A description and an example of specific device methods conclude this chapter. Specific methods define the mathematical model of the device, as follows.
1.Initialization of state and algebraic variables (for devices initialized after the power flow analysis).
2.Algebraic equations g.
3.Di erential equations f .
4.Jacobian matrix gy .
5.Jacobian matrices gx, f y and f x.7
6.Objective function, inequality constraints and Hessian matrix (for devices that are included in the OPF problem).
7.Windup and anti-windup limiters (for devices with variables than can saturate).
Script 9.3 Methods of the Synchronous Machine Two-Axis
Model
The following methods implement the algebraic di erential equations and Jacobian matrices as well as the initialization function for the two-axis model of the synchronous machine. It is assumed that the constructor method is the one presented in the previous Script 9.2 and that all parameters are vectors initialized by the function matrix of the module CVXOPT. For all functions, the following header is assumed:
import system
from cvxopt.base import spmatrix, matrix
from cvxopt.base import mul, div, exp, log, sin, cos
1.Initialization of algebraic and state variables. Since the synchronous machine model is not used in power flow analysis, this device has to be initialized after the solution of the power flow problem. The equations
7The Jacobian matrix gy is taken apart from other Jacobian matrices to allows implementing e ciently explicit numerical methods (see Chapter 8).

242 |
9 Device Generalities |
used for initializing the synchronous machine two-axis model are given in Example 9.2.
def xinit(self, dae):
p0 = mul(mul(self.u, system.Bus.pg[self.a]), self.gammap)
q0 = mul(mul(self.u, system.Bus.qg[self.a]), self.gammaq)
v0 = mul(self.u, dae.y[self.v]) theta0 = dae.y[self.a]
V = mul(v0 + 0j, exp(theta0*1j))
S = p0 - q0*1j
I = div(S, V.H.T)
E = V + mul(self.ra + self.xq*1j, I) delta = log(div(E, abs(E) + 0j))
dae.x[self.delta] = mul(self.u, delta.imag()) dae.x[self.omega] = matrix(1.0, (self.n, 1), ’d’)
# d- and q-axis voltages and currents jpi2 = 1.5707963267948966j
vdq = mul(self.u + 0j, mul(V, exp(jpi2 - delta))) idq = mul(self.u + 0j, mul(I, exp(jpi2 - delta)))
vd = dae.y[self.vd] = vdq.real() vq = dae.y[self.vq] = vdq.imag() Id = dae.y[self.Id] = idq.real() Iq = dae.y[self.Iq] = idq.imag()
self.tm0 = mul(vq + mul(self.ra, Iq), Iq) + \ mul(vd + mul(self.ra, Id), Id)
dae.y[self.tm] = self.tm0
dae.x[self.e1q] = vq + mul(self.ra, Iq) + mul(self.xd1, Id) dae.x[self.e1d] = vd + mul(self.ra, Id) - mul(self.xq1, Iq) self.vf0 = dae.x[self.e1q] + mul(self.xd - self.xd1, Id) dae.y[self.vf] = self.vf0
system.Device.remove gen(self.gen)
In this example, it is assumed that generated active and reactive powers are stored in the vectors system.Bus.Pg and system.Bus.Qg, while the bus voltage is contained in the variable dae (which is passed as an argument of the method and coincides with system.DAE). In the last line, the interface class system.Device is called for removing, if required, the static generator connected at the synchronous machine buses. A possible implementation of the method system.Device.remove gen is as follows:
def remove gen(self, idx):
for item, stagen in zip(self.devices, self.stagen): if stagen:
indexes = system. dict [item].int.keys() for key in idx:

9.2 Devices as Classes |
243 |
if key in indexes:
system. dict [item].remove(key)
where self.devices is is the list of devices currently in use and the attribute self.stagen is a list of the same length as self.devices and whose elements are True if the correspondent device is a static generator, False otherwise.
2. Algebraic equations.
def gcall(self, dae):
delta = dae.x[delta] e1d = dae.x[e1d]
e1q = dae.x[e1q]
v = mul(self.u, dae.y[self.v]) theta = dae.y[self.a]
tm = dae.y[self.tm] vf = dae.y[self.vf] te = dae.y[self.te]
vq = mul(self.u, dae.y[self.vq]) vd = mul(self.u, dae.y[self.vd]) Iq = mul(self.u, dae.y[self.Iq]) Id = mul(self.u, dae.y[self.Id])
# internal algebraic equations
dae.g[self.te] = mul(vq + mul(self.ra, Iq), Iq) + \ mul(vd + mul(self.ra, Id), Id) - te
dae.g[self.Id] = vq + mul(self.ra, Iq) - e1q + mul(self.xd1, Id) dae.g[self.Iq] = vd + mul(self.ra, Id) - e1d - mul(self.xq1, Iq) dae.g[self.vd] = mul(v, sin(delta - theta)) - vd
dae.g[self.vq] = mul(v, cos(delta - theta)) - vq dae.g[self.tm] = mul(self.u, self.tm0) - tm dae.g[self.vf] = mul(self.u, self.vf0) - vf
#network interface equations
#active power
dae.g[self.a] += spmatrix(mul(vd, Id) + mul(vq, Iq), \ self.a, [0]*self.n, (dae.ny, 1), ’d’)
# reactive power
dae.g[self.v] += spmatrix(mul(vq, Id) - mul(vd, Iq), \ self.v, [0]*self.n, (dae.ny, 1), ’d’)
There is a conceptual di erence between internal algebraic equations gˆi and gp,hi and gq,hi. Equations gˆi are specific of the synchronous machine model and are not shared with other devices. Thus a direct indexation works fine. On the other hand, gp,hi and gq,hi are the synchronous machine contribution to the power balance at the bus h. Since other devices (e.g., transmission lines) sum their power injections to the same bus, the function

244 |
9 Device Generalities |
gcall has to update and not to overwrite the current value of gp,hi and
gq,hi.
The status vector self.u systematically multiplies parameters and/or variables to impose gˆi = 0, gp,hi = 0 and gq,hi = 0 in case some synchronous machine element is o -line.
3. Di erential equations.
def fcall(self, dae):
# differential equations
omega = dae.x[self.omega] tm = dae.y[self.tm]
te = dae.y[self.te] vf = dae.y[self.vf]
iTd = div(self.u, |
self.Td10) |
|
|
xTd = mul(iTd, self.xd - self.xd1) |
|||
iTq = div(self.u, |
self.Tq10) |
|
|
xTq = mul(iTq, self.xq - self.xq1) |
|||
dae.f[self.delta] |
= system.Settings.rad * \ |
||
|
|
mul(self.u, |
omega - 1) |
dae.f[self.omega] |
= mul(self.u, |
div(tm - te - \ |
|
|
|
mul(self.D, |
omega - 1), 2*self.H)) |
dae.f[self.e1q] |
= |
mul(iTd, vf) |
- mul(xTd, dae.y[self.Id]) - \ |
|
|
mul(iTd, dae.x[self.e1q]) |
|
dae.f[self.e1d] |
= |
mul(xTq, dae.y[self.Iq]) - mul(iTq, dae.x[self.e1d]) |
The variable system.Settings.rad contains the base synchronous speed in rad/s. The status vector self.u imposes f i = 0 if some synchronous machine element is switched o .
4. Jacobian matrices.
def gycall(self, dae):
delta = dae.x[delta]
v = mul(self.u, dae.y[self.v]) theta = dae.y[self.a]
vq = mul(self.u, dae.y[self.vq]) vd = mul(self.u, dae.y[self.vd]) Iq = mul(self.u, dae.y[self.Iq]) Id = mul(self.u, dae.y[self.Id]) ny x ny = (dae.ny, dae.ny)
# internal Jacobians
dae.Gy -= spmatrix(1, self.te, self.te, ny x ny, ’d’) dae.Gy += spmatrix(Iq, self.te, self.vq, ny x ny, ’d’) dae.Gy += spmatrix(Id, self.te, self.vd, ny x ny, ’d’)


246 9 Device Generalities
dae.Gx -= spmatrix(self.u, self.Id, self.e1q, (dae.ny, dae.nx), ’d’) dae.Gx -= spmatrix(self.u, self.Iq, self.e1d, (dae.ny, dae.nx), ’d’)
# Jacobian matrix f y
dae.Fy += spmatrix(div(self.u, 2*self.H), self.omega, \ self.tm, (dae.nx, dae.ny), ’d’)
dae.Fy -= spmatrix(div(self.u, 2*self.H), self.omega, \ self.te, (dae.nx, dae.ny), ’d’)
dae.Fy += spmatrix(div(self.u, self.Td10), self.e1q, \ self.vf, (dae.nx, dae.ny), ’d’)
dae.Fy -= spmatrix(div(mul(self.u, self.xd - self.xd1), self.Td10), \ self.e1q, self.Id, (dae.nx, dae.ny), ’d’)
dae.Fy += spmatrix(div(mul(self.u, self.xq - self.xq1), self.Tq10), \ self.e1d, self.Iq, (dae.nx, dae.ny), ’d’)
# Jacobian matrix f x
dae.Fx += spmatrix(1 - self.u, self.delta, \ self.delta, (dae.nx, dae.nx), ’d’)
dae.Fx += spmatrix(system.Settings.rad*self.u, self.delta, \ self.omega, (dae.nx, dae.nx), ’d’)
dae.Fx -= spmatrix(div(self.D, 2*self.H), self.omega, \ self.omega, (dae.nx, dae.nx), ’d’)
dae.Fx -= spmatrix(div(1.0, self.Td10), self.e1q, \ self.e1q, (dae.nx, dae.nx), ’d’) dae.Fx -= spmatrix(div(1.0, self.Tq10), self.e1d, \ self.e1d, (dae.nx, dae.nx), ’d’)
In case an element is o -line, all Jacobians matrices are null except for the diagonal elements, which cannot be zero to avoid singularities.
248 |
10 Power Flow Devices |
sections. These features can be easily modelled by means of coupling devices (see Section 11.1.5 of Chapter 11).
Typical bus parameters are depicted in Table 10.1. Bus numbers (or names) are used by all other devices for identifying the bus to which are connected. Thus, each bus has to be identified by a unique number, code or name. In some old formats (e.g., IEEE common data format [350]), bus identification codes are integers. This was mainly due to old-style system programming languages such as FORTRAN that hardly handle hash types (e.g., lists of key-value pairs). Modern script languages provide a simple manner to handle hashes. The Python implementation of the hash is the built-in type dictionary. A key can be any number or string, the only requisite is that keys have to be unique. Then, the value associated to each key is the bus index h. In general, hashes provide a great programming flexibility and should be preferred to other indexing methods.
Table 10.1 Bus parameters
Variable |
Description |
Unit |
|
|
|
|
|
|
- |
Bus code |
- |
- |
Bus name |
- |
- |
Area code |
- |
- |
Zone code |
- |
- |
Region code |
- |
- |
System code |
- |
v(0) |
Voltage amplitude initial guess |
pu |
Vb |
Voltage rating |
kV |
vmax |
Maximum admissible voltage |
pu |
vmin |
Minimum admissible voltage |
pu |
θ(0) |
Voltage phase initial guess |
rad |
The voltage rating Vb is generally used in power flow analysis for fixing the voltage bases for per unit analysis. Voltage magnitudes v(0) and phases θ(0) can be optionally set if the power flow solution is known or if a custom initial guess is needed. If voltages are not specified, a flat start is used (e.g., v(0) = 1 at all buses except for buses where a PV or slack generator is present, and θ(0) = 0). Finally, area, zone, region and system codes are generally used for evaluating inter-area power flows or simply to assign an owner to the bus.
Since buses contain only topological information, no equation is required for defining the bus model. The only issue that can arise is in case a bus is islanded. In that case, the system admittance matrix may become singular if it is not properly conditioned. The easiest solution is to find islanded buses and to impose that the power balance at those buses is zero. This also implies that, the Jacobian matrix has to be properly conditioned, as follows. If a given bus h is islanded, the columns associated with the derivatives with respect to