1 /****************************************************************************/
2 // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
3 // Copyright (C) 2001-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 METriggeredCalibrator.cpp
11 /// @author Daniel Krajzewicz
12 /// @date Tue, May 2005
13 /// @version $Id$
14 ///
15 // Calibrates the flow on a segment to a specified one
16 /****************************************************************************/
17
18
19 // ===========================================================================
20 // included modules
21 // ===========================================================================
22 #include <config.h>
23
24 #include <string>
25 #include <algorithm>
26 #include <cmath>
27 #include <microsim/MSGlobals.h>
28 #include <microsim/MSNet.h>
29 #include <microsim/MSEdge.h>
30 #include <microsim/MSEventControl.h>
31 #include <microsim/MSVehicleControl.h>
32 #include <microsim/output/MSRouteProbe.h>
33 #include <utils/xml/SUMOXMLDefinitions.h>
34 #include <utils/common/ToString.h>
35 #include <utils/common/UtilExceptions.h>
36 #include <utils/common/StringTokenizer.h>
37 #include <utils/xml/XMLSubSys.h>
38 #include <utils/common/StringUtils.h>
39 #include <utils/options/OptionsCont.h>
40 #include <utils/vehicle/SUMOVehicleParserHelper.h>
41 #include <utils/distribution/RandomDistributor.h>
42 #include <utils/vehicle/SUMOVehicleParameter.h>
43 #include "MELoop.h"
44 #include "MESegment.h"
45 #include "MEVehicle.h"
46 #include "METriggeredCalibrator.h"
47
48
49 // ===========================================================================
50 // method definitions
51 // ===========================================================================
METriggeredCalibrator(const std::string & id,const MSEdge * const edge,const double pos,const std::string & aXMLFilename,const std::string & outputFilename,const SUMOTime freq,const double length,const MSRouteProbe * probe)52 METriggeredCalibrator::METriggeredCalibrator(const std::string& id,
53 const MSEdge* const edge, const double pos,
54 const std::string& aXMLFilename,
55 const std::string& outputFilename,
56 const SUMOTime freq, const double length,
57 const MSRouteProbe* probe) :
58 MSCalibrator(id, edge, (MSLane*)nullptr, pos, aXMLFilename, outputFilename, freq, length, probe, false),
59 mySegment(MSGlobals::gMesoNet->getSegmentForEdge(*edge, pos)) {
60 myEdgeMeanData.setDescription("meandata_calibrator_" + getID());
61 mySegment->addDetector(&myEdgeMeanData);
62 }
63
64
~METriggeredCalibrator()65 METriggeredCalibrator::~METriggeredCalibrator() {
66 if (myCurrentStateInterval != myIntervals.end()) {
67 // need to do it here and not in MSCalibrator because otherwise meandata is gone
68 writeXMLOutput();
69 // but avoid to call it again in MSCalibrator
70 myCurrentStateInterval = myIntervals.end();
71 }
72 mySegment->removeDetector(&myEdgeMeanData);
73 }
74
75
76 bool
tryEmit(MESegment * s,MEVehicle * vehicle)77 METriggeredCalibrator::tryEmit(MESegment* s, MEVehicle* vehicle) {
78 if (s->initialise(vehicle, vehicle->getParameter().depart)) {
79 if (!MSNet::getInstance()->getVehicleControl().addVehicle(vehicle->getID(), vehicle)) {
80 throw ProcessError("Emission of vehicle '" + vehicle->getID() + "' in calibrator '" + getID() + "'failed!");
81 }
82 return true;
83 }
84 return false;
85 }
86
87
88 SUMOTime
execute(SUMOTime currentTime)89 METriggeredCalibrator::execute(SUMOTime currentTime) {
90 // get current simulation values (valid for the last simulation second)
91 // XXX could we miss vehicle movements if this is called less often than every DELTA_T (default) ?
92 mySegment->prepareDetectorForWriting(myEdgeMeanData);
93
94 // check whether an adaptation value exists
95 if (isCurrentStateActive(currentTime)) {
96 // all happens in isCurrentStateActive()
97 } else {
98 myEdgeMeanData.reset(); // discard collected values
99 if (!mySpeedIsDefault) {
100 // if not, reset adaptation values
101 mySegment->getEdge().setMaxSpeed(myDefaultSpeed);
102 MESegment* first = MSGlobals::gMesoNet->getSegmentForEdge(mySegment->getEdge());
103 const double jamThresh = OptionsCont::getOptions().getFloat("meso-jam-threshold");
104 while (first != nullptr) {
105 first->setSpeed(myDefaultSpeed, currentTime, jamThresh);
106 first = first->getNextSegment();
107 }
108 mySpeedIsDefault = true;
109 }
110 if (myCurrentStateInterval == myIntervals.end()) {
111 // keep calibrator alive but do not call again
112 return TIME2STEPS(86400);
113 }
114 return myFrequency;
115 }
116 // we are active
117 if (!myDidSpeedAdaption && myCurrentStateInterval->v >= 0 && myCurrentStateInterval->v != mySegment->getEdge().getSpeedLimit()) {
118 mySegment->getEdge().setMaxSpeed(myCurrentStateInterval->v);
119 MESegment* first = MSGlobals::gMesoNet->getSegmentForEdge(mySegment->getEdge());
120 while (first != nullptr) {
121 first->setSpeed(myCurrentStateInterval->v, currentTime, -1);
122 first = first->getNextSegment();
123 }
124 mySpeedIsDefault = false;
125 myDidSpeedAdaption = true;
126 }
127 // clear invalid jams
128 bool hadInvalidJam = false;
129 while (invalidJam()) {
130 hadInvalidJam = true;
131 if (!myHaveWarnedAboutClearingJam) {
132 WRITE_WARNING("Clearing jam at calibrator '" + myID + "' at time " + time2string(currentTime));
133 }
134 // remove one vehicle currently on the segment
135 if (mySegment->vaporizeAnyCar(currentTime)) {
136 myClearedInJam++;
137 } else {
138 if (!myHaveWarnedAboutClearingJam) {
139 // this frequenly happens for very short edges
140 WRITE_WARNING("Could not clear jam at calibrator '" + myID + "' at time " + time2string(currentTime));
141 }
142 break;
143 }
144 myHaveWarnedAboutClearingJam = true;
145 }
146 if (myCurrentStateInterval->q >= 0) {
147 // flow calibration starts here ...
148 // compute the number of vehicles that should have passed the calibrator within the time
149 // rom begin of the interval
150 const double totalHourFraction = STEPS2TIME(myCurrentStateInterval->end - myCurrentStateInterval->begin) / (double) 3600.;
151 const int totalWishedNum = (int)std::floor(myCurrentStateInterval->q * totalHourFraction + 0.5); // round to closest int
152 int adaptedNum = passed() + myClearedInJam;
153 if (!hadInvalidJam) {
154 // only add vehicles if we do not have an invalid upstream jam to prevent spill-back
155 const double hourFraction = STEPS2TIME(currentTime - myCurrentStateInterval->begin + DELTA_T) / (double) 3600.;
156 const int wishedNum = (int)std::floor(myCurrentStateInterval->q * hourFraction + 0.5); // round to closest int
157 // only the difference between inflow and aspiredFlow should be added, thus
158 // we should not count vehicles vaporized from a jam here
159 // if we have enough time left we can add missing vehicles later
160 const int relaxedInsertion = (int)std::floor(STEPS2TIME(myCurrentStateInterval->end - currentTime) / 3);
161 const int insertionSlack = MAX2(0, adaptedNum + relaxedInsertion - totalWishedNum);
162 // increase number of vehicles
163 //std::cout << "time:" << STEPS2TIME(currentTime) << " w:" << wishedNum << " s:" << insertionSlack << " before:" << adaptedNum;
164 while (wishedNum > adaptedNum + insertionSlack && remainingVehicleCapacity() > maximumInflow()) {
165 SUMOVehicleParameter* pars = myCurrentStateInterval->vehicleParameter;
166 const MSRoute* route = myProbe != nullptr ? myProbe->getRoute() : nullptr;
167 if (route == nullptr) {
168 route = MSRoute::dictionary(pars->routeid);
169 }
170 if (route == nullptr) {
171 WRITE_WARNING("No valid routes in calibrator '" + myID + "'.");
172 break;
173 }
174 if (!route->contains(myEdge)) {
175 WRITE_WARNING("Route '" + route->getID() + "' in calibrator '" + myID + "' does not contain edge '" + myEdge->getID() + "'.");
176 break;
177 }
178 MSVehicleType* vtype = MSNet::getInstance()->getVehicleControl().getVType(pars->vtypeid);
179 assert(route != 0 && vtype != 0);
180 // build the vehicle
181 const SUMOTime depart = mySegment->getNextInsertionTime(currentTime);
182 SUMOVehicleParameter* newPars = new SUMOVehicleParameter(*pars);
183 newPars->id = myID + "." + toString(depart) + "." + toString(myInserted);
184 newPars->depart = depart;
185 newPars->routeid = route->getID();
186 MEVehicle* vehicle;
187 try {
188 vehicle = static_cast<MEVehicle*>(MSNet::getInstance()->getVehicleControl().buildVehicle(
189 newPars, route, vtype, false, false));
190 } catch (const ProcessError& e) {
191 if (!MSGlobals::gCheckRoutes) {
192 WRITE_WARNING(e.what());
193 vehicle = nullptr;
194 break;
195 } else {
196 throw e;
197 }
198 }
199 vehicle->setSegment(mySegment); // needed or vehicle will not be registered (XXX why?)
200 vehicle->setEventTime(currentTime); // XXX superfluous?
201 // move vehicle forward when the route does not begin at the calibrator's edge
202 const MSEdge* myedge = &mySegment->getEdge();
203 bool atDest = false;
204 while (vehicle->getEdge() != myedge) {
205 // let the vehicle move to the next edge
206 atDest = vehicle->moveRoutePointer();
207 }
208 // insert vehicle into the net
209 if (atDest || !tryEmit(mySegment, vehicle)) {
210 //std::cout << "F ";
211 MSNet::getInstance()->getVehicleControl().deleteVehicle(vehicle, true);
212 break;
213 }
214 //std::cout << "I ";
215 myInserted++;
216 adaptedNum++;
217 }
218 }
219 //std::cout << " after:" << adaptedNum << "\n";
220 // we only remove vehicles once we really have to
221 while (totalWishedNum < adaptedNum) {
222 if (!mySegment->vaporizeAnyCar(currentTime)) {
223 // @bug: short edges may be jumped in a single step, giving us no chance to remove a vehicle
224 break;
225 }
226 myRemoved++;
227 adaptedNum--;
228 }
229 }
230 if (myCurrentStateInterval->end <= currentTime + myFrequency) {
231 writeXMLOutput();
232 }
233 assert(!invalidJam());
234 return myFrequency;
235 }
236
237
238 bool
invalidJam() const239 METriggeredCalibrator::invalidJam() const {
240 if (mySegment->getBruttoOccupancy() == 0.) {
241 return false;
242 }
243 // maxSpeed reflects the calibration target
244 const bool toSlow = mySegment->getMeanSpeed() < 0.8 * mySegment->getEdge().getSpeedLimit();
245 return toSlow && remainingVehicleCapacity() < maximumInflow();
246 }
247
248
249 int
remainingVehicleCapacity() const250 METriggeredCalibrator::remainingVehicleCapacity() const {
251 const SUMOVehicleParameter* pars = myCurrentStateInterval->vehicleParameter;
252 const MSVehicleType* vtype = MSNet::getInstance()->getVehicleControl().getVType(pars->vtypeid);
253 return mySegment->remainingVehicleCapacity(vtype->getLengthWithGap());
254 }
255
256
257 void
reset()258 METriggeredCalibrator::reset() {
259 myEdgeMeanData.reset();
260 }
261
262
263 /****************************************************************************/
264
265