Power System Platform  2026w10a-beta
Loading...
Searching...
No Matches
Electromechanical.cpp
1/*
2 * Copyright (C) 2017 Thales Lima Oliveira <thales@ufu.br>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "../elements/controlElement/ControlElementSolver.h"
19#include "Electromechanical.h"
20
21Electromechanical::Electromechanical(wxWindow* parent, std::vector<Element*> elementList, SimulationData data)
22{
23 m_parent = parent;
24 GetElementsFromList(elementList);
25 SetEventTimeList();
26
27 Bus dummyBus;
28 m_simData = data;
29 m_powerSystemBase = dummyBus.GetValueFromUnit(data.basePower, data.basePowerUnit);
30 m_systemFreq = data.stabilityFrequency;
31 m_simTime = data.stabilitySimulationTime;
32 m_timeStep = data.timeStep;
33 m_tolerance = data.stabilityTolerance;
34 m_maxIterations = data.stabilityMaxIterations;
35
36 m_ctrlTimeStepMultiplier = 1.0 / static_cast<double>(data.controlTimeStepRatio);
37
38 m_plotTime = data.plotTime;
39 m_useCOI = data.useCOI;
40 // If the user use all load as ZIP, updates the portions of each model, otherwise use constant impedance only.
41 for (auto it = m_loadList.begin(), itEnd = m_loadList.end(); it != itEnd; ++it) {
42 Load* load = *it;
43 auto loadData = load->GetElectricalData();
44 if (!loadData.useCompLoad) { // If no individual load composition defined.
45 if (data.useCompLoads) { // Use general composition, if defined.
46 loadData.constImpedanceActive = data.constImpedanceActive;
47 loadData.constCurrentActive = data.constCurrentActive;
48 loadData.constPowerActive = data.constPowerActive;
49 loadData.constImpedanceReactive = data.constImpedanceReactive;
50 loadData.constCurrentReactive = data.constCurrentReactive;
51 loadData.constPowerReactive = data.constPowerReactive;
52 }
53 else { // Otherwise, use constant impedance.
54 loadData.constImpedanceActive = 100.0;
55 loadData.constCurrentActive = 0.0;
56 loadData.constPowerActive = 0.0;
57 loadData.constImpedanceReactive = 100.0;
58 loadData.constCurrentReactive = 0.0;
59 loadData.constPowerReactive = 0.0;
60 }
61 }
62
63 loadData.constCurrentUV = data.underVoltageConstCurrent / 100.0;
64 loadData.constPowerUV = data.underVoltageConstPower / 100.0;
65 load->SetElectricalData(loadData);
66 }
67}
68
69Electromechanical::~Electromechanical() { }
70
71bool Electromechanical::RunStabilityCalculation()
72{
73 wxProgressDialog pbd(_("Running simulation"), _("Initializing..."), 100, m_parent,
74 wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_SMOOTH);
75
76 SetSyncMachinesModel();
77
78 // Calculate the admittance matrix with the synchronous machines.
79 if (!GetYBus(m_yBus, m_powerSystemBase, POSITIVE_SEQ, false, true, true)) {
80 m_errorMsg = _("It was not possible to build the admittance matrix.");
81 return false;
82 }
83 InsertSyncMachinesOnYBus();
84 if (!InsertIndMachinesOnYBus()) return false;
85 GetLUDecomposition(m_yBus, m_yBusL, m_yBusU);
86
87 // Get buses voltages.
88 m_vBus.clear();
89 m_vBus.resize(m_yBus.size());
90 for (auto it = m_busList.begin(), itEnd = m_busList.end(); it != itEnd; ++it) {
91 Bus* bus = *it;
92 auto data = bus->GetElectricalData();
93 if (data.number >= 0) m_vBus[data.number] = data.voltage;
94 }
95 m_vBus.shrink_to_fit();
96
97 // Calculate injected currents
98 m_iBus = ComplexMatrixTimesVector(m_yBus, m_vBus);
99 for (unsigned int i = 0; i < m_iBus.size(); ++i) {
100 if (std::abs(m_iBus[i]) < 1e-5) m_iBus[i] = std::complex<double>(0.0, 0.0);
101 }
102
103 if (!InitializeDynamicElements()) return false;
104 PreallocateVectors(); // Reserve the vectors' memory with a estimated size, this can optimize the simulation.
105
106#ifdef _DEBUG
107 for (auto* syncGenerator : m_syncGeneratorList) {
108 auto data = syncGenerator->GetPUElectricalData(m_powerSystemBase);
109 m_debugMessage += "------\n";
110 m_debugMessage += data.name + ":\n";
111 m_debugMessage += "P = " + wxString::FromDouble(data.electricalPower.real(), 5) + " p.u.\n";
112 m_debugMessage += "Q = " + wxString::FromDouble(data.electricalPower.imag(), 5) + " p.u.\n";
113 m_debugMessage += "If0 = " + wxString::FromDouble(data.initialFieldVoltage, 5) + " p.u.\n";
114 m_debugMessage += "If = " + wxString::FromDouble(data.fieldVoltage, 5) + " p.u.\n";
115 m_debugMessage += "delta = " + wxString::FromDouble(data.delta, 5) + " rad?\n";
116 m_debugMessage += "Vf = " + wxString::FromDouble(data.fieldVoltage, 5) + " p.u.\n";
117 m_debugMessage += "Vf0 = " + wxString::FromDouble(data.initialFieldVoltage, 5) + " p.u.\n";
118 m_debugMessage += "Vt = " + wxString::FromDouble(std::abs(data.terminalVoltage), 5) + " p.u.\n";
119 m_debugMessage += "theta = " + wxString::FromDouble(std::arg(data.terminalVoltage), 5) + " rad\n";
120 m_debugMessage += "Pe = " + wxString::FromDouble(data.pe, 5) + " p.u.\n";
121 m_debugMessage += "Pm = " + wxString::FromDouble(data.pm, 5) + " p.u.\n";
122 m_debugMessage += "w = " + wxString::FromDouble(data.speed, 5) + " p.u.\n";
123 m_debugMessage += "Id = " + wxString::FromDouble(data.id, 5) + " p.u.\n";
124 m_debugMessage += "Iq = " + wxString::FromDouble(data.iq, 5) + " p.u.\n";
125 m_debugMessage += "Ed' = " + wxString::FromDouble(data.tranEd, 5) + " p.u.\n";
126 m_debugMessage += "Eq' = " + wxString::FromDouble(data.tranEq, 5) + " p.u.\n";
127 m_debugMessage += "Ed'' = " + wxString::FromDouble(data.subEd, 5) + " p.u.\n";
128 m_debugMessage += "Eq'' = " + wxString::FromDouble(data.subEq, 5) + " p.u.\n";
129 m_debugMessage += "------\n\n";
130 }
131#endif
132
133 double pbdTime = m_plotTime;
134 m_currentTime = 0.0;
135 double currentPlotTime = 0.0;
136 double currentPbdTime = 0.0;
137 while (m_currentTime <= m_simTime) {
138 bool hasEvent = false;
139 if (HasEvent(m_currentTime)) {
140 hasEvent = true;
141 SetEvent(m_currentTime);
142 GetLUDecomposition(m_yBus, m_yBusL, m_yBusU);
143 }
144
145 if (currentPlotTime >= m_plotTime || m_currentTime == 0.0) {
146 m_timeVector.emplace_back(m_currentTime);
147 SaveData();
148 currentPlotTime = 0.0;
149 }
150
151 if (currentPbdTime > pbdTime) {
152 if (!pbd.Update((m_currentTime / m_simTime) * 100, wxString::Format("Time = %.2fs", m_currentTime))) {
153 m_errorMsg = wxString::Format(_("Simulation cancelled at %.2fs."), m_currentTime);
154 pbd.Update(100);
155 return false;
156 }
157 currentPbdTime = 0.0;
158 }
159
160 if (!SolveMachines()) return false;
161
162 CalculateBusesFrequency(hasEvent);
163
164 m_currentTime += m_timeStep;
165 currentPlotTime += m_timeStep;
166 currentPbdTime += m_timeStep;
167 }
168
169 return true;
170}
171
172void Electromechanical::SetEventTimeList()
173{
174 // Fault
175 for (auto it = m_busList.begin(), itEnd = m_busList.end(); it != itEnd; ++it) {
176 Bus* bus = *it;
177 auto data = bus->GetElectricalData();
178 if (data.stabHasFault) {
179 m_eventTimeList.emplace_back(data.stabFaultTime);
180 m_eventOccurrenceList.push_back(false);
181 m_eventTimeList.emplace_back(data.stabFaultTime + data.stabFaultLength);
182 m_eventOccurrenceList.push_back(false);
183 }
184 }
185 // Switching
186 for (auto it = m_powerElementList.begin(), itEnd = m_powerElementList.end(); it != itEnd; ++it) {
187 PowerElement* element = *it;
188 SwitchingData swData = element->GetSwitchingData();
189 for (unsigned int i = 0; i < swData.swTime.size(); ++i) {
190 m_eventTimeList.emplace_back(swData.swTime[i]);
191 m_eventOccurrenceList.push_back(false);
192 }
193 }
194}
195
196bool Electromechanical::HasEvent(double currentTime)
197{
198 for (unsigned int i = 0; i < m_eventTimeList.size(); ++i) {
199 if (!m_eventOccurrenceList[i]) {
200 if (EventTrigger(m_eventTimeList[i], currentTime)) {
201 m_eventOccurrenceList[i] = true;
202 return true;
203 }
204 }
205 }
206 return false;
207}
208
209void Electromechanical::SetEvent(double currentTime)
210{
211 // Fault
212 for (auto it = m_busList.begin(), itEnd = m_busList.end(); it != itEnd; ++it) {
213 Bus* bus = *it;
214 auto data = bus->GetElectricalData();
215 int n = data.number;
216 if (data.stabHasFault && n >= 0) {
217
218 // Insert fault
219 if (EventTrigger(data.stabFaultTime, currentTime)) {
220 double r, x;
221 r = data.stabFaultResistance;
222 x = data.stabFaultReactance;
223 if (x < 1e-5) x = 1e-5;
224 m_yBus[n][n] += std::complex<double>(1.0, 0.0) / std::complex<double>(r, x);
225 }
226
227 // Remove fault
228 else if (EventTrigger(data.stabFaultTime + data.stabFaultLength, currentTime)) {
229 double r, x;
230 r = data.stabFaultResistance;
231 x = data.stabFaultReactance;
232 if (x < 1e-5) x = 1e-5;
233 m_yBus[n][n] -= std::complex<double>(1.0, 0.0) / std::complex<double>(r, x);
234 }
235 }
236 }
237
238 // SyncGenerator switching
239 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
240 SyncGenerator* generator = *it;
241 auto swData = generator->GetSwitchingData();
242 for (unsigned int i = 0; i < swData.swType.size(); ++i) {
243 if (EventTrigger(swData.swTime[i], currentTime)) {
244 // Remove machine (only connected machines)
245 if (swData.swType[i] == SwitchingType::SW_REMOVE && generator->IsOnline()) {
246 generator->SetOnline(false);
247 int n = static_cast<Bus*>(generator->GetParentList()[0])->GetElectricalData().number;
248 if (n >= 0) m_yBus[n][n] -= GetSyncMachineAdmittance(generator);
249 }
250
251 // Insert machine (only disconnected machines)
252 if (swData.swType[i] == SwitchingType::SW_INSERT && !generator->IsOnline() && generator->GetParentList().size() == 1) {
253 if (generator->SetOnline(true)) {
254 int n = static_cast<Bus*>(generator->GetParentList()[0])->GetElectricalData().number;
255 if (n >= 0) m_yBus[n][n] += GetSyncMachineAdmittance(generator);
256 }
257 }
258 }
259 }
260 }
261
262 // Induction motor switching
263 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
264 IndMotor* motor = *it;
265 auto swData = motor->GetSwitchingData();
266 for (unsigned int i = 0; i < swData.swType.size(); ++i) {
267 if (EventTrigger(swData.swTime[i], currentTime)) {
268 // Remove machine (only connected machines)
269 if (swData.swType[i] == SwitchingType::SW_REMOVE && motor->IsOnline() && motor->GetParentList().size() == 1) {
270 auto data = motor->GetElectricalData();
271 motor->SetOnline(false);
272 int n = static_cast<Bus*>(motor->GetParentList()[0])->GetElectricalData().number;
273 if (n >= 0) m_yBus[n][n] -= (std::complex<double>(1, 0) / std::complex<double>(data.r1t, data.xt));
274 }
275
276 // Insert machine (only disconnected machines)
277 if (swData.swType[i] == SwitchingType::SW_INSERT && !motor->IsOnline() && motor->GetParentList().size() == 1) {
278 auto data = motor->GetElectricalData();
279 if (motor->SetOnline(true)) {
280 int n = static_cast<Bus*>(motor->GetParentList()[0])->GetElectricalData().number;
281 if (n >= 0) m_yBus[n][n] += (std::complex<double>(1, 0) / std::complex<double>(data.r1t, data.xt));
282 }
283 }
284 }
285 }
286 }
287
288 // Load switching
289 for (auto it = m_loadList.begin(), itEnd = m_loadList.end(); it != itEnd; ++it) {
290 Load* load = *it;
291 auto swData = load->GetSwitchingData();
292 for (unsigned int i = 0; i < swData.swType.size(); ++i) {
293 if (EventTrigger(swData.swTime[i], currentTime)) {
294 // Remove load (only connected loads)
295 if (swData.swType[i] == SwitchingType::SW_REMOVE && load->IsOnline() && load->GetParentList().size() == 1) {
296 load->SetOnline(false);
297 auto data = load->GetPUElectricalData(m_powerSystemBase);
298 Bus* parentBus = static_cast<Bus*>(load->GetParentList()[0]);
299 int n = parentBus->GetElectricalData().number;
300 std::complex<double> v = parentBus->GetElectricalData().voltage;
301 if (n >= 0) {
302 m_yBus[n][n] -=
303 std::complex<double>(data.activePower, -data.reactivePower) / (std::abs(v) * std::abs(v));
304 }
305 }
306
307 // Insert load (only disconnected load)
308 if (swData.swType[i] == SwitchingType::SW_INSERT && !load->IsOnline() && load->GetParentList().size() == 1) {
309 if (load->SetOnline(true)) {
310 auto data = load->GetPUElectricalData(m_powerSystemBase);
311 Bus* parentBus = static_cast<Bus*>(load->GetParentList()[0]);
312 int n = parentBus->GetElectricalData().number;
313 std::complex<double> v = parentBus->GetElectricalData().voltage;
314 if (n >= 0) {
315 m_yBus[n][n] +=
316 std::complex<double>(data.activePower, -data.reactivePower) / (std::abs(v) * std::abs(v));
317 }
318 }
319 }
320 }
321 }
322 }
323
324 // Line switching
325 for (auto it = m_lineList.begin(), itEnd = m_lineList.end(); it != itEnd; ++it) {
326 Line* line = *it;
327 auto swData = line->GetSwitchingData();
328 for (unsigned int i = 0; i < swData.swType.size(); ++i) {
329 if (EventTrigger(swData.swTime[i], currentTime)) {
330 // Remove line (only connected lines)
331 if (swData.swType[i] == SwitchingType::SW_REMOVE && line->IsOnline()) {
332 line->SetOnline(false);
333 auto data = line->GetElectricalData();
334
335 int n1 = static_cast<Bus*>(line->GetParentList()[0])->GetElectricalData().number;
336 int n2 = static_cast<Bus*>(line->GetParentList()[1])->GetElectricalData().number;
337 if (n1 >= 0 && n2 >= 0) {
338 m_yBus[n1][n2] += 1.0 / std::complex<double>(data.resistance, data.indReactance);
339 m_yBus[n2][n1] += 1.0 / std::complex<double>(data.resistance, data.indReactance);
340
341 m_yBus[n1][n1] -= 1.0 / std::complex<double>(data.resistance, data.indReactance);
342 m_yBus[n2][n2] -= 1.0 / std::complex<double>(data.resistance, data.indReactance);
343
344 m_yBus[n1][n1] -= std::complex<double>(0.0, data.capSusceptance / 2.0);
345 m_yBus[n2][n2] -= std::complex<double>(0.0, data.capSusceptance / 2.0);
346 }
347 }
348
349 // Insert line (only disconnected lines)
350 if (swData.swType[i] == SwitchingType::SW_INSERT && !line->IsOnline() && line->GetParentList().size() == 2) {
351 if (line->SetOnline(true)) {
352 auto data = line->GetElectricalData();
353
354 int n1 = static_cast<Bus*>(line->GetParentList()[0])->GetElectricalData().number;
355 int n2 = static_cast<Bus*>(line->GetParentList()[1])->GetElectricalData().number;
356 if (n1 >= 0 && n2 >= 0) {
357 m_yBus[n1][n2] -= 1.0 / std::complex<double>(data.resistance, data.indReactance);
358 m_yBus[n2][n1] -= 1.0 / std::complex<double>(data.resistance, data.indReactance);
359
360 m_yBus[n1][n1] += 1.0 / std::complex<double>(data.resistance, data.indReactance);
361 m_yBus[n2][n2] += 1.0 / std::complex<double>(data.resistance, data.indReactance);
362
363 m_yBus[n1][n1] += std::complex<double>(0.0, data.capSusceptance / 2.0);
364 m_yBus[n2][n2] += std::complex<double>(0.0, data.capSusceptance / 2.0);
365 }
366 }
367 }
368 }
369 }
370 }
371
372 // Transformer switching
373 for (auto it = m_transformerList.begin(), itEnd = m_transformerList.end(); it != itEnd; ++it) {
374 Transformer* transformer = *it;
375 auto swData = transformer->GetSwitchingData();
376 for (unsigned int i = 0; i < swData.swType.size(); ++i) {
377 if (EventTrigger(swData.swTime[i], currentTime)) {
378 // Remove transformer (only connected transformers)
379 if (swData.swType[i] == SwitchingType::SW_REMOVE && transformer->IsOnline()) {
380 transformer->SetOnline(false);
381 auto data = transformer->GetElectricalData();
382
383 int n1 = static_cast<Bus*>(transformer->GetParentList()[0])->GetElectricalData().number;
384 int n2 = static_cast<Bus*>(transformer->GetParentList()[1])->GetElectricalData().number;
385 if (n1 >= 0 && n2 >= 0) {
386 if (data.turnsRatio == 1.0 && data.phaseShift == 0.0) {
387 m_yBus[n1][n2] -= -1.0 / std::complex<double>(data.resistance, data.indReactance);
388 m_yBus[n2][n1] -= -1.0 / std::complex<double>(data.resistance, data.indReactance);
389
390 m_yBus[n1][n1] -= 1.0 / std::complex<double>(data.resistance, data.indReactance);
391 m_yBus[n2][n2] -= 1.0 / std::complex<double>(data.resistance, data.indReactance);
392 }
393 else {
394 // Complex turns ratio
395 double radPhaseShift = wxDegToRad(data.phaseShift);
396 std::complex<double> a = std::complex<double>(data.turnsRatio * std::cos(radPhaseShift),
397 -data.turnsRatio * std::sin(radPhaseShift));
398
399 // Transformer admitance
400 std::complex<double> y = 1.0 / std::complex<double>(data.resistance, data.indReactance);
401 m_yBus[n1][n1] -= y / std::pow(std::abs(a), 2.0);
402 m_yBus[n1][n2] -= -(y / std::conj(a));
403 m_yBus[n2][n1] -= -(y / a);
404 m_yBus[n2][n2] -= y;
405 }
406 }
407 }
408
409 // Insert transformer (only disconnected transformers)
410 if (swData.swType[i] == SwitchingType::SW_INSERT && !transformer->IsOnline() &&
411 transformer->GetParentList().size() == 2) {
412 if (transformer->SetOnline(true)) {
413 auto data = transformer->GetElectricalData();
414
415 int n1 = static_cast<Bus*>(transformer->GetParentList()[0])->GetElectricalData().number;
416 int n2 = static_cast<Bus*>(transformer->GetParentList()[1])->GetElectricalData().number;
417 if (n1 >= 0 && n2 >= 0) {
418 if (data.turnsRatio == 1.0 && data.phaseShift == 0.0) {
419 m_yBus[n1][n2] += -1.0 / std::complex<double>(data.resistance, data.indReactance);
420 m_yBus[n2][n1] += -1.0 / std::complex<double>(data.resistance, data.indReactance);
421
422 m_yBus[n1][n1] += 1.0 / std::complex<double>(data.resistance, data.indReactance);
423 m_yBus[n2][n2] += 1.0 / std::complex<double>(data.resistance, data.indReactance);
424 }
425 else {
426 // Complex turns ratio
427 double radPhaseShift = wxDegToRad(data.phaseShift);
428 std::complex<double> a = std::complex<double>(data.turnsRatio * std::cos(radPhaseShift),
429 -data.turnsRatio * std::sin(radPhaseShift));
430
431 // Transformer admitance
432 std::complex<double> y = 1.0 / std::complex<double>(data.resistance, data.indReactance);
433 m_yBus[n1][n1] += y / std::pow(std::abs(a), 2.0);
434 m_yBus[n1][n2] += -(y / std::conj(a));
435 m_yBus[n2][n1] += -(y / a);
436 m_yBus[n2][n2] += y;
437 }
438 }
439 }
440 }
441 }
442 }
443 }
444
445 // Capacitor switching
446 for (auto it = m_capacitorList.begin(), itEnd = m_capacitorList.end(); it != itEnd; ++it) {
447 Capacitor* capacitor = *it;
448 auto swData = capacitor->GetSwitchingData();
449 for (unsigned int i = 0; i < swData.swType.size(); ++i) {
450 if (EventTrigger(swData.swTime[i], currentTime)) {
451 // Remove capacitor (only connected capacitors)
452 if (swData.swType[i] == SwitchingType::SW_REMOVE && capacitor->IsOnline()) {
453 capacitor->SetOnline(false);
454 auto data = capacitor->GetPUElectricalData(m_powerSystemBase);
455 int n = static_cast<Bus*>(capacitor->GetParentList()[0])->GetElectricalData().number;
456 if (n >= 0) m_yBus[n][n] -= std::complex<double>(0.0, data.reactivePower);
457 }
458
459 // Insert capacitor (only disconnected capacitors)
460 if (swData.swType[i] == SwitchingType::SW_INSERT && !capacitor->IsOnline() && capacitor->GetParentList().size() == 1) {
461 if (capacitor->SetOnline(true)) {
462 auto data = capacitor->GetPUElectricalData(m_powerSystemBase);
463 int n = static_cast<Bus*>(capacitor->GetParentList()[0])->GetElectricalData().number;
464 if (n >= 0) m_yBus[n][n] += std::complex<double>(0.0, data.reactivePower);
465 }
466 }
467 }
468 }
469 }
470
471 // Inductor switching
472 for (auto it = m_inductorList.begin(), itEnd = m_inductorList.end(); it != itEnd; ++it) {
473 Inductor* inductor = *it;
474 auto swData = inductor->GetSwitchingData();
475 for (unsigned int i = 0; i < swData.swType.size(); ++i) {
476 if (EventTrigger(swData.swTime[i], currentTime)) {
477 // Remove inductor (only connected inductors)
478 if (swData.swType[i] == SwitchingType::SW_REMOVE && inductor->IsOnline()) {
479 inductor->SetOnline(false);
480 auto data = inductor->GetPUElectricalData(m_powerSystemBase);
481 int n = static_cast<Bus*>(inductor->GetParentList()[0])->GetElectricalData().number;
482 if (n >= 0) m_yBus[n][n] -= std::complex<double>(0.0, -data.reactivePower);
483 }
484
485 // Insert inductor (only disconnected inductors)
486 if (swData.swType[i] == SwitchingType::SW_INSERT && !inductor->IsOnline() && inductor->GetParentList().size() == 1) {
487 if (inductor->SetOnline(true)) {
488 auto data = inductor->GetPUElectricalData(m_powerSystemBase);
489 int n = static_cast<Bus*>(inductor->GetParentList()[0])->GetElectricalData().number;
490 if (n >= 0) m_yBus[n][n] += std::complex<double>(0.0, -data.reactivePower);
491 }
492 }
493 }
494 }
495 }
496}
497
498void Electromechanical::InsertSyncMachinesOnYBus()
499{
500 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
501 SyncGenerator* syncGenerator = *it;
502 if (syncGenerator->IsOnline()) {
503 Bus* connBus = static_cast<Bus*>(syncGenerator->GetParentList()[0]);
504 if (connBus->GetElectricalData().number < 0) {
505 syncGenerator->SetOnline(false);
506 //auto data = syncGenerator->GetElectricalData();
507 //data.activePower = 0.0;
508 //data.reactivePower = 0.0;
509 //syncGenerator->SetElectricalData(data);
510 }
511
512 if (syncGenerator->IsOnline()) {
513 auto data = syncGenerator->GetElectricalData();
514 int n = connBus->GetElectricalData().number;
515 m_yBus[n][n] += GetSyncMachineAdmittance(syncGenerator);
516 }
517 }
518 }
519}
520
521bool Electromechanical::EventTrigger(double eventTime, double currentTime)
522{
523 return (((eventTime - m_timeStep) < currentTime) && (eventTime >= currentTime));
524}
525
526std::complex<double> Electromechanical::GetSyncMachineAdmittance(SyncGenerator* generator)
527{
528 auto data = generator->GetElectricalData();
529 double k = 1.0; // Power base change factor.
530 if (data.useMachineBase) {
531 double oldBase = generator->GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
532 k = m_powerSystemBase / oldBase;
533 }
534
535 double ra = data.armResistance * k;
536 auto smModelData = GetSyncMachineModelData(generator);
537 double xd = smModelData.xd;
538 double xq = smModelData.xq;
539 double xdq = 0.5 * (xd + xq);
540 return (std::complex<double>(ra, -xdq) / std::complex<double>(ra * ra + xd * xq, 0.0));
541}
542
543bool Electromechanical::InitializeDynamicElements()
544{
545 // Buses
546 for (auto it = m_busList.begin(), itEnd = m_busList.end(); it != itEnd; ++it) {
547 Bus* bus = *it;
548 auto data = bus->GetElectricalData();
549 data.stabVoltageVector.clear();
550 data.stabVoltageVector.shrink_to_fit();
551 data.stabFreqVector.clear();
552 data.stabFreqVector.shrink_to_fit();
553 data.stabFreq = m_systemFreq;
554 data.oldAngle = std::arg(data.voltage);
555 data.filteredAngle = 0.0;
556 data.dxt = 0.0;
557 data.filteredVelocity = 0.0;
558 data.ddw = 0.0;
559 bus->SetElectricalData(data);
560 }
561 // Loads
562 for (auto* load : m_loadList) {
563 auto dataPU = load->GetPUElectricalData(m_powerSystemBase);
564 auto data = load->GetElectricalData();
565
566 double activePower = dataPU.activePower;
567 double reactivePower = dataPU.reactivePower;
568
569 Bus* bus = nullptr;
570 if (GetParentBus(load, bus))
571 {
572 data.voltage = bus->GetElectricalData().voltage;
573 }
574 else {
575 load->SetOnline(false);
576 }
577
578 data.v0 = std::abs(data.voltage);
579 data.y0 = std::complex<double>(activePower, -reactivePower) / (data.v0 * data.v0);
580
581 if (data.loadType == CONST_IMPEDANCE) {
582 std::complex<double> s0 = std::complex<double>(activePower, -reactivePower) * (data.v0 * data.v0);
583 activePower = s0.real();
584 reactivePower = -s0.imag();
585 }
586
587 data.pz0 = (data.constImpedanceActive / 100.0) * activePower;
588 data.pi0 = (data.constCurrentActive / 100.0) * activePower;
589 data.pp0 = (data.constPowerActive / 100.0) * activePower;
590
591 data.qz0 = (data.constImpedanceReactive / 100.0) * reactivePower;
592 data.qi0 = (data.constCurrentReactive / 100.0) * reactivePower;
593 data.qp0 = (data.constPowerReactive / 100.0) * reactivePower;
594
595 data.voltageVector.clear();
596 data.voltageVector.shrink_to_fit();
597 data.electricalPowerVector.clear();
598 data.electricalPowerVector.shrink_to_fit();
599
600 if (load->IsOnline())
601 data.electricalPower = std::complex<double>(activePower, reactivePower);
602 else {
603 data.electricalPower = std::complex<double>(0.0, 0.0);
604 data.voltage = std::complex<double>(0.0, 0.0);
605 }
606
607 load->SetElectricalData(data);
608 }
609 // Synchronous generators
610 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
611 SyncGenerator* syncGenerator = *it;
612 auto dataPU = syncGenerator->GetPUElectricalData(m_powerSystemBase);
613 auto data = syncGenerator->GetElectricalData();
614 double k = 1.0; // Power base change factor.
615 if (data.useMachineBase) {
616 double oldBase = syncGenerator->GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
617 k = m_powerSystemBase / oldBase;
618 }
619 Bus* bus = nullptr;
620 if (GetParentBus(syncGenerator, bus))
621 data.terminalVoltage = bus->GetElectricalData().voltage;
622 else
623 data.terminalVoltage = std::complex<double>(1.0, 0.0);
624
625 std::complex<double> conjS(dataPU.activePower, -dataPU.reactivePower);
626 std::complex<double> vt = data.terminalVoltage;
627 std::complex<double> ia = conjS / std::conj(vt);
628
629 double xd = data.syncXd * k;
630 double xq = data.syncXq * k;
631 double ra = data.armResistance * k;
632
633 if (data.model == Machines::SM_MODEL_1) {
634 xq = data.transXd * k;
635 xd = xq;
636 }
637 else if (data.syncXq == 0.0)
638 xq = data.syncXd * k;
639
640 double sd = 1.0;
641 double sq = 1.0;
642 double satF = 1.0;
643 double xp = data.potierReactance * k;
644 bool hasSaturation = false;
645 if (data.satFactor != 0.0) { // Have saturation.
646 satF = (data.satFactor - 1.2) / std::pow(1.2, 7);
647 if (xp == 0.0) xp = 0.8 * (data.transXd * k);
648 hasSaturation = true;
649 }
650
651 // Initialize state variables
652 std::complex<double> eq0 = vt + std::complex<double>(ra, xq) * ia;
653 double delta = std::arg(eq0);
654
655 double id0, iq0, vd0, vq0;
656 ABCtoDQ0(ia, delta, id0, iq0);
657 ABCtoDQ0(vt, delta, vd0, vq0);
658
659 // Initialize saturation
660 double xqs = xq;
661 double xds = xd;
662 if (hasSaturation) {
663 double oldDelta = 0;
664 bool exit = false;
665 int numIt = 0;
666 while (!exit) {
667 oldDelta = delta;
668 ABCtoDQ0(ia, delta, id0, iq0);
669 ABCtoDQ0(vt, delta, vd0, vq0);
670
671 // Direct-axis Potier voltage.
672 double epd = vd0 + ra * id0 + xp * iq0;
673
674 sq = 1.0 + satF * (xq / xd) * std::pow(epd, 6);
675 xqs = (xq - xp) / sq + xp;
676 eq0 = data.terminalVoltage + std::complex<double>(ra, xqs) * ia;
677 delta = std::arg(eq0);
678 if (std::abs(delta - oldDelta) < m_saturationTolerance) {
679 exit = true;
680 }
681 else if (numIt >= m_maxIterations) {
682 m_errorMsg = _("Error on initializate the saturation values of \"") + data.name + _("\".");
683 return false;
684 }
685 numIt++;
686 }
687 // Quadrature-axis Potier voltage.
688 double epq = vq0 + ra * iq0 - xp * id0;
689 sd = 1.0 + satF * std::pow(epq, 6);
690 xds = (xd - xp) / sd + xp;
691 }
692
693 double ef0 = vq0 + ra * iq0 - xds * id0;
694
695 data.initialFieldVoltage = ef0 * sd;
696 data.fieldVoltage = data.initialFieldVoltage;
697 data.pm = std::real((data.terminalVoltage * std::conj(ia)) + (std::abs(ia) * std::abs(ia) * ra));
698 syncGenerator->IsOnline() ? data.speed = 2.0 * M_PI * m_systemFreq : data.speed = 2.0 * M_PI * data.ocFrequency;
699 data.delta = delta;
700 data.pe = data.pm;
701 data.electricalPower = std::complex<double>(dataPU.activePower, dataPU.reactivePower);
702 if (!syncGenerator->IsOnline()) data.electricalPower = std::complex<double>(0.0, 0.0);
703 data.sd = sd;
704 data.sq = sq;
705 data.id = id0;
706 data.iq = iq0;
707
708 // Variables to extrapolate.
709 data.oldIq = iq0;
710 data.oldId = id0;
711 data.oldPe = data.pe;
712 data.oldSd = sd;
713 data.oldSq = sq;
714
715 switch (data.model) {
716 case Machines::SM_MODEL_1: {
717 data.tranEq = std::abs(eq0);
718
719 data.tranEd = 0.0;
720 data.subEq = 0.0;
721 data.subEd = 0.0;
722 } break;
723 case Machines::SM_MODEL_2: {
724 double tranXd = data.transXd * k;
725
726 data.tranEq = ef0 + (xd - tranXd) * (id0 / sd);
727 data.tranEd = 0.0;
728 data.subEd = 0.0;
729 data.subEq = 0.0;
730 } break;
731 case Machines::SM_MODEL_3: {
732 double tranXd = data.transXd * k;
733 double tranXq = data.transXq * k;
734 if (tranXq == 0.0) tranXq = tranXd;
735
736 data.tranEq = ef0 + (xd - tranXd) * (id0 / sd);
737 data.tranEd = -(xq - tranXq) * (iq0 / sq);
738
739 data.subEd = 0.0;
740 data.subEq = 0.0;
741 } break;
742 case Machines::SM_MODEL_4: {
743 double tranXd = data.transXd * k;
744 double subXd = data.subXd * k;
745 double subXq = data.subXq * k;
746 if (subXd == 0.0) subXd = subXq;
747 if (subXq == 0.0) subXq = subXd;
748
749 data.tranEq = ef0 + (xd - tranXd) * (id0 / sd);
750 data.tranEd = 0.0;
751 data.subEq = data.tranEq + (tranXd - subXd) * (id0 / sd);
752 data.subEd = -(xq - subXq) * (iq0 / sq);
753 } break;
754 case Machines::SM_MODEL_5: {
755 double tranXd = data.transXd * k;
756 double tranXq = data.transXq * k;
757 double subXd = data.subXd * k;
758 double subXq = data.subXq * k;
759 if (subXd == 0.0) subXd = subXq;
760 if (subXq == 0.0) subXq = subXd;
761
762 data.tranEq = ef0 + (xd - tranXd) * (id0 / sd);
763 data.tranEd = -(xq - tranXq) * (iq0 / sq);
764 data.subEq = data.tranEq + (tranXd - subXd) * (id0 / sd);
765 data.subEd = data.tranEd - (tranXq - subXq) * (iq0 / sq);
766 } break;
767 default:
768 break;
769 }
770
771 // Initialize controllers
772 if (data.useAVR) {
773 //if (data.avrSolver) delete data.avrSolver;
774 //data.avrSolver =
775 // new ControlElementSolver(data.avr, m_timeStep * m_ctrlTimeStepMultiplier, m_tolerance, m_parent);
776 data.avrSolver = std::make_shared<ControlElementSolver>(data.avr, m_timeStep * m_ctrlTimeStepMultiplier, m_tolerance, m_parent);
777
778 data.avrSolver->SetSwitchStatus(syncGenerator->IsOnline());
779 data.avrSolver->SetCurrentTime(m_currentTime);
780 data.avrSolver->SetTerminalVoltage(std::abs(data.terminalVoltage));
781 data.avrSolver->SetInitialTerminalVoltage(std::abs(data.terminalVoltage));
782 data.avrSolver->SetActivePower(dataPU.activePower);
783 data.avrSolver->SetReactivePower(dataPU.reactivePower);
784 data.avrSolver->SetVelocity(data.speed);
785 data.avrSolver->SetInitialVelocity(data.speed);
786 data.avrSolver->InitializeValues(false);
787 if (!data.avrSolver->IsOK()) {
788 m_errorMsg = _("Error on initializate the AVR of \"") + data.name + wxT("\".\n") +
789 data.avrSolver->GetErrorMessage();
790 syncGenerator->SetElectricalData(data);
791 return false;
792 }
793 }
794 if (data.useSpeedGovernor) {
795 //if (data.speedGovSolver) delete data.speedGovSolver;
796 //data.speedGovSolver =
797 // new ControlElementSolver(data.speedGov, m_timeStep * m_ctrlTimeStepMultiplier, m_tolerance, m_parent);
798
799 data.speedGovSolver = std::make_shared<ControlElementSolver>(data.speedGov, m_timeStep * m_ctrlTimeStepMultiplier, m_tolerance, m_parent);
800 data.speedGovSolver->SetSwitchStatus(syncGenerator->IsOnline());
801 data.speedGovSolver->SetCurrentTime(m_currentTime);
802 data.speedGovSolver->SetActivePower(dataPU.activePower);
803 data.speedGovSolver->SetReactivePower(dataPU.reactivePower);
804 data.speedGovSolver->SetVelocity(data.speed);
805 data.speedGovSolver->SetInitialVelocity(data.speed);
806 data.speedGovSolver->SetInitialMecPower(data.pm);
807 data.speedGovSolver->InitializeValues(false);
808 if (!data.speedGovSolver->IsOK()) {
809 m_errorMsg = _("Error on initializate the speed governor of \"") + data.name + wxT("\".\n") +
810 data.speedGovSolver->GetErrorMessage();
811 syncGenerator->SetElectricalData(data);
812 return false;
813 }
814 }
815 //}
816 //} else {
817 // Initialize open circuit machine.
818 //}
819 // Reset plot data
820 data.terminalVoltageVector.clear();
821 data.terminalVoltageVector.shrink_to_fit();
822 data.electricalPowerVector.clear();
823 data.electricalPowerVector.shrink_to_fit();
824 data.mechanicalPowerVector.clear();
825 data.mechanicalPowerVector.shrink_to_fit();
826 data.freqVector.clear();
827 data.freqVector.shrink_to_fit();
828 data.fieldVoltageVector.clear();
829 data.fieldVoltageVector.shrink_to_fit();
830 data.deltaVector.clear();
831 data.deltaVector.shrink_to_fit();
832
833 syncGenerator->SetElectricalData(data);
834 }
835 // Induction motors
836 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
837 IndMotor* indMotor = *it;
838 auto dataPU = indMotor->GetPUElectricalData(m_powerSystemBase);
839 auto data = indMotor->GetElectricalData();
840
841 Bus* connBus = static_cast<Bus*>(indMotor->GetParentList()[0]);
842 if (connBus->GetElectricalData().number < 0) indMotor->SetOnline(false);
843
844 double w0 = 2.0 * M_PI * m_systemFreq;
845 std::complex<double> i1 = std::complex<double>(dataPU.activePower, -data.q0) / std::conj(data.terminalVoltage);
846 double ir = std::real(i1);
847 double im = std::imag(i1);
848 std::complex<double> e = data.terminalVoltage - std::complex<double>(data.r1t, data.x1t) * i1;
849 double te = std::real(e * std::conj(i1)) / w0;
850
851 double wi = w0 * (1 - data.s0); // Initial velocity
852 data.as = te * (data.aw + (data.bw * w0) / wi + (data.cw * w0 * w0) / (wi * wi));
853 data.bs = te * ((data.bw * w0) / wi + (2.0 * data.cw * w0 * w0) / (wi * wi));
854 data.cs = (te * data.cw * w0 * w0) / (wi * wi);
855
856 data.aCalc = data.as;
857 data.bCalc = data.bs;
858 data.cCalc = data.cs;
859
860 if (indMotor->IsOnline()) {
861 std::complex<double> tranE =
862 (std::complex<double>(0, data.x0 - data.xt) * i1) / std::complex<double>(1.0, w0 * data.s0 * data.t0);
863
864 data.tranEr = std::real(tranE);
865 data.tranEm = std::imag(tranE);
866
867 data.slip = data.s0;
868 data.ir = ir;
869 data.im = im;
870 data.te = te;
871 }
872 else { // Offline
873 data.tranEr = 0.0;
874 data.tranEm = 0.0;
875 data.slip = 1.0 - 1e-7;
876 data.ir = 0.0;
877 data.im = 0.0;
878 data.te = 0.0;
879 }
880
881 // Variables to extrapolate.
882 data.oldTe = data.te;
883 data.oldIr = data.ir;
884 data.oldIm = data.im;
885
886 // Reset plot data
887 data.slipVector.clear();
888 data.slipVector.shrink_to_fit();
889 data.electricalTorqueVector.clear();
890 data.electricalTorqueVector.shrink_to_fit();
891 data.mechanicalTorqueVector.clear();
892 data.mechanicalTorqueVector.shrink_to_fit();
893 data.velocityVector.clear();
894 data.velocityVector.shrink_to_fit();
895 data.currentVector.clear();
896 data.currentVector.shrink_to_fit();
897 data.terminalVoltageVector.clear();
898 data.terminalVoltageVector.shrink_to_fit();
899 data.activePowerVector.clear();
900 data.activePowerVector.shrink_to_fit();
901 data.reactivePowerVector.clear();
902 data.reactivePowerVector.shrink_to_fit();
903
904 indMotor->SetElectricalData(data);
905 }
906 CalculateReferenceSpeed();
907 return true;
908}
909
910bool Electromechanical::CalculateInjectedCurrents()
911{
912 // Reset injected currents vector
913 for (unsigned int i = 0; i < m_iBus.size(); ++i) m_iBus[i] = std::complex<double>(0.0, 0.0);
914
915 // Synchronous machines
916 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
917 SyncGenerator* syncGenerator = *it;
918 auto data = syncGenerator->GetElectricalData();
919 if (syncGenerator->IsOnline()) {
920 double k = 1.0; // Power base change factor.
921 if (data.useMachineBase) {
922 double oldBase = syncGenerator->GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
923 k = m_powerSystemBase / oldBase;
924 }
925
926 double ra = data.armResistance * k;
927 double xp = data.potierReactance * k;
928 if (xp == 0.0) xp = 0.8 * data.transXd * k;
929
930 int n = static_cast<Bus*>(syncGenerator->GetParentList()[0])->GetElectricalData().number;
931 std::complex<double> e = std::complex<double>(0.0, 0.0);
932 std::complex<double> v = m_vBus[n];
933 std::complex<double> iInj = m_iBus[n];
934
935 auto smModelData = GetSyncMachineModelData(syncGenerator);
936 DQ0toABC(smModelData.ed, smModelData.eq, data.delta, e);
937 double xd = smModelData.xd;
938 double xq = smModelData.xq;
939
940 double sd = data.sd;
941 double sq = data.sq;
942 double id, iq;
943
944 // Calculate the saturation effect
945 if (data.satFactor != 0.0) {
946 if (!CalculateSyncMachineSaturation(syncGenerator, id, iq, sd, sq, false, k)) return false;
947 }
948
949 double xdq, xds, xqs, xdqs;
950 xdq = 0.5 * (xd + xq);
951 xds = (xd - xp) / sd + xp;
952 xqs = (xq - xp) / sq + xp;
953 xdqs = 0.5 * (xds + xqs);
954
955 std::complex<double> y0 = std::complex<double>(ra, -xdq) / std::complex<double>(ra * ra + xd * xq, 0.0);
956 std::complex<double> iUnadjusted = y0 * v;
957
958 // [Ref] Arrillaga, J.; Arnold, C. P.. "Computer Modelling of Electrical Power Systems". Pg. 225-226
959 // [Ref] Dommell, H. W.; Sato, N.. "Fast transient stability solutions". IEEE Transactions on Power
960 // Apparatus and Systems, PAS-91 (4), 1643-1650
961 std::complex<double> iSaliency = std::complex<double>(0.0, -((0.5 * (xqs - xds)) / (ra * ra + xds * xqs))) *
962 (std::conj(e) - std::conj(v));
963 iSaliency = iSaliency * std::cos(2.0 * data.delta) +
964 iSaliency * std::complex<double>(0.0, std::sin(2.0 * data.delta));
965
966 // [Ref] Arrillaga, J.; Arnold, C. P.; Computer Modelling of Electrical Power Systems. Pg. 258-259
967 std::complex<double> y0s = std::complex<double>(ra, -xdqs) / std::complex<double>(ra * ra + xds * xqs, 0.0);
968 std::complex<double> iSaturation = y0s * (e - v);
969
970 iInj = iUnadjusted + iSaliency + iSaturation;
971
972 m_iBus[n] += iInj;
973
974 // Remove the current flowing through y0 (i.e. iUnadjusted in this case, y0 is inserted in admittance
975 // matrix) to calculate the electrical power.
976 std::complex<double> iMachine = iInj - iUnadjusted;
977 data.electricalPower = v * std::conj(iMachine);
978
979 ABCtoDQ0(iMachine, data.delta, id, iq);
980
981 data.id = id;
982 data.iq = iq;
983 data.sd = sd;
984 data.sq = sq;
985 }
986 else {
987 data.electricalPower = std::complex<double>(0.0, 0.0);
988 }
989
990 syncGenerator->SetElectricalData(data);
991 }
992 // Induction motors
993 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
994 IndMotor* motor = *it;
995 auto data = motor->GetElectricalData();
996 if (motor->IsOnline()) {
997 int n = static_cast<Bus*>(motor->GetParentList()[0])->GetElectricalData().number;
998 std::complex<double> y0 = std::complex<double>(1.0, 0.0) / std::complex<double>(data.r1t, data.xt);
999 std::complex<double> v = m_vBus[n];
1000 std::complex<double> e = std::complex<double>(data.tranEr, data.tranEm);
1001 std::complex<double> iInj = y0 * e;
1002 m_iBus[n] += iInj;
1003
1004 std::complex<double> iMachine = y0 * (v - e);
1005
1006 data.ir = std::real(iMachine);
1007 data.im = std::imag(iMachine);
1008 }
1009 else {
1010 data.ir = 0.0;
1011 data.im = 0.0;
1012 }
1013 motor->SetElectricalData(data);
1014 }
1015
1016 // Loads
1017 for (auto it = m_loadList.begin(), itEnd = m_loadList.end(); it != itEnd; ++it) {
1018 Load* load = *it;
1019 auto data = load->GetElectricalData();
1020 Bus* bus = nullptr;
1021 if (!GetParentBus(load, bus)) {
1022 load->SetOnline(false);
1023 }
1024 else if (load->IsOnline()) {
1025
1026 data.voltage = m_vBus[bus->GetElectricalData().number];
1027 double vAbs = std::abs(data.voltage);
1028
1029 double pz, pi, pp, qz, qi, qp;
1030 pz = data.pz0 * std::pow(vAbs / data.v0, 2);
1031 pi = data.pi0 * (vAbs / data.v0);
1032 pp = data.pp0;
1033 qz = data.qz0 * std::pow(vAbs / data.v0, 2);
1034 qi = data.qi0 * (vAbs / data.v0);
1035 qp = data.qp0;
1036
1037 // If voltage value is low, set the ZIP load to constant impedance.
1038 if (vAbs < data.constCurrentUV) {
1039 pi = data.pi0 * (data.constCurrentUV / data.v0) * std::pow(vAbs / data.constCurrentUV, 2);
1040 qi = data.qi0 * (data.constCurrentUV / data.v0) * std::pow(vAbs / data.constCurrentUV, 2);
1041 }
1042 if (vAbs < data.constPowerUV) {
1043 pp *= std::pow(vAbs / data.constPowerUV, 2);
1044 qp *= std::pow(vAbs / data.constPowerUV, 2);
1045 }
1046
1047 double activePower = pz + pi + pp;
1048 double reactivePower = qz + qi + qp;
1049
1050 std::complex<double> newY = std::complex<double>(activePower, -reactivePower) / (vAbs * vAbs);
1051 m_iBus[bus->GetElectricalData().number] += (data.y0 - newY) * data.voltage;
1052
1053 data.electricalPower = std::complex<double>(activePower, reactivePower);
1054 }
1055 else {
1056 data.voltage = std::complex<double>(0.0, 0.0);
1057 data.electricalPower = std::complex<double>(0.0, 0.0);
1058 }
1059
1060 load->SetElectricalData(data);
1061 }
1062 return true;
1063}
1064
1065void Electromechanical::CalculateIntegrationConstants(SyncGenerator* syncGenerator, double id, double iq, double k)
1066{
1067 CalculateReferenceSpeed();
1068 auto data = syncGenerator->GetElectricalData();
1069
1070 double syncXd, syncXq, transXd, transXq, subXd, subXq;
1071 syncXd = data.syncXd * k;
1072 syncXq = data.syncXq * k;
1073 transXd = data.transXd * k;
1074 transXq = data.transXq * k;
1075 subXd = data.subXd * k;
1076 subXq = data.subXq * k;
1077
1078 if (syncXq == 0.0) syncXq = syncXd;
1079 if (transXq == 0.0) transXq = transXd;
1080 if (subXd == 0.0) subXd = subXq;
1081 if (subXq == 0.0) subXq = subXd;
1082
1083 double transTd0, transTq0, subTd0, subTq0;
1084 transTd0 = data.transTd0;
1085 transTq0 = data.transTq0;
1086 subTd0 = data.subTd0;
1087 subTq0 = data.subTq0;
1088
1089 if (subTd0 == 0.0) subTd0 = subTq0;
1090 if (subTq0 == 0.0) subTq0 = subTd0;
1091
1092 // Speed
1093 data.icSpeed.m = m_timeStep / ((4.0f * data.inertia / m_refSpeed) / k + m_timeStep * data.damping * k);
1094 data.icSpeed.c = (1.0f - 2.0f * data.icSpeed.m * data.damping * k) * data.speed +
1095 data.icSpeed.m * (data.pm - data.pe + 2.0f * m_refSpeed * data.damping * k);
1096
1097 // Delta
1098 data.icDelta.m = 0.5f * m_timeStep;
1099 data.icDelta.c = data.delta + data.icDelta.m * (data.speed - 2.0f * m_refSpeed);
1100
1101 // Eq'
1102 if (data.model == Machines::SM_MODEL_2 || data.model == Machines::SM_MODEL_3 || data.model == Machines::SM_MODEL_4 ||
1103 data.model == Machines::SM_MODEL_5) {
1104 data.icTranEq.m = m_timeStep / (2.0f * transTd0 + m_timeStep);
1105 data.icTranEq.c = (1.0 - data.icTranEq.m * (1.0 + data.sd)) * data.tranEq +
1106 data.icTranEq.m * (data.fieldVoltage + (syncXd - transXd) * id);
1107 }
1108
1109 // Ed'
1110 if (data.model == Machines::SM_MODEL_3 || data.model == Machines::SM_MODEL_4 || data.model == Machines::SM_MODEL_5) {
1111 data.icTranEd.m = m_timeStep / (2.0f * transTq0 + m_timeStep);
1112 data.icTranEd.c =
1113 (1.0 - data.icTranEd.m * (1.0 + data.sq)) * data.tranEd - data.icTranEd.m * (syncXq - transXq) * iq;
1114 }
1115
1116 // Eq''
1117 if (data.model == Machines::SM_MODEL_4 || data.model == Machines::SM_MODEL_5) {
1118 data.icSubEq.m = m_timeStep / (2.0f * subTd0 + m_timeStep);
1119 data.icSubEq.c = (1.0 - data.icSubEq.m * (1.0 + data.sd)) * data.subEq +
1120 data.icSubEq.m * (data.sd * data.tranEq + (transXd - subXd) * id);
1121 }
1122 // Ed''
1123 if (data.model == Machines::SM_MODEL_4) {
1124 data.icSubEd.m = m_timeStep / (2.0f * subTq0 + m_timeStep);
1125 data.icSubEd.c =
1126 (1.0f - data.icSubEd.m * (1.0 + data.sq)) * data.subEd - data.icSubEd.m * (syncXq - subXq) * iq;
1127 }
1128 if (data.model == Machines::SM_MODEL_5) {
1129 data.icSubEd.m = m_timeStep / (2.0f * subTq0 + m_timeStep);
1130 data.icSubEd.c = (1.0f - data.icSubEd.m * (1.0 + data.sq)) * data.subEd +
1131 data.icSubEd.m * (data.sq * data.tranEd - (transXq - subXq) * iq);
1132 }
1133
1134 syncGenerator->SetElectricalData(data);
1135}
1136
1137void Electromechanical::CalculateIntegrationConstants(IndMotor* indMotor, double ir, double im, double k)
1138{
1139 double w0 = 2.0 * M_PI * m_systemFreq;
1140
1141 auto data = indMotor->GetElectricalData();
1142
1143 // If the velocity is too low set mechanical torque to zero (a, b and c coeficients)
1144 if (data.slip > 0.99999) {
1145 data.as = 0.0;
1146 data.bs = 0.0;
1147 data.cs = 0.0;
1148 }
1149 else {
1150 data.as = data.aCalc;
1151 data.bs = data.bCalc;
1152 data.cs = data.cCalc;
1153 }
1154
1155 // Mechanical equations
1156 // s
1157 data.icSlip.m = m_timeStep / ((4.0f * data.inertia / w0) / k + data.bs * m_timeStep);
1158 data.icSlip.c = data.slip * (1.0 - 2.0 * data.bs * data.icSlip.m) +
1159 data.icSlip.m * (2.0 * data.as + data.cs * data.slip * data.slip - data.te);
1160
1161 // Electrical equations
1162 // Er
1163 data.icTranEr.m = m_timeStep / (2.0 * data.t0 + m_timeStep);
1164 data.icTranEr.c = data.tranEr * (1.0 - 2.0 * data.icTranEr.m) +
1165 data.icTranEr.m * (w0 * data.t0 * data.slip * data.tranEm - (data.x0 - data.xt) * im);
1166 // Em
1167 data.icTranEm.m = m_timeStep / (2.0 * data.t0 + m_timeStep);
1168 data.icTranEm.c = data.tranEm * (1.0 - 2.0 * data.icTranEm.m) -
1169 data.icTranEm.m * (w0 * data.t0 * data.slip * data.tranEr - (data.x0 - data.xt) * ir);
1170
1171 indMotor->SetElectricalData(data);
1172}
1173
1174bool Electromechanical::SolveMachines()
1175{
1176 // First interation values and extrapolations
1177 // Synchronous machines
1178 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
1179 SyncGenerator* syncGenerator = *it;
1180 auto data = syncGenerator->GetElectricalData();
1181
1182 if (syncGenerator->IsOnline()) {
1183 double id, iq, pe, sd, sq;
1184 pe = data.pe;
1185 id = data.id;
1186 iq = data.iq;
1187 sd = data.sd;
1188 sq = data.sq;
1189
1190 double k = 1.0; // Power base change factor.
1191 if (data.useMachineBase) {
1192 double oldBase = syncGenerator->GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
1193 k = m_powerSystemBase / oldBase;
1194 }
1195
1196 // Calculate integration constants.
1197 CalculateIntegrationConstants(syncGenerator, id, iq, k);
1198
1199 if (!CalculateNonIntVariables(syncGenerator, id, iq, sd, sq, pe, k)) return false;
1200 // Extrapolate nonintegrable variables.
1201 id = 2.0 * id - data.oldId;
1202 iq = 2.0 * iq - data.oldIq;
1203 pe = 2.0 * pe - data.oldPe;
1204 sd = 2.0 * sd - data.oldSd;
1205 sq = 2.0 * sq - data.oldSq;
1206
1207 CalculateIntVariables(syncGenerator, id, iq, sd, sq, pe, k);
1208 }
1209 else {
1210 CalculateIntegrationConstants(syncGenerator, 0.0f, 0.0f);
1211 }
1212 }
1213 // Induction machines
1214 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
1215 IndMotor* indMotor = *it;
1216 auto data = indMotor->GetElectricalData();
1217
1218 //if(indMotor->IsOnline()) {
1219 double ir, im, te;
1220 te = data.te;
1221 ir = data.ir;
1222 im = data.im;
1223
1224 double k = 1.0; // Power base change factor.
1225 if (data.useMachinePowerAsBase) {
1226 double oldBase = indMotor->GetValueFromUnit(data.ratedPower, data.ratedPowerUnit);
1227 k = m_powerSystemBase / oldBase;
1228 }
1229
1230 // Calculate integration constants.
1231 CalculateIntegrationConstants(indMotor, ir, im, k);
1232
1233 if (!CalculateNonIntVariables(indMotor, ir, im, te, k)) return false;
1234 // Extrapolate nonintegrable variables.
1235 ir = 2.0 * ir - data.oldIr;
1236 im = 2.0 * im - data.oldIm;
1237 te = 2.0 * te - data.oldTe;
1238
1239 CalculateIntVariables(indMotor, ir, im, te, k);
1240 //} else {
1241 //CalculateIntegrationConstants(indMotor, 0.0f, 0.0f);
1242 //}
1243 }
1244
1245 double error = 1.0;
1246 int iterations = 0;
1247 while (error > m_tolerance) {
1248 error = 0.0;
1249
1250 // Calculate the injected currents.
1251 if (!CalculateInjectedCurrents()) return false;
1252
1253 // Calculate the buses voltages.
1254 m_vBus = LUEvaluate(m_yBusU, m_yBusL, m_iBus);
1255
1256 // Solve synchronous machine equations.
1257 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
1258 SyncGenerator* syncGenerator = *it;
1259
1260 auto data = syncGenerator->GetElectricalData();
1261
1262 double id = data.id;
1263 double iq = data.iq;
1264 double pe = data.pe;
1265 double sd = data.sd;
1266 double sq = data.sq;
1267
1268 // Power base change factor.
1269 double k = 1.0;
1270 if (data.useMachineBase) {
1271 double oldBase = syncGenerator->GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
1272 k = m_powerSystemBase / oldBase;
1273 }
1274
1275 // Calculate id, iq, dq, sd
1276 if (!CalculateNonIntVariables(syncGenerator, id, iq, sd, sq, pe, k)) return false;
1277
1278 double genError = CalculateIntVariables(syncGenerator, id, iq, sd, sq, pe, k);
1279
1280 if (genError > error) error = genError;
1281 }
1282
1283 // Solve induction machine equation
1284 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
1285 IndMotor* indMotor = *it;
1286
1287 auto data = indMotor->GetElectricalData();
1288
1289 double ir = data.ir;
1290 double im = data.im;
1291 double te = data.te;
1292
1293 // Power base change factor.
1294 double k = 1.0;
1295 if (data.useMachinePowerAsBase) {
1296 double oldBase = indMotor->GetValueFromUnit(data.ratedPower, data.ratedPowerUnit);
1297 k = m_powerSystemBase / oldBase;
1298 }
1299
1300 // Calculate te
1301 if (!CalculateNonIntVariables(indMotor, ir, im, te, k)) return false;
1302
1303 double motorError = CalculateIntVariables(indMotor, ir, im, te, k);
1304
1305 if (motorError > error) error = motorError;
1306 }
1307
1308 ++iterations;
1309
1310 if (iterations > m_maxIterations) {
1311 m_errorMsg = _("Impossible to solve the system machines.\nCheck the system parameters and/or "
1312 "decrease the time step.");
1313 return false;
1314 }
1315 }
1316 m_iterationsNum = iterations;
1317
1318 // Solve controllers.
1319 int ctrlRatio = static_cast<int>(1 / m_ctrlTimeStepMultiplier);
1320 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
1321 SyncGenerator* syncGenerator = *it;
1322 auto data = syncGenerator->GetElectricalData();
1323 if (data.useAVR && data.avrSolver) {
1324 data.avrSolver->SetSwitchStatus(syncGenerator->IsOnline());
1325 data.avrSolver->SetCurrentTime(m_currentTime);
1326 data.avrSolver->SetTerminalVoltage(std::abs(data.terminalVoltage));
1327 data.avrSolver->SetDeltaActivePower((data.electricalPower.real() - data.avrSolver->GetActivePower()) /
1328 m_timeStep);
1329 data.avrSolver->SetActivePower(data.electricalPower.real());
1330 data.avrSolver->SetReactivePower(data.electricalPower.imag());
1331 data.avrSolver->SetDeltaVelocity((data.speed - data.avrSolver->GetVelocity()) / m_timeStep);
1332 data.avrSolver->SetVelocity(data.speed);
1333
1334 for (int i = 0; i < ctrlRatio; ++i) data.avrSolver->SolveNextStep();
1335
1336 data.fieldVoltage = data.initialFieldVoltage + data.avrSolver->GetFieldVoltage();
1337 }
1338
1339 if (data.useSpeedGovernor && data.speedGovSolver) {
1340 data.speedGovSolver->SetSwitchStatus(syncGenerator->IsOnline());
1341 data.speedGovSolver->SetCurrentTime(m_currentTime);
1342 data.speedGovSolver->SetVelocity(data.speed);
1343 data.speedGovSolver->SetActivePower(data.electricalPower.real());
1344 data.speedGovSolver->SetReactivePower(data.electricalPower.imag());
1345
1346 for (int i = 0; i < ctrlRatio; ++i) data.speedGovSolver->SolveNextStep();
1347
1348 data.pm = data.speedGovSolver->GetMechanicalPower();
1349 }
1350 syncGenerator->SetElectricalData(data);
1351 }
1352
1353 return true;
1354}
1355
1356void Electromechanical::SaveData()
1357{
1358 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
1359 SyncGenerator* syncGenerator = *it;
1360 auto data = syncGenerator->GetElectricalData();
1361 if (data.plotSyncMachine) { syncGenerator->SavePlotData(); }
1362 }
1363 for (auto it = m_busList.begin(), itEnd = m_busList.end(); it != itEnd; ++it) {
1364 Bus* bus = *it;
1365 auto data = bus->GetElectricalData();
1366 if (data.plotBus) {
1367 data.stabVoltageVector.emplace_back(data.number >= 0 ? m_vBus[data.number] : 0.0);
1368 data.stabFreqVector.emplace_back(data.number >= 0 ? data.stabFreq : 0.0);
1369 bus->SetElectricalData(data);
1370 }
1371 }
1372 for (auto it = m_loadList.begin(), itEnd = m_loadList.end(); it != itEnd; ++it) {
1373 Load* load = *it;
1374 auto data = load->GetElectricalData();
1375 if (data.plotLoad) {
1376 data.voltageVector.emplace_back(data.voltage);
1377 data.electricalPowerVector.emplace_back(data.electricalPower);
1378 load->SetElectricalData(data);
1379 }
1380 }
1381 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
1382 IndMotor* motor = *it;
1383 auto data = motor->GetElectricalData();
1384 if (data.plotIndMachine) {
1385 data.slipVector.emplace_back(data.slip * 100.0);
1386 data.electricalTorqueVector.emplace_back(data.te * 2.0 * M_PI * m_systemFreq);
1387 double tm = (data.as - data.bs * data.slip + data.cs * data.slip * data.slip) * 2.0 * M_PI * m_systemFreq;
1388 data.mechanicalTorqueVector.emplace_back(tm);
1389 double w = (1.0 - data.slip) * 2.0 * M_PI * m_systemFreq;
1390 data.velocityVector.emplace_back(w);
1391 std::complex<double> i1 = std::complex<double>(data.ir, data.im);
1392 data.currentVector.emplace_back(std::abs(i1));
1393 int n = static_cast<Bus*>(motor->GetParentList()[0])->GetElectricalData().number;
1394 std::complex<double> v = n >= 0 ? m_vBus[n] : 0.0;
1395 data.terminalVoltageVector.emplace_back(std::abs(v));
1396 std::complex<double> s = v * std::conj(i1);
1397 data.activePowerVector.emplace_back(std::real(s));
1398 data.reactivePowerVector.emplace_back(std::imag(s));
1399 motor->SetElectricalData(data);
1400 }
1401 }
1402 m_iterationsNumVector.emplace_back(m_iterationsNum);
1403}
1404
1405void Electromechanical::SetSyncMachinesModel()
1406{
1407 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
1408 SyncGenerator* syncGenerator = *it;
1409 auto data = syncGenerator->GetElectricalData();
1410 data.model = GetMachineModel(syncGenerator);
1411 syncGenerator->SetElectricalData(data);
1412 }
1413}
1414
1415bool Electromechanical::CalculateNonIntVariables(SyncGenerator* syncGenerator,
1416 double& id,
1417 double& iq,
1418 double& sd,
1419 double& sq,
1420 double& pe,
1421 double k)
1422{
1423 auto data = syncGenerator->GetElectricalData();
1424 Bus* bus = nullptr;
1425 if (GetParentBus(syncGenerator, bus)) { data.terminalVoltage = m_vBus[bus->GetElectricalData().number]; }
1426
1427 double vd, vq;
1428 ABCtoDQ0(data.terminalVoltage, data.delta, vd, vq);
1429
1430 if (data.satFactor != 0.0) {
1431 if (!CalculateSyncMachineSaturation(syncGenerator, id, iq, sd, sq, true, k)) return false;
1432 data.sd = sd;
1433 data.sq = sq;
1434 data.oldSd = sd;
1435 data.oldSq = sq;
1436 }
1437
1438 if (syncGenerator->IsOnline()) {
1439 pe = id * vd + iq * vq + (id * id + iq * iq) * data.armResistance * k;
1440 }
1441 else {
1442 pe = id = iq = 0.0f;
1443 }
1444 data.pe = pe;
1445 data.id = id;
1446 data.iq = iq;
1447 data.oldPe = pe;
1448 data.oldId = id;
1449 data.oldIq = iq;
1450 syncGenerator->SetElectricalData(data);
1451
1452 return true;
1453}
1454
1455bool Electromechanical::CalculateNonIntVariables(IndMotor* indMotor, double& ir, double& im, double& te, double k)
1456{
1457 auto data = indMotor->GetElectricalData();
1458 if (indMotor->IsOnline()) {
1459 double w0 = 2.0 * M_PI * m_systemFreq;
1460 te = (data.tranEr * ir + data.tranEm * im) / w0;
1461 }
1462 else {
1463 te = ir = im = 0.0;
1464 }
1465 data.te = te;
1466 data.ir = ir;
1467 data.im = im;
1468 data.oldTe = te;
1469 data.oldIr = ir;
1470 data.oldIm = im;
1471 indMotor->SetElectricalData(data);
1472
1473 return true;
1474}
1475
1476double Electromechanical::CalculateIntVariables(SyncGenerator* syncGenerator,
1477 double id,
1478 double iq,
1479 double sd,
1480 double sq,
1481 double pe,
1482 double k)
1483{
1484 double error = 0.0;
1485 auto data = syncGenerator->GetElectricalData();
1486
1487 // Mechanical differential equations.
1488 double w = data.icSpeed.c + data.icSpeed.m * (data.pm - pe);
1489 error = std::max(error, std::abs(data.speed - w) / m_refSpeed);
1490
1491 double delta = data.icDelta.c + data.icDelta.m * w;
1492 error = std::max(error, std::abs(data.delta - delta));
1493
1494 data.speed = w;
1495 data.delta = delta;
1496
1497 // Electrical differential equations
1498 switch (data.model) {
1499 case Machines::SM_MODEL_1: {
1500 // There is no differential equations.
1501 } break;
1502 case Machines::SM_MODEL_2: {
1503 double syncXd, transXd;
1504 syncXd = data.syncXd * k;
1505 transXd = data.transXd * k;
1506
1507 double tranEq = (data.icTranEq.c + data.icTranEq.m * (data.fieldVoltage + (syncXd - transXd) * id)) /
1508 (1.0 + data.icTranEq.m * (sd - 1.0));
1509 error = std::max(error, std::abs(data.tranEq - tranEq));
1510
1511 data.tranEq = tranEq;
1512 } break;
1513 case Machines::SM_MODEL_3: {
1514 double syncXd, syncXq, transXd, transXq;
1515 syncXd = data.syncXd * k;
1516 syncXq = data.syncXq * k;
1517 transXd = data.transXd * k;
1518 transXq = data.transXq * k;
1519 if (syncXq == 0.0) syncXq = syncXd;
1520 if (transXq == 0.0) transXq = transXd;
1521
1522 double tranEq = (data.icTranEq.c + data.icTranEq.m * (data.fieldVoltage + (syncXd - transXd) * id)) /
1523 (1.0 + data.icTranEq.m * (sd - 1.0));
1524 error = std::max(error, std::abs(data.tranEq - tranEq));
1525
1526 double tranEd =
1527 (data.icTranEd.c - data.icTranEd.m * (syncXq - transXq) * iq) / (1.0 + data.icTranEd.m * (sq - 1.0));
1528 error = std::max(error, std::abs(data.tranEd - tranEd));
1529
1530 data.tranEq = tranEq;
1531 data.tranEd = tranEd;
1532
1533 if (!syncGenerator->IsOnline()) {
1534 std::complex<double> e;
1535 DQ0toABC(data.tranEd, data.tranEq, data.delta, e);
1536 data.terminalVoltage = e;
1537 }
1538 } break;
1539 case Machines::SM_MODEL_4: {
1540 double syncXd, syncXq, transXd, subXd, subXq;
1541 syncXd = data.syncXd * k;
1542 syncXq = data.syncXq * k;
1543 transXd = data.transXd * k;
1544 subXd = data.subXd * k;
1545 subXq = data.subXq * k;
1546 if (syncXq == 0.0) syncXq = syncXd;
1547 if (subXd == 0.0) subXd = subXq;
1548 if (subXq == 0.0) subXq = subXd;
1549
1550 double tranEq = (data.icTranEq.c + data.icTranEq.m * (data.fieldVoltage + (syncXd - transXd) * id)) /
1551 (1.0 + data.icTranEq.m * (sd - 1.0));
1552 error = std::max(error, std::abs(data.tranEq - tranEq));
1553
1554 double subEq = (data.icSubEq.c + data.icSubEq.m * (sd * tranEq + (transXd - subXd) * id)) /
1555 (1.0 + data.icSubEq.m * (sd - 1.0));
1556 error = std::max(error, std::abs(data.subEq - subEq));
1557
1558 double subEd =
1559 (data.icSubEd.c - data.icSubEd.m * ((syncXq - subXq) * iq)) / (1.0 + data.icSubEd.m * (sq - 1.0));
1560 error = std::max(error, std::abs(data.subEd - subEd));
1561
1562 data.tranEq = tranEq;
1563 data.subEq = subEq;
1564 data.subEd = subEd;
1565 } break;
1566 case Machines::SM_MODEL_5: {
1567 double syncXd, syncXq, transXd, transXq, subXd, subXq;
1568 syncXd = data.syncXd * k;
1569 syncXq = data.syncXq * k;
1570 transXd = data.transXd * k;
1571 transXq = data.transXq * k;
1572 subXd = data.subXd * k;
1573 subXq = data.subXq * k;
1574 if (syncXq == 0.0) syncXq = syncXd;
1575 if (transXq == 0.0) transXq = transXd;
1576 if (subXd == 0.0) subXd = subXq;
1577 if (subXq == 0.0) subXq = subXd;
1578
1579 double tranEq = (data.icTranEq.c + data.icTranEq.m * (data.fieldVoltage + (syncXd - transXd) * id)) /
1580 (1.0 + data.icTranEq.m * (sd - 1.0));
1581 error = std::max(error, std::abs(data.tranEq - tranEq));
1582
1583 double tranEd =
1584 (data.icTranEd.c - data.icTranEd.m * (syncXq - transXq) * iq) / (1.0 + data.icTranEd.m * (sq - 1.0));
1585 error = std::max(error, std::abs(data.tranEd - tranEd));
1586
1587 double subEq = (data.icSubEq.c + data.icSubEq.m * (sd * tranEq + (transXd - subXd) * id)) /
1588 (1.0 + data.icSubEq.m * (sd - 1.0));
1589 error = std::max(error, std::abs(data.subEq - subEq));
1590
1591 double subEd = (data.icSubEd.c + data.icSubEd.m * (sq * tranEd - (transXq - subXq) * iq)) /
1592 (1.0 + data.icSubEd.m * (sq - 1.0));
1593 error = std::max(error, std::abs(data.subEd - subEd));
1594
1595 data.tranEq = tranEq;
1596 data.tranEd = tranEd;
1597 data.subEq = subEq;
1598 data.subEd = subEd;
1599 } break;
1600 }
1601
1602 syncGenerator->SetElectricalData(data);
1603 return error;
1604}
1605
1606double Electromechanical::CalculateIntVariables(IndMotor* indMotor, double ir, double im, double te, double k)
1607{
1608 double error = 0.0;
1609 auto data = indMotor->GetElectricalData();
1610 double w0 = 2.0 * M_PI * m_systemFreq;
1611
1612 // Mechanical differential equations
1613 // Using Newton method to solve the non-linear slip equation: s = Cs + Ms * (C * s^2 - Te):
1614 double slip = data.slip; // Initial value. CAN BE THE PROBLEM ON MOTOR START!
1615 double ds = (data.icSlip.c + data.icSlip.m * (data.cs * slip * slip - te) - slip) /
1616 (1.0 - 2.0 * data.icSlip.m * data.cs * slip * slip);
1617 int iteration = 0;
1618 while (std::abs(ds) > 1e-8) {
1619 slip += ds;
1620 ds = (data.icSlip.c + data.icSlip.m * (data.cs * slip * slip - te) - slip) /
1621 (1.0 - 2.0 * data.icSlip.m * data.cs * slip * slip);
1622 iteration++;
1623 if (iteration > m_maxIterations) break;
1624 }
1625
1626 //if(!indMotor->IsOnline()) slip = 1.0 - 1e-7;
1627 error = std::max(error, std::abs(data.slip - slip));
1628 data.slip = slip;
1629
1630 // Change T'0 with the cage factor
1631 if (data.useKf)
1632 data.t0 = (data.x2t + data.xmt) / (2.0 * M_PI * m_systemFreq * data.r2t * (1.0 + data.kf * data.r2t));
1633
1634 // Electrical differential equations
1635 double tranEr = data.icTranEr.c + data.icTranEr.m * (w0 * data.t0 * slip * data.tranEm - (data.x0 - data.xt) * im);
1636 error = std::max(error, std::abs(data.tranEr - tranEr));
1637
1638 double tranEm = data.icTranEm.c - data.icTranEm.m * (w0 * data.t0 * slip * data.tranEr - (data.x0 - data.xt) * ir);
1639 error = std::max(error, std::abs(data.tranEm - tranEm));
1640
1641 data.tranEr = tranEr;
1642 data.tranEm = tranEm;
1643
1644 indMotor->SetElectricalData(data);
1645 return error;
1646}
1647
1648void Electromechanical::CalculateReferenceSpeed()
1649{
1650 if (m_useCOI) {
1651 double sumHW = 0.0;
1652 double sumH = 0.0;
1653 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
1654 SyncGenerator* syncGenerator = *it;
1655 if (syncGenerator->IsOnline()) {
1656 auto data = syncGenerator->GetElectricalData();
1657 double k = 1.0; // Power base change factor.
1658 if (data.useMachineBase) {
1659 double oldBase = syncGenerator->GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
1660 k = m_powerSystemBase / oldBase;
1661 }
1662 sumH += data.inertia / k;
1663 sumHW += data.inertia * data.speed / k;
1664 }
1665 }
1666 m_refSpeed = sumHW / sumH;
1667 }
1668 else {
1669 m_refSpeed = 2.0 * M_PI * m_systemFreq;
1670 }
1671}
1672
1673bool Electromechanical::CalculateSyncMachineSaturation(SyncGenerator* syncMachine,
1674 double& id,
1675 double& iq,
1676 double& sd,
1677 double& sq,
1678 bool updateCurrents,
1679 double k)
1680{
1681 // [Ref] Arrillaga, J.; Arnold, C. P.. "Computer Modelling of Electrical Power Systems". Pg. 254-260
1682 auto data = syncMachine->GetElectricalData();
1683 auto smDataModel = GetSyncMachineModelData(syncMachine);
1684
1685 Bus* bus = nullptr;
1686 if (GetParentBus(syncMachine, bus)) { data.terminalVoltage = m_vBus[bus->GetElectricalData().number]; }
1687 //else {
1688 //data.terminalVoltage = 0.0;
1689 //return true;
1690 //}
1691 double idCalc = id;
1692 double iqCalc = iq;
1693 double sdCalc = sd;
1694 double sqCalc = sq;
1695
1696 double vd, vq;
1697 ABCtoDQ0(data.terminalVoltage, data.delta, vd, vq);
1698 double deltaVd = smDataModel.ed - vd;
1699 double deltaVq = smDataModel.eq - vq;
1700
1701 double ra = data.armResistance * k;
1702 double xd = smDataModel.xd;
1703 double xq = smDataModel.xq;
1704
1705 double syncXd = data.syncXd * k;
1706 double syncXq = data.syncXq * k;
1707 if (data.model == Machines::SM_MODEL_1) {
1708 syncXq = data.transXd * k;
1709 syncXd = syncXq;
1710 }
1711 else if (data.syncXq == 0.0)
1712 syncXq = data.syncXd * k;
1713
1714 double xp = data.potierReactance * k;
1715 if (xp == 0.0) xp = 0.8 * data.transXd * k;
1716 double satFacd = (data.satFactor - 1.2) / std::pow(1.2, 7);
1717 double satFacq = satFacd * (syncXq / syncXd);
1718
1719 bool exit = false;
1720 int iterations = 0;
1721 while (!exit) {
1722 double oldSd = sdCalc;
1723 double oldSq = sqCalc;
1724
1725 // Saturated reactances.
1726 double xds = (xd - xp) / sdCalc + xp;
1727 double xqs = (xq - xp) / sqCalc + xp;
1728 // dq currents.
1729 double den = 1.0 / (ra * ra + xds * xqs);
1730 iqCalc = den * (ra * deltaVq + xds * deltaVd);
1731 idCalc = den * (-xqs * deltaVq + ra * deltaVd);
1732 // Potier voltages
1733 double epq = vq + ra * iqCalc - xp * idCalc;
1734 double epd = vd + ra * idCalc + xp * iqCalc;
1735 // Saturation factors.
1736 // Gauss
1737 /*sdCalc = 1.0 + satFacd * std::pow(epq, 6);
1738 sqCalc = 1.0 + satFacq * std::pow(epd, 6);*/
1739
1740 // Newton-raphson
1741 double f1 = 1.0 - sdCalc + satFacd * std::pow(epq, 6);
1742 double f2 = 1.0 - sqCalc + satFacq * std::pow(epd, 6);
1743 double dF1dSd =
1744 (6.0 * satFacd * std::pow(epq, 5) * xp * (xd - xp) * deltaVq) / ((sdCalc - 1.0) * xp + xd) - 1.0;
1745 double dF2dSq =
1746 (6.0 * satFacq * std::pow(epd, 5) * xp * (xq - xp) * deltaVd) / ((sqCalc - 1.0) * xp + xq) - 1.0;
1747
1748 sdCalc = sdCalc - f1 / dF1dSd;
1749 sqCalc = sqCalc - f2 / dF2dSq;
1750
1751 double error = std::abs(sdCalc - oldSd) + std::abs(sqCalc - oldSq);
1752 if (error < m_saturationTolerance) exit = true;
1753
1754 iterations++;
1755 if ((iterations >= m_maxIterations) && !exit) {
1756 m_errorMsg =
1757 _("It was not possible to solve the saturation of the synchronous machine \"") + data.name + wxT("\".");
1758 return false;
1759 }
1760 }
1761
1762 sd = sdCalc;
1763 sq = sqCalc;
1764 if (updateCurrents) {
1765 id = idCalc;
1766 iq = iqCalc;
1767 }
1768 return true;
1769}
1770
1771void Electromechanical::CalculateBusesFrequency(bool hasEvent)
1772{
1773 bool ignoreEventStep = true;
1774 for (auto& bus : m_busList) {
1775 auto data = bus->GetElectricalData();
1776 if (!data.plotBus) continue;
1777
1778 if (m_simData.busFreqEstimation == BusFreqEstimation::WASHOUT_FILTER) {
1779 //tex:
1780 // Low-pass filter:
1781 // $$\frac{d}{dt}x_{\theta} = x_{\theta}' = \frac{\omega^{-1}(\theta_k - \theta_0) - x_{\theta}}{T_f}$$
1782 // Washout filter:
1783 // $$\frac{d}{dt}\Delta\omega_{k} = \Delta\omega_{k}' = \frac{x_{\theta}' - \Delta\omega_{k}}{T_w}$$
1784 // Trapezoidal rule:
1785 // $$ y_{k} = y_{k-1} + 0.5 h \cdot (y_{k-1}'+y_{k}') $$
1786
1787 double tf = m_simData.tf;
1788 double tw = m_simData.tw;
1789 double error = 1.0;
1790 double theta0 = std::arg(data.number >= 0 ? data.voltage : 0.0);
1791
1792 // Low-pass filter
1793 auto fdxt = [this, tf, theta0](double theta, double xt) {
1794 return (1.0 / tf) * ((1.0 / (2.0 * M_PI * m_systemFreq)) * (theta - theta0) - xt);
1795 };
1796
1797 // Washout filter
1798 auto fddw = [this, tw](double dxt, double dw) {
1799 return (1.0 / tw) * (dxt - dw);
1800 };
1801
1802 double theta = std::arg(data.number >= 0 ? m_vBus[data.number] : theta0);
1803
1804 double xt = 0, dw = 0, dxt = 0, ddw = 0;
1805 int iterations = 0;
1806 while (error > 1e-6 && iterations <= 100) {
1807 double oldXT = xt, oldDW = dw;
1808
1809 dxt = fdxt(theta, xt);
1810 xt = data.filteredAngle + 0.5 * m_timeStep * (data.dxt + dxt); // Trapezoidal rule
1811 error = std::abs(xt - oldXT);
1812
1813 ddw = fddw(dxt, dw);
1814 dw = data.filteredVelocity + 0.5 * m_timeStep * (data.ddw + ddw); // Trapezoidal rule
1815 error = std::max(error, std::abs(dw - oldDW));
1816 iterations++;
1817 }
1818
1819 //if (ignoreEventStep && hasEvent) {
1820 // xt = data.filteredAngle;
1821 // dw = 0.0;
1822 // dxt = 0.0;
1823 // ddw = 0.0;
1824 //}
1825
1826 data.filteredAngle = xt;
1827 data.filteredVelocity = dw;
1828 data.dxt = dxt;
1829 data.ddw = ddw;
1830
1831 double busVelocity = m_refSpeed + dw * (2.0 * M_PI * m_systemFreq);
1832 data.stabFreq = busVelocity / (2.0 * M_PI);
1833
1834 bus->SetElectricalData(data);
1835 }
1836 else if (m_simData.busFreqEstimation == BusFreqEstimation::ANGLE_DERIVATION) {
1837 double newAngle = std::arg(data.number >= 0 ? m_vBus[data.number] : 0.0);
1838
1839
1840 //tex: $$\Delta \omega^k=\frac{\theta^k - \theta^{k-1}}{h}$$
1841 double dw = (newAngle - data.oldAngle) / m_timeStep;
1842 //double dw = (1.0 / (2.0 * M_PI * m_systemFreq)) * ((newAngle - data.oldAngle) / m_timeStep); // p.u.
1843
1844 if (m_simData.ignoreBusFreqEventStep && hasEvent) dw = 0.0;
1845
1846 //tex: $$f^k= \frac{\omega_{CoI}+\Delta \omega^k}{2\pi}$$
1847 data.stabFreq = (m_refSpeed + dw) / (2.0 * M_PI);
1848 //data.stabFreq = ((2.0 * M_PI * m_systemFreq) + dw) / (2.0 * M_PI);
1849
1850 data.oldAngle = newAngle;
1851 }
1852 bus->SetElectricalData(data);
1853 }
1854}
1855
1856SyncMachineModelData Electromechanical::GetSyncMachineModelData(SyncGenerator* syncMachine)
1857{
1858 SyncMachineModelData smModelData;
1859
1860 auto data = syncMachine->GetElectricalData();
1861 double k = 1.0; // Power base change factor.
1862 if (data.useMachineBase) {
1863 double oldBase = syncMachine->GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
1864 k = m_powerSystemBase / oldBase;
1865 }
1866
1867 switch (data.model) {
1868 case Machines::SM_MODEL_1: {
1869 smModelData.ed = data.tranEd;
1870 smModelData.eq = data.tranEq;
1871 smModelData.xq = data.transXd * k;
1872 smModelData.xd = smModelData.xq;
1873 } break;
1874 case Machines::SM_MODEL_2: {
1875 smModelData.ed = data.tranEd;
1876 smModelData.eq = data.tranEq;
1877 smModelData.xd = data.transXd * k;
1878 smModelData.xq = data.transXq * k;
1879 if (smModelData.xq == 0.0) {
1880 smModelData.xq = data.syncXq * k;
1881 if (smModelData.xq == 0.0) { smModelData.xq = data.syncXd * k; }
1882 }
1883 } break;
1884 case Machines::SM_MODEL_3: {
1885 smModelData.ed = data.tranEd;
1886 smModelData.eq = data.tranEq;
1887 smModelData.xd = data.transXd * k;
1888 smModelData.xq = data.transXq * k;
1889 if (smModelData.xq == 0.0) smModelData.xq = smModelData.xd;
1890 } break;
1891 case Machines::SM_MODEL_4:
1892 case Machines::SM_MODEL_5: {
1893 smModelData.ed = data.subEd;
1894 smModelData.eq = data.subEq;
1895 smModelData.xd = data.subXd * k;
1896 smModelData.xq = data.subXq * k;
1897 if (smModelData.xd == 0.0) smModelData.xd = smModelData.xq;
1898 if (smModelData.xq == 0.0) smModelData.xq = smModelData.xd;
1899 } break;
1900 }
1901 return smModelData;
1902}
1903
1904void Electromechanical::PreallocateVectors()
1905{
1906 int numPoints = static_cast<unsigned int>(m_simTime / m_plotTime);
1907
1908 m_timeVector.reserve(numPoints);
1909 for (auto it = m_syncGeneratorList.begin(), itEnd = m_syncGeneratorList.end(); it != itEnd; ++it) {
1910 SyncGenerator* syncGenerator = *it;
1911 auto data = syncGenerator->GetElectricalData();
1912 if (data.plotSyncMachine) {
1913 data.terminalVoltageVector.reserve(numPoints);
1914 data.electricalPowerVector.reserve(numPoints);
1915 data.mechanicalPowerVector.reserve(numPoints);
1916 data.freqVector.reserve(numPoints);
1917 data.fieldVoltageVector.reserve(numPoints);
1918 data.deltaVector.reserve(numPoints);
1919 syncGenerator->SetElectricalData(data);
1920 }
1921 }
1922 for (auto it = m_busList.begin(), itEnd = m_busList.end(); it != itEnd; ++it) {
1923 Bus* bus = *it;
1924 auto data = bus->GetElectricalData();
1925 if (data.plotBus) {
1926 data.stabVoltageVector.reserve(numPoints);
1927 data.stabFreqVector.reserve(numPoints);
1928 bus->SetElectricalData(data);
1929 }
1930 }
1931 for (auto it = m_loadList.begin(), itEnd = m_loadList.end(); it != itEnd; ++it) {
1932 Load* load = *it;
1933 auto data = load->GetElectricalData();
1934 if (data.plotLoad) {
1935 data.voltageVector.reserve(numPoints);
1936 data.electricalPowerVector.reserve(numPoints);
1937 load->SetElectricalData(data);
1938 }
1939 }
1940 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
1941 IndMotor* motor = *it;
1942 auto data = motor->GetElectricalData();
1943 if (data.plotIndMachine) {
1944 data.slipVector.reserve(numPoints);
1945 data.electricalTorqueVector.reserve(numPoints);
1946 motor->SetElectricalData(data);
1947 }
1948 }
1949}
1950
1951std::complex<double> Electromechanical::GetIndMachineAdmittance(IndMotor* motor)
1952{
1953 auto data = motor->GetElectricalData();
1954 auto dataPU = motor->GetPUElectricalData(m_powerSystemBase);
1955 std::complex<double> v = static_cast<Bus*>(motor->GetParentList()[0])->GetElectricalData().voltage;
1956 std::complex<double> y0 = (std::complex<double>(1, 0) / std::complex<double>(data.r1t, data.xt));
1957 // The difference between calculated and defined reactive power
1958 std::complex<double> yq = std::complex<double>(0.0, data.q0 - dataPU.reactivePower) / (std::abs(v) * std::abs(v));
1959 return y0 + yq;
1960}
1961
1962bool Electromechanical::InsertIndMachinesOnYBus()
1963{
1964 for (auto it = m_indMotorList.begin(), itEnd = m_indMotorList.end(); it != itEnd; ++it) {
1965 IndMotor* motor = *it;
1966 if (!CalculateIndMachinesTransientValues(motor)) return false;
1967 if (motor->IsOnline()) {
1968 int n = static_cast<Bus*>(motor->GetParentList()[0])->GetElectricalData().number;
1969 m_yBus[n][n] += GetIndMachineAdmittance(motor);
1970 }
1971 }
1972 return true;
1973}
1974
1975bool Electromechanical::CalculateIndMachinesTransientValues(IndMotor* motor)
1976{
1977 auto data = motor->GetElectricalData();
1978 auto dataPU = motor->GetPUElectricalData(m_powerSystemBase);
1979 double k = 1.0; // Power base change factor.
1980 if (data.useMachinePowerAsBase) {
1981 double oldBase = motor->GetValueFromUnit(data.ratedPower, data.ratedPowerUnit);
1982 k = m_powerSystemBase / oldBase;
1983 }
1984
1985 data.terminalVoltage = static_cast<Bus*>(motor->GetParentList()[0])->GetElectricalData().voltage;
1986
1987 // Calculate the induction machine transient constants at the machine base
1988 double r1t = data.r1 * k;
1989 double r2t = data.r2 * k;
1990 double x1t = data.x1 * k;
1991 double x2t = data.x2 * k;
1992 double xmt = data.xm * k;
1993 data.r1t = r1t;
1994 data.r2t = r2t;
1995 data.x1t = x1t;
1996 data.x2t = x2t;
1997 data.xmt = xmt;
1998
1999 double xt = x1t + (x2t * xmt) / (x2t + xmt);
2000 double x0 = x1t + xmt;
2001 data.xt = xt;
2002 data.x0 = x0;
2003
2004 double p = dataPU.activePower;
2005 double v = std::abs(data.terminalVoltage);
2006
2007 //[Ref.] Induction Motor Static Models for Power Flow and Voltage stability Studies
2008 // If the motor is offline, calculate the nominal slip to user-defined power input and 1.0 p.u. voltage
2009 if (!motor->IsOnline()) v = 1.0;
2010 double r1 = data.r1t;
2011 double r2 = data.r2t;
2012 if (data.useKf) r2 *= (1.0 + data.kf * r2t);
2013 double x1 = data.x1t;
2014 double x2 = data.x2t;
2015 double xm = data.xmt;
2016 double k1 = x2 + xm;
2017 double k2 = -x1 * k1 - x2 * xm;
2018 double k3 = xm + x1;
2019 double k4 = r1 * k1;
2020 double a = p * (r1 * r1 + k3 * k3) - v * v * r1;
2021 double b = 2.0 * p * (r1 * k2 + k3 * k4) - v * v * (k2 + k1 * k3);
2022 double c = p * (k2 * k2 + k4 * k4) - v * v * k1 * k4;
2023 double d = (b * b - 4.0 * a * c);
2024 if (d < 0.0) {
2025 m_errorMsg = _("Error on initializate the slip of \"") + data.name + _("\".");
2026 return false;
2027 }
2028 double r2_s = (-b + std::sqrt(d)) / (2.0 * a);
2029 data.s0 = r2 / r2_s;
2030
2031 double qa = k1 * (r2_s * r1 - x1 * k1 - x2 * xm);
2032 double qb = r2_s * (r2_s * (xm + x1) + r1 * k1);
2033 double qc = r2_s * r1 - x1 * k1 - x2 * xm;
2034 double qd = r2_s * (xm + x1) + r1 * k1;
2035 data.q0 = (-v * v * (qa - qb)) / (qc * qc + qd * qd);
2036
2037 data.t0 = (x2t + xmt) / (2.0 * M_PI * m_systemFreq * r2);
2038
2039 motor->SetElectricalData(data);
2040 return true;
2041}
@ POSITIVE_SEQ
Node for power elements. All others power elements are connected through this.
Definition Bus.h:86
Shunt capactior power element.
Definition Capacitor.h:39
std::vector< std::complex< double > > ComplexMatrixTimesVector(std::vector< std::vector< std::complex< double > > > matrix, std::vector< std::complex< double > > vector)
Multiply a complex matrix by a complex vector.
std::vector< Line * > m_lineList
List of transmission lines in the system.
std::vector< PowerElement * > m_powerElementList
List of power elements in the system.
std::vector< Bus * > m_busList
List of buses in the system.
std::vector< Load * > m_loadList
List of load elements in the system.
void ABCtoDQ0(std::complex< double > complexValue, double angle, double &dValue, double &qValue)
Convert a complex phasor in ABC representation to DQ components.
std::vector< Capacitor * > m_capacitorList
List of capacitor elements in the system.
std::vector< std::complex< double > > LUEvaluate(std::vector< std::vector< std::complex< double > > > u, std::vector< std::vector< std::complex< double > > > l, std::vector< std::complex< double > > b)
Solve a linear system using LU decomposition.
std::vector< IndMotor * > m_indMotorList
List of induction motors in the system.
bool GetParentBus(Element *childElement, Bus *&parentBus)
Get the parent bus of a given shunt element.
std::vector< Transformer * > m_transformerList
List of transformers in the system.
std::vector< SyncGenerator * > m_syncGeneratorList
List of synchronous generators in the system.
virtual bool GetYBus(std::vector< std::vector< std::complex< double > > > &yBus, double systemPowerBase, YBusSequence sequence=POSITIVE_SEQ, bool includeSyncMachines=false, bool allLoadsAsImpedances=false, bool usePowerFlowVoltagesOnImpedances=false)
Get the admittance matrix from the list of elements (use GetElementsFromList first).
virtual void GetElementsFromList(std::vector< Element * > elementList)
Separate the power elements from a generic list.
std::vector< Inductor * > m_inductorList
List of inductors in the system.
Machines::SyncMachineModel GetMachineModel(SyncGenerator *generator)
Get the synchronous machine model used by the generator based on user-defined parameters.
void GetLUDecomposition(std::vector< std::vector< std::complex< double > > > matrix, std::vector< std::vector< std::complex< double > > > &matrixL, std::vector< std::vector< std::complex< double > > > &matrixU)
Compute the LU decomposition of a matrix.
void DQ0toABC(double dValue, double qValue, double angle, std::complex< double > &complexValue)
Convert DQ components to a complex phasor in ABC representation.
virtual std::vector< Element * > GetParentList() const
Get the parent list.
Definition Element.h:559
bool IsOnline() const
Checks if the element is online or offline.
Definition Element.h:226
bool SetOnline(bool online=true)
Set if the element is online or offline.
Definition Element.cpp:447
Induction motor power element.
Definition IndMotor.h:119
Inductor shunt power element.
Definition Inductor.h:39
Power line element.
Definition Line.h:64
Loas shunt power element.
Definition Load.h:74
Abstract class of power elements.
virtual SwitchingData GetSwitchingData()
Returns the switching data of the element.
Synchronous generator power element.
Two-winding transformer power element.
Definition Transformer.h:84
Switching data of power elements.
std::vector< double > swTime
std::vector< SwitchingType > swType
Synchronous machine data for different models.