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