LCOV - code coverage report
Current view: top level - src/utils/traction_wire - Circuit.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 75.1 % 454 341
Test Date: 2026-07-05 15:55:58 Functions: 75.8 % 33 25

            Line data    Source code
       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              : /****************************************************************************/
      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 "Element.h"
      36              : #include "Node.h"
      37              : #include "Circuit.h"
      38              : 
      39              : static std::mutex circuit_lock;
      40              : 
      41              : bool Circuit::myCurrentLimits = false;
      42              : 
      43          264 : Node* Circuit::addNode(std::string name) {
      44          528 :     if (getNode(name) != nullptr) {
      45            0 :         WRITE_ERRORF(TL("The node: '%' already exists."), name);
      46            0 :         return nullptr;
      47              :     }
      48              : 
      49          264 :     if (nodes->size() == 0) {
      50            8 :         lastId = -1;
      51              :     }
      52          528 :     Node* tNode = new Node(name, this->lastId);
      53          264 :     if (lastId == -1) {
      54            8 :         tNode->setGround(true);
      55              :     }
      56          264 :     this->lastId++;
      57          264 :     circuit_lock.lock();
      58          264 :     this->nodes->push_back(tNode);
      59              :     circuit_lock.unlock();
      60          264 :     return tNode;
      61              : }
      62              : 
      63          223 : void Circuit::eraseNode(Node* node) {
      64          223 :     circuit_lock.lock();
      65          223 :     this->nodes->erase(std::remove(this->nodes->begin(), this->nodes->end(), node), this->nodes->end());
      66              :     circuit_lock.unlock();
      67          223 : }
      68              : 
      69            0 : double Circuit::getCurrent(std::string name) {
      70            0 :     Element* tElement = getElement(name);
      71            0 :     if (tElement == nullptr) {
      72              :         return DBL_MAX;
      73              :     }
      74            0 :     return tElement->getCurrent();
      75              : }
      76              : 
      77            0 : double Circuit::getVoltage(std::string name) {
      78            0 :     Element* tElement = getElement(name);
      79            0 :     if (tElement == nullptr) {
      80            0 :         Node* node = getNode(name);
      81            0 :         if (node != nullptr) {
      82            0 :             return node->getVoltage();
      83              :         } else {
      84              :             return DBL_MAX;
      85              :         }
      86              :     } else {
      87            0 :         return tElement->getVoltage();
      88              :     }
      89              : }
      90              : 
      91            0 : double Circuit::getResistance(std::string name) {
      92            0 :     Element* tElement = getElement(name);
      93            0 :     if (tElement == nullptr) {
      94              :         return -1;
      95              :     }
      96            0 :     return tElement->getResistance();
      97              : }
      98              : 
      99          493 : Node* Circuit::getNode(std::string name) {
     100         3342 :     for (Node* const node : *nodes) {
     101         3070 :         if (node->getName() == name) {
     102              :             return node;
     103              :         }
     104              :     }
     105              :     return nullptr;
     106              : }
     107              : 
     108         5261 : Node* Circuit::getNode(int id) {
     109        47820 :     for (Node* const node : *nodes) {
     110        47438 :         if (node->getId() == id) {
     111              :             return node;
     112              :         }
     113              :     }
     114              :     return nullptr;
     115              : }
     116              : 
     117          614 : Element* Circuit::getElement(std::string name) {
     118         5821 :     for (Element* const el : *elements) {
     119         5387 :         if (el->getName() == name) {
     120              :             return el;
     121              :         }
     122              :     }
     123          869 :     for (Element* const voltageSource : *voltageSources) {
     124          435 :         if (voltageSource->getName() == name) {
     125              :             return voltageSource;
     126              :         }
     127              :     }
     128              :     return nullptr;
     129              : }
     130              : 
     131          382 : Element* Circuit::getElement(int id) {
     132         5380 :     for (Element* const el : *elements) {
     133         4998 :         if (el->getId() == id) {
     134              :             return el;
     135              :         }
     136              :     }
     137          382 :     return getVoltageSource(id);
     138              : }
     139              : 
     140          382 : Element* Circuit::getVoltageSource(int id) {
     141          404 :     for (Element* const voltageSource : *voltageSources) {
     142          404 :         if (voltageSource->getId() == id) {
     143              :             return voltageSource;
     144              :         }
     145              :     }
     146              :     return nullptr;
     147              : }
     148              : 
     149          180 : double Circuit::getTotalPowerOfCircuitSources() {
     150              :     double power = 0;
     151          371 :     for (Element* const voltageSource : *voltageSources) {
     152          191 :         power += voltageSource->getPower();
     153              :     }
     154          180 :     return power;
     155              : }
     156              : 
     157          180 : double Circuit::getTotalCurrentOfCircuitSources() {
     158              :     double current = 0;
     159          371 :     for (Element* const voltageSource : *voltageSources) {
     160          191 :         current += voltageSource->getCurrent();
     161              :     }
     162          180 :     return current;
     163              : }
     164              : 
     165              : // RICE_CHECK: Locking removed?
     166          180 : std::string& Circuit::getCurrentsOfCircuitSource(std::string& currents) {
     167              :     //circuit_lock.lock();
     168              :     currents.clear();
     169          371 :     for (Element* const voltageSource : *voltageSources) {
     170          382 :         currents += toString(voltageSource->getCurrent(), 4) + " ";
     171              :     }
     172          180 :     if (!currents.empty()) {
     173              :         currents.pop_back();
     174              :     }
     175              :     //circuit_lock.unlock();
     176          180 :     return currents;
     177              : }
     178              : 
     179            0 : std::vector<Element*>* Circuit::getCurrentSources() {
     180            0 :     std::vector<Element*>* vsources = new std::vector<Element*>(0);
     181            0 :     for (Element* const el : *elements) {
     182            0 :         if (el->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire) {
     183              :             //if ((*it)->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire && !isnan((*it)->getPowerWanted())) {
     184            0 :             vsources->push_back(el);
     185              :         }
     186              :     }
     187            0 :     return vsources;
     188              : }
     189              : 
     190            0 : void Circuit::lock() {
     191            0 :     circuit_lock.lock();
     192            0 : }
     193              : 
     194            0 : void Circuit::unlock() {
     195              :     circuit_lock.unlock();
     196            0 : }
     197              : 
     198              : #ifdef HAVE_EIGEN
     199         1861 : void Circuit::removeColumn(Eigen::MatrixXd& matrix, int colToRemove) {
     200         1861 :     const int numRows = (int)matrix.rows();
     201         1861 :     const int numCols = (int)matrix.cols() - 1;
     202              : 
     203         1861 :     if (colToRemove < numCols) {
     204         1861 :         matrix.block(0, colToRemove, numRows, numCols - colToRemove) = matrix.rightCols(numCols - colToRemove);
     205              :     }
     206              : 
     207         1861 :     matrix.conservativeResize(numRows, numCols);
     208         1861 : }
     209              : 
     210          180 : bool Circuit::solveEquationsNRmethod(double* eqn, double* vals, std::vector<int>* removable_ids) {
     211              :     // removable_ids includes nodes with voltage source already
     212          180 :     int numofcolumn = (int)voltageSources->size() + (int)nodes->size() - 1;
     213          180 :     int numofeqs = numofcolumn - (int)removable_ids->size();
     214              : 
     215              :     // map equations into matrix A
     216          180 :     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         2041 :     for (std::vector<int>::reverse_iterator it = removable_ids->rbegin(); it != removable_ids->rend(); ++it) {
     222         1861 :         id = (*it >= 0 ? *it : -(*it));
     223         1861 :         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         2804 :     for (int i = 0; i < numofcolumn; i++) {
     233         2624 :         tNode = getNode(i);
     234         2624 :         if (tNode != nullptr) {
     235         2433 :             if (tNode->isRemovable()) {
     236         1670 :                 tNode->setNumMatrixCol(-1);
     237         1670 :                 continue;
     238              :             } else {
     239          763 :                 if (j > numofeqs) {
     240            0 :                     WRITE_ERROR(TL("Index of renumbered node exceeded the reduced number of equations."));
     241            0 :                     break;
     242              :                 }
     243          763 :                 tNode->setNumMatrixCol(j);
     244          763 :                 j++;
     245          763 :                 continue;
     246              :             }
     247              :         } else {
     248          191 :             tElem = getElement(i);
     249          191 :             if (tElem != nullptr) {
     250          191 :                 if (j > numofeqs) {
     251            0 :                     WRITE_ERROR(TL("Index of renumbered element exceeded the reduced number of equations."));
     252            0 :                     break;
     253              :                 }
     254          191 :                 continue;
     255              :             }
     256              :         }
     257              :         // tNode == nullptr && tElem == nullptr
     258            0 :         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          360 :     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          180 :     double alpha = 1;
     272              :     // the best (maximum) value of alpha that guarantees the existence of solution
     273          180 :     alphaBest = 0;
     274              :     // reason why is alpha not 1
     275          180 :     alphaReason = ALPHA_NOT_APPLIED;
     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          360 :     if (x.maxCoeff() > 10e6 || x.minCoeff() < -10e6) {
     287            0 :         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         1832 :             for (int i = 0; i < numofeqs - (int) voltageSources->size(); i++) {
     300         1390 :                 vals[i] = 0;
     301              :             }
     302              :             J = A;
     303              : 
     304              :             int i = 0;
     305         6980 :             for (auto& node : *nodes) {
     306         6538 :                 if (node->isGround() || node->isRemovable() || node->getNumMatrixRow() == -2) {
     307         5148 :                     continue;
     308              :                 }
     309         1390 :                 if (node->getNumMatrixRow() != i) {
     310            0 :                     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         3748 :                 for (auto it_element = node->getElements()->begin(); it_element != node->getElements()->end(); it_element++) {
     315         2358 :                     if ((*it_element)->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire) {
     316              :                         // if the element is current source
     317          442 :                         if ((*it_element)->isEnabled()) {
     318              :                             double diff_voltage;
     319          442 :                             int PosNode_NumACol = (*it_element)->getPosNode()->getNumMatrixCol();
     320          442 :                             int NegNode_NumACol = (*it_element)->getNegNode()->getNumMatrixCol();
     321              :                             // compute voltage on current source
     322          442 :                             if (PosNode_NumACol == -1) {
     323              :                                 // if the positive node is the ground => U = 0 - phi(NegNode)
     324            0 :                                 diff_voltage = -x[NegNode_NumACol];
     325          442 :                             } else if (NegNode_NumACol == -1) {
     326              :                                 // if the negative node is the ground => U = phi(PosNode) - 0
     327          442 :                                 diff_voltage = x[PosNode_NumACol];
     328              :                             } else {
     329              :                                 // U = phi(PosNode) - phi(NegNode)
     330            0 :                                 diff_voltage = (x[PosNode_NumACol] - x[NegNode_NumACol]);
     331              :                             }
     332              : 
     333          442 :                             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          442 :                                 vals[i] -= alpha * (*it_element)->getPowerWanted() / diff_voltage;
     336          442 :                                 (*it_element)->setCurrent(-alpha * (*it_element)->getPowerWanted() / diff_voltage);
     337          442 :                                 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          442 :                                     J(i, PosNode_NumACol) -= alpha * (*it_element)->getPowerWanted() / diff_voltage / diff_voltage;
     340              :                                 }
     341          442 :                                 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            0 :                                     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            0 :                                 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            0 :                                 WRITE_WARNING(TL("The negative node of current source is not the ground."))
     353            0 :                                 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            0 :                                     J(i, PosNode_NumACol) += alpha * (*it_element)->getPowerWanted() / diff_voltage / diff_voltage;
     356              :                                 }
     357            0 :                                 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            0 :                                     J(i, NegNode_NumACol) -= alpha * (*it_element)->getPowerWanted() / diff_voltage / diff_voltage;
     360              :                                 }
     361              :                             }
     362              :                         }
     363              :                     }
     364              :                 }
     365         1390 :                 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         1832 :             for (i = 0; i < numofeqs - (int)voltageSources->size(); i++) {
     375         1390 :                 currentSumActual -= vals[i];
     376              :             }
     377              :             // RICE_TODO @20210409 This epsilon should be specified somewhere as a constant. Or should be a parameter.
     378          442 :             if ((A * x - b).norm() < 1e-6) {
     379              :                 //current limits
     380          180 :                 if (currentSumActual > getCurrentLimit() && Circuit::myCurrentLimits) {
     381            0 :                     alphaReason = ALPHA_CURRENT_LIMITS;
     382            0 :                     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          360 :                 if (x.maxCoeff() > voltageSources->front()->getVoltage() * 1.2 || x.minCoeff() < voltageSources->front()->getVoltage() * 0.7) {
     391            0 :                     alphaReason = ALPHA_VOLTAGE_LIMITS;
     392            0 :                     alpha_notSolution.push_back(alpha);
     393              :                     if (x_best_exist) {
     394              :                         x = x_best;
     395              :                     }
     396              :                     break;
     397              :                 }
     398              : 
     399          180 :                 alphaBest = alpha;
     400              :                 x_best = x;
     401              :                 x_best_exist = true;
     402          180 :                 break;
     403          262 :             } else if (iterNR == max_iter_of_NR) {
     404            0 :                 alphaReason = ALPHA_NOT_CONVERGING;
     405            0 :                 alpha_notSolution.push_back(alpha);
     406              :                 if (x_best_exist) {
     407              :                     x = x_best;
     408              :                 }
     409              :                 break;
     410              :             }
     411              : 
     412              :             // Newton=Rhapson iteration
     413          262 :             dx = -J.colPivHouseholderQr().solve(A * x - b);
     414          262 :             x = x + dx;
     415          262 :             ++iterNR;
     416          262 :         }
     417              : 
     418          180 :         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            0 :         if ((alpha_notSolution.back() - alphaBest) < alpha_res) {
     424            0 :             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            0 :             alpha_res = alpha_res / 10;
     429              :             // RICE_TODO @20210409 This epsilon should be specified somewhere as a constant. Or should be a parameter.
     430            0 :             if (alpha_res < 5e-5) {
     431              :                 break;
     432              :             }
     433            0 :             alpha = alpha_notSolution.back();
     434              :             alpha_notSolution.pop_back();
     435            0 :             continue;
     436              :         }
     437              : 
     438            0 :         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          943 :     for (int i = 0; i < numofeqs; i++) {
     443          763 :         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         2793 :     for (auto& node : *nodes) {
     450         2613 :         if (node->isGround() || node->isRemovable() || node->getNumMatrixRow() == -2) {
     451         2041 :             continue;
     452              :         }
     453          572 :         if (node->getNumMatrixRow() != i) {
     454            0 :             WRITE_ERROR(TL("wrongly assigned row of matrix A during solving the circuit"));
     455              :         }
     456         1546 :         for (auto it_element = node->getElements()->begin(); it_element != node->getElements()->end(); it_element++) {
     457          974 :             if ((*it_element)->getType() == Element::ElementType::CURRENT_SOURCE_traction_wire) {
     458          180 :                 if ((*it_element)->isEnabled()) {
     459              :                     double diff_voltage;
     460          180 :                     int PosNode_NumACol = (*it_element)->getPosNode()->getNumMatrixCol();
     461          180 :                     int NegNode_NumACol = (*it_element)->getNegNode()->getNumMatrixCol();
     462          180 :                     if (PosNode_NumACol == -1) {
     463            0 :                         diff_voltage = -x_best[NegNode_NumACol];
     464          180 :                     } else if (NegNode_NumACol == -1) {
     465          180 :                         diff_voltage = x_best[PosNode_NumACol];
     466              :                     } else {
     467            0 :                         diff_voltage = (x_best[PosNode_NumACol] - x_best[NegNode_NumACol]);
     468              :                     }
     469              : 
     470          180 :                     if ((*it_element)->getPosNode() == node) {
     471          180 :                         (*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            0 :                         WRITE_WARNING(TL("The negative node of current source is not the ground."))
     478              :                     }
     479              :                 }
     480              :             }
     481              :         }
     482          572 :         i++;
     483              :     }
     484              : 
     485          180 :     return true;
     486          180 : }
     487              : #endif
     488              : 
     489          180 : void Circuit::deployResults(double* vals, std::vector<int>* removable_ids) {
     490              :     // vals are the solution x
     491              : 
     492          180 :     int numofcolumn = (int)voltageSources->size() + (int)nodes->size() - 1;
     493          180 :     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         2804 :     for (int i = 0; i < numofcolumn; i++) {
     500         2624 :         tNode = getNode(i);
     501         2624 :         if (tNode != nullptr)
     502         2433 :             if (tNode->isRemovable()) {
     503         1670 :                 continue;
     504              :             } else {
     505          763 :                 if (j > numofeqs) {
     506            0 :                     WRITE_ERROR(TL("Results deployment during circuit evaluation was unsuccessful."));
     507            0 :                     break;
     508              :                 }
     509          763 :                 tNode->setVoltage(vals[j]);
     510          763 :                 j++;
     511          763 :                 continue;
     512              :             } else {
     513          191 :             tElem = getElement(i);
     514          191 :             if (tElem != nullptr) {
     515          191 :                 if (j > numofeqs) {
     516            0 :                     WRITE_ERROR(TL("Results deployment during circuit evaluation was unsuccessful."));
     517            0 :                     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          191 :                 continue;
     522              :             }
     523              :         }
     524            0 :         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         2793 :     for (Node* const node : *nodes) {
     533         2613 :         if (!node->isRemovable()) {
     534          943 :             continue;
     535              :         }
     536         1670 :         if (node->getElements()->size() != 2) {
     537            0 :             continue;
     538              :         }
     539              : 
     540         1670 :         el1 = node->getElements()->front();
     541         1670 :         el2 = node->getElements()->back();
     542         1670 :         nextNONremovableNode1 = el1->getTheOtherNode(node);
     543         1670 :         nextNONremovableNode2 = el2->getTheOtherNode(node);
     544         1670 :         double x = el1->getResistance();
     545         1670 :         double y = el2->getResistance();
     546              : 
     547         2273 :         while (nextNONremovableNode1->isRemovable()) {
     548          603 :             el1 = nextNONremovableNode1->getAnOtherElement(el1);
     549          603 :             x += el1->getResistance();
     550          603 :             nextNONremovableNode1 = el1->getTheOtherNode(nextNONremovableNode1);
     551              :         }
     552              : 
     553         4133 :         while (nextNONremovableNode2->isRemovable()) {
     554         2463 :             el2 = nextNONremovableNode2->getAnOtherElement(el2);
     555         2463 :             y += el2->getResistance();
     556         2463 :             nextNONremovableNode2 = el2->getTheOtherNode(nextNONremovableNode2);
     557              :         }
     558              : 
     559         1670 :         x = x / (x + y);
     560         1670 :         y = ((1 - x) * nextNONremovableNode1->getVoltage()) + (x * nextNONremovableNode2->getVoltage());
     561         1670 :         node->setVoltage(((1 - x)*nextNONremovableNode1->getVoltage()) + (x * nextNONremovableNode2->getVoltage()));
     562         1670 :         node->setRemovability(false);
     563              :     }
     564              : 
     565              :     // Update the electric currents for voltage sources (based on Kirchhof's law: current out = current in)
     566          371 :     for (Element* const voltageSource : *voltageSources) {
     567              :         double currentSum = 0;
     568          754 :         for (Element* const el : *voltageSource->getPosNode()->getElements()) {
     569              :             // loop over all elements on PosNode excluding the actual voltage source it
     570          563 :             if (el != voltageSource) {
     571              :                 //currentSum += el->getCurrent();
     572          372 :                 currentSum += (voltageSource->getPosNode()->getVoltage() - el->getTheOtherNode(voltageSource->getPosNode())->getVoltage()) / el->getResistance();
     573          372 :                 if (el->getType() == Element::ElementType::VOLTAGE_SOURCE_traction_wire) {
     574            0 :                     WRITE_WARNING(TL("Cannot assign unambigous electric current value to two voltage sources connected in parallel at the same node."));
     575              :                 }
     576              :             }
     577              :         }
     578          191 :         voltageSource->setCurrent(currentSum);
     579              :     }
     580          180 : }
     581              : 
     582            0 : Circuit::Circuit() {
     583            0 :     nodes = new std::vector<Node*>(0);
     584            0 :     elements = new std::vector<Element*>(0);
     585            0 :     voltageSources = new std::vector<Element*>(0);
     586            0 :     lastId = 0;
     587            0 :     iscleaned = true;
     588            0 :     circuitCurrentLimit = INFINITY;
     589            0 : }
     590              : 
     591           17 : Circuit::Circuit(double currentLimit) {
     592           17 :     nodes = new std::vector<Node*>(0);
     593           17 :     elements = new std::vector<Element*>(0);
     594           17 :     voltageSources = new std::vector<Element*>(0);
     595           17 :     lastId = 0;
     596           17 :     iscleaned = true;
     597           17 :     circuitCurrentLimit = currentLimit;
     598           17 : }
     599              : 
     600              : #ifdef HAVE_EIGEN
     601          180 : bool Circuit::_solveNRmethod() {
     602          180 :     double* eqn = nullptr;
     603          180 :     double* vals = nullptr;
     604              :     std::vector<int> removable_ids;
     605              : 
     606          180 :     detectRemovableNodes(&removable_ids);
     607          180 :     createEquationsNRmethod(eqn, vals, &removable_ids);
     608          180 :     if (!solveEquationsNRmethod(eqn, vals, &removable_ids)) {
     609              :         return false;
     610              :     }
     611              :     // vals are now the solution x of the circuit
     612          180 :     deployResults(vals, &removable_ids);
     613              : 
     614          180 :     delete[] eqn;
     615          180 :     delete[] vals;
     616              :     return true;
     617          180 : }
     618              : 
     619          180 : bool Circuit::solve() {
     620          180 :     if (!iscleaned) {
     621            0 :         cleanUpSP();
     622              :     }
     623          180 :     return this->_solveNRmethod();
     624              : }
     625              : 
     626          180 : bool 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          180 :     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          180 :     int m = n - (int)(removable_ids->size() + voltageSources->size());
     635              : 
     636              :     // allocate and initialize zero matrix eqs and vector vals
     637          180 :     eqs = new double[m * n];
     638          180 :     vals = new double[m];
     639              : 
     640          943 :     for (int i = 0; i < m; i++) {
     641          763 :         vals[i] = 0;
     642        11603 :         for (int j = 0; j < n; j++) {
     643        10840 :             eqs[i * n + j] = 0;
     644              :         }
     645              :     }
     646              : 
     647              :     // loop over all nodes
     648              :     int i = 0;
     649         2793 :     for (std::vector<Node*>::iterator it = nodes->begin(); it != nodes->end(); it++) {
     650         2613 :         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         1850 :             (*it)->setNumMatrixRow(-1);
     653         1850 :             continue;
     654              :         }
     655              :         assert(i < m);
     656              :         // constitute the equation corresponding to node it, add all passed voltage source elements into removable_ids
     657          763 :         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          763 :         if (noVoltageSource) {
     660          572 :             (*it)->setNumMatrixRow(i);
     661          572 :             i++;
     662              :         } else {
     663          191 :             (*it)->setNumMatrixRow(-2);
     664          191 :             vals[i] = 0;
     665         2903 :             for (int j = 0; j < n; j++) {
     666         2712 :                 eqs[n * i + j] = 0;
     667              :             }
     668              :         }
     669              :     }
     670              : 
     671              :     // removable_ids includes nodes with voltage source already
     672          180 :     std::sort(removable_ids->begin(), removable_ids->end(), std::less<int>());
     673              : 
     674              : 
     675          371 :     for (std::vector<Element*>::iterator it = voltageSources->begin(); it != voltageSources->end(); it++) {
     676              :         assert(i < m);
     677          191 :         createEquation((*it), (eqs + n * i), vals[i]);
     678          191 :         i++;
     679              :     }
     680              : 
     681          180 :     return true;
     682              : }
     683              : 
     684          191 : bool Circuit::createEquation(Element* vsource, double* eqn, double& val) {
     685          191 :     if (!vsource->getPosNode()->isGround()) {
     686          191 :         eqn[vsource->getPosNode()->getId()] = 1;
     687              :     }
     688          191 :     if (!vsource->getNegNode()->isGround()) {
     689            0 :         eqn[vsource->getNegNode()->getId()] = -1;
     690              :     }
     691          191 :     if (vsource->isEnabled()) {
     692          191 :         val = vsource->getVoltage();
     693              :     } else {
     694            0 :         val = 0;
     695              :     }
     696          191 :     return true;
     697              : }
     698              : 
     699          763 : bool Circuit::createEquationNRmethod(Node* node, double* eqn, double& val, std::vector<int>* removable_ids) {
     700              :     // loop over all elements connected to the node
     701         1896 :     for (std::vector<Element*>::iterator it = node->getElements()->begin(); it != node->getElements()->end(); it++) {
     702              :         double x;
     703         1324 :         switch ((*it)->getType()) {
     704              :             case Element::ElementType::RESISTOR_traction_wire:
     705          953 :                 if ((*it)->isEnabled()) {
     706          953 :                     x = (*it)->getResistance();
     707              :                     // go through all neighboring removable nodes and sum resistance of resistors in the serial branch
     708          953 :                     Node* nextNONremovableNode = (*it)->getTheOtherNode(node);
     709          953 :                     Element* nextSerialResistor = *it;
     710         3535 :                     while (nextNONremovableNode->isRemovable()) {
     711         2582 :                         nextSerialResistor = nextNONremovableNode->getAnOtherElement(nextSerialResistor);
     712         2582 :                         x += nextSerialResistor->getResistance();
     713         2582 :                         nextNONremovableNode = nextSerialResistor->getTheOtherNode(nextNONremovableNode);
     714              :                     }
     715              :                     // compute inverse value and place/add this value at proper places in eqn
     716          953 :                     x = 1 / x;
     717          953 :                     eqn[node->getId()] += x;
     718              : 
     719          953 :                     if (!nextNONremovableNode->isGround()) {
     720          953 :                         eqn[nextNONremovableNode->getId()] -= x;
     721              :                     }
     722              :                 }
     723              :                 break;
     724              :             case Element::ElementType::CURRENT_SOURCE_traction_wire:
     725          180 :                 if ((*it)->isEnabled()) {
     726              :                     // initialize current in current source
     727          180 :                     if ((*it)->getPosNode() == node) {
     728          180 :                         x = -(*it)->getPowerWanted() / voltageSources->front()->getVoltage();
     729              :                     } else {
     730            0 :                         x = (*it)->getPowerWanted() / voltageSources->front()->getVoltage();
     731              :                     }
     732              :                 } else {
     733              :                     x = 0;
     734              :                 }
     735          180 :                 val += x;
     736          180 :                 break;
     737              :             case Element::ElementType::VOLTAGE_SOURCE_traction_wire:
     738          191 :                 if ((*it)->getPosNode() == node) {
     739              :                     x = -1;
     740              :                 } else {
     741              :                     x = 1;
     742              :                 }
     743          191 :                 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          191 :                 removable_ids->push_back((*it)->getId());
     746          191 :                 return false;
     747              :             case Element::ElementType::ERROR_traction_wire:
     748              :                 return false;
     749              :         }
     750              :     }
     751              :     return true;
     752              : }
     753              : #endif
     754              : 
     755              : /**
     756              :  * Select removable nodes, i.e. nodes that are NOT the ground of the circuit
     757              :  * and that have exactly two resistor elements connected. Ids of those
     758              :  * removable nodes are added into the internal vector `removable_ids`.
     759              :  */
     760          180 : void Circuit::detectRemovableNodes(std::vector<int>* removable_ids) {
     761              :     // loop over all nodes in the circuit
     762         2793 :     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         2613 :         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         1691 :             (*it)->setRemovability(true);
     767         5031 :             for (std::vector<Element*>::iterator it2 = (*it)->getElements()->begin(); it2 != (*it)->getElements()->end(); it2++) {
     768         3361 :                 if ((*it2)->getType() != Element::ElementType::RESISTOR_traction_wire) {
     769           21 :                     (*it)->setRemovability(false);
     770              :                     break;
     771              :                 }
     772              :             }
     773         1691 :             if ((*it)->isRemovable()) {
     774              :                 //if the node is removable add pointer into the vector of removable nodes
     775         1670 :                 removable_ids->push_back((*it)->getId());
     776              :             }
     777              :         } else {
     778          922 :             (*it)->setRemovability(false);
     779              :         }
     780              :     }
     781              :     // sort the vector of removable ids
     782          180 :     std::sort(removable_ids->begin(), removable_ids->end(), std::less<int>());
     783          180 :     return;
     784              : }
     785              : 
     786          434 : Element* 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          434 :     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            0 :         if (value > -1e-6) {
     793              :             value = 1e-6;
     794            0 :             WRITE_WARNING(TL("Trying to add resistor element into the overhead wire circuit with resistance < 1e-6. "))
     795              :         } else {
     796            0 :             WRITE_ERROR(TL("Trying to add resistor element into the overhead wire circuit with resistance < 0. "))
     797            0 :             return nullptr;
     798              :         }
     799              :     }
     800              : 
     801          434 :     Element* e = getElement(name);
     802              : 
     803          434 :     if (e != nullptr) {
     804              :         //WRITE_ERRORF(TL("The element: '%' already exists."), name);
     805            0 :         std::cout << "The element: '" + name + "' already exists.";
     806            0 :         return nullptr;
     807              :     }
     808              : 
     809          868 :     e = new Element(name, et, value);
     810          434 :     if (e->getType() == Element::ElementType::VOLTAGE_SOURCE_traction_wire) {
     811           11 :         e->setId(lastId);
     812           11 :         lastId++;
     813           11 :         circuit_lock.lock();
     814           11 :         this->voltageSources->push_back(e);
     815              :         circuit_lock.unlock();
     816              :     } else {
     817          423 :         circuit_lock.lock();
     818          423 :         this->elements->push_back(e);
     819              :         circuit_lock.unlock();
     820              :     }
     821              : 
     822          434 :     e->setPosNode(pNode);
     823          434 :     e->setNegNode(nNode);
     824              : 
     825          434 :     pNode->addElement(e);
     826          434 :     nNode->addElement(e);
     827              :     return e;
     828              : }
     829              : 
     830          412 : void Circuit::eraseElement(Element* element) {
     831          412 :     element->getPosNode()->eraseElement(element);
     832          412 :     element->getNegNode()->eraseElement(element);
     833          412 :     circuit_lock.lock();
     834          412 :     this->elements->erase(std::remove(this->elements->begin(), this->elements->end(), element), this->elements->end());
     835              :     circuit_lock.unlock();
     836          412 : }
     837              : 
     838            5 : void Circuit::replaceAndDeleteNode(Node* unusedNode, Node* newNode) {
     839              :     //replace element node if it is unusedNode
     840            9 :     for (auto& voltageSource : *voltageSources) {
     841            4 :         if (voltageSource->getNegNode() == unusedNode) {
     842            0 :             voltageSource->setNegNode(newNode);
     843            0 :             newNode->eraseElement(voltageSource);
     844            0 :             newNode->addElement(voltageSource);
     845              :         }
     846            4 :         if (voltageSource->getPosNode() == unusedNode) {
     847            0 :             voltageSource->setPosNode(newNode);
     848            0 :             newNode->eraseElement(voltageSource);
     849            0 :             newNode->addElement(voltageSource);
     850              :         }
     851              :     }
     852           20 :     for (auto& element : *elements) {
     853           15 :         if (element->getNegNode() == unusedNode) {
     854            0 :             element->setNegNode(newNode);
     855            0 :             newNode->eraseElement(element);
     856            0 :             newNode->addElement(element);
     857              :         }
     858           15 :         if (element->getPosNode() == unusedNode) {
     859            5 :             element->setPosNode(newNode);
     860            5 :             newNode->eraseElement(element);
     861            5 :             newNode->addElement(element);
     862              :         }
     863              :     }
     864              : 
     865              :     //erase unusedNode from nodes vector
     866            5 :     this->eraseNode(unusedNode);
     867              : 
     868              :     //modify id of other elements and nodes
     869            5 :     int modLastId = this->getLastId() - 1;
     870            5 :     if (unusedNode->getId() != modLastId) {
     871            5 :         Node* node_last = this->getNode(modLastId);
     872            5 :         if (node_last != nullptr) {
     873            5 :             node_last->setId(unusedNode->getId());
     874              :         } else {
     875            0 :             Element* elem_last = this->getVoltageSource(modLastId);
     876            0 :             if (elem_last != nullptr) {
     877            0 :                 elem_last->setId(unusedNode->getId());
     878              :             } else {
     879            0 :                 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            5 :     delete unusedNode;
     886            5 : }
     887              : 
     888            0 : void Circuit::cleanUpSP() {
     889            0 :     for (std::vector<Element*>::iterator it = elements->begin(); it != elements->end(); it++) {
     890            0 :         if ((*it)->getType() != Element::ElementType::RESISTOR_traction_wire) {
     891            0 :             (*it)->setEnabled(true);
     892              :         }
     893              :     }
     894              : 
     895            0 :     for (std::vector<Element*>::iterator it = voltageSources->begin(); it != voltageSources->end(); it++) {
     896            0 :         (*it)->setEnabled(true);
     897              :     }
     898            0 :     this->iscleaned = true;
     899            0 : }
     900              : 
     901            8 : bool Circuit::checkCircuit(std::string substationId) {
     902              :     // check empty nodes
     903           87 :     for (std::vector<Node*>::iterator it = nodes->begin(); it != nodes->end(); it++) {
     904           79 :         if ((*it)->getNumOfElements() < 2) {
     905              :             //cout << "WARNING: Node [" << (*it)->getName() << "] is connected to less than two elements, please enter other elements.\n";
     906           24 :             if ((*it)->getNumOfElements() < 1) {
     907              :                 return false;
     908              :             }
     909              :         }
     910              :     }
     911              :     // check voltage sources
     912           19 :     for (std::vector<Element*>::iterator it = voltageSources->begin(); it != voltageSources->end(); it++) {
     913           11 :         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            0 :             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           71 :     for (std::vector<Element*>::iterator it = elements->begin(); it != elements->end(); it++) {
     921           63 :         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            0 :             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            8 :     int num = (int)nodes->size() + getNumVoltageSources() - 1;
     930            8 :     bool* nodesVisited = new bool[num];
     931           90 :     for (int i = 0; i < num; i++) {
     932           82 :         nodesVisited[i] = false;
     933              :     }
     934              :     // TODO: Probably unused
     935              :     // int id = -1;
     936            8 :     if (!getNode(-1)->isGround()) {
     937              :         //cout << "ERROR: Node id -1 is not the ground \n";
     938            0 :         WRITE_ERRORF(TL("Circuit Node with id '-1' is not the grounded, please adjust the definition of the section (with substation '%')."), substationId);
     939              :     }
     940            8 :     std::vector<Node*>* queue = new std::vector<Node*>(0);
     941            8 :     Node* node = nullptr;
     942            8 :     Node* neigboringNode = nullptr;
     943              :     //start with (voltageSources->front()->getPosNode())
     944            8 :     nodesVisited[voltageSources->front()->getId()] = 1;
     945            8 :     node = voltageSources->front()->getPosNode();
     946            8 :     queue->push_back(node);
     947              : 
     948          142 :     while (!queue->empty()) {
     949          134 :         node = queue->back();
     950              :         queue->pop_back();
     951          134 :         if (!nodesVisited[node->getId()]) {
     952           71 :             nodesVisited[node->getId()] = true;
     953          208 :             for (auto it = node->getElements()->begin(); it != node->getElements()->end(); it++) {
     954          137 :                 neigboringNode = (*it)->getTheOtherNode(node);
     955          137 :                 if (!neigboringNode->isGround()) {
     956          126 :                     queue->push_back(neigboringNode);
     957           11 :                 } else if ((*it)->getType() == Element::ElementType::VOLTAGE_SOURCE_traction_wire) {
     958              :                     /// there used to be == 1 which was probably a typo ... check!
     959           11 :                     nodesVisited[(*it)->getId()] = 1;
     960            0 :                 } else if ((*it)->getType() == Element::ElementType::RESISTOR_traction_wire) {
     961              :                     //cout << "ERROR: The resistor type connects the ground \n";
     962            0 :                     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           90 :     for (int i = 0; i < num; i++) {
     969           82 :         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            0 :             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            8 :     return true;
     976              : }
     977              : 
     978          196 : int Circuit::getNumVoltageSources() {
     979          196 :     return (int) voltageSources->size();
     980              : }
        

Generated by: LCOV version 2.0-1