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 : /****************************************************************************/
|