Eclipse SUMO - Simulation of Urban MObility
Loading...
Searching...
No Matches
Circuit.cpp
Go to the documentation of this file.
1/****************************************************************************/
2// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3// Copyright (C) 2001-2024 German Aerospace Center (DLR) and others.
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// https://www.eclipse.org/legal/epl-2.0/
7// This Source Code may also be made available under the following Secondary
8// Licenses when the conditions for such availability set forth in the Eclipse
9// Public License 2.0 are satisfied: GNU General Public License, version 2
10// or later which is available at
11// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13/****************************************************************************/
24// Representation of electric circuit of overhead wires
25/****************************************************************************/
26#include <config.h>
27
28#include <cfloat>
29#include <cstdlib>
30#include <iostream>
31#include <ctime>
32#include <mutex>
35#include <microsim/MSGlobals.h>
36#include "Element.h"
37#include "Node.h"
38#include "Circuit.h"
39
40static std::mutex circuit_lock;
41
42Node* Circuit::addNode(std::string name) {
43 if (getNode(name) != nullptr) {
44 WRITE_ERRORF(TL("The node: '%' already exists."), name);
45 return nullptr;
46 }
47
48 if (nodes->size() == 0) {
49 lastId = -1;
50 }
51 Node* tNode = new Node(name, this->lastId);
52 if (lastId == -1) {
53 tNode->setGround(true);
54 }
55 this->lastId++;
56 circuit_lock.lock();
57 this->nodes->push_back(tNode);
58 circuit_lock.unlock();
59 return tNode;
60}
61
63 circuit_lock.lock();
64 this->nodes->erase(std::remove(this->nodes->begin(), this->nodes->end(), node), this->nodes->end());
65 circuit_lock.unlock();
66}
67
68double Circuit::getCurrent(std::string name) {
69 Element* tElement = getElement(name);
70 if (tElement == nullptr) {
71 return DBL_MAX;
72 }
73 return tElement->getCurrent();
74}
75
76double Circuit::getVoltage(std::string name) {
77 Element* tElement = getElement(name);
78 if (tElement == nullptr) {
79 Node* node = getNode(name);
80 if (node != nullptr) {
81 return node->getVoltage();
82 } else {
83 return DBL_MAX;
84 }
85 } else {
86 return tElement->getVoltage();
87 }
88}
89
90double Circuit::getResistance(std::string name) {
91 Element* tElement = getElement(name);
92 if (tElement == nullptr) {
93 return -1;
94 }
95 return tElement->getResistance();
96}
97
98Node* Circuit::getNode(std::string name) {
99 for (Node* const node : *nodes) {
100 if (node->getName() == name) {
101 return node;
102 }
103 }
104 return nullptr;
105}
106
108 for (Node* const node : *nodes) {
109 if (node->getId() == id) {
110 return node;
111 }
112 }
113 return nullptr;
114}
115
116Element* Circuit::getElement(std::string name) {
117 for (Element* const el : *elements) {
118 if (el->getName() == name) {
119 return el;
120 }
121 }
122 for (Element* const voltageSource : *voltageSources) {
123 if (voltageSource->getName() == name) {
124 return voltageSource;
125 }
126 }
127 return nullptr;
128}
129
131 for (Element* const el : *elements) {
132 if (el->getId() == id) {
133 return el;
134 }
135 }
136 return getVoltageSource(id);
137}
138
140 for (Element* const voltageSource : *voltageSources) {
141 if (voltageSource->getId() == id) {
142 return voltageSource;
143 }
144 }
145 return nullptr;
146}
147
149 double power = 0;
150 for (Element* const voltageSource : *voltageSources) {
151 power += voltageSource->getPower();
152 }
153 return power;
154}
155
157 double current = 0;
158 for (Element* const voltageSource : *voltageSources) {
159 current += voltageSource->getCurrent();
160 }
161 return current;
162}
163
164// RICE_CHECK: Locking removed?
165std::string& Circuit::getCurrentsOfCircuitSource(std::string& currents) {
166 //circuit_lock.lock();
167 currents.clear();
168 for (Element* const voltageSource : *voltageSources) {
169 currents += toString(voltageSource->getCurrent(), 4) + " ";
170 }
171 if (!currents.empty()) {
172 currents.pop_back();
173 }
174 //circuit_lock.unlock();
175 return currents;
176}
177
178std::vector<Element*>* Circuit::getCurrentSources() {
179 std::vector<Element*>* vsources = new std::vector<Element*>(0);
180 for (Element* const el : *elements) {
182 //if ((*it)->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire && !isnan((*it)->getPowerWanted())) {
183 vsources->push_back(el);
184 }
185 }
186 return vsources;
187}
188
190 circuit_lock.lock();
191}
192
194 circuit_lock.unlock();
195}
196
197#ifdef HAVE_EIGEN
198void Circuit::removeColumn(Eigen::MatrixXd& matrix, int colToRemove) {
199 const int numRows = (int)matrix.rows();
200 const int numCols = (int)matrix.cols() - 1;
201
202 if (colToRemove < numCols) {
203 matrix.block(0, colToRemove, numRows, numCols - colToRemove) = matrix.rightCols(numCols - colToRemove);
204 }
205
206 matrix.conservativeResize(numRows, numCols);
207}
208
209bool Circuit::solveEquationsNRmethod(double* eqn, double* vals, std::vector<int>* removable_ids) {
210 // removable_ids includes nodes with voltage source already
211 int numofcolumn = (int)voltageSources->size() + (int)nodes->size() - 1;
212 int numofeqs = numofcolumn - (int)removable_ids->size();
213
214 // map equations into matrix A
215 Eigen::MatrixXd A = Eigen::Map < Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> >(eqn, numofeqs, numofcolumn);
216
217 int id;
218 // remove removable columns of matrix A, i.e. remove equations corresponding to nodes with two resistors connected in series
219 // RICE_TODO auto for ?
220 for (std::vector<int>::reverse_iterator it = removable_ids->rbegin(); it != removable_ids->rend(); ++it) {
221 id = (*it >= 0 ? *it : -(*it));
222 removeColumn(A, id);
223 }
224
225 // detect number of column for each node
226 // in other words: detect elements of x to certain node
227 // in other words: assign number of column to the proper non removable node
228 int j = 0;
229 Element* tElem = nullptr;
230 Node* tNode = nullptr;
231 for (int i = 0; i < numofcolumn; i++) {
232 tNode = getNode(i);
233 if (tNode != nullptr) {
234 if (tNode->isRemovable()) {
235 tNode->setNumMatrixCol(-1);
236 continue;
237 } else {
238 if (j > numofeqs) {
239 WRITE_ERROR(TL("Index of renumbered node exceeded the reduced number of equations."));
240 break;
241 }
242 tNode->setNumMatrixCol(j);
243 j++;
244 continue;
245 }
246 } else {
247 tElem = getElement(i);
248 if (tElem != nullptr) {
249 if (j > numofeqs) {
250 WRITE_ERROR(TL("Index of renumbered element exceeded the reduced number of equations."));
251 break;
252 }
253 continue;
254 }
255 }
256 // tNode == nullptr && tElem == nullptr
257 WRITE_ERROR(TL("Structural error in reduced circuit matrix."));
258 }
259
260 // map 'vals' into vector b and initialize solution x
261 Eigen::Map<Eigen::VectorXd> b(vals, numofeqs);
262 Eigen::VectorXd x = A.colPivHouseholderQr().solve(b);
263
264 // initialize Jacobian matrix J and vector dx
265 Eigen::MatrixXd J = A;
266 Eigen::VectorXd dx;
267 // initialize progressively increasing maximal number of Newton-Rhapson iterations
268 int max_iter_of_NR = 10;
269 // value of scaling parameter alpha
270 double alpha = 1;
271 // the best (maximum) value of alpha that guarantees the existence of solution
272 alphaBest = 0;
273 // reason why is alpha not 1
275 // vector of alphas for that no solution has been found
276 std::vector<double> alpha_notSolution;
277 // initialize progressively decreasing tolerance for alpha
278 double alpha_res = 1e-2;
279
280 double currentSumActual = 0.0;
281 // solution x corresponding to the alphaBest
282 Eigen::VectorXd x_best = x;
283 bool x_best_exist = true;
284
285 if (x.maxCoeff() > 10e6 || x.minCoeff() < -10e6) {
286 WRITE_ERROR(TL("Initial solution x used during solving DC circuit is out of bounds.\n"));
287 }
288
289 // Search for the suitable scaling value alpha
290 while (true) {
291
292 int iterNR = 0;
293 // run Newton-Raphson methods
294 while (true) {
295
296 // update right-hand side vector vals and Jacobian matrix J
297 // node's right-hand side set to zero
298 for (int i = 0; i < numofeqs - (int) voltageSources->size(); i++) {
299 vals[i] = 0;
300 }
301 J = A;
302
303 int i = 0;
304 for (auto& node : *nodes) {
305 if (node->isGround() || node->isRemovable() || node->getNumMatrixRow() == -2) {
306 continue;
307 }
308 if (node->getNumMatrixRow() != i) {
309 WRITE_ERROR(TL("wrongly assigned row of matrix A during solving the circuit"));
310 }
311 // TODO: Range-based loop
312 // loop over all node's elements
313 for (auto it_element = node->getElements()->begin(); it_element != node->getElements()->end(); it_element++) {
314 if ((*it_element)->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire) {
315 // if the element is current source
316 if ((*it_element)->isEnabled()) {
317 double diff_voltage;
318 int PosNode_NumACol = (*it_element)->getPosNode()->getNumMatrixCol();
319 int NegNode_NumACol = (*it_element)->getNegNode()->getNumMatrixCol();
320 // compute voltage on current source
321 if (PosNode_NumACol == -1) {
322 // if the positive node is the ground => U = 0 - phi(NegNode)
323 diff_voltage = -x[NegNode_NumACol];
324 } else if (NegNode_NumACol == -1) {
325 // if the negative node is the ground => U = phi(PosNode) - 0
326 diff_voltage = x[PosNode_NumACol];
327 } else {
328 // U = phi(PosNode) - phi(NegNode)
329 diff_voltage = (x[PosNode_NumACol] - x[NegNode_NumACol]);
330 }
331
332 if ((*it_element)->getPosNode() == node) {
333 // the positive current (the element is consuming energy if powerWanted > 0) is flowing from the positive node (sign minus)
334 vals[i] -= alpha * (*it_element)->getPowerWanted() / diff_voltage;
335 (*it_element)->setCurrent(-alpha * (*it_element)->getPowerWanted() / diff_voltage);
336 if (PosNode_NumACol != -1) {
337 // -1* d_b/d_phiPos = -1* d(-alpha*P/(phiPos-phiNeg) )/d_phiPos = -1* (--alpha*P/(phiPos-phiNeg)^2 )
338 J(i, PosNode_NumACol) -= alpha * (*it_element)->getPowerWanted() / diff_voltage / diff_voltage;
339 }
340 if (NegNode_NumACol != -1) {
341 // -1* d_b/d_phiNeg = -1* d(-alpha*P/(phiPos-phiNeg) )/d_phiNeg = -1* (---alpha*P/(phiPos-phiNeg)^2 )
342 J(i, NegNode_NumACol) += alpha * (*it_element)->getPowerWanted() / diff_voltage / diff_voltage;
343 }
344 } else {
345 // the positive current (the element is consuming energy if powerWanted > 0) is flowing to the negative node (sign plus)
346 vals[i] += alpha * (*it_element)->getPowerWanted() / diff_voltage;
347 //Question: sign before alpha - or + during setting current?
348 //Answer: sign before alpha is minus since we assume positive powerWanted if the current element behaves as load
349 // (*it_element)->setCurrent(-alpha * (*it_element)->getPowerWanted() / diff_voltage);
350 // Note: we should never reach this part of code since the authors assumes the negative node of current source as the ground node
351 WRITE_WARNING(TL("The negative node of current source is not the ground."))
352 if (PosNode_NumACol != -1) {
353 // -1* d_b/d_phiPos = -1* d(alpha*P/(phiPos-phiNeg) )/d_phiPos = -1* (-alpha*P/(phiPos-phiNeg)^2 )
354 J(i, PosNode_NumACol) += alpha * (*it_element)->getPowerWanted() / diff_voltage / diff_voltage;
355 }
356 if (NegNode_NumACol != -1) {
357 // -1* d_b/d_phiNeg = -1* d(alpha*P/(phiPos-phiNeg) )/d_phiNeg = -1* (--alpha*P/(phiPos-phiNeg)^2 )
358 J(i, NegNode_NumACol) -= alpha * (*it_element)->getPowerWanted() / diff_voltage / diff_voltage;
359 }
360 }
361 }
362 }
363 }
364 i++;
365 }
366
367
368 // RICE_CHECK @20210409 This had to be merged into the master/main manually.
369 // Sum of currents going through the all voltage sources
370 // the sum is over all nodes, but the nonzero nodes are only those neighboring with current sources,
371 // so the sum is negative sum of currents through/from current sources representing trolleybuses
372 currentSumActual = 0;
373 for (i = 0; i < numofeqs - (int)voltageSources->size(); i++) {
374 currentSumActual -= vals[i];
375 }
376 // RICE_TODO @20210409 This epsilon should be specified somewhere as a constant. Or should be a parameter.
377 if ((A * x - b).norm() < 1e-6) {
378 //current limits
379 if (currentSumActual > getCurrentLimit() && MSGlobals::gOverheadWireCurrentLimits) {
381 alpha_notSolution.push_back(alpha);
382 if (x_best_exist) {
383 x = x_best;
384 }
385 break;
386 }
387 //voltage limits 70% - 120% of nominal voltage
388 // RICE_TODO @20210409 Again, these limits should be parametrized.
389 if (x.maxCoeff() > voltageSources->front()->getVoltage() * 1.2 || x.minCoeff() < voltageSources->front()->getVoltage() * 0.7) {
391 alpha_notSolution.push_back(alpha);
392 if (x_best_exist) {
393 x = x_best;
394 }
395 break;
396 }
397
398 alphaBest = alpha;
399 x_best = x;
400 x_best_exist = true;
401 break;
402 } else if (iterNR == max_iter_of_NR) {
404 alpha_notSolution.push_back(alpha);
405 if (x_best_exist) {
406 x = x_best;
407 }
408 break;
409 }
410
411 // Newton=Rhapson iteration
412 dx = -J.colPivHouseholderQr().solve(A * x - b);
413 x = x + dx;
414 ++iterNR;
415 }
416
417 if (alpha_notSolution.empty()) {
418 // no alpha without solution is in the alpha_notSolution, so the solving procedure is terminating
419 break;
420 }
421
422 if ((alpha_notSolution.back() - alphaBest) < alpha_res) {
423 max_iter_of_NR = 2 * max_iter_of_NR;
424 // RICE_TODO @20210409 Why division by 10?
425 // it follows Sevcik, Jakub, et al. "Solvability of the Power Flow Problem in DC Overhead Wire Circuit Modeling." Applications of Mathematics (2021): 1-19.
426 // see Alg 2 (progressive decrease of optimality tolerance)
427 alpha_res = alpha_res / 10;
428 // RICE_TODO @20210409 This epsilon should be specified somewhere as a constant. Or should be a parameter.
429 if (alpha_res < 5e-5) {
430 break;
431 }
432 alpha = alpha_notSolution.back();
433 alpha_notSolution.pop_back();
434 continue;
435 }
436
437 alpha = alphaBest + 0.5 * (alpha_notSolution.back() - alphaBest);
438 }
439
440 // vals is pointer to memory and we use it now for saving solution x_best instead of right-hand side b
441 for (int i = 0; i < numofeqs; i++) {
442 vals[i] = x_best[i];
443 }
444
445 // RICE_TODO: Describe what is happening here.
446 // we take x_best and alphaBest and update current values in current sources in order to be in agreement with the solution
447 int i = 0;
448 for (auto& node : *nodes) {
449 if (node->isGround() || node->isRemovable() || node->getNumMatrixRow() == -2) {
450 continue;
451 }
452 if (node->getNumMatrixRow() != i) {
453 WRITE_ERROR(TL("wrongly assigned row of matrix A during solving the circuit"));
454 }
455 for (auto it_element = node->getElements()->begin(); it_element != node->getElements()->end(); it_element++) {
456 if ((*it_element)->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire) {
457 if ((*it_element)->isEnabled()) {
458 double diff_voltage;
459 int PosNode_NumACol = (*it_element)->getPosNode()->getNumMatrixCol();
460 int NegNode_NumACol = (*it_element)->getNegNode()->getNumMatrixCol();
461 if (PosNode_NumACol == -1) {
462 diff_voltage = -x_best[NegNode_NumACol];
463 } else if (NegNode_NumACol == -1) {
464 diff_voltage = x_best[PosNode_NumACol];
465 } else {
466 diff_voltage = (x_best[PosNode_NumACol] - x_best[NegNode_NumACol]);
467 }
468
469 if ((*it_element)->getPosNode() == node) {
470 (*it_element)->setCurrent(-alphaBest * (*it_element)->getPowerWanted() / diff_voltage);
471 } else {
472 //Question: sign before alpha - or + during setting current?
473 //Answer: sign before alpha is minus since we assume positive powerWanted if the current element behaves as load
474 // (*it_element)->setCurrent(-alphaBest * (*it_element)->getPowerWanted() / diff_voltage);
475 // Note: we should never reach this part of code since the authors assumes the negative node of current source as the ground node
476 WRITE_WARNING(TL("The negative node of current source is not the ground."))
477 }
478 }
479 }
480 }
481 i++;
482 }
483
484 return true;
485}
486#endif
487
488void Circuit::deployResults(double* vals, std::vector<int>* removable_ids) {
489 // vals are the solution x
490
491 int numofcolumn = (int)voltageSources->size() + (int)nodes->size() - 1;
492 int numofeqs = numofcolumn - (int)removable_ids->size();
493
494 //loop over non-removable nodes: we assign the computed voltage to the non-removables nodes
495 int j = 0;
496 Element* tElem = nullptr;
497 Node* tNode = nullptr;
498 for (int i = 0; i < numofcolumn; i++) {
499 tNode = getNode(i);
500 if (tNode != nullptr)
501 if (tNode->isRemovable()) {
502 continue;
503 } else {
504 if (j > numofeqs) {
505 WRITE_ERROR(TL("Results deployment during circuit evaluation was unsuccessful."));
506 break;
507 }
508 tNode->setVoltage(vals[j]);
509 j++;
510 continue;
511 } else {
512 tElem = getElement(i);
513 if (tElem != nullptr) {
514 if (j > numofeqs) {
515 WRITE_ERROR(TL("Results deployment during circuit evaluation was unsuccessful."));
516 break;
517 }
518 // tElem should be voltage source - the current through voltage source is computed in a loop below
519 // if tElem is current source (JS thinks that no current source's id <= numofeqs), the current is already assign at the end of solveEquationsNRmethod method
520 continue;
521 }
522 }
523 WRITE_ERROR(TL("Results deployment during circuit evaluation was unsuccessful."));
524 }
525
526 Element* el1 = nullptr;
527 Element* el2 = nullptr;
528 Node* nextNONremovableNode1 = nullptr;
529 Node* nextNONremovableNode2 = nullptr;
530 // interpolate result of voltage to removable nodes
531 for (Node* const node : *nodes) {
532 if (!node->isRemovable()) {
533 continue;
534 }
535 if (node->getElements()->size() != 2) {
536 continue;
537 }
538
539 el1 = node->getElements()->front();
540 el2 = node->getElements()->back();
541 nextNONremovableNode1 = el1->getTheOtherNode(node);
542 nextNONremovableNode2 = el2->getTheOtherNode(node);
543 double x = el1->getResistance();
544 double y = el2->getResistance();
545
546 while (nextNONremovableNode1->isRemovable()) {
547 el1 = nextNONremovableNode1->getAnOtherElement(el1);
548 x += el1->getResistance();
549 nextNONremovableNode1 = el1->getTheOtherNode(nextNONremovableNode1);
550 }
551
552 while (nextNONremovableNode2->isRemovable()) {
553 el2 = nextNONremovableNode2->getAnOtherElement(el2);
554 y += el2->getResistance();
555 nextNONremovableNode2 = el2->getTheOtherNode(nextNONremovableNode2);
556 }
557
558 x = x / (x + y);
559 y = ((1 - x) * nextNONremovableNode1->getVoltage()) + (x * nextNONremovableNode2->getVoltage());
560 node->setVoltage(((1 - x)*nextNONremovableNode1->getVoltage()) + (x * nextNONremovableNode2->getVoltage()));
561 node->setRemovability(false);
562 }
563
564 // Update the electric currents for voltage sources (based on Kirchhof's law: current out = current in)
565 for (Element* const voltageSource : *voltageSources) {
566 double currentSum = 0;
567 for (Element* const el : *voltageSource->getPosNode()->getElements()) {
568 // loop over all elements on PosNode excluding the actual voltage source it
569 if (el != voltageSource) {
570 //currentSum += el->getCurrent();
571 currentSum += (voltageSource->getPosNode()->getVoltage() - el->getTheOtherNode(voltageSource->getPosNode())->getVoltage()) / el->getResistance();
573 WRITE_WARNING(TL("Cannot assign unambigous electric current value to two voltage sources connected in parallel at the same node."));
574 }
575 }
576 }
577 voltageSource->setCurrent(currentSum);
578 }
579}
580
582 nodes = new std::vector<Node*>(0);
583 elements = new std::vector<Element*>(0);
584 voltageSources = new std::vector<Element*>(0);
585 lastId = 0;
586 iscleaned = true;
587 circuitCurrentLimit = INFINITY;
588}
589
590Circuit::Circuit(double currentLimit) {
591 nodes = new std::vector<Node*>(0);
592 elements = new std::vector<Element*>(0);
593 voltageSources = new std::vector<Element*>(0);
594 lastId = 0;
595 iscleaned = true;
596 circuitCurrentLimit = currentLimit;
597}
598
599#ifdef HAVE_EIGEN
600bool Circuit::_solveNRmethod() {
601 double* eqn = nullptr;
602 double* vals = nullptr;
603 std::vector<int> removable_ids;
604
605 detectRemovableNodes(&removable_ids);
606 createEquationsNRmethod(eqn, vals, &removable_ids);
607 if (!solveEquationsNRmethod(eqn, vals, &removable_ids)) {
608 return false;
609 }
610 // vals are now the solution x of the circuit
611 deployResults(vals, &removable_ids);
612
613 delete[] eqn;
614 delete[] vals;
615 return true;
616}
617
618bool Circuit::solve() {
619 if (!iscleaned) {
620 cleanUpSP();
621 }
622 return this->_solveNRmethod();
623}
624
625bool Circuit::createEquationsNRmethod(double*& eqs, double*& vals, std::vector<int>* removable_ids) {
626 // removable_ids does not include nodes with voltage source yet
627
628 // number of voltage sources + nodes without the ground node
629 int n = (int)(voltageSources->size() + nodes->size() - 1);
630 // number of equations
631 // assumption: each voltage source has different positive node and common ground node,
632 // i.e. any node excluding the ground node is connected to 0 or 1 voltage source
633 int m = n - (int)(removable_ids->size() + voltageSources->size());
634
635 // allocate and initialize zero matrix eqs and vector vals
636 eqs = new double[m * n];
637 vals = new double[m];
638
639 for (int i = 0; i < m; i++) {
640 vals[i] = 0;
641 for (int j = 0; j < n; j++) {
642 eqs[i * n + j] = 0;
643 }
644 }
645
646 // loop over all nodes
647 int i = 0;
648 for (std::vector<Node*>::iterator it = nodes->begin(); it != nodes->end(); it++) {
649 if ((*it)->isGround() || (*it)->isRemovable()) {
650 // if the node is grounded or is removable set the corresponding number of row in matrix to -1 (no equation in eqs)
651 (*it)->setNumMatrixRow(-1);
652 continue;
653 }
654 assert(i < m);
655 // constitute the equation corresponding to node it, add all passed voltage source elements into removable_ids
656 bool noVoltageSource = createEquationNRmethod((*it), (eqs + n * i), vals[i], removable_ids);
657 // if the node it has element of type "voltage source" we do not use the equation, because some value of current throw the voltage source can be always find
658 if (noVoltageSource) {
659 (*it)->setNumMatrixRow(i);
660 i++;
661 } else {
662 (*it)->setNumMatrixRow(-2);
663 vals[i] = 0;
664 for (int j = 0; j < n; j++) {
665 eqs[n * i + j] = 0;
666 }
667 }
668 }
669
670 // removable_ids includes nodes with voltage source already
671 std::sort(removable_ids->begin(), removable_ids->end(), std::less<int>());
672
673
674 for (std::vector<Element*>::iterator it = voltageSources->begin(); it != voltageSources->end(); it++) {
675 assert(i < m);
676 createEquation((*it), (eqs + n * i), vals[i]);
677 i++;
678 }
679
680 return true;
681}
682
683bool Circuit::createEquation(Element* vsource, double* eqn, double& val) {
684 if (!vsource->getPosNode()->isGround()) {
685 eqn[vsource->getPosNode()->getId()] = 1;
686 }
687 if (!vsource->getNegNode()->isGround()) {
688 eqn[vsource->getNegNode()->getId()] = -1;
689 }
690 if (vsource->isEnabled()) {
691 val = vsource->getVoltage();
692 } else {
693 val = 0;
694 }
695 return true;
696}
697
698bool Circuit::createEquationNRmethod(Node* node, double* eqn, double& val, std::vector<int>* removable_ids) {
699 // loop over all elements connected to the node
700 for (std::vector<Element*>::iterator it = node->getElements()->begin(); it != node->getElements()->end(); it++) {
701 double x;
702 switch ((*it)->getType()) {
704 if ((*it)->isEnabled()) {
705 x = (*it)->getResistance();
706 // go through all neighboring removable nodes and sum resistance of resistors in the serial branch
707 Node* nextNONremovableNode = (*it)->getTheOtherNode(node);
708 Element* nextSerialResistor = *it;
709 while (nextNONremovableNode->isRemovable()) {
710 nextSerialResistor = nextNONremovableNode->getAnOtherElement(nextSerialResistor);
711 x += nextSerialResistor->getResistance();
712 nextNONremovableNode = nextSerialResistor->getTheOtherNode(nextNONremovableNode);
713 }
714 // compute inverse value and place/add this value at proper places in eqn
715 x = 1 / x;
716 eqn[node->getId()] += x;
717
718 if (!nextNONremovableNode->isGround()) {
719 eqn[nextNONremovableNode->getId()] -= x;
720 }
721 }
722 break;
724 if ((*it)->isEnabled()) {
725 // initialize current in current source
726 if ((*it)->getPosNode() == node) {
727 x = -(*it)->getPowerWanted() / voltageSources->front()->getVoltage();
728 } else {
729 x = (*it)->getPowerWanted() / voltageSources->front()->getVoltage();
730 }
731 } else {
732 x = 0;
733 }
734 val += x;
735 break;
737 if ((*it)->getPosNode() == node) {
738 x = -1;
739 } else {
740 x = 1;
741 }
742 eqn[(*it)->getId()] += x;
743 // equations with voltage source can be ignored, because some value of current throw the voltage source can be always find
744 removable_ids->push_back((*it)->getId());
745 return false;
746 break;
748 return false;
749 break;
750 }
751 }
752 return true;
753}
754#endif
755
761void Circuit::detectRemovableNodes(std::vector<int>* removable_ids) {
762 // loop over all nodes in the circuit
763 for (std::vector<Node*>::iterator it = nodes->begin(); it != nodes->end(); it++) {
764 // if the node is connected to two elements and is not the ground
765 if ((*it)->getElements()->size() == 2 && !(*it)->isGround()) {
766 // set such node by default as removable. But check if the two elements are both resistors
767 (*it)->setRemovability(true);
768 for (std::vector<Element*>::iterator it2 = (*it)->getElements()->begin(); it2 != (*it)->getElements()->end(); it2++) {
769 if ((*it2)->getType() != Element::ElementType::RESISTOR_traction_wire) {
770 (*it)->setRemovability(false);
771 break;
772 }
773 }
774 if ((*it)->isRemovable()) {
775 //if the node is removable add pointer into the vector of removable nodes
776 removable_ids->push_back((*it)->getId());
777 }
778 } else {
779 (*it)->setRemovability(false);
780 }
781 }
782 // sort the vector of removable ids
783 std::sort(removable_ids->begin(), removable_ids->end(), std::less<int>());
784 return;
785}
786
787Element* Circuit::addElement(std::string name, double value, Node* pNode, Node* nNode, Element::ElementType et) {
788 // RICE_CHECK: This seems to be a bit of work in progress, is it final?
789 // if ((et == Element::ElementType::RESISTOR_traction_wire && value <= 0) || et == Element::ElementType::ERROR_traction_wire) {
790 if (et == Element::ElementType::RESISTOR_traction_wire && value <= 1e-6) {
791 //due to numeric problems
792 // RICE_TODO @20210409 This epsilon should be specified somewhere as a constant. Or should be a parameter.
793 if (value > -1e-6) {
794 value = 1e-6;
795 WRITE_WARNING(TL("Trying to add resistor element into the overhead wire circuit with resistance < 1e-6. "))
796 } else {
797 WRITE_ERROR(TL("Trying to add resistor element into the overhead wire circuit with resistance < 0. "))
798 return nullptr;
799 }
800 }
801
802 Element* e = getElement(name);
803
804 if (e != nullptr) {
805 //WRITE_ERRORF(TL("The element: '%' already exists."), name);
806 std::cout << "The element: '" + name + "' already exists.";
807 return nullptr;
808 }
809
810 e = new Element(name, et, value);
812 e->setId(lastId);
813 lastId++;
814 circuit_lock.lock();
815 this->voltageSources->push_back(e);
816 circuit_lock.unlock();
817 } else {
818 circuit_lock.lock();
819 this->elements->push_back(e);
820 circuit_lock.unlock();
821 }
822
823 e->setPosNode(pNode);
824 e->setNegNode(nNode);
825
826 pNode->addElement(e);
827 nNode->addElement(e);
828 return e;
829}
830
832 element->getPosNode()->eraseElement(element);
833 element->getNegNode()->eraseElement(element);
834 circuit_lock.lock();
835 this->elements->erase(std::remove(this->elements->begin(), this->elements->end(), element), this->elements->end());
836 circuit_lock.unlock();
837}
838
839void Circuit::replaceAndDeleteNode(Node* unusedNode, Node* newNode) {
840 //replace element node if it is unusedNode
841 for (auto& voltageSource : *voltageSources) {
842 if (voltageSource->getNegNode() == unusedNode) {
843 voltageSource->setNegNode(newNode);
844 newNode->eraseElement(voltageSource);
845 newNode->addElement(voltageSource);
846 }
847 if (voltageSource->getPosNode() == unusedNode) {
848 voltageSource->setPosNode(newNode);
849 newNode->eraseElement(voltageSource);
850 newNode->addElement(voltageSource);
851 }
852 }
853 for (auto& element : *elements) {
854 if (element->getNegNode() == unusedNode) {
855 element->setNegNode(newNode);
856 newNode->eraseElement(element);
857 newNode->addElement(element);
858 }
859 if (element->getPosNode() == unusedNode) {
860 element->setPosNode(newNode);
861 newNode->eraseElement(element);
862 newNode->addElement(element);
863 }
864 }
865
866 //erase unusedNode from nodes vector
867 this->eraseNode(unusedNode);
868
869 //modify id of other elements and nodes
870 int modLastId = this->getLastId() - 1;
871 if (unusedNode->getId() != modLastId) {
872 Node* node_last = this->getNode(modLastId);
873 if (node_last != nullptr) {
874 node_last->setId(unusedNode->getId());
875 } else {
876 Element* elem_last = this->getVoltageSource(modLastId);
877 if (elem_last != nullptr) {
878 elem_last->setId(unusedNode->getId());
879 } else {
880 WRITE_ERROR(TL("The element or node with the last Id was not found in the circuit!"));
881 }
882 }
883 }
884
885 this->decreaseLastId();
886 delete unusedNode;
887}
888
890 for (std::vector<Element*>::iterator it = elements->begin(); it != elements->end(); it++) {
891 if ((*it)->getType() != Element::ElementType::RESISTOR_traction_wire) {
892 (*it)->setEnabled(true);
893 }
894 }
895
896 for (std::vector<Element*>::iterator it = voltageSources->begin(); it != voltageSources->end(); it++) {
897 (*it)->setEnabled(true);
898 }
899 this->iscleaned = true;
900}
901
902bool Circuit::checkCircuit(std::string substationId) {
903 // check empty nodes
904 for (std::vector<Node*>::iterator it = nodes->begin(); it != nodes->end(); it++) {
905 if ((*it)->getNumOfElements() < 2) {
906 //cout << "WARNING: Node [" << (*it)->getName() << "] is connected to less than two elements, please enter other elements.\n";
907 if ((*it)->getNumOfElements() < 1) {
908 return false;
909 }
910 }
911 }
912 // check voltage sources
913 for (std::vector<Element*>::iterator it = voltageSources->begin(); it != voltageSources->end(); it++) {
914 if ((*it)->getPosNode() == nullptr || (*it)->getNegNode() == nullptr) {
915 //cout << "ERROR: Voltage Source [" << (*it)->getName() << "] is connected to less than two nodes, please enter the other end.\n";
916 WRITE_ERRORF(TL("Circuit Voltage Source '%' is connected to less than two nodes, please adjust the definition of the section (with substation '%')."), (*it)->getName(), substationId);
917 return false;
918 }
919 }
920 // check other elements
921 for (std::vector<Element*>::iterator it = elements->begin(); it != elements->end(); it++) {
922 if ((*it)->getPosNode() == nullptr || (*it)->getNegNode() == nullptr) {
923 //cout << "ERROR: Element [" << (*it)->getName() << "] is connected to less than two nodes, please enter the other end.\n";
924 WRITE_ERRORF(TL("Circuit Element '%' is connected to less than two nodes, please adjust the definition of the section (with substation '%')."), (*it)->getName(), substationId);
925 return false;
926 }
927 }
928
929 // check connectivity
930 int num = (int)nodes->size() + getNumVoltageSources() - 1;
931 bool* nodesVisited = new bool[num];
932 for (int i = 0; i < num; i++) {
933 nodesVisited[i] = false;
934 }
935 // TODO: Probably unused
936 // int id = -1;
937 if (!getNode(-1)->isGround()) {
938 //cout << "ERROR: Node id -1 is not the ground \n";
939 WRITE_ERRORF(TL("Circuit Node with id '-1' is not the grounded, please adjust the definition of the section (with substation '%')."), substationId);
940 }
941 std::vector<Node*>* queue = new std::vector<Node*>(0);
942 Node* node = nullptr;
943 Node* neigboringNode = nullptr;
944 //start with (voltageSources->front()->getPosNode())
945 nodesVisited[voltageSources->front()->getId()] = 1;
946 node = voltageSources->front()->getPosNode();
947 queue->push_back(node);
948
949 while (!queue->empty()) {
950 node = queue->back();
951 queue->pop_back();
952 if (!nodesVisited[node->getId()]) {
953 nodesVisited[node->getId()] = true;
954 for (auto it = node->getElements()->begin(); it != node->getElements()->end(); it++) {
955 neigboringNode = (*it)->getTheOtherNode(node);
956 if (!neigboringNode->isGround()) {
957 queue->push_back(neigboringNode);
958 } else if ((*it)->getType() == Element::ElementType::VOLTAGE_SOURCE_traction_wire) {
960 nodesVisited[(*it)->getId()] = 1;
961 } else if ((*it)->getType() == Element::ElementType::RESISTOR_traction_wire) {
962 //cout << "ERROR: The resistor type connects the ground \n";
963 WRITE_ERRORF(TL("A Circuit Resistor Element connects the ground, please adjust the definition of the section (with substation '%')."), substationId);
964 }
965 }
966 }
967 }
968
969 for (int i = 0; i < num; i++) {
970 if (nodesVisited[i] == 0) {
971 //cout << "ERROR: Node or voltage source with id " << (i) << " has been not visited during checking of the circuit => Disconnectivity of the circuit. \n";
972 WRITE_WARNINGF(TL("Circuit Node or Voltage Source with internal id '%' has been not visited during checking of the circuit. The circuit is disconnected, please adjust the definition of the section (with substation '%')."), toString(i), substationId);
973 }
974 }
975
976 return true;
977}
978
980 return (int) voltageSources->size();
981}
static std::mutex circuit_lock
Definition Circuit.cpp:40
#define WRITE_WARNINGF(...)
Definition MsgHandler.h:296
#define WRITE_ERRORF(...)
Definition MsgHandler.h:305
#define WRITE_ERROR(msg)
Definition MsgHandler.h:304
#define WRITE_WARNING(msg)
Definition MsgHandler.h:295
#define TL(string)
Definition MsgHandler.h:315
std::string toString(const T &t, std::streamsize accuracy=gPrecision)
Definition ToString.h:46
std::vector< Node * > * nodes
Definition Circuit.h:66
std::vector< Element * > * getCurrentSources()
Definition Circuit.cpp:178
Node * addNode(std::string name)
Definition Circuit.cpp:42
double getVoltage(std::string name)
Definition Circuit.cpp:76
int getNumVoltageSources()
Definition Circuit.cpp:979
Element * addElement(std::string name, double value, Node *pNode, Node *nNode, Element::ElementType et)
Definition Circuit.cpp:787
int lastId
Definition Circuit.h:70
void eraseNode(Node *node)
Definition Circuit.cpp:62
void cleanUpSP()
Definition Circuit.cpp:889
double getCurrent(std::string name)
Definition Circuit.cpp:68
void unlock()
Definition Circuit.cpp:193
int getLastId()
Definition Circuit.h:240
Element * getElement(std::string name)
Definition Circuit.cpp:116
double circuitCurrentLimit
The electric current limit of the voltage sources.
Definition Circuit.h:74
double getTotalCurrentOfCircuitSources()
The sum of voltage source currents in the circuit.
Definition Circuit.cpp:156
void lock()
Definition Circuit.cpp:189
void detectRemovableNodes(std::vector< int > *removable_ids)
Definition Circuit.cpp:761
double getCurrentLimit()
@ brief Get the electric current limit of this circuit.
Definition Circuit.h:256
std::vector< Element * > * elements
Definition Circuit.h:67
void replaceAndDeleteNode(Node *unusedNode, Node *newNode)
Definition Circuit.cpp:839
Element * getVoltageSource(int id)
Definition Circuit.cpp:139
alphaFlag alphaReason
Definition Circuit.h:107
double getTotalPowerOfCircuitSources()
The sum of voltage source powers in the circuit.
Definition Circuit.cpp:148
void deployResults(double *vals, std::vector< int > *removable_ids)
Definition Circuit.cpp:488
double getResistance(std::string name)
Definition Circuit.cpp:90
double alphaBest
Best alpha scaling value.
Definition Circuit.h:86
bool checkCircuit(std::string substationId="")
Definition Circuit.cpp:902
std::vector< Element * > * voltageSources
Definition Circuit.h:68
@ ALPHA_VOLTAGE_LIMITS
The scaling alpha is applied (is not one] due to voltage limits.
Definition Circuit.h:102
@ ALPHA_NOT_APPLIED
The scaling alpha is not applied (is one)
Definition Circuit.h:98
@ ALPHA_CURRENT_LIMITS
The scaling alpha is applied (is not one) due to current limits.
Definition Circuit.h:100
@ ALPHA_NOT_CONVERGING
The Newton-Rhapson method has reached maximum iterations and no solution of circuit has been found wi...
Definition Circuit.h:104
void decreaseLastId()
Definition Circuit.h:245
Node * getNode(std::string name)
Definition Circuit.cpp:98
bool iscleaned
Definition Circuit.h:71
std::string & getCurrentsOfCircuitSource(std::string &currents)
List of currents of voltage sources as a string.
Definition Circuit.cpp:165
void eraseElement(Element *element)
Definition Circuit.cpp:831
void setId(int id)
Definition Element.cpp:133
ElementType getType()
Definition Element.cpp:119
double getResistance()
Definition Element.cpp:99
void setNegNode(Node *node)
Definition Element.cpp:130
Node * getNegNode()
Definition Element.cpp:115
double getCurrent()
Definition Element.cpp:85
void setPosNode(Node *node)
Definition Element.cpp:126
Node * getPosNode()
Definition Element.cpp:112
double getVoltage()
Definition Element.cpp:76
bool isEnabled()
Definition Element.cpp:148
double getPower()
Definition Element.cpp:105
Node * getTheOtherNode(Node *node)
Definition Element.cpp:138
ElementType
Definition Element.h:53
@ RESISTOR_traction_wire
Definition Element.h:54
@ CURRENT_SOURCE_traction_wire
Definition Element.h:55
@ VOLTAGE_SOURCE_traction_wire
Definition Element.h:56
@ ERROR_traction_wire
Definition Element.h:57
static bool gOverheadWireCurrentLimits
Definition MSGlobals.h:127
Definition Node.h:39
void setVoltage(double volt)
Definition Node.cpp:57
void setGround(bool isground)
Definition Node.cpp:73
void setNumMatrixCol(int num)
Definition Node.cpp:93
int getId()
Definition Node.cpp:77
Element * getAnOtherElement(Element *element)
Definition Node.cpp:109
bool isGround()
Definition Node.cpp:69
void addElement(Element *element)
Definition Node.cpp:44
void eraseElement(Element *element)
Definition Node.cpp:48
double getVoltage()
Definition Node.cpp:53
void setId(int id)
Definition Node.cpp:81
std::vector< Element * > * getElements()
Definition Node.cpp:101
bool isRemovable() const
Definition Node.h:68