LCOV - code coverage report
Current view: top level - src/microsim/devices - MSDevice_ElecHybrid.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 67.7 % 359 243
Test Date: 2025-11-13 15:38:19 Functions: 94.1 % 34 32

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2002-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    MSDevice_ElecHybrid.cpp
      15              : /// @author  Jakub Sevcik (RICE)
      16              : /// @author  Jan Prikryl (RICE)
      17              : /// @date    2019-12-15
      18              : ///
      19              : // The ElecHybrid device simulates internal electric parameters of an
      20              : // battery-assisted electric vehicle (typically a trolleybus), i.e. a vehicle
      21              : // that is being powered by overhead wires and has also a battery pack
      22              : // installed, that is being charged from the overhead wires.
      23              : /****************************************************************************/
      24              : #include <config.h>
      25              : 
      26              : #include <string.h> //due to strncmp
      27              : #include <ctime> //due to clock()
      28              : #include <utils/common/StringUtils.h>
      29              : #include <utils/options/OptionsCont.h>
      30              : #include <utils/iodevices/OutputDevice.h>
      31              : #include <utils/vehicle/SUMOVehicle.h>
      32              : #include <utils/common/SUMOTime.h>
      33              : #include <utils/emissions/HelpersEnergy.h>
      34              : #include <utils/traction_wire/Node.h>
      35              : #include <microsim/MSNet.h>
      36              : #include <microsim/MSLane.h>
      37              : #include <microsim/MSEdge.h>
      38              : #include <microsim/MSVehicle.h>
      39              : #include <microsim/MSGlobals.h>
      40              : #include <microsim/MSEdgeControl.h>
      41              : #include <mesosim/MEVehicle.h>
      42              : #include "MSDevice_Tripinfo.h"
      43              : #include "MSDevice_Emissions.h"
      44              : #include "MSDevice_ElecHybrid.h"
      45              : 
      46              : 
      47              : // ===========================================================================
      48              : // method definitions
      49              : // ===========================================================================
      50              : // ---------------------------------------------------------------------------
      51              : // static initialisation methods
      52              : // ---------------------------------------------------------------------------
      53              : void
      54        39784 : MSDevice_ElecHybrid::insertOptions(OptionsCont& oc) {
      55        39784 :     oc.addOptionSubTopic("ElecHybrid Device");
      56        79568 :     insertDefaultAssignmentOptions("elechybrid", "ElecHybrid Device", oc);
      57        39784 : }
      58              : 
      59              : 
      60              : void
      61      5371069 : MSDevice_ElecHybrid::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
      62              :     // Check if vehicle should get an 'elecHybrid' device.
      63      5371069 :     OptionsCont& oc = OptionsCont::getOptions();
      64     10742138 :     if (equippedByDefaultAssignmentOptions(oc, "elechybrid", v, false)) {
      65              :         // Yes, build the device.
      66              :         // Fetch the battery capacity (if present) from the vehicle descriptor.
      67           50 :         const SUMOVTypeParameter& typeParams = v.getVehicleType().getParameter();
      68           50 :         const SUMOVehicleParameter& vehicleParams = v.getParameter();
      69              :         double actualBatteryCapacity = 0;
      70              :         /* The actual battery capacity can be a parameter of the vehicle or its vehicle type.
      71              :            The vehicle parameter takes precedence over the type parameter. */
      72           50 :         std::string attrName = toString(SUMO_ATTR_ACTUALBATTERYCAPACITY);
      73           50 :         if (vehicleParams.hasParameter(attrName)) {
      74           50 :             const std::string abc = vehicleParams.getParameter(attrName, "-1");
      75              :             try {
      76           50 :                 actualBatteryCapacity = StringUtils::toDouble(abc);
      77            0 :             } catch (...) {
      78            0 :                 WRITE_WARNING("Invalid value '" + abc + "'for vehicle parameter '" + attrName + "'. Using the default of " + std::to_string(actualBatteryCapacity));
      79            0 :             }
      80              :         } else {
      81            0 :             if (typeParams.hasParameter(attrName)) {
      82            0 :                 const std::string abc = typeParams.getParameter(attrName, "-1");
      83              :                 try {
      84            0 :                     actualBatteryCapacity = StringUtils::toDouble(abc);
      85            0 :                     WRITE_WARNING("Vehicle '" + v.getID() + "' does not provide vehicle parameter '" + attrName + "'. Using the vehicle type value of " + std::to_string(actualBatteryCapacity));
      86            0 :                 } catch (...) {
      87            0 :                     WRITE_WARNING("Invalid value '" + abc + "'for vehicle type parameter '" + attrName + "'. Using the default of " + std::to_string(actualBatteryCapacity));
      88            0 :                 }
      89              :             } else {
      90            0 :                 WRITE_WARNING("Vehicle '" + v.getID() + "' does not provide vehicle or vehicle type parameter '" + attrName + "'. Using the default of " + std::to_string(actualBatteryCapacity));
      91              :             }
      92              :         }
      93              : 
      94              :         // obtain maximumBatteryCapacity
      95              :         double maximumBatteryCapacity = 0;
      96           50 :         attrName = toString(SUMO_ATTR_MAXIMUMBATTERYCAPACITY);
      97           50 :         if (typeParams.hasParameter(attrName)) {
      98           50 :             const std::string mbc = typeParams.getParameter(attrName, "-1");
      99              :             try {
     100           50 :                 maximumBatteryCapacity = StringUtils::toDouble(mbc);
     101            0 :             } catch (...) {
     102            0 :                 WRITE_WARNINGF(TL("Invalid value '%'for vType parameter '%'"), mbc, attrName);
     103            0 :             }
     104              :         } else {
     105            0 :             WRITE_WARNING("Vehicle '" + v.getID() + "' is missing the vType parameter '" + attrName + "'. Using the default of " + std::to_string(maximumBatteryCapacity));
     106              :         }
     107              : 
     108              :         // obtain overheadWireChargingPower
     109              :         double overheadWireChargingPower = 0;
     110           50 :         attrName = toString(SUMO_ATTR_OVERHEADWIRECHARGINGPOWER);
     111           50 :         if (typeParams.hasParameter(attrName)) {
     112           50 :             const std::string ocp = typeParams.getParameter(attrName, "-1");
     113              :             try {
     114           50 :                 overheadWireChargingPower = StringUtils::toDouble(ocp);
     115            0 :             } catch (...) {
     116            0 :                 WRITE_WARNINGF(TL("Invalid value '%'for vType parameter '%'"), ocp, attrName);
     117            0 :             }
     118              :         } else {
     119            0 :             WRITE_WARNING("Vehicle '" + v.getID() + "' is missing the vType parameter '" + attrName + "'. Using the default of " + std::to_string(overheadWireChargingPower));
     120              :         }
     121              : 
     122              :         // elecHybrid constructor
     123           50 :         MSDevice_ElecHybrid* device = new MSDevice_ElecHybrid(v, "elecHybrid_" + v.getID(),
     124          100 :                 actualBatteryCapacity, maximumBatteryCapacity, overheadWireChargingPower);
     125              : 
     126              :         // Add device to vehicle
     127           50 :         into.push_back(device);
     128              :     }
     129      5371069 : }
     130              : 
     131              : 
     132              : // ---------------------------------------------------------------------------
     133              : // MSDevice_ElecHybrid-methods
     134              : // ---------------------------------------------------------------------------
     135           50 : MSDevice_ElecHybrid::MSDevice_ElecHybrid(SUMOVehicle& holder, const std::string& id,
     136           50 :         const double actualBatteryCapacity, const double maximumBatteryCapacity, const double overheadWireChargingPower) :
     137              :     MSVehicleDevice(holder, id),
     138           50 :     myActualBatteryCapacity(0),   // [actualBatteryCapacity <= maximumBatteryCapacity]
     139           50 :     myMaximumBatteryCapacity(0),  // [maximumBatteryCapacity >= 0]t
     140           50 :     myOverheadWireChargingPower(0),
     141           50 :     myConsum(0),
     142           50 :     myBatteryDischargedLogic(false),
     143           50 :     myCharging(false),            // Initially vehicle don't charge
     144           50 :     myEnergyCharged(0),           // Initially the energy charged is zero
     145           50 :     myCircuitCurrent(NAN),        // Initially the current is unknown
     146           50 :     myCircuitVoltage(NAN),        // Initially the voltage is unknown as well
     147           50 :     myMaxBatteryCharge(NAN),      // Initial maximum of the battery energy during the simulation is unknown
     148           50 :     myMinBatteryCharge(NAN),      // Initial minimum of the battery energy during the simulation is unknown
     149           50 :     myTotalEnergyConsumed(0),     // No energy spent yet
     150           50 :     myTotalEnergyRegenerated(0),  // No energy regenerated
     151           50 :     myTotalEnergyWasted(0),       // No energy wasted on resistors
     152              :     // RICE_TODO: make these two parameters user configurable
     153           50 :     mySOCMin(0.005),              // Minimum SOC of the battery
     154           50 :     mySOCMax(0.980),              // Maximum SOC of the battery
     155           50 :     myActOverheadWireSegment(nullptr),         // Initially the vehicle isn't under any overhead wire segment
     156           50 :     myPreviousOverheadWireSegment(nullptr),    // Initially the vehicle wasn't under any overhead wire segment
     157           50 :     veh_elem(nullptr),
     158           50 :     veh_pos_tail_elem(nullptr),
     159           50 :     pos_veh_node(nullptr) {
     160              : 
     161           50 :     if (maximumBatteryCapacity < 0) {
     162            0 :         WRITE_WARNINGF(TL("ElecHybrid builder: Vehicle '%' doesn't have a valid value for parameter % (%)."), getID(), toString(SUMO_ATTR_MAXIMUMBATTERYCAPACITY), toString(maximumBatteryCapacity));
     163              :     } else {
     164           50 :         myMaximumBatteryCapacity = maximumBatteryCapacity;
     165              :     }
     166              : 
     167           50 :     if (actualBatteryCapacity > maximumBatteryCapacity) {
     168            0 :         WRITE_WARNING("ElecHybrid builder: Vehicle '" + getID() + "' has a " + toString(SUMO_ATTR_ACTUALBATTERYCAPACITY) + " (" + toString(actualBatteryCapacity) + ") greater than it's " + toString(SUMO_ATTR_MAXIMUMBATTERYCAPACITY) + " (" + toString(maximumBatteryCapacity) + "). A max battery capacity value will be asigned");
     169            0 :         myActualBatteryCapacity = myMaximumBatteryCapacity;
     170              :     } else {
     171           50 :         myActualBatteryCapacity = actualBatteryCapacity;
     172              :     }
     173              : 
     174           50 :     if (overheadWireChargingPower < 0) {
     175            0 :         WRITE_WARNINGF(TL("ElecHybrid builder: Vehicle '%' doesn't have a valid value for parameter % (%)."), getID(), toString(SUMO_ATTR_OVERHEADWIRECHARGINGPOWER), toString(overheadWireChargingPower));
     176              :     } else {
     177           50 :         myOverheadWireChargingPower = overheadWireChargingPower;
     178              :     }
     179           50 : }
     180              : 
     181              : 
     182          100 : MSDevice_ElecHybrid::~MSDevice_ElecHybrid() {
     183          100 : }
     184              : 
     185              : 
     186              : bool
     187         1135 : MSDevice_ElecHybrid::notifyMove(SUMOTrafficObject& tObject, double /* oldPos */, double /* newPos */, double /* newSpeed */) {
     188         1135 :     if (!tObject.isVehicle()) {
     189              :         return false;
     190              :     }
     191              :     SUMOVehicle& veh = static_cast<SUMOVehicle&>(tObject);
     192              :     // Do not compute the current consumption here anymore:
     193              :     // myConsum is (non-systematically, we agree) set in MSVehicle so that the vehicle `vNext` value can
     194              :     // be influenced by the maximum traction power of the vehicle (i.e. installing a 80 kWh powertrain will
     195              :     // limit the acceleration regardless of the acceleration specified in vehicleType params, in case that
     196              :     // the vehicleType acceleration is too high).
     197              :     //
     198              :     // myParam[SUMO_ATTR_ANGLE] = myLastAngle == std::numeric_limits<double>::infinity() ? 0. : GeomHelper::angleDiff(myLastAngle, veh.getAngle());
     199              :     // myConsum = PollutantsInterface::getEnergyHelper().compute(0, PollutantsInterface::ELEC, veh.getSpeed(), veh.getAcceleration(), veh.getSlope(), &myParam);
     200              :     assert(!std::isnan(myConsum));
     201              : 
     202              :     // is battery pack discharged (from previous timestep)
     203         1135 :     if (myActualBatteryCapacity < mySOCMin * myMaximumBatteryCapacity) {
     204            0 :         myBatteryDischargedLogic = true;
     205              :     } else {
     206         1135 :         myBatteryDischargedLogic = false;
     207              :     }
     208              : 
     209              :     /* If battery is discharged we will force the vehicle to slowly come to
     210              :        a halt (freewheel motion). It could still happen that some energy will
     211              :        be recovered in later steps due to regenerative braking. */
     212         1135 :     if (isBatteryDischarged()) {
     213              :         std::vector<std::pair<SUMOTime, double> > speedTimeLine;
     214              :         /// @todo modify equation for deceleration, getNoEnergyDecel
     215              :         /// @todo doublecheck this mode, we probably assume here that the acceleration is negative
     216              :         /// @todo check the value of myConsum here, it should be probably zero
     217            0 :         double accel = acceleration(veh, 0, veh.getSpeed());  // or use veh.getAcceleration() method???
     218            0 :         const double nextSpeed = MAX2(0., veh.getSpeed() + ACCEL2SPEED(accel));
     219              :         speedTimeLine.push_back(
     220            0 :             std::make_pair(
     221            0 :                 MSNet::getInstance()->getCurrentTimeStep(),
     222            0 :                 veh.getSpeed()));
     223              :         speedTimeLine.push_back(
     224            0 :             std::make_pair(
     225            0 :                 MSNet::getInstance()->getCurrentTimeStep() + DELTA_T,
     226              :                 nextSpeed));
     227              : 
     228            0 :         static_cast<MSVehicle*>(&veh)->getInfluencer().setSpeedTimeLine(speedTimeLine);
     229            0 :     }
     230              : 
     231              :     /* Check if there is an overhead wire either over the lane where the vehicle is or over a
     232              :        neighboring lanes. This check has to be performed at every simulation step as the
     233              :        overhead wires for trolleybuses will typically end at a bus stop that is located somewhere
     234              :        in the middle of the lane. */
     235         1135 :     std::string overheadWireSegmentID = MSNet::getInstance()->getStoppingPlaceID(veh.getLane(), veh.getPositionOnLane(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     236              : 
     237              :     //check overhead line on the left neighboring lane
     238         1135 :     if (overheadWireSegmentID == "" && veh.getEdge()->leftLane(veh.getLane()) != nullptr) {
     239          810 :         overheadWireSegmentID = MSNet::getInstance()->getStoppingPlaceID(veh.getEdge()->leftLane(veh.getLane()), veh.getPositionOnLane(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     240              :     }
     241              :     //check overhead line on the right neighboring lane
     242         1135 :     if (overheadWireSegmentID == "" && veh.getEdge()->rightLane(veh.getLane()) != nullptr) {
     243           26 :         overheadWireSegmentID = MSNet::getInstance()->getStoppingPlaceID(veh.getEdge()->rightLane(veh.getLane()), veh.getPositionOnLane(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     244              :     }
     245              : 
     246              :     /* Store the amount of power that could not be recuperated. */
     247              :     double energyWasted = 0.0;
     248              :     /* If vehicle has access to an overhead wire (including the installation on neighboring lanes) */
     249         1135 :     if (overheadWireSegmentID != "") {
     250              :         /* Update the actual overhead wire segment of this device */
     251          710 :         myActOverheadWireSegment =
     252          710 :             static_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace(overheadWireSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     253              :         /* Store the traction substation of the actual overhead wire segment */
     254              :         MSTractionSubstation* actualSubstation = myActOverheadWireSegment->getTractionSubstation();
     255              : 
     256              :         /* Disable charging from previous (not the actual) overhead wire segment.
     257              :            REASON:
     258              :            If there is no gap between two different overhead wire segments that are
     259              :            places on the same lane, the vehicle switches from the one segment to another
     260              :            in one timestep. */
     261          710 :         if (myPreviousOverheadWireSegment != myActOverheadWireSegment) {
     262           42 :             if (myPreviousOverheadWireSegment != nullptr) {
     263              :                 /* Remove the vehicle from the list of vehicles powered by the previous segment. */
     264           16 :                 myPreviousOverheadWireSegment->eraseVehicle(veh);
     265           16 :                 MSTractionSubstation* ts = myPreviousOverheadWireSegment->getTractionSubstation();
     266           16 :                 if (ts != nullptr) {
     267           16 :                     ts->decreaseElecHybridCount();
     268           16 :                     ts->eraseVehicle(this);
     269              :                 }
     270              :             }
     271              :             /* Add the vehicle reference to the current segment. */
     272           42 :             myActOverheadWireSegment->addVehicle(veh);
     273           42 :             if (actualSubstation != nullptr) {
     274           22 :                 actualSubstation->increaseElecHybridCount();
     275           22 :                 actualSubstation->addVehicle(this);
     276              :             }
     277              :         }
     278              : 
     279              :         /* Do we simulate the behaviour of the overhead wire electric circuit? */
     280          710 :         if (MSGlobals::gOverheadWireSolver) {
     281              : #ifdef HAVE_EIGEN
     282              :             /// @todo Should this part of the code be #ifdefed in case that EIGEN is not installed?
     283              :             /* Circuit update due to vehicle movement:
     284              :                Delete vehicle resistor element, vehicle resistor nodes and vehicle resistor
     285              :                tails in the circuit used in the previous timestep. */
     286          710 :             deleteVehicleFromCircuit(veh);
     287              : 
     288              :             /* Add the vehicle to the circuit in case that there is a substation that provides
     289              :                power to it. */
     290          710 :             if (actualSubstation != nullptr) {
     291              :                 /* Add a resistor (current source in the future?) representing trolleybus
     292              :                    vehicle to the circuit.
     293              :                    pos/neg_veh_node elements
     294              :                     [0] .... vehicle_resistor
     295              :                     [1] .... leading resistor
     296              :                     [2] .... tail resistor pos/neg_tail_vehID
     297              :                 */
     298              : 
     299              :                 // pos_veh_node and veh_elem should be NULL
     300          180 :                 if (pos_veh_node != nullptr || veh_elem != nullptr) {
     301            0 :                     WRITE_WARNING("pos_veh_node or neg_veh_node or veh_elem is not NULL (and they should be at the beginning of adding elecHybrid to the circuit)");
     302              :                 }
     303              : 
     304              :                 // create pos and veh_elem
     305          180 :                 Circuit* owc = myActOverheadWireSegment->getCircuit();
     306          180 :                 pos_veh_node = owc->addNode("pos_" + veh.getID());
     307              :                 assert(pos_veh_node != nullptr);
     308              :                 // adding current source element representing elecHybrid vehicle. The value of current is computed from wantedPower later by circuit solver. Thus NAN is used as an initial value.
     309          180 :                 veh_elem = owc->addElement("currentSrc" + veh.getID(), NAN,
     310              :                                            pos_veh_node, owc->getNode("negNode_ground"),
     311              :                                            Element::ElementType::CURRENT_SOURCE_traction_wire);
     312              : 
     313              :                 // Connect vehicle to an existing overhead wire segment = add elecHybridVehicle to the myActOverheadWireSegment circuit
     314              :                 // Find pos resistor element of the actual overhead line section and their end nodes
     315          180 :                 Element* element_pos = owc->getElement("pos_" + myActOverheadWireSegment->getID());
     316          180 :                 Node*  node_pos = element_pos->getNegNode();
     317          180 :                 double resistance = element_pos->getResistance();
     318              : 
     319              :                 /* Find the correct position of the vehicle at the overhead line.
     320              :                    We start the while loop at the end of the actual overhead line section and go against the direction of vehicle movement.
     321              :                    The decision rule is based on the resistance value:
     322              :                      * from the vehicle position to the end of lane,
     323              :                      * sum of resistance of elements (going from the end of overhead line section in the contrary direction).
     324              : 
     325              :                    The original solution runs into problems when a vehicle is going on a neighboring lane and the two lanes have different lengths:
     326              :                    while (resistance < (veh.getLane()->getLength() - veh.getPositionOnLane())*WIRE_RESISTIVITY) {
     327              :                    Improvement: take the relative distance of the vehicle to the end of its lane and map it to the segment's lane length. (This works also in case that the segment's lane and the vehicle's lane are identical.)
     328              :                 */
     329              :                 double relativePosOnSegment =
     330          180 :                     myActOverheadWireSegment->getLane().getLength() * (1 -
     331          180 :                             (veh.getPositionOnLane() / veh.getLane()->getLength()));
     332              : 
     333          180 :                 while (resistance < relativePosOnSegment * WIRE_RESISTIVITY) {
     334            0 :                     node_pos = element_pos->getPosNode();
     335            0 :                     element_pos = node_pos->getElements()->at(2);
     336            0 :                     resistance += element_pos->getResistance();
     337            0 :                     if (strncmp(element_pos->getName().c_str(), "pos_tail_", 9) != 0) {
     338            0 :                         WRITE_WARNING("splitting element is not 'pos_tail_XXX'")
     339              :                     }
     340              :                 }
     341              : 
     342          180 :                 node_pos = element_pos->getPosNode();
     343              :                 //resistance of vehicle tail nodes
     344          180 :                 resistance -= relativePosOnSegment * WIRE_RESISTIVITY;
     345              : 
     346              :                 /* dividing element_pos
     347              :                    before:   |node_pos|---------------------------------------------|element_pos|----
     348              :                    after:    |node_pos|----|veh_pos_tail_elem|----|pos_veh_node|----|element_pos|----
     349              :                 */
     350          180 :                 element_pos->setPosNode(pos_veh_node);
     351          180 :                 node_pos->eraseElement(element_pos);
     352          180 :                 pos_veh_node->addElement(element_pos);
     353              : 
     354          180 :                 veh_pos_tail_elem = owc->addElement("pos_tail_" + veh.getID(),
     355              :                                                     resistance, node_pos, pos_veh_node, Element::ElementType::RESISTOR_traction_wire);
     356              : 
     357          180 :                 if (element_pos->getResistance() - resistance < 0) {
     358            0 :                     WRITE_WARNINGF(TL("The resistivity of overhead wire segment connected to vehicle % is < 0. Set to 1e-6."), veh.getID());
     359              :                 }
     360              : 
     361          180 :                 element_pos->setResistance(element_pos->getResistance() - resistance);
     362              : 
     363              : 
     364              :                 // Set the power requirement to the consumption + charging power.
     365              :                 // RICE_TODO: The charging power could be different when moving and when not. Add a parameter.
     366              :                 // Note that according to PMDP data, the charging power seems to be the same in both situations,
     367              :                 // ignoring the potential benefits of using a higher charging power when the vehicle is moving.
     368          180 :                 if (myActualBatteryCapacity < mySOCMax * myMaximumBatteryCapacity) {
     369          180 :                     veh_elem->setPowerWanted(WATTHR2WATT(myConsum) + myOverheadWireChargingPower);
     370              :                 } else {
     371            0 :                     veh_elem->setPowerWanted(WATTHR2WATT(myConsum));
     372              :                 }
     373              : 
     374              :                 // No recuperation to overheadwire (only to the batterypack)
     375          180 :                 if (!MSGlobals::gOverheadWireRecuperation && veh_elem->getPowerWanted() < 0.0) {
     376              :                     // the value of energyWasted is properly computed and updated after solving circuit by the solver
     377              :                     // energyWasted = 0;
     378            0 :                     veh_elem->setPowerWanted(0.0);
     379              :                 }
     380              : 
     381              :                 // RICE_TODO: The voltage in the solver should never exceed or drop below some limits. Maximum allowed voltage is typically 800 V.
     382              :                 // The numerical solver that computes the circuit state needs initial values of electric currents and
     383              :                 // voltages from which it will start the iterative solving process. We prefer to reuse the "old" values
     384              :                 // as it is likely that the new values will not be far away from the old ones. The safety limits of
     385              :                 // 10 and 1500 Volts that are used below are chosen fairly arbitrarily to keep the initial values within
     386              :                 // reasonable limits.
     387          180 :                 double voltage = myCircuitVoltage;
     388          180 :                 if (voltage < 10.0 || voltage > 1500.0 || std::isnan(voltage)) {
     389              :                     // RICE_TODO : It seems to output warning whenever a vehicle is appearing under the overhead wire for the first time?
     390              :                     // WRITE_WARNINGF(TL("The initial voltage is was % V, replacing it with substation voltage % V."), toString(voltage), toString(actualSubstation->getSubstationVoltage()));
     391              :                     voltage = actualSubstation->getSubstationVoltage();
     392              :                 }
     393              :                 // Initial value of the electric current flowing into the vehicle that will be used by the solver
     394          180 :                 double current = -(veh_elem->getPowerWanted() / voltage);
     395          180 :                 veh_elem->setCurrent(current);
     396              : 
     397              :                 // Set the device as charging the battery by default
     398          180 :                 myCharging = true;
     399              : 
     400              :                 // And register the call to solver at the end of the simulation step
     401          180 :                 actualSubstation->addSolvingCircuitToEndOfTimestepEvents();
     402              :             } else {
     403              :                 /*
     404              :                     No substation on this wire ...
     405              :                 */
     406              :                 // RICE_TODO myCharging = false; current 0 or nan, voltage 0 or nan, maybe write warning that the overhead wire is not connected to any substation,
     407              : 
     408              :                 // Energy flowing to/from the battery pack [Wh] has to completely cover vehicle consumption.
     409          530 :                 myEnergyCharged = -myConsum;
     410              :                 // Update Battery charge
     411          530 :                 myActualBatteryCapacity += myEnergyCharged;
     412              :                 // No substation is connected to this segment and the charging output is therefore zero.
     413          530 :                 myActOverheadWireSegment->addChargeValueForOutput(0, this, false);
     414              :             }
     415              : #else
     416              :             WRITE_ERROR(TL("Overhead wire solver is on, but the Eigen library has not been compiled in!"))
     417              : #endif
     418              :         } else {
     419              :             /*
     420              :                 Faster approximation without circuit solving at every simulation step.
     421              :             */
     422              : 
     423              :             // First check that there is a traction substation connected to the overhead wire
     424              :             double voltage = 0.0;
     425            0 :             if (actualSubstation != nullptr) {
     426              :                 voltage = actualSubstation->getSubstationVoltage();
     427              :             }
     428              : 
     429              :             // At this point the voltage can be (a) NAN if the substation voltage was not specified,
     430              :             // (b) 0 if no substation powers the current segment or if someone put its power to zero,
     431              :             // (c) >0 if the substation can provide energy to the circuit.
     432            0 :             if (voltage > 0.0) {
     433              :                 // There is a power source connected to this segment.
     434              :                 // Set the simplified power requirement to the consumption + charging power.
     435              :                 // RICE_TODO: The charging power could be different when moving and when not. Add a parameter. See a similar code snippet above.
     436              :                 // Note that according to PMDP data, the charging power seems to be the same in both situations,
     437              :                 // ignoring the potential benefits of using a higher charging power when the vehicle is moving.
     438            0 :                 double powerWanted = WATTHR2WATT(myConsum);
     439            0 :                 if (myActualBatteryCapacity < mySOCMax * myMaximumBatteryCapacity) {
     440              :                     // Additional `myOverheadWireChargingPower` due to charging of battery pack
     441            0 :                     powerWanted += myOverheadWireChargingPower;
     442              :                 }
     443              : 
     444              :                 // No recuperation to overhead wire (only to the battery pack)
     445              :                 // RICE_TODO: How to recuperate into the circuit without solver? (energy balance?)
     446              :                 //            - solution: assume, that any power is possible to recuperate
     447            0 :                 if (!MSGlobals::gOverheadWireRecuperation && powerWanted < 0.0) {
     448              :                     // the value of energyWasted is properly computed and updated below
     449              :                     powerWanted = 0.0;
     450              :                 }
     451              : 
     452              :                 // Set the actual current and voltage of the global circuit
     453              :                 // RICE_TODO: Process the traction station current limiting here as well.
     454            0 :                 myCircuitCurrent = powerWanted / voltage;
     455            0 :                 myCircuitVoltage = voltage;
     456              : 
     457              :                 // Calculate energy charged
     458            0 :                 double energyIn = WATT2WATTHR(powerWanted);
     459              : 
     460              :                 // Calculate energy flowing to/from the battery in this step [Wh]
     461              :                 // RICE_TODO: It should be possible to define different efficiency values for direction overhead wire -> battery; motor -> battery.
     462              :                 // We use a simplification here. The biggest contributor to the total losses is the battery pack itself
     463              :                 // (the input LC filter is probably more efficient -- eta_LC ~ 0.99 -- compared to the induction motor
     464              :                 // with eta_motor ~ 0.95).
     465            0 :                 myEnergyCharged = computeChargedEnergy(energyIn);
     466              : 
     467              :                 // Update the energy that has been stored in the battery pack and return the real energy charged in this step
     468              :                 // considering SOC limits of the battery pack.
     469            0 :                 double realEnergyCharged = storeEnergyToBattery(myEnergyCharged);
     470              :                 // Set energy wasted
     471            0 :                 energyWasted = myEnergyCharged - realEnergyCharged;
     472              : 
     473              :                 // Add the energy provided by the overhead wire segment to the output of the segment
     474            0 :                 myActOverheadWireSegment->addChargeValueForOutput(energyIn, this);
     475              :             } else {
     476              :                 /*
     477              :                     Overhead wire without a connected substation
     478              :                 */
     479              :                 // RICE_TODO myCharging = false; current 0 or nan, voltage 0 or nan, maybe write warning that the overhead wire is not connected to any substation,
     480              : 
     481              :                 // Energy for the powertrain is provided by the battery pack
     482            0 :                 myEnergyCharged = -myConsum;
     483              :                 // Update battery charge
     484            0 :                 myActualBatteryCapacity += myEnergyCharged;
     485              :                 // No energy was provided by the overhead wire segment
     486            0 :                 myActOverheadWireSegment->addChargeValueForOutput(0.0, this);
     487              :             }
     488              :         }
     489              :         assert(myActOverheadWireSegment != nullptr);
     490          710 :         myPreviousOverheadWireSegment = myActOverheadWireSegment;
     491              :     } else {
     492              :         /*
     493              :             No overhead wires, no charging.
     494              :         */
     495              : 
     496              :         // Disable charing flag
     497          425 :         myCharging = false;
     498              : 
     499              :         // Invalidate the circuit voltage and current
     500          425 :         myCircuitCurrent = NAN;
     501          425 :         myCircuitVoltage = NAN;
     502              : 
     503              :         // Additional bookkeeping in case that the circuit solver is used
     504          425 :         if (MSGlobals::gOverheadWireSolver) {
     505              : #ifdef HAVE_EIGEN
     506              :             /*
     507              :                 Delete vehicle resistor element, vehicle resistor nodes and vehicle resistor tails
     508              :                 in the previous circuit (i.e. the circuit used in the previous timestep)
     509              :             */
     510          425 :             deleteVehicleFromCircuit(veh);
     511              : #else
     512              :             WRITE_ERROR(TL("Overhead wire solver is on, but the Eigen library has not been compiled in!"))
     513              : #endif
     514              :         }
     515              : 
     516              :         // Vehicle is not under an overhead wire
     517          425 :         myActOverheadWireSegment = nullptr;
     518              : 
     519              :         // Remove the vehicle from overhead wire if it was under it in the previous step.
     520              :         // This has to be called after deleteVehicleFromCircuit() as the file uses myPreviousOverheadWire.
     521          425 :         if (myPreviousOverheadWireSegment != nullptr) {
     522           11 :             myPreviousOverheadWireSegment->eraseVehicle(veh);
     523           11 :             MSTractionSubstation* ts = myPreviousOverheadWireSegment->getTractionSubstation();
     524           11 :             if (ts != nullptr) {
     525            6 :                 ts->decreaseElecHybridCount();
     526            6 :                 ts->eraseVehicle(this);
     527              :             }
     528           11 :             myPreviousOverheadWireSegment = nullptr;
     529              :         }
     530              : 
     531              :         // Energy for the powertrain is provided by the battery pack
     532          425 :         myEnergyCharged = -myConsum;
     533              :         // Update battery charge
     534          425 :         myActualBatteryCapacity += myEnergyCharged;
     535              :     }
     536              : 
     537              :     // Update the statistical values
     538              :     // RICE_TODO: update these statistical values also after solving circuit by electric circuit
     539         1135 :     if (std::isnan(myMaxBatteryCharge) || myMaxBatteryCharge < myActualBatteryCapacity) {
     540          201 :         myMaxBatteryCharge = myActualBatteryCapacity;
     541              :     }
     542         1135 :     if (std::isnan(myMinBatteryCharge) || myMinBatteryCharge > myActualBatteryCapacity) {
     543          360 :         myMinBatteryCharge = myActualBatteryCapacity;
     544              :     }
     545              : 
     546         1135 :     if (myConsum > 0.0) {
     547          992 :         myTotalEnergyConsumed += myConsum;
     548              :     } else {
     549          143 :         myTotalEnergyRegenerated -= myConsum;
     550              :     }
     551         1135 :     myTotalEnergyWasted += energyWasted;
     552              : 
     553              :     return true; // keep the device
     554              : }
     555              : 
     556              : // Note: This is called solely in the mesoscopic mode to mimic the `notifyMove()` reminder
     557              : void
     558           12 : MSDevice_ElecHybrid::notifyMoveInternal(
     559              :     const SUMOTrafficObject& tObject,
     560              :     const double frontOnLane,
     561              :     const double timeOnLane,
     562              :     const double meanSpeedFrontOnLane,
     563              :     const double meanSpeedVehicleOnLane,
     564              :     const double travelledDistanceFrontOnLane,
     565              :     const double travelledDistanceVehicleOnLane,
     566              :     const double meanLengthOnLane) {
     567              :     UNUSED_PARAMETER(tObject);
     568              :     UNUSED_PARAMETER(frontOnLane);
     569              :     UNUSED_PARAMETER(timeOnLane);
     570              :     UNUSED_PARAMETER(meanSpeedFrontOnLane);
     571              :     UNUSED_PARAMETER(meanSpeedVehicleOnLane);
     572              :     UNUSED_PARAMETER(travelledDistanceFrontOnLane);
     573              :     UNUSED_PARAMETER(travelledDistanceVehicleOnLane);
     574              :     UNUSED_PARAMETER(meanLengthOnLane);
     575           12 : }
     576              : 
     577              : void
     578         1145 : MSDevice_ElecHybrid::deleteVehicleFromCircuit(SUMOVehicle& veh) {
     579         1145 :     if (myPreviousOverheadWireSegment != nullptr) {
     580          695 :         if (myPreviousOverheadWireSegment->getTractionSubstation() != nullptr) {
     581              :             //check if all pointers to vehicle elements and nodes are not nullptr
     582          180 :             if (veh_elem == nullptr || veh_pos_tail_elem == nullptr || pos_veh_node == nullptr) {
     583            0 :                 WRITE_ERRORF("During deleting vehicle '%' from circuit some init previous Nodes or Elements was not assigned.", veh.getID());
     584              :             }
     585              :             //check if pos_veh_node has 3 elements - they should be: veh_elem, veh_pos_tail_elem and an overhead line resistor element "ahead" of vehicle.
     586          180 :             if (pos_veh_node->getElements()->size() != 3) {
     587            0 :                 WRITE_ERRORF("During deleting vehicle '%' from circuit the size of element-vector of pNode or nNode was not 3. It should be 3 by Jakub's opinion.", veh.getID());
     588              :             }
     589              :             //delete vehicle resistor element "veh_elem" in the previous circuit,
     590          180 :             pos_veh_node->eraseElement(veh_elem);
     591          180 :             myPreviousOverheadWireSegment->getCircuit()->eraseElement(veh_elem);
     592          360 :             delete veh_elem;
     593          180 :             veh_elem = nullptr;
     594              : 
     595              :             //erasing of tail elements (the element connected to the veh_node is after then only the ahead overhead line resistor element)
     596          180 :             pos_veh_node->eraseElement(veh_pos_tail_elem);
     597              : 
     598          180 :             if (pos_veh_node->getElements()->size() != 1) {
     599            0 :                 WRITE_ERRORF("During deleting vehicle '%' from circuit the size of element-vector of pNode or nNode was not 1. It should be 1 by Jakub's opinion.", veh.getID());
     600              :             }
     601              : 
     602              :             // add the resistance value of veh_tail element to the resistance value of the ahead overhead line element
     603          180 :             pos_veh_node->getElements()->front()->setResistance(pos_veh_node->getElements()->front()->getResistance() + veh_pos_tail_elem->getResistance());
     604              :             //set PosNode of the ahead overhead line element to the posNode value of tail element
     605          180 :             Element* aux = pos_veh_node->getElements()->front();
     606              :             //set node = 3 operations
     607          180 :             aux->setPosNode(veh_pos_tail_elem->getPosNode());
     608          180 :             aux->getPosNode()->eraseElement(aux);
     609          180 :             veh_pos_tail_elem->getPosNode()->addElement(aux);
     610              : 
     611              :             // erase tail element from its PosNode
     612          180 :             veh_pos_tail_elem->getPosNode()->eraseElement(veh_pos_tail_elem);
     613              :             // delete veh_pos_tail_elem
     614          180 :             myPreviousOverheadWireSegment->getCircuit()->eraseElement(veh_pos_tail_elem);
     615          360 :             delete veh_pos_tail_elem;
     616          180 :             veh_pos_tail_elem = nullptr;
     617              : 
     618              :             //erase pos_veh_node
     619          180 :             myPreviousOverheadWireSegment->getCircuit()->eraseNode(pos_veh_node);
     620              :             //modify id of other elements (the id of erasing element should be the greatest)
     621          180 :             int lastId = myPreviousOverheadWireSegment->getCircuit()->getLastId() - 1;
     622          180 :             if (pos_veh_node->getId() != lastId) {
     623            0 :                 Node* node_last = myPreviousOverheadWireSegment->getCircuit()->getNode(lastId);
     624            0 :                 if (node_last != nullptr) {
     625            0 :                     node_last->setId(pos_veh_node->getId());
     626              :                 } else {
     627            0 :                     Element* elem_last = myPreviousOverheadWireSegment->getCircuit()->getVoltageSource(lastId);
     628            0 :                     if (elem_last != nullptr) {
     629            0 :                         elem_last->setId(pos_veh_node->getId());
     630              :                     } else {
     631            0 :                         WRITE_ERROR(TL("The element or node with the last Id was not found in the circuit!"));
     632              :                     }
     633              :                 }
     634              :             }
     635          180 :             myPreviousOverheadWireSegment->getCircuit()->decreaseLastId();
     636          360 :             delete pos_veh_node;
     637          180 :             pos_veh_node = nullptr;
     638              :         }
     639              :     }
     640         1145 : }
     641              : 
     642              : bool
     643           90 : MSDevice_ElecHybrid::notifyEnter(
     644              :     SUMOTrafficObject& tObject,
     645              :     MSMoveReminder::Notification /* reason */,
     646              :     const MSLane* /* enteredLane */) {
     647           90 :     if (!tObject.isVehicle()) {
     648              :         return false;
     649              :     }
     650              : #ifdef ELECHYBRID_MESOSCOPIC_DEBUG
     651              :     SUMOVehicle& veh = static_cast<SUMOVehicle&>(tObject);
     652              :     std::cout << "device '" << getID() << "' notifyEnter: reason=" << reason << " currentEdge=" << veh.getEdge()->getID() << "\n";
     653              : #endif
     654              : 
     655              :     return true; // keep the device
     656              : }
     657              : 
     658              : bool
     659           65 : MSDevice_ElecHybrid::notifyLeave(
     660              :     SUMOTrafficObject& tObject,
     661              :     double /*lastPos*/,
     662              :     MSMoveReminder::Notification reason,
     663              :     const MSLane* /* enteredLane */) {
     664           65 :     if (!tObject.isVehicle()) {
     665              :         return false;
     666              :     }
     667              :     SUMOVehicle& veh = static_cast<SUMOVehicle&>(tObject);
     668              : #ifdef ELECHYBRID_MESOSCOPIC_DEBUG
     669              :     SUMOVehicle& veh = static_cast<SUMOVehicle&>(tObject);
     670              :     std::cout << "device '" << getID() << "' notifyLeave: reason=" << reason << " currentEdge=" << veh.getEdge()->getID() << "\n";
     671              :     SUMOVehicle* sumoVehicle = *veh;
     672              :     if (MSGlobals::gUseMesoSim) {
     673              :         MEVehicle& v = dynamic_cast<MEVehicle&>(veh);
     674              :         std::cout << "***************** MESO - notifyLeave*** START ****************** '" << v.getID() << "' \n";
     675              :         //TODO the second argument of getStoptime should change
     676              :         std::cout << "getSpeed: '" << v.getSpeed() << "' | getAverageSpeed: '" << v.getAverageSpeed() << "' | getStoptime: '"  << v.getStoptime(v.getSegment(), 0) << "' \n";
     677              :         std::cout << "getStopEdges: '"  << "' | getLastEntryTime: '" << v.getLastEntryTime() << "' | getBlockTimeSeconds: '" << v.getBlockTimeSeconds() << "' \n";
     678              :         std::cout << "getWaitingTime: '" << v.getWaitingTime() << "' | getAccumulatedWaitingTime: '" << v.getWaitingTime(true) << "' | getLastEntryTimeSeconds: '" << v.getLastEntryTimeSeconds() << "' \n";
     679              :         std::cout << "***************** MESO - notifyLeave***  END  ****************** '" << v.getID() << "' \n";
     680              :     }
     681              : #endif
     682              : 
     683              :     // The MSMoveReminders are sorted so that we can do `<`. See also BTreceiver and BTsender devices...
     684           65 :     if (reason < MSMoveReminder::NOTIFICATION_TELEPORT) {
     685              :         return true;
     686              :     }
     687              : 
     688           10 :     if (MSGlobals::gOverheadWireSolver) {
     689              : #ifdef HAVE_EIGEN
     690              :         // ------- *** ------- delete vehicle resistor element, vehicle resistor nodes and vehicle resistor tails in the previous circuit (circuit used in the previous timestep)------- *** -------
     691           10 :         deleteVehicleFromCircuit(veh);
     692              : #else
     693              :         UNUSED_PARAMETER(veh);
     694              :         WRITE_ERROR(TL("Overhead wire solver is on, but the Eigen library has not been compiled in!"))
     695              : #endif
     696              :     }
     697              :     // disable charging vehicle from overhead wire and segment and traction station
     698           10 :     if (myPreviousOverheadWireSegment != nullptr) {
     699            0 :         myPreviousOverheadWireSegment->eraseVehicle(veh);
     700            0 :         MSTractionSubstation* prevSubstation = myPreviousOverheadWireSegment->getTractionSubstation();
     701            0 :         if (prevSubstation != nullptr) {
     702            0 :             prevSubstation->decreaseElecHybridCount();
     703            0 :             prevSubstation->eraseVehicle(this);
     704              :         }
     705            0 :         myPreviousOverheadWireSegment = nullptr;
     706              :     }
     707              :     return true;
     708              : }
     709              : 
     710              : 
     711              : void
     712           10 : MSDevice_ElecHybrid::generateOutput(OutputDevice* tripinfoOut) const {
     713           10 :     if (tripinfoOut != nullptr) {
     714              :         // Write the summary elecHybrid information into tripinfo output
     715            0 :         tripinfoOut->openTag("elechybrid");
     716            0 :         tripinfoOut->writeAttr("maxBatteryCharge", myMaxBatteryCharge);
     717            0 :         tripinfoOut->writeAttr("minBatteryCharge", myMinBatteryCharge);
     718            0 :         tripinfoOut->writeAttr("totalEnergyConsumed", myTotalEnergyConsumed);
     719            0 :         tripinfoOut->writeAttr("totalEnergyRegenerated", myTotalEnergyRegenerated);
     720            0 :         tripinfoOut->writeAttr("totalEnergyWasted", myTotalEnergyWasted);
     721            0 :         tripinfoOut->closeTag();
     722              :     }
     723           10 : }
     724              : 
     725              : 
     726              : double
     727         1095 : MSDevice_ElecHybrid::getActualBatteryCapacity() const {
     728         1095 :     return myActualBatteryCapacity;
     729              : }
     730              : 
     731              : 
     732              : double
     733         1426 : MSDevice_ElecHybrid::getMaximumBatteryCapacity() const {
     734         1426 :     return myMaximumBatteryCapacity;
     735              : }
     736              : 
     737              : std::string
     738            0 : MSDevice_ElecHybrid::getParameter(const std::string& key) const {
     739              :     // RICE_TODO: full traci support
     740            0 :     if (key == toString(SUMO_ATTR_ACTUALBATTERYCAPACITY)) {
     741            0 :         return toString(myActualBatteryCapacity);
     742            0 :     } else if (key == toString(SUMO_ATTR_ENERGYCONSUMED)) {
     743            0 :         return toString(myConsum);
     744            0 :     } else if (key == toString(SUMO_ATTR_ENERGYCHARGED)) {
     745            0 :         return toString(myEnergyCharged);
     746            0 :     } else if (key == toString(SUMO_ATTR_MAXIMUMBATTERYCAPACITY)) {
     747            0 :         return toString(myMaximumBatteryCapacity);
     748            0 :     } else if (key == toString(SUMO_ATTR_OVERHEADWIREID)) {
     749            0 :         return getOverheadWireSegmentID();
     750            0 :     } else if (key == toString(SUMO_ATTR_SUBSTATIONID)) {
     751            0 :         return getTractionSubstationID();
     752            0 :     } else if (key == toString(SUMO_ATTR_VEHICLEMASS)) {
     753            0 :         WRITE_WARNING(TL("Getting the vehicle mass via parameters is deprecated, please use getMass for the vehicle or its type."));
     754            0 :         return toString(myHolder.getEmissionParameters()->getDouble(SUMO_ATTR_MASS));
     755              :     }
     756            0 :     throw InvalidArgument("Parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
     757              : }
     758              : 
     759              : 
     760          180 : double MSDevice_ElecHybrid::computeChargedEnergy(double energyIn) {
     761          180 :     double energyCharged = energyIn - myConsum;
     762              :     /*
     763              :     Apply recuperation or propulsion efficiency if necessary
     764              :         1. if (energyIn > 0.0 && energyCharged > 0 && it->getConsum() >= 0) = > recuper eff for energyCharged
     765              :         2. if (energyIn > 0.0 && energyCharged > 0 && it->getConsum() < 0)  => recuper eff only for energyIn
     766              :         3. if (energyIn < 0.0 && it->getConsum() > 0) => 1/propulsion eff only for energyIn
     767              :         4. if (energyIn < 0.0 && energyCharged < 0 && it->getConsum() < 0) => 1/propulsion eff only for energyCharged
     768              :     */
     769          180 :     if (energyIn > 0.0 && energyCharged > 0.0) {
     770              :         // the vehicle is charging battery from overhead wire
     771          164 :         if (myConsum >= 0) {
     772          141 :             energyCharged *= myHolder.getEmissionParameters()->getDouble(SUMO_ATTR_RECUPERATIONEFFICIENCY);
     773              :         } else {
     774           23 :             energyCharged = myHolder.getEmissionParameters()->getDouble(SUMO_ATTR_RECUPERATIONEFFICIENCY) * energyIn - myConsum;
     775              :         }
     776           16 :     } else if (energyIn < 0.0 && energyCharged < 0.0) {
     777              :         // the vehicle is recuperating energy into the overhead wire and discharging batterypack at the same time
     778            0 :         if (myConsum >= 0) {
     779            0 :             energyCharged *= energyIn / myHolder.getEmissionParameters()->getDouble(SUMO_ATTR_PROPULSIONEFFICIENCY) - myConsum;
     780              :         } else {
     781            0 :             energyCharged /= myHolder.getEmissionParameters()->getDouble(SUMO_ATTR_PROPULSIONEFFICIENCY);
     782              :         }
     783              :     }
     784          180 :     return energyCharged;
     785              : }
     786              : 
     787              : double
     788         1520 : MSDevice_ElecHybrid::getConsum() const {
     789         1520 :     return myConsum;
     790              : }
     791              : 
     792              : void
     793         1205 : MSDevice_ElecHybrid::setConsum(const double consumption) {
     794         1205 :     myConsum = consumption;
     795         1205 : }
     796              : 
     797              : void
     798          180 : MSDevice_ElecHybrid::updateTotalEnergyWasted(const double energyWasted) {
     799          180 :     myTotalEnergyWasted += energyWasted;
     800          180 : }
     801              : 
     802              : double
     803          385 : MSDevice_ElecHybrid::getEnergyCharged() const {
     804          385 :     return myEnergyCharged;
     805              : }
     806              : 
     807              : void
     808          180 : MSDevice_ElecHybrid::setEnergyCharged(double energyCharged) {
     809          180 :     myEnergyCharged = energyCharged;
     810          180 : }
     811              : 
     812              : double
     813          385 : MSDevice_ElecHybrid::getCircuitAlpha() const {
     814          385 :     if (myActOverheadWireSegment != nullptr && MSGlobals::gOverheadWireSolver) {
     815              : #ifdef HAVE_EIGEN
     816          180 :         Circuit* owc = myActOverheadWireSegment->getCircuit();
     817          180 :         if (owc != nullptr) {
     818          180 :             return owc->getAlphaBest() ;
     819              :         }
     820              : #else
     821              :         WRITE_ERROR(TL("Overhead wire solver is on, but the Eigen library has not been compiled in!"))
     822              : #endif
     823              :     }
     824              :     return NAN;
     825              : }
     826              : 
     827              : double
     828          385 : MSDevice_ElecHybrid::getPowerWanted() const {
     829          385 :     if (veh_elem != nullptr) {
     830          180 :         return veh_elem->getPowerWanted();
     831              :     }
     832              :     return NAN;
     833              : }
     834              : 
     835              : double
     836          385 : MSDevice_ElecHybrid::getCurrentFromOverheadWire() const {
     837          385 :     return myCircuitCurrent;
     838              : }
     839              : 
     840              : void
     841          180 : MSDevice_ElecHybrid::setCurrentFromOverheadWire(double current) {
     842          180 :     myCircuitCurrent = current;
     843          180 : }
     844              : 
     845              : double
     846         1095 : MSDevice_ElecHybrid::getVoltageOfOverheadWire() const {
     847         1095 :     return myCircuitVoltage;
     848              : }
     849              : 
     850              : void
     851          180 : MSDevice_ElecHybrid::setVoltageOfOverheadWire(double voltage) {
     852          180 :     myCircuitVoltage = voltage;
     853          180 : }
     854              : 
     855              : std::string
     856          385 : MSDevice_ElecHybrid::getOverheadWireSegmentID() const {
     857          385 :     if (myActOverheadWireSegment != nullptr) {
     858              :         return myActOverheadWireSegment->getID();
     859              :     } else {
     860          205 :         return "";
     861              :     }
     862              : }
     863              : 
     864              : std::string
     865          385 : MSDevice_ElecHybrid::getTractionSubstationID() const {
     866          385 :     if (myActOverheadWireSegment != nullptr) {
     867              :         MSTractionSubstation* ts = myActOverheadWireSegment->getTractionSubstation();
     868          180 :         if (ts != nullptr) {
     869              :             return ts->getID();
     870              :         }
     871              :     }
     872          205 :     return "";
     873              : }
     874              : 
     875              : bool
     876         1135 : MSDevice_ElecHybrid::isBatteryDischarged() const {
     877         1135 :     return myBatteryDischargedLogic;
     878              : }
     879              : 
     880              : double
     881          180 : MSDevice_ElecHybrid::storeEnergyToBattery(const double energy) {
     882              :     // Original energy state of the battery pack
     883          180 :     double previousEnergyInBattery = myActualBatteryCapacity;
     884              :     // Push as much energy as the battery pack can accomodate.
     885              :     // The battery has SOC limits that will prevent it from being overcharged, hence some energy may remain "free".
     886          180 :     setActualBatteryCapacity(myActualBatteryCapacity + energy);
     887              :     // The "free" energy that still remains and has to be stored elsewhere or burned on resistors is the difference.
     888          180 :     return myActualBatteryCapacity - previousEnergyInBattery;
     889              : }
     890              : 
     891              : void
     892          180 : MSDevice_ElecHybrid::setActualBatteryCapacity(const double actualBatteryCapacity) {
     893              :     // Use the SOC limits to cap the actual battery capacity
     894          180 :     if (actualBatteryCapacity < mySOCMin * myMaximumBatteryCapacity) {
     895              :         //WRITE_WARNINGF(TL("The Battery of vehicle '%' has been exhausted."), getID());
     896            0 :         myActualBatteryCapacity = MIN2(mySOCMin * myMaximumBatteryCapacity, myActualBatteryCapacity);
     897          180 :     } else if (actualBatteryCapacity > mySOCMax * myMaximumBatteryCapacity) {
     898            0 :         myActualBatteryCapacity = MAX2(mySOCMax * myMaximumBatteryCapacity, myActualBatteryCapacity);
     899              :     } else {
     900          180 :         myActualBatteryCapacity = actualBatteryCapacity;
     901              :     }
     902          180 : }
     903              : 
     904              : void
     905            0 : MSDevice_ElecHybrid::setParameter(const std::string& key, const std::string& value) {
     906              :     double doubleValue;
     907              :     try {
     908            0 :         doubleValue = StringUtils::toDouble(value);
     909            0 :     } catch (NumberFormatException&) {
     910            0 :         throw InvalidArgument("Setting parameter '" + key + "' requires a number for device of type '" + deviceName() + "'");
     911            0 :     }
     912            0 :     if (key == toString(SUMO_ATTR_ACTUALBATTERYCAPACITY)) {
     913            0 :         myActualBatteryCapacity = doubleValue;
     914            0 :     } else if (key == toString(SUMO_ATTR_MAXIMUMBATTERYCAPACITY)) {
     915            0 :         myMaximumBatteryCapacity = doubleValue;
     916            0 :     } else if (key == toString(SUMO_ATTR_OVERHEADWIRECHARGINGPOWER)) {
     917            0 :         myOverheadWireChargingPower = doubleValue;
     918              :     } else {
     919            0 :         throw InvalidArgument("Setting parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
     920              :     }
     921            0 : }
     922              : 
     923              : double
     924           70 : MSDevice_ElecHybrid::acceleration(SUMOVehicle& veh, double power, double oldSpeed) {
     925           70 :     return PollutantsInterface::getEnergyHelper().acceleration(0, PollutantsInterface::ELEC, oldSpeed, power, veh.getSlope(), myHolder.getEmissionParameters());
     926              : }
     927              : 
     928              : double
     929         1205 : MSDevice_ElecHybrid::consumption(SUMOVehicle& veh, double a, double newSpeed) {
     930         1205 :     return PollutantsInterface::getEnergyHelper().compute(0, PollutantsInterface::ELEC, newSpeed, a, veh.getSlope(), myHolder.getEmissionParameters()) * TS;
     931              : }
     932              : 
     933              : 
     934              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1