diff --git a/include/nigiri/footpath.h b/include/nigiri/footpath.h index d762906f1..73cd36423 100644 --- a/include/nigiri/footpath.h +++ b/include/nigiri/footpath.h @@ -18,8 +18,8 @@ struct footpath { static constexpr auto const kDurationBits = kTotalBits - kTargetBits; static constexpr auto const kMaxDuration = duration_t{ std::numeric_limits::max() >> kTargetBits}; - static constexpr auto const kMaxTarget = - std::numeric_limits::max() >> kDurationBits; + static constexpr auto const kMaxTarget = location_idx_t{ + std::numeric_limits::max() >> kDurationBits}; footpath() = default; @@ -31,7 +31,7 @@ struct footpath { : target_{target}, duration_{static_cast( (duration > kMaxDuration ? kMaxDuration : duration).count())} { - utl::verify(to_idx(target) < kMaxTarget, "station index overflow"); + utl::verify(target <= kMaxTarget, "station index overflow"); } static auto cmp_by_duration() { diff --git a/include/nigiri/loader/reduce_footpaths.h b/include/nigiri/loader/reduce_footpaths.h new file mode 100644 index 000000000..c6f217092 --- /dev/null +++ b/include/nigiri/loader/reduce_footpaths.h @@ -0,0 +1,15 @@ +#pragma once + +#include "nigiri/timetable.h" +#include "nigiri/types.h" + +namespace nigiri { +struct timatable; +} + +namespace nigiri::loader { + +vecvec reduce_footpaths( + timetable&, vecvec const&); + +} \ No newline at end of file diff --git a/include/nigiri/routing/query.h b/include/nigiri/routing/query.h index 3a22c5b9a..f2ff39caf 100644 --- a/include/nigiri/routing/query.h +++ b/include/nigiri/routing/query.h @@ -87,6 +87,7 @@ struct query { bool extend_interval_later_{false}; std::optional> max_interval_{}; profile_idx_t prf_idx_{0}; + bool use_reduced_transfers_{true}; clasz_mask_t allowed_claszes_{all_clasz_allowed()}; bool require_bike_transport_{false}; bool require_car_transport_{false}; diff --git a/include/nigiri/routing/raptor/raptor.h b/include/nigiri/routing/raptor/raptor.h index a96cb8dbe..c6d8297d3 100644 --- a/include/nigiri/routing/raptor/raptor.h +++ b/include/nigiri/routing/raptor/raptor.h @@ -94,6 +94,7 @@ struct raptor { bool const require_bike_transport, bool const require_car_transport, bool const is_wheelchair, + bool const use_reduced_transfers, transfer_time_settings const& tts) : tt_{tt}, rtt_{rtt}, @@ -116,6 +117,7 @@ struct raptor { require_bike_transport_{require_bike_transport}, require_car_transport_{require_car_transport}, is_wheelchair_{is_wheelchair}, + use_reduced_transfers_{use_reduced_transfers}, transfer_time_settings_{tts} { assert(Vias == via_stops_.size()); reset_arrivals(); @@ -466,8 +468,12 @@ struct raptor { } } - auto const& fps = kFwd ? tt_.locations_.footpaths_out_[prf_idx][l_idx] - : tt_.locations_.footpaths_in_[prf_idx][l_idx]; + auto const& fps = + (use_reduced_transfers_ + ? (kFwd ? tt_.locations_.footpaths_out_ + : tt_.locations_.footpaths_in_) + : (kFwd ? tt_.locations_.footpaths_full_out_ + : tt_.locations_.footpaths_full_in_))[prf_idx][l_idx]; for (auto const& fp : fps) { ++stats_.n_footpaths_visited_; @@ -1267,6 +1273,7 @@ struct raptor { bool require_bike_transport_; bool require_car_transport_; bool is_wheelchair_; + bool use_reduced_transfers_; transfer_time_settings transfer_time_settings_; }; diff --git a/include/nigiri/routing/search.h b/include/nigiri/routing/search.h index a43d45dd3..122b10038 100644 --- a/include/nigiri/routing/search.h +++ b/include/nigiri/routing/search.h @@ -150,7 +150,8 @@ struct search { allowed_claszes, require_bikes_allowed, require_cars_allowed, - q_.prf_idx_ == 2U, + q_.prf_idx_ == kWheelchairProfile, + q_.use_reduced_transfers_, tts}; } diff --git a/include/nigiri/timetable.h b/include/nigiri/timetable.h index 61f945ec6..152f5da42 100644 --- a/include/nigiri/timetable.h +++ b/include/nigiri/timetable.h @@ -43,7 +43,7 @@ struct timetable { location_id{.id_ = l.id_, .src_ = l.src_}, l_idx); if (is_new) { - utl::verify(next_idx <= footpath::kMaxTarget, + utl::verify(l_idx <= footpath::kMaxTarget, "MAX={} locations reached", footpath::kMaxTarget); names_.emplace_back(l.name_); @@ -127,6 +127,8 @@ struct timetable { mutable_fws_multimap preprocessing_footpaths_in_; array, kNProfiles> footpaths_out_; array, kNProfiles> footpaths_in_; + array, kNProfiles> footpaths_full_out_; + array, kNProfiles> footpaths_full_in_; vector_map timezones_; vector_map location_importance_; std::uint32_t max_importance_{0U}; diff --git a/src/loader/build_footpaths.cc b/src/loader/build_footpaths.cc index cb47f4b1e..466e7e2f1 100644 --- a/src/loader/build_footpaths.cc +++ b/src/loader/build_footpaths.cc @@ -15,6 +15,7 @@ #include "nigiri/loader/link_nearby_stations.h" #include "nigiri/loader/merge_duplicates.h" +#include "nigiri/loader/reduce_footpaths.h" #include "nigiri/common/day_list.h" #include "nigiri/constants.h" #include "nigiri/logging.h" @@ -390,18 +391,21 @@ void write_footpaths(timetable& tt) { assert(tt.locations_.preprocessing_footpaths_out_.size() == tt.n_locations()); assert(tt.locations_.preprocessing_footpaths_in_.size() == tt.n_locations()); - profile_idx_t const prf_idx{0}; - for (auto i = location_idx_t{0U}; i != tt.n_locations(); ++i) { - tt.locations_.footpaths_out_[prf_idx].emplace_back( + tt.locations_.footpaths_full_out_[kDefaultProfile].emplace_back( tt.locations_.preprocessing_footpaths_out_[i]); } for (auto i = location_idx_t{0U}; i != tt.n_locations(); ++i) { - tt.locations_.footpaths_in_[prf_idx].emplace_back( + tt.locations_.footpaths_full_in_[kDefaultProfile].emplace_back( tt.locations_.preprocessing_footpaths_in_[i]); } + tt.locations_.footpaths_out_[kDefaultProfile] = + reduce_footpaths(tt, tt.locations_.footpaths_full_out_[kDefaultProfile]); + tt.locations_.footpaths_in_[kDefaultProfile] = + reduce_footpaths(tt, tt.locations_.footpaths_full_in_[kDefaultProfile]); + tt.locations_.preprocessing_footpaths_in_.clear(); tt.locations_.preprocessing_footpaths_out_.clear(); } diff --git a/src/loader/init_finish.cc b/src/loader/init_finish.cc index 7ded1ad60..9d631b29c 100644 --- a/src/loader/init_finish.cc +++ b/src/loader/init_finish.cc @@ -138,7 +138,7 @@ void finalize(timetable& tt, finalize_options const opt) { log(log_lvl::info, "nigiri.loader.finalize", "{} locations ({}% of idx space used)", tt.n_locations(), - static_cast(tt.n_locations()) / footpath::kMaxTarget * 100.0); + 100.0 * tt.n_locations() / to_idx(footpath::kMaxTarget)); } void finalize(timetable& tt, diff --git a/src/loader/reduce_footpaths.cc b/src/loader/reduce_footpaths.cc new file mode 100644 index 000000000..e6e467460 --- /dev/null +++ b/src/loader/reduce_footpaths.cc @@ -0,0 +1,107 @@ +#include "nigiri/loader/reduce_footpaths.h" + +#include "utl/erase_duplicates.h" +#include "utl/insert_sorted.h" +#include "utl/parallel_for.h" +#include "utl/timer.h" + +#include "nigiri/timetable.h" + +namespace nigiri::loader { + +constexpr auto const kN = 2U; + +vecvec reduce_footpaths( + timetable& tt, vecvec const& fps) { + auto const timer = utl::scoped_timer{"reduce-footpaths"}; + + auto const init = []() { + auto x = std::array{}; + x.fill(footpath{footpath::kMaxTarget, footpath::kMaxDuration}); + return x; + }(); + + auto reduced = + vector_map>(tt.n_locations()); + + struct state { + vector_map> route_fps_; + bitvec_map route_is_reachable_; + }; + + utl::parallel_for_run_threadlocal( + tt.n_locations(), [&](state& s, std::size_t const l_idx) { + auto const l = location_idx_t{l_idx}; + + if (s.route_fps_.size() != tt.n_locations()) { + s.route_fps_ = vector_map>{ + tt.n_locations(), init}; + s.route_is_reachable_.resize(tt.n_locations()); + } + + // Group by route (duplicates footpaths). + // This eliminates footpaths to locations w/o scheduled traffic. + auto const l_routes = tt.location_routes_[l]; + for (auto const& fp : fps[l]) { + for (auto const& r : tt.location_routes_[fp.target()]) { + if (utl::find(l_routes, r) != end(l_routes)) { + continue; // Skip routes that are already available at this stop. + } + + s.route_is_reachable_.set(r, true); + + auto& r_reachable = s.route_fps_[r]; + + auto insert = fp; + for (auto i = 0U; i != r_reachable.size(); ++i) { + if (insert.duration() < r_reachable[i].duration()) { + std::swap(insert, r_reachable[i]); + } + } + } + } + + // Join fastest N footpaths per route. + s.route_is_reachable_.for_each_set_bit([&](route_idx_t const r) { + auto& route_fps = s.route_fps_[r]; + for (auto j = 0U; j != route_fps.size(); ++j) { + if (route_fps[j].target() != footpath::kMaxTarget) { + reduced[l].push_back(route_fps[j]); + } + } + route_fps = init; + }); + s.route_is_reachable_.zero_out(); + + // Deduplicate and sort by duration. + utl::erase_duplicates(reduced[l], + [](footpath const& a, footpath const& b) { + return std::tuple{a.duration(), a.target()} < + std::tuple{b.duration(), b.target()}; + }); + }); + + // Copy to vecvec. + auto compact = vecvec{}; + for (auto const& r : reduced) { + compact.emplace_back(r); + } + + // Count. + auto n_full = 0U; + for (auto const x : fps) { + n_full += x.size(); + } + auto n_reduced = 0U; + for (auto const x : compact) { + n_reduced += x.size(); + } + + log(log_lvl::info, "nigiri.loader.reduce_footpaths", + "reduce footpaths: #full={}, #reduced={} ({}%)", n_full, n_reduced, + 100.0 * n_reduced / n_full); + + return compact; +} + +} // namespace nigiri::loader \ No newline at end of file diff --git a/src/qa/qa.cc b/src/qa/qa.cc index 0a71f89db..aa9c5c4cc 100644 --- a/src/qa/qa.cc +++ b/src/qa/qa.cc @@ -150,7 +150,7 @@ cista::wrapped benchmark_criteria::read( return std::visit( utl::overloaded{[&](cista::buf& b) { auto const ptr = reinterpret_cast( - &b[cista::data_start(kMode)]); + b.base() + cista::data_start(kMode)); return cista::wrapped{std::move(mem), ptr}; }, [&](cista::buffer& b) { diff --git a/src/routing/one_to_all.cc b/src/routing/one_to_all.cc index c1e96e65d..2872a5be2 100644 --- a/src/routing/one_to_all.cc +++ b/src/routing/one_to_all.cc @@ -85,6 +85,7 @@ raptor_state one_to_all(timetable const& tt, q.require_bike_transport_, q.require_car_transport_, is_wheelchair, + q.use_reduced_transfers_, q.transfer_time_settings_}; run_raptor(std::move(r), tt, start_time, q); diff --git a/test/loader/build_footpaths_test.cc b/test/loader/build_footpaths_test.cc index 1e82862be..86885d841 100644 --- a/test/loader/build_footpaths_test.cc +++ b/test/loader/build_footpaths_test.cc @@ -20,19 +20,25 @@ namespace { constexpr auto const test_files = R"( # agency.txt agency_id,agency_name,agency_url,agency_timezone + # 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 + # trips.txt route_id,service_id,trip_id,trip_headsign,block_id + # stop_times.txt trip_id,arrival_time,departure_time,stop_id,stop_sequence,pickup_type,drop_off_type + # calendar_dates.txt service_id,date,exception_type + # transfers.txt from_stop_id,to_stop_id,transfer_type,min_transfer_time B,A,2,180