MODULE 5ΒΆ

Small Signal Analyzes of the Inductive DroopΒΆ

Grid-Forming Inverter with Inductive Droop Control:

Drawing Drawing Drawing

The real power p and reactive power q, delivered from the voltage source e to the grid V through the reactance XL, are:

(1)p=VXLesin⁑(δ)

(2)q=VXLecos⁑(Ξ΄)βˆ’V2XL

where Ξ΄ is the power angle. The active and reactive power are usually filtered, here this filter is implemented as:

(3)ddtpf=βˆ’apf+ap

(4)ddtqf=βˆ’apf+aq

where a defines the low-pass filter cutoff frequency.

The Droop Law is:

(5)Ο‰=Ο‰oβˆ’m(pfβˆ’Po)

(6)e=Eoβˆ’n(qfβˆ’Qo)

where n and m corresponds to the Droop coefficients. Therefore, these equations can be rewritten as:

(7)pf=Ο‰o+mPoβˆ’Ο‰m

(8)qf=Eo+nQoβˆ’en

By assuming that Po, Qo, Eo and Ο‰o are constant, it can be considered that:

(9)ddte=βˆ’nddtqf

(10)ddtΟ‰=βˆ’mddtpf

and by rearanging the above equations, it is possible to obtain:

(11)ddte=βˆ’n(βˆ’aqf+aq)=βˆ’n[βˆ’a(Eo+nQoβˆ’en)+a(VXLecos⁑(Ξ΄)βˆ’V2XL)]=βˆ’aeβˆ’nVaXLecos⁑(Ξ΄)+aEo+anQo+nV2aXL

(12)ddtΟ‰=βˆ’m(βˆ’apf+ap)=βˆ’m[βˆ’a(Ο‰o+mPoβˆ’Ο‰m)+a(VXLesin⁑(Ξ΄))]=βˆ’aΟ‰βˆ’amVXLesin⁑(Ξ΄)+aΟ‰o+amPo

Note that Ο‰ is the Droop control variable that by integration will define the angular position of the voltage sinthesized by the inverter, that is:

(13)ddtΞΈ=Ο‰

Since we are consider the operation in sinusoidal steady state, the ΞΈ is given by:

(14)ΞΈ=Ο‰rt+Ξ΄

therefore:

(15)ddtΞΈ=Ο‰r+ddtΞ΄

where:

(16)ddtΞ΄=Ο‰βˆ’Ο‰r

The nonlinear dynamic equations that govern the grid connected converter with droop are described by:

(16)ddtΞ΄=Ο‰βˆ’Ο‰r

(17)ddtΟ‰=βˆ’aΟ‰βˆ’amVXLesin⁑(Ξ΄)+a(Ο‰o+mPo)

(18)ddte=βˆ’aeβˆ’nVaXLecos⁑(Ξ΄)+a(Eo+nQo)+nV2aXL

In order to simplify the notation let us define some constants, that are:

a21=βˆ’a b11=βˆ’Ο‰r

a22=βˆ’mVaXL b21=a(Ο‰o+mPo)

a32=βˆ’nVaXL b31=a(Eo+nQo)+nV2aXL

then:

(19)ddtΞ΄=Ο‰+b11

(20)ddtΟ‰=a21Ο‰+a22esin⁑(Ξ΄)+b21

(21)ddte=a21e+a32ecos⁑(δ)+b31

Let us start by computing the equilibrium point of (20):

(22)0=Ο‰+b11β†’Ο‰=βˆ’b11

(23)0=a21Ο‰+a22esin⁑(Ξ΄)+b21β†’esin⁑(Ξ΄)=βˆ’b21+a21b11a22

(24)0=a21e+a32ecos⁑(Ξ΄)+b31β†’ecos⁑(Ξ΄)=βˆ’b31+a21Ea32

From (23) and (24) we have:

(25)e2=(βˆ’b21+a21b11a22)2+(βˆ’b31βˆ’a21ea32)2

The quadratic equation described in (25) has two solutions. Let us call them as e1 and e2, that are:

(26)e1=a222a322(2a213b11b21βˆ’a214b112+a212a322b112βˆ’2a212b212βˆ’2a21a322b11b21+a222b312+a322b212a22a32βˆ’a21b31a322)a212a222βˆ’a222a322

(27)e2=βˆ’a222a322(2a213b11b21βˆ’a214b112+a212a322b112βˆ’2a212b212βˆ’2a21a322b11b21+a222b312+a322b212a22a32+a21b31a322)a212a222βˆ’a222a322

The load angle can be found as:

(28)Ξ΄1,2=atan2(βˆ’b31βˆ’a21e1,2a32,βˆ’b21+a21b11a22)

and the inverter reference frequency:

(29)Ο‰=βˆ’b11

Example:ΒΆ

In [178]:
pi = 3.14159265359
V  = 220.         # Grid Voltage
wr = 2.*pi*60.    # Grid Frequency
L  = 2.e-3        # Inductance 
XL = wr*L         # Inductive Reactance 
n  = 11./(33.33e3)     # Droop coefficient n
m  = 2.*pi*3./33.33e3  # Droop coefficient m
a  = 2.*pi*12.         # Low-pass filters
Zb = V**2/33000.       # Based Impedance
print (" XL = ", format(XL, ".2f") , " \u03A9")  
print (" XL = ", format(XL/Zb, ".2f"), " pu ")  
 XL =  0.75  Ξ©
 XL =  0.51  pu 

The reference voltages and frequency will be expressed as a function of the ative and reative power to be dispached to the grid:

In [179]:
Qo = 33e3 * 0.
Eo = V

Po = 33e3 * 0.
wo = wr 

Qo = ((V**2 + (Po/(V/XL))**2)**(1/2) - Eo)/n

print (" Eo = ", format(Eo, ".2f"),  " V")
print (" wo = ", format(wo, ".2f"),  " rad/s" )
print (" Po = ", format(Po, ".2f"),  "   W" )
print (" Qo = ", format(Qo, ".2f"),  "   VAr" )
 Eo =  220.00  V
 wo =  376.99  rad/s
 Po =  0.00    W
 Qo =  0.00    VAr
In [180]:
a21 = - a
a22 = - (a*m*V)/XL
a32 = - (a*n*V)/XL
b11 = - wr
b21 = a*(wo + m*Po)
b31 = a*(Eo + n*Qo) + (n*a*V**2)/XL

print (" a21 = ", format(a21, ".2f"))
print (" a22 = ", format(a22, ".2f"))
print (" a32 = ", format(a32, ".2f"))
print (" b11 = ", format(b11, ".2f"))
print (" b21 = ", format(b21, ".2f"))
print (" b31 = ", format(b31, ".2f"))
 a21 =  -75.40
 a22 =  -12.44
 a32 =  -7.26
 b11 =  -376.99
 b21 =  28424.46
 b31 =  18184.97
In [181]:
e1 = (a22**2)*(a32**2) * (( (2*(a21**3)*(b11)*(b21) - (a21**4)*(b11**2) + (a21**2)*(a32**2)*(b11**2) - (a21**2)*(b21**2) - 2*a21*(a32**2)*b11*b21 + (a22**2)*(b31**2) + (a32**2)*(b21**2))**(1/2)) /(a22*a32) - ((a21*b31)/(a32**2))) / ((a21**2)*(a22**2) - (a22**2)*(a32**2))

e2 = - (a22**2)*(a32**2) * (( (2*(a21**3)*(b11)*(b21) - (a21**4)*(b11**2) + (a21**2)*(a32**2)*(b11**2) - (a21**2)*(b21**2) - 2*a21*(a32**2)*b11*b21 + (a22**2)*(b31**2) + (a32**2)*(b21**2))**(1/2)) /(a22*a32) + ((a21*b31)/(a32**2))) / ((a21**2)*(a22**2) - (a22**2)*(a32**2))

print (" e1 = ", format(e1, ".2f"), "  V")
print (" e2 = ", format(e2, ".2f"), "  V")
 e1 =  266.89   V
 e2 =  220.00   V
In [182]:
import math

delt1 = math.atan2( XL*(wo + (m*Po) - wr) / (V*m) , (V + ((XL*(Eo + n*Qo - e1))/(V*n))))

Delt1 = delt1*180./pi

delt2 = math.atan2( XL*(wo + (m*Po) - wr) / (V*m) , (V + ((XL*(Eo + n*Qo - e2))/(V*n))))

Delt2 = delt2*180./pi

print (" Delt_1 = ", format(Delt1, ".2f"))    
print (" Delt_2 = ", format(Delt2, ".2f"))                   
 Delt_1 =  180.00
 Delt_2 =  0.00
In [183]:
p1 = (V/XL)*e1*math.sin(delt1)

q1 = (V/XL)*e1*math.cos(delt1) - (V**2)/XL

p2 = (V/XL)*e2*math.sin(delt2)

q2 = (V/XL)*e2*math.cos(delt2) - (V**2)/XL

print (" p1 = ", format(p1/1000, ".2f") , "   kW")        
print (" q1 = ", format(q1/1000, ".2f") , "kVAr")               
print (" p2 = ", format(p2/1000, ".2f"), "   kW")               
print (" q2 = ", format(q2/1000, ".2f"), "  kVAr")               
 p1 =  0.00    kW
 q1 =  -142.07 kVAr
 p2 =  0.00    kW
 q2 =  -0.00   kVAr

The Jacobian matrix for the first equilibrium point, described as e1 and Ξ΄1, is given by:

In [184]:
import numpy as np 
from scipy import sqrt, exp, cos, matrix, vstack, hstack, zeros

np.set_printoptions(precision=2)

e = e1
delt = delt1

J = matrix([[0, 1, 0], [(a22*e*math.cos(delt)), a21, (a22*math.sin(delt))], [(-a32*e*math.sin(delt)), 0, (a21 + a32*math.cos(delt))]])

print (" J = \n", J)                              
 J = 
 [[ 0.00e+00  1.00e+00  0.00e+00]
 [ 3.32e+03 -7.54e+01 -1.52e-15]
 [ 2.37e-13  0.00e+00 -6.81e+01]]
In [185]:
import scipy.linalg as la

evals, evecs = la.eig(J)    
    
Eig_V_J = evals   

print (" [ \u03BB\u2081  \u03BB\u2082  \u03BB\u2083 ]'= ",  np.array2string(Eig_V_J.reshape((-1, 1)), prefix=' [ \u03BB\u2081  \u03BB\u2082  \u03BB\u2083 ] =  ') )   
 [ λ₁  Ξ»β‚‚  λ₃ ]'=  [[  31.16+0.j]
                    [-106.56+0.j]
                    [ -68.14+0.j]]

Note that if an eigenvalue has a positive real part, this indicates that the considered equilibrium point is unstable.

In order to characterizes the interaction between modes and state variables, let us compute the participation factors:

In [186]:
evals, evecs = la.eig(J)   

T = evals.real 

R = evecs.real              # Matrix whose columns are the Right eigenvectors of J

L = np.linalg.inv(R)        # Matrix whose rows are the left eigenvecotors of J

print (" R = \n", R)      

print (" \n L = \n", L)

print ("\n \n Participation Factors: ")
print (" \n               \u03BB\u2081                         \u03BB\u2082                          \u03BB\u2083 \n ")
print("|L(0,0)*R(0,0)|=", format(np.abs(L[0][0]*R[0][0]), ".2f"), "     |L(1,0)*R(0,1)|=", format(np.abs(L[1][0]*R[0][1]), ".2f"), "      |L(2,0)*R(0,2)|=", format(np.abs(L[2][0]*R[0][2]), ".2f"), "       \u03B4")
print("|L(0,1)*R(1,0)|=", format(np.abs(L[0][1]*R[1][0]), ".2f"), "     |L(1,1)*R(1,1)|=", format(np.abs(L[1][1]*R[1][1]), ".2f"), "      |L(2,1)*R(1,2)|=", format(np.abs(L[2][1]*R[1][2]), ".2f"), "       \u03C9")
print("|L(0,2)*R(2,0)|=", format(np.abs(L[0][2]*R[2][0]), ".2f"), "     |L(1,2)*R(2,1)|=", format(np.abs(L[1][2]*R[2][1]), ".2f"), "      |L(2,2)*R(2,2)|=", format(np.abs(L[2][2]*R[2][2]), ".2f"), "       e" )
 R = 
 [[ 3.21e-02  9.38e-03  3.99e-19]
 [ 9.99e-01 -1.00e+00 -2.72e-17]
 [ 7.14e-17 -7.15e-17  1.00e+00]]
 
 L = 
 [[ 2.41e+01  2.26e-01 -3.47e-18]
 [ 2.41e+01 -7.74e-01 -3.07e-17]
 [ 0.00e+00 -7.15e-17  1.00e+00]]

 
 Participation Factors: 
 
               λ₁                         Ξ»β‚‚                          λ₃ 
 
|L(0,0)*R(0,0)|= 0.77      |L(1,0)*R(0,1)|= 0.23       |L(2,0)*R(0,2)|= 0.00        Ξ΄
|L(0,1)*R(1,0)|= 0.23      |L(1,1)*R(1,1)|= 0.77       |L(2,1)*R(1,2)|= 0.00        Ο‰
|L(0,2)*R(2,0)|= 0.00      |L(1,2)*R(2,1)|= 0.00       |L(2,2)*R(2,2)|= 1.00        e

Let us now consider the second equilibrium point, e2 and Ξ΄2, and calculate the Jacobian matrix, as:

In [187]:
e = e2
delt = delt2

J = matrix([[0, 1, 0], [(a22*e*math.cos(delt)), a21, (a22*math.sin(delt))], [(-a32*e*math.sin(delt)), 0, (a21 + a32*math.cos(delt))]])

print (" J = \n", J)   
 J = 
 [[ 0.00e+00  1.00e+00  0.00e+00]
 [-2.74e+03 -7.54e+01 -0.00e+00]
 [ 0.00e+00  0.00e+00 -8.27e+01]]
In [188]:
evals, evecs = la.eig(J)    
    
Eig_V_J = evals

print (" [ \u03BB\u2081  \u03BB\u2082  \u03BB\u2083 ]'= ",  np.array2string(Eig_V_J.reshape((-1, 1)), prefix=' [ \u03BB\u2081  \u03BB\u2082  \u03BB\u2083 ] =  ') )   
 [ λ₁  Ξ»β‚‚  λ₃ ]'=  [[-37.7 +36.28j]
                    [-37.7 -36.28j]
                    [-82.66 +0.j  ]]

Note that these eigenvalues indicate that the considered equilibrium point is stable.

In order to characterizes the interaction between modes and state variables, let us compute the participarion factors:

In [189]:
np.set_printoptions(linewidth=np.inf)

evals, evecs = la.eig(J)   

T = evals 

R = evecs                   # Matrix whose columns are the Right eigenvectors of J

L = np.linalg.inv(R)        # Matrix whose rows are the left eigenvecotors of J

print (" R = \n", R)      

print (" \n L = \n", L)

print ("\n \n Participation Factors: ")
print (" \n               \u03BB\u2081                         \u03BB\u2082                          \u03BB\u2083 \n ")
print("|L(0,0)*R(0,0)|=", format(np.abs(L[0][0]*R[0][0]), ".2f"), "     |L(1,0)*R(0,1)|=", format(np.abs(L[1][0]*R[0][1]), ".2f"), "      |L(2,0)*R(0,2)|=", format(np.abs(L[2][0]*R[0][2]), ".2f"), "       \u03B4")
print("|L(0,1)*R(1,0)|=", format(np.abs(L[0][1]*R[1][0]), ".2f"), "     |L(1,1)*R(1,1)|=", format(np.abs(L[1][1]*R[1][1]), ".2f"), "      |L(2,1)*R(1,2)|=", format(np.abs(L[2][1]*R[1][2]), ".2f"), "       \u03C9")
print("|L(0,2)*R(2,0)|=", format(np.abs(L[0][2]*R[2][0]), ".2f"), "     |L(1,2)*R(2,1)|=", format(np.abs(L[1][2]*R[2][1]), ".2f"), "      |L(2,2)*R(2,2)|=", format(np.abs(L[2][2]*R[2][2]), ".2f"), "       e" )
 R = 
 [[ 0.01+0.01j  0.01-0.01j  0.  +0.j  ]
 [-1.  +0.j   -1.  -0.j    0.  +0.j  ]
 [ 0.  +0.j    0.  -0.j    1.  +0.j  ]]
 
 L = 
 [[ 0. -37.73j -0.5 -0.52j  0.  -0.j  ]
 [ 0. +37.73j -0.5 +0.52j  0.  +0.j  ]
 [ 0.  +0.j    0.  +0.j    1.  +0.j  ]]

 
 Participation Factors: 
 
               λ₁                         Ξ»β‚‚                          λ₃ 
 
|L(0,0)*R(0,0)|= 0.72      |L(1,0)*R(0,1)|= 0.72       |L(2,0)*R(0,2)|= 0.00        Ξ΄
|L(0,1)*R(1,0)|= 0.72      |L(1,1)*R(1,1)|= 0.72       |L(2,1)*R(1,2)|= 0.00        Ο‰
|L(0,2)*R(2,0)|= 0.00      |L(1,2)*R(2,1)|= 0.00       |L(2,2)*R(2,2)|= 1.00        e

Static Caracteristics - Voltage DroopΒΆ

In [164]:
PN = 100
j = np.arange(start=1, stop=101, step=1)
DV= 22.

Vj= 220 - DV + j*(2*DV)/PN

wr = 2.*pi*60.
L  = .5e-3
XL = wr*L 
n  = 22./(33.33e3)
m  = (2.*pi*3.)/(33.33e3)
a  = 2.*pi*12.
wo = wr
Eo = 220.
Po = 33000.
Qo = 0.

b11 = -wr
a21 = -a
b21 = a*(wo + m*Po)
a22j = - a*m*Vj/XL
b31j = a*(Eo + n*Qo) + n*(Vj**2)*a/XL
a32j = - n*Vj*a/XL

e2j = - (a22j**2)*(a32j**2) * (( (2*(a21**3)*(b11)*(b21) - (a21**4)*(b11**2) + (a21**2)*(a32j**2)*(b11**2) - (a21**2)*(b21**2) - 2*a21*(a32j**2)*b11*b21 + (a22j**2)*(b31j**2) + (a32j**2)*(b21**2))**(1/2)) /(a22j*a32j) + ((a21*b31j)/(a32j**2))) / ((a21**2)*(a22j**2) - (a22j**2)*(a32j**2))

delt2j = np.arctan2( XL*(wo + (m*Po) - wr) / (Vj*m) , (Vj + ((XL*(Eo + n*Qo - e2j))/(Vj*n))))

qj = (Vj/XL)*e2j*np.cos(delt2j) - (Vj**2)/XL

pj = (Vj/XL)*e2j*np.sin(delt2j)
In [199]:
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 4
                   ))
plt.subplot(121)
plt.plot(Vj, qj/33000)
plt.xlim([198, 242])
plt.xticks([198, 209, 220, 231, 242],
           ["0.9", "0.95", "1", "1.05", "1.1"])
plt.ylim([-1.1, 1.1])
plt.yticks([-1,-0.5, 0, 0.5, 1],
           ["-1", "-0.5", "0.", "0.5", "1"])
plt.grid(True)            
plt.ylabel('$\dfrac{qj}{33000}$', rotation=0) 
plt.xlabel('$\dfrac{Vj}{220}$') 

plt.subplot(122)
plt.plot(Vj, pj/33000)
plt.xlim([198, 242])
plt.xticks([198, 209, 220, 231, 242],
           ["0.9", "0.95", "1", "1.05", "1.1"])
plt.ylim([-1.1, 1.1])
plt.yticks([-1,-0.5, 0, 0.5, 1],
           ["-1", "-0.5", "0.", "0.5", "1"])
plt.grid(True)            
plt.ylabel('$\dfrac{pj}{33000}$', rotation=0) 
plt.xlabel('$\dfrac{Vj}{220}$') 

plt.subplots_adjust(left=0.125,
                    bottom=0.1, 
                    right=0.9, 
                    top=0.9, 
                    wspace=0.4, 
                    hspace=0.35)

Static Caracteristics - Frequency DroopΒΆ

In [17]:
PN = 100
j  = np.arange(start=1, stop=101, step=1)
DW = 2.*pi*3.

V  = 220
wrj= 2.*pi*(60. - 3.) + (j/PN)*2.*DW

L  = .1e-3
XLj = wrj*L 
n  = 22./(33.33e3)
m  = (2.*pi*3.)/(33.33e3)
a  = 2.*pi*12.
wo = 2.*pi*60.
Eo = 220.
Po = 0.
Qo = 0.

b11j = - wrj
a21 = - a
b21 = a*(wo + m*Po)
a22j = - a*m*V/XLj
b31j = a*(Eo + n*Qo) + n*(V**2)*a/XLj
a32j = - n*V*a/XLj

e2j = - (a22j**2)*(a32j**2) * (( (2*(a21**3)*(b11j)*(b21) - (a21**4)*(b11j**2) + (a21**2)*(a32j**2)*(b11j**2) - (a21**2)*(b21**2) - 2*a21*(a32j**2)*b11j*b21 + (a22j**2)*(b31j**2) + (a32j**2)*(b21**2))**(1/2)) /(a22j*a32j) + ((a21*b31j)/(a32j**2))) / ((a21**2)*(a22j**2) - (a22j**2)*(a32j**2))

delt2j = np.arctan2( XLj*(wo + (m*Po) - wrj) / (V*m) , (V + ((XLj*(Eo + n*Qo - e2j))/(V*n))))

qj = (V/XLj)*e2j*np.cos(delt2j) - (V**2)/XLj

pj = (V/XLj)*e2j*np.sin(delt2j)
In [64]:
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 4
                   ))
plt.subplot(121)
plt.plot(wrj/(2*pi*60), qj/33000)
plt.xlim([0.95, 1.05])
plt.xticks([0.95, 0.975, 1, 1.025, 1.05],
           ["0.95", "0.975", "1", "1.025", "1.05"])
plt.ylim([-1.1, 1.1])
plt.yticks([-1,-0.5, 0, 0.5, 1],
           ["-1", "-0.5", "0.", "0.5", "1"])
plt.grid(True)            
plt.ylabel('$\dfrac{qj}{33000}$', rotation=0) 
plt.xlabel('$\dfrac{\u03C9 rj}{2 \u03C0 60}$') 

plt.subplot(122)
plt.plot(wrj/(2*pi*60), pj/33000)
plt.xlim([0.95, 1.05])
plt.xticks([0.95, 0.975, 1, 1.025, 1.05],
           ["0.95", "0.975", "1", "1.025", "1.05"])
plt.ylim([-1.1, 1.1])
plt.yticks([-1,-0.5, 0, 0.5, 1],
           ["-1", "-0.5", "0.", "0.5", "1"])
plt.grid(True)            
plt.ylabel('$\dfrac{pj}{33000}$', rotation=0) 
plt.xlabel('$\dfrac{\u03C9 rj}{2 \u03C0 60}$') 

plt.subplots_adjust(left=0.125,
                    bottom=0.1, 
                    right=0.9, 
                    top=0.9, 
                    wspace=0.4, 
                    hspace=0.35)