Eclipse SUMO - Simulation of Urban MObility
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
AStarLookupTable.h
Go to the documentation of this file.
1/****************************************************************************/
2// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3// Copyright (C) 2012-2025 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/****************************************************************************/
18// Precomputed landmark distances to speed up the A* routing algorithm
19/****************************************************************************/
20#pragma once
21#include <config.h>
22
23#include <iostream>
24#include <fstream>
25#ifdef HAVE_ZLIB
26#include <foreign/zstr/zstr.hpp>
27#endif
28#ifdef HAVE_FOX
30#endif
35
36#define UNREACHABLE (std::numeric_limits<double>::max() / 1000.0)
37
38//#define ASTAR_DEBUG_LOOKUPTABLE
39//#define ASTAR_DEBUG_LOOKUPTABLE_FROM "disabled"
40//#define ASTAR_DEBUG_UNREACHABLE
41
42// ===========================================================================
43// class definitions
44// ===========================================================================
61template<class E, class V>
63public:
65
67 virtual double lowerBound(const E* from, const E* to, double speed, double speedFactor, double fromEffort, double toEffort) const = 0;
68
70 virtual bool consistent() const = 0;
71};
72
73
74template<class E, class V>
76public:
77 FullLookupTable(const std::string& filename, const int size) :
78 myTable(size) {
79 std::ifstream strm(filename.c_str());
80 for (int i = 0; i < size; i++) {
81 for (int j = 0; j < size; j++) {
82 double val;
83 strm >> val;
84 myTable[i].push_back(val);
85 }
86 }
87 }
88
89 virtual ~FullLookupTable() {
90 }
91
92 double lowerBound(const E* from, const E* to, double /*speed*/, double speedFactor, double /*fromEffort*/, double /*toEffort*/) const {
93 return myTable[from->getNumericalID()][to->getNumericalID()] / speedFactor;
94 }
95
96 bool consistent() const {
97 return true;
98 }
99
100private:
101 std::vector<std::vector<double> > myTable;
102};
103
104
105template<class E, class V, class M>
107public:
108 LandmarkLookupTable(const std::string& filename, const std::vector<E*>& edges, SUMOAbstractRouter<E, V>* router,
109 SUMOAbstractRouter<ReversedEdge<E, V>, V>* reverseRouter,
110 const V* defaultVehicle, const std::string& outfile, const int maxNumThreads, M* mapMatcher) {
112 std::map<std::string, int> numericID;
113 bool haveTaz = false;
114 for (E* e : edges) {
115 if (!e->isInternal()) {
116 if (myFirstNonInternal == -1) {
117 myFirstNonInternal = e->getNumericalID();
118 }
119 if (e->isTazConnector()) {
120 haveTaz = true;
121 }
122 numericID[e->getID()] = e->getNumericalID() - myFirstNonInternal;
123 }
124 }
125#ifdef HAVE_ZLIB
126 zstr::ifstream strm(filename.c_str(), std::fstream::in | std::fstream::binary);
127#else
128 std::ifstream strm(filename.c_str());
129#endif
130 if (!strm.good()) {
131 throw ProcessError(TLF("Could not load landmark-lookup-table from '%'.", filename));
132 }
133 std::string line;
134 std::vector<const E*> landmarks;
135 int numLandMarks = 0;
136 bool haveData = false;
137 while (std::getline(strm, line)) {
138 if (line == "") {
139 break;
140 }
141 StringTokenizer st(line);
142 if (st.size() == 1) {
143 const std::string lm = st.get(0);
144 if (myLandmarks.count(lm) != 0) {
145 throw ProcessError(TLF("Duplicate edge '%' in landmark file.", lm));
146 }
147 // retrieve landmark edge
148 const auto& it = numericID.find(lm);
149 if (it == numericID.end()) {
150 throw ProcessError(TLF("Landmark edge '%' does not exist in the network.", lm));
151 }
152 myLandmarks[lm] = numLandMarks++;
153 myFromLandmarkDists.push_back(std::vector<double>(0));
154 myToLandmarkDists.push_back(std::vector<double>(0));
155 landmarks.push_back(edges[it->second + myFirstNonInternal]);
156 } else if (st.size() == 2) {
157 // geo landmark
158 try {
159 std::string lonStr = st.get(0);
160 if (!lonStr.empty() && lonStr.back() == ',') {
161 // remove trailing comma
162 lonStr = lonStr.substr(0, lonStr.size() - 1);
163 }
164 double lon = StringUtils::toDouble(lonStr);
165 double lat = StringUtils::toDouble(st.get(1));
166 std::vector<const E*> mapped;
167 bool ok;
168 mapMatcher->parseGeoEdges(PositionVector({Position(lon, lat)}), true, SVC_IGNORING, mapped, "LMLT", false, ok, true);
169 if (mapped.size() != 1) {
170 throw ProcessError(TLF("Invalid coordinate in landmark file, could not find edge at '%'", line));
171 }
172 std::string lm = mapped.front()->getID();
173 const auto& it = numericID.find(lm);
174 if (it == numericID.end()) {
175 throw ProcessError(TLF("Landmark edge '%' does not exist in the network.", lm));
176 }
177 myLandmarks[lm] = numLandMarks++;
178 myFromLandmarkDists.push_back(std::vector<double>(0));
179 myToLandmarkDists.push_back(std::vector<double>(0));
180 landmarks.push_back(edges[it->second + myFirstNonInternal]);
181 } catch (const NumberFormatException&) {
182 throw ProcessError(TLF("Broken landmark file, could not parse '%' as coordinates.", line));
183 }
184 } else if (st.size() == 4) {
185 // legacy style landmark table
186 const std::string lm = st.get(0);
187 const std::string edge = st.get(1);
188 if (numericID[edge] != (int)myFromLandmarkDists[myLandmarks[lm]].size()) {
189 throw ProcessError(TLF("Unknown or unordered edge '%' in landmark file.", edge));
190 }
191 const double distFrom = StringUtils::toDouble(st.get(2));
192 const double distTo = StringUtils::toDouble(st.get(3));
193 myFromLandmarkDists[myLandmarks[lm]].push_back(distFrom);
194 myToLandmarkDists[myLandmarks[lm]].push_back(distTo);
195 haveData = true;
196 } else {
197 const std::string edge = st.get(0);
198 if ((int)st.size() != 2 * numLandMarks + 1) {
199 throw ProcessError(TLF("Broken landmark file, unexpected number of entries (%) for edge '%'.", st.size() - 1, edge));
200 }
201 if (numericID[edge] != (int)myFromLandmarkDists[0].size()) {
202 throw ProcessError(TLF("Unknown or unordered edge '%' in landmark file.", edge));
203 }
204 for (int i = 0; i < numLandMarks; i++) {
205 const double distFrom = StringUtils::toDouble(st.get(2 * i + 1));
206 const double distTo = StringUtils::toDouble(st.get(2 * i + 2));
207 myFromLandmarkDists[i].push_back(distFrom);
208 myToLandmarkDists[i].push_back(distTo);
209 }
210 haveData = true;
211 }
212 }
213 if (myLandmarks.empty()) {
214 WRITE_WARNINGF("No landmarks in '%', falling back to standard A*.", filename);
215 return;
216 }
217#ifdef HAVE_FOX
218 MFXWorkerThread::Pool threadPool;
219 std::vector<RoutingTask*> currentTasks;
220#endif
221 if (!haveData) {
222 WRITE_MESSAGE(TL("Calculating new lookup table."));
223 }
224 for (int i = 0; i < numLandMarks; ++i) {
225 if ((int)myFromLandmarkDists[i].size() != (int)edges.size() - myFirstNonInternal) {
226 const E* const landmark = landmarks[i];
227 if (haveData) {
228 if (myFromLandmarkDists[i].empty()) {
229 WRITE_WARNINGF(TL("No lookup table for landmark edge '%', recalculating."), landmark->getID());
230 } else {
231 const std::string tazWarning = haveTaz ? " Make sure that any used taz or junction-taz definitions are loaded when generating the table" : "";
232 throw ProcessError(TLF("Not all network edges were found in the lookup table '%' for landmark edge '%'.%", filename, landmark->getID(), tazWarning));
233 }
234 }
235#ifdef HAVE_FOX
236 if (maxNumThreads > 0) {
237 const double lmCost = router->recomputeCosts({landmark}, defaultVehicle, 0);
238 router->setAutoBulkMode(true);
239 if (threadPool.size() == 0) {
240 if (reverseRouter == nullptr) {
241 // The CHRouter needs initialization
242 // before it gets cloned, so we do a dummy routing which is not in parallel
243 std::vector<const E*> route;
244 router->compute(landmark, landmark, defaultVehicle, 0, route, true);
245 } else {
246 reverseRouter->setAutoBulkMode(true);
247 }
248 while ((int)threadPool.size() < maxNumThreads) {
249 auto revClone = reverseRouter == nullptr ? nullptr : reverseRouter->clone();
250 new WorkerThread(threadPool, router->clone(), revClone, defaultVehicle);
251 }
252 }
253 for (int j = (int)myFromLandmarkDists[i].size() + myFirstNonInternal; j < (int)edges.size(); ++j) {
254 const E* const edge = edges[j];
255 if (landmark != edge) {
256 const double sourceDestCost = lmCost + router->recomputeCosts({edge}, defaultVehicle, 0);
257 currentTasks.push_back(new RoutingTask(landmark, edge, sourceDestCost));
258 threadPool.add(currentTasks.back(), i % maxNumThreads);
259 }
260 }
261 }
262#else
263 UNUSED_PARAMETER(reverseRouter);
264#endif
265 }
266 }
267#ifdef HAVE_FOX
268 threadPool.waitAll(false);
269 int taskIndex = 0;
270#endif
271 for (int i = 0; i < numLandMarks; ++i) {
272 if ((int)myFromLandmarkDists[i].size() != (int)edges.size() - myFirstNonInternal) {
273 const E* landmark = landmarks[i];
274 const double lmCost = router->recomputeCosts({landmark}, defaultVehicle, 0);
275 int unreachableFrom = 0;
276 int unreachableTo = 0;
277 for (int j = (int)myFromLandmarkDists[i].size() + myFirstNonInternal; j < (int)edges.size(); ++j) {
278 const E* const edge = edges[j];
279 double distFrom = -1;
280 double distTo = -1;
281 if (landmark == edge) {
282 distFrom = 0;
283 distTo = 0;
284 } else {
285 if (maxNumThreads > 0) {
286#ifdef HAVE_FOX
287 distFrom = currentTasks[taskIndex]->getFromCost();
288 distTo = currentTasks[taskIndex]->getToCost();
289 delete currentTasks[taskIndex++];
290#endif
291 } else {
292 const double sourceDestCost = lmCost + router->recomputeCosts({edge}, defaultVehicle, 0);
293 std::vector<const E*> route;
294 std::vector<const ReversedEdge<E, V>*> reversedRoute;
295 // compute from-distance (skip taz-sources and other unreachable edges)
296 if (edge->getPredecessors().size() > 0 && landmark->getSuccessors().size() > 0) {
297 if (router->compute(landmark, edge, defaultVehicle, 0, route, true)) {
298 distFrom = MAX2(0.0, router->recomputeCosts(route, defaultVehicle, 0) - sourceDestCost);
299 route.clear();
300 }
301 }
302 // compute to-distance (skip unreachable landmarks)
303 if (landmark->getPredecessors().size() > 0 && edge->getSuccessors().size() > 0) {
304 if (router->compute(edge, landmark, defaultVehicle, 0, route, true)) {
305 distTo = MAX2(0.0, router->recomputeCosts(route, defaultVehicle, 0) - sourceDestCost);
306 route.clear();
307 }
308 }
309 }
310 }
311 myFromLandmarkDists[i].push_back(distFrom);
312 myToLandmarkDists[i].push_back(distTo);
313 if (!edge->isTazConnector()) {
314 if (distFrom == -1) {
315 unreachableFrom++;
316 }
317 if (distTo == -1) {
318 unreachableTo++;
319 }
320 }
321 }
322 if (unreachableFrom > 0 || unreachableTo > 0) {
323 WRITE_WARNINGF(TL("Landmark % is not reachable from % edges and is unable to reach % out of % total edges."),
324 landmark->getID(), unreachableFrom, unreachableTo, numericID.size());
325 }
326 }
327 }
328 if (!outfile.empty()) {
329 std::ostream* ostrm = nullptr;
330#ifdef HAVE_ZLIB
331 if (StringUtils::endsWith(outfile, ".gz")) {
332 ostrm = new zstr::ofstream(outfile.c_str(), std::ios_base::out);
333 } else {
334#endif
335 ostrm = new std::ofstream(outfile.c_str());
336#ifdef HAVE_ZLIB
337 }
338#endif
339 if (!ostrm->good()) {
340 delete ostrm;
341 throw ProcessError(TLF("Could not open file '%' for writing.", outfile));
342 }
343 WRITE_MESSAGEF(TL("Saving new matrix to '%'."), outfile);
344 for (int i = 0; i < numLandMarks; ++i) {
345 (*ostrm) << getLandmark(i) << "\n";
346 }
347 for (int j = 0; j < (int)myFromLandmarkDists[0].size(); ++j) {
348 (*ostrm) << edges[j + myFirstNonInternal]->getID();
349 for (int i = 0; i < numLandMarks; ++i) {
350 (*ostrm) << " " << myFromLandmarkDists[i][j] << " " << myToLandmarkDists[i][j];
351 }
352 (*ostrm) << "\n";
353 }
354 delete ostrm;
355 }
356 }
357
359 }
360
361 double lowerBound(const E* from, const E* to, double speed, double speedFactor, double fromEffort, double toEffort) const {
362 double result = from->getDistanceTo(to) / speed;
363#ifdef ASTAR_DEBUG_LOOKUPTABLE
364 if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM) {
365 std::cout << " lowerBound to=" << to->getID() << " result1=" << result << "\n";
366 }
367#endif
368 for (int i = 0; i < (int)myLandmarks.size(); ++i) {
369 // a cost of -1 is used to encode unreachability.
370 const double fl = myToLandmarkDists[i][from->getNumericalID() - myFirstNonInternal];
371 const double tl = myToLandmarkDists[i][to->getNumericalID() - myFirstNonInternal];
372 if (fl >= 0 && tl >= 0) {
373 const double bound = (fl - tl - toEffort) / speedFactor;
374#ifdef ASTAR_DEBUG_LOOKUPTABLE
375 if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM && result < bound) {
376 std::cout << " landmarkTo=" << getLandmark(i) << " result2=" << bound
377 << " fl=" << fl << " tl=" << tl << "\n";
378 }
379#endif
380 result = MAX2(result, bound);
381 }
382 const double lt = myFromLandmarkDists[i][to->getNumericalID() - myFirstNonInternal];
383 const double lf = myFromLandmarkDists[i][from->getNumericalID() - myFirstNonInternal];
384 if (lt >= 0 && lf >= 0) {
385 const double bound = (lt - lf - fromEffort) / speedFactor;
386#ifdef ASTAR_DEBUG_LOOKUPTABLE
387 if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM && result < bound) {
388 std::cout << " landmarkFrom=" << getLandmark(i) << " result3=" << bound
389 << " lt=" << lt << " lf=" << lf << "\n";
390 }
391#endif
392 result = MAX2(result, bound);
393 }
394 if ((tl >= 0 && fl < 0)
395 || (lf >= 0 && lt < 0)) {
396 // target unreachable.
397#ifdef ASTAR_DEBUG_UNREACHABLE
398 std::cout << " unreachable: from=" << from->getID() << " to=" << to->getID() << " landmark=" << getLandmark(i) << " "
399 << ((tl >= 0 && fl < 0) ? " (toLandmark)" : " (fromLandmark)")
400 << " fl=" << fl << " tl=" << tl << " lt=" << lt << " lf=" << lf
401 << "\n";
402#endif
403 return UNREACHABLE;
404 }
405 }
406 return result;
407 }
408
409 bool consistent() const {
410 return false;
411 }
412
413private:
414 std::map<std::string, int> myLandmarks;
415 std::vector<std::vector<double> > myFromLandmarkDists;
416 std::vector<std::vector<double> > myToLandmarkDists;
418
419#ifdef HAVE_FOX
420private:
421 class WorkerThread : public MFXWorkerThread {
422 public:
423 WorkerThread(MFXWorkerThread::Pool& pool,
425 SUMOAbstractRouter<ReversedEdge<E, V>, V>* reverseRouter, const V* vehicle)
426 : MFXWorkerThread(pool), myRouter(router), myReversedRouter(reverseRouter), myVehicle(vehicle) {}
427
428 virtual ~WorkerThread() {
429 delete myRouter;
430 delete myReversedRouter;
431 }
432
433 const std::pair<double, double> compute(const E* src, const E* dest, const double costOff) {
434 double fromResult = -1.;
435 if (myRouter->compute(src, dest, myVehicle, 0, myRoute, true)) {
436 fromResult = MAX2(0.0, myRouter->recomputeCosts(myRoute, myVehicle, 0) + costOff);
437 myRoute.clear();
438 }
439 double toResult = -1.;
440 if (myReversedRouter != nullptr) {
441 if (myReversedRouter->compute(src->getReversedRoutingEdge(), dest->getReversedRoutingEdge(), myVehicle, 0, myReversedRoute, true)) {
442 toResult = MAX2(0.0, myReversedRouter->recomputeCosts(myReversedRoute, myVehicle, 0) + costOff);
443 myReversedRoute.clear();
444 }
445 } else {
446 if (myRouter->compute(dest, src, myVehicle, 0, myRoute, true)) {
447 toResult = MAX2(0.0, myRouter->recomputeCosts(myRoute, myVehicle, 0) + costOff);
448 myRoute.clear();
449 }
450 }
451 return std::make_pair(fromResult, toResult);
452 }
453
454 private:
455 SUMOAbstractRouter<E, V>* myRouter;
456 SUMOAbstractRouter<ReversedEdge<E, V>, V>* myReversedRouter;
457 const V* myVehicle;
458 std::vector<const E*> myRoute;
459 std::vector<const ReversedEdge<E, V>*> myReversedRoute;
460 };
461
462 class RoutingTask : public MFXWorkerThread::Task {
463 public:
464 RoutingTask(const E* src, const E* dest, const double costOff)
465 : mySrc(src), myDest(dest), myCostOff(-costOff) {}
466 void run(MFXWorkerThread* context) {
467 myCost = ((WorkerThread*)context)->compute(mySrc, myDest, myCostOff);
468 }
469 double getFromCost() {
470 return myCost.first;
471 }
472 double getToCost() {
473 return myCost.second;
474 }
475 private:
476 const E* const mySrc;
477 const E* const myDest;
478 const double myCostOff;
479 std::pair<double, double> myCost;
480 private:
482 RoutingTask& operator=(const RoutingTask&) = delete;
483 };
484
485private:
487#endif
488
489 std::string getLandmark(int i) const {
490 for (std::map<std::string, int>::const_iterator it = myLandmarks.begin(); it != myLandmarks.end(); ++it) {
491 if (it->second == i) {
492 return it->first;
493 }
494 }
495 return "";
496 }
497};
#define UNREACHABLE
#define WRITE_WARNINGF(...)
Definition MsgHandler.h:288
#define WRITE_MESSAGEF(...)
Definition MsgHandler.h:290
#define WRITE_MESSAGE(msg)
Definition MsgHandler.h:289
#define TL(string)
Definition MsgHandler.h:305
#define TLF(string,...)
Definition MsgHandler.h:307
@ SVC_IGNORING
vehicles ignoring classes
T MAX2(T a, T b)
Definition StdDefs.h:82
virtual double lowerBound(const E *from, const E *to, double speed, double speedFactor, double fromEffort, double toEffort) const =0
provide a lower bound on the distance between from and to (excluding traveltime of both edges)
virtual bool consistent() const =0
whether the heuristic ist consistent (found nodes are always visited on the shortest path the first t...
double lowerBound(const E *from, const E *to, double, double speedFactor, double, double) const
provide a lower bound on the distance between from and to (excluding traveltime of both edges)
bool consistent() const
whether the heuristic ist consistent (found nodes are always visited on the shortest path the first t...
std::vector< std::vector< double > > myTable
virtual ~FullLookupTable()
FullLookupTable(const std::string &filename, const int size)
Computes the shortest path through a network using the A* algorithm.
std::string getLandmark(int i) const
std::vector< std::vector< double > > myToLandmarkDists
LandmarkLookupTable(const std::string &filename, const std::vector< E * > &edges, SUMOAbstractRouter< E, V > *router, SUMOAbstractRouter< ReversedEdge< E, V >, V > *reverseRouter, const V *defaultVehicle, const std::string &outfile, const int maxNumThreads, M *mapMatcher)
bool consistent() const
whether the heuristic ist consistent (found nodes are always visited on the shortest path the first t...
std::vector< std::vector< double > > myFromLandmarkDists
double lowerBound(const E *from, const E *to, double speed, double speedFactor, double fromEffort, double toEffort) const
provide a lower bound on the distance between from and to (excluding traveltime of both edges)
std::map< std::string, int > myLandmarks
A pool of worker threads which distributes the tasks and collects the results.
void waitAll(const bool deleteFinished=true)
waits for all tasks to be finished
void add(Task *const t, int index=-1)
Gives a number to the given task and assigns it to the worker with the given index....
int size() const
Returns the number of threads in the pool.
Abstract superclass of a task to be run with an index to keep track of pending tasks.
A thread repeatingly calculating incoming tasks.
A point in 2D or 3D with translation and scaling methods.
Definition Position.h:37
A list of positions.
the edge type representing backward edges
virtual SUMOAbstractRouter * clone()=0
void setAutoBulkMode(const bool mode)
virtual bool compute(const E *from, const E *to, const V *const vehicle, SUMOTime msTime, std::vector< const E * > &into, bool silent=false)=0
Builds the route between the given edges using the minimum effort at the given time The definition of...
virtual double recomputeCosts(const std::vector< const E * > &edges, const V *const v, SUMOTime msTime, double *lengthp=nullptr) const
int size() const
returns the number of existing substrings
std::string get(int pos) const
returns the item at the given position
static double toDouble(const std::string &sData)
converts a string into the double value described by it by calling the char-type converter
static bool endsWith(const std::string &str, const std::string suffix)
Checks whether a given string ends with the suffix.
#define UNUSED_PARAMETER(x)