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

          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       36320 : MSDevice_ElecHybrid::insertOptions(OptionsCont& oc) {
      55       36320 :     oc.addOptionSubTopic("ElecHybrid Device");
      56       72640 :     insertDefaultAssignmentOptions("elechybrid", "ElecHybrid Device", oc);
      57       36320 : }
      58             : 
      59             : 
      60             : void
      61     4658938 : MSDevice_ElecHybrid::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
      62             :     // Check if vehicle should get an 'elecHybrid' device.
      63     4658938 :     OptionsCont& oc = OptionsCont::getOptions();
      64     9317876 :     if (equippedByDefaultAssignmentOptions(oc, "elechybrid", v, false)) {
      65             :         // Yes, build the device.
      66             :         // Fetch the battery capacity (if present) from the vehicle descriptor.
      67          58 :         const SUMOVTypeParameter& typeParams = v.getVehicleType().getParameter();
      68          58 :         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          58 :         std::string attrName = toString(SUMO_ATTR_ACTUALBATTERYCAPACITY);
      73          58 :         if (vehicleParams.hasParameter(attrName)) {
      74          58 :             const std::string abc = vehicleParams.getParameter(attrName, "-1");
      75             :             try {
      76          58 :                 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          58 :         attrName = toString(SUMO_ATTR_MAXIMUMBATTERYCAPACITY);
      97          58 :         if (typeParams.hasParameter(attrName)) {
      98          58 :             const std::string mbc = typeParams.getParameter(attrName, "-1");
      99             :             try {
     100          58 :                 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          58 :         attrName = toString(SUMO_ATTR_OVERHEADWIRECHARGINGPOWER);
     111          58 :         if (typeParams.hasParameter(attrName)) {
     112          58 :             const std::string ocp = typeParams.getParameter(attrName, "-1");
     113             :             try {
     114          58 :                 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          58 :         MSDevice_ElecHybrid* device = new MSDevice_ElecHybrid(v, "elecHybrid_" + v.getID(),
     124          58 :                 actualBatteryCapacity, maximumBatteryCapacity, overheadWireChargingPower);
     125             : 
     126             :         // Add device to vehicle
     127          58 :         into.push_back(device);
     128             :     }
     129     4658938 : }
     130             : 
     131             : 
     132             : // ---------------------------------------------------------------------------
     133             : // MSDevice_ElecHybrid-methods
     134             : // ---------------------------------------------------------------------------
     135          58 : MSDevice_ElecHybrid::MSDevice_ElecHybrid(SUMOVehicle& holder, const std::string& id,
     136          58 :         const double actualBatteryCapacity, const double maximumBatteryCapacity, const double overheadWireChargingPower) :
     137             :     MSVehicleDevice(holder, id),
     138          58 :     myActualBatteryCapacity(0),   // [actualBatteryCapacity <= maximumBatteryCapacity]
     139          58 :     myMaximumBatteryCapacity(0),  // [maximumBatteryCapacity >= 0]t
     140          58 :     myOverheadWireChargingPower(0),
     141          58 :     myLastAngle(NAN),
     142          58 :     myConsum(0),
     143          58 :     myBatteryDischargedLogic(false),
     144          58 :     myCharging(false),            // Initially vehicle don't charge
     145          58 :     myEnergyCharged(0),           // Initially the energy charged is zero
     146          58 :     myCircuitCurrent(NAN),        // Initially the current is unknown
     147          58 :     myCircuitVoltage(NAN),        // Initially the voltage is unknown as well
     148          58 :     myMaxBatteryCharge(NAN),      // Initial maximum of the the battery energy during the simulation is unknown
     149          58 :     myMinBatteryCharge(NAN),      // Initial minimum of the the battery energy during the simulation is unknown
     150          58 :     myTotalEnergyConsumed(0),     // No energy spent yet
     151          58 :     myTotalEnergyRegenerated(0),  // No energy regenerated
     152          58 :     myTotalEnergyWasted(0),       // No energy wasted on resistors
     153             :     // RICE_TODO: make these two parameters user configurable
     154          58 :     mySOCMin(0.005),              // Minimum SOC of the battery
     155          58 :     mySOCMax(0.980),              // Maximum SOC of the battery
     156          58 :     myActOverheadWireSegment(nullptr),         // Initially the vehicle isn't under any overhead wire segment
     157          58 :     myPreviousOverheadWireSegment(nullptr),    // Initially the vehicle wasn't under any overhead wire segment
     158          58 :     veh_elem(nullptr),
     159          58 :     veh_pos_tail_elem(nullptr),
     160          58 :     pos_veh_node(nullptr) {
     161             : 
     162          58 :     EnergyParams* const params = myHolder.getEmissionParameters();
     163          58 :     params->setDouble(SUMO_ATTR_MAXIMUMPOWER, holder.getVehicleType().getParameter().getDouble(toString(SUMO_ATTR_MAXIMUMPOWER), 100000.));
     164             : 
     165          58 :     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          58 :         myMaximumBatteryCapacity = maximumBatteryCapacity;
     169             :     }
     170             : 
     171          58 :     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          58 :         myActualBatteryCapacity = actualBatteryCapacity;
     176             :     }
     177             : 
     178          58 :     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          58 :         myOverheadWireChargingPower = overheadWireChargingPower;
     182             :     }
     183          58 : }
     184             : 
     185             : 
     186         116 : MSDevice_ElecHybrid::~MSDevice_ElecHybrid() {
     187         116 : }
     188             : 
     189             : 
     190             : bool
     191        1289 : MSDevice_ElecHybrid::notifyMove(SUMOTrafficObject& tObject, double /* oldPos */, double /* newPos */, double /* newSpeed */) {
     192        1289 :     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        1289 :     if (myActualBatteryCapacity < mySOCMin * myMaximumBatteryCapacity) {
     208           0 :         myBatteryDischargedLogic = true;
     209             :     } else {
     210        1289 :         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        1289 :     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             :     }
     234             : 
     235             :     /* Check if there is an overhead wire either over the lane where the vehicle is or over a
     236             :        neighbouring 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        1289 :     std::string overheadWireSegmentID = MSNet::getInstance()->getStoppingPlaceID(veh.getLane(), veh.getPositionOnLane(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     240             : 
     241             :     //check overhead line on the left neighbouring lane
     242        1289 :     if (overheadWireSegmentID == "" && veh.getEdge()->leftLane(veh.getLane()) != nullptr) {
     243         906 :         overheadWireSegmentID = MSNet::getInstance()->getStoppingPlaceID(veh.getEdge()->leftLane(veh.getLane()), veh.getPositionOnLane(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     244             :     }
     245             :     //check overhead line on the right neighbouring lane
     246        1289 :     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 neighbouring lanes) */
     253        1289 :     if (overheadWireSegmentID != "") {
     254             :         /* Update the actual overhead wire segment of this device */
     255         816 :         myActOverheadWireSegment =
     256         816 :             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         816 :         if (myPreviousOverheadWireSegment != myActOverheadWireSegment) {
     266          46 :             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          46 :             myActOverheadWireSegment->addVehicle(veh);
     277          46 :             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         816 :         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         816 :             deleteVehicleFromCircuit(veh);
     291             : 
     292             :             /* Add the vehicle to the circuit in case that there is a substation that provides
     293             :                power to it. */
     294         816 :             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 ovehead line section in the contrary direction).
     328             : 
     329             :                    The original solution runs into problems when a vehicle is going on a neigboring 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             :                 // ignoreing 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 typicallty 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 vehilce 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->addSolvingCirucitToEndOfTimestepEvents();
     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         636 :                 myEnergyCharged = -myConsum;
     414             :                 // Update Battery charge
     415         636 :                 myActualBatteryCapacity += myEnergyCharged;
     416             :                 // No substation is connected to this segmenr and the charging output is therefore zero.
     417         636 :                 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 approximaion 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 volate 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             :                 // ignoreing 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 stataion current limiting here as well.
     458           0 :                 myCircuitCurrent = powerWanted / voltage;
     459           0 :                 myCircuitVoltage = voltage;
     460             : 
     461             :                 // Calulate energy charged
     462           0 :                 double energyIn = WATT2WATTHR(powerWanted);
     463             : 
     464             :                 // Calulate 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         816 :         myPreviousOverheadWireSegment = myActOverheadWireSegment;
     495             :     } else {
     496             :         /*
     497             :             No overhead wires, no charging.
     498             :         */
     499             : 
     500             :         // Disable charing flag
     501         473 :         myCharging = false;
     502             : 
     503             :         // Invalidate the circuit voltage and current
     504         473 :         myCircuitCurrent = NAN;
     505         473 :         myCircuitVoltage = NAN;
     506             : 
     507             :         // Additional bookkeeping in case that the circuit solver is used
     508         473 :         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         473 :             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         473 :         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 afer deleteVehicleFromCircuit() as the file uses myPreviousOverheadWire.
     525         473 :         if (myPreviousOverheadWireSegment != nullptr) {
     526          12 :             myPreviousOverheadWireSegment->eraseVehicle(veh);
     527          12 :             MSTractionSubstation* ts = myPreviousOverheadWireSegment->getTractionSubstation();
     528          12 :             if (ts != nullptr) {
     529           6 :                 ts->decreaseElecHybridCount();
     530           6 :                 ts->eraseVehicle(this);
     531             :             }
     532          12 :             myPreviousOverheadWireSegment = nullptr;
     533             :         }
     534             : 
     535             :         // Energy for the powertrain is provided by the battery pack
     536         473 :         myEnergyCharged = -myConsum;
     537             :         // Update battery charge
     538         473 :         myActualBatteryCapacity += myEnergyCharged;
     539             :     }
     540             : 
     541             :     // Update the statistical values
     542             :     // RICE_TODO: update these statistical values also after solving circuit by electric circuit
     543        1289 :     if (std::isnan(myMaxBatteryCharge) || myMaxBatteryCharge < myActualBatteryCapacity) {
     544         206 :         myMaxBatteryCharge = myActualBatteryCapacity;
     545             :     }
     546        1289 :     if (std::isnan(myMinBatteryCharge) || myMinBatteryCharge > myActualBatteryCapacity) {
     547         413 :         myMinBatteryCharge = myActualBatteryCapacity;
     548             :     }
     549             : 
     550        1289 :     if (myConsum > 0.0) {
     551        1131 :         myTotalEnergyConsumed += myConsum;
     552             :     } else {
     553         158 :         myTotalEnergyRegenerated -= myConsum;
     554             :     }
     555        1289 :     myTotalEnergyWasted += energyWasted;
     556             : 
     557        1289 :     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        1299 : MSDevice_ElecHybrid::deleteVehicleFromCircuit(SUMOVehicle& veh) {
     584        1299 :     if (myPreviousOverheadWireSegment != nullptr) {
     585         798 :         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             :             //eraising of tail elements (the element connected to the veh_node is afterthen 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()->descreaseLastId();
     641         360 :             delete pos_veh_node;
     642         180 :             pos_veh_node = nullptr;
     643             :         }
     644             :     }
     645        1299 : }
     646             : 
     647             : bool
     648          96 : MSDevice_ElecHybrid::notifyEnter(
     649             :     SUMOTrafficObject& tObject,
     650             :     MSMoveReminder::Notification /* reason */,
     651             :     const MSLane* /* enteredLane */) {
     652          96 :     if (!tObject.isVehicle()) {
     653           0 :         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          66 : MSDevice_ElecHybrid::notifyLeave(
     665             :     SUMOTrafficObject& tObject,
     666             :     double /*lastPos*/,
     667             :     MSMoveReminder::Notification reason,
     668             :     const MSLane* /* enteredLane */) {
     669          66 :     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          66 :     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        1201 : MSDevice_ElecHybrid::getActualBatteryCapacity() const {
     733        1201 :     return myActualBatteryCapacity;
     734             : }
     735             : 
     736             : 
     737             : double
     738        1532 : MSDevice_ElecHybrid::getMaximumBatteryCapacity() const {
     739        1532 :     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        1289 : MSDevice_ElecHybrid::getParameterDouble(const std::string& key) const {
     767        1289 :     if (key == toString(SUMO_ATTR_MAXIMUMPOWER)) {
     768        1289 :         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        1674 : MSDevice_ElecHybrid::getConsum() const {
     805        1674 :     return myConsum;
     806             : }
     807             : 
     808             : void
     809        1360 : MSDevice_ElecHybrid::setConsum(const double consumption) {
     810        1360 :     myConsum = consumption;
     811        1360 : }
     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        1201 : MSDevice_ElecHybrid::getVoltageOfOverheadWire() const {
     863        1201 :     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        1289 : MSDevice_ElecHybrid::isBatteryDischarged() const {
     893        1289 :     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        1360 : MSDevice_ElecHybrid::consumption(SUMOVehicle& veh, double a, double newSpeed) {
     950        1360 :     myHolder.getEmissionParameters()->setDouble(SUMO_ATTR_ANGLE, std::isnan(myLastAngle) ? 0. : GeomHelper::angleDiff(myLastAngle, veh.getAngle()));
     951        1360 :     return PollutantsInterface::getEnergyHelper().compute(0, PollutantsInterface::ELEC, newSpeed, a, veh.getSlope(), myHolder.getEmissionParameters()) * TS;
     952             : }
     953             : 
     954             : 
     955             : /****************************************************************************/

Generated by: LCOV version 1.14