Coverage for pySDC/projects/DAE/problems/synchronousMachine.py: 96%

75 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 14:51 +0000

1import numpy as np 

2import warnings 

3from scipy.interpolate import interp1d 

4 

5from pySDC.projects.DAE.misc.problemDAE import ProblemDAE 

6 

7 

8class SynchronousMachineInfiniteBus(ProblemDAE): 

9 r""" 

10 Synchronous machine model from Kundur (equiv. circuits fig. 3.18 in [1]_) attached to infinite bus. The machine can be 

11 represented as two different circuits at the direct-axis and the quadrature-axis. Detailed information can be found in 

12 [1]_. The system of differential-algebraic equations (DAEs) consists of the equations for 

13 

14 - the stator voltage equations 

15 

16 .. math:: 

17 \frac{d \Psi_d (t)}{dt} = \omega_b (v_d + R_a i_d (t) + \omega_r \Psi_q (t)), 

18 

19 .. math:: 

20 \frac{d \Psi_q (t)}{dt} = \omega_b (v_q + R_a i_q (t) - \omega_r \Psi_d (t)), 

21 

22 .. math:: 

23 \frac{d \Psi_0 (t)}{dt} = \omega_b (v_0 + R_a i_0 (t)), 

24 

25 - the rotor voltage equations 

26 

27 .. math:: 

28 \frac{d \Psi_F (t)}{dt} = \omega_b (v_F - R_F i_F (t)), 

29 

30 .. math:: 

31 \frac{d \Psi_D (t)}{dt} = -\omega_b (R_D i_D (t)), 

32 

33 .. math:: 

34 \frac{d \Psi_{Q1} (t)}{dt} = -\omega_b (R_{Q1} i_{Q1} (t)), 

35 

36 .. math:: 

37 \frac{d \Psi_{Q2} (t)}{dt} = -\omega_b (R_{Q2} i_{Q2} (t)), 

38 

39 - the stator flux linkage equations 

40 

41 .. math:: 

42 \Psi_d (t) = L_d i_d (t) + L_{md} i_F (t) + L_{md} i_D (t), 

43 

44 .. math:: 

45 \Psi_q (t) = L_q i_q (t) + L_{mq} i_{Q1} (t) + L_{mq} i_{Q2} (t), 

46 

47 .. math:: 

48 \Psi_0 (t) = L_0 i_0 (t) 

49 

50 - the rotor flux linkage equations 

51 

52 .. math:: 

53 \Psi_F = L_F i_F (t) + L_D i_D + L_{md} i_d (t), 

54 

55 .. math:: 

56 \Psi_D = L_F i_F (t) + L_D i_D + L_{md} i_d (t), 

57 

58 .. math:: 

59 \Psi_{Q1} = L_{Q1} i_{Q1} (t) + L_{mq} i_{Q2} + L_{mq} i_q (t), 

60 

61 .. math:: 

62 \Psi_{Q2} = L_{mq} i_{Q1} (t) + L_{Q2} i_{Q2} + L_{mq} i_q (t), 

63 

64 - the swing equations 

65 

66 .. math:: 

67 \frac{d \delta (t)}{dt} = \omega_b (\omega_r (t) - 1), 

68 

69 .. math:: 

70 \frac{d \omega_r (t)}{dt} = \frac{1}{2 H}(T_m - T_e - K_D \omega_b (\omega_r (t) - 1)). 

71 

72 The voltages :math:`v_d`, :math:`v_q` can be updated via the following procedure. The stator's currents are mapped 

73 to the comlex-valued external reference frame current :math:`I` with 

74 

75 .. math:: 

76 \Re(I) = i_d (t) \sin(\delta (t)) + i_q (t) \cos(\delta (t)), 

77 

78 .. math:: 

79 \Im(I) = -i_d (t) \cos(\delta (t)) + i_q (t) \sin(\delta (t)). 

80 

81 The voltage V across the stator terminals can then be computed as complex-value via 

82 

83 .. math:: 

84 V_{comp} = E_B + Z_{line} (\Re(I) + i \Im(I)) 

85 

86 with impedance :math:`Z_{line}\in\mathbb{C}`. Then, :math:`v_d`, :math:`v_q` can be computed via the network equations 

87 

88 .. math:: 

89 v_d = \Re(V_{comp}) \sin(\delta (t)) - \Im(V_{comp}) \cos(\delta (t)), 

90 

91 .. math:: 

92 v_q = \Re(V_{comp}) \cos(\delta (t)) + \Im(V_{comp}) \sin(\delta (t)), 

93 

94 which describes the connection between the machine and the infinite bus. 

95 

96 Parameters 

97 ---------- 

98 nvars : int 

99 Number of unknowns of the system of DAEs. 

100 newton_tol : float 

101 Tolerance for Newton solver. 

102 

103 Attributes 

104 ---------- 

105 L_d: float 

106 Inductance of inductor :math:'L_d', see [1]_. 

107 L_q: float 

108 Inductance of inductor :math:'L_q', see [1]_. 

109 L_F: float 

110 Inductance of inductor :math:'L_F', see [1]_. 

111 L_D: float 

112 Inductance of inductor :math:'L_D', see [1]_. 

113 L_Q1: float 

114 Inductance of inductor :math:'L_{Q1}', see [1]_. 

115 L_Q2: float 

116 Inductance of inductor :math:'L_{Q2}', see [1]_. 

117 L_md: float 

118 Inductance of inductor :math:'L_{md}', see [1]_. 

119 L_mq: float 

120 Inductance of inductor :math:'L_{mq}', see [1]_. 

121 R_s: float 

122 Resistance of resistor :math:`R_s`, see [1]_. 

123 R_F: float 

124 Resistance of resistor :math:`R_F`, see [1]_. 

125 R_D: float 

126 Resistance of resistor :math:`R_D`, see [1]_. 

127 R_Q1: float 

128 Resistance of resistor :math:`R_{Q1}`, see [1]_. 

129 R_Q2: float 

130 Resistance of resistor :math:`R_{Q2}`, see [1]_. 

131 omega_b: float 

132 Base frequency of the rotor in mechanical :math:`rad/s`. 

133 H_: float 

134 Defines the per unit inertia constant. 

135 K_D: float 

136 Factor that accounts for damping losses. 

137 Z_line: complex 

138 Impedance of the transmission line that connects the infinite bus to the generator. 

139 E_B: float 

140 Voltage of infinite bus. 

141 v_F: float 

142 Voltage at the field winding. 

143 T_m: float 

144 Defines the mechanical torque applied to the rotor shaft. 

145 

146 References 

147 ---------- 

148 .. [1] P. Kundur, N. J. Balu, M. G. Lauby. Power system stability and control. The EPRI power system series (1994). 

149 """ 

150 

151 def __init__(self, newton_tol): 

152 super().__init__(nvars=14, newton_tol=newton_tol) 

153 # load reference solution 

154 # data file must be generated and stored under misc/data and self.t_end = t[-1] 

155 # data = np.load(r'pySDC/projects/DAE/misc/data/synch_gen.npy') 

156 # x = data[:, 0] 

157 # y = data[:, 1:] 

158 # self.u_ref = interp1d(x, y, kind='cubic', axis=0, fill_value='extrapolate') 

159 self.t_end = 0.0 

160 

161 self.L_d = 1.8099 

162 self.L_q = 1.76 

163 self.L_F = 1.8247 

164 self.L_D = 1.8312 

165 self.L_Q1 = 2.3352 

166 self.L_Q2 = 1.735 

167 self.L_md = 1.6599 

168 self.L_mq = 1.61 

169 self.R_s = 0.003 

170 self.R_F = 0.0006 

171 self.R_D = 0.0284 

172 self.R_Q1 = 0.0062 

173 self.R_Q2 = 0.0237 

174 self.omega_b = 376.9911184307752 

175 self.H_ = 3.525 

176 self.K_D = 0.0 

177 # Line impedance 

178 self.Z_line = -0.2688022164909709 - 0.15007173591230372j 

179 # Infinite bus voltage 

180 self.E_B = 0.7 

181 # Rotor (field) operating voltages 

182 # These are modelled as constants. Intuition: permanent magnet as rotor 

183 self.v_F = 8.736809687330562e-4 

184 self.T_m = 0.854 

185 

186 def eval_f(self, u, du, t): 

187 r""" 

188 Routine to evaluate the implicit representation of the problem, i.e., :math:`F(u, u', t)`. 

189 

190 Parameters 

191 ---------- 

192 u : dtype_u 

193 Current values of the numerical solution at time t. 

194 du : dtype_u 

195 Current values of the derivative of the numerical solution at time t. 

196 t : float 

197 Current time of the numerical solution. 

198 

199 Returns 

200 ------- 

201 f : dtype_f 

202 Current value of the right-hand side of f (which includes 14 components). 

203 """ 

204 

205 # simulate torque change at t = 0.05 

206 if t >= 0.05: 

207 self.T_m = 0.354 

208 

209 f = self.dtype_f(self.init) 

210 

211 # u = [psi_d, psi_q, psi_F, psi_D, psi_Q1, psi_Q2, 

212 # i_d, i_q, i_F, i_D, i_Q1, i_Q2 

213 # omega_m, 

214 # v_d, v_q, 

215 # iz_d, iz_q, il_d, il_q, vl_d, vl_q] 

216 

217 # extract variables for readability 

218 # algebraic components 

219 psi_d, psi_q, psi_F, psi_D, psi_Q1, psi_Q2 = u.diff[0], u.diff[1], u.diff[2], u.diff[3], u.diff[4], u.diff[5] 

220 delta_r, omega_m = u.diff[6], u.diff[7] 

221 i_d, i_q, i_F, i_D, i_Q1, i_Q2 = u.alg[0], u.alg[1], u.alg[2], u.alg[3], u.alg[4], u.alg[5] 

222 

223 # differential components 

224 # these result directly from the voltage equations, introduced e.g. pg. 145 Krause 

225 dpsi_d, dpsi_q, dpsi_F, dpsi_D, dpsi_Q1, dpsi_Q2 = ( 

226 du.diff[0], 

227 du.diff[1], 

228 du.diff[2], 

229 du.diff[3], 

230 du.diff[4], 

231 du.diff[5], 

232 ) 

233 ddelta_r, domega_m = du.diff[6], du.diff[7] 

234 

235 # Network current 

236 I_Re = i_d * np.sin(delta_r) + i_q * np.cos(delta_r) 

237 I_Im = -i_d * np.cos(delta_r) + i_q * np.sin(delta_r) 

238 # Machine terminal voltages in network coordinates 

239 # Need to transform like this to subtract infinite bus voltage 

240 V_comp = self.E_B - self.Z_line * (-1) * (I_Re + 1j * I_Im) 

241 # Terminal voltages in dq0 coordinates 

242 v_d = np.real(V_comp) * np.sin(delta_r) - np.imag(V_comp) * np.cos(delta_r) 

243 v_q = np.real(V_comp) * np.cos(delta_r) + np.imag(V_comp) * np.sin(delta_r) 

244 

245 f.diff[:8] = ( 

246 -dpsi_d + self.omega_b * (v_d - self.R_s * i_d + omega_m * psi_q), 

247 -dpsi_q + self.omega_b * (v_q - self.R_s * i_q - omega_m * psi_d), 

248 -dpsi_F + self.omega_b * (self.v_F - self.R_F * i_F), 

249 -dpsi_D - self.omega_b * self.R_D * i_D, 

250 -dpsi_Q1 - self.omega_b * self.R_Q1 * i_Q1, 

251 -dpsi_Q2 - self.omega_b * self.R_Q2 * i_Q2, 

252 -ddelta_r + self.omega_b * (omega_m - 1), 

253 -domega_m 

254 + 1 / (2 * self.H_) * (self.T_m - (psi_q * i_d - psi_d * i_q) - self.K_D * self.omega_b * (omega_m - 1)), 

255 ) 

256 f.alg[:6] = ( 

257 -psi_d + self.L_d * i_d + self.L_md * i_F + self.L_md * i_D, 

258 -psi_q + self.L_q * i_q + self.L_mq * i_Q1 + self.L_mq * i_Q2, 

259 -psi_F + self.L_md * i_d + self.L_F * i_F + self.L_md * i_D, 

260 -psi_D + self.L_md * i_d + self.L_md * i_F + self.L_D * i_D, 

261 -psi_Q1 + self.L_mq * i_q + self.L_Q1 * i_Q1 + self.L_mq * i_Q2, 

262 -psi_Q2 + self.L_mq * i_q + self.L_mq * i_Q1 + self.L_Q2 * i_Q2, 

263 ) 

264 self.work_counters['rhs']() 

265 return f 

266 

267 def u_exact(self, t): 

268 """ 

269 Approximation of the exact solution generated by spline interpolation of an extremely accurate numerical reference solution. 

270 

271 Parameters 

272 ---------- 

273 t : float 

274 The time of the reference solution. 

275 

276 Returns 

277 ------- 

278 me : dtype_u 

279 The reference solution as mesh object. It contains fixed initial conditions at initial time (which includes 

280 14 components). 

281 """ 

282 me = self.dtype_u(self.init) 

283 

284 if t == 0: 

285 psi_d = 0.7770802016688648 

286 psi_q = -0.6337183129426077 

287 psi_F = 1.152966888216155 

288 psi_D = 0.9129958488040036 

289 psi_Q1 = -0.5797082294536264 

290 psi_Q2 = -0.579708229453273 

291 i_d = -0.9061043142342473 

292 i_q = -0.36006722326230495 

293 i_F = 1.45613494788927 

294 i_D = 0.0 

295 i_Q1 = 0.0 

296 i_Q2 = 0.0 

297 

298 delta_r = 39.1 * np.pi / 180 

299 omega_0 = 2 * np.pi * 60 

300 omega_b = 2 * np.pi * 60 

301 omega_m = omega_0 / omega_b # = omega_r since pf = 2 i.e. two pole machine 

302 

303 me.diff[:8] = (psi_d, psi_q, psi_F, psi_D, psi_Q1, psi_Q2, delta_r, omega_m) 

304 me.alg[:6] = (i_d, i_q, i_F, i_D, i_Q1, i_Q2) 

305 elif t < self.t_end: 

306 u_ref = self.u_ref(t) 

307 me.diff[:8] = u_ref[:8] 

308 me.alg[:6] = u_ref[8:] 

309 else: 

310 self.logger.warning("Requested time exceeds domain of the reference solution. Returning zero.") 

311 me.diff[:8] = (0, 0, 0, 0, 0, 0, 0, 0) 

312 me.alg[:6] = (0, 0, 0, 0, 0, 0) 

313 

314 return me 

315 

316 

317# class synchronous_machine_pi_line(ptype_dae): 

318# """ 

319# Synchronous machine model from Kundur (equiv. circuits fig. 3.18) 

320# attached to pi line with resistive load 

321 

322# This model does not work yet but is included as a starting point for developing similar models and in the hope that somebody will figure out why it does not work 

323# """ 

324 

325# def __init__(self, problem_params, dtype_u=mesh, dtype_f=mesh): 

326# super(synchronous_machine_pi_line, self).__init__(problem_params, dtype_u, dtype_f) 

327# # load reference solution 

328# # data file must be generated and stored under misc/data and self.t_end = t[-1] 

329# # data = np.load(r'pySDC/projects/DAE/misc/data/synch_gen.npy') 

330# # x = data[:, 0] 

331# # y = data[:, 1:] 

332# # self.u_ref = interp1d(x, y, kind='cubic', axis=0, fill_value='extrapolate') 

333# self.t_end = 0.0 

334 

335# self.L_d = 1.8099 

336# self.L_q = 1.76 

337# self.L_F = 1.8247 

338# self.L_D = 1.8312 

339# self.L_Q1 = 2.3352 

340# self.L_Q2 = 1.735 

341# self.L_md = 1.6599 

342# self.L_mq = 1.61 

343# self.R_s = 0.003 

344# self.R_F = 0.0006 

345# self.R_D = 0.0284 

346# self.R_Q1 = 0.0062 

347# self.R_Q2 = 0.0237 

348# self.omega_b = 376.9911184307752 

349# self.H_ = 3.525 

350# self.K_D = 0.0 

351# # pi line 

352# self.C_pi = 0.000002 

353# self.R_pi = 0.02 

354# self.L_pi = 0.00003 

355# # load 

356# self.R_L = 0.75 

357# self.v_F = 8.736809687330562e-4 

358# self.v_D = 0 

359# self.v_Q1 = 0 

360# self.v_Q2 = 0 

361# self.T_m = 0.854 

362 

363# def eval_f(self, u, du, t): 

364# """ 

365# Routine to evaluate the implicit representation of the problem i.e. F(u', u, t) 

366# Args: 

367# u (dtype_u): the current values. This parameter has been "hijacked" to contain [u', u] in this case to enable evaluation of the implicit representation 

368# t (float): current time 

369# Returns: 

370# Current value of F(), 21 components 

371# """ 

372 

373# # simulate torque change at t = 0.05 

374# if t >= 0.05: 

375# self.T_m = 0.354 

376 

377# f = self.dtype_f(self.init) 

378 

379# # u = [psi_d, psi_q, psi_F, psi_D, psi_Q1, psi_Q2, 

380# # i_d, i_q, i_F, i_D, i_Q1, i_Q2 

381# # omega_m, 

382# # v_d, v_q, 

383# # iz_d, iz_q, il_d, il_q, vl_d, vl_q] 

384 

385# # extract variables for readability 

386# # algebraic components 

387# psi_d, psi_q, psi_F, psi_D, psi_Q1, psi_Q2 = u[0], u[1], u[2], u[3], u[4], u[5] 

388# i_d, i_q, i_F, i_D, i_Q1, i_Q2 = u[6], u[7], u[8], u[9], u[10], u[11] 

389# # delta_r = u[12] 

390# omega_m = u[12] 

391# v_d, v_q = u[13], u[14] 

392# iz_d, iz_q, il_d, il_q, vl_d, vl_q = u[15], u[16], u[17], u[18], u[19], u[20] 

393 

394# # differential components 

395# # these result directly from the voltage equations, introduced e.g. pg. 145 Krause 

396# dpsi_d, dpsi_q, dpsi_F, dpsi_D, dpsi_Q1, dpsi_Q2 = du[0], du[1], du[2], du[3], du[4], du[5] 

397# # ddelta_r = du[12] 

398# domega_m = du[12] 

399# dv_d, dv_q = du[13], du[14] 

400# diz_d, diz_q, dvl_d, dvl_q = du[15], du[16],du[19], du[20] 

401 

402# # algebraic variables are i_d, i_q, i_F, i_D, i_Q1, i_Q2, il_d, il_q 

403 

404# f[:] = ( 

405# # differential generator 

406# dpsi_d + self.omega_b * (v_d - self.R_s * i_d + omega_m * psi_q), 

407# dpsi_q + self.omega_b * (v_q - self.R_s * i_q - omega_m * psi_d), 

408# dpsi_F + self.omega_b * (self.v_F - self.R_F * i_F), 

409# dpsi_D + self.omega_b * (self.v_D - self.R_D * i_D), 

410# dpsi_Q1 + self.omega_b * (self.v_Q1 - self.R_Q1 * i_Q1), 

411# dpsi_Q2 + self.omega_b * (self.v_Q2 - self.R_Q2 * i_Q2), 

412# -domega_m + 1 / (2 * self.H_) * (self.T_m - (psi_q * i_d - psi_d * i_q) - self.K_D * self.omega_b * (omega_m-1)), 

413# # differential pi line 

414# -dv_d + omega_m * v_q + 2/self.C_pi * (i_d - iz_d), 

415# -dv_q - omega_m * v_d + 2/self.C_pi * (i_q - iz_q), 

416# -dvl_d + omega_m * vl_q + 2/self.C_pi * (iz_d - il_d), 

417# -dvl_q - omega_m * vl_d + 2/self.C_pi * (iz_q - il_q), 

418# -diz_d - self.R_pi/self.L_pi * iz_d + omega_m * iz_q + (v_d - vl_d) / self.L_pi, 

419# -diz_q - self.R_pi/self.L_pi * iz_q - omega_m * iz_d + (v_q - vl_q) / self.L_pi, 

420# # algebraic generator 

421# psi_d + self.L_d * i_d + self.L_md * i_F + self.L_md * i_D, 

422# psi_q + self.L_q * i_q + self.L_mq * i_Q1 + self.L_mq * i_Q2, 

423# psi_F + self.L_md * i_d + self.L_F * i_F + self.L_md * i_D, 

424# psi_D + self.L_md * i_d + self.L_md * i_F + self.L_D * i_D, 

425# psi_Q1 + self.L_mq * i_q + self.L_Q1 * i_Q1 + self.L_mq * i_Q2, 

426# psi_Q2 + self.L_mq * i_q + self.L_mq * i_Q1 + self.L_Q2 * i_Q2, 

427# # algebraic pi line 

428# -il_d + vl_d/self.R_L, 

429# -il_q + vl_q/self.R_L, 

430# ) 

431# return f 

432 

433# def u_exact(self, t): 

434# """ 

435# Approximation of the exact solution generated by spline interpolation of an extremely accurate numerical reference solution. 

436# Args: 

437# t (float): current time 

438# Returns: 

439# Mesh containing fixed initial value, 5 components 

440# """ 

441# me = self.dtype_u(self.init) 

442 

443# if t == 0: 

444# psi_d = 0.3971299 

445# psi_q = 0.9219154 

446# psi_F = 0.8374232 

447# psi_D = 0.5795112 

448# psi_Q1 = 0.8433430 

449# psi_Q2 = 0.8433430 

450# i_d = -1.215876 

451# i_q = 0.5238156 

452# i_F = 1.565 

453# i_D = 0 

454# i_Q1 = 0 

455# i_Q2 = 0 

456# v_d = -0.9362397 

457# v_q = 0.4033005 

458# omega_m = 1.0 

459# # pi line 

460# iz_d = -1.215875 

461# iz_q = 0.5238151 

462# il_d = -1.215875 

463# il_q = 0.5238147 

464# vl_d = -0.9119063 

465# vl_q = 0.3928611 

466# me[:] = (psi_d, psi_q, psi_F, psi_D, psi_Q1, psi_Q2, 

467# i_d, i_q, i_F, i_D, i_Q1, i_Q2, 

468# omega_m, 

469# v_d, v_q, 

470# iz_d, iz_q, il_d, il_q, vl_d, vl_q) 

471# elif t < self.t_end: 

472# me[:] = self.u_ref(t) 

473# else: 

474# self.logger.warning("Requested time exceeds domain of the reference solution. Returning zero.") 

475# me[:] = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) 

476 

477# return me