diff --git a/include/nigiri/routing/raptor/raptor.h b/include/nigiri/routing/raptor/raptor.h index 60273027..82510859 100644 --- a/include/nigiri/routing/raptor/raptor.h +++ b/include/nigiri/routing/raptor/raptor.h @@ -336,9 +336,9 @@ struct raptor { continue; } - auto const is_dest = v == Vias && is_dest_[i]; auto const is_via = v != Vias && is_via_[v][i]; auto const target_v = is_via ? v + 1 : v; + auto const is_dest = target_v == Vias && is_dest_[i]; auto const stay = is_via ? via_stops_[v].stay_ : 0_minutes; trace( @@ -592,7 +592,11 @@ struct raptor { if (best_time == kInvalid) { return; } - auto const end_time = clamp(best_time + dir(dist_to_end_[i])); + auto const stay = Vias != 0 && is_via_[Vias - 1U][i] + ? via_stops_[Vias - 1U].stay_ + : 0_minutes; + auto const end_time = + clamp(best_time + stay.count() + dir(dist_to_end_[i])); if (is_better(end_time, best_[kIntermodalTarget][Vias])) { round_times_[k][kIntermodalTarget][Vias] = end_time; @@ -662,9 +666,17 @@ struct raptor { auto const v = Vias - j; auto target_v = v + v_offset[v]; if (et[v] && stp.can_finish(is_wheelchair_)) { - auto const is_via = target_v != Vias && is_via_[target_v][l_idx] && - via_stops_[target_v].stay_ == 0_minutes; - if (is_via) { + auto const is_via = target_v != Vias && is_via_[target_v][l_idx]; + auto const is_no_stay_via = + is_via && via_stops_[target_v].stay_ == 0_minutes; + + // special case: stop is via with stay > 0m + destination + auto const is_via_and_dest = + is_via && !is_no_stay_via && + (is_dest_[l_idx] || + (is_intermodal_dest() && state_.end_reachable_[l_idx])); + + if (is_no_stay_via) { ++v_offset[v]; ++target_v; } @@ -701,6 +713,36 @@ struct raptor { current_best = by_transport; any_marked = true; } + + if (is_via_and_dest) { + auto const dest_v = target_v + 1; + assert(dest_v == Vias); + auto const best_dest = + get_best(round_times_[k - 1][l_idx][dest_v], + tmp_[l_idx][dest_v], best_[l_idx][dest_v]); + + if (is_better(by_transport, best_dest) && + is_better(by_transport, time_at_dest_[k]) && + lb_[l_idx] != kUnreachable && + is_better(by_transport + dir(lb_[l_idx]), time_at_dest_[k])) { + trace_upd( + "┊ │k={} v={}->{} RT name={}, dbg={}, " + "time_by_transport={}, " + "BETTER THAN dest_best={} => update, {} marking station " + "{} (destination)!\n", + k, v, dest_v, tt_.transport_name(et[v].t_idx_), + tt_.dbg(et[v].t_idx_), to_unix(by_transport), + to_unix(best_dest), + !is_better(by_transport, best_dest) ? "NOT" : "", + location{tt_, stp.location_idx()}); + + ++stats_.n_earliest_arrival_updated_by_route_; + tmp_[l_idx][dest_v] = + get_best(by_transport, tmp_[l_idx][dest_v]); + state_.station_mark_.set(l_idx, true); + any_marked = true; + } + } } } } @@ -783,19 +825,27 @@ struct raptor { auto const by_transport = time_at_stop( r, et[v], stop_idx, kFwd ? event_type::kArr : event_type::kDep); - auto const is_via = target_v != Vias && is_via_[target_v][l_idx] && - via_stops_[target_v].stay_ == 0_minutes; + auto const is_via = target_v != Vias && is_via_[target_v][l_idx]; + auto const is_no_stay_via = + is_via && via_stops_[target_v].stay_ == 0_minutes; + + // special case: stop is via with stay > 0m + destination + auto const is_via_and_dest = + is_via && !is_no_stay_via && + (is_dest_[l_idx] || + (is_intermodal_dest() && state_.end_reachable_[l_idx])); if (Vias != 0) { trace_upd( "┊ │k={} v={}(+{})={} via_count={} is_via_dest={} stay={} " - "is_via={}\n", + "is_via={} is_dest={} is_via_and_dest={}\n", k, v, v_offset[v], target_v, Vias, target_v != Vias ? is_via_[target_v][l_idx] : is_dest_[l_idx], - via_stops_[target_v].stay_, is_via); + via_stops_[target_v].stay_, is_no_stay_via, is_dest_[l_idx], + is_via_and_dest); } - if (is_via) { + if (is_no_stay_via) { ++v_offset[v]; ++target_v; } @@ -864,6 +914,34 @@ struct raptor { is_better(clamp(by_transport + dir(lb_[l_idx])), time_at_dest_[k])); } + + if (is_via_and_dest) { + auto const dest_v = target_v + 1; + assert(dest_v == Vias); + auto const best_dest = + get_best(round_times_[k - 1][l_idx][dest_v], + tmp_[l_idx][dest_v], best_[l_idx][dest_v]); + + if (is_better(by_transport, best_dest) && + is_better(by_transport, time_at_dest_[k]) && + lb_[l_idx] != kUnreachable && + is_better(by_transport + dir(lb_[l_idx]), time_at_dest_[k])) { + trace_upd( + "┊ │k={} v={}->{} name={}, dbg={}, time_by_transport={}, " + "BETTER THAN dest_best={} => update, {} marking station " + "{} (destination)!\n", + k, v, dest_v, tt_.transport_name(et[v].t_idx_), + tt_.dbg(et[v].t_idx_), to_unix(by_transport), + to_unix(best_dest), + !is_better(by_transport, best_dest) ? "NOT" : "", + location{tt_, stp.location_idx()}); + + ++stats_.n_earliest_arrival_updated_by_route_; + tmp_[l_idx][dest_v] = get_best(by_transport, tmp_[l_idx][dest_v]); + state_.station_mark_.set(l_idx, true); + any_marked = true; + } + } } else { trace( "┊ │k={} v={}->{} *** NO UPD: no_trip={}, in_allowed={}, " diff --git a/src/routing/raptor/reconstruct.cc b/src/routing/raptor/reconstruct.cc index 9b9cad1c..8102bf6f 100644 --- a/src/routing/raptor/reconstruct.cc +++ b/src/routing/raptor/reconstruct.cc @@ -182,8 +182,23 @@ void reconstruct_journey_with_vias(timetable const& tt, break; } + auto const stop_matches_via = + new_v != 0 && q.via_stops_[new_v - 1].stay_ == 0_minutes && + matches(tt, location_match_mode::kEquivalent, + q.via_stops_[new_v - 1].location_, l); + + auto const check_via = [&]() { + if (stop_matches_via) { + trace_reconstruct( + " [find_entry_in_prev_round] new_v={}->{} (stop matches via)\n", + v, new_v, new_v - 1); + --new_v; + } + }; + if ((kFwd && !stp.in_allowed(is_wheelchair)) || (!kFwd && !stp.out_allowed(is_wheelchair))) { + check_via(); continue; } @@ -191,11 +206,6 @@ void reconstruct_journey_with_vias(timetable const& tt, base, stp.time(kFwd ? event_type::kDep : event_type::kArr)); auto const round_time = round_times[k - 1][to_idx(l)][new_v]; - auto const stop_matches_via = - new_v != 0 && q.via_stops_[new_v - 1].stay_ == 0_minutes && - matches(tt, location_match_mode::kEquivalent, - q.via_stops_[new_v - 1].location_, l); - if (is_better_or_eq(round_time, event_time) || // special case: first stop with meta stations (k == 1 && q.start_match_mode_ == location_match_mode::kEquivalent && @@ -212,12 +222,7 @@ void reconstruct_journey_with_vias(timetable const& tt, journey::run_enter_exit{r, stop_idx, from_stop_idx}}; } else { trace_rc_transport_entry_not_possible; - if (stop_matches_via) { - trace_reconstruct( - " [find_entry_in_prev_round] new_v={}->{} (stop matches via)\n", - v, new_v, new_v - 1); - --new_v; - } + check_via(); } } @@ -391,15 +396,22 @@ void reconstruct_journey_with_vias(timetable const& tt, auto const backup_v = v; + auto const is_final_leg = k == j.transfers_ + 1U; + auto const is_intermodal = + q.dest_match_mode_ == location_match_mode::kIntermodal; auto stay_l = 0_minutes; auto stay_fp_target = 0_minutes; - trace_reconstruct(" [check_fp] v={}, l={}, fp.target={}\n", v, - location{tt, l}, location{tt, fp.target()}); + trace_reconstruct( + " [check_fp] v={}, l={}, fp.target={}, final_leg={}, intermodal={}\n", + v, location{tt, l}, location{tt, fp.target()}, is_final_leg, + is_intermodal); if (v != 0 && matches(tt, location_match_mode::kEquivalent, q.via_stops_[v - 1].location_, l)) { --v; if (matches(tt, location_match_mode::kEquivalent, l, fp.target())) { - stay_fp_target = q.via_stops_[v].stay_; + if (!is_final_leg) { + stay_fp_target = q.via_stops_[v].stay_; + } trace_reconstruct( " [check_fp]: fp start+target matches current via: v={}->{}, " "stay_target={}\n", @@ -415,7 +427,9 @@ void reconstruct_journey_with_vias(timetable const& tt, q.via_stops_[v - 1].location_, fp.target())) { --v; assert(stay_fp_target == 0_minutes); - stay_fp_target = q.via_stops_[v].stay_; + if (!is_final_leg || is_intermodal) { + stay_fp_target = q.via_stops_[v].stay_; + } trace_reconstruct( " [check_fp]: fp target matches current via: v={}->{}, " "stay_fp_target={}\n", diff --git a/test/routing/via_search_test.cc b/test/routing/via_search_test.cc index fdd4a123..44aead41 100644 --- a/test/routing/via_search_test.cc +++ b/test/routing/via_search_test.cc @@ -1240,3 +1240,130 @@ leg 5: (P, P) [2019-05-01 09:43] -> (END, END) [2019-05-01 10:03] EXPECT_EQ(expected_A_intermodal_LP_via_O_0m, results_to_str(results, tt)); } + +TEST(routing, via_test_31_M_Q_via_Q_10m) { + // test: last via = destination, with stay duration (ignored) + // M -> Q, via Q (10 min) + auto tt = load_timetable(test_files_1); + + constexpr auto const expected_M_Q_via_O_10min = + R"( +[2019-05-01 09:00, 2019-05-01 10:00] +TRANSFERS: 0 + FROM: (M, M) [2019-05-01 09:00] + TO: (Q, Q) [2019-05-01 10:00] +leg 0: (M, M) [2019-05-01 09:00] -> (Q, Q) [2019-05-01 10:00] + 0: M M............................................... d: 01.05 09:00 [01.05 11:00] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 1: N N............................................... a: 01.05 09:13 [01.05 11:13] d: 01.05 09:15 [01.05 11:15] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 2: O O............................................... a: 01.05 09:28 [01.05 11:28] d: 01.05 09:30 [01.05 11:30] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 3: P P............................................... a: 01.05 09:43 [01.05 11:43] d: 01.05 09:45 [01.05 11:45] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 4: Q Q............................................... a: 01.05 10:00 [01.05 12:00] + + +)"sv; + + for (auto const& [dir, start_time] : + {std::pair{direction::kForward, time("2019-05-01 11:00 Europe/Berlin")}, + std::pair{direction::kBackward, + time("2019-05-01 12:00 Europe/Berlin")}}) { + auto const results = + search(tt, nullptr, + routing::query{.start_time_ = start_time, + .start_ = {{loc(tt, "M"), 0_minutes, 0U}}, + .destination_ = {{loc(tt, "Q"), 0_minutes, 0U}}, + .via_stops_ = {{loc(tt, "Q"), 10_minutes}}}, + dir); + + EXPECT_EQ(expected_M_Q_via_O_10min, results_to_str(results, tt)); + } +} + +TEST(routing, via_test_32_M_intermodal_Q_via_Q_10m) { + // test: last via = destination, with stay duration (before intermodal fp) + // M -> Q, via Q (10 min) + auto tt = load_timetable(test_files_1); + + constexpr auto const expected_M_intermodal_Q_via_O_10min = + R"( +[2019-05-01 09:00, 2019-05-01 10:25] +TRANSFERS: 0 + FROM: (M, M) [2019-05-01 09:00] + TO: (END, END) [2019-05-01 10:25] +leg 0: (M, M) [2019-05-01 09:00] -> (Q, Q) [2019-05-01 10:00] + 0: M M............................................... d: 01.05 09:00 [01.05 11:00] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 1: N N............................................... a: 01.05 09:13 [01.05 11:13] d: 01.05 09:15 [01.05 11:15] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 2: O O............................................... a: 01.05 09:28 [01.05 11:28] d: 01.05 09:30 [01.05 11:30] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 3: P P............................................... a: 01.05 09:43 [01.05 11:43] d: 01.05 09:45 [01.05 11:45] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 4: Q Q............................................... a: 01.05 10:00 [01.05 12:00] +leg 1: (Q, Q) [2019-05-01 10:10] -> (END, END) [2019-05-01 10:25] + MUMO (id=0, duration=15) + + +)"sv; + + for (auto const& [dir, start_time] : + {std::pair{direction::kForward, time("2019-05-01 11:00 Europe/Berlin")}, + std::pair{direction::kBackward, + time("2019-05-01 12:25 Europe/Berlin")}}) { + auto const results = + search(tt, nullptr, + routing::query{.start_time_ = start_time, + .dest_match_mode_ = + routing::location_match_mode::kIntermodal, + .start_ = {{loc(tt, "M"), 0_minutes, 0U}}, + .destination_ = {{loc(tt, "Q"), 15_minutes, 0U}}, + .via_stops_ = {{loc(tt, "Q"), 10_minutes}}}, + dir); + + auto results_str = results_to_str(results, tt); + if (dir == direction::kBackward) { + results_str = std::regex_replace(results_str, std::regex("START"), "END"); + } + EXPECT_EQ(expected_M_intermodal_Q_via_O_10min, results_str); + } +} + +TEST(routing, via_test_33_M_intermodal_Q_via_Q_0m) { + // test: last via = destination + // M -> Q, via Q (0 min) + auto tt = load_timetable(test_files_1); + + constexpr auto const expected_M_intermodal_Q_via_O_0min = + R"( +[2019-05-01 09:00, 2019-05-01 10:15] +TRANSFERS: 0 + FROM: (M, M) [2019-05-01 09:00] + TO: (END, END) [2019-05-01 10:15] +leg 0: (M, M) [2019-05-01 09:00] -> (Q, Q) [2019-05-01 10:00] + 0: M M............................................... d: 01.05 09:00 [01.05 11:00] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 1: N N............................................... a: 01.05 09:13 [01.05 11:13] d: 01.05 09:15 [01.05 11:15] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 2: O O............................................... a: 01.05 09:28 [01.05 11:28] d: 01.05 09:30 [01.05 11:30] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 3: P P............................................... a: 01.05 09:43 [01.05 11:43] d: 01.05 09:45 [01.05 11:45] [{name=Bus 7, day=2019-05-01, id=T9, src=0}] + 4: Q Q............................................... a: 01.05 10:00 [01.05 12:00] +leg 1: (Q, Q) [2019-05-01 10:00] -> (END, END) [2019-05-01 10:15] + MUMO (id=0, duration=15) + + +)"sv; + + for (auto const& [dir, start_time] : + {std::pair{direction::kForward, time("2019-05-01 11:00 Europe/Berlin")}, + std::pair{direction::kBackward, + time("2019-05-01 12:15 Europe/Berlin")}}) { + auto const results = + search(tt, nullptr, + routing::query{.start_time_ = start_time, + .dest_match_mode_ = + routing::location_match_mode::kIntermodal, + .start_ = {{loc(tt, "M"), 0_minutes, 0U}}, + .destination_ = {{loc(tt, "Q"), 15_minutes, 0U}}, + .via_stops_ = {{loc(tt, "Q"), 0_minutes}}}, + dir); + + auto results_str = results_to_str(results, tt); + if (dir == direction::kBackward) { + results_str = std::regex_replace(results_str, std::regex("START"), "END"); + } + EXPECT_EQ(expected_M_intermodal_Q_via_O_0min, results_str); + } +}