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