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