Power System Platform  2026w11a-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, wxWindow* workspace)
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(*this);
266 copy->m_gcTextInputVector.clear();
267 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) {
268 copy->m_gcTextInputVector.push_back((*it)->GetCopy());
269 }
270 return copy;
271}
272
273void MathExpression::UpdatePoints()
274{
275 CalculateBlockSize(static_cast<double>(m_nodeList.size()) - 1.0);
276
277 if (m_nodeList.size() == 2) // Only one input (and the output).
278 {
279 if (m_angle == 0.0)
280 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
281 else if (m_angle == 90.0)
282 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
283 else if (m_angle == 180.0)
284 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
285 else if (m_angle == 270.0)
286 m_nodeList[0]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
287 }
288 else {
289 for (unsigned int i = 0; i < m_nodeList.size() - 1; ++i) {
290 if (m_angle == 0.0)
291 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 9 + 18 * i - m_height / 2));
292 else if (m_angle == 90.0)
293 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(m_width / 2 - 9 - 18 * i, -m_height / 2));
294 else if (m_angle == 180.0)
295 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, m_height / 2 - 9 - 18 * i));
296 else if (m_angle == 270.0)
297 m_nodeList[i]->SetPosition(m_position + wxPoint2DDouble(9 + 18 * i - m_width / 2, m_height / 2));
298 }
299 }
300 if (m_angle == 0.0)
301 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(m_width / 2, 0));
302 else if (m_angle == 90.0)
303 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(0, m_height / 2));
304 else if (m_angle == 180.0)
305 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(-m_width / 2, 0));
306 else if (m_angle == 270.0)
307 m_nodeList[m_nodeList.size() - 1]->SetPosition(m_position + wxPoint2DDouble(0, -m_height / 2));
308
309 SetPosition(m_position); // Update rect.
310}
311
312void MathExpression::AddInNode()
313{
314 Node* newNode = new Node(wxPoint2DDouble(0, 0), Node::NodeType::NODE_IN, m_borderSize);
315 newNode->SetAngle(m_angle);
316 m_nodeList.insert(m_nodeList.end() - 1, newNode);
317}
318
319void MathExpression::RemoveInNode()
320{
321 Node* nodeToRemove = *(m_nodeList.end() - 2);
322 bool foundChild = false;
323 for (auto it = m_childList.begin(), itEnd = m_childList.end(); it != itEnd; ++it) {
324 ControlElement* child = static_cast<ControlElement*>(*it);
325 auto childNodeList = child->GetNodeList();
326 for (auto itN = childNodeList.begin(), itEndN = childNodeList.end(); itN != itEndN; ++itN) {
327 Node* node = *itN;
328 if (node == nodeToRemove) {
329 child->RemoveParent(this);
330 RemoveChild(child);
331 foundChild = true;
332 break;
333 }
334 }
335 if (foundChild) break;
336 }
337 m_nodeList.erase(m_nodeList.end() - 2);
338}
339
340void MathExpression::CalculateBlockSize(double numInNodes)
341{
342 m_maxSringSize = 0;
343 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) {
344 if (m_maxSringSize < (*it)->GetWidth()) m_maxSringSize = (*it)->GetWidth();
345 }
346 if (m_angle == 0.0 || m_angle == 180.0) {
347 m_height = 18.0 * numInNodes;
348 if (m_height < m_minimumSize) m_height = m_minimumSize; // minimum height
349 m_width = m_maxSringSize + m_symbolSize.GetWidth() + 18;
350 }
351 else {
352 m_width = 18.0 * numInNodes;
353 if (m_width < m_minimumSize) m_width = m_minimumSize; // minimum width
354 m_height = m_maxSringSize + m_symbolSize.GetHeight() + 18;
355 }
356}
357
359{
360 bool isTextureOK = true;
361 m_symbol.SetText(m_symbol.GetText());
362 //if (!m_symbol.IsTextureOK()) isTextureOK = false;
363 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) {
364 (*it)->SetText((*it)->GetText());
365 //if (!(*it)->IsTextureOK()) isTextureOK = false;
366 }
367 return isTextureOK;
368}
369
370void MathExpression::SetVariables(std::vector<wxString> variablesVector)
371{
372 m_variablesVector = variablesVector;
373 // Clear old glTextVector
374 for (auto it = m_gcTextInputVector.begin(), itEnd = m_gcTextInputVector.end(); it != itEnd; ++it) { delete* it; }
375 m_gcTextInputVector.clear();
376
377 for (auto it = m_variablesVector.begin(), itEnd = m_variablesVector.end(); it != itEnd; ++it)
378 m_gcTextInputVector.push_back(new GCText(*(it)));
379}
380
381bool MathExpression::Initialize()
382{
383 m_variables = "time,step,switch,";
384 for (auto it = m_variablesVector.begin(), itEnd = m_variablesVector.end(); it != itEnd; ++it)
385 m_variables += *(it)+",";
386 m_variables.RemoveLast();
387
388 // Set locale to ENGLISH_US to avoid parser error in numbers (eg. comma).
389 int currentLang = wxLocale::GetSystemLanguage();
390 wxLocale newLocale(wxLANGUAGE_ENGLISH_US);
391 int parserRes = m_fparser.Parse(static_cast<std::string>(m_mathExpression), static_cast<std::string>(m_variables));
392 if (parserRes != -1) return false; // Parse error.
393 wxLocale oldLocale(currentLang); // Return to current language.
394
395 if (m_inputValues) delete m_inputValues;
396 m_inputValues = new double[m_variablesVector.size() + 3]; // Custom variables + time + step + switch
397
398 // Optimize only once to gain performance.
399 m_fparser.Optimize();
400
401 m_solved = false;
402 m_output = 0.0;
403 return true;
404}
405
406rapidxml::xml_node<>* MathExpression::SaveElement(rapidxml::xml_document<>& doc, rapidxml::xml_node<>* elementListNode)
407{
408 auto elementNode = XMLParser::AppendNode(doc, elementListNode, "MathExpr");
409 XMLParser::SetNodeAttribute(doc, elementNode, "ID", m_elementID);
410
411 SaveCADProperties(doc, elementNode);
412 SaveControlNodes(doc, elementNode);
413
414 // Element properties
415 auto variablesNode = XMLParser::AppendNode(doc, elementNode, "VariableList");
416 for (unsigned int i = 0; i < m_variablesVector.size(); ++i) {
417 auto variable = XMLParser::AppendNode(doc, variablesNode, "Variable");
418 XMLParser::SetNodeValue(doc, variable, m_variablesVector[i]);
419 }
420 auto mathExprValue = XMLParser::AppendNode(doc, elementNode, "MathExprValue");
421 XMLParser::SetNodeValue(doc, mathExprValue, m_mathExpression);
422
423 return elementNode;
424}
425
426bool MathExpression::OpenElement(rapidxml::xml_node<>* elementNode)
427{
428 if (!OpenCADProperties(elementNode)) return false;
429 if (!OpenControlNodes(elementNode)) return false;
430
431 // Element properties
432 std::vector<wxString> variables;
433 auto variablesNode = elementNode->first_node("VariableList");
434 auto variable = variablesNode->first_node("Variable");
435 while (variable) {
436 variables.push_back(variable->value());
437 variable = variable->next_sibling("Variable");
438 }
439 SetVariables(variables);
440
441 auto mathExprValueNode = elementNode->first_node("MathExprValue");
442 m_mathExpression = mathExprValueNode->value();
443
444 // Init opened properties
445 StartMove(m_position);
446 UpdatePoints();
447
448 return true;
449}
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:495
void SetPosition(const wxPoint2DDouble position)
Set the element position and update the rectangle.
Definition Element.cpp:28
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 Element * GetCopy()
Get a the element copy.
virtual bool UpdateText()
Update the OpenGL text in the element (if present).
virtual bool ShowForm(wxWindow *parent, Element *element, wxWindow *workspace=nullptr)
Show element data form.
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...