Power System Platform  2026w11a-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, wxWindow* workspace)
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(*this);
423 copy->m_gcTextNum = m_gcTextNum ? m_gcTextNum->GetCopy() : nullptr;
424 copy->m_gcTextDen = m_gcTextDen ? m_gcTextDen->GetCopy() : nullptr;
425 return copy;
426}
427
429{
430 UpdateTFText();
431 //if (!m_glTextDen->IsTextureOK()) return false;
432 //if (!m_glTextNum->IsTextureOK()) return false;
433 return true;
434}
435
436rapidxml::xml_node<>* TransferFunction::SaveElement(rapidxml::xml_document<>& doc,
437 rapidxml::xml_node<>* elementListNode)
438{
439 auto elementNode = XMLParser::AppendNode(doc, elementListNode, "TransferFunction");
440 XMLParser::SetNodeAttribute(doc, elementNode, "ID", m_elementID);
441
442 SaveCADProperties(doc, elementNode);
443 SaveControlNodes(doc, elementNode);
444
445 // Element properties
446 auto numeratorNode = XMLParser::AppendNode(doc, elementNode, "Numerator");
447 for (unsigned int i = 0; i < m_numerator.size(); ++i) {
448 auto value = XMLParser::AppendNode(doc, numeratorNode, "Value");
449 XMLParser::SetNodeValue(doc, value, m_numerator[i]);
450 }
451 auto denominatorNode = XMLParser::AppendNode(doc, elementNode, "Denominator");
452 for (unsigned int i = 0; i < m_denominator.size(); ++i) {
453 auto value = XMLParser::AppendNode(doc, denominatorNode, "Value");
454 XMLParser::SetNodeValue(doc, value, m_denominator[i]);
455 }
456
457 return elementNode;
458}
459
460bool TransferFunction::OpenElement(rapidxml::xml_node<>* elementNode)
461{
462 if (!OpenCADProperties(elementNode)) return false;
463 if (!OpenControlNodes(elementNode)) return false;
464
465 // Element properties
466 std::vector<double> numerator, denominator;
467 m_numerator.clear();
468 m_denominator.clear();
469 auto numeratorNode = elementNode->first_node("Numerator");
470 auto nValue = numeratorNode->first_node("Value");
471 while (nValue) {
472 double value = 0.0;
473 wxString(nValue->value()).ToCDouble(&value);
474 m_numerator.push_back(value);
475 nValue = nValue->next_sibling("Value");
476 }
477 auto denominatorNode = elementNode->first_node("Denominator");
478 auto dValue = denominatorNode->first_node("Value");
479 while (dValue) {
480 double value = 0.0;
481 wxString(dValue->value()).ToCDouble(&value);
482 m_denominator.push_back(value);
483 dValue = dValue->next_sibling("Value");
484 }
485 StartMove(m_position);
486 UpdateTFText();
487
488 return true;
489}
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:28
static wxString StringFromDouble(double value, int minDecimal=1, int maxDecimals=13)
Convert a double value to string.
Definition Element.cpp:461
Class to draw text on Graphics Context using wxWidgets.
Definition GCText.h:32
virtual GCText * GetCopy()
Get a deep text copy.
Definition GCText.cpp:167
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 bool ShowForm(wxWindow *parent, Element *element, wxWindow *workspace=nullptr)
Show element data form.
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.