1 /****************************************************************************/
2 // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
3 // Copyright (C) 2005-2019 German Aerospace Center (DLR) and others.
4 // This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v2.0
6 // which accompanies this distribution, and is available at
7 // http://www.eclipse.org/legal/epl-v20.html
8 // SPDX-License-Identifier: EPL-2.0
9 /****************************************************************************/
10 /// @file MSCalibrator.cpp
11 /// @author Daniel Krajzewicz
12 /// @author Jakob Erdmann
13 /// @author Michael Behrisch
14 /// @date Tue, May 2005
15 /// @version $Id$
16 ///
17 // Calibrates the flow on an edge by removing an inserting vehicles
18 /****************************************************************************/
19
20
21 // ===========================================================================
22 // included modules
23 // ===========================================================================
24 #include <config.h>
25
26 #include <string>
27 #include <algorithm>
28 #include <cmath>
29 #include <microsim/MSNet.h>
30 #include <microsim/MSEdge.h>
31 #include <microsim/MSLane.h>
32 #include <microsim/MSEventControl.h>
33 #include <microsim/MSVehicleControl.h>
34 #include <microsim/output/MSRouteProbe.h>
35 #include <utils/xml/SUMOXMLDefinitions.h>
36 #include <utils/common/ToString.h>
37 #include <utils/common/UtilExceptions.h>
38 #include <utils/common/StringTokenizer.h>
39 #include <utils/xml/XMLSubSys.h>
40 #include <utils/common/StringUtils.h>
41 #include <utils/options/OptionsCont.h>
42 #include <utils/vehicle/SUMOVehicleParserHelper.h>
43 #include <utils/distribution/RandomDistributor.h>
44 #include <utils/vehicle/SUMOVehicleParameter.h>
45 #include "MSCalibrator.h"
46
47 //#define MSCalibrator_DEBUG
48
49 // ===========================================================================
50 // static members
51 // ===========================================================================
52 std::vector<MSMoveReminder*> MSCalibrator::LeftoverReminders;
53 std::vector<SUMOVehicleParameter*> MSCalibrator::LeftoverVehicleParameters;
54
55 // ===========================================================================
56 // method definitions
57 // ===========================================================================
MSCalibrator(const std::string & id,const MSEdge * const edge,MSLane * lane,const double pos,const std::string & aXMLFilename,const std::string & outputFilename,const SUMOTime freq,const double length,const MSRouteProbe * probe,bool addLaneMeanData)58 MSCalibrator::MSCalibrator(const std::string& id,
59 const MSEdge* const edge,
60 MSLane* lane,
61 const double pos,
62 const std::string& aXMLFilename,
63 const std::string& outputFilename,
64 const SUMOTime freq, const double length,
65 const MSRouteProbe* probe,
66 bool addLaneMeanData) :
67 MSTrigger(id),
68 MSRouteHandler(aXMLFilename, true),
69 myEdge(edge),
70 myLane(lane),
71 myPos(pos), myProbe(probe),
72 myEdgeMeanData(nullptr, length, false, nullptr),
73 myCurrentStateInterval(myIntervals.begin()),
74 myOutput(nullptr), myFrequency(freq), myRemoved(0),
75 myInserted(0), myClearedInJam(0),
76 mySpeedIsDefault(true), myDidSpeedAdaption(false), myDidInit(false),
77 myDefaultSpeed(myLane == nullptr ? myEdge->getSpeedLimit() : myLane->getSpeedLimit()),
78 myHaveWarnedAboutClearingJam(false),
79 myAmActive(false) {
80 if (outputFilename != "") {
81 myOutput = &OutputDevice::getDevice(outputFilename);
82 myOutput->writeXMLHeader("calibratorstats", "calibratorstats_file.xsd");
83 }
84 if (aXMLFilename != "") {
85 XMLSubSys::runParser(*this, aXMLFilename);
86 if (!myDidInit) {
87 init();
88 }
89 }
90 if (addLaneMeanData) {
91 // disabled for METriggeredCalibrator
92 for (int i = 0; i < (int)myEdge->getLanes().size(); ++i) {
93 MSLane* lane = myEdge->getLanes()[i];
94 if (myLane == nullptr || myLane == lane) {
95 //std::cout << " cali=" << getID() << " myLane=" << Named::getIDSecure(myLane) << " checkLane=" << i << "\n";
96 MSMeanData_Net::MSLaneMeanDataValues* laneData = new MSMeanData_Net::MSLaneMeanDataValues(lane, lane->getLength(), true, nullptr);
97 laneData->setDescription("meandata_calibrator_" + lane->getID());
98 LeftoverReminders.push_back(laneData);
99 myLaneMeanData.push_back(laneData);
100 VehicleRemover* remover = new VehicleRemover(lane, (int)i, this);
101 LeftoverReminders.push_back(remover);
102 myVehicleRemovers.push_back(remover);
103 }
104 }
105 }
106 }
107
108
109 void
init()110 MSCalibrator::init() {
111 if (myIntervals.size() > 0) {
112 if (myIntervals.back().end == -1) {
113 myIntervals.back().end = SUMOTime_MAX;
114 }
115 // calibration should happen after regular insertions have taken place
116 MSNet::getInstance()->getEndOfTimestepEvents()->addEvent(new CalibratorCommand(this));
117 } else {
118 WRITE_WARNING("No flow intervals in calibrator '" + myID + "'.");
119 }
120 myDidInit = true;
121 }
122
123
~MSCalibrator()124 MSCalibrator::~MSCalibrator() {
125 if (myCurrentStateInterval != myIntervals.end()) {
126 writeXMLOutput();
127 }
128 for (std::vector<VehicleRemover*>::iterator it = myVehicleRemovers.begin(); it != myVehicleRemovers.end(); ++it) {
129 (*it)->disable();
130 }
131 }
132
133
134 void
myStartElement(int element,const SUMOSAXAttributes & attrs)135 MSCalibrator::myStartElement(int element,
136 const SUMOSAXAttributes& attrs) {
137 if (element == SUMO_TAG_FLOW) {
138 AspiredState state;
139 SUMOTime lastEnd = -1;
140 if (myIntervals.size() > 0) {
141 lastEnd = myIntervals.back().end;
142 if (lastEnd == -1) {
143 lastEnd = myIntervals.back().begin;
144 }
145 }
146 try {
147 bool ok = true;
148 state.q = attrs.getOpt<double>(SUMO_ATTR_VEHSPERHOUR, nullptr, ok, -1.);
149 state.v = attrs.getOpt<double>(SUMO_ATTR_SPEED, nullptr, ok, -1.);
150 state.begin = attrs.getSUMOTimeReporting(SUMO_ATTR_BEGIN, myID.c_str(), ok);
151 if (state.begin < lastEnd) {
152 WRITE_ERROR("Overlapping or unsorted intervals in calibrator '" + myID + "'.");
153 }
154 state.end = attrs.getOptSUMOTimeReporting(SUMO_ATTR_END, myID.c_str(), ok, -1);
155 state.vehicleParameter = SUMOVehicleParserHelper::parseVehicleAttributes(attrs, true, true);
156 LeftoverVehicleParameters.push_back(state.vehicleParameter);
157 // vehicles should be inserted with max speed unless stated otherwise
158 if (state.vehicleParameter->departSpeedProcedure == DEPART_SPEED_DEFAULT) {
159 state.vehicleParameter->departSpeedProcedure = DEPART_SPEED_MAX;
160 }
161 // vehicles should be inserted on any lane unless stated otherwise
162 if (state.vehicleParameter->departLaneProcedure == DEPART_LANE_DEFAULT) {
163 if (myLane == nullptr) {
164 state.vehicleParameter->departLaneProcedure = DEPART_LANE_ALLOWED_FREE;
165 } else {
166 state.vehicleParameter->departLaneProcedure = DEPART_LANE_GIVEN;
167 state.vehicleParameter->departLane = myLane->getIndex();
168 }
169 } else if (myLane != nullptr && (
170 state.vehicleParameter->departLaneProcedure != DEPART_LANE_GIVEN
171 || state.vehicleParameter->departLane != myLane->getIndex())) {
172 WRITE_WARNING("Insertion lane may differ from calibrator lane for calibrator '" + getID() + "'.");
173 }
174 if (state.vehicleParameter->vtypeid != DEFAULT_VTYPE_ID &&
175 MSNet::getInstance()->getVehicleControl().getVType(state.vehicleParameter->vtypeid) == nullptr) {
176 WRITE_ERROR("Unknown vehicle type '" + state.vehicleParameter->vtypeid + "' in calibrator '" + myID + "'.");
177 }
178 } catch (EmptyData&) {
179 WRITE_ERROR("Mandatory attribute missing in definition of calibrator '" + myID + "'.");
180 } catch (NumberFormatException&) {
181 WRITE_ERROR("Non-numeric value for numeric attribute in definition of calibrator '" + myID + "'.");
182 }
183 if (state.q < 0 && state.v < 0) {
184 WRITE_ERROR("Either 'vehsPerHour' or 'speed' has to be given in flow definition of calibrator '" + myID + "'.");
185 }
186 if (myIntervals.size() > 0 && myIntervals.back().end == -1) {
187 myIntervals.back().end = state.begin;
188 }
189 myIntervals.push_back(state);
190 myCurrentStateInterval = myIntervals.begin();
191 } else {
192 MSRouteHandler::myStartElement(element, attrs);
193 }
194 }
195
196
197 void
myEndElement(int element)198 MSCalibrator::myEndElement(int element) {
199 if (element == SUMO_TAG_CALIBRATOR) {
200 if (!myDidInit) {
201 init();
202 }
203 } else if (element != SUMO_TAG_FLOW) {
204 MSRouteHandler::myEndElement(element);
205 }
206 }
207
208
209 void
writeXMLOutput()210 MSCalibrator::writeXMLOutput() {
211 if (myOutput != nullptr) {
212 updateMeanData();
213 const int p = passed();
214 // meandata will be off if vehicles are removed on the next edge instead of this one
215 const int discrepancy = myEdgeMeanData.nVehEntered + myEdgeMeanData.nVehDeparted - myEdgeMeanData.nVehVaporized - passed();
216 assert(discrepancy >= 0);
217 const std::string ds = (discrepancy > 0 ? "\" vaporizedOnNextEdge=\"" + toString(discrepancy) : "");
218 const double durationSeconds = STEPS2TIME(myCurrentStateInterval->end - myCurrentStateInterval->begin);
219 (*myOutput) << " <interval begin=\"" << time2string(myCurrentStateInterval->begin) <<
220 "\" end=\"" << time2string(myCurrentStateInterval->end) <<
221 "\" id=\"" << myID <<
222 "\" nVehContrib=\"" << p <<
223 "\" removed=\"" << myRemoved <<
224 "\" inserted=\"" << myInserted <<
225 "\" cleared=\"" << myClearedInJam <<
226 "\" flow=\"" << p * 3600.0 / durationSeconds <<
227 "\" aspiredFlow=\"" << myCurrentStateInterval->q <<
228 "\" speed=\"" << myEdgeMeanData.getTravelledDistance() / myEdgeMeanData.getSamples() <<
229 "\" aspiredSpeed=\"" << myCurrentStateInterval->v <<
230 ds << //optional
231 "\"/>\n";
232 }
233 myDidSpeedAdaption = false;
234 myInserted = 0;
235 myRemoved = 0;
236 myClearedInJam = 0;
237 myHaveWarnedAboutClearingJam = false;
238 reset();
239 }
240
241
242 bool
isCurrentStateActive(SUMOTime time)243 MSCalibrator::isCurrentStateActive(SUMOTime time) {
244 while (myCurrentStateInterval != myIntervals.end() && myCurrentStateInterval->end <= time) {
245 // XXX what about skipped intervals?
246 myCurrentStateInterval++;
247 }
248 return myCurrentStateInterval != myIntervals.end() &&
249 myCurrentStateInterval->begin <= time && myCurrentStateInterval->end > time;
250 }
251
252 int
totalWished() const253 MSCalibrator::totalWished() const {
254 if (myCurrentStateInterval != myIntervals.end()) {
255 const double totalHourFraction = STEPS2TIME(myCurrentStateInterval->end - myCurrentStateInterval->begin) / (double) 3600.;
256 return (int)std::floor(myCurrentStateInterval->q * totalHourFraction + 0.5); // round to closest int
257 } else {
258 return -1;
259 }
260 }
261
262
263 double
currentFlow() const264 MSCalibrator::currentFlow() const {
265 const double totalHourFraction = STEPS2TIME(MSNet::getInstance()->getCurrentTimeStep() - myCurrentStateInterval->begin) / (double) 3600.;
266 return passed() / totalHourFraction;
267 }
268
269 double
currentSpeed() const270 MSCalibrator::currentSpeed() const {
271 if (myEdgeMeanData.getSamples() > 0) {
272 return myEdgeMeanData.getTravelledDistance() / myEdgeMeanData.getSamples();
273 } else {
274 return -1;
275 }
276 }
277
278
279 bool
removePending()280 MSCalibrator::removePending() {
281 if (myToRemove.size() > 0) {
282 MSVehicleControl& vc = MSNet::getInstance()->getVehicleControl();
283 // it is not save to remove the vehicles inside
284 // VehicleRemover::notifyEnter so we do it here
285 for (std::set<std::string>::iterator it = myToRemove.begin(); it != myToRemove.end(); ++it) {
286 MSVehicle* vehicle = dynamic_cast<MSVehicle*>(vc.getVehicle(*it));
287 if (vehicle != nullptr) {
288 vehicle->onRemovalFromNet(MSMoveReminder::NOTIFICATION_VAPORIZED);
289 vehicle->getLane()->removeVehicle(vehicle, MSMoveReminder::NOTIFICATION_VAPORIZED);
290 vc.scheduleVehicleRemoval(vehicle);
291 } else {
292 WRITE_WARNING("Calibrator '" + getID() + "' could not remove vehicle '" + *it + "'.");
293 }
294 }
295 myToRemove.clear();
296 return true;
297 }
298 return false;
299 }
300
301
302 SUMOTime
execute(SUMOTime currentTime)303 MSCalibrator::execute(SUMOTime currentTime) {
304 // get current simulation values (valid for the last simulation second)
305 // XXX could we miss vehicle movements if this is called less often than every DELTA_T (default) ?
306 updateMeanData();
307 const bool hadRemovals = removePending();
308 // check whether an adaptation value exists
309 if (isCurrentStateActive(currentTime)) {
310 myAmActive = true;
311 // all happens in isCurrentStateActive()
312 } else {
313 myAmActive = false;
314 reset();
315 if (!mySpeedIsDefault) {
316 // reset speed to default
317 if (myLane == nullptr) {
318 myEdge->setMaxSpeed(myDefaultSpeed);
319 } else {
320 myLane->setMaxSpeed(myDefaultSpeed);
321 }
322 mySpeedIsDefault = true;
323 }
324 if (myCurrentStateInterval == myIntervals.end()) {
325 // keep calibrator alive for gui but do not call again
326 return TIME2STEPS(86400);
327 }
328 return myFrequency;
329 }
330 // we are active
331 if (!myDidSpeedAdaption && myCurrentStateInterval->v >= 0) {
332 if (myLane == nullptr) {
333 myEdge->setMaxSpeed(myCurrentStateInterval->v);
334 } else {
335 myLane->setMaxSpeed(myCurrentStateInterval->v);
336 }
337 mySpeedIsDefault = false;
338 myDidSpeedAdaption = true;
339 }
340
341 const bool calibrateFlow = myCurrentStateInterval->q >= 0;
342 const int totalWishedNum = totalWished();
343 int adaptedNum = passed() + myClearedInJam;
344 #ifdef MSCalibrator_DEBUG
345 std::cout << time2string(currentTime) << " " << myID
346 << " q=" << myCurrentStateInterval->q
347 << " totalWished=" << totalWishedNum
348 << " adapted=" << adaptedNum
349 << " jam=" << invalidJam(myLane == 0 ? -1 : myLane->getIndex())
350 << " entered=" << myEdgeMeanData.nVehEntered
351 << " departed=" << myEdgeMeanData.nVehDeparted
352 << " arrived=" << myEdgeMeanData.nVehArrived
353 << " left=" << myEdgeMeanData.nVehLeft
354 << " waitSecs=" << myEdgeMeanData.waitSeconds
355 << " vaporized=" << myEdgeMeanData.nVehVaporized
356 << "\n";
357 #endif
358 if (calibrateFlow && adaptedNum < totalWishedNum && !hadRemovals) {
359 // we need to insert some vehicles
360 const double hourFraction = STEPS2TIME(currentTime - myCurrentStateInterval->begin + DELTA_T) / (double) 3600.;
361 const int wishedNum = (int)std::floor(myCurrentStateInterval->q * hourFraction + 0.5); // round to closest int
362 // only the difference between inflow and aspiredFlow should be added, thus
363 // we should not count vehicles vaporized from a jam here
364 // if we have enough time left we can add missing vehicles later
365 const int relaxedInsertion = (int)std::floor(STEPS2TIME(myCurrentStateInterval->end - currentTime) / 3);
366 const int insertionSlack = MAX2(0, adaptedNum + relaxedInsertion - totalWishedNum);
367 // increase number of vehicles
368 #ifdef MSCalibrator_DEBUG
369 std::cout
370 << " wished:" << wishedNum
371 << " slack:" << insertionSlack
372 << " before:" << adaptedNum
373 << "\n";
374 #endif
375 while (wishedNum > adaptedNum + insertionSlack) {
376 SUMOVehicleParameter* pars = myCurrentStateInterval->vehicleParameter;
377 const MSRoute* route = myProbe != nullptr ? myProbe->getRoute() : nullptr;
378 if (route == nullptr) {
379 route = MSRoute::dictionary(pars->routeid);
380 }
381 if (route == nullptr) {
382 WRITE_WARNING("No valid routes in calibrator '" + myID + "'.");
383 break;
384 }
385 if (!route->contains(myEdge)) {
386 WRITE_WARNING("Route '" + route->getID() + "' in calibrator '" + myID + "' does not contain edge '" + myEdge->getID() + "'.");
387 break;
388 }
389 const int routeIndex = (int)std::distance(route->begin(),
390 std::find(route->begin(), route->end(), myEdge));
391 MSVehicleType* vtype = MSNet::getInstance()->getVehicleControl().getVType(pars->vtypeid);
392 assert(route != 0 && vtype != 0);
393 // build the vehicle
394 SUMOVehicleParameter* newPars = new SUMOVehicleParameter(*pars);
395 newPars->id = myID + "." + toString((int)STEPS2TIME(myCurrentStateInterval->begin)) + "." + toString(myInserted);
396 newPars->depart = currentTime;
397 newPars->routeid = route->getID();
398 MSVehicle* vehicle;
399 try {
400 vehicle = dynamic_cast<MSVehicle*>(MSNet::getInstance()->getVehicleControl().buildVehicle(
401 newPars, route, vtype, true, false));
402 } catch (const ProcessError& e) {
403 if (!MSGlobals::gCheckRoutes) {
404 WRITE_WARNING(e.what());
405 vehicle = nullptr;
406 break;
407 } else {
408 throw e;
409 }
410 }
411 #ifdef MSCalibrator_DEBUG
412 std::cout << " resetting route pos: " << routeIndex << "\n";
413 #endif
414 vehicle->resetRoutePosition(routeIndex);
415 if (myEdge->insertVehicle(*vehicle, currentTime)) {
416 if (!MSNet::getInstance()->getVehicleControl().addVehicle(vehicle->getID(), vehicle)) {
417 throw ProcessError("Emission of vehicle '" + vehicle->getID() + "' in calibrator '" + getID() + "'failed!");
418 }
419 myInserted++;
420 adaptedNum++;
421 #ifdef MSCalibrator_DEBUG
422 std::cout << "I ";
423 #endif
424 } else {
425 // could not insert vehicle
426 #ifdef MSCalibrator_DEBUG
427 std::cout << "F ";
428 #endif
429 MSNet::getInstance()->getVehicleControl().deleteVehicle(vehicle, true);
430 break;
431 }
432 }
433 }
434 if (myCurrentStateInterval->end <= currentTime + myFrequency) {
435 writeXMLOutput();
436 }
437 return myFrequency;
438 }
439
440 void
reset()441 MSCalibrator::reset() {
442 myEdgeMeanData.reset();
443 for (std::vector<MSMeanData_Net::MSLaneMeanDataValues*>::iterator it = myLaneMeanData.begin(); it != myLaneMeanData.end(); ++it) {
444 (*it)->reset();
445 }
446 }
447
448
449 bool
invalidJam(int laneIndex) const450 MSCalibrator::invalidJam(int laneIndex) const {
451 if (laneIndex < 0) {
452 const int numLanes = (int)myEdge->getLanes().size();
453 for (int i = 0; i < numLanes; ++i) {
454 if (invalidJam(i)) {
455 return true;
456 }
457 }
458 return false;
459 }
460 assert(laneIndex < (int)myEdge->getLanes().size());
461 const MSLane* const lane = myEdge->getLanes()[laneIndex];
462 if (lane->getVehicleNumber() < 4) {
463 // cannot reliably detect invalid jams
464 return false;
465 }
466 // maxSpeed reflects the calibration target
467 const bool toSlow = lane->getMeanSpeed() < 0.5 * myEdge->getSpeedLimit();
468 return toSlow && remainingVehicleCapacity(laneIndex) < 1;
469 }
470
471
472 int
remainingVehicleCapacity(int laneIndex) const473 MSCalibrator::remainingVehicleCapacity(int laneIndex) const {
474 if (laneIndex < 0) {
475 const int numLanes = (int)myEdge->getLanes().size();
476 int result = 0;
477 for (int i = 0; i < numLanes; ++i) {
478 result = MAX2(result, remainingVehicleCapacity(i));
479 }
480 return result;
481 }
482 assert(laneIndex < (int)myEdge->getLanes().size());
483 MSLane* lane = myEdge->getLanes()[laneIndex];
484 MSVehicle* last = lane->getLastFullVehicle();
485 const SUMOVehicleParameter* pars = myCurrentStateInterval->vehicleParameter;
486 const MSVehicleType* vtype = MSNet::getInstance()->getVehicleControl().getVType(pars->vtypeid);
487 const double spacePerVehicle = vtype->getLengthWithGap() + myEdge->getSpeedLimit() * vtype->getCarFollowModel().getHeadwayTime();
488 if (last == nullptr) {
489 // ensure vehicles can be inserted on short edges
490 return MAX2(1, (int)(myEdge->getLength() / spacePerVehicle));
491 } else {
492 return (int)(last->getPositionOnLane() / spacePerVehicle);
493 }
494 }
495
496
497 void
cleanup()498 MSCalibrator::cleanup() {
499 for (std::vector<MSMoveReminder*>::iterator it = LeftoverReminders.begin(); it != LeftoverReminders.end(); ++it) {
500 delete *it;
501 }
502 LeftoverReminders.clear();
503 for (std::vector<SUMOVehicleParameter*>::iterator it = LeftoverVehicleParameters.begin();
504 it != LeftoverVehicleParameters.end(); ++it) {
505 delete *it;
506 }
507 LeftoverVehicleParameters.clear();
508 }
509
510
511 void
updateMeanData()512 MSCalibrator::updateMeanData() {
513 myEdgeMeanData.reset();
514 for (std::vector<MSMeanData_Net::MSLaneMeanDataValues*>::iterator it = myLaneMeanData.begin();
515 it != myLaneMeanData.end(); ++it) {
516 (*it)->addTo(myEdgeMeanData);
517 }
518 }
519
notifyEnter(SUMOTrafficObject & veh,Notification,const MSLane *)520 bool MSCalibrator::VehicleRemover::notifyEnter(SUMOTrafficObject& veh, Notification /* reason */, const MSLane* /* enteredLane */) {
521 if (myParent == nullptr) {
522 return false;
523 }
524 if (myParent->isActive()) {
525 myParent->updateMeanData();
526 const bool calibrateFlow = myParent->myCurrentStateInterval->q >= 0;
527 const int totalWishedNum = myParent->totalWished();
528 int adaptedNum = myParent->passed() + myParent->myClearedInJam;
529 MSVehicle* vehicle = dynamic_cast<MSVehicle*>(&veh);
530 if (calibrateFlow && adaptedNum > totalWishedNum) {
531 #ifdef MSCalibrator_DEBUG
532 std::cout << time2string(MSNet::getInstance()->getCurrentTimeStep()) << " " << myParent->getID()
533 << " vaporizing " << vehicle->getID() << " to reduce flow\n";
534 #endif
535 if (myParent->scheduleRemoval(vehicle)) {
536 myParent->myRemoved++;
537 }
538 } else if (myParent->invalidJam(myLaneIndex)) {
539 #ifdef MSCalibrator_DEBUG
540 std::cout << time2string(MSNet::getInstance()->getCurrentTimeStep()) << " " << myParent->getID()
541 << " vaporizing " << vehicle->getID() << " to clear jam\n";
542 #endif
543 if (!myParent->myHaveWarnedAboutClearingJam) {
544 WRITE_WARNING("Clearing jam at calibrator '" + myParent->myID + "' at time "
545 + time2string(MSNet::getInstance()->getCurrentTimeStep()));
546 myParent->myHaveWarnedAboutClearingJam = true;
547 }
548 if (myParent->scheduleRemoval(vehicle)) {
549 myParent->myClearedInJam++;
550 }
551 }
552 }
553 return true;
554 }
555
556
557
558 /****************************************************************************/
559
560