Power System Platform  2026w23a-beta
Loading...
Searching...
No Matches
EMTElement.cpp
1#include "EMTElement.h"
2
3#include <ranges>
4#include <wx/app.h>
5
6#include "../../forms/EMTElementForm.h"
7#include "../../editors/Workspace.h"
8#include "../../utils/PropertiesData.h"
9#include "../../extLibs/fftw/fftw3.h"
10#include "../../elements/GCText.h"
11#include "../../utils/Path.h"
12
13#include <wx/dcgraph.h>
14#include <wx/textfile.h>
15#include <wx/dir.h>
16#include <wx/process.h>
17#include <wx/wfstream.h>
18#include <wx/datstrm.h>
19#include <wx/busyinfo.h>
20
21EMTElement::EMTElement()
22{
23}
24
25EMTElement::EMTElement(wxString name)
26{
27 m_data.name = name;
28}
29
30EMTElement::~EMTElement()
31{
32}
33
35{
36 EMTElement* copy = new EMTElement();
37 *copy = *this;
38 return copy;
39}
40
41bool EMTElement::AddParent(Element* parent, wxPoint2DDouble position)
42{
43 if (parent) {
44 m_parentList.push_back(parent);
45 parent->AddChild(this);
46 wxPoint2DDouble parentPt =
47 parent->RotateAtPosition(position, -parent->GetAngle()); // Rotate click to horizontal position.
48 parentPt.m_y = parent->GetPosition().m_y; // Centralize on bus.
49 parentPt = parent->RotateAtPosition(parentPt, parent->GetAngle()); // Rotate back.
50
51 m_position = parentPt + wxPoint2DDouble(0.0, 100.0); // Shifts the position to the down of the bus.
52 m_width = 50;
53 m_height = 50;
54 m_rect = wxRect2DDouble(m_position.m_x - m_width / 2.0, m_position.m_y - m_height / 2.0, m_width, m_height);
55
56 m_pointList.push_back(parentPt);
57 m_pointList.push_back(GetSwitchPoint(parent, parentPt, m_position));
58 m_pointList.push_back(m_position + wxPoint2DDouble(0.0, -m_height / 2.0 - 10.0));
59 m_pointList.push_back(m_position + wxPoint2DDouble(0.0, -m_height / 2.0));
60
61 m_inserted = true;
62
63 wxRect2DDouble genRect(0, 0, 0, 0);
64 m_switchRect.push_back(genRect); // Push a general rectangle.
66
67 // Base data
68 auto data = static_cast<Bus*>(parent)->GetElectricalData();
69 m_data.puVoltage = data.voltage;
70 m_data.baseVoltage = GetValueFromUnit(data.nominalVoltage, data.nominalVoltageUnit);
71 m_data.frequency = data.stabFreq;
72
73 return true;
74 }
75 return false;
76}
77
78void EMTElement::DrawDC(GUIColour* guiColour, wxPoint2DDouble translation, double scale, wxGraphicsContext* gc) const
79{
80 wxColour elementColour;
81 if (m_online) {
82 if (m_dynEvent)
83 elementColour = guiColour->eventElement;
84 else
85 elementColour = guiColour->enabled;
86 }
87 else
88 elementColour = guiColour->disable;
89
90 if (m_inserted) {
91
92 if (m_selected) {
93 gc->SetPen(wxPen(guiColour->selection, 2.0 + m_borderSize * 2.0));
94 gc->SetBrush(*wxTRANSPARENT_BRUSH);
95
96 gc->StrokeLines(m_pointList.size(), &m_pointList[0]);
97
98 // Push the current matrix on stack.
99 gc->PushState();
100 // Rotate the matrix around the object position.
101 gc->Translate(m_position.m_x, m_position.m_y);
102 gc->Rotate(wxDegToRad(m_angle));
103 gc->Translate(-m_position.m_x, -m_position.m_y);
104
105 //gc->DrawRectangle(m_position.m_x - m_width / 2.0, m_position.m_y - m_height / 2.0, m_width, m_height);
106 gc->DrawRoundedRectangle(m_position.m_x - m_width / 2.0, m_position.m_y - m_height / 2.0, m_width, m_height, 10.0);
107
108 gc->PopState();
109
110 // Draw node selection.
111 gc->SetPen(*wxTRANSPARENT_PEN);
112 gc->SetBrush(wxBrush(guiColour->selection));
113 DrawDCCircle(m_pointList[0], 5.0 + m_borderSize / scale, 10, gc);
114 }
115 // Draw EMTElement (layer 2).
116 // Draw node.
117 gc->SetPen(*wxTRANSPARENT_PEN);
118 gc->SetBrush(wxBrush(elementColour));
119 DrawDCCircle(m_pointList[0], 5.0, 10, gc);
120
121 gc->SetPen(wxPen(elementColour, 2));
122 gc->SetBrush(*wxTRANSPARENT_BRUSH);
123 gc->StrokeLines(m_pointList.size(), &m_pointList[0]);
124
125 DrawDCSwitches(guiColour, gc);
126
127 // Push the current matrix on stack.
128 gc->PushState();
129 // Rotate the matrix around the object position.
130 gc->Translate(m_position.m_x, m_position.m_y);
131 gc->Rotate(wxDegToRad(m_angle));
132 gc->Translate(-m_position.m_x, -m_position.m_y);
133
134 gc->SetPen(wxPen(elementColour, 2));
135 gc->SetBrush(wxBrush(guiColour->background));
136 //gc->DrawRectangle(m_position.m_x - m_width / 2.0, m_position.m_y - m_height / 2.0, m_width, m_height);
137 gc->DrawRoundedRectangle(m_position.m_x - m_width / 2.0, m_position.m_y - m_height / 2.0, m_width, m_height, 10.0);
138
139 //gc->SetPen(*wxBLACK_PEN);
140 //wxFont font(10, wxFONTFAMILY_ROMAN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
141 wxFont font;
142 font.SetFaceName(wxT("CMU Serif"));
143 font.SetPointSize(10);
144 font.MakeBold();
145 gc->SetFont(font, m_online ? wxColour(255, 60, 0) : m_offlineElementColour);
146 double textWidth, textHeight;
147 gc->GetTextExtent(_("EMT"), &textWidth, &textHeight);
148 gc->DrawText(_("EMT"), m_position.m_x - textWidth / 2.0, m_position.m_y - textHeight / 2.0 + 20.0);
149
150 wxGCDC* gcdc = new wxGCDC(gc);
151 gcdc->SetPen(wxPen(m_online ? wxColour(0, 60, 255) : m_offlineElementColour, 2));
152 std::vector<wxPoint> ptList;
153 for (double x = m_position.m_x - m_width / 2.0 + 2; x < (m_position.m_x + m_width / 2.0); x += (m_width - 4.0) / 6.0) {
154 ptList.emplace_back(x, m_position.m_y + std::sin((x - (m_position.m_x - m_width / 2.0 + 2)) / m_width * 6 * M_PI) * 20.0);
155 }
156 gcdc->DrawSpline(ptList.size(), ptList.data());
157
158 gc->PopState();
159 }
160}
161
162void EMTElement::DrawDC(GUIColour* guiColour, wxPoint2DDouble translation, double scale, wxDC& dc) const
163{
164 wxColour elementColour;
165 if (m_online) {
166 if (m_dynEvent)
167 elementColour = guiColour->eventElement;
168 else
169 elementColour = guiColour->enabled;
170 }
171 else
172 elementColour = guiColour->disable;
173
174 std::vector<wxPoint> pointListInt;
175 for (auto& pt : m_pointList) {
176 pointListInt.emplace_back(static_cast<int>(pt.m_x), static_cast<int>(pt.m_y));
177 }
178
179 if (m_inserted) {
180
181 if (m_selected) {
182 dc.SetPen(wxPen(guiColour->selection, 2 + m_borderSize * 2.0));
183 dc.SetBrush(*wxTRANSPARENT_BRUSH);
184
185 dc.DrawLines(pointListInt.size(), &pointListInt[0]);
186
187 DrawDCRoundedRectRotated(dc, m_position, m_width, m_height, 10.0, m_angle);
188
189 // Draw node selection.
190 dc.SetPen(*wxTRANSPARENT_PEN);
191 dc.SetBrush(wxBrush(guiColour->selection));
192 DrawDCCircle(m_pointList[0], 5.0 + m_borderSize / scale, dc);
193 }
194 // Draw EMTElement (layer 2).
195 // Draw node.
196 dc.SetPen(*wxTRANSPARENT_PEN);
197 dc.SetBrush(wxBrush(elementColour));
198 DrawDCCircle(pointListInt[0], 5.0, dc);
199
200 dc.SetPen(wxPen(elementColour, 2));
201 dc.SetBrush(*wxTRANSPARENT_BRUSH);
202 dc.DrawLines(pointListInt.size(), &pointListInt[0]);
203
204 DrawDCSwitches(guiColour, dc);
205
206 dc.SetPen(wxPen(elementColour, 2));
207 dc.SetBrush(wxBrush(guiColour->background));
208 DrawDCRoundedRectRotated(dc, m_position, m_width, m_height, 10.0, m_angle);
209
210 wxFont font;
211 font.SetFaceName(wxT("CMU Serif"));
212 font.SetPointSize(10);
213 font.MakeBold();
214 GCText gcText;
215 gcText.SetFont(font);
216 gcText.SetText(_("EMT"));
217 wxPoint pt = RotateAround(m_position + wxPoint2DDouble(0.0, 20.0), m_position, m_angle);
218 gcText.Draw(pt, gcText.GetWidth(), gcText.GetHeight(), dc, m_angle, m_online ? wxColour(255, 60, 0) : m_offlineElementColour);
219
220 dc.SetPen(wxPen(m_online ? wxColour(0, 60, 255) : m_offlineElementColour, 2));
221 std::vector<wxPoint> ptList;
222 for (double x = m_position.m_x - m_width / 2.0 + 2; x < (m_position.m_x + m_width / 2.0); x += (m_width - 4.0) / 6.0) {
223 wxPoint pt = RotateAround(wxPoint2DDouble(x, m_position.m_y + std::sin((x - (m_position.m_x - m_width / 2.0 + 2)) / m_width * 6 * M_PI) * 20.0), m_position, m_angle);
224 ptList.emplace_back(pt);
225 }
226 dc.DrawSpline(ptList.size(), ptList.data());
227 }
228}
229
230void EMTElement::Rotate(bool clockwise)
231{
232 double rotAngle = m_rotationAngle;
233 if (!clockwise) rotAngle = -m_rotationAngle;
234
235 m_angle += rotAngle;
236 if (m_angle >= 360 || m_angle <= -360) m_angle = 0.0;
237 m_pointList[2] = RotateAtPosition(m_pointList[2], rotAngle);
238 m_pointList[3] = RotateAtPosition(m_pointList[3], rotAngle);
239 UpdateSwitchesPosition();
240}
241
243{
244 menu.Append(ID_EDIT_ELEMENT, _("Edit Electromagnetic Transient"));
245 GeneralMenuItens(menu);
246 return true;
247}
248
250{
251 wxString tipText = m_data.name;
252 tipText += wxT("\n");
253
254 tipText += wxString::Format(_("\nP = %.5f p.u."), m_data.power.real());
255 tipText += wxString::Format(_("\nQ = %.5f p.u."), m_data.power.imag());
256 if (auto itCurrrent = m_data.currHarmonics.find(1); itCurrrent != m_data.currHarmonics.end())
257 tipText += wxString::Format(_("\nI = %.5f A"), std::abs(itCurrrent->second));
258
259 wxString harmonicsInfo = _("\n\nHarmonics info:");
260 bool hasHarmonics = false;
261 for (auto& [order, current] : m_data.currHarmonics) {
262 if (order != 1) {
263 hasHarmonics = true;
264 harmonicsInfo += wxString::Format(_("\nIh(%d): %.5f%s%.2f%s A"), order, std::abs(current), wxString(L'\u2220'), wxRadToDeg(std::arg(current)), wxString(L'\u00B0'));
265 }
266 }
267
268 if (hasHarmonics) tipText += harmonicsInfo;
269
270 return tipText;
271}
272
273bool EMTElement::ShowForm(wxWindow* parent, Element* element, wxWindow* workspace)
274{
275 Workspace* ws = dynamic_cast<Workspace*>(workspace);
276 if (!ws) return false;
277
278 EMTElementForm emtForm(parent, this, ws);
279 emtForm.SetTitle(_("Electromagnetic Transient"));
280 emtForm.CenterOnParent();
281 if (emtForm.ShowModal() == wxID_OK) {
282 return true;
283 }
284 return false;
285}
286
287rapidxml::xml_node<>* EMTElement::SaveElement(rapidxml::xml_document<>& doc, rapidxml::xml_node<>* elementListNode)
288{
289 auto elementNode = XMLParser::AppendNode(doc, elementListNode, "EMTElement");
290 XMLParser::SetNodeAttribute(doc, elementNode, "ID", m_elementID);
291
292 SaveCADProperties(doc, elementNode);
293
294 auto electricalProp = XMLParser::AppendNode(doc, elementNode, "ElectricalProperties");
295 auto isOnline = XMLParser::AppendNode(doc, electricalProp, "IsOnline");
296 XMLParser::SetNodeValue(doc, isOnline, m_online);
297 auto name = XMLParser::AppendNode(doc, electricalProp, "Name");
298 XMLParser::SetNodeValue(doc, name, m_data.name);
299 auto atpFilePath = XMLParser::AppendNode(doc, electricalProp, "ATPFilePath");
300 XMLParser::SetNodeValue(doc, atpFilePath, m_data.atpFile.GetFullPath());
301 auto atpNodeName = XMLParser::AppendNode(doc, electricalProp, "ATPNodeName");
302 XMLParser::SetNodeValue(doc, atpNodeName, m_data.atpNodeName);
303 auto stepSize = XMLParser::AppendNode(doc, electricalProp, "StepSize");
304 XMLParser::SetNodeValue(doc, stepSize, m_data.stepSize);
305 auto cyclesToSS = XMLParser::AppendNode(doc, electricalProp, "CyclesToSS");
306 XMLParser::SetNodeValue(doc, cyclesToSS, m_data.cyclesToSS);
307 auto recordFrequency = XMLParser::AppendNode(doc, electricalProp, "RecordFrequency");
308 XMLParser::SetNodeValue(doc, recordFrequency, m_data.recordFrequency);
309 auto useMedianFilter = XMLParser::AppendNode(doc, electricalProp, "UseMedianFilter");
310 XMLParser::SetNodeValue(doc, useMedianFilter, m_data.useMedianFilter);
311 auto numMaxHarmonics = XMLParser::AppendNode(doc, electricalProp, "NumMaxHarmonics");
312 XMLParser::SetNodeValue(doc, numMaxHarmonics, m_data.numMaxHarmonics);
313
314 return elementNode;
315}
316
317bool EMTElement::OpenElement(rapidxml::xml_node<>* elementNode, std::vector<Element*> parentList)
318{
319 if (!OpenCADProperties(elementNode, parentList)) return false;
320
321 auto electricalProp = elementNode->first_node("ElectricalProperties");
322 if (!electricalProp) return false;
323
324 SetOnline(XMLParser::GetNodeValueInt(electricalProp, "IsOnline"));
325 m_data.name = electricalProp->first_node("Name")->value();
326 m_data.atpFile = wxFileName(electricalProp->first_node("ATPFilePath")->value());
327 m_data.atpNodeName = electricalProp->first_node("ATPNodeName")->value();
328 m_data.stepSize = XMLParser::GetNodeValueDouble(electricalProp, "StepSize");
329 m_data.cyclesToSS = XMLParser::GetNodeValueInt(electricalProp, "CyclesToSS");
330 m_data.recordFrequency = XMLParser::GetNodeValueInt(electricalProp, "RecordFrequency");
331 m_data.useMedianFilter = XMLParser::GetNodeValueInt(electricalProp, "UseMedianFilter");
332 m_data.numMaxHarmonics = XMLParser::GetNodeValueInt(electricalProp, "NumMaxHarmonics");
333
334 m_inserted = true;
335 return true;
336}
337
338wxArrayString EMTElement::GetATPNodes(wxArrayString atpFile)
339{
340 wxArrayString nodeList;
341 uint32_t mode = 0;
342 std::vector<wxString> mode0Cards{ "/OUTPUT", "BLANK", "TACS", "INCLUDE", "TRANSFORMER" };
343 std::vector<wxString> mode1Cards{ "/SOURCE" };
344 std::vector<wxString> mode2Cards{ "/BRANCH", "/SWITCH", };
345 wxString ground = "000000";
346 for (wxString line : atpFile) {
347 if (line.IsEmpty()) continue;
348 if (tolower(line[0]) == 'c') continue; // Skip comments
349
350 bool cardHeader = false;
351 for (wxString card : mode0Cards) {
352 if (line.Find(card) != wxNOT_FOUND) {
353 mode = 0;
354 cardHeader = true;
355 break;
356 }
357 }
358 for (wxString card : mode1Cards) {
359 if (line.Find(card) != wxNOT_FOUND) {
360 mode = 1;
361 cardHeader = true;
362 break;
363 }
364 }
365 for (wxString card : mode2Cards) {
366 if (line.Find(card) != wxNOT_FOUND) {
367 mode = 2;
368 cardHeader = true;
369 break;
370 }
371 }
372 if (cardHeader) continue;
373
374
375 wxString node = "";
376 switch (mode)
377 {
378 case 1: {
379 node = line(2, 6).Trim();
380 node.Replace(" ", "0");
381 if (node != ground && node[5] >= 'A') { // Ignore ground and monophasic nodes
382 if (nodeList.Index(node(0, 5)) == wxNOT_FOUND)
383 nodeList.Add(node(0, 5));
384 }
385 } break;
386 case 2: {
387 node = line(2, 6);
388 if (node != ground && node[5] >= 'A') { // Ignore ground and monophasic nodes
389 if (nodeList.Index(node(0, 5)) == wxNOT_FOUND)
390 nodeList.Add(node(0, 5));
391 }
392 node = line(8, 6);
393 if (node != ground && node[5] >= 'A') { // Ignore ground and monophasic nodes
394 if (nodeList.Index(node(0, 5)) == wxNOT_FOUND)
395 nodeList.Add(node(0, 5));
396 }
397 } break;
398 }
399 }
400 return nodeList;
401}
402
403bool EMTElement::SetATPParameter(wxTextFile& atpFile, const wxString& card, const int& line, const int& initPos, const int& size, const wxString& value)
404{
405 bool foundCard = false;
406 int currLine = -1;
407 wxString lineStr = "";
408 lineStr = atpFile.GetFirstLine();
409 while (!atpFile.Eof())
410 {
411 if (tolower(lineStr[0]) == 'c') {
412 lineStr = atpFile.GetNextLine();
413 continue; // Skip comments
414 }
415 if (lineStr.Find(card) != wxNOT_FOUND) {
416 foundCard = true;
417 currLine = -1;
418 }
419 if (foundCard) currLine++;
420 if (foundCard && currLine == line) {
421 for (int i = initPos; i < initPos + size; i++) {
422 lineStr[i] = value[i - initPos];
423 }
424 atpFile.RemoveLine(atpFile.GetCurrentLine());
425 atpFile.InsertLine(lineStr, atpFile.GetCurrentLine());
426 return true;
427 }
428
429 lineStr = atpFile.GetNextLine();
430 }
431 return false;
432}
433
434bool EMTElement::AddConnectionToNode(wxTextFile& atpFile, const wxString& node)
435{
436 // Get connnected bus data
437 bool hasBusData = false;
438 BusElectricalData busData;
439 if (!m_parentList.empty()) {
440 if (m_parentList[0] != nullptr) {
441 Bus* bus = static_cast<Bus*>(m_parentList[0]);
442 busData = bus->GetElectricalData();
443 hasBusData = true;
444 }
445 }
446
447 std::map<wxString, wxString> masks;
448
449 // Load masks from T94.atp file
450 wxTextFile t94File(Paths::GetDataPath() + "/atp/T94.atp");
451 if (t94File.Open()) {
452 wxString current;
453
454 for (size_t i = 0; i < t94File.GetLineCount(); i++) {
455 wxString line = t94File.GetLine(i);
456
457 if (line.StartsWith("[[") && line.EndsWith("]]")) {
458 current = line.Mid(2, line.Length() - 4);
459 masks[current].Clear();
460 continue;
461 }
462
463 if (!current.IsEmpty())
464 masks[current] += line + "\n";
465 }
466 }
467 t94File.Close();
468
469 for (const auto& [key, value] : masks) {
470 masks[key].RemoveLast(); // remove last newline character
471 }
472
473 // Load the FOREIGN MODELS from PSPMOD.mod file
474 wxTextFile pspmodFile(Paths::GetDataPath() + "/atp/PSPMOD.mod");
475 wxString pspmodString = "";
476 if (pspmodFile.Open()) {
477 pspmodString = pspmodFile.GetFirstLine();
478 while (!pspmodFile.Eof())
479 {
480 pspmodString += pspmodFile.GetNextLine() + "\n";
481 }
482 }
483 pspmodFile.Close();
484
485 masks["BRANCH"].Replace("T94L_", "PSPNC");
486 masks["BRANCH"].Replace("<STEP>", ATPField(m_data.pspStepSize, 6));
487
488 masks["SWITCH"].Replace("T94L_", "PSPNC");
489
490 //wxString switchMask = " PSPNC%c%s%c MEASURING %d";
491 //wxString sourceMask = "14PSPNC%c %s%s%s -1. 100.";
492
493 wxString lineStr = "";
494 int branchCardPos = -1, switchCardPos = -1, sourceCardPos = -1, modelsCardPos = -1, outputCardPos = -1;
495 int blankBranchCardPos = -1, blankModelsCardPos = -1;
496 lineStr = atpFile.GetFirstLine();
497 while (!atpFile.Eof())
498 {
499 if (lineStr.IsEmpty()) {
500 lineStr = atpFile.GetNextLine();
501 continue;
502 }
503 if (tolower(lineStr[0]) == 'c') {
504 lineStr = atpFile.GetNextLine();
505 continue; // Skip comments
506 }
507
508 if (lineStr.Find("/SWITCH") != wxNOT_FOUND) {
509 switchCardPos = atpFile.GetCurrentLine() + 1;
510 }
511 if (lineStr.Find("/SOURCE") != wxNOT_FOUND) {
512 sourceCardPos = atpFile.GetCurrentLine() + 1;
513 }
514 if (lineStr.Find("/BRANCH") != wxNOT_FOUND) {
515 branchCardPos = atpFile.GetCurrentLine() + 1;
516 }
517 if (lineStr.Find("/MODELS") != wxNOT_FOUND) {
518 modelsCardPos = atpFile.GetCurrentLine() + 1;
519 }
520 if (lineStr.Find("/OUTPUT") != wxNOT_FOUND) {
521 outputCardPos = atpFile.GetCurrentLine() + 1;
522 }
523 if (lineStr.Find("BLANK BRANCH") != wxNOT_FOUND) {
524 blankBranchCardPos = atpFile.GetCurrentLine() + 1;
525 }
526 if (lineStr.Find("BLANK MODELS") != wxNOT_FOUND) {
527 blankModelsCardPos = atpFile.GetCurrentLine() + 1;
528 }
529
530 lineStr = atpFile.GetNextLine();
531 }
532
533 if (branchCardPos < 0) return false;
534 if (outputCardPos < 0) return false;
535
536 if (modelsCardPos < 0 && branchCardPos > 0) {
537 // Models card must be the first card
538 atpFile.InsertLine("/MODELS", branchCardPos - 1);
539 atpFile.InsertLine("MODELS", branchCardPos);
540 atpFile.InsertLine("ENDMODELS", branchCardPos + 1);
541 modelsCardPos = branchCardPos - 1;
542 outputCardPos += 3;
543 blankBranchCardPos += 3;
544 if (blankModelsCardPos < 0)
545 {
546 atpFile.InsertLine("BLANK MODELS", blankBranchCardPos - 1);
547 blankModelsCardPos = blankBranchCardPos;
548 blankBranchCardPos++;
549
550 }
551 }
552
553 if (switchCardPos < 0 && outputCardPos > 0) {
554 atpFile.InsertLine("/SWITCH", outputCardPos - 1);
555 switchCardPos = outputCardPos;
556 outputCardPos++;
557 blankBranchCardPos++;
558 }
559
560 if (sourceCardPos < 0 && outputCardPos > 0) {
561 atpFile.InsertLine("/SOURCE", outputCardPos - 1);
562 sourceCardPos = outputCardPos;
563 outputCardPos++;
564 blankBranchCardPos++;
565 }
566
567 //for (char i = 'A'; i <= 'C'; ++i) {
568 // lineStr = wxString::Format(switchMask, i, node, i, i == 'A' ? 1 : 0);
569 // atpFile.InsertLine(lineStr, switchCardPos + (i - 'A'));
570 //}
571 //sourceCardPos += 3;
572
573 double voltage = std::abs(m_data.puVoltage) * m_data.baseVoltage * (std::sqrt(2) / std::sqrt(3)); // phase-ground, peak value
574 //wxString ampl = wxString::FromCDouble(voltage, 3); // Amplitude in pu
575 //wxString freq = wxString::FromCDouble(m_data.frequency, 5); // Frequency in Hz
576 // Insert spaces before to complete 10 characters
577 //while (freq.Length() < 10) freq = " " + freq;
578 //while (ampl.Length() < 10) ampl = " " + ampl;
579 for (char i = 'A'; i <= 'C'; ++i) {
580 wxString tag = wxString::Format("<AMP.%c >", i);
581 masks["SOURCE"].Replace(tag, ATPField(voltage, 10));
582
583 tag = wxString::Format("<FREQ.%c >", i);
584 masks["SOURCE"].Replace(tag, ATPField(m_data.frequency, 10));
585
586 double angle = std::arg(m_data.puVoltage) * 180.0 / M_PI - 120.0 * (i - 'A'); // Angle in degrees
587 tag = wxString::Format("<PHASE.%c >", i);
588 masks["SOURCE"].Replace(tag, ATPField(angle, 10));
589 }
590
591 lineStr = atpFile.GetFirstLine();
592 while (!atpFile.Eof())
593 {
594 if (lineStr.IsEmpty()) {
595 lineStr = atpFile.GetNextLine();
596 continue;
597 }
598 if (tolower(lineStr[0]) == 'c') {
599 lineStr = atpFile.GetNextLine();
600 continue; // Skip comments
601 }
602
603 if (lineStr.Find(node) != wxNOT_FOUND) {
604 lineStr.Replace(node, "PSPNC");
605 atpFile.RemoveLine(atpFile.GetCurrentLine());
606 atpFile.InsertLine(lineStr, atpFile.GetCurrentLine());
607 }
608
609 if (lineStr.Find("/MODELS") != wxNOT_FOUND)
610 atpFile.InsertLine(pspmodString, atpFile.GetCurrentLine() + 2);
611
612 if (lineStr.Find("/SWITCH") != wxNOT_FOUND)
613 atpFile.InsertLine(masks["SWITCH"], atpFile.GetCurrentLine() + 1);
614
615 if (lineStr.Find("/SOURCE") != wxNOT_FOUND)
616 atpFile.InsertLine(masks["SOURCE"], atpFile.GetCurrentLine() + 1);
617
618 if (lineStr.Find("/BRANCH") != wxNOT_FOUND)
619 atpFile.InsertLine(masks["BRANCH"], atpFile.GetCurrentLine() + 1);
620
621 lineStr = atpFile.GetNextLine();
622 }
623
624 lineStr = atpFile.GetFirstLine();
625 while (!atpFile.Eof())
626 {
627 lineStr += atpFile.GetNextLine() + "\n";
628
629 }
630
631
632 //int cardPos = sourceCardPos;
633 //for (char i = 'A'; i <= 'C'; ++i) {
634 // wxString angle = wxString::FromCDouble(std::arg(m_data.puVoltage) * 180.0 / M_PI - 120.0 * (i - 'A'), 5); // Angle in degrees
635 //
636 // // Insert spaces before to complete 10 characters
637 // while (angle.Length() < 10) angle = " " + angle;
638 //
639 // //lineStr = wxString::Format(sourceMask, i, ampl, freq, angle);
640 // atpFile.InsertLine(lineStr, cardPos);
641 // cardPos++;
642 //
643 // if (hasBusData) {
644 // // Insert harmonic voltage
645 // for (size_t j = 0; j < busData.harmonicOrder.size(); ++j) {
646 // int order = busData.harmonicOrder[j];
647 // if (order == 1) continue; // Skip fundamental
648 //
649 // std::complex<double> harmVoltage = busData.harmonicVoltage[j];
650 //
651 // voltage = std::abs(busData.harmonicVoltage[j]) * m_data.baseVoltage * (std::sqrt(2) / std::sqrt(3));
652 // wxString amplH = wxString::FromCDouble(voltage, 3);
653 // while (amplH.Length() < 10) amplH = " " + amplH;
654 //
655 // wxString freqH = wxString::FromCDouble(m_data.frequency * static_cast<double>(order), 5);
656 // while (freqH.Length() < 10) freqH = " " + freqH;
657 //
658 // double angleValue = std::arg(harmVoltage) * 180.0 / M_PI;
659 // if (order % 3 == 1) { // Positive sequence
660 // angleValue += -120.0 * (i - 'A');
661 // }
662 // else if (order % 3 == 2) { // Negative sequence
663 // angleValue += 120.0 * (i - 'A');
664 // }
665 // // Zero sequence doesn't change the angle because it's the same for all phases
666 //
667 // wxString angleH = wxString::FromCDouble(angleValue, 5);
668 // while (angleH.Length() < 10) angleH = " " + angleH;
669 //
670 // //lineStr = wxString::Format(sourceMask, i, amplH, freqH, angleH);
671 // atpFile.InsertLine(lineStr, cardPos);
672 // cardPos++;
673 // }
674 // }
675 //}
676
677 return true;
678}
679
680void EMTElement::MedianFilter(std::vector<double>& data)
681{
682 // Based on: http://www.librow.com/articles/article-1
683
684 const size_t n = data.size();
685
686 if (n < 2)
687 return;
688
689 std::vector<double> extension(n + 4);
690
691 std::memcpy(extension.data() + 2, data.data(), n * sizeof(double));
692
693 extension[0] = data[1];
694 extension[1] = data[0];
695 extension[n + 2] = data[n - 1];
696 extension[n + 3] = data[n - 2];
697
698 DoMedianFilter(extension.data(), data, static_cast<int>(n + 4));
699
700 //size_t n = data.size();
701 //std::vector<double> result(data);
702 //
704 //if (data.empty() || n < 1)
705 // return result;
707 //if (n == 1)
708 //{
709 // result[0] = data[0];
710 // return result;
711 //}
713 //double* extension = new double[n + 4];
718 //memcpy(extension + 2, data.data(), n * sizeof(double));
719 //for (int i = 0; i < 2; ++i)
720 //{
721 // extension[i] = data[1 - i];
722 // extension[n + 2 + i] = data[n - 1 - i];
723 //}
725 // DoMedianFilter(extension, result, n + 4);
728 //delete[] extension;
729 //
730 //return result;
731}
732
733bool EMTElement::CalculateCurrent(wxString& errorMsg, const Mode& mode, const bool& saveRawData, const bool& saveFFTData)
734{
735 wxFileName fileName(m_data.atpFile);
736 if (!fileName.IsOk()) {
737 errorMsg = wxString::Format(_("Invalid ATP file path for the electromagnetic element \"%s\"."), m_data.name);
738 return false;
739 }
740
741 //double fundFreq = properties.GetSimulationPropertiesData().stabilityFrequency;
742 double fundFreq = m_data.frequency;
743 double timeSim = (1.0 / fundFreq) * static_cast<double>(m_data.cyclesToSS);
744
745 //wxString atpFolder = properties.GetGeneralPropertiesData().atpPath.GetPath();
746 wxString atpFolder = m_data.atpPath.GetPath();
747 // Set fundamental frequency to EMT element
748 //m_data.frequency = fundFreq;
749
750 wxExecuteEnv env;
751 env.cwd = atpFolder;
752
753 // Save the ATP file in ATP work folder
754 wxTextFile origFile(m_data.atpFile.GetFullPath());
755 //fileName.SetPath(properties.GetGeneralPropertiesData().atpWorkFolder);
756 fileName.SetPath(m_data.atpWorkFolder);
757 wxString name = fileName.GetFullPath();
758 wxTextFile copyFile(fileName.GetFullPath());
759 if (origFile.Open()) {
760 copyFile.Create();
761 for (size_t i = 0; i < origFile.GetLineCount(); i++) {
762 copyFile.AddLine(origFile.GetLine(i));
763 }
764 origFile.Close();
765
766 wxString stepSizeStr = wxString::Format("%.2E", m_data.stepSize);
767 stepSizeStr.Replace(wxT(","), wxT("."));
768 wxString timeStr = wxString::Format("%.6f", timeSim);
769 timeStr.Replace(wxT(","), wxT("."));
770 SetATPParameter(copyFile, wxT("BEGIN NEW DATA CASE"), 1, 0, 8, stepSizeStr);
771 SetATPParameter(copyFile, wxT("BEGIN NEW DATA CASE"), 1, 8, 8, timeStr);
772 SetATPParameter(copyFile, wxT("BEGIN NEW DATA CASE"), 2, 0, 8, wxT("99999999"));
773 SetATPParameter(copyFile, wxT("BEGIN NEW DATA CASE"), 2, 8, 8, wxString::Format("%8d", m_data.recordFrequency));
774
775 // Add source and switch to selected node
776 AddConnectionToNode(copyFile, m_data.atpNodeName);
777
778 copyFile.Write();
779 }
780 else {
781 errorMsg = wxString::Format(_("Fail to open ATP file of the electromagnetic element \"%s\"."), m_data.name);
782 return false;
783 }
784
785
786 ATPPSPBridge bridge;
787
788 //wxProcess* proc = new wxProcess();
789
790 //wxString cmd = properties.GetGeneralPropertiesData().atpPath.GetFullPath() + wxT(" both ") + fileName.GetFullPath() + wxT(" s -R");
791 wxString cmd = m_data.atpPath.GetFullPath() + wxT(" both ") + fileName.GetFullPath() + wxT(" s -R");
792 long pid = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_NODISABLE, nullptr, &env);
793 //long pid = wxExecute(cmd, wxEXEC_SYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_NODISABLE, nullptr, &env);
794 //long pid = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_NODISABLE, nullptr, &env);
795 //long pid = wxExecute(cmd, wxEXEC_ASYNC | wxEXEC_NODISABLE, proc, &env);
796
797 if (!bridge.Connect(5000, 100))
798 {
799 if (!CheckLISFile(fileName, errorMsg))
800 {
801 errorMsg = _("Unable to connect to ATP.\n"
802 "Please make sure you are using an ATP version with PSP support.");
803 }
804
805 return false;
806 }
807 //wxEndBusyCursor();
808 bridge.SetProcessId(pid);
809
810
811 auto& data = bridge.GetSharedData();
812 data.vrms = std::abs(m_data.puVoltage) * m_data.baseVoltage / std::sqrt(3);
813 data.phase = std::arg(m_data.puVoltage) * 180.0 / M_PI;
814 data.mode = mode;
815 //m_data.atpData.clear();
816 m_data.atpSampleData.clear();
817
818 //bridge.ReleaseATP();
819 while (bridge.WaitATP())
820 {
821 if (!saveRawData || !saveFFTData) {
822 bridge.ReleaseATP();
823 continue;
824 }
825
826 data = bridge.GetSharedData();
827
828
829 // Process ATP data
830 if (data.stepCount > 0)
831 {
832 const double dt = data.atpStepsize;
833 const double t0 = data.t - (data.stepCount - 1) * dt;
834
835 //m_data.atpData.reserve(m_data.atpData.size() + data.stepCount);
836 m_data.atpSampleData.reserve(m_data.atpSampleData.size() + data.stepCount);
837
838 std::vector<double> ia, ib, ic;
839
840 if (m_data.useMedianFilter)
841 {
842 ia.reserve(data.stepCount);
843 ib.reserve(data.stepCount);
844 ic.reserve(data.stepCount);
845
846 for (uint32_t i = 0; i < data.stepCount; ++i)
847 {
848 ia.push_back(data.samples[i].current[0]);
849 ib.push_back(data.samples[i].current[1]);
850 ic.push_back(data.samples[i].current[2]);
851 }
852
853 MedianFilter(ia);
854 MedianFilter(ib);
855 MedianFilter(ic);
856 }
857
858 for (uint32_t i = 0; i < data.stepCount; ++i)
859 {
860 const double t = t0 + i * dt;
861
862 const double currentA = m_data.useMedianFilter ? -ia[i] : -data.samples[i].current[0];
863 const double currentB = m_data.useMedianFilter ? -ib[i] : -data.samples[i].current[1];
864 const double currentC = m_data.useMedianFilter ? -ic[i] : -data.samples[i].current[2];
865
866 const double iaMag = std::hypot(-data.phasor.Id[0], -data.phasor.Iq[0]);
867 const double ibMag = std::hypot(-data.phasor.Id[1], -data.phasor.Iq[1]);
868 const double icMag = std::hypot(-data.phasor.Id[2], -data.phasor.Iq[2]);
869 const double iaAng = std::atan2(-data.phasor.Iq[0], -data.phasor.Id[0]);
870 const double ibAng = std::atan2(-data.phasor.Iq[1], -data.phasor.Id[1]);
871 const double icAng = std::atan2(-data.phasor.Iq[2], -data.phasor.Id[2]);
872
873 //m_data.atpData.emplace_back(t, currentA);
874 m_data.atpSampleData.push_back({
875 .t = t,
876 .current = { currentA, currentB, currentC },
877 .mag = {iaMag, ibMag, icMag},
878 .angle = {iaAng, ibAng, icAng}
879 });
880 }
881 }
882
883 bridge.ReleaseATP();
884 }
885
886 if (data.terminate < 0)
887 {
888 CheckLISFile(fileName, errorMsg);
889 return false;
890 }
891 constexpr size_t phase = 0; // 0 = A, 1 = B, 2 = C
892
893 // Save current
894 m_data.current = std::complex<double>(-data.phasor.Id[phase], -data.phasor.Iq[phase]);
895
896 // Delete all .tmp files
897 wxDir dir(atpFolder);
898 if (dir.IsOpened()) {
899 wxString file;
900 bool cont = dir.GetFirst(&file, wxT("*.tmp"), wxDIR_FILES);
901 while (cont) {
902 wxRemoveFile(atpFolder + wxT("/") + file);
903 cont = dir.GetNext(&file);
904 }
905 }
906
907 if (saveFFTData)
908 {
909 // FFT
910 double dataStepSize = m_data.atpSampleData[1].t - m_data.atpSampleData[0].t;
911 bool useRemainder = false;
912 size_t sampleCount = m_data.atpSampleData.size();
913 size_t n = static_cast<size_t>(std::ceil(1.0 / (dataStepSize * fundFreq)));
914
915 // Due to ATP logic, the number of samples can be greater than the number
916 // of samples stored. In this case, follow the remainder approach.
917 if (n > sampleCount)
918 {
919 n = sampleCount;
920 useRemainder = true;
921 }
922
923 double fs = 1.0 / dataStepSize;
924 double df = fs / static_cast<double>(n);
925
926 if (useRemainder) {
927 double rmder = std::abs(std::remainder(fundFreq, df));
928 size_t minRmderN = n;
929
930 while (rmder > 1e-3)
931 {
932 --n;
933 df = fs / static_cast<double>(n);
934 if (std::abs(std::remainder(fundFreq, df)) < rmder)
935 minRmderN = n;
936
937 rmder = std::abs(std::remainder(fundFreq, df));
938 if (n == 0) {
939 n = minRmderN;
940 df = fs / static_cast<double>(n);
941 break;
942 }
943 }
944 }
945
946 double dtWindow = m_data.atpSampleData.back().t - m_data.atpSampleData[sampleCount - n].t;
947
948 fftw_complex* out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * (n / 2 + 1));
949
950 double* in = (double*)fftw_malloc(sizeof(double) * n);
951
952 // Populate FFT input
953 const size_t first = sampleCount - n;
954 for (size_t i = 0; i < n; ++i) {
955 in[i] = m_data.atpSampleData[first + i].current[phase];
956 }
957
958 fftw_plan p = fftw_plan_dft_r2c_1d(n, in, out, FFTW_ESTIMATE);
959
960 fftw_execute(p);
961 fftw_destroy_plan(p);
962
963 double ampCorrection = 2.0 / static_cast<double>(n);
964
965 // Identify fundamental frequency
966 int fundIndex = -1;
967 double fundMagnitude = 0.0;
968
969 for (size_t i = 0; i < (n / 2 + 1); ++i) {
970 double freq = static_cast<double>(i) * df;
971
972 if (std::remainder(freq, fundFreq)) {
973 fundIndex = static_cast<int>(i);
974
975 fundMagnitude =
976 std::hypot(out[i][0], out[i][1]) * ampCorrection;
977
978 break;
979 }
980 }
981
982 if (fundIndex < 0) {
983 errorMsg = wxString::Format(_("Fail to identify the fundamental frequency of the electromagnetic element \"%s\"."), m_data.name);
984
985 fftw_free(in);
986 fftw_free(out);
987
988 return false;
989 }
990
991 // Fundamental and harmonics
992 m_data.currHarmonics.clear();
993
994 for (size_t i = 0; i < (n / 2 + 1); ++i) {
995 double freq = static_cast<double>(i) * df;
996 int order = static_cast<int>(std::round(freq / fundFreq));
997
998 if (order > m_data.numMaxHarmonics)
999 break;
1000
1001 double magnitude = std::hypot(out[i][0], out[i][1]) * ampCorrection;
1002
1003 if ((magnitude / fundMagnitude) > (m_data.harmonicsThreshold / 100.0)) {
1004 m_data.currHarmonics[order] =
1005 std::complex<double>(out[i][0], out[i][1]) *
1006 ampCorrection / std::sqrt(2.0);
1007 }
1008 }
1009
1010 m_data.inFFTData.clear();
1011 m_data.outFFTData.clear();
1012
1013 for (size_t i = 0; i < n; ++i)
1014 {
1015 m_data.inFFTData.emplace_back(m_data.atpSampleData[first + i].t, in[i]);
1016 }
1017
1018 for (size_t i = 0; i < (n / 2 + 1); ++i)
1019 {
1020 std::complex<double> value(out[i][0] * ampCorrection, out[i][1] * ampCorrection);
1021 double freq = static_cast<double>(i) * df;
1022 m_data.outFFTData.emplace_back(freq, value);
1023 }
1024
1025 fftw_free(in);
1026 fftw_free(out);
1027 }
1028
1029 return true;
1030
1031 errorMsg = "Not implemented yet.\n EMTElement.cpp, line 772ish";
1032 return false;
1033
1034 fileName.SetFullName(fileName.GetName() + wxT(".pl4"));
1035 wxFileInputStream pl4File(fileName.GetFullPath());
1036 // https://github.com/ldemattos/readPL4/wiki/PISA's-PL4-Format---Time-domain
1037 if (pl4File.IsOk() && pl4File.GetSize() > 0) {
1038 wxDataInputStream store(pl4File);
1039 store.UseBasicPrecisions();
1040 float* buffer = new float[2];
1041 std::vector<double> timeVec;
1042 std::vector<double> valueVec;
1043 double oldTime = 0.0;
1044 int read16Bytes = 0;
1045 while (!pl4File.Eof()) {
1046 store.ReadFloat(buffer, 2);
1047 if (read16Bytes > 11 && (buffer[0] >= oldTime)) {
1048 oldTime = buffer[0];
1049 timeVec.emplace_back(buffer[0]);
1050 valueVec.emplace_back(buffer[1]);
1051 }
1052 read16Bytes++;
1053 }
1054 delete[] buffer;
1055
1056 if (m_data.useMedianFilter)
1057 MedianFilter(valueVec);
1058
1059 // FFT
1060 double dataStepSize = timeVec[1] - timeVec[0]; // Real sampling time from data
1061 bool useRemainder = false;
1062 size_t n = ceil(1.0 / (dataStepSize * fundFreq)); // Number of samples
1063 // Due to ATP logic, the number of samples can be greater than the number of samples in the PL4 file
1064 // In this case, I choose to follow the remainder approach
1065 if (n > valueVec.size()) {
1066 n = valueVec.size();
1067 useRemainder = true;
1068 }
1069
1070 double fs = 1.0 / dataStepSize; // Sampling frequency
1071 double df = fs / static_cast<double>(n); // Frequency resolution
1072
1073 if (useRemainder) {
1074 double rmder = abs(remainder(fundFreq, df));
1075 int minRmderN = n;
1076 while (rmder > 1e-3) {
1077 n--;
1078 df = fs / static_cast<double>(n);
1079
1080 if (abs(remainder(fundFreq, df)) < rmder) minRmderN = n;
1081
1082 rmder = abs(remainder(fundFreq, df));
1083
1084 if (n <= 0) {
1085 n = minRmderN;
1086 df = fs / static_cast<double>(n);
1087 break;
1088 }
1089 }
1090 }
1091
1092 double dtWindow = timeVec[timeVec.size() - 1] - timeVec[timeVec.size() - n];
1093
1094 fftw_complex* out;
1095 double* in;
1096 fftw_plan p;
1097 out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * (n / 2 + 1));
1098 in = (double*)fftw_malloc(sizeof(double) * n);
1099 // Populate input array
1100 for (size_t i = (valueVec.size() - n); i < valueVec.size(); ++i) {
1101 size_t index = i - (valueVec.size() - n);
1102 in[index] = valueVec[i];
1103 }
1104
1105 p = fftw_plan_dft_r2c_1d(n, in, out, FFTW_ESTIMATE);
1106 fftw_execute(p); /* repeat as needed */
1107 fftw_destroy_plan(p);
1108
1109 double ampCorrection = 2.0 / static_cast<double>(n);
1110
1111 // Identify fundamental frequency
1112 int fundIndex = -1;
1113 double fundMagnitude = 0.0;
1114 for (size_t i = 0; i < (n / 2 + 1); i++) {
1115 double freq = static_cast<double>(i) * df;
1116 if (remainder(freq, fundFreq)) {
1117 fundIndex = i;
1118 fundMagnitude = sqrt(out[i][0] * out[i][0] + out[i][1] * out[i][1]) * ampCorrection;
1119 break;
1120 }
1121 }
1122 if (fundIndex < 0) {
1123 errorMsg = wxString::Format(_("Fail to identify the fundamental frequency of the electromagnetic element \"%s\"."), m_data.name);
1124 return false;
1125 }
1126
1127 // Fundamental and harmonics
1128 m_data.currHarmonics.clear();
1129 //m_data.currHarmonicsOrder.clear();
1130 for (size_t i = 0; i < (n / 2 + 1); i++) {
1131 double freq = static_cast<double>(i) * df;
1132 int order = static_cast<int>(round(freq / fundFreq));
1133
1134 if (order > m_data.numMaxHarmonics) break; // Stop if the order is greater than the maximum number of harmonics
1135
1136 double magnitude = sqrt(out[i][0] * out[i][0] + out[i][1] * out[i][1]) * ampCorrection;
1137 if ((magnitude / fundMagnitude) > (m_data.harmonicsThreshold / 100.0)) {
1138 //m_data.currHarmonics.emplace_back(std::complex<double>(out[i][0], out[i][1]) * ampCorrection);
1139 //m_data.currHarmonicsOrder.emplace_back(order);
1140 m_data.currHarmonics[order] = std::complex<double>(out[i][0], out[i][1]) * ampCorrection / sqrt(2.0); // RMS value
1141 }
1142 }
1143
1144 //if (saveFFTData) {
1145 // m_data.atpData.clear();
1146 // m_data.inFFTData.clear();
1147 // m_data.outFFTData.clear();
1148 // for (size_t i = 0; i < valueVec.size(); i++) {
1149 // m_data.atpData.emplace_back(std::make_pair(timeVec[i], valueVec[i]));
1150 // }
1151 // for (size_t i = 0; i < n; i++) {
1152 // m_data.inFFTData.emplace_back(std::make_pair(timeVec[i + timeVec.size() - n], in[i]));
1153 // }
1154 // for (size_t i = 0; i < (n / 2 + 1); i++) {
1155 // std::complex<double> value(out[i][0] * ampCorrection, out[i][1] * ampCorrection);
1156 // double freq = static_cast<double>(i) * df;
1157 // m_data.outFFTData.emplace_back(std::make_pair(freq, value));
1158 // }
1159 //}
1160 //
1161 fftw_free(in);
1162 fftw_free(out);
1163
1164 }
1165 else {
1166 wxString atpErrorMsg = _("No error found.");
1167
1168 fileName.SetFullName(fileName.GetName() + wxT(".lis"));
1169 wxTextFile lisFile(fileName.GetFullPath());
1170
1171 if (!lisFile.Exists()) {
1172 errorMsg = wxString::Format(_("Fail to run ATP file of the electromagnetic element \"%s\".\nThe ATP program did not return any error messages."), m_data.name);
1173 return false;
1174 }
1175 if (lisFile.Open()) {
1176 wxString line = lisFile.GetFirstLine() + "\n";
1177 bool foundError = false;
1178 int lineCount = -1;
1179 atpErrorMsg.Clear();
1180 while (!lisFile.Eof())
1181 {
1182 if (line.Find(wxT("KILL ")) != wxNOT_FOUND) {
1183 foundError = true;
1184 lineCount++;
1185 }
1186 if (line.Find(wxT("----------")) != wxNOT_FOUND) {
1187 foundError = false;
1188 }
1189 if (foundError && lineCount == 1)
1190 atpErrorMsg += line + " ";
1191 line = lisFile.GetNextLine();
1192 }
1193 lisFile.Close();
1194 }
1195 else
1196 {
1197 errorMsg = wxString::Format(_("Fail to run ATP file of the electromagnetic element \"%s\".\nThe ATP program did not return any error messages."), m_data.name);
1198 return false;
1199 }
1200
1201 errorMsg = wxString::Format(_("Fail to run ATP file of the electromagnetic element \"%s\".\nThe ATP returned the following error message:\n\"%s\""), m_data.name, atpErrorMsg);
1202 return false;
1203 }
1204 return true;
1205}
1206
1207void EMTElement::UpdateData(const PropertiesData* properties, bool updateVoltageBase)
1208{
1209 if (properties != nullptr) {
1210 m_data.frequency = properties->GetSimulationPropertiesData().stabilityFrequency;
1211 m_data.atpPath = properties->GetGeneralPropertiesData().atpPath;
1212 m_data.atpWorkFolder = properties->GetGeneralPropertiesData().atpWorkFolder;
1213 m_data.pspStepSize = properties->GetSimulationPropertiesData().timeStep;
1214 }
1215 if (!m_parentList.empty()) {
1216 Bus* bus = static_cast<Bus*>(m_parentList[0]);
1217 if (bus != nullptr) {
1218 auto busData = bus->GetElectricalData();
1219 std::complex<double> voltage = std::complex<double>(1.0, 0.0);
1220 if (abs(busData.voltage) > 1e-3)
1221 m_data.puVoltage = busData.voltage;
1222 if (updateVoltageBase) {
1223 m_data.baseVoltage = GetValueFromUnit(busData.nominalVoltage, busData.nominalVoltageUnit);
1224 }
1225 }
1226 }
1227
1228}
1229
1230void EMTElement::SetNominalVoltage(std::vector<double> nominalVoltage, std::vector<ElectricalUnit> nominalVoltageUnit)
1231{
1232 if (!nominalVoltage.empty()) {
1233 m_data.baseVoltage = GetValueFromUnit(nominalVoltage[0], nominalVoltageUnit[0]);
1234 }
1235}
1236
1237void EMTElement::DoMedianFilter(double* extension, std::vector<double>& result, const int& n)
1238{
1239 // Move window through all elements of the signal
1240 for (int i = 2; i < n - 2; ++i)
1241 {
1242 // Pick up window elements
1243 double window[5];
1244 for (int j = 0; j < 5; ++j)
1245 window[j] = extension[i - 2 + j];
1246 // Order elements (only half of them)
1247 for (int j = 0; j < 3; ++j)
1248 {
1249 // Find position of minimum element
1250 int min = j;
1251 for (int k = j + 1; k < 5; ++k)
1252 if (window[k] < window[min])
1253 min = k;
1254 // Put found minimum element in its place
1255 std::swap(window[j], window[min]);
1256 }
1257 // Get result - the middle element
1258 result[i - 2] = window[2];
1259 }
1260 //return result;
1261}
1262
1263wxString EMTElement::ATPField(double value, size_t width)
1264{
1265 wxString best;
1266
1267 // Try fixed-point notation
1268 for (int prec = 15; prec >= 0; --prec)
1269 {
1270 wxString s = wxString::Format("%.*f", prec, value);
1271 s.Replace(",", ".");
1272
1273 while (s.Contains(".") && s.EndsWith("0"))
1274 s.RemoveLast();
1275 if (s.EndsWith("."))
1276 s.RemoveLast();
1277
1278 if (s.Length() <= width)
1279 {
1280 best = s;
1281 break;
1282 }
1283 }
1284
1285 // Try scientific notation
1286 for (int prec = 15; prec >= 0; --prec)
1287 {
1288 wxString s = wxString::Format("%.*E", prec, value);
1289 s.Replace(",", ".");
1290
1291 if (prec == 0)
1292 s.Replace("E", ".E");
1293
1294 if (s.Length() <= width)
1295 {
1296 // Keep the representation with the highest useful precision
1297 if (best.empty() || s.Length() > best.Length())
1298 best = s;
1299
1300 break;
1301 }
1302 }
1303
1304 if (best.empty())
1305 return wxString('*', width);
1306
1307 best.Prepend(wxString(' ', width - best.Length()));
1308 return best;
1309}
1310
1311bool EMTElement::CheckLISFile(wxFileName fileName, wxString& errorMsg) const
1312{
1313 wxString atpErrorMsg = _("No error found.");
1314
1315 fileName.SetFullName(fileName.GetName() + wxT(".lis"));
1316 wxTextFile lisFile(fileName.GetFullPath());
1317
1318 if (!lisFile.Exists()) {
1319 errorMsg = wxString::Format(_("Fail to run ATP file of the electromagnetic element \"%s\".\nThe ATP program did not return any error messages."), m_data.name);
1320 return false;
1321 }
1322 if (lisFile.Open()) {
1323 wxString line = lisFile.GetFirstLine() + "\n";
1324 bool foundError = false;
1325 int lineCount = -1;
1326 atpErrorMsg.Clear();
1327 while (!lisFile.Eof())
1328 {
1329 if (line.Find(wxT("KILL ")) != wxNOT_FOUND) {
1330 foundError = true;
1331 lineCount++;
1332 }
1333 if (line.Find(wxT("----------")) != wxNOT_FOUND) {
1334 foundError = false;
1335 }
1336 if (foundError && lineCount == 1)
1337 atpErrorMsg += line + " ";
1338 line = lisFile.GetNextLine();
1339 }
1340 lisFile.Close();
1341 }
1342 else
1343 {
1344 errorMsg = wxString::Format(_("Fail to run ATP file of the electromagnetic element \"%s\".\nThe ATP program did not return any error messages."), m_data.name);
1345 return true;
1346 }
1347
1348 errorMsg = wxString::Format(_("Fail to run ATP file of the electromagnetic element \"%s\".\nThe ATP returned the following error message:\n\"%s\""), m_data.name, atpErrorMsg);
1349 return true;
1350}
@ ID_EDIT_ELEMENT
Definition Element.h:77
Node for power elements. All others power elements are connected through this.
Definition Bus.h:86
Element to connect ATP-EMTP.
Definition EMTElement.h:83
virtual void Rotate(bool clockwise=true)
Rotate the element.
virtual bool GetContextMenu(wxMenu &menu)
Get the element contex menu.
virtual bool ShowForm(wxWindow *parent, Element *element, wxWindow *workspace=nullptr)
Show element data form.
virtual wxString GetTipText() const
Get the tip text.
virtual void DrawDC(GUIColour *guiColour, wxPoint2DDouble translation, double scale, wxGraphicsContext *gc) const
Draw the element using GDI+.
virtual bool AddParent(Element *parent, wxPoint2DDouble position)
Add a parent to the element. This method must be used on power elements that connect to a bus,...
virtual Element * GetCopy()
Get a the element copy.
void SetNominalVoltage(std::vector< double > nominalVoltage, std::vector< ElectricalUnit > nominalVoltageUnit)
Set nominal voltage of the element.
Base class of all elements of the program. This class is responsible for manage graphical and his dat...
Definition Element.h:114
virtual void GeneralMenuItens(wxMenu &menu)
Insert general itens to context menu.
Definition Element.cpp:393
wxPoint2DDouble GetPosition() const
Get the element position.
Definition Element.h:188
double GetAngle() const
Get the element angle.
Definition Element.h:213
virtual wxPoint2DDouble RotateAtPosition(wxPoint2DDouble pointToRotate, double angle, bool degrees=true) const
Rotate a point as element position being the origin.
Definition Element.cpp:228
virtual void AddChild(Element *child)
Add a child to the child list.
Definition Element.cpp:499
bool SetOnline(bool online=true)
Set if the element is online or offline.
Definition Element.cpp:383
virtual void DrawDCCircle(wxPoint2DDouble position, double radius, int numSegments, wxGraphicsContext *gc) const
Draw a circle using device context.
Definition Element.cpp:173
Class to draw text on Graphics Context using wxWidgets.
Definition GCText.h:32
virtual void Draw(wxPoint2DDouble position, wxGraphicsContext *gc, double angle=0.0, wxColour colour= *wxBLACK) const
Draw the text in wxGraphicsContext.
Definition GCText.cpp:35
virtual void SetText(wxString text)
Set correctly a new text string.
Definition GCText.cpp:68
virtual void UpdateSwitches()
Update the switch position.
virtual void DrawDCSwitches(GUIColour *guiColour, wxGraphicsContext *gc) const
Draw switch.
virtual wxPoint2DDouble GetSwitchPoint(Element *parent, wxPoint2DDouble parentPoint, wxPoint2DDouble secondPoint) const
Get the correct switch position.
General and simulation data manager.
This class manages the graphical and power elements. It is responsible for handling the user's intera...
Definition Workspace.h:105