1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! An implementation of [RAPTOR][paper] (Delling, Pajor, Werneck): given a
//! public transit network, find all Pareto-optimal journeys between two stops,
//! trading fewer transfers against earlier arrival.
//!
//! [paper]: https://www.microsoft.com/en-us/research/publication/round-based-public-transit-routing/
//!
//! # Quick start
//!
//! The [`gtfs`] adapter implements [`Timetable`] over a parsed GTFS feed.
//!
//! ```no_run
//! use gtfs_structures::Gtfs;
//! use jiff::civil::date;
//! use vulture::{SecondOfDay, Timetable};
//! use vulture::gtfs::GtfsTimetable;
//!
//! # fn main() -> anyhow::Result<()> {
//! let gtfs = Gtfs::new("path/to/gtfs.zip")?;
//! // Pin the timetable to one service date; inactive trips are filtered out.
//! let tt = GtfsTimetable::new(>fs, date(2026, 5, 4))?;
//!
//! // The algorithm takes dense u32 indices, not GTFS string IDs – resolve first.
//! let start = tt.stop_idx("dilshad_garden").expect("unknown stop");
//! let target = tt.stop_idx("vishwavidyalaya").expect("unknown stop");
//!
//! let journeys = tt
//! .query()
//! .from(start)
//! .to(target)
//! .max_transfers(10)
//! .depart_at(SecondOfDay::hms(9, 0, 0))
//! .run();
//!
//! for j in &journeys {
//! println!("{} trip(s), arrives {}", j.plan.len(), j.arrival());
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Concepts
//!
//! RAPTOR proceeds in *rounds*. Round k holds the earliest known arrival time
//! at every stop reachable using at most k trips. Each round scans the routes
//! through stops improved in the previous round and then relaxes walking
//! footpaths to a fixed point. The output is a Pareto front: one [`Journey`]
//! per trip count between zero and `max_transfers`, with strictly earlier
//! arrivals as trip count rises.
//!
//! The default routing is *single-criterion*: minimise arrival time, one
//! journey per trip count. The criterion is abstracted by the [`Label`] trait;
//! the default [`ArrivalTime`] carries a [`SecondOfDay`]. Multi-criterion
//! routing plugs in via [`Timetable::query_with_label`] with one of the
//! [`labels`] impls or a custom [`Label`].
//!
//! # Common patterns
//!
//! - **Single query.** [`Timetable::query`] returns a typestate builder.
//! Chain `.from(...).to(...).max_transfers(...).depart_at(...).run()`.
//! - **Multi-platform stations.** [`gtfs::GtfsTimetable::station_stops`]
//! expands a parent station to its child platforms for `.from(...)` /
//! `.to(...)`.
//! - **Range queries.** [`Query::depart_in_window`] returns a
//! [`Vec<RangeJourney>`](RangeJourney) Pareto profile across departure times.
//! Serial `.run()` is rRAPTOR; `.run_par()` / `.run_with_pool(&pool)` fan
//! per-departure work across Rayon (default-on `parallel` feature).
//! - **Server reuse.** Allocate a [`RaptorCache`] once via
//! [`RaptorCache::for_timetable`]; finish each chain with
//! `.run_with_cache(&mut cache)`.
//! - **Multi-threaded reuse.** [`RaptorCachePool`] is the `Sync` variant.
//! Each worker calls [`RaptorCachePool::checkout`] for one query; the cache
//! returns to the pool on drop.
//! - **Multi-criterion routing.** [`Timetable::query_with_label`] takes a
//! custom [`Label`]. [`labels::ArrivalAndWalk`] trades arrival against
//! accumulated walking time; [`labels::ArrivalAndFare`] trades arrival
//! against accumulated fare via a context supplied through
//! [`Query::with_context`].
//! - **Per-leg trip IDs and timing.** [`Journey::with_timing`] reconstructs
//! per-leg `(boarding stop, trip, depart, alight stop, arrive)` tuples.
//! `Journey.plan` carries the topology only.
//! - **Wheelchair-accessibility filter.**
//! [`Query::require_wheelchair_accessible`] skips trips and stops flagged
//! `NotAvailable` in GTFS `wheelchair_accessible` /
//! `wheelchair_boarding`.
//! - **Multi-day search.**
//! [`gtfs::GtfsTimetable::new_with_overnight_days`] loads
//! [`gtfs::OvernightDays`] additional service days after the base date,
//! shifting day-`d` trips by `d × 86 400` seconds onto one time axis.
//! - **Sparse `transfers.txt`.**
//! [`gtfs::GtfsTimetable::with_walking_footpaths`] builds bidirectional
//! R-tree walking edges from stop coordinates.
//! - **Feed introspection.** [`gtfs::GtfsTimetable::features`] returns a
//! [`gtfs::FeedFeatures`] snapshot (counts, transfer-type breakdown,
//! parent stations, shaped trips, wheelchair flags, footpath state).
//! [`gtfs::FeedFeatures::suggestions`] maps it to advisory strings.
//!
//! # Custom backends
//!
//! Implement the [`Timetable`] trait directly for non-GTFS data. Identifiers
//! are dense `u32` newtypes ([`StopIdx`], [`RouteIdx`], [`TripIdx`]); the
//! adapter interns external IDs at construction. The trait carries one
//! mandatory soundness contract, no-overtaking within a route, documented
//! on [`Timetable`]. Footpaths returned by [`Timetable::get_footpaths_from`]
//! are direct walks only; the algorithm chains them within a round, so
//! transitive closure is not required. Adapters whose relation *is* closed
//! can opt into single-pass relaxation via
//! [`Timetable::footpaths_are_transitively_closed`].
//!
//! [`manual::SimpleTimetable`] is a hand-rolled in-memory adapter for tests
//! and small fixtures.
//!
//! Issue-by-issue audit against the paper:
//! [`docs/soundness.md`](https://github.com/urschrei/vulture/blob/main/docs/soundness.md).
//!
//! # Errors
//!
//! Four patterns, picked per call:
//!
//! - **`Result` for construction.** [`gtfs::GtfsTimetable`] returns
//! [`Result<_, gtfs::GtfsError>`](gtfs::GtfsError). Custom adapters
//! should follow the same pattern with their own error enum.
//! - **`Option` for lookups.** [`gtfs::GtfsTimetable::stop_idx`],
//! [`gtfs::GtfsTimetable::route_idx`], and similar "find an `X` matching
//! `Y`" accessors return `Option`; `None` means "not in this timetable".
//! - **`Result` for plan reconstruction.** [`Journey::with_timing`]
//! returns [`Result<_, TimingError>`]; failure modes (no boarding
//! stop reachable, no catchable trip, alighting stop not on route)
//! are typed variants for debug context.
//! - **Panics for programmer-violated invariants.** A [`RaptorCache`]
//! sized for one timetable, used against a differently-sized one,
//! panics with an assertion message. Custom [`Timetable`] adapters
//! that violate the no-overtaking contract typically produce wrong
//! answers rather than panic.
//!
//! # Cargo features
//!
//! - `parallel` (default-on) – enables [`Query::run_par`] /
//! [`Query::run_with_pool`] via `rayon`. `default-features = false` drops
//! the dependency; [`RaptorCachePool`] stays available.
//! - `gtfs-bench` – the `gtfs` criterion benchmark.
//! - `internal` – the `raptor` criterion benchmark over [`manual`].
/// In-memory `Timetable` adapter you build by hand with `.route(...)` /
/// `.footpath(...)` calls. Useful when your data isn't from a parsed
/// GTFS feed – for tests, custom data sources, and toy examples.
// Central items: the trait, the builder, the result.
pub use Timetable;
pub use Query;
pub use RangeJourney;
pub use Journey;
pub use TimedLeg;
pub use TimingError;
// Cache reuse: scratch buffers for repeated queries.
pub use PooledCache;
pub use RaptorCache;
pub use RaptorCachePool;
// Label trait + the default single-criterion impl.
pub use ArrivalTime;
pub use Label;
pub use TripContext;
// Query builder typestate markers.
pub use NeedsDeparture;
pub use RangeDeparture;
pub use SingleDeparture;
// Endpoints – query input.
pub use Endpoints;
pub use IntoEndpoints;
// Newtypes for indices and time quantities.
pub use RouteIdx;
pub use StopIdx;
pub use TripIdx;
pub use Duration;
pub use ParseSecondOfDayError;
pub use SecondOfDay;
pub use Transfers;
pub use TryFromSignedDurationError;
/// Internal round-counter type, used only for indexing label arrays.
/// User-facing transfer caps are passed as [`Transfers`].
pub type K = usize;