LCOV - code coverage report
Current view: top level - src/microsim/devices - MSDevice_ElecHybrid.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 67.4 % 374 252
Test Date: 2024-11-22 15:46:21 Functions: 94.3 % 35 33

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

Generated by: LCOV version 2.0-1