From 7f0443243c7842c1a6579b666a21bd73dd221bfc Mon Sep 17 00:00:00 2001 From: Patrick Brosi Date: Tue, 24 Jul 2018 19:33:18 +0200 Subject: [PATCH] be a bit less verbose --- CMakeLists.txt | 6 +-- src/pfaedle/PfaedleMain.cpp | 20 ++++++-- src/pfaedle/config/ConfigReader.cpp | 23 ++++++--- src/pfaedle/config/ConfigReader.h | 2 +- src/pfaedle/osm/OsmBuilder.cpp | 4 +- src/pfaedle/router/ShapeBuilder.cpp | 42 ++++++++------- src/pfaedle/router/ShapeBuilder.h | 3 +- src/pfaedle/trgraph/NodePL.cpp | 47 +++++++++++++++-- src/pfaedle/trgraph/NodePL.h | 7 +-- src/util/geo/Geo.h | 80 +++++++++++++++++++++++++++++ src/util/geo/Polygon.h | 2 +- src/util/tests/TestMain.cpp | 19 ++++++- 12 files changed, 210 insertions(+), 45 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 27c0aee..cc923d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ else() message(WARNING "Configuring without OpenMP!") set(CMAKE_CXX_FLAGS "-Ofast -fno-signed-zeros -fno-trapping-math -Wall -Wno-format-extra-args -Wextra -Wformat-nonliteral -Wformat-security -Wformat=2 -Wfatal-errors -Wextra -Wno-implicit-fallthrough -pedantic") endif() -set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DLOGLEVEL=3") +set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DLOGLEVEL=3 -DPFAEDLE_DBG=1") set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS} -DLOGLEVEL=2") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DLOGLEVEL=2") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -g -DLOGLEVEL=3") @@ -77,10 +77,10 @@ add_custom_target( # handles install target install( - FILES README.md pfaedle.cfg DESTINATION share/${PROJECT_NAME} PERMISSIONS WORLD_READ + FILES pfaedle.cfg DESTINATION etc/${PROJECT_NAME} COMPONENT config PERMISSIONS WORLD_READ ) install( FILES build/pfaedle DESTINATION bin - PERMISSIONS WORLD_EXECUTE + PERMISSIONS WORLD_EXECUTE COMPONENT binaries ) diff --git a/src/pfaedle/PfaedleMain.cpp b/src/pfaedle/PfaedleMain.cpp index 2c54028..93b0515 100644 --- a/src/pfaedle/PfaedleMain.cpp +++ b/src/pfaedle/PfaedleMain.cpp @@ -56,14 +56,19 @@ int main(int argc, char** argv) { motCfgReader.parse(cfg.configPaths); + if (cfg.osmPath.empty()) { + std::cerr << "No OSM input file specified (-x), see --help." << std::endl; + exit(5); + } + if (cfg.feedPaths.size() == 1) { LOG(INFO) << "Reading " << cfg.feedPaths[0] << " ..."; ad::cppgtfs::Parser p; p.parse(>fs, cfg.feedPaths[0]); LOG(INFO) << "Done."; } else if (cfg.feedPaths.size() > 1) { - LOG(ERROR) << "Maximal one input feed allowed."; - exit(1); + std::cerr << "Maximally one input feed allowed." << std::endl; + exit(2); } LOG(DEBUG) << "Read " << motCfgReader.getConfigs().size() @@ -75,7 +80,7 @@ int main(int argc, char** argv) { singleTrip = gtfs.getTrips().get(cfg.shapeTripId); if (!singleTrip) { LOG(ERROR) << "Trip #" << cfg.shapeTripId << " not found."; - exit(1); + exit(3); } } @@ -84,7 +89,7 @@ int main(int argc, char** argv) { BBoxIdx box(2500); if (cfg.feedPaths.size()) { box = ShapeBuilder::getPaddedGtfsBox(>fs, 2500, cmdCfgMots, - cfg.shapeTripId); + cfg.shapeTripId, true); } OsmBuilder osmBuilder; std::vector opts; @@ -96,6 +101,13 @@ int main(int argc, char** argv) { } osmBuilder.filterWrite(cfg.osmPath, cfg.writeOsm, opts, box); exit(0); + } else if (!cfg.feedPaths.size()) { + std::cout << "No input feed specified, see --help" << std::endl; + exit(1); + } + + if (motCfgReader.getConfigs().size() == 0) { + LOG(WARN) << "No MOT configurations specified, see --help."; } std::vector dfBins; diff --git a/src/pfaedle/config/ConfigReader.cpp b/src/pfaedle/config/ConfigReader.cpp index 0771ff9..a211d59 100644 --- a/src/pfaedle/config/ConfigReader.cpp +++ b/src/pfaedle/config/ConfigReader.cpp @@ -18,15 +18,21 @@ using std::string; using std::exception; using std::vector; +static const char* YEAR = __DATE__ + 7; +static const char* COPY = + "University of Freiburg - Chair of Algorithms and Data Structures"; +static const char* AUTHORS = "Patrick Brosi "; + // _____________________________________________________________________________ -void ConfigReader::help() { +void ConfigReader::help(const char* bin) { std::cout << std::setfill(' ') << std::left - << "\033[1mpfaedle GTFS map matcher \033[22m\n" + << "pfaedle GTFS map matcher\n" << VERSION_FULL << " (built " << __DATE__ << " " << __TIME__ << ")\n\n" - << "(C) 2018 University of Freiburg\n" - << "Author: Patrick Brosi \n\n" + << "(C) " << YEAR << " " << COPY << "\n" + << "Authors: " << AUTHORS << "\n\n" << "Usage: " + << bin << " -x -c \n\n" << "Allowed options:\n\n" << "General:\n" @@ -163,14 +169,17 @@ void ConfigReader::read(Config* cfg, int argc, char** argv) { cfg->dbgOutputPath = optarg; break; case 'v': - std::cout << VERSION_FULL << " (built " << __DATE__ << " " << __TIME__ - << ")\n\n"; + std::cout << "pfaedle " << VERSION_FULL << " (built " << __DATE__ << " " + << __TIME__ << ")\n" + << "(C) " << YEAR << " " << COPY << "\n" + << "Authors: " << AUTHORS << "\nGNU General Public " + "License v3.0\n"; exit(0); case 'p': printOpts = true; break; case 'h': - help(); + help(argv[0]); exit(0); case ':': std::cerr << argv[optind - 1]; diff --git a/src/pfaedle/config/ConfigReader.h b/src/pfaedle/config/ConfigReader.h index 1a038ec..02490bc 100644 --- a/src/pfaedle/config/ConfigReader.h +++ b/src/pfaedle/config/ConfigReader.h @@ -14,7 +14,7 @@ namespace config { class ConfigReader { public: static void read(Config* targetConfig, int argc, char** argv); - static void help(); + static void help(const char* bin); }; } } diff --git a/src/pfaedle/osm/OsmBuilder.cpp b/src/pfaedle/osm/OsmBuilder.cpp index 455bb1d..cb7839f 100644 --- a/src/pfaedle/osm/OsmBuilder.cpp +++ b/src/pfaedle/osm/OsmBuilder.cpp @@ -54,6 +54,8 @@ void OsmBuilder::read(const std::string& path, const OsmReadOpts& opts, if (!bbox.size()) return; if (!fs->size()) return; + LOG(INFO) << "Reading OSM file " << path << " ... "; + NodeSet orphanStations; EdgTracks eTracks; { @@ -210,7 +212,7 @@ void OsmBuilder::read(const std::string& path, const OsmReadOpts& opts, LOG(VDEBUG) << "Write dummy node self-edges..."; writeSelfEdgs(g); - LOG(INFO) << "Graph has " << g->getNds()->size() << " nodes and " << comps + LOG(DEBUG) << "Graph has " << g->getNds()->size() << " nodes and " << comps << " connected component(s)"; } diff --git a/src/pfaedle/router/ShapeBuilder.cpp b/src/pfaedle/router/ShapeBuilder.cpp index 068a26b..1852e24 100644 --- a/src/pfaedle/router/ShapeBuilder.cpp +++ b/src/pfaedle/router/ShapeBuilder.cpp @@ -187,9 +187,9 @@ pfaedle::router::Shape ShapeBuilder::shape(Trip* trip) { void ShapeBuilder::shape(pfaedle::netgraph::Graph* ng) { TrGraphEdgs gtfsGraph; - LOG(INFO) << "Clustering trips..."; + LOG(DEBUG) << "Clustering trips..."; Clusters clusters = clusterTrips(_feed, _mots); - LOG(INFO) << "Clustered trips into " << clusters.size() << " clusters."; + LOG(DEBUG) << "Clustered trips into " << clusters.size() << " clusters."; std::map shpUsage; for (auto t : _feed->getTrips()) { @@ -218,11 +218,18 @@ void ShapeBuilder::shape(pfaedle::netgraph::Graph* ng) { { LOG(INFO) << "@ " << j << " / " << clusters.size() << " (" << (static_cast((j * 1.0) / clusters.size() * 100)) - << "%, " << (EDijkstra::ITERS - oiters) << " iters, tput " + << "%, " + << (EDijkstra::ITERS - oiters) << " iters, " + /** + TODO: this is actually misleading. We are counting the + Dijkstra iterations, but the measuring them against + the total running time (including all overhead + HMM solve) + << tput " << (static_cast(EDijkstra::ITERS - oiters)) / TOOK(t1, TIME()) - << " iters/ms" - << ", " << (10.0 / (TOOK(t1, TIME()) / 1000)) + << " iters/ms, " + **/ + << "matching " << (10.0 / (TOOK(t1, TIME()) / 1000)) << " trips/sec)"; oiters = EDijkstra::ITERS; @@ -265,19 +272,18 @@ void ShapeBuilder::shape(pfaedle::netgraph::Graph* ng) { } } - LOG(INFO) << "Done."; LOG(INFO) << "Matched " << totNumTrips << " trips in " << clusters.size() << " clusters."; - LOG(INFO) << "Took " << (EDijkstra::ITERS - totiters) + LOG(DEBUG) << "Took " << (EDijkstra::ITERS - totiters) << " iterations in total."; - LOG(INFO) << "Took " << TOOK(t2, TIME()) << " ms in total."; - LOG(INFO) << "Total avg. tput " + LOG(DEBUG) << "Took " << TOOK(t2, TIME()) << " ms in total."; + LOG(DEBUG) << "Total avg. tput " << (static_cast(EDijkstra::ITERS - totiters)) / TOOK(t2, TIME()) << " iters/sec"; - LOG(INFO) << "Total avg. trip tput " + LOG(DEBUG) << "Total avg. trip tput " << (clusters.size() / (TOOK(t2, TIME()) / 1000)) << " trips/sec"; - LOG(INFO) << "Avg hop distance was " + LOG(DEBUG) << "Avg hop distance was " << (totAvgDist / static_cast(clusters.size())) << " meters"; if (_cfg.buildTransitGraph) { @@ -438,11 +444,13 @@ const RoutingAttrs& ShapeBuilder::getRAttrs(const Trip* trip) const { // _____________________________________________________________________________ BBoxIdx ShapeBuilder::getPaddedGtfsBox(const Feed* feed, double pad, - const MOTs& mots, - const std::string& tid) { + const MOTs& mots, const std::string& tid, + bool dropShapes) { osm::BBoxIdx box(pad); for (const auto& t : feed->getTrips()) { if (!tid.empty() && t.second->getId() != tid) continue; + if (tid.empty() && t.second->getShape() && !dropShapes) continue; + if (t.second->getStopTimes().size() < 2) continue; if (mots.count(t.second->getRoute()->getType())) { Box cur = minbox(); for (const auto& st : t.second->getStopTimes()) { @@ -458,10 +466,11 @@ BBoxIdx ShapeBuilder::getPaddedGtfsBox(const Feed* feed, double pad, // _____________________________________________________________________________ void ShapeBuilder::buildGraph() { - LOG(INFO) << "Reading " << _cfg.osmPath << " ... "; osm::OsmBuilder osmBuilder; - osm::BBoxIdx box = getPaddedGtfsBox(_feed, 2500, _mots, _cfg.shapeTripId); + osm::BBoxIdx box = + getPaddedGtfsBox(_feed, 2500, _mots, _cfg.shapeTripId, _cfg.dropShapes); + osmBuilder.read(_cfg.osmPath, _motCfg.osmBuildOpts, &_g, box, _cfg.gridSize, getFeedStops(), &_restr); @@ -475,8 +484,6 @@ void ShapeBuilder::buildGraph() { _motCfg.routingOpts.nonOsmPen); } } - - LOG(INFO) << "Done."; } // _____________________________________________________________________________ @@ -525,7 +532,6 @@ Clusters ShapeBuilder::clusterTrips(Feed* f, MOTs mots) { Clusters ret; for (const auto& trip : f->getTrips()) { - // if (trip.second->getId() != "L5Cvl_T01") continue; if (trip.second->getShape() && !_cfg.dropShapes) continue; if (trip.second->getStopTimes().size() < 2) continue; if (!mots.count(trip.second->getRoute()->getType()) || diff --git a/src/pfaedle/router/ShapeBuilder.h b/src/pfaedle/router/ShapeBuilder.h index f782fac..8e98912 100644 --- a/src/pfaedle/router/ShapeBuilder.h +++ b/src/pfaedle/router/ShapeBuilder.h @@ -66,7 +66,8 @@ class ShapeBuilder { static osm::BBoxIdx getPaddedGtfsBox(const Feed* feed, double pad, const MOTs& mots, - const std::string& tid); + const std::string& tid, + bool dropShapes); private: Feed* _feed; diff --git a/src/pfaedle/trgraph/NodePL.cpp b/src/pfaedle/trgraph/NodePL.cpp index d3917c3..c4bd10c 100644 --- a/src/pfaedle/trgraph/NodePL.cpp +++ b/src/pfaedle/trgraph/NodePL.cpp @@ -21,21 +21,52 @@ StatInfo NodePL::_blockerSI = StatInfo(); std::unordered_map NodePL::_comps; // _____________________________________________________________________________ -NodePL::NodePL() : _geom(0, 0), _si(0), _component(0), _vis(0) {} +NodePL::NodePL() + : _geom(0, 0), + _si(0), + _component(0) +#ifdef PFAEDLE_DBG + , + _vis(0) +#endif +{ +} // _____________________________________________________________________________ NodePL::NodePL(const NodePL& pl) - : _geom(pl._geom), _si(0), _component(pl._component), _vis(pl._vis) { + : _geom(pl._geom), + _si(0), + _component(pl._component) +#ifdef PFAEDLE_DBG + , + _vis(pl._vis) +#endif +{ if (pl._si) setSI(*(pl._si)); } // _____________________________________________________________________________ NodePL::NodePL(const util::geo::FPoint& geom) - : _geom(geom), _si(0), _component(0), _vis(0) {} + : _geom(geom), + _si(0), + _component(0) +#ifdef PFAEDLE_DBG + , + _vis(0) +#endif +{ +} // _____________________________________________________________________________ NodePL::NodePL(const util::geo::FPoint& geom, const StatInfo& si) - : _geom(geom), _si(0), _component(0), _vis(0) { + : _geom(geom), + _si(0), + _component(0) +#ifdef PFAEDLE_DBG + , + _vis(0) +#endif +{ setSI(si); } @@ -52,7 +83,11 @@ NodePL::~NodePL() { } // _____________________________________________________________________________ -void NodePL::setVisited() const { _vis = true; } +void NodePL::setVisited() const { +#ifdef PFAEDLE_DBG + _vis = true; +#endif +} // _____________________________________________________________________________ void NodePL::setNoStat() { _si = 0; } @@ -81,7 +116,9 @@ void NodePL::setGeom(const util::geo::FPoint& geom) { _geom = geom; } // _____________________________________________________________________________ void NodePL::getAttrs(std::map* obj) const { (*obj)["component"] = std::to_string(reinterpret_cast(_component)); +#ifdef PFAEDLE_DBG (*obj)["dijkstra_vis"] = _vis ? "yes" : "no"; +#endif if (getSI()) { (*obj)["station_info_ptr"] = util::toString(_si); (*obj)["station_name"] = _si->getName(); diff --git a/src/pfaedle/trgraph/NodePL.h b/src/pfaedle/trgraph/NodePL.h index 054129f..a9bc4a9 100644 --- a/src/pfaedle/trgraph/NodePL.h +++ b/src/pfaedle/trgraph/NodePL.h @@ -62,19 +62,20 @@ class NodePL : public GeoNodePL { bool isBlocker() const; // Mark this node as visited (usefull for counting search space in Dijkstra) + // (only works for DEBUG build type) void setVisited() const; private: - std::string _b; // 32bit floats are enough here util::geo::FPoint _geom; StatInfo* _si; const Component* _component; - static StatInfo _blockerSI; - +#ifdef PFAEDLE_DBG mutable bool _vis; +#endif + static StatInfo _blockerSI; static std::unordered_map _comps; }; } // namespace trgraph diff --git a/src/util/geo/Geo.h b/src/util/geo/Geo.h index c63e3b1..94bb67a 100644 --- a/src/util/geo/Geo.h +++ b/src/util/geo/Geo.h @@ -403,6 +403,14 @@ inline bool intersects(const LineSegment& ls1, const LineSegment& ls2) { ((crossProd(ls2.first, ls1) < 0) ^ (crossProd(ls2.second, ls1) < 0))); } +// _____________________________________________________________________________ +template +inline bool intersects(const Point& a, const Point& b, const Point& c, + const Point& d) { + // legacy function + return intersects(LineSegment(a, b), LineSegment(c, d)); +} + // _____________________________________________________________________________ template inline bool intersects(const Line& ls1, const Line& ls2) { @@ -605,6 +613,78 @@ inline double dist(double x1, double y1, double x2, double y2) { return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); } +// _____________________________________________________________________________ +template +inline double dist(const LineSegment& ls, const Point& p) { + return distToSegment(ls, p); +} + +// _____________________________________________________________________________ +template +inline double dist(const Point& p, const LineSegment& ls) { + return dist(ls, p); +} + +// _____________________________________________________________________________ +template +inline double dist(const LineSegment& ls1, const LineSegment& ls2) { + if (intersects(ls1, ls2)) return 0; + double d1 = dist(ls1.first, ls2); + double d2 = dist(ls1.second, ls2); + double d3 = dist(ls2.first, ls1); + double d4 = dist(ls2.second, ls1); + return std::min(d1, std::min(d2, (std::min(d3, d4)))); +} + +// _____________________________________________________________________________ +template +inline double dist(const Point& p, const Line& l) { + double d = std::numeric_limits::infinity(); + for (size_t i = 1; i < l.size(); i++) { + double dTmp = distToSegment(l[i-1], l[i], p); + if (dTmp < EPSILON) return 0; + if (dTmp < d) d = dTmp; + } + return d; +} + +// _____________________________________________________________________________ +template +inline double dist(const Line& l, const Point& p) { + return dist(p, l); +} + +// _____________________________________________________________________________ +template +inline double dist(const LineSegment& ls, const Line& l) { + double d = std::numeric_limits::infinity(); + for (size_t i = 1; i < l.size(); i++) { + double dTmp = dist(ls, LineSegment(l[i-1], l[i])); + if (dTmp < EPSILON) return 0; + if (dTmp < d) d = dTmp; + } + return d; +} + +// _____________________________________________________________________________ +template +inline double dist(const Line& l, const LineSegment& ls) { + return dist(ls, l); +} + +// _____________________________________________________________________________ +template +inline double dist(const Line& la, const Line& lb) { + double d = std::numeric_limits::infinity(); + for (size_t i = 1; i < la.size(); i++) { + double dTmp = dist(LineSegment(la[i-1], la[i]), lb); + if (dTmp < EPSILON) return 0; + if (dTmp < d) d = dTmp; + } + return d; +} + + // _____________________________________________________________________________ inline double innerProd(double x1, double y1, double x2, double y2, double x3, double y3) { diff --git a/src/util/geo/Polygon.h b/src/util/geo/Polygon.h index d39c9a5..1c9a905 100644 --- a/src/util/geo/Polygon.h +++ b/src/util/geo/Polygon.h @@ -33,7 +33,7 @@ class Polygon { }; template -using MultiPolyon = std::vector>; +using MultiPolygon = std::vector>; } // namespace geo } // namespace util diff --git a/src/util/tests/TestMain.cpp b/src/util/tests/TestMain.cpp index f253dac..1037504 100644 --- a/src/util/tests/TestMain.cpp +++ b/src/util/tests/TestMain.cpp @@ -1024,7 +1024,7 @@ CASE("geometry") { EXPECT(geo::dist(geo::centroid(Polygon({{0, 0}, {3, 4}, {4,3}})), Point(7.0/3.0,7.0/3.0)) == approx(0)); auto polyy = Polygon({{0, 0}, {3, 4}, {4,3}}); - MultiPolyon mpoly{polyy, polyy}; + MultiPolygon mpoly{polyy, polyy}; EXPECT(geo::getWKT(polyy) == "POLYGON ((0 0, 3 4, 4 3, 0 0))"); EXPECT(geo::getWKT(mpoly) == "MULTIPOLYGON (((0 0, 3 4, 4 3, 0 0)), ((0 0, 3 4, 4 3, 0 0)))"); @@ -1209,6 +1209,23 @@ CASE("geometry") { auto obox = geo::getOrientedEnvelope(geo::Line{{0, 0}, {1, 1}, {1.5, 0.5}}); EXPECT(geo::contains(geo::convexHull(obox), geo::Polygon({{0.0, 0.0}, {1.0, 1.0}, {1.5, 0.5}, {0.5, -0.5}}))); EXPECT(geo::contains(geo::Polygon({{0.0, 0.0}, {1.0, 1.0}, {1.5, 0.5}, {0.5, -0.5}}), geo::convexHull(obox))); + + EXPECT(geo::dist(geo::LineSegment{{1, 1}, {3, 1}}, geo::LineSegment{{2, 2}, {2, 0}}) == approx(0)); + EXPECT(geo::dist(geo::LineSegment{{1, 1}, {3, 1}}, geo::LineSegment{{2, 4}, {2, 2}}) == approx(1)); + EXPECT(geo::dist(geo::LineSegment{{1, 1}, {3, 1}}, geo::LineSegment{{1, 1}, {3, 1}}) == approx(0)); + EXPECT(geo::dist(geo::LineSegment{{1, 1}, {3, 1}}, geo::LineSegment{{1, 2}, {3, 2}}) == approx(1)); + EXPECT(geo::dist(geo::LineSegment{{1, 1}, {3, 1}}, geo::LineSegment{{1, 2}, {3, 5}}) == approx(1)); + + EXPECT(geo::dist(geo::Line{{1, 1}, {3, 1}}, geo::Point{2, 1}) == approx(0)); + EXPECT(geo::dist(geo::Line{{1, 1}, {3, 1}}, geo::Point{2, 2}) == approx(1)); + EXPECT(geo::dist(geo::Line{{1, 1}, {3, 1}}, geo::Point{3, 1}) == approx(0)); + EXPECT(geo::dist(geo::Line{{1, 1}, {3, 1}}, geo::Point{1, 1}) == approx(0)); + + EXPECT(geo::dist(Line{{7, 7}, {7, -7}, {-7, -7}, {-7, 7}, {9, 0}, {-9, 0}, {0, 9}, {0, -9}}, Line{{7, 7}, {7, -7}, {-7, -7}, {-7, 7}, {9, 0}, {-9, 0}, {0, 9}, {0, -9}}) == approx(0)); + EXPECT(geo::dist(Line{{7, 7}, {7, -7}, {-7, -7}, {-7, 7}, {9, 0}, {-9, 0}, {0, 9}, {0, -9}}, LineSegment{{6, 7}, {8, -7}}) == approx(0)); + EXPECT(geo::dist(Line{{7, 7}, {7, -7}, {-7, -7}, {-7, 7}, {9, 0}, {-9, 0}, {0, 9}, {0, -9}}, Point{7, 4}) == approx(0)); + EXPECT(geo::dist(Line{{0, 0}, {1, 1}, {2, 0}}, Line{{1.5, 0.5}, {1.5, 100}}) == approx(0)); + EXPECT(geo::dist(Line{{0, 0}, {1, 1}, {2, 0}}, Line{{2, 0.5}, {2, 100}}) == approx(0.353553)); } }};