Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/utils/traction_wire/Circuit.cpp
169678 views
1
/****************************************************************************/
2
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
// Copyright (C) 2001-2025 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
/****************************************************************************/
14
/// @file Circuit.cpp
15
/// @author Jakub Sevcik (RICE)
16
/// @author Jan Prikryl (RICE)
17
/// @date 2019-12-15
18
///
19
/// @note based on console-based C++ DC circuits simulator,
20
/// https://github.com/rka97/Circuits-Solver by
21
/// Ahmad Khaled, Ahmad Essam, Omnia Zakaria, Mary Nader
22
/// and available under MIT license, see https://github.com/rka97/Circuits-Solver/blob/master/LICENSE
23
///
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>
33
#include <utils/common/MsgHandler.h>
34
#include <utils/common/ToString.h>
35
#include <microsim/MSGlobals.h>
36
#include "Element.h"
37
#include "Node.h"
38
#include "Circuit.h"
39
40
static std::mutex circuit_lock;
41
42
Node* 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
62
void Circuit::eraseNode(Node* node) {
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
68
double 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
76
double 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
90
double Circuit::getResistance(std::string name) {
91
Element* tElement = getElement(name);
92
if (tElement == nullptr) {
93
return -1;
94
}
95
return tElement->getResistance();
96
}
97
98
Node* 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
107
Node* Circuit::getNode(int id) {
108
for (Node* const node : *nodes) {
109
if (node->getId() == id) {
110
return node;
111
}
112
}
113
return nullptr;
114
}
115
116
Element* 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
130
Element* Circuit::getElement(int id) {
131
for (Element* const el : *elements) {
132
if (el->getId() == id) {
133
return el;
134
}
135
}
136
return getVoltageSource(id);
137
}
138
139
Element* Circuit::getVoltageSource(int id) {
140
for (Element* const voltageSource : *voltageSources) {
141
if (voltageSource->getId() == id) {
142
return voltageSource;
143
}
144
}
145
return nullptr;
146
}
147
148
double Circuit::getTotalPowerOfCircuitSources() {
149
double power = 0;
150
for (Element* const voltageSource : *voltageSources) {
151
power += voltageSource->getPower();
152
}
153
return power;
154
}
155
156
double Circuit::getTotalCurrentOfCircuitSources() {
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?
165
std::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
178
std::vector<Element*>* Circuit::getCurrentSources() {
179
std::vector<Element*>* vsources = new std::vector<Element*>(0);
180
for (Element* const el : *elements) {
181
if (el->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire) {
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
189
void Circuit::lock() {
190
circuit_lock.lock();
191
}
192
193
void Circuit::unlock() {
194
circuit_lock.unlock();
195
}
196
197
#ifdef HAVE_EIGEN
198
void 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
209
bool 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
274
alphaReason = ALPHA_NOT_APPLIED;
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) {
380
alphaReason = ALPHA_CURRENT_LIMITS;
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) {
390
alphaReason = ALPHA_VOLTAGE_LIMITS;
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) {
403
alphaReason = ALPHA_NOT_CONVERGING;
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
488
void 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();
572
if (el->getType() == Element::ElementType::VOLTAGE_SOURCE_traction_wire) {
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
581
Circuit::Circuit() {
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
590
Circuit::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
600
bool 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
618
bool Circuit::solve() {
619
if (!iscleaned) {
620
cleanUpSP();
621
}
622
return this->_solveNRmethod();
623
}
624
625
bool 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
683
bool 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
698
bool 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()) {
703
case Element::ElementType::RESISTOR_traction_wire:
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;
723
case Element::ElementType::CURRENT_SOURCE_traction_wire:
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;
736
case Element::ElementType::VOLTAGE_SOURCE_traction_wire:
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;
747
case Element::ElementType::ERROR_traction_wire:
748
return false;
749
break;
750
}
751
}
752
return true;
753
}
754
#endif
755
756
/**
757
* Select removable nodes, i.e. nodes that are NOT the ground of the circuit
758
* and that have exactly two resistor elements connected. Ids of those
759
* removable nodes are added into the internal vector `removable_ids`.
760
*/
761
void 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
787
Element* 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);
811
if (e->getType() == Element::ElementType::VOLTAGE_SOURCE_traction_wire) {
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
831
void Circuit::eraseElement(Element* element) {
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
839
void 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
889
void Circuit::cleanUpSP() {
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
902
bool 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) {
959
/// there used to be == 1 which was probably a typo ... check!
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
979
int Circuit::getNumVoltageSources() {
980
return (int) voltageSources->size();
981
}
982
983