Power System Platform  2026w10a-beta
Loading...
Searching...
No Matches
MathExpression.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 "ConnectionLine.h"
19#include "MathExpression.h"
20#include "../../forms/MathExpressionForm.h"
21#include <wx/pen.h>
22#include <wx/brush.h>
23
24MathExpression::MathExpression(int id) : ControlElement(id)
25{
26 m_variablesVector.push_back("x");
27 m_variablesVector.push_back("y");
28
29 for (unsigned int i = 0; i < m_variablesVector.size(); ++i) {
30 m_gcTextInputVector.push_back(new GCText(m_variablesVector[i]));
31 }
32
33 // Symbol
34 //m_symbol.SetFontSize(12);
35 //m_symbol.SetFontWeight(wxFONTWEIGHT_BOLD);
36 //m_symbol.SetFontStyle(wxFONTSTYLE_ITALIC);
37 //m_symbol.SetFontFamily(wxFONTFAMILY_ROMAN);
38 m_symbol.SetFont(wxFont(12, wxFONTFAMILY_ROMAN, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_BOLD));
39 m_symbol.SetText("f(x)");
40 m_symbolSize = wxSize(m_symbol.GetWidth(), m_symbol.GetHeight());
41
42 CalculateBlockSize(static_cast<double>(m_variablesVector.size()));
43
44 for (unsigned int i = 0; i < m_variablesVector.size(); ++i) {
45 wxPoint2DDouble nodePosition(0, 0);
46 if (m_variablesVector.size() == 1) {
47 nodePosition = m_position + wxPoint2DDouble(-m_width / 2, 0);
48 }
49 else {
50 nodePosition = m_position + wxPoint2DDouble(-m_width / 2, 9 + 18 * i - m_height / 2);
51 }
52 Node* nodeIn = new Node(nodePosition, Node::NodeType::NODE_IN, m_borderSize);
53 nodeIn->StartMove(m_position);
54 m_nodeList.push_back(nodeIn);
55 }
56 Node* nodeOut = new Node(m_position + wxPoint2DDouble(m_width / 2, 0), Node::NodeType::NODE_OUT, m_borderSize);
57 nodeOut->SetAngle(180.0);
58 nodeOut->StartMove(m_position);
59 m_nodeList.push_back(nodeOut);
60
61 UpdatePoints();
62}
63
64MathExpression::~MathExpression()
65{
66 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) { delete* it; }
67 m_gcTextInputVector.clear();
68 for (auto& node : m_nodeList) if (node) delete node;
69 m_nodeList.clear();
70}
71
72//void MathExpression::Draw(wxPoint2DDouble translation, double scale) const
73//{
74// glLineWidth(1.0);
75// if (m_selected) {
76// glColor4dv(m_selectionColour.GetRGBA());
77// double borderSize = (m_borderSize * 2.0 + 1.0) / scale;
78// DrawRectangle(m_position, m_width + borderSize, m_height + borderSize);
79// }
80// glColor4d(1.0, 1.0, 1.0, 1.0);
81// DrawRectangle(m_position, m_width, m_height);
82// glColor4d(0.0, 0.0, 0.0, 1.0);
83// DrawRectangle(m_position, m_width, m_height, GL_LINE_LOOP);
84//
85// // Plot input variables and symbol.
86// glColor4d(0.0, 0.3, 1.0, 1.0);
87// if (m_angle == 0.0) {
88// m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() -
89// wxPoint2DDouble(m_symbolSize.GetWidth() / 2.0 + 6.0, 0));
90// glColor4d(0.0, 0.0, 0.0, 1.0);
91// for (unsigned int i = 0; i < m_glTextInputVector.size(); ++i) {
92// m_glTextInputVector[i]->Draw(m_nodeList[i]->GetPosition() +
93// wxPoint2DDouble(m_glTextInputVector[i]->GetWidth() / 2.0 + 6, 0));
94// }
95// }
96// else if (m_angle == 90.0) {
97// m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() -
98// wxPoint2DDouble(0, m_symbolSize.GetHeight() / 2.0 + 6.0));
99// glColor4d(0.0, 0.0, 0.0, 1.0);
100// for (unsigned int i = 0; i < m_glTextInputVector.size(); ++i) {
101// m_glTextInputVector[i]->Draw(
102// m_nodeList[i]->GetPosition() +
103// wxPoint2DDouble(m_glTextInputVector[i]->GetWidth() / 2.0 + m_glTextInputVector[i]->GetHeight() / 2,
104// 15),
105// 90);
106// }
107// }
108// else if (m_angle == 180.0) {
109// m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() +
110// wxPoint2DDouble(m_symbolSize.GetWidth() / 2.0 + 6.0, 0));
111// glColor4d(0.0, 0.0, 0.0, 1.0);
112// for (unsigned int i = 0; i < m_glTextInputVector.size(); ++i) {
113// m_glTextInputVector[i]->Draw(m_nodeList[i]->GetPosition() -
114// wxPoint2DDouble(m_glTextInputVector[i]->GetWidth() / 2.0 + 6, 0));
115// }
116// }
117// else if (m_angle == 270.0) {
118// m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() +
119// wxPoint2DDouble(0, m_symbolSize.GetHeight() / 2.0 + 6.0));
120// glColor4d(0.0, 0.0, 0.0, 1.0);
121// for (unsigned int i = 0; i < m_glTextInputVector.size(); ++i) {
122// m_glTextInputVector[i]->Draw(
123// m_nodeList[i]->GetPosition() + wxPoint2DDouble(m_glTextInputVector[i]->GetWidth() / 2.0 + m_glTextInputVector[i]->GetHeight() / 2.0,
124// -m_glTextInputVector[i]->GetWidth()),
125// 90);
126// }
127// }
128//
129// glColor4d(0.0, 0.0, 0.0, 1.0);
130// DrawNodes();
131//}
132
133void MathExpression::DrawDC(wxPoint2DDouble translation, double scale, wxGraphicsContext* gc) const
134{
135 double pi = 3.1415926535897932;
136 if (m_selected) {
137 gc->SetPen(*wxTRANSPARENT_PEN);
138 gc->SetBrush(wxBrush(m_selectionColour));
139 double borderSize = (m_borderSize * 2.0 + 1.0) / scale;
140 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);
141 }
142 gc->SetPen(wxPen(wxColour(0, 0, 0, 255), 1));
143 gc->SetBrush(wxBrush(wxColour(255, 255, 255, 255)));
144 gc->DrawRectangle(m_position.m_x - m_width / 2, m_position.m_y - m_height / 2, m_width, m_height);
145
146 // Plot input variables and symbol.
147 if (m_angle == 0.0) {
148 double w = static_cast<double>(m_symbolSize.GetWidth());
149 double h = static_cast<double>(m_symbolSize.GetHeight());
150 m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() - wxPoint2DDouble(w + 6.0, h / 2.0), gc, 0, wxColour(0, 77, 255, 255));
151 for (unsigned int i = 0; i < m_gcTextInputVector.size(); ++i) {
152 w = static_cast<double>(m_gcTextInputVector[i]->GetWidth());
153 h = static_cast<double>(m_gcTextInputVector[i]->GetHeight());
154 m_gcTextInputVector[i]->Draw(m_nodeList[i]->GetPosition() + wxPoint2DDouble(6.0, -h / 2.0), gc);
155 }
156 }
157 else if (m_angle == 90.0) {
158 double w = static_cast<double>(m_symbolSize.GetWidth());
159 double h = static_cast<double>(m_symbolSize.GetHeight());
160 m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() - wxPoint2DDouble(w / 2, h + 6.0), gc, 0, wxColour(0, 77, 255, 255));
161 for (unsigned int i = 0; i < m_gcTextInputVector.size(); ++i) {
162 w = static_cast<double>(m_gcTextInputVector[i]->GetWidth());
163 h = static_cast<double>(m_gcTextInputVector[i]->GetHeight());
164 m_gcTextInputVector[i]->Draw(m_nodeList[i]->GetPosition() + wxPoint2DDouble(-h / 2, w + 6.0), gc, pi / 2.0);
165 }
166 }
167 else if (m_angle == 180.0) {
168 double w = static_cast<double>(m_symbolSize.GetWidth());
169 double h = static_cast<double>(m_symbolSize.GetHeight());
170 m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() + wxPoint2DDouble(6.0, -h / 2.0), gc, 0, wxColour(0, 77, 255, 255));
171 for (unsigned int i = 0; i < m_gcTextInputVector.size(); ++i) {
172 w = static_cast<double>(m_gcTextInputVector[i]->GetWidth());
173 h = static_cast<double>(m_gcTextInputVector[i]->GetHeight());
174 m_gcTextInputVector[i]->Draw(m_nodeList[i]->GetPosition() - wxPoint2DDouble(w + 6.0, h / 2.0), gc);
175 }
176 }
177 else if (m_angle == 270.0) {
178 double w = static_cast<double>(m_symbolSize.GetWidth());
179 double h = static_cast<double>(m_symbolSize.GetHeight());
180 m_symbol.Draw(m_nodeList[m_nodeList.size() - 1]->GetPosition() + wxPoint2DDouble(-w / 2, 6.0), gc, 0, wxColour(0, 77, 255, 255));
181 for (unsigned int i = 0; i < m_gcTextInputVector.size(); ++i) {
182 w = static_cast<double>(m_gcTextInputVector[i]->GetWidth());
183 h = static_cast<double>(m_gcTextInputVector[i]->GetHeight());
184 m_gcTextInputVector[i]->Draw(m_nodeList[i]->GetPosition() - wxPoint2DDouble(-h / 2, w + 6.0), gc, -pi / 2.0);
185 }
186 }
187
188 gc->SetPen(*wxTRANSPARENT_PEN);
189 gc->SetBrush(*wxBLACK_BRUSH);
190 DrawDCNodes(gc);
191}
192
193bool MathExpression::ShowForm(wxWindow* parent, Element* element)
194{
195 MathExpressionForm mathExprForm(parent, this);
196 mathExprForm.CenterOnParent();
197 if (mathExprForm.ShowModal() == wxID_OK) {
198 return true;
199 }
200 return false;
201}
202
203void MathExpression::Rotate(bool clockwise)
204{
205 if (clockwise)
206 m_angle += 90.0;
207 else
208 m_angle -= 90.0;
209 if (m_angle >= 360.0)
210 m_angle = 0.0;
211 else if (m_angle < 0)
212 m_angle = 270.0;
213
214 UpdatePoints();
215
216 for (auto it = m_nodeList.begin(), itEnd = m_nodeList.end(); it != itEnd; ++it) {
217 Node* node = *it;
218 node->Rotate(clockwise);
219 }
220}
221
222bool MathExpression::Solve(double* input, double timeStep)
223{
224 if (!input) {
225 m_output = 0.0;
226 return true;
227 }
228 // Get the input vector from connection lines (can't use default (one) input argument)
229 m_inputValues[0] = input[1]; // Current time
230 m_inputValues[1] = timeStep;
231 m_inputValues[2] = input[2]; // Switch status
232 int i = 3;
233 for (auto itN = m_nodeList.begin(), itNEnd = m_nodeList.end(); itN != itNEnd; ++itN) {
234 Node* node = *itN;
235 if (node->GetNodeType() != Node::NodeType::NODE_OUT) {
236 if (!node->IsConnected()) {
237 m_inputValues[i] = 0.0; // Node not connected means zero value as input.
238 }
239 else {
240 for (auto itC = m_childList.begin(), itCEnd = m_childList.end(); itC != itCEnd; ++itC) {
241 ConnectionLine* cLine = static_cast<ConnectionLine*>(*itC);
242 auto nodeList = cLine->GetNodeList();
243 for (auto itCN = nodeList.begin(), itCNEnd = nodeList.end(); itCN != itCNEnd; ++itCN) {
244 Node* childNode = *itCN;
245 if (childNode == node) {
246 m_inputValues[i] = cLine->GetValue();
247 break;
248 }
249 }
250 }
251 }
252 ++i;
253 }
254 }
255
256 // Solve the math expression using fparser
257 double result = m_fparser.Eval(m_inputValues);
258 if (m_fparser.EvalError() != 0) return false;
259 m_output = result;
260 return true;
261}
262
264{
265 MathExpression* copy = new MathExpression(m_elementID);
266 *copy = *this;
267 copy->m_gcTextInputVector.clear();
268 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) {
269 copy->m_gcTextInputVector.push_back((*it)->GetCopy());
270 }
271 return copy;
272}
273
274void MathExpression::UpdatePoints()
275{
276 CalculateBlockSize(static_cast<double>(m_nodeList.size()) - 1.0);
277
278 if (m_nodeList.size() == 2) // Only one input (and the output).
279 {
280 if (m_angle == 0.0)
281 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
282 else if (m_angle == 90.0)
283 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
284 else if (m_angle == 180.0)
285 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
286 else if (m_angle == 270.0)
287 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
288 }
289 else {
290 for (unsigned int i = 0; i < m_nodeList.size() - 1; ++i) {
291 if (m_angle == 0.0)
292 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 9 + 18 * i - m_height / 2));
293 else if (m_angle == 90.0)
294 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(m_width / 2 - 9 - 18 * i, -m_height / 2));
295 else if (m_angle == 180.0)
296 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, m_height / 2 - 9 - 18 * i));
297 else if (m_angle == 270.0)
298 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(9 + 18 * i - m_width / 2, m_height / 2));
299 }
300 }
301 if (m_angle == 0.0)
302 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
303 else if (m_angle == 90.0)
304 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
305 else if (m_angle == 180.0)
306 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
307 else if (m_angle == 270.0)
308 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
309
310 SetPosition(m_position); // Update rect.
311}
312
313void MathExpression::AddInNode()
314{
315 Node* newNode = new Node(wxPoint2DDouble(0, 0), Node::NodeType::NODE_IN, m_borderSize);
316 newNode->SetAngle(m_angle);
317 m_nodeList.insert(m_nodeList.end() - 1, newNode);
318}
319
320void MathExpression::RemoveInNode()
321{
322 Node* nodeToRemove = *(m_nodeList.end() - 2);
323 bool foundChild = false;
324 for (auto it = m_childList.begin(), itEnd = m_childList.end(); it != itEnd; ++it) {
325 ControlElement* child = static_cast<ControlElement*>(*it);
326 auto childNodeList = child->GetNodeList();
327 for (auto itN = childNodeList.begin(), itEndN = childNodeList.end(); itN != itEndN; ++itN) {
328 Node* node = *itN;
329 if (node == nodeToRemove) {
330 child->RemoveParent(this);
331 RemoveChild(child);
332 foundChild = true;
333 break;
334 }
335 }
336 if (foundChild) break;
337 }
338 m_nodeList.erase(m_nodeList.end() - 2);
339}
340
341void MathExpression::CalculateBlockSize(double numInNodes)
342{
343 m_maxSringSize = 0;
344 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) {
345 if (m_maxSringSize < (*it)->GetWidth()) m_maxSringSize = (*it)->GetWidth();
346 }
347 if (m_angle == 0.0 || m_angle == 180.0) {
348 m_height = 18.0 * numInNodes;
349 if (m_height < m_minimumSize) m_height = m_minimumSize; // minimum height
350 m_width = m_maxSringSize + m_symbolSize.GetWidth() + 18;
351 }
352 else {
353 m_width = 18.0 * numInNodes;
354 if (m_width < m_minimumSize) m_width = m_minimumSize; // minimum width
355 m_height = m_maxSringSize + m_symbolSize.GetHeight() + 18;
356 }
357}
358
360{
361 bool isTextureOK = true;
362 m_symbol.SetText(m_symbol.GetText());
363 //if (!m_symbol.IsTextureOK()) isTextureOK = false;
364 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) {
365 (*it)->SetText((*it)->GetText());
366 //if (!(*it)->IsTextureOK()) isTextureOK = false;
367 }
368 return isTextureOK;
369}
370
371void MathExpression::SetVariables(std::vector<wxString> variablesVector)
372{
373 m_variablesVector = variablesVector;
374 // Clear old glTextVector
375 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) { delete* it; }
376 m_gcTextInputVector.clear();
377
378 for (auto it = m_variablesVector.begin(), itEnd = m_variablesVector.end(); it != itEnd; ++it)
379 m_gcTextInputVector.push_back(new GCText(*(it)));
380}
381
382bool MathExpression::Initialize()
383{
384 m_variables = "time,step,switch,";
385 for (auto it = m_variablesVector.begin(), itEnd = m_variablesVector.end(); it != itEnd; ++it)
386 m_variables += *(it)+",";
387 m_variables.RemoveLast();
388
389 // Set locale to ENGLISH_US to avoid parser error in numbers (eg. comma).
390 int currentLang = wxLocale::GetSystemLanguage();
391 wxLocale newLocale(wxLANGUAGE_ENGLISH_US);
392 int parserRes = m_fparser.Parse(static_cast<std::string>(m_mathExpression), static_cast<std::string>(m_variables));
393 if (parserRes != -1) return false; // Parse error.
394 wxLocale oldLocale(currentLang); // Return to current language.
395
396 if (m_inputValues) delete m_inputValues;
397 m_inputValues = new double[m_variablesVector.size() + 3]; // Custom variables + time + step + switch
398
399 // Optimize only once to gain performance.
400 m_fparser.Optimize();
401
402 m_solved = false;
403 m_output = 0.0;
404 return true;
405}
406
407rapidxml::xml_node<>* MathExpression::SaveElement(rapidxml::xml_document<>& doc, rapidxml::xml_node<>* elementListNode)
408{
409 auto elementNode = XMLParser::AppendNode(doc, elementListNode, "MathExpr");
410 XMLParser::SetNodeAttribute(doc, elementNode, "ID", m_elementID);
411
412 SaveCADProperties(doc, elementNode);
413 SaveControlNodes(doc, elementNode);
414
415 // Element properties
416 auto variablesNode = XMLParser::AppendNode(doc, elementNode, "VariableList");
417 for (unsigned int i = 0; i < m_variablesVector.size(); ++i) {
418 auto variable = XMLParser::AppendNode(doc, variablesNode, "Variable");
419 XMLParser::SetNodeValue(doc, variable, m_variablesVector[i]);
420 }
421 auto mathExprValue = XMLParser::AppendNode(doc, elementNode, "MathExprValue");
422 XMLParser::SetNodeValue(doc, mathExprValue, m_mathExpression);
423
424 return elementNode;
425}
426
427bool MathExpression::OpenElement(rapidxml::xml_node<>* elementNode)
428{
429 if (!OpenCADProperties(elementNode)) return false;
430 if (!OpenControlNodes(elementNode)) return false;
431
432 // Element properties
433 std::vector<wxString> variables;
434 auto variablesNode = elementNode->first_node("VariableList");
435 auto variable = variablesNode->first_node("Variable");
436 while (variable) {
437 variables.push_back(variable->value());
438 variable = variable->next_sibling("Variable");
439 }
440 SetVariables(variables);
441
442 auto mathExprValueNode = elementNode->first_node("MathExprValue");
443 m_mathExpression = mathExprValueNode->value();
444
445 // Init opened properties
446 StartMove(m_position);
447 UpdatePoints();
448
449 return true;
450}
Connection between two control elements or other connection line and an element.
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
wxPoint2DDouble GetPosition() const
Get the element position.
Definition Element.h:186
virtual void RemoveChild(Element *child)
Remove a child from the list.
Definition Element.cpp:567
void SetPosition(const wxPoint2DDouble position)
Set the element position and update the rectangle.
Definition Element.cpp:27
virtual void RemoveParent(Element *parent)
Remove a parent.
Definition Element.h:371
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
A generic math expression block that can perform math and conditional operations with the inputs.
virtual bool ShowForm(wxWindow *parent, Element *element)
Show element data form.
virtual Element * GetCopy()
Get a the element copy.
virtual bool UpdateText()
Update the OpenGL text in the element (if present).
virtual void Rotate(bool clockwise=true)
Rotate the element.
virtual void DrawDC(wxPoint2DDouble translation, double scale, wxGraphicsContext *gc) const
Draw the element using GDI+.
Node of a control element. This class manages the user interaction with the connection and control el...