Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions include/nigiri/footpath.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ struct footpath {
static constexpr auto const kDurationBits = kTotalBits - kTargetBits;
static constexpr auto const kMaxDuration = duration_t{
std::numeric_limits<location_idx_t::value_t>::max() >> kTargetBits};
static constexpr auto const kMaxTarget =
std::numeric_limits<location_idx_t::value_t>::max() >> kDurationBits;
static constexpr auto const kMaxTarget = location_idx_t{
std::numeric_limits<location_idx_t::value_t>::max() >> kDurationBits};

footpath() = default;

Expand All @@ -31,7 +31,7 @@ struct footpath {
: target_{target},
duration_{static_cast<value_type>(
(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() {
Expand Down
15 changes: 15 additions & 0 deletions include/nigiri/loader/reduce_footpaths.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include "nigiri/timetable.h"
#include "nigiri/types.h"

namespace nigiri {
struct timatable;
}

namespace nigiri::loader {

vecvec<location_idx_t, footpath> reduce_footpaths(
timetable&, vecvec<location_idx_t, footpath> const&);

}
1 change: 1 addition & 0 deletions include/nigiri/routing/query.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ struct query {
bool extend_interval_later_{false};
std::optional<interval<unixtime_t>> 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};
Expand Down
11 changes: 9 additions & 2 deletions include/nigiri/routing/raptor/raptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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();
Expand Down Expand Up @@ -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_;
Expand Down Expand Up @@ -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_;
};

Expand Down
3 changes: 2 additions & 1 deletion include/nigiri/routing/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}

Expand Down
4 changes: 3 additions & 1 deletion include/nigiri/timetable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
Expand Down Expand Up @@ -127,6 +127,8 @@ struct timetable {
mutable_fws_multimap<location_idx_t, footpath> preprocessing_footpaths_in_;
array<vecvec<location_idx_t, footpath>, kNProfiles> footpaths_out_;
array<vecvec<location_idx_t, footpath>, kNProfiles> footpaths_in_;
array<vecvec<location_idx_t, footpath>, kNProfiles> footpaths_full_out_;
array<vecvec<location_idx_t, footpath>, kNProfiles> footpaths_full_in_;
vector_map<timezone_idx_t, timezone> timezones_;
vector_map<location_idx_t, std::uint32_t> location_importance_;
std::uint32_t max_importance_{0U};
Expand Down
12 changes: 8 additions & 4 deletions src/loader/build_footpaths.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion src/loader/init_finish.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<double>(tt.n_locations()) / footpath::kMaxTarget * 100.0);
100.0 * tt.n_locations() / to_idx(footpath::kMaxTarget));
}

void finalize(timetable& tt,
Expand Down
107 changes: 107 additions & 0 deletions src/loader/reduce_footpaths.cc
Original file line number Diff line number Diff line change
@@ -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<location_idx_t, footpath> reduce_footpaths(
timetable& tt, vecvec<location_idx_t, footpath> const& fps) {
auto const timer = utl::scoped_timer{"reduce-footpaths"};

auto const init = []() {
auto x = std::array<footpath, kN>{};
x.fill(footpath{footpath::kMaxTarget, footpath::kMaxDuration});
return x;
}();

auto reduced =
vector_map<location_idx_t, std::vector<footpath>>(tt.n_locations());

struct state {
vector_map<route_idx_t, std::array<footpath, kN>> route_fps_;
bitvec_map<route_idx_t> route_is_reachable_;
};

utl::parallel_for_run_threadlocal<state>(
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<route_idx_t, std::array<footpath, kN>>{
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.

@miklcct miklcct Aug 15, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about footpaths to locations which doesn't have scheduled traffic, but are intended to be used for real-time trips, for example, stops reserved for rail replacement buses or spare rail platforms?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They will be recovered from footpaths_full_out_ which stores the full set of footpaths (unfiltered) and have to be copied to the rt_timetable in case a previously unused stop will be visited.

The main goal of this branch was to figure out the speedup potential. Results so far:

  • negligible speedup for default footpaths
  • measurable speedup for 20min walking footpaths
  • to be measured for longer settings (probably speedup will be higher the greater the distance, but still calculation time will grow in total)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update of rt_timetable is not yet implemented.

I expected a greater speedup and put the PR on hold as it turns out the speedup is less than expected.

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<location_idx_t, footpath>{};
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
2 changes: 1 addition & 1 deletion src/qa/qa.cc
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ cista::wrapped<benchmark_criteria> benchmark_criteria::read(
return std::visit(
utl::overloaded{[&](cista::buf<cista::mmap>& b) {
auto const ptr = reinterpret_cast<benchmark_criteria*>(
&b[cista::data_start(kMode)]);
b.base() + cista::data_start(kMode));
return cista::wrapped{std::move(mem), ptr};
},
[&](cista::buffer& b) {
Expand Down
1 change: 1 addition & 0 deletions src/routing/one_to_all.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions test/loader/build_footpaths_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading