Power System Platform  2026w10a-beta
Loading...
Searching...
No Matches
ControlElementSolver.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
19
20#include "../../editors/ControlEditor.h"
21
22#include "ConnectionLine.h"
23#include "Constant.h"
25#include "Exponential.h"
26#include "Gain.h"
27#include "IOControl.h"
28#include "Limiter.h"
29#include "Multiplier.h"
30#include "RateLimiter.h"
31#include "Sum.h"
32#include "TransferFunction.h"
33
34ControlElementSolver::ControlElementSolver(ControlEditor* controlEditor, double timeStep, double integrationError)
35{
36 m_ctrlContainer = new ControlElementContainer();
37 m_ctrlContainer->FillContainer(controlEditor);
38 Initialize(controlEditor, timeStep, integrationError);
39}
40
41ControlElementSolver::ControlElementSolver(ControlElementContainer* ctrlContainer,
42 double timeStep,
43 double integrationError,
44 wxWindow* parent)
45{
46 m_ctrlContainer = ctrlContainer;
47 Initialize(parent, timeStep, integrationError);
48}
49
50ControlElementSolver::~ControlElementSolver()
51{
52 if (m_inputToSolve) delete[] m_inputToSolve;
53 //if (m_ctrlContainer) delete m_ctrlContainer;
54}
55
56void ControlElementSolver::Initialize(wxWindow* parent, double timeStep, double integrationError)
57{
58 // Init the input array size
59 if (m_inputToSolve) delete[] m_inputToSolve;
60 m_inputToSolve = new double[3];
61 // Check if the sistem have one input and one output
62 bool fail = false;
63 auto ioList = m_ctrlContainer->GetIOControlList();
64 if (ioList.size() < 2) {
65 fail = true;
66 m_failMessage = _("The control system must have at least one input and one output.");
67 }
68 bool haveInput, haveOutput;
69 haveInput = haveOutput = false;
70 for (auto it = ioList.begin(), itEnd = ioList.end(); it != itEnd; ++it) {
71 IOControl* io = *it;
72 if (io->GetType() == Node::NodeType::NODE_OUT && !haveInput) {
73 m_inputControl = io;
74 haveInput = true;
75 }
76 else if (io->GetType() == Node::NodeType::NODE_IN) {
77 m_outputControl = io;
78 haveOutput = true;
79 }
80 }
81 if (!fail && !haveInput) {
82 fail = true;
83 m_failMessage = _("There is no input in the control system.");
84 }
85 if (!fail && !haveOutput) {
86 fail = true;
87 m_failMessage = _("There is no output in the control system.");
88 }
89 if (!fail) {
90 if (m_inputControl->GetChildList().size() == 0) {
91 fail = true;
92 m_failMessage = _("Input not connected.");
93 }
94 }
95
96 m_timeStep = timeStep;
97 m_integrationError = integrationError;
98 if (!fail) {
99 if (!InitializeValues(true)) {
100 fail = true;
101 m_failMessage = _("It was not possible to initialize the control system.");
102 }
103 }
104
105 if (fail) {
106 wxMessageDialog msgDialog(parent, m_failMessage, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
107 msgDialog.ShowModal();
108 }
109 else {
110 m_isOK = true;
111 }
112}
113
114bool ControlElementSolver::InitializeValues(bool startAllZero)
115{
116 // Reset Elements values
117 auto elementList = m_ctrlContainer->GetControlElementsList();
118 for (auto it = elementList.begin(), itEnd = elementList.end(); it != itEnd; ++it) {
119 if (!(*it)->Initialize()) return false;
120 }
121 auto connectionLineList = m_ctrlContainer->GetConnectionLineList();
122 for (auto it = connectionLineList.begin(), itEnd = connectionLineList.end(); it != itEnd; ++it) {
123 if (!(*it)->Initialize()) return false;
124 }
125 auto tfList = m_ctrlContainer->GetTFList();
126 for (auto it = tfList.begin(), itEnd = tfList.end(); it != itEnd; ++it) {
127 (*it)->CalculateSpaceState(100, m_integrationError);
128 }
129
130 if (!startAllZero) {
131 double origTimeStep = m_timeStep;
132 double minStep = m_timeStep / 10;
133 double maxStep = m_timeStep * 10;
134 // Calculate the steady-state results according to the input.
135 double minError = 1e-7 * m_timeStep;
136 int maxIteration = 100 / m_timeStep;
137
138 double prevSol = 0.0;
139 double currentSol = 1.0;
140 double error = 1.0;
141 double prevError = 1.0;
142 int numIt = 0;
143 while (error > minError) {
144 prevSol = currentSol;
145 prevError = error;
146 SolveNextStep();
147 currentSol = GetLastSolution();
148 numIt++;
149 error = std::abs(prevSol - currentSol);
150 if (std::abs(error - prevError) < 1e-1) {
151 if (m_timeStep < maxStep) { m_timeStep *= 1.5; }
152 }
153 else if (std::abs(error - prevError) > 10) {
154 if (m_timeStep > minStep) { m_timeStep /= 1.5; }
155 }
156 if (numIt >= maxIteration) {
157 m_failMessage = _("It was not possible to initialize the control system.");
158 return false;
159 }
160 }
161 m_timeStep = origTimeStep;
162 m_solutions.clear();
163 }
164
165 return true;
166}
167
168void ControlElementSolver::SolveNextStep()
169{
170 // Set all elements as not solved
171 auto elementList = m_ctrlContainer->GetControlElementsList();
172 for (auto& element : elementList) {
173 element->SetSolved(false);
174 }
175 auto connectionLineList = m_ctrlContainer->GetConnectionLineList();
176 for (auto& cLine : connectionLineList) {
177 cLine->SetSolved(false);
178 }
179
180 // Get first node connection
181 ConnectionLine* firstConn = static_cast<ConnectionLine*>(m_inputControl->GetChildList()[0]);
182
183 // Set value to the connected lines in constants
184 auto constantList = m_ctrlContainer->GetConstantList();
185 for (auto it = constantList.begin(), itEnd = constantList.end(); it != itEnd; ++it) {
186 Constant* constant = *it;
187 if (constant->GetChildList().size() == 1) {
188 constant->SetSolved();
189 ConnectionLine* child = static_cast<ConnectionLine*>(constant->GetChildList()[0]);
190 child->SetValue(constant->GetValue());
191 child->SetSolved();
192 FillAllConnectedChildren(child);
193 }
194 }
195
196 // Solve math expression without inputs (but connected)
197 auto mathExprList = m_ctrlContainer->GetMathExprList();
198 for (auto it = mathExprList.begin(), itEnd = mathExprList.end(); it != itEnd; ++it) {
199 MathExpression* mathExpr = *it;
200 if (mathExpr->GetVariables().size() == 0) { // No variables, no inputs.
201 m_inputToSolve[0] = 0.0;
202 m_inputToSolve[1] = m_currentTime;
203 m_inputToSolve[2] = m_switchStatus;
204 mathExpr->Solve(m_inputToSolve, m_timeStep);
205 mathExpr->SetSolved();
206 ConnectionLine* child = static_cast<ConnectionLine*>(mathExpr->GetChildList()[0]);
207 child->SetValue(mathExpr->GetOutput());
208 child->SetSolved();
209 FillAllConnectedChildren(child);
210 }
211 }
212
213 // Set value to the connected lines in inputs
214 auto ioList = m_ctrlContainer->GetIOControlList();
215 for (auto it = ioList.begin(), itEnd = ioList.end(); it != itEnd; ++it) {
216 IOControl* io = *it;
217 if (io->GetChildList().size() == 1) {
218 io->SetSolved();
219 ConnectionLine* child = static_cast<ConnectionLine*>(io->GetChildList()[0]);
220 if (m_inputControl == io) firstConn = child;
221 bool inputType = true;
222 io->SetSolved();
223 switch (io->GetValue()) {
224 case IOControl::IN_TERMINAL_VOLTAGE: {
225 child->SetValue(m_terminalVoltage);
226 } break;
227 case IOControl::IN_VELOCITY: {
228 child->SetValue(m_velocity);
229 } break;
230 case IOControl::IN_ACTIVE_POWER: {
231 child->SetValue(m_activePower);
232 } break;
233 case IOControl::IN_REACTIVE_POWER: {
234 child->SetValue(m_reactivePower);
235 } break;
236 case IOControl::IN_INITIAL_TERMINAL_VOLTAGE: {
237 child->SetValue(m_initTerminalVoltage);
238 } break;
239 case IOControl::IN_INITIAL_MEC_POWER: {
240 child->SetValue(m_initMecPower);
241 } break;
242 case IOControl::IN_INITIAL_VELOCITY: {
243 child->SetValue(m_initVelocity);
244 } break;
245 case IOControl::IN_DELTA_VELOCITY: {
246 child->SetValue(m_deltaVelocity);
247 } break;
248 case IOControl::IN_DELTA_ACTIVE_POWER: {
249 child->SetValue(m_deltaPe);
250 } break;
251 case IOControl::IN_TEST: {
252 child->SetValue(io->GetTestValue());
253 } break;
254 default: {
255 inputType = false;
256 io->SetSolved(false);
257 } break;
258 }
259 if (inputType) {
260 child->SetSolved();
261 FillAllConnectedChildren(child);
262 }
263 }
264 }
265
266 ConnectionLine* currentLine = firstConn;
267 while (currentLine) {
268 currentLine = SolveNextElement(currentLine);
269 if (!m_isOK) return;
270 }
271
272 bool haveUnsolvedElement = true;
273 while (haveUnsolvedElement) {
274 haveUnsolvedElement = false;
275 // Get the solved line connected with unsolved element (elements not connected in the main branch).
276 for (auto& cLine : connectionLineList) {
277 if (cLine->IsSolved()) {
278 auto parentList = cLine->GetParentList();
279 for (auto itP = parentList.begin(), itPEnd = parentList.end(); itP != itPEnd; ++itP) {
280 ControlElement* parent = static_cast<ControlElement*>(*itP);
281 if (!parent->IsSolved()) {
282 haveUnsolvedElement = true;
283 // Solve secondary branch.
284 currentLine = cLine.get();
285 while (currentLine) {
286 currentLine = SolveNextElement(currentLine);
287 if (!m_isOK) return;
288 }
289 break;
290 }
291 }
292 }
293 if (haveUnsolvedElement) break;
294 }
295 }
296
297 // Set the control system output.
298 for (auto it = ioList.begin(), itEnd = ioList.end(); it != itEnd; ++it) {
299 IOControl* io = *it;
300 if (io->GetChildList().size() == 1) {
301 io->SetSolved();
302 ConnectionLine* child = static_cast<ConnectionLine*>(io->GetChildList()[0]);
303 switch (io->GetValue()) {
304 case IOControl::OUT_MEC_POWER: {
305 m_mecPower = child->GetValue();
306 m_solutions.push_back(m_mecPower);
307 } break;
308 case IOControl::OUT_FIELD_VOLTAGE: {
309 m_fieldVoltage = child->GetValue();
310 m_solutions.push_back(m_fieldVoltage);
311 } break;
312 default:
313 break;
314 }
315 }
316 }
317}
318
319void ControlElementSolver::FillAllConnectedChildren(ConnectionLine* parent)
320{
321 auto childList = parent->GetLineChildList();
322 for (auto it = childList.begin(), itEnd = childList.end(); it != itEnd; ++it) {
323 ConnectionLine* child = *it;
324 child->SetValue(parent->GetValue());
325 child->SetSolved();
326 FillAllConnectedChildren(child);
327 }
328}
329
330ConnectionLine* ControlElementSolver::SolveNextElement(ConnectionLine* currentLine)
331{
332 auto parentList = currentLine->GetParentList();
333 for (auto it = parentList.begin(), itEnd = parentList.end(); it != itEnd; ++it) {
334 ControlElement* element = static_cast<ControlElement*>(*it);
335 // Solve the unsolved parent.
336 if (!element->IsSolved()) {
337 m_inputToSolve[0] = currentLine->GetValue();
338 if (!std::isfinite(m_inputToSolve[0])) {
339 m_inputToSolve[0] = 0.0;
340 m_isOK = false;
341 m_failMessage = _("The control system solution is diverging. Try to reduce the time step.");
342 return nullptr;
343 }
344 m_inputToSolve[1] = m_currentTime;
345 m_inputToSolve[2] = m_switchStatus;
346 if (!element->Solve(m_inputToSolve, m_timeStep)) {
347 m_isOK = false;
348 if(auto tf = dynamic_cast<TransferFunction*>(element)) {
349 wxString numerator, denominator;
350 tf->GetTFString(numerator, denominator);
351 m_failMessage = wxString::Format(_("Unable to compute the transfer function: (%s)/(%s)"), numerator, denominator);
352 }
353 return nullptr;
354 }
355 element->SetSolved();
356
357 // Get the output node (must have one or will result nullptr).
358 Node* outNode = nullptr;
359 auto nodeList = element->GetNodeList();
360 for (auto itN = nodeList.begin(), itNEnd = nodeList.end(); itN != itNEnd; ++itN) {
361 Node* node = *itN;
362 if (node->GetNodeType() == Node::NodeType::NODE_OUT) outNode = node;
363 }
364 if (!outNode) return nullptr;
365
366 // Set connection line value associated with the output node.
367 auto childList = element->GetChildList();
368 for (auto itC = childList.begin(), itCEnd = childList.end(); itC != itCEnd; ++itC) {
369 ConnectionLine* cLine = static_cast<ConnectionLine*>(*itC);
370 if (!cLine->IsSolved()) { // Only check unsolved lines
371 // Check if the connection line have the output node on the list
372 auto lineNodeList = cLine->GetNodeList();
373 for (auto itCN = lineNodeList.begin(), itCNEnd = lineNodeList.end(); itCN != itCNEnd; ++itCN) {
374 Node* childNode = *itCN;
375 if (childNode == outNode) {
376 // Check if the line connect two elements, otherwise return nullptr
377 if (cLine->GetType() != ConnectionLine::ConnectionLineType::ELEMENT_ELEMENT) return nullptr;
378
379 // Set the connection line value and return it.
380 cLine->SetValue(element->GetOutput());
381 cLine->SetSolved();
382 FillAllConnectedChildren(cLine);
383 return cLine;
384 }
385 }
386 }
387 }
388 }
389 }
390 return nullptr;
391}
Connection between two control elements or other connection line and an element.
A control element that provides a constant value.
Definition Constant.h:37
Class that can contain all control elements. Can identify (using RTTI) the elements from a generic li...
virtual std::vector< Element * > GetParentList() const
Get the parent list.
Definition Element.h:559
virtual std::vector< Element * > GetChildList() const
Get the Child list.
Definition Element.h:564
Provides the communication with the power element.
Definition IOControl.h:43
A generic math expression block that can perform math and conditional operations with the inputs.
Node of a control element. This class manages the user interaction with the connection and control el...
Calculates the time response by a frequency domain transfer function.