1 /****************************************************************************/
2 // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
3 // Copyright (C) 2012-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    AStarLookupTable.h
11 /// @author  Jakob Erdmann
12 /// @date    July 2017
13 /// @version $Id$
14 ///
15 // Precomputed landmark distances to speed up the A* routing algorithm
16 /****************************************************************************/
17 #ifndef AStarLookupTable_h
18 #define AStarLookupTable_h
19 
20 
21 // ===========================================================================
22 // included modules
23 // ===========================================================================
24 #include <config.h>
25 
26 #include <iostream>
27 #include <fstream>
28 
29 #ifdef HAVE_FOX
30 #include <utils/foxtools/FXWorkerThread.h>
31 #endif
32 
33 #define UNREACHABLE (std::numeric_limits<double>::max() / 1000.0)
34 
35 //#define ASTAR_DEBUG_LOOKUPTABLE
36 //#define ASTAR_DEBUG_LOOKUPTABLE_FROM "disabled"
37 //#define ASTAR_DEBUG_UNREACHABLE
38 
39 // ===========================================================================
40 // class definitions
41 // ===========================================================================
42 /**
43  * @class LandmarkLookupTable
44  * @brief Computes the shortest path through a network using the A* algorithm.
45  *
46  * The template parameters are:
47  * @param E The edge class to use (MSEdge/ROEdge)
48  * @param V The vehicle class to use (MSVehicle/ROVehicle)
49  * @param PF The prohibition function to use (prohibited_withPermissions/noProhibitions)
50  * @param EC The class to retrieve the effort for an edge from
51  *
52  * The router is edge-based. It must know the number of edges for internal reasons
53  *  and whether a missing connection between two given edges (unbuild route) shall
54  *  be reported as an error or as a warning.
55  *
56  */
57 
58 template<class E, class V>
59 class AbstractLookupTable {
60 public:
61     /// @brief provide a lower bound on the distance between from and to (excluding traveltime of both edges)
62     virtual double lowerBound(const E* from, const E* to, double speed, double speedFactor, double fromEffort, double toEffort) const = 0;
63 
64     /// @brief whether the heuristic ist consistent (found nodes are always visited on the shortest path the first time)
65     virtual bool consistent() const = 0;
66 };
67 
68 
69 template<class E, class V>
70 class FullLookupTable : public AbstractLookupTable<E, V> {
71 public:
FullLookupTable(const std::string & filename,const int size)72     FullLookupTable(const std::string& filename, const int size) :
73         myTable(size) {
74         BinaryInputDevice dev(filename);
75         for (int i = 0; i < size; i++) {
76             for (int j = 0; j < size; j++) {
77                 double val;
78                 dev >> val;
79                 myTable[i].push_back(val);
80             }
81         }
82     }
83 
~FullLookupTable()84     virtual ~FullLookupTable() {
85     }
86 
lowerBound(const E * from,const E * to,double,double speedFactor,double,double)87     double lowerBound(const E* from, const E* to, double /*speed*/, double speedFactor, double /*fromEffort*/, double /*toEffort*/) const {
88         return myTable[from->getNumericalID()][to->getNumericalID()] / speedFactor;
89     }
90 
consistent()91     bool consistent() const {
92         return true;
93     }
94 
95 private:
96     std::vector<std::vector<double> > myTable;
97 };
98 
99 
100 template<class E, class V>
101 class LandmarkLookupTable : public AbstractLookupTable<E, V> {
102 public:
LandmarkLookupTable(const std::string & filename,const std::vector<E * > & edges,SUMOAbstractRouter<E,V> * router,const V * defaultVehicle,const std::string & outfile,const int maxNumThreads)103     LandmarkLookupTable(const std::string& filename, const std::vector<E*>& edges, SUMOAbstractRouter<E, V>* router, const V* defaultVehicle, const std::string& outfile, const int maxNumThreads) {
104         myFirstNonInternal = -1;
105         std::map<std::string, int> numericID;
106         for (E* e : edges) {
107             if (!e->isInternal()) {
108                 if (myFirstNonInternal == -1) {
109                     myFirstNonInternal = e->getNumericalID();
110                 }
111                 numericID[e->getID()] = e->getNumericalID() - myFirstNonInternal;
112             }
113         }
114         std::ifstream strm(filename.c_str());
115         if (!strm.good()) {
116             throw ProcessError("Could not load landmark-lookup-table from '" + filename + "'.");
117         }
118         std::ofstream* ostrm = nullptr;
119         if (!outfile.empty()) {
120             ostrm = new std::ofstream(outfile.c_str());
121             if (!ostrm->good()) {
122                 throw ProcessError("Could not open file '" + outfile + "' for writing.");
123             }
124         }
125         std::string line;
126         int numLandMarks = 0;
127         while (std::getline(strm, line)) {
128             if (line == "") {
129                 break;
130             }
131             //std::cout << "'" << line << "'" << "\n";
132             StringTokenizer st(line);
133             if (st.size() == 1) {
134                 const std::string lm = st.get(0);
135                 myLandmarks[lm] = numLandMarks++;
136                 myFromLandmarkDists.push_back(std::vector<double>(0));
137                 myToLandmarkDists.push_back(std::vector<double>(0));
138                 if (ostrm != nullptr) {
139                     (*ostrm) << lm << "\n";
140                 }
141             } else {
142                 assert(st.size() == 4);
143                 const std::string lm = st.get(0);
144                 const std::string edge = st.get(1);
145                 if (numericID[edge] != (int)myFromLandmarkDists[myLandmarks[lm]].size()) {
146                     WRITE_WARNING("Unknown or unordered edge '" + edge + "' in landmark file.");
147                 }
148                 const double distFrom = StringUtils::toDouble(st.get(2));
149                 const double distTo = StringUtils::toDouble(st.get(3));
150                 myFromLandmarkDists[myLandmarks[lm]].push_back(distFrom);
151                 myToLandmarkDists[myLandmarks[lm]].push_back(distTo);
152             }
153         }
154         if (myLandmarks.empty()) {
155             WRITE_WARNING("No landmarks in '" + filename + "', falling back to standard A*.");
156             delete ostrm;
157             return;
158         }
159 #ifdef HAVE_FOX
160         FXWorkerThread::Pool threadPool;
161 #endif
162         for (int i = 0; i < (int)myLandmarks.size(); ++i) {
163             if ((int)myFromLandmarkDists[i].size() != (int)edges.size() - myFirstNonInternal) {
164                 const std::string landmarkID = getLandmark(i);
165                 const E* landmark = nullptr;
166                 // retrieve landmark edge
167                 for (const E* const edge : edges) {
168                     if (edge->getID() == landmarkID) {
169                         landmark = edge;
170                         break;
171                     }
172                 }
173                 if (landmark == nullptr) {
174                     WRITE_WARNING("Landmark '" + landmarkID + "' does not exist in the network.");
175                     continue;
176                 }
177                 if (router != nullptr) {
178                     const std::string missing = outfile.empty() ? filename + ".missing" : outfile;
179                     WRITE_WARNING("Not all network edges were found in the lookup table '" + filename + "' for landmark '" + landmarkID + "'. Saving missing values to '" + missing + "'.");
180                     if (ostrm == nullptr) {
181                         ostrm = new std::ofstream(missing.c_str());
182                         if (!ostrm->good()) {
183                             throw ProcessError("Could not open file '" + missing + "' for writing.");
184                         }
185                     }
186                 } else {
187                     throw ProcessError("Not all network edges were found in the lookup table '" + filename + "' for landmark '" + landmarkID + "'.");
188                 }
189                 std::vector<const E*> routeLM(1, landmark);
190                 const double lmCost = router->recomputeCosts(routeLM, defaultVehicle, 0);
191                 std::vector<const E*> route;
192 #ifdef HAVE_FOX
193                 if (maxNumThreads > 0) {
194                     if (threadPool.size() == 0) {
195                         // The CHRouter needs initialization
196                         // before it gets cloned, so we do a dummy routing which is not in parallel
197                         router->compute(landmark, landmark, defaultVehicle, 0, route);
198                         route.clear();
199                         while ((int)threadPool.size() < maxNumThreads) {
200                             new WorkerThread(threadPool, router->clone(), defaultVehicle);
201                         }
202                     }
203                     std::vector<RoutingTask*> currentTasks;
204                     for (int j = (int)myFromLandmarkDists[i].size() + myFirstNonInternal; j < (int)edges.size(); ++j) {
205                         const E* edge = edges[j];
206                         if (landmark != edge) {
207                             std::vector<const E*> routeE(1, edge);
208                             const double sourceDestCost = lmCost + router->recomputeCosts(routeE, defaultVehicle, 0);
209                             // compute from-distance (skip taz-sources and other unreachable edges)
210                             if (edge->getPredecessors().size() > 0 && landmark->getSuccessors().size() > 0) {
211                                 currentTasks.push_back(new RoutingTask(landmark, edge, sourceDestCost));
212                                 threadPool.add(currentTasks.back());
213                             }
214                             // compute to-distance (skip unreachable landmarks)
215                             if (landmark->getPredecessors().size() > 0 && edge->getSuccessors().size() > 0) {
216                                 currentTasks.push_back(new RoutingTask(edge, landmark, sourceDestCost));
217                                 threadPool.add(currentTasks.back());
218                             }
219                         }
220                     }
221                     threadPool.waitAll(false);
222                     int taskIndex = 0;
223                     for (int j = (int)myFromLandmarkDists[i].size() + myFirstNonInternal; j < (int)edges.size(); ++j) {
224                         const E* edge = edges[j];
225                         double distFrom = -1;
226                         double distTo = -1;
227                         if (landmark == edge) {
228                             distFrom = 0;
229                             distTo = 0;
230                         } else {
231                             if (edge->getPredecessors().size() > 0 && landmark->getSuccessors().size() > 0) {
232                                 distFrom = currentTasks[taskIndex]->getCost();
233                                 delete currentTasks[taskIndex++];
234                             }
235                             if (landmark->getPredecessors().size() > 0 && edge->getSuccessors().size() > 0) {
236                                 distTo = currentTasks[taskIndex]->getCost();
237                                 delete currentTasks[taskIndex++];
238                             }
239                         }
240                         myFromLandmarkDists[i].push_back(distFrom);
241                         myToLandmarkDists[i].push_back(distTo);
242                         (*ostrm) << landmarkID << " " << edge->getID() << " " << distFrom << " " << distTo << "\n";
243                     }
244                     currentTasks.clear();
245                     continue;
246                 }
247 #else
248                 UNUSED_PARAMETER(maxNumThreads);
249 #endif
250                 for (int j = (int)myFromLandmarkDists[i].size() + myFirstNonInternal; j < (int)edges.size(); ++j) {
251                     const E* edge = edges[j];
252                     double distFrom = -1.;
253                     double distTo = -1.;
254                     if (landmark == edge) {
255                         distFrom = 0.;
256                         distTo = 0.;
257                     } else {
258                         std::vector<const E*> routeE(1, edge);
259                         const double sourceDestCost = lmCost + router->recomputeCosts(routeE, defaultVehicle, 0);
260                         // compute from-distance (skip taz-sources and other unreachable edges)
261                         if (edge->getPredecessors().size() > 0 && landmark->getSuccessors().size() > 0) {
262                             if (router->compute(landmark, edge, defaultVehicle, 0, route)) {
263                                 distFrom = MAX2(0.0, router->recomputeCosts(route, defaultVehicle, 0) - sourceDestCost);
264                                 route.clear();
265                             }
266                         }
267                         // compute to-distance (skip unreachable landmarks)
268                         if (landmark->getPredecessors().size() > 0 && edge->getSuccessors().size() > 0) {
269                             if (router->compute(edge, landmark, defaultVehicle, 0, route)) {
270                                 distTo = MAX2(0.0, router->recomputeCosts(route, defaultVehicle, 0) - sourceDestCost);
271                                 route.clear();
272                             }
273                         }
274                     }
275                     myFromLandmarkDists[i].push_back(distFrom);
276                     myToLandmarkDists[i].push_back(distTo);
277                     (*ostrm) << landmarkID << " " << edge->getID() << " " << distFrom << " " << distTo << "\n";
278                 }
279             }
280         }
281         delete ostrm;
282     }
283 
~LandmarkLookupTable()284     virtual ~LandmarkLookupTable() {
285     }
286 
lowerBound(const E * from,const E * to,double speed,double speedFactor,double fromEffort,double toEffort)287     double lowerBound(const E* from, const E* to, double speed, double speedFactor, double fromEffort, double toEffort) const {
288         double result = from->getDistanceTo(to) / speed;
289 #ifdef ASTAR_DEBUG_LOOKUPTABLE
290         if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM) {
291             std::cout << " lowerBound to=" << to->getID() << " result1=" << result << "\n";
292         }
293 #endif
294         for (int i = 0; i < (int)myLandmarks.size(); ++i) {
295             // a cost of -1 is used to encode unreachability.
296             const double fl = myToLandmarkDists[i][from->getNumericalID() - myFirstNonInternal];
297             const double tl = myToLandmarkDists[i][to->getNumericalID() - myFirstNonInternal];
298             if (fl >= 0 && tl >= 0) {
299                 const double bound = (fl - tl - toEffort) / speedFactor;
300 #ifdef ASTAR_DEBUG_LOOKUPTABLE
301                 if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM && result < bound) {
302                     std::cout << "   landmarkTo=" << getLandmark(i) << " result2=" << bound
303                               << " fl=" << fl << " tl=" << tl << "\n";
304                 }
305 #endif
306                 result = MAX2(result, bound);
307             }
308             const double lt = myFromLandmarkDists[i][to->getNumericalID() - myFirstNonInternal];
309             const double lf = myFromLandmarkDists[i][from->getNumericalID() - myFirstNonInternal];
310             if (lt >= 0 && lf >= 0) {
311                 const double bound = (lt - lf - fromEffort) / speedFactor;
312 #ifdef ASTAR_DEBUG_LOOKUPTABLE
313                 if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM && result < bound) {
314                     std::cout << "   landmarkFrom=" << getLandmark(i) << " result3=" << bound
315                               << " lt=" << lt << " lf=" << lf << "\n";
316                 }
317 #endif
318                 result = MAX2(result, bound);
319             }
320             if ((tl >= 0 && fl < 0)
321                     || (lf >= 0 && lt < 0)) {
322                 // target unreachable.
323 #ifdef ASTAR_DEBUG_UNREACHABLE
324                 std::cout << "   unreachable: from=" << from->getID() << " to=" << to->getID() << " landmark=" << getLandmark(i) << " "
325                           << ((tl >= 0 && fl < 0) ? " (toLandmark)" : " (fromLandmark)")
326                           << " fl=" << fl << " tl=" << tl << " lt=" << lt << " lf=" << lf
327                           << "\n";
328 #endif
329                 return UNREACHABLE;
330             }
331         }
332         return result;
333     }
334 
consistent()335     bool consistent() const {
336         return false;
337     }
338 
339 private:
340     std::map<std::string, int> myLandmarks;
341     std::vector<std::vector<double> > myFromLandmarkDists;
342     std::vector<std::vector<double> > myToLandmarkDists;
343     int myFirstNonInternal;
344 
345 #ifdef HAVE_FOX
346 private:
347     class WorkerThread : public FXWorkerThread {
348     public:
WorkerThread(FXWorkerThread::Pool & pool,SUMOAbstractRouter<E,V> * router,const V * vehicle)349         WorkerThread(FXWorkerThread::Pool& pool,
350                      SUMOAbstractRouter<E, V>* router, const V* vehicle)
351             : FXWorkerThread(pool), myRouter(router), myVehicle(vehicle) {}
~WorkerThread()352         virtual ~WorkerThread() {
353             delete myRouter;
354         }
compute(const E * src,const E * dest,const double costOff)355         double compute(const E* src, const E* dest, const double costOff) {
356             double result = -1.;
357             if (myRouter->compute(src, dest, myVehicle, 0, myRoute)) {
358                 result = MAX2(0.0, myRouter->recomputeCosts(myRoute, myVehicle, 0) + costOff);
359                 myRoute.clear();
360             }
361             return result;
362         }
363     private:
364         SUMOAbstractRouter<E, V>* myRouter;
365         const V* myVehicle;
366         std::vector<const E*> myRoute;
367     };
368 
369     class RoutingTask : public FXWorkerThread::Task {
370     public:
RoutingTask(const E * src,const E * dest,const double costOff)371         RoutingTask(const E* src, const E* dest, const double costOff)
372             : mySrc(src), myDest(dest), myCost(-costOff) {}
run(FXWorkerThread * context)373         void run(FXWorkerThread* context) {
374             myCost = ((WorkerThread*)context)->compute(mySrc, myDest, myCost);
375         }
getCost()376         double getCost() {
377             return myCost;
378         }
379     private:
380         const E* const mySrc;
381         const E* const myDest;
382         double myCost;
383     private:
384         /// @brief Invalidated assignment operator.
385         RoutingTask& operator=(const RoutingTask&);
386     };
387 
388 
389 private:
390     /// @brief for multi threaded routing
391 #endif
392 
getLandmark(int i)393     std::string getLandmark(int i) const {
394         for (std::map<std::string, int>::const_iterator it = myLandmarks.begin(); it != myLandmarks.end(); ++it) {
395             if (it->second == i) {
396                 return it->first;
397             }
398         }
399         return "";
400     }
401 };
402 
403 
404 
405 
406 #endif
407 
408 /****************************************************************************/
409 
410