Power System Platform  2026w10a-beta
Loading...
Searching...
No Matches
TransferFunction.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 "TransferFunction.h"
19#include "../../forms/TransferFunctionForm.h"
20
21TransferFunction::TransferFunction(int id) : ControlElement(id)
22{
23 // Superscript unicode numbers
24 m_supNumber[0] = L'\u2070';
25 m_supNumber[1] = L'\u00B9';
26 m_supNumber[2] = L'\u00B2';
27 m_supNumber[3] = L'\u00B3';
28 m_supNumber[4] = L'\u2074';
29 m_supNumber[5] = L'\u2075';
30 m_supNumber[6] = L'\u2076';
31 m_supNumber[7] = L'\u2077';
32 m_supNumber[8] = L'\u2078';
33 m_supNumber[9] = L'\u2079';
34
35 m_numerator.clear();
36 m_numerator.push_back(1);
37 m_denominator.clear();
38 m_denominator.push_back(1);
39 m_denominator.push_back(1);
40 UpdateTFText();
41
42 Node* node1 = new Node(m_position + wxPoint2DDouble(-m_width / 2, 0), Node::NodeType::NODE_IN, m_borderSize);
43 node1->StartMove(m_position);
44 Node* node2 = new Node(m_position + wxPoint2DDouble(m_width / 2, 0), Node::NodeType::NODE_OUT, m_borderSize);
45 node2->SetAngle(180.0);
46 node2->StartMove(m_position);
47 m_nodeList.push_back(node1);
48 m_nodeList.push_back(node2);
49}
50
51TransferFunction::~TransferFunction()
52{
53 //if (m_glTextDen) delete m_glTextDen;
54 if (m_gcTextDen) delete m_gcTextDen;
55 if (m_gcTextNum) delete m_gcTextNum;
56 //if (m_glTextNum) delete m_glTextNum;
57 for (auto& node : m_nodeList) if (node) delete node;
58 m_nodeList.clear();
59}
60
61//void TransferFunction::Draw(wxPoint2DDouble translation, double scale) const
62//{
63// glLineWidth(1.0);
64// if (m_selected) {
65// glColor4dv(m_selectionColour.GetRGBA());
66// double borderSize = (m_borderSize * 2.0 + 1.0) / scale;
67// DrawRectangle(m_position, m_width + borderSize, m_height + borderSize);
68// }
69// glColor4d(1.0, 1.0, 1.0, 1.0);
70// DrawRectangle(m_position, m_width, m_height);
71// glColor4d(0.0, 0.0, 0.0, 1.0);
72// DrawRectangle(m_position, m_width, m_height, GL_LINE_LOOP);
73//
74// std::vector<wxPoint2DDouble> linePts;
75// linePts.push_back(wxPoint2DDouble(m_position.m_x - m_width / 2 + 5 + m_borderSize, m_position.m_y));
76// linePts.push_back(wxPoint2DDouble(m_position.m_x + m_width / 2 - 5 - m_borderSize, m_position.m_y));
77// DrawLine(linePts);
78//
79// DrawNodes();
80//
81// glColor4d(0.0, 0.0, 0.0, 1.0);
82// m_glTextNum->Draw(m_position + wxPoint2DDouble(0.0, -m_height / 4));
83// m_glTextDen->Draw(m_position + wxPoint2DDouble(0.0, m_height / 4));
84//}
85
86void TransferFunction::DrawDC(wxPoint2DDouble translation, double scale, wxGraphicsContext* gc) const
87{
88 if (m_selected) {
89 gc->SetPen(*wxTRANSPARENT_PEN);
90 gc->SetBrush(wxBrush(m_selectionColour));
91 double borderSize = (m_borderSize * 2.0 + 1.0) / scale;
92 gc->DrawRectangle(m_position.m_x - m_width / 2 - borderSize / 2, m_position.m_y - m_height / 2 - borderSize / 2, m_width + borderSize, m_height + borderSize);
93 }
94 gc->SetPen(*wxBLACK_PEN);
95 gc->SetBrush(*wxWHITE_BRUSH);
96 gc->DrawRectangle(m_position.m_x - m_width / 2, m_position.m_y - m_height / 2, m_width, m_height);
97
98 wxPoint2DDouble linePts[2];
99 linePts[0] = wxPoint2DDouble(m_position.m_x - m_width / 2 + 5 + m_borderSize, m_position.m_y);
100 linePts[1] = wxPoint2DDouble(m_position.m_x + m_width / 2 - 5 - m_borderSize, m_position.m_y);
101 gc->StrokeLines(2, linePts);
102
103 gc->SetPen(*wxTRANSPARENT_PEN);
104 gc->SetBrush(*wxBLACK_BRUSH);
105 DrawDCNodes(gc);
106
107 //glColor4d(0.0, 0.0, 0.0, 1.0);
108 m_gcTextNum->Draw(m_position + wxPoint2DDouble(-m_gcTextNum->GetWidth() / 2, -m_height / 4 - m_gcTextNum->GetHeight() / 2), gc);
109 m_gcTextDen->Draw(m_position + wxPoint2DDouble(-m_gcTextDen->GetWidth() / 2, m_height / 4 - m_gcTextDen->GetHeight() / 2), gc);
110}
111
112void TransferFunction::SetText(wxString numerator, wxString denominator)
113{
114 if (m_gcTextNum)
115 m_gcTextNum->SetText(numerator);
116 else
117 m_gcTextNum = new GCText(numerator);
118
119 if (m_gcTextDen)
120 m_gcTextDen->SetText(denominator);
121 else
122 m_gcTextDen = new GCText(denominator);
123
124 double nWidth = static_cast<double>(m_gcTextNum->GetWidth()) + 5 + m_borderSize;
125 double dWidth = static_cast<double>(m_gcTextDen->GetWidth()) + 5 + m_borderSize;
126
127 m_width = nWidth > dWidth ? nWidth : dWidth;
128 m_height = static_cast<double>(m_gcTextNum->GetHeight()) + static_cast<double>(m_gcTextDen->GetHeight()) + 2 * m_borderSize;
129 SetPosition(m_position); // Update rect properly.
130}
131
132wxString TransferFunction::GetSuperscriptNumber(int number)
133{
134 wxString strNumber = wxString::Format("%d", number);
135 wxString superscriptStr = "";
136 for (int i = 0; i < (int)strNumber.length(); ++i) {
137 wxString digitStr = strNumber[i];
138 long digit = 0;
139 digitStr.ToLong(&digit);
140 superscriptStr += wxString(m_supNumber[digit]);
141 }
142 return superscriptStr;
143}
144
145void TransferFunction::GetTFString(wxString& numerator, wxString& denominator)
146{
147 numerator = "";
148 denominator = "";
149 int index = static_cast<int>(m_numerator.size()) - 1;
150 for (auto it = m_numerator.begin(), itEnd = m_numerator.end(); it != itEnd; ++it) {
151 double value = *it;
152 if (value != 0.0) {
153 wxString signal;
154 if (index == static_cast<int>(m_numerator.size()) - 1) {
155 if (value >= 0.0)
156 signal += "";
157 else
158 signal += "-";
159 }
160 else {
161 if (value >= 0.0)
162 signal += "+ ";
163 else
164 signal += "- ";
165 }
166
167 if (index == 0) {
168 numerator += signal + StringFromDouble(std::abs(value), 0);
169 break;
170 }
171 else if (index == 1) {
172 if (value == 1.0) {
173 numerator += signal + "s";
174 }
175 else {
176 numerator += signal + StringFromDouble(std::abs(value), 0) + "s";
177 }
178 }
179 else {
180 if (value == 1.0) {
181 numerator += signal + "s" + GetSuperscriptNumber(index);
182 }
183 else {
184 numerator += signal + StringFromDouble(std::abs(value), 0) + "s" + GetSuperscriptNumber(index);
185 }
186 }
187 numerator += " ";
188 }
189 --index;
190 }
191
192 index = static_cast<int>(m_denominator.size()) - 1;
193 for (auto it = m_denominator.begin(), itEnd = m_denominator.end(); it != itEnd; ++it) {
194 double value = *it;
195 if (value != 0.0) {
196 wxString signal;
197 if (index == static_cast<int>(m_denominator.size()) - 1) {
198 if (value >= 0.0)
199 signal += "";
200 else
201 signal += "-";
202 }
203 else {
204 if (value >= 0.0)
205 signal += "+ ";
206 else
207 signal += "- ";
208 }
209
210 if (index == 0) {
211 denominator += signal + StringFromDouble(std::abs(value), 0);
212 break;
213 }
214 else if (index == 1) {
215 if (value == 1.0) {
216 denominator += signal + "s";
217 }
218 else {
219 denominator += signal + StringFromDouble(std::abs(value), 0) + "s";
220 }
221 }
222 else {
223 if (value == 1.0) {
224 denominator += signal + "s" + GetSuperscriptNumber(index);
225 }
226 else {
227 denominator += signal + StringFromDouble(std::abs(value), 0) + "s" + GetSuperscriptNumber(index);
228 }
229 }
230 denominator += " ";
231 }
232 --index;
233 }
234}
235
236void TransferFunction::UpdateTFText()
237{
238 wxString num, den;
239 GetTFString(num, den);
240 SetText(num, den);
241 if (m_nodeList.size() == 2) {
242 if (m_angle == 0.0) {
243 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
244 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
245 }
246 else if (m_angle == 90.0) {
247 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
248 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
249 }
250 else if (m_angle == 180.0) {
251 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
252 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
253 }
254 else if (m_angle == 270.0) {
255 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
256 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
257 }
258 }
259}
260
261bool TransferFunction::ShowForm(wxWindow* parent, Element* element)
262{
263 TransferFunctionForm tfForm(parent, this);
264 tfForm.CenterOnParent();
265 if (tfForm.ShowModal() == wxID_OK) {
266 return true;
267 }
268 return false;
269}
270
271void TransferFunction::Rotate(bool clockwise)
272{
273 if (clockwise)
274 m_angle += 90.0;
275 else
276 m_angle -= 90.0;
277 if (m_angle >= 360.0)
278 m_angle = 0.0;
279 else if (m_angle < 0)
280 m_angle = 270.0;
281
282 if (m_angle == 0.0) {
283 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
284 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
285 }
286 else if (m_angle == 90.0) {
287 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
288 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
289 }
290 else if (m_angle == 180.0) {
291 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
292 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
293 }
294 else if (m_angle == 270.0) {
295 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
296 m_nodeList[1]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
297 }
298
299 for (auto it = m_nodeList.begin(), itEnd = m_nodeList.end(); it != itEnd; ++it) {
300 Node* node = *it;
301 node->Rotate(clockwise);
302 }
303}
304
305void TransferFunction::CalculateSpaceState(int maxIteration, double error)
306{
307 m_maxIteration = maxIteration;
308 m_error = error;
309
310 int order = static_cast<int>(m_denominator.size());
311 std::vector<double> denominator = m_denominator;
312 std::vector<double> numerator;
313
314 //[Ref.] http://lpsa.swarthmore.edu/Representations/SysRepTransformations/TF2SS.html
315 int k = order;
316 for (int i = 0; i < order; i++) {
317 int numIndex = i - (order - static_cast<int>(m_numerator.size()));
318 if (numIndex < 0)
319 numerator.push_back(0.0);
320 else
321 numerator.push_back(m_numerator[numIndex]);
322 k--;
323 }
324
325 SpaceState ss;
326 for (int i = 0; i < (order - 1); i++) {
327 std::vector<double> lineA;
328 for (int j = 0; j < (order - 1); j++) {
329 if (j == i + 1)
330 lineA.push_back(1.0);
331 else
332 lineA.push_back(0.0);
333 }
334 ss.A.push_back(lineA);
335 ss.B.push_back(0.0);
336 ss.C.push_back(0.0);
337 }
338 for (int i = 0; i < order - 1; i++) {
339 ss.A[order - 2][i] = -(denominator[order - 1 - i] / denominator[0]);
340 ss.C[i] = numerator[order - 1 - i] / denominator[0] -
341 (denominator[order - 1 - i] / denominator[0]) * (numerator[0] / denominator[0]);
342 }
343 ss.B[order - 2] = 1.0;
344 ss.D = numerator[0] / denominator[0];
345
346 m_ss = ss;
347
348 // Reset state
349 m_x.clear();
350 m_dx.clear();
351
352 for (unsigned int i = 0; i < m_denominator.size(); ++i) {
353 m_x.push_back(0.0);
354 m_dx.push_back(0.0);
355 }
356}
357
358bool TransferFunction::Solve(double* input, double timeStep)
359{
360 if (!input) {
361 m_output = 0.0;
362 return true;
363 }
364
365 int order = static_cast<int>(m_ss.A.size());
366
367 std::vector<double> x;
368 std::vector<double> oldx;
369 std::vector<double> dx;
370 std::vector<double> olddx;
371 for (int i = 0; i < order; i++) {
372 x.push_back(m_x[i]);
373 oldx.push_back(m_x[i]);
374
375 dx.push_back(m_dx[i]);
376 olddx.push_back(m_dx[i]);
377 }
378
379 bool exit = false;
380 int iter = 0;
381 while (!exit) {
382 double xError = 0.0;
383 double dxError = 0.0;
384 for (int i = 0; i < order; i++) {
385 // Trapezoidal method
386 x[i] = m_x[i] + 0.5 * timeStep * (m_dx[i] + dx[i]);
387
388 if (std::abs(x[i] - oldx[i]) > xError) xError = std::abs(x[i] - oldx[i]);
389
390 oldx[i] = x[i];
391 }
392 for (int i = 0; i < order; i++) {
393 // x' = Ax + Bu
394 dx[i] = 0.0;
395 for (int j = 0; j < order; j++) dx[i] += m_ss.A[i][j] * x[j];
396 dx[i] += m_ss.B[i] * input[0];
397
398 if (std::abs(dx[i] - olddx[i]) > dxError) dxError = std::abs(dx[i] - olddx[i]);
399
400 olddx[i] = dx[i];
401 }
402 if (std::max(xError, dxError) < m_error) exit = true;
403
404 iter++;
405 if (iter >= m_maxIteration) return false;
406 }
407
408 m_output = 0.0;
409 for (int i = 0; i < order; i++) {
410 m_output += m_ss.C[i] * x[i];
411 m_x[i] = x[i];
412 m_dx[i] = dx[i];
413 }
414
415 m_output += m_ss.D * input[0];
416
417 return true;
418}
419
421{
422 TransferFunction* copy = new TransferFunction(m_elementID);
423 *copy = *this;
424 copy->m_gcTextNum = m_gcTextNum->GetCopy();
425 copy->m_gcTextDen = m_gcTextDen->GetCopy();
426 return copy;
427}
428
430{
431 UpdateTFText();
432 //if (!m_glTextDen->IsTextureOK()) return false;
433 //if (!m_glTextNum->IsTextureOK()) return false;
434 return true;
435}
436
437rapidxml::xml_node<>* TransferFunction::SaveElement(rapidxml::xml_document<>& doc,
438 rapidxml::xml_node<>* elementListNode)
439{
440 auto elementNode = XMLParser::AppendNode(doc, elementListNode, "TransferFunction");
441 XMLParser::SetNodeAttribute(doc, elementNode, "ID", m_elementID);
442
443 SaveCADProperties(doc, elementNode);
444 SaveControlNodes(doc, elementNode);
445
446 // Element properties
447 auto numeratorNode = XMLParser::AppendNode(doc, elementNode, "Numerator");
448 for (unsigned int i = 0; i < m_numerator.size(); ++i) {
449 auto value = XMLParser::AppendNode(doc, numeratorNode, "Value");
450 XMLParser::SetNodeValue(doc, value, m_numerator[i]);
451 }
452 auto denominatorNode = XMLParser::AppendNode(doc, elementNode, "Denominator");
453 for (unsigned int i = 0; i < m_denominator.size(); ++i) {
454 auto value = XMLParser::AppendNode(doc, denominatorNode, "Value");
455 XMLParser::SetNodeValue(doc, value, m_denominator[i]);
456 }
457
458 return elementNode;
459}
460
461bool TransferFunction::OpenElement(rapidxml::xml_node<>* elementNode)
462{
463 if (!OpenCADProperties(elementNode)) return false;
464 if (!OpenControlNodes(elementNode)) return false;
465
466 // Element properties
467 std::vector<double> numerator, denominator;
468 m_numerator.clear();
469 m_denominator.clear();
470 auto numeratorNode = elementNode->first_node("Numerator");
471 auto nValue = numeratorNode->first_node("Value");
472 while (nValue) {
473 double value = 0.0;
474 wxString(nValue->value()).ToCDouble(&value);
475 m_numerator.push_back(value);
476 nValue = nValue->next_sibling("Value");
477 }
478 auto denominatorNode = elementNode->first_node("Denominator");
479 auto dValue = denominatorNode->first_node("Value");
480 while (dValue) {
481 double value = 0.0;
482 wxString(dValue->value()).ToCDouble(&value);
483 m_denominator.push_back(value);
484 dValue = dValue->next_sibling("Value");
485 }
486 StartMove(m_position);
487 UpdateTFText();
488
489 return true;
490}
virtual void StartMove(wxPoint2DDouble position)
Update the element attributes related to the movement.
Base class of all elements of the program. This class is responsible for manage graphical and his dat...
Definition Element.h:112
void SetPosition(const wxPoint2DDouble position)
Set the element position and update the rectangle.
Definition Element.cpp:27
static wxString StringFromDouble(double value, int minDecimal=1, int maxDecimals=13)
Convert a double value to string.
Definition Element.cpp:533
Class to draw text on Graphics Context using wxWidgets.
Definition GCText.h:32
virtual GCText * GetCopy()
Get a deep text copy.
Definition GCText.cpp:161
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
Node of a control element. This class manages the user interaction with the connection and control el...
Form to edit the transfer function control data.
Calculates the time response by a frequency domain transfer function.
virtual void DrawDC(wxPoint2DDouble translation, double scale, wxGraphicsContext *gc) const
Draw the element using GDI+.
virtual Element * GetCopy()
Get a the element copy.
virtual bool UpdateText()
Update the OpenGL text in the element (if present).
virtual void CalculateSpaceState(int maxIteration=100, double error=1e-3)
Convert the transfer function to space state on controllable canonical form (CCF).
virtual void Rotate(bool clockwise=true)
Rotate the element.
virtual bool Solve(double *input, double timeStep)
Calculates the time response by the space state form of transfer function.
virtual bool ShowForm(wxWindow *parent, Element *element)
Show element data form.