making a variant of pathfinding that can abort a walking trip early and

use transit
This commit is contained in:
Dustin Carlino 2018-11-29 08:45:38 -08:00
parent d529b006cb
commit 3f635e74c3
6 changed files with 231 additions and 112 deletions

View File

@ -1,16 +0,0 @@
# Assumptions
aka things to verify up-front before bugs creep into sim layers
## Map model
- no short lanes
- exactly what's the limit or problem?
- how should dog-leg intersections be modeled?
- maybe an easy baseline: a parking or driving lane that cant fit one max vehicle length
- connectivity
- from any sidewalk to any other
- from any driving lane to any other
- parking spots and bus stops line up with driving lane reasonably; dont exceed length
- associated lanes
- parking lane or bus stop without driving lane

View File

@ -27,10 +27,38 @@ Instead of picking acceleration in order to achieve some goal, just state the
goals and magically set the distance/speed based on that. As long as it's
physically possible, why go through extra work?
Instead of returning accel, what if we return (dist to travel this tick, new speed).
- How can we sanity check that those choices are reasonable? Seems like we
still have to sorta reason about acceleration properties of vehicles.
- But maybe not. each constraint...
- dont hit lead... if we're ALREADY close to them (current speed * timestep puts us within follow_dist of them right now), just warp follow_dist away from where they are now.
- if we need to stop for a point that's close, then we ideally want to do the kinda (do this distance, over this duration) thing and somehow not reason about what to do the next few ticks. event based approach for one thing. :\
Or different primitives, much closer to event sim...
- Often return (dist to wind up at, duration needed to get there)
- linear interpolate position for rendering in between there
- use for freeflow travel across most of a lane
- to slow down and stop for a fixed point, do a new interval
(STOPPING_DISTANCE_CONSTANT, few seconds to stop). the vehicle will
kinda linearly slow down into the stop. as long as the stopping
distance and time is reasonable based on the vehicle and the speed
limit (freeflow vs shuffling forwards case?), then this'll look fine.
- What about following vehicles?
- it'd be cool to have some freeflow movement to catch up to a lead vehicle, but if we look at the lead's position when we first analyze, we'll cover some ground, then replan again and do zenos paradox, planning freeflow for less new dist...
- parked cars departing will have to interrupt existing plans!
- rendering becomes expensive to do piecemeal; need to process cars in order on a queue to figure out positions. if we need to lookup a single one, might need to do a bunch of hops to figure out the front.
- not really easy to percolate down a single time to pass the intersection from the lead vehicle... intersections need to stay pretty detailedish.
Lookahead constraints:
- go the speed limit
- dont hit lead vehicle (dist is limited by their dist + length + following dist)
- stop by a certain point
- future: do lane-changing stuff
Blah. Initial simplicity of this idea lost. Maybe try a single, simple hack
out: replace accel_to_follow with something to warp to the right spot behind an
agent and set the speed equal to min(follower's max speed, lead vehicle's
current speed).
## The software engineering question
@ -40,3 +68,13 @@ DrawAgents trait, but a bit more expanded. Except stuff like time travel
applied to discrete-time sim can't support debugging agents or showing their
path. I think time travel with discrete-event would work fine -- just storing
all the previous states with the exact times.
## What broke about the old simplified driving model?
- straw model has some quirks with queueing
- after the lead vehicle starts the turn, the queue behind it magically warps to the front of the road
- following distance needs to apply across lanes/turns -- lookahead has to still happen
- the first vehicle in the turn jumps to a strange position based on the front/back rendering
- dont remember what this was
- at signals, cars doing the same turn wont start it until the last car finishes it
- dont remember what this was

View File

@ -257,12 +257,14 @@ it?
## Invariants
I thought I had a list of these somewhere else?
- min length for lanes, turns
- length for related lanes (sidewalk spot / parking / driving) matching up
- connectivity
- from any sidewalk to any other
- from any driving lane to any other
- no loop lanes (same src and dst endpt)... but what about cul-de-sacs then?
- associated lanes
- parking lane or bus stop without driving lane
## Border nodes

View File

@ -80,3 +80,20 @@ Buses have no trip, and that breaks some UI stuff. What if we assign some dummy
- Have to filter out buses from score summary. Can detect easily by lack of ped.
This seems actually not bad. Let's do that.
## Peds using transit
- Similar to use_bike, have a param to decide if the ped will consider transit or not.
- For now, cost transit rides as ZERO cost. Need to switch to time estimates otherwise.
The complicated part: should RideBus be a PathStep or not?
- eventually yes, along with many more FSM actions
- but right now, no -- riding the bus needs to be a separate trip leg.
- We just need the first walking part of the trip, and to know what two bus stops to use, and the route.
- other problem: dont know trip legs...
- make/scenario calls spawner to make trips using various modes.
- the trip legs are set up-front there.
- queued command to Walk then pathfinds and would discover we should use transit! can we amend the trip easily at that point?
- if we did this lazily in Command::Walk, then bus transfers / park + take a bus could emerge.
- or do we pathfind in spawn and throw away most of the result (or plumb it along as an optimization)?

View File

@ -504,14 +504,17 @@ impl Map {
}
// Not including transfers
pub fn get_connected_bus_stops(&self, start: BusStopID) -> BTreeSet<BusStopID> {
let mut stops: BTreeSet<BusStopID> = BTreeSet::new();
pub fn get_connected_bus_stops(&self, start: BusStopID) -> BTreeSet<(BusStopID, String)> {
let mut stops: BTreeSet<(BusStopID, String)> = BTreeSet::new();
for r in &self.bus_routes {
if r.stops.contains(&start) {
stops.extend(r.stops.clone());
for stop in &r.stops {
if *stop != start {
stops.insert((*stop, r.name.clone()));
}
}
}
}
stops.remove(&start);
stops
}

View File

@ -2,11 +2,11 @@ use dimensioned::si;
use geom::{Line, PolyLine, Pt2D};
use ordered_float::NotNaN;
use std::collections::{BinaryHeap, HashMap, VecDeque};
use {LaneID, LaneType, Map, Position, Traversable, TurnID};
use {BusStopID, LaneID, LaneType, Map, Position, Traversable, TurnID};
pub type Trace = PolyLine;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum PathStep {
// Original direction
Lane(LaneID),
@ -15,6 +15,16 @@ pub enum PathStep {
Turn(TurnID),
}
// TODO This is like PathStep, but also encodes the possibility of taking a bus.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
enum InternalPathStep {
Lane(LaneID),
ContraflowLane(LaneID),
Turn(TurnID),
// TODO It'd be great to assign a RouteID in the map layer and not clone a string constantly.
RideBus(BusStopID, BusStopID, String),
}
impl PathStep {
pub fn is_contraflow(&self) -> bool {
match self {
@ -214,13 +224,11 @@ pub struct PathRequest {
pub can_use_bus_lanes: bool,
}
pub enum Pathfinder {
ShortestDistance {
goal_pt: Pt2D,
can_use_bike_lanes: bool,
can_use_bus_lanes: bool,
},
UsingTransit,
pub struct Pathfinder {
goal_pt: Pt2D,
can_use_bike_lanes: bool,
can_use_bus_lanes: bool,
can_use_transit: bool,
}
impl Pathfinder {
@ -229,128 +237,195 @@ impl Pathfinder {
// TODO using first_pt here and in heuristic_dist is particularly bad for walking
// directions
let goal_pt = req.end.pt(map);
Pathfinder::ShortestDistance {
let internal_steps = Pathfinder {
goal_pt,
can_use_bike_lanes: req.can_use_bike_lanes,
can_use_bus_lanes: req.can_use_bus_lanes,
}.pathfind(map, req.start, req.end)
can_use_transit: false,
}.pathfind(map, req.start, req.end)?;
let steps: Vec<PathStep> = internal_steps
.into_iter()
.map(|s| match s {
InternalPathStep::Lane(l) => PathStep::Lane(l),
InternalPathStep::ContraflowLane(l) => PathStep::ContraflowLane(l),
InternalPathStep::Turn(t) => PathStep::Turn(t),
InternalPathStep::RideBus(_, _, _) => {
panic!("shortest_distance pathfind had {:?} as a step", s)
}
}).collect();
assert_eq!(
steps[0].as_traversable(),
Traversable::Lane(req.start.lane())
);
assert_eq!(
steps.last().unwrap().as_traversable(),
Traversable::Lane(req.end.lane())
);
return Some(Path::new(map, steps, req.end.dist_along()));
}
// Attempt the pathfinding and see if riding a bus is a step.
pub fn should_use_transit(
map: &Map,
req: PathRequest,
) -> Option<(BusStopID, BusStopID, String)> {
// TODO using first_pt here and in heuristic_dist is particularly bad for walking
// directions
let goal_pt = req.end.pt(map);
let internal_steps = Pathfinder {
goal_pt,
can_use_bike_lanes: false,
can_use_bus_lanes: false,
can_use_transit: true,
}.pathfind(map, req.start, req.end)?;
for s in internal_steps.into_iter() {
if let InternalPathStep::RideBus(stop1, stop2, route) = s {
return Some((stop1, stop2, route));
}
}
None
}
// Returns the cost of the potential next step, plus an optional heuristic to the goal
fn expand(&self, map: &Map, current: PathStep) -> Vec<(PathStep, f64)> {
match self {
Pathfinder::ShortestDistance {
goal_pt,
can_use_bike_lanes,
can_use_bus_lanes,
} => match current {
PathStep::Lane(l) | PathStep::ContraflowLane(l) => {
let endpoint = if current == PathStep::Lane(l) {
map.get_l(l).dst_i
// TODO Do this cost/heuristic thing somewhere else.
fn expand(&self, map: &Map, current: InternalPathStep) -> Vec<(InternalPathStep, f64)> {
let mut results: Vec<(InternalPathStep, f64)> = Vec::new();
match current {
InternalPathStep::Lane(l) | InternalPathStep::ContraflowLane(l) => {
let endpoint = if current == InternalPathStep::Lane(l) {
map.get_l(l).dst_i
} else {
map.get_l(l).src_i
};
for (turn, next) in map.get_next_turns_and_lanes(l, endpoint).into_iter() {
if !self.can_use_bike_lanes && next.lane_type == LaneType::Biking {
// Skip
} else if !self.can_use_bus_lanes && next.lane_type == LaneType::Bus {
// Skip
} else {
map.get_l(l).src_i
};
map.get_next_turns_and_lanes(l, endpoint)
.into_iter()
.filter_map(|(turn, next)| {
if !can_use_bike_lanes && next.lane_type == LaneType::Biking {
None
} else if !can_use_bus_lanes && next.lane_type == LaneType::Bus {
None
} else {
let cost = turn.length();
let heuristic = Line::new(turn.last_pt(), *goal_pt).length();
Some((PathStep::Turn(turn.id), (cost + heuristic).value_unsafe))
}
}).collect()
}
PathStep::Turn(t) => {
let dst = map.get_l(t.dst);
let cost = dst.length();
if t.parent == dst.src_i {
let heuristic = Line::new(dst.last_pt(), *goal_pt).length();
vec![(PathStep::Lane(dst.id), (cost + heuristic).value_unsafe)]
} else {
let heuristic = Line::new(dst.first_pt(), *goal_pt).length();
vec![(
PathStep::ContraflowLane(dst.id),
let cost = turn.length();
let heuristic = Line::new(turn.last_pt(), self.goal_pt).length();
results.push((
InternalPathStep::Turn(turn.id),
(cost + heuristic).value_unsafe,
)]
));
}
}
},
Pathfinder::UsingTransit => {
// TODO Need to add a PathStep for riding a bus between two stops.
/*
for stop1 in &current_lane.bus_stops {
for stop2 in &map.get_connected_bus_stops(*stop1) {
results.push((stop2.sidewalk, current_length));
if self.can_use_transit {
for stop1 in &map.get_l(l).bus_stops {
for (stop2, route) in map.get_connected_bus_stops(*stop1).into_iter() {
// No cost for riding the bus, for now.
let heuristic =
Line::new(map.get_bs(stop2).sidewalk_pos.pt(map), self.goal_pt)
.length();
results.push((
InternalPathStep::RideBus(*stop1, stop2, route),
heuristic.value_unsafe,
));
}
}
}
*/
Vec::new()
}
}
InternalPathStep::Turn(t) => {
let dst = map.get_l(t.dst);
let cost = dst.length();
if t.parent == dst.src_i {
let heuristic = Line::new(dst.last_pt(), self.goal_pt).length();
results.push((
InternalPathStep::Lane(dst.id),
(cost + heuristic).value_unsafe,
));
} else {
let heuristic = Line::new(dst.first_pt(), self.goal_pt).length();
results.push((
InternalPathStep::ContraflowLane(dst.id),
(cost + heuristic).value_unsafe,
));
}
}
InternalPathStep::RideBus(_, stop2, _) => {
let pos = map.get_bs(stop2).sidewalk_pos;
let sidewalk = map.get_l(pos.lane());
if pos.dist_along() != sidewalk.length() {
let cost = sidewalk.length() - pos.dist_along();
let heuristic = Line::new(sidewalk.last_pt(), self.goal_pt).length();
results.push((
InternalPathStep::Lane(sidewalk.id),
(cost + heuristic).value_unsafe,
));
}
if pos.dist_along() != 0.0 * si::M {
let cost = pos.dist_along();
let heuristic = Line::new(sidewalk.first_pt(), self.goal_pt).length();
results.push((
InternalPathStep::ContraflowLane(sidewalk.id),
(cost + heuristic).value_unsafe,
));
}
}
};
results
}
fn pathfind(&self, map: &Map, start: Position, end: Position) -> Option<Path> {
fn pathfind(&self, map: &Map, start: Position, end: Position) -> Option<Vec<InternalPathStep>> {
if start.lane() == end.lane() {
if start.dist_along() > end.dist_along() {
assert_eq!(map.get_l(start.lane()).lane_type, LaneType::Sidewalk);
return Some(Path::new(
map,
vec![PathStep::ContraflowLane(start.lane())],
end.dist_along(),
));
return Some(vec![InternalPathStep::ContraflowLane(start.lane())]);
}
return Some(Path::new(
map,
vec![PathStep::Lane(start.lane())],
end.dist_along(),
));
return Some(vec![InternalPathStep::Lane(start.lane())]);
}
// This should be deterministic, since cost ties would be broken by PathStep.
let mut queue: BinaryHeap<(NotNaN<f64>, PathStep)> = BinaryHeap::new();
queue.push((NotNaN::new(-0.0).unwrap(), PathStep::Lane(start.lane())));
if map.get_l(start.lane()).is_sidewalk() && start.dist_along() != 0.0 * si::M {
let mut queue: BinaryHeap<(NotNaN<f64>, InternalPathStep)> = BinaryHeap::new();
if map.get_l(start.lane()).is_sidewalk() {
if start.dist_along() != map.get_l(start.lane()).length() {
queue.push((
NotNaN::new(-0.0).unwrap(),
InternalPathStep::Lane(start.lane()),
));
}
if start.dist_along() != 0.0 * si::M {
queue.push((
NotNaN::new(-0.0).unwrap(),
InternalPathStep::ContraflowLane(start.lane()),
));
}
} else {
queue.push((
NotNaN::new(-0.0).unwrap(),
PathStep::ContraflowLane(start.lane()),
InternalPathStep::Lane(start.lane()),
));
}
let mut backrefs: HashMap<PathStep, PathStep> = HashMap::new();
let mut backrefs: HashMap<InternalPathStep, InternalPathStep> = HashMap::new();
while !queue.is_empty() {
let (cost_sofar, current) = queue.pop().unwrap();
// Found it, now produce the path
if current.as_traversable() == Traversable::Lane(end.lane()) {
let mut reversed_steps: Vec<PathStep> = Vec::new();
if current == InternalPathStep::Lane(end.lane())
|| current == InternalPathStep::ContraflowLane(end.lane())
{
let mut reversed_steps: Vec<InternalPathStep> = Vec::new();
let mut lookup = current;
loop {
reversed_steps.push(lookup);
if lookup.as_traversable() == Traversable::Lane(start.lane()) {
reversed_steps.push(lookup.clone());
if lookup == InternalPathStep::Lane(start.lane())
|| lookup == InternalPathStep::ContraflowLane(start.lane())
{
reversed_steps.reverse();
assert_eq!(
reversed_steps[0].as_traversable(),
Traversable::Lane(start.lane())
);
assert_eq!(
reversed_steps.last().unwrap().as_traversable(),
Traversable::Lane(end.lane())
);
return Some(Path::new(map, reversed_steps, end.dist_along()));
return Some(reversed_steps);
}
lookup = backrefs[&lookup];
lookup = backrefs[&lookup].clone();
}
}
// Expand
for (next, cost) in self.expand(map, current).into_iter() {
for (next, cost) in self.expand(map, current.clone()).into_iter() {
if !backrefs.contains_key(&next) {
backrefs.insert(next, current);
backrefs.insert(next.clone(), current.clone());
// Negate since BinaryHeap is a max-heap.
queue.push((
NotNaN::new(-1.0).unwrap() * (NotNaN::new(cost).unwrap() + cost_sofar),