From 72600ea3c7302df080e46f5cea764389bc334dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCndling?= Date: Tue, 27 Feb 2024 00:01:33 +0100 Subject: [PATCH] clasz routing search filter --- include/nigiri/routing/clasz_mask.h | 29 +++ include/nigiri/routing/query.h | 3 + include/nigiri/routing/raptor/raptor.h | 71 +++++-- include/nigiri/routing/search.h | 7 +- include/nigiri/timetable.h | 7 + include/nigiri/types.h | 21 ++- src/routing/clasz_mask.cc | 16 ++ test/raptor_search.cc | 11 +- test/raptor_search.h | 28 +-- test/routing/clasz_filter_test.cc | 247 +++++++++++++++++++++++++ 10 files changed, 395 insertions(+), 45 deletions(-) create mode 100644 include/nigiri/routing/clasz_mask.h create mode 100644 src/routing/clasz_mask.cc create mode 100644 test/routing/clasz_filter_test.cc diff --git a/include/nigiri/routing/clasz_mask.h b/include/nigiri/routing/clasz_mask.h new file mode 100644 index 000000000..2994e703e --- /dev/null +++ b/include/nigiri/routing/clasz_mask.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include "nigiri/types.h" + +namespace nigiri::routing { + +using clasz_mask_t = std::uint16_t; + +constexpr inline clasz_mask_t all_clasz_allowed() { + return std::numeric_limits::max(); +} + +constexpr inline clasz_mask_t to_mask(clasz const c) { + auto const c_as_int = static_cast>(c); + return static_cast(1U << c_as_int); +} + +constexpr inline bool is_allowed(clasz_mask_t const mask, clasz const c) { + auto const c_as_mask = to_mask(c); + return (mask & c_as_mask) == c_as_mask; +} + +std::string to_str(clasz_mask_t const x); + +} // namespace nigiri::routing \ No newline at end of file diff --git a/include/nigiri/routing/query.h b/include/nigiri/routing/query.h index 9f8c6f758..48e00010b 100644 --- a/include/nigiri/routing/query.h +++ b/include/nigiri/routing/query.h @@ -1,11 +1,13 @@ #pragma once #include +#include #include #include #include "nigiri/common/interval.h" #include "nigiri/footpath.h" +#include "nigiri/routing/clasz_mask.h" #include "nigiri/routing/limits.h" #include "nigiri/routing/location_match_mode.h" #include "nigiri/types.h" @@ -45,6 +47,7 @@ struct query { bool extend_interval_earlier_{false}; bool extend_interval_later_{false}; profile_idx_t prf_idx_{0}; + clasz_mask_t allowed_claszes_{all_clasz_allowed()}; }; } // namespace nigiri::routing diff --git a/include/nigiri/routing/raptor/raptor.h b/include/nigiri/routing/raptor/raptor.h index ffb9aa5d0..5be896021 100644 --- a/include/nigiri/routing/raptor/raptor.h +++ b/include/nigiri/routing/raptor/raptor.h @@ -55,7 +55,8 @@ struct raptor { std::vector& is_dest, std::vector& dist_to_dest, std::vector& lb, - day_idx_t const base) + day_idx_t const base, + clasz_mask_t const allowed_claszes) : tt_{tt}, rtt_{rtt}, state_{state}, @@ -66,7 +67,8 @@ struct raptor { n_days_{tt_.internal_interval_days().size().count()}, n_locations_{tt_.n_locations()}, n_routes_{tt.n_routes()}, - n_rt_transports_{Rt ? rtt->n_rt_transports() : 0U} { + n_rt_transports_{Rt ? rtt->n_rt_transports() : 0U}, + allowed_claszes_{allowed_claszes} { state_.resize(n_locations_, n_routes_, n_rt_transports_); utl::fill(time_at_dest_, kInvalid); state_.round_times_.reset(kInvalid); @@ -144,22 +146,13 @@ struct raptor { std::swap(state_.prev_station_mark_, state_.station_mark_); utl::fill(state_.station_mark_, false); - any_marked = false; - for (auto r_id = 0U; r_id != n_routes_; ++r_id) { - if (state_.route_mark_[r_id]) { - ++stats_.n_routes_visited_; - trace("┊ ├k={} updating route {}\n", k, r_id); - any_marked |= update_route(k, route_idx_t{r_id}); - } - } + any_marked = (allowed_claszes_ == all_clasz_allowed()) + ? loop_routes(k) + : loop_routes(k); if constexpr (Rt) { - for (auto rt_t = 0U; rt_t != n_rt_transports_; ++rt_t) { - if (state_.rt_transport_mark_[rt_t]) { - ++stats_.n_routes_visited_; - trace("┊ ├k={} updating rt transport {}\n", k, rt_t); - any_marked |= update_rt_transport(k, rt_transport_idx_t{rt_t}); - } - } + any_marked |= (allowed_claszes_ == all_clasz_allowed()) + ? loop_rt_routes(k) + : loop_rt_routes(k); } if (!any_marked) { @@ -216,6 +209,49 @@ struct raptor { return tt_.internal_interval_days().from_ + as_int(base_) * date::days{1}; } + template + bool loop_routes(unsigned const k) { + auto any_marked = false; + for (auto r_idx = 0U; r_idx != n_routes_; ++r_idx) { + auto const r = route_idx_t{r_idx}; + + if (state_.route_mark_[r_idx]) { + if constexpr (WithClaszFilter) { + if (!is_allowed(allowed_claszes_, tt_.route_clasz_[r])) { + continue; + } + } + + ++stats_.n_routes_visited_; + trace("┊ ├k={} updating route {}\n", k, r); + any_marked |= update_route(k, r); + } + } + return any_marked; + } + + template + bool loop_rt_routes(unsigned const k) { + auto any_marked = false; + for (auto rt_t_idx = 0U; rt_t_idx != n_rt_transports_; ++rt_t_idx) { + if (state_.rt_transport_mark_[rt_t_idx]) { + auto const rt_t = rt_transport_idx_t{rt_t_idx}; + + if constexpr (WithClaszFilter) { + if (!is_allowed(allowed_claszes_, + rtt_->rt_transport_section_clasz_[rt_t][0])) { + continue; + } + } + + ++stats_.n_routes_visited_; + trace("┊ ├k={} updating rt transport {}\n", k, rt_t); + any_marked |= update_rt_transport(k, rt_t); + } + } + return any_marked; + } + void update_transfers(unsigned const k) { for (auto i = 0U; i != n_locations_; ++i) { if (!state_.prev_station_mark_[i]) { @@ -687,6 +723,7 @@ struct raptor { int n_days_; raptor_stats stats_; std::uint32_t n_locations_, n_routes_, n_rt_transports_; + clasz_mask_t allowed_claszes_; }; } // namespace nigiri::routing diff --git a/include/nigiri/routing/search.h b/include/nigiri/routing/search.h index 7fdd2204f..c52489ed6 100644 --- a/include/nigiri/routing/search.h +++ b/include/nigiri/routing/search.h @@ -55,7 +55,7 @@ struct search { static constexpr auto const kFwd = (SearchDir == direction::kForward); static constexpr auto const kBwd = (SearchDir == direction::kBackward); - Algo init(algo_state_t& algo_state) { + Algo init(clasz_mask_t const allowed_claszes, algo_state_t& algo_state) { stats_.fastest_direct_ = static_cast(fastest_direct_.count()); @@ -102,7 +102,8 @@ struct search { state_.travel_time_lower_bound_, day_idx_t{std::chrono::duration_cast( search_interval_.from_ - tt_.internal_interval().from_) - .count()}}; + .count()}, + allowed_claszes}; } search(timetable const& tt, @@ -125,7 +126,7 @@ struct search { }}, q_.start_time_)}, fastest_direct_{get_fastest_direct(tt_, q_, SearchDir)}, - algo_{init(algo_state)}, + algo_{init(q_.allowed_claszes_, algo_state)}, timeout_(timeout) {} routing_result execute() { diff --git a/include/nigiri/timetable.h b/include/nigiri/timetable.h index f6332c57c..aa55b1c27 100644 --- a/include/nigiri/timetable.h +++ b/include/nigiri/timetable.h @@ -163,12 +163,16 @@ struct timetable { route_idx_t register_route( std::basic_string const& stop_seq, std::basic_string const& clasz_sections) { + assert(!stop_seq.empty() && stop_seq.size() > 1U); + assert(!clasz_sections.empty()); + auto const idx = route_location_seq_.size(); route_transport_ranges_.emplace_back( transport_idx_t{transport_traffic_days_.size()}, transport_idx_t::invalid()); route_location_seq_.emplace_back(stop_seq); route_section_clasz_.emplace_back(clasz_sections); + route_clasz_.emplace_back(clasz_sections[0]); return route_idx_t{idx}; } @@ -376,6 +380,9 @@ struct timetable { // Route -> list of stops vecvec route_location_seq_; + // Route -> clasz + vector_map route_clasz_; + // Route -> clasz per section vecvec route_section_clasz_; diff --git a/include/nigiri/types.h b/include/nigiri/types.h index 843d2c5e3..978aa0259 100644 --- a/include/nigiri/types.h +++ b/include/nigiri/types.h @@ -326,15 +326,6 @@ inline std::ostream& operator<<(std::ostream& out, } // namespace std::chrono -template <> -struct fmt::formatter : ostream_formatter {}; - -template <> -struct fmt::formatter : ostream_formatter {}; - -template <> -struct fmt::formatter : ostream_formatter {}; - #include namespace nigiri { @@ -431,3 +422,15 @@ inline local_time to_local_time(timezone const& tz, unixtime_t const t) { } } // namespace nigiri + +template <> +struct fmt::formatter : ostream_formatter {}; + +template <> +struct fmt::formatter : ostream_formatter {}; + +template <> +struct fmt::formatter : ostream_formatter {}; + +template <> +struct fmt::formatter : ostream_formatter {}; \ No newline at end of file diff --git a/src/routing/clasz_mask.cc b/src/routing/clasz_mask.cc new file mode 100644 index 000000000..8b874987b --- /dev/null +++ b/src/routing/clasz_mask.cc @@ -0,0 +1,16 @@ +#include "nigiri/routing/clasz_mask.h" + +namespace nigiri::routing { + +std::string to_str(clasz_mask_t const x) { + auto s = std::string{}; + s += ", x=" + std::to_string(x); + for (auto i = std::underlying_type_t{0U}; + i != sizeof(clasz_mask_t) * 8; ++i) { + auto const allowed = is_allowed(x, clasz{i}); + s = std::string{allowed ? '1' : '0'} + s; + } + return s; +} + +} // namespace nigiri::routing \ No newline at end of file diff --git a/test/raptor_search.cc b/test/raptor_search.cc index 21a1d9d40..c03e9733b 100644 --- a/test/raptor_search.cc +++ b/test/raptor_search.cc @@ -57,7 +57,8 @@ pareto_set raptor_search(timetable const& tt, std::string_view from, std::string_view to, routing::start_time_t time, - direction const search_dir) { + direction const search_dir, + routing::clasz_mask_t const mask) { auto const src = source_idx_t{0}; auto q = routing::query{ .start_time_ = time, @@ -65,7 +66,8 @@ pareto_set raptor_search(timetable const& tt, 0U}}, .destination_ = {{tt.locations_.location_id_to_idx_.at({to, src}), 0_minutes, 0U}}, - .prf_idx_ = 0}; + .prf_idx_ = 0, + .allowed_claszes_ = mask}; return raptor_search(tt, rtt, std::move(q), search_dir); } @@ -74,9 +76,10 @@ pareto_set raptor_search(timetable const& tt, std::string_view from, std::string_view to, std::string_view time, - direction const search_dir) { + direction const search_dir, + routing::clasz_mask_t mask) { return raptor_search(tt, rtt, from, to, parse_time(time, "%Y-%m-%d %H:%M %Z"), - search_dir); + search_dir, mask); } pareto_set raptor_intermodal_search( diff --git a/test/raptor_search.h b/test/raptor_search.h index 3d039a221..b3f80afed 100644 --- a/test/raptor_search.h +++ b/test/raptor_search.h @@ -9,19 +9,23 @@ struct rt_timetable; namespace nigiri::test { -pareto_set raptor_search(timetable const&, - rt_timetable const*, - std::string_view from, - std::string_view to, - std::string_view time, - direction = direction::kForward); +pareto_set raptor_search( + timetable const&, + rt_timetable const*, + std::string_view from, + std::string_view to, + std::string_view time, + direction = direction::kForward, + routing::clasz_mask_t mask = routing::all_clasz_allowed()); -pareto_set raptor_search(timetable const&, - rt_timetable const*, - std::string_view from, - std::string_view to, - routing::start_time_t, - direction = direction::kForward); +pareto_set raptor_search( + timetable const&, + rt_timetable const*, + std::string_view from, + std::string_view to, + routing::start_time_t, + direction = direction::kForward, + routing::clasz_mask_t mask = routing::all_clasz_allowed()); pareto_set raptor_search(timetable const&, rt_timetable const*, diff --git a/test/routing/clasz_filter_test.cc b/test/routing/clasz_filter_test.cc new file mode 100644 index 000000000..62d7f6206 --- /dev/null +++ b/test/routing/clasz_filter_test.cc @@ -0,0 +1,247 @@ +#include "gtest/gtest.h" + +#include "nigiri/loader/gtfs/files.h" +#include "nigiri/loader/gtfs/load_timetable.h" +#include "nigiri/loader/init_finish.h" +#include "nigiri/rt/create_rt_timetable.h" +#include "nigiri/rt/gtfsrt_update.h" +#include "nigiri/rt/rt_timetable.h" +#include "nigiri/timetable.h" + +#include "../raptor_search.h" +#include "../rt/util.h" + +using namespace nigiri; +using namespace date; +using namespace std::chrono_literals; +using namespace std::string_view_literals; +using nigiri::test::raptor_search; + +namespace { + +// ROUTING CONNECTIONS: +// 10:00 - 11:00 A-C airplane direct +// 10:00 - 12:00 A-B-C train, one transfer +constexpr auto const test_files = R"( +# agency.txt +agency_id,agency_name,agency_url,agency_timezone +DB,Deutsche Bahn,https://deutschebahn.com,Europe/Berlin +LH,Lufthansa,https://lufthansa.de,Europe/Berlin + +# stops.txt +stop_id,stop_name,stop_desc,stop_lat,stop_lon,stop_url,location_type,parent_station +A,A,,0.0,1.0,, +B,B,,2.0,3.0,, +C,C,,4.0,5.0,, + +# routes.txt +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type +AIR,LH,X,,,1100 +R1,DB,1,,,3 +R2,DB,2,,,2 + +# trips.txt +route_id,service_id,trip_id,trip_headsign,block_id +AIR,S1,AIR,, +R1,S1,T1,, +R2,S1,T2,, + +# stop_times.txt +trip_id,arrival_time,departure_time,stop_id,stop_sequence,pickup_type,drop_off_type +AIR,10:00:00,10:00:00,A,0,0,0 +AIR,11:00:00,11:00:00,C,1,0,0 +T1,10:00:00,10:00:00,A,0,0,0 +T1,10:55:00,10:55:00,B,1,0,0 +T2,11:05:00,11:05:00,B,0,0,0 +T2,12:00:00,12:00:00,C,1,0,0 + +# calendar_dates.txt +service_id,date,exception_type +S1,20240301,1 +)"sv; + +constexpr auto const expected = + R"( +[2024-03-01 09:00, 2024-03-01 10:00] +TRANSFERS: 0 + FROM: (A, A) [2024-03-01 09:00] + TO: (C, C) [2024-03-01 10:00] +leg 0: (A, A) [2024-03-01 09:00] -> (C, C) [2024-03-01 10:00] + 0: A A............................................... d: 01.03 09:00 [01.03 10:00] [{name=X , day=2024-03-01, id=AIR, src=0}] + 1: C C............................................... a: 01.03 10:00 [01.03 11:00] +leg 1: (C, C) [2024-03-01 10:00] -> (C, C) [2024-03-01 10:00] + FOOTPATH (duration=0) + + +)"sv; + +constexpr auto const expected_1 = + R"( +[2024-03-01 09:00, 2024-03-01 11:00] +TRANSFERS: 1 + FROM: (A, A) [2024-03-01 09:00] + TO: (C, C) [2024-03-01 11:00] +leg 0: (A, A) [2024-03-01 09:00] -> (B, B) [2024-03-01 09:55] + 0: A A............................................... d: 01.03 09:00 [01.03 10:00] [{name=Bus 1, day=2024-03-01, id=T1, src=0}] + 1: B B............................................... a: 01.03 09:55 [01.03 10:55] +leg 1: (B, B) [2024-03-01 09:55] -> (B, B) [2024-03-01 09:57] + FOOTPATH (duration=2) +leg 2: (B, B) [2024-03-01 10:05] -> (C, C) [2024-03-01 11:00] + 0: B B............................................... d: 01.03 10:05 [01.03 11:05] [{name=2 , day=2024-03-01, id=T2, src=0}] + 1: C C............................................... a: 01.03 11:00 [01.03 12:00] +leg 3: (C, C) [2024-03-01 11:00] -> (C, C) [2024-03-01 11:00] + FOOTPATH (duration=0) + + +)"sv; + +constexpr auto const expected_rt = + R"( +[2024-03-01 09:10, 2024-03-01 10:10] +TRANSFERS: 0 + FROM: (A, A) [2024-03-01 09:10] + TO: (C, C) [2024-03-01 10:10] +leg 0: (A, A) [2024-03-01 09:10] -> (C, C) [2024-03-01 10:10] + 0: A A............................................... d: 01.03 09:00 [01.03 10:00] [{name=X , day=2024-03-01, id=AIR, src=0}] + 1: C C............................................... a: 01.03 10:00 [01.03 11:00] +leg 1: (C, C) [2024-03-01 10:10] -> (C, C) [2024-03-01 10:10] + FOOTPATH (duration=0) + + +)"sv; + +constexpr auto const expected_rt_1 = + R"( +[2024-03-01 09:10, 2024-03-01 11:10] +TRANSFERS: 1 + FROM: (A, A) [2024-03-01 09:10] + TO: (C, C) [2024-03-01 11:10] +leg 0: (A, A) [2024-03-01 09:10] -> (B, B) [2024-03-01 10:05] + 0: A A............................................... d: 01.03 09:00 [01.03 10:00] [{name=Bus 1, day=2024-03-01, id=T1, src=0}] + 1: B B............................................... a: 01.03 09:55 [01.03 10:55] +leg 1: (B, B) [2024-03-01 10:05] -> (B, B) [2024-03-01 10:07] + FOOTPATH (duration=2) +leg 2: (B, B) [2024-03-01 10:15] -> (C, C) [2024-03-01 11:10] + 0: B B............................................... d: 01.03 10:05 [01.03 11:05] [{name=2 , day=2024-03-01, id=T2, src=0}] + 1: C C............................................... a: 01.03 11:00 [01.03 12:00] +leg 3: (C, C) [2024-03-01 11:10] -> (C, C) [2024-03-01 11:10] + FOOTPATH (duration=0) + + +)"sv; + +} // namespace + +template +routing::clasz_mask_t make_mask(T... c) { + auto allowed = routing::clasz_mask_t{0U}; + ((allowed |= (1U << static_cast>(c))), ...); + return allowed; +} + +TEST(routing, clasz_filter_test) { + auto tt = timetable{}; + + tt.date_range_ = {date::sys_days{2024_y / March / 1}, + date::sys_days{2024_y / March / 2}}; + loader::register_special_stations(tt); + loader::gtfs::load_timetable({}, source_idx_t{0}, + loader::mem_dir::read(test_files), tt); + loader::finalize(tt); + + { // All classes. + auto const results = + raptor_search(tt, nullptr, "A", "C", tt.date_range_, + direction::kForward, routing::all_clasz_allowed()); + + std::stringstream ss; + ss << "\n"; + for (auto const& x : results) { + x.print(ss, tt); + ss << "\n\n"; + } + EXPECT_EQ(expected, ss.str()); + } + + { // All available classes. + auto const results = raptor_search( + tt, nullptr, "A", "C", tt.date_range_, direction::kForward, + make_mask(clasz::kCoach, clasz::kRegionalFast, clasz::kAir)); + + std::stringstream ss; + ss << "\n"; + for (auto const& x : results) { + x.print(ss, tt); + ss << "\n\n"; + } + EXPECT_EQ(expected, ss.str()); + } + + { // No plane - one transfer, 2h + auto const results = raptor_search( + tt, nullptr, "A", "C", tt.date_range_, direction::kForward, + make_mask(clasz::kCoach, clasz::kRegionalFast)); + + std::stringstream ss; + ss << "\n"; + for (auto const& x : results) { + x.print(ss, tt); + ss << "\n\n"; + } + EXPECT_EQ(expected_1, ss.str()); + } + + { // No connection. + auto const results = + raptor_search(tt, nullptr, "A", "C", tt.date_range_, + direction::kForward, make_mask(clasz::kShip)); + + EXPECT_TRUE(results.size() == 0U); + } + + // Update. + auto rtt = rt::create_rt_timetable(tt, date::sys_days{2024_y / March / 1}); + auto const msg = test::to_feed_msg( + {test::trip{.trip_id_ = "AIR", + .delays_ = {{.stop_id_ = "A", + .ev_type_ = nigiri::event_type::kArr, + .delay_minutes_ = 10}}}, + test::trip{.trip_id_ = "T1", + .delays_ = {{.stop_id_ = "A", + .ev_type_ = event_type::kDep, + .delay_minutes_ = 10U}}}, + test::trip{.trip_id_ = "T2", + .delays_ = {{.stop_id_ = "B", + .ev_type_ = event_type::kDep, + .delay_minutes_ = 10U}}}}, + date::sys_days{2024_y / March / 1} + 1h); + rt::gtfsrt_update_msg(tt, rtt, source_idx_t{0}, "", msg); + + { // All available classes. + auto const results = raptor_search( + tt, &rtt, "A", "C", tt.date_range_, direction::kForward, + make_mask(clasz::kCoach, clasz::kRegionalFast, clasz::kAir)); + + std::stringstream ss; + ss << "\n"; + for (auto const& x : results) { + x.print(ss, tt); + ss << "\n\n"; + } + EXPECT_EQ(expected_rt, ss.str()); + } + + { // No plane - one transfer, 2h + auto const results = + raptor_search(tt, &rtt, "A", "C", tt.date_range_, direction::kForward, + make_mask(clasz::kCoach, clasz::kRegionalFast)); + + std::stringstream ss; + ss << "\n"; + for (auto const& x : results) { + x.print(ss, tt); + ss << "\n\n"; + } + EXPECT_EQ(expected_rt_1, ss.str()); + } +} \ No newline at end of file