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