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"
13#include <wx/dcgraph.h>
14#include <wx/textfile.h>
16#include <wx/process.h>
17#include <wx/wfstream.h>
18#include <wx/datstrm.h>
19#include <wx/busyinfo.h>
21EMTElement::EMTElement()
25EMTElement::EMTElement(wxString name)
30EMTElement::~EMTElement()
44 m_parentList.push_back(parent);
46 wxPoint2DDouble parentPt =
51 m_position = parentPt + wxPoint2DDouble(0.0, 100.0);
54 m_rect = wxRect2DDouble(m_position.m_x - m_width / 2.0, m_position.m_y - m_height / 2.0, m_width, m_height);
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));
63 wxRect2DDouble genRect(0, 0, 0, 0);
64 m_switchRect.push_back(genRect);
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;
80 wxColour elementColour;
83 elementColour = guiColour->eventElement;
85 elementColour = guiColour->enabled;
88 elementColour = guiColour->disable;
93 gc->SetPen(wxPen(guiColour->selection, 2.0 + m_borderSize * 2.0));
94 gc->SetBrush(*wxTRANSPARENT_BRUSH);
96 gc->StrokeLines(m_pointList.size(), &m_pointList[0]);
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);
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);
111 gc->SetPen(*wxTRANSPARENT_PEN);
112 gc->SetBrush(wxBrush(guiColour->selection));
113 DrawDCCircle(m_pointList[0], 5.0 + m_borderSize / scale, 10, gc);
117 gc->SetPen(*wxTRANSPARENT_PEN);
118 gc->SetBrush(wxBrush(elementColour));
121 gc->SetPen(wxPen(elementColour, 2));
122 gc->SetBrush(*wxTRANSPARENT_BRUSH);
123 gc->StrokeLines(m_pointList.size(), &m_pointList[0]);
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);
134 gc->SetPen(wxPen(elementColour, 2));
135 gc->SetBrush(wxBrush(guiColour->background));
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);
142 font.SetFaceName(wxT(
"CMU Serif"));
143 font.SetPointSize(10);
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);
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);
156 gcdc->DrawSpline(ptList.size(), ptList.data());
164 wxColour elementColour;
167 elementColour = guiColour->eventElement;
169 elementColour = guiColour->enabled;
172 elementColour = guiColour->disable;
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));
182 dc.SetPen(wxPen(guiColour->selection, 2 + m_borderSize * 2.0));
183 dc.SetBrush(*wxTRANSPARENT_BRUSH);
185 dc.DrawLines(pointListInt.size(), &pointListInt[0]);
187 DrawDCRoundedRectRotated(dc, m_position, m_width, m_height, 10.0, m_angle);
190 dc.SetPen(*wxTRANSPARENT_PEN);
191 dc.SetBrush(wxBrush(guiColour->selection));
192 DrawDCCircle(m_pointList[0], 5.0 + m_borderSize / scale, dc);
196 dc.SetPen(*wxTRANSPARENT_PEN);
197 dc.SetBrush(wxBrush(elementColour));
200 dc.SetPen(wxPen(elementColour, 2));
201 dc.SetBrush(*wxTRANSPARENT_BRUSH);
202 dc.DrawLines(pointListInt.size(), &pointListInt[0]);
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);
211 font.SetFaceName(wxT(
"CMU Serif"));
212 font.SetPointSize(10);
215 gcText.SetFont(font);
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);
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);
226 dc.DrawSpline(ptList.size(), ptList.data());
232 double rotAngle = m_rotationAngle;
233 if (!clockwise) rotAngle = -m_rotationAngle;
236 if (m_angle >= 360 || m_angle <= -360) m_angle = 0.0;
239 UpdateSwitchesPosition();
251 wxString tipText = m_data.name;
252 tipText += wxT(
"\n");
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));
259 wxString harmonicsInfo = _(
"\n\nHarmonics info:");
260 bool hasHarmonics =
false;
261 for (
auto& [order, current] : m_data.currHarmonics) {
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'));
268 if (hasHarmonics) tipText += harmonicsInfo;
276 if (!ws)
return false;
279 emtForm.SetTitle(_(
"Electromagnetic Transient"));
280 emtForm.CenterOnParent();
281 if (emtForm.ShowModal() == wxID_OK) {
287rapidxml::xml_node<>* EMTElement::SaveElement(rapidxml::xml_document<>& doc, rapidxml::xml_node<>* elementListNode)
289 auto elementNode = XMLParser::AppendNode(doc, elementListNode,
"EMTElement");
290 XMLParser::SetNodeAttribute(doc, elementNode,
"ID", m_elementID);
292 SaveCADProperties(doc, elementNode);
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);
317bool EMTElement::OpenElement(rapidxml::xml_node<>* elementNode, std::vector<Element*> parentList)
319 if (!OpenCADProperties(elementNode, parentList))
return false;
321 auto electricalProp = elementNode->first_node(
"ElectricalProperties");
322 if (!electricalProp)
return false;
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");
338wxArrayString EMTElement::GetATPNodes(wxArrayString atpFile)
340 wxArrayString nodeList;
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;
350 bool cardHeader =
false;
351 for (wxString card : mode0Cards) {
352 if (line.Find(card) != wxNOT_FOUND) {
358 for (wxString card : mode1Cards) {
359 if (line.Find(card) != wxNOT_FOUND) {
365 for (wxString card : mode2Cards) {
366 if (line.Find(card) != wxNOT_FOUND) {
372 if (cardHeader)
continue;
379 node = line(2, 6).Trim();
380 node.Replace(
" ",
"0");
381 if (node != ground && node[5] >=
'A') {
382 if (nodeList.Index(node(0, 5)) == wxNOT_FOUND)
383 nodeList.Add(node(0, 5));
388 if (node != ground && node[5] >=
'A') {
389 if (nodeList.Index(node(0, 5)) == wxNOT_FOUND)
390 nodeList.Add(node(0, 5));
393 if (node != ground && node[5] >=
'A') {
394 if (nodeList.Index(node(0, 5)) == wxNOT_FOUND)
395 nodeList.Add(node(0, 5));
403bool EMTElement::SetATPParameter(wxTextFile& atpFile,
const wxString& card,
const int& line,
const int& initPos,
const int& size,
const wxString& value)
405 bool foundCard =
false;
407 wxString lineStr =
"";
408 lineStr = atpFile.GetFirstLine();
409 while (!atpFile.Eof())
411 if (tolower(lineStr[0]) ==
'c') {
412 lineStr = atpFile.GetNextLine();
415 if (lineStr.Find(card) != wxNOT_FOUND) {
419 if (foundCard) currLine++;
420 if (foundCard && currLine == line) {
421 for (
int i = initPos; i < initPos + size; i++) {
422 lineStr[i] = value[i - initPos];
424 atpFile.RemoveLine(atpFile.GetCurrentLine());
425 atpFile.InsertLine(lineStr, atpFile.GetCurrentLine());
429 lineStr = atpFile.GetNextLine();
434bool EMTElement::AddConnectionToNode(wxTextFile& atpFile,
const wxString& node)
437 bool hasBusData =
false;
439 if (!m_parentList.empty()) {
440 if (m_parentList[0] !=
nullptr) {
441 Bus* bus =
static_cast<Bus*
>(m_parentList[0]);
442 busData = bus->GetElectricalData();
447 std::map<wxString, wxString> masks;
450 wxTextFile t94File(Paths::GetDataPath() +
"/atp/T94.atp");
451 if (t94File.Open()) {
454 for (
size_t i = 0; i < t94File.GetLineCount(); i++) {
455 wxString line = t94File.GetLine(i);
457 if (line.StartsWith(
"[[") && line.EndsWith(
"]]")) {
458 current = line.Mid(2, line.Length() - 4);
459 masks[current].Clear();
463 if (!current.IsEmpty())
464 masks[current] += line +
"\n";
469 for (
const auto& [key, value] : masks) {
470 masks[key].RemoveLast();
474 wxTextFile pspmodFile(Paths::GetDataPath() +
"/atp/PSPMOD.mod");
475 wxString pspmodString =
"";
476 if (pspmodFile.Open()) {
477 pspmodString = pspmodFile.GetFirstLine();
478 while (!pspmodFile.Eof())
480 pspmodString += pspmodFile.GetNextLine() +
"\n";
485 masks[
"BRANCH"].Replace(
"T94L_",
"PSPNC");
486 masks[
"BRANCH"].Replace(
"<STEP>", ATPField(m_data.pspStepSize, 6));
488 masks[
"SWITCH"].Replace(
"T94L_",
"PSPNC");
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())
499 if (lineStr.IsEmpty()) {
500 lineStr = atpFile.GetNextLine();
503 if (tolower(lineStr[0]) ==
'c') {
504 lineStr = atpFile.GetNextLine();
508 if (lineStr.Find(
"/SWITCH") != wxNOT_FOUND) {
509 switchCardPos = atpFile.GetCurrentLine() + 1;
511 if (lineStr.Find(
"/SOURCE") != wxNOT_FOUND) {
512 sourceCardPos = atpFile.GetCurrentLine() + 1;
514 if (lineStr.Find(
"/BRANCH") != wxNOT_FOUND) {
515 branchCardPos = atpFile.GetCurrentLine() + 1;
517 if (lineStr.Find(
"/MODELS") != wxNOT_FOUND) {
518 modelsCardPos = atpFile.GetCurrentLine() + 1;
520 if (lineStr.Find(
"/OUTPUT") != wxNOT_FOUND) {
521 outputCardPos = atpFile.GetCurrentLine() + 1;
523 if (lineStr.Find(
"BLANK BRANCH") != wxNOT_FOUND) {
524 blankBranchCardPos = atpFile.GetCurrentLine() + 1;
526 if (lineStr.Find(
"BLANK MODELS") != wxNOT_FOUND) {
527 blankModelsCardPos = atpFile.GetCurrentLine() + 1;
530 lineStr = atpFile.GetNextLine();
533 if (branchCardPos < 0)
return false;
534 if (outputCardPos < 0)
return false;
536 if (modelsCardPos < 0 && branchCardPos > 0) {
538 atpFile.InsertLine(
"/MODELS", branchCardPos - 1);
539 atpFile.InsertLine(
"MODELS", branchCardPos);
540 atpFile.InsertLine(
"ENDMODELS", branchCardPos + 1);
541 modelsCardPos = branchCardPos - 1;
543 blankBranchCardPos += 3;
544 if (blankModelsCardPos < 0)
546 atpFile.InsertLine(
"BLANK MODELS", blankBranchCardPos - 1);
547 blankModelsCardPos = blankBranchCardPos;
548 blankBranchCardPos++;
553 if (switchCardPos < 0 && outputCardPos > 0) {
554 atpFile.InsertLine(
"/SWITCH", outputCardPos - 1);
555 switchCardPos = outputCardPos;
557 blankBranchCardPos++;
560 if (sourceCardPos < 0 && outputCardPos > 0) {
561 atpFile.InsertLine(
"/SOURCE", outputCardPos - 1);
562 sourceCardPos = outputCardPos;
564 blankBranchCardPos++;
573 double voltage = std::abs(m_data.puVoltage) * m_data.baseVoltage * (std::sqrt(2) / std::sqrt(3));
579 for (
char i =
'A'; i <=
'C'; ++i) {
580 wxString tag = wxString::Format(
"<AMP.%c >", i);
581 masks[
"SOURCE"].Replace(tag, ATPField(voltage, 10));
583 tag = wxString::Format(
"<FREQ.%c >", i);
584 masks[
"SOURCE"].Replace(tag, ATPField(m_data.frequency, 10));
586 double angle = std::arg(m_data.puVoltage) * 180.0 / M_PI - 120.0 * (i -
'A');
587 tag = wxString::Format(
"<PHASE.%c >", i);
588 masks[
"SOURCE"].Replace(tag, ATPField(angle, 10));
591 lineStr = atpFile.GetFirstLine();
592 while (!atpFile.Eof())
594 if (lineStr.IsEmpty()) {
595 lineStr = atpFile.GetNextLine();
598 if (tolower(lineStr[0]) ==
'c') {
599 lineStr = atpFile.GetNextLine();
603 if (lineStr.Find(node) != wxNOT_FOUND) {
604 lineStr.Replace(node,
"PSPNC");
605 atpFile.RemoveLine(atpFile.GetCurrentLine());
606 atpFile.InsertLine(lineStr, atpFile.GetCurrentLine());
609 if (lineStr.Find(
"/MODELS") != wxNOT_FOUND)
610 atpFile.InsertLine(pspmodString, atpFile.GetCurrentLine() + 2);
612 if (lineStr.Find(
"/SWITCH") != wxNOT_FOUND)
613 atpFile.InsertLine(masks[
"SWITCH"], atpFile.GetCurrentLine() + 1);
615 if (lineStr.Find(
"/SOURCE") != wxNOT_FOUND)
616 atpFile.InsertLine(masks[
"SOURCE"], atpFile.GetCurrentLine() + 1);
618 if (lineStr.Find(
"/BRANCH") != wxNOT_FOUND)
619 atpFile.InsertLine(masks[
"BRANCH"], atpFile.GetCurrentLine() + 1);
621 lineStr = atpFile.GetNextLine();
624 lineStr = atpFile.GetFirstLine();
625 while (!atpFile.Eof())
627 lineStr += atpFile.GetNextLine() +
"\n";
680void EMTElement::MedianFilter(std::vector<double>& data)
684 const size_t n = data.size();
689 std::vector<double> extension(n + 4);
691 std::memcpy(extension.data() + 2, data.data(), n *
sizeof(
double));
693 extension[0] = data[1];
694 extension[1] = data[0];
695 extension[n + 2] = data[n - 1];
696 extension[n + 3] = data[n - 2];
698 DoMedianFilter(extension.data(), data,
static_cast<int>(n + 4));
733bool EMTElement::CalculateCurrent(wxString& errorMsg,
const Mode& mode,
const bool& saveRawData,
const bool& saveFFTData)
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);
742 double fundFreq = m_data.frequency;
743 double timeSim = (1.0 / fundFreq) *
static_cast<double>(m_data.cyclesToSS);
746 wxString atpFolder = m_data.atpPath.GetPath();
754 wxTextFile origFile(m_data.atpFile.GetFullPath());
756 fileName.SetPath(m_data.atpWorkFolder);
757 wxString name = fileName.GetFullPath();
758 wxTextFile copyFile(fileName.GetFullPath());
759 if (origFile.Open()) {
761 for (
size_t i = 0; i < origFile.GetLineCount(); i++) {
762 copyFile.AddLine(origFile.GetLine(i));
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));
776 AddConnectionToNode(copyFile, m_data.atpNodeName);
781 errorMsg = wxString::Format(_(
"Fail to open ATP file of the electromagnetic element \"%s\"."), m_data.name);
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);
797 if (!bridge.Connect(5000, 100))
799 if (!CheckLISFile(fileName, errorMsg))
801 errorMsg = _(
"Unable to connect to ATP.\n"
802 "Please make sure you are using an ATP version with PSP support.");
808 bridge.SetProcessId(pid);
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;
816 m_data.atpSampleData.clear();
819 while (bridge.WaitATP())
821 if (!saveRawData || !saveFFTData) {
826 data = bridge.GetSharedData();
830 if (data.stepCount > 0)
832 const double dt = data.atpStepsize;
833 const double t0 = data.t - (data.stepCount - 1) * dt;
836 m_data.atpSampleData.reserve(m_data.atpSampleData.size() + data.stepCount);
838 std::vector<double> ia, ib, ic;
840 if (m_data.useMedianFilter)
842 ia.reserve(data.stepCount);
843 ib.reserve(data.stepCount);
844 ic.reserve(data.stepCount);
846 for (uint32_t i = 0; i < data.stepCount; ++i)
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]);
858 for (uint32_t i = 0; i < data.stepCount; ++i)
860 const double t = t0 + i * dt;
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];
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]);
874 m_data.atpSampleData.push_back({
876 .current = { currentA, currentB, currentC },
877 .mag = {iaMag, ibMag, icMag},
878 .angle = {iaAng, ibAng, icAng}
886 if (data.terminate < 0)
888 CheckLISFile(fileName, errorMsg);
891 constexpr size_t phase = 0;
894 m_data.current = std::complex<double>(-data.phasor.Id[phase], -data.phasor.Iq[phase]);
897 wxDir dir(atpFolder);
898 if (dir.IsOpened()) {
900 bool cont = dir.GetFirst(&file, wxT(
"*.tmp"), wxDIR_FILES);
902 wxRemoveFile(atpFolder + wxT(
"/") + file);
903 cont = dir.GetNext(&file);
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)));
923 double fs = 1.0 / dataStepSize;
924 double df = fs /
static_cast<double>(n);
927 double rmder = std::abs(std::remainder(fundFreq, df));
928 size_t minRmderN = n;
933 df = fs /
static_cast<double>(n);
934 if (std::abs(std::remainder(fundFreq, df)) < rmder)
937 rmder = std::abs(std::remainder(fundFreq, df));
940 df = fs /
static_cast<double>(n);
946 double dtWindow = m_data.atpSampleData.back().t - m_data.atpSampleData[sampleCount - n].t;
948 fftw_complex* out = (fftw_complex*)fftw_malloc(
sizeof(fftw_complex) * (n / 2 + 1));
950 double* in = (
double*)fftw_malloc(
sizeof(
double) * n);
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];
958 fftw_plan p = fftw_plan_dft_r2c_1d(n, in, out, FFTW_ESTIMATE);
961 fftw_destroy_plan(p);
963 double ampCorrection = 2.0 /
static_cast<double>(n);
967 double fundMagnitude = 0.0;
969 for (
size_t i = 0; i < (n / 2 + 1); ++i) {
970 double freq =
static_cast<double>(i) * df;
972 if (std::remainder(freq, fundFreq)) {
973 fundIndex =
static_cast<int>(i);
976 std::hypot(out[i][0], out[i][1]) * ampCorrection;
983 errorMsg = wxString::Format(_(
"Fail to identify the fundamental frequency of the electromagnetic element \"%s\"."), m_data.name);
992 m_data.currHarmonics.clear();
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));
998 if (order > m_data.numMaxHarmonics)
1001 double magnitude = std::hypot(out[i][0], out[i][1]) * ampCorrection;
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);
1010 m_data.inFFTData.clear();
1011 m_data.outFFTData.clear();
1013 for (
size_t i = 0; i < n; ++i)
1015 m_data.inFFTData.emplace_back(m_data.atpSampleData[first + i].t, in[i]);
1018 for (
size_t i = 0; i < (n / 2 + 1); ++i)
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);
1031 errorMsg =
"Not implemented yet.\n EMTElement.cpp, line 772ish";
1034 fileName.SetFullName(fileName.GetName() + wxT(
".pl4"));
1035 wxFileInputStream pl4File(fileName.GetFullPath());
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]);
1056 if (m_data.useMedianFilter)
1057 MedianFilter(valueVec);
1060 double dataStepSize = timeVec[1] - timeVec[0];
1061 bool useRemainder =
false;
1062 size_t n = ceil(1.0 / (dataStepSize * fundFreq));
1065 if (n > valueVec.size()) {
1066 n = valueVec.size();
1067 useRemainder =
true;
1070 double fs = 1.0 / dataStepSize;
1071 double df = fs /
static_cast<double>(n);
1074 double rmder = abs(remainder(fundFreq, df));
1076 while (rmder > 1e-3) {
1078 df = fs /
static_cast<double>(n);
1080 if (abs(remainder(fundFreq, df)) < rmder) minRmderN = n;
1082 rmder = abs(remainder(fundFreq, df));
1086 df = fs /
static_cast<double>(n);
1092 double dtWindow = timeVec[timeVec.size() - 1] - timeVec[timeVec.size() - n];
1097 out = (fftw_complex*)fftw_malloc(
sizeof(fftw_complex) * (n / 2 + 1));
1098 in = (
double*)fftw_malloc(
sizeof(
double) * n);
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];
1105 p = fftw_plan_dft_r2c_1d(n, in, out, FFTW_ESTIMATE);
1107 fftw_destroy_plan(p);
1109 double ampCorrection = 2.0 /
static_cast<double>(n);
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)) {
1118 fundMagnitude = sqrt(out[i][0] * out[i][0] + out[i][1] * out[i][1]) * ampCorrection;
1122 if (fundIndex < 0) {
1123 errorMsg = wxString::Format(_(
"Fail to identify the fundamental frequency of the electromagnetic element \"%s\"."), m_data.name);
1128 m_data.currHarmonics.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));
1134 if (order > m_data.numMaxHarmonics)
break;
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)) {
1140 m_data.currHarmonics[order] = std::complex<double>(out[i][0], out[i][1]) * ampCorrection / sqrt(2.0);
1166 wxString atpErrorMsg = _(
"No error found.");
1168 fileName.SetFullName(fileName.GetName() + wxT(
".lis"));
1169 wxTextFile lisFile(fileName.GetFullPath());
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);
1175 if (lisFile.Open()) {
1176 wxString line = lisFile.GetFirstLine() +
"\n";
1177 bool foundError =
false;
1179 atpErrorMsg.Clear();
1180 while (!lisFile.Eof())
1182 if (line.Find(wxT(
"KILL ")) != wxNOT_FOUND) {
1186 if (line.Find(wxT(
"----------")) != wxNOT_FOUND) {
1189 if (foundError && lineCount == 1)
1190 atpErrorMsg += line +
" ";
1191 line = lisFile.GetNextLine();
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);
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);
1207void EMTElement::UpdateData(
const PropertiesData* properties,
bool updateVoltageBase)
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;
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);
1232 if (!nominalVoltage.empty()) {
1233 m_data.baseVoltage = GetValueFromUnit(nominalVoltage[0], nominalVoltageUnit[0]);
1237void EMTElement::DoMedianFilter(
double* extension, std::vector<double>& result,
const int& n)
1240 for (
int i = 2; i < n - 2; ++i)
1244 for (
int j = 0; j < 5; ++j)
1245 window[j] = extension[i - 2 + j];
1247 for (
int j = 0; j < 3; ++j)
1251 for (
int k = j + 1; k < 5; ++k)
1252 if (window[k] < window[min])
1255 std::swap(window[j], window[min]);
1258 result[i - 2] = window[2];
1263wxString EMTElement::ATPField(
double value,
size_t width)
1268 for (
int prec = 15; prec >= 0; --prec)
1270 wxString s = wxString::Format(
"%.*f", prec, value);
1271 s.Replace(
",",
".");
1273 while (s.Contains(
".") && s.EndsWith(
"0"))
1275 if (s.EndsWith(
"."))
1278 if (s.Length() <= width)
1286 for (
int prec = 15; prec >= 0; --prec)
1288 wxString s = wxString::Format(
"%.*E", prec, value);
1289 s.Replace(
",",
".");
1292 s.Replace(
"E",
".E");
1294 if (s.Length() <= width)
1297 if (best.empty() || s.Length() > best.Length())
1305 return wxString(
'*', width);
1307 best.Prepend(wxString(
' ', width - best.Length()));
1311bool EMTElement::CheckLISFile(wxFileName fileName, wxString& errorMsg)
const
1313 wxString atpErrorMsg = _(
"No error found.");
1315 fileName.SetFullName(fileName.GetName() + wxT(
".lis"));
1316 wxTextFile lisFile(fileName.GetFullPath());
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);
1322 if (lisFile.Open()) {
1323 wxString line = lisFile.GetFirstLine() +
"\n";
1324 bool foundError =
false;
1326 atpErrorMsg.Clear();
1327 while (!lisFile.Eof())
1329 if (line.Find(wxT(
"KILL ")) != wxNOT_FOUND) {
1333 if (line.Find(wxT(
"----------")) != wxNOT_FOUND) {
1336 if (foundError && lineCount == 1)
1337 atpErrorMsg += line +
" ";
1338 line = lisFile.GetNextLine();
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);
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);
Node for power elements. All others power elements are connected through this.
Element to connect ATP-EMTP.
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...
virtual void GeneralMenuItens(wxMenu &menu)
Insert general itens to context menu.
wxPoint2DDouble GetPosition() const
Get the element position.
double GetAngle() const
Get the element angle.
virtual wxPoint2DDouble RotateAtPosition(wxPoint2DDouble pointToRotate, double angle, bool degrees=true) const
Rotate a point as element position being the origin.
virtual void AddChild(Element *child)
Add a child to the child list.
bool SetOnline(bool online=true)
Set if the element is online or offline.
virtual void DrawDCCircle(wxPoint2DDouble position, double radius, int numSegments, wxGraphicsContext *gc) const
Draw a circle using device context.
Class to draw text on Graphics Context using wxWidgets.
virtual void Draw(wxPoint2DDouble position, wxGraphicsContext *gc, double angle=0.0, wxColour colour= *wxBLACK) const
Draw the text in wxGraphicsContext.
virtual void SetText(wxString text)
Set correctly a new text string.
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...