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