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