First tentative experiments in automatic filter placement -- the greedy

approach
This commit is contained in:
Dustin Carlino 2022-01-11 08:40:56 +00:00
parent 27452f0e08
commit 65899f3a79
3 changed files with 92 additions and 1 deletions

66
game/src/ltn/auto.rs Normal file
View File

@ -0,0 +1,66 @@
//! Experiments to make a neighborhood be low-traffic by automatically placing filters to prevent all rat runs.
use abstutil::Timer;
use widgetry::EventCtx;
use super::rat_runs::find_rat_runs;
use super::Neighborhood;
use crate::app::App;
/// Find the road with the most rat-runs that can be closed without creating a disconnected cell,
/// and filter it. There's a vague intuition or observation that the "bottleneck" will have the
/// most rat-runs going through it, so tackle the worst problem first.
pub fn greedy_heuristic(
ctx: &EventCtx,
app: &mut App,
neighborhood: &Neighborhood,
timer: &mut Timer,
) {
if neighborhood
.cells
.iter()
.filter(|c| c.is_disconnected())
.count()
!= 0
{
warn!("Not applying the greedy heuristic to a neighborhood; it already has a disconnected cell");
return;
}
let rat_runs = find_rat_runs(
&app.primary.map,
&neighborhood,
&app.session.modal_filters,
timer,
);
// TODO How should we break ties? Some rat-runs are worse than others; use that weight?
// TODO Should this operation be per cell instead? We could hover on a road belonging to that
// cell to select it
if let Some((r, _)) = rat_runs
.count_per_road
.borrow()
.iter()
.max_by_key(|pair| pair.1)
{
let road = app.primary.map.get_r(*r);
app.session
.modal_filters
.roads
.insert(road.id, road.length() / 2.0);
let new_neighborhood = Neighborhood::new(ctx, app, neighborhood.orig_perimeter.clone());
if new_neighborhood
.cells
.iter()
.filter(|c| c.is_disconnected())
.count()
!= 0
{
warn!("Filtering {} disconnects a cell, never mind", road.id);
app.session.modal_filters.roads.remove(&road.id).unwrap();
// TODO Try the next choice
}
}
}
// TODO The brute force approach: try to filter every possible road, find the one with the least
// rat-runs by the end

View File

@ -36,6 +36,12 @@ impl Viewer {
Toggle::choice(ctx, "draw cells", "areas", "streets", Key::D, true), Toggle::choice(ctx, "draw cells", "areas", "streets", Key::D, true),
]), ]),
Text::new().into_widget(ctx).named("warnings"), Text::new().into_widget(ctx).named("warnings"),
ctx.style()
.btn_outline
.text("Automatically stop rat-runs")
.hotkey(Key::A)
.tooltip("Warning: uses experimental heuristics to place filters")
.build_def(ctx),
]), ]),
) )
.build(ctx); .build(ctx);
@ -60,7 +66,7 @@ impl Viewer {
.neighborhood .neighborhood
.cells .cells
.iter() .iter()
.filter(|c| c.borders.is_empty() && !c.car_free) .filter(|c| c.is_disconnected())
.count(); .count();
// TODO Also add a red outline to them or something // TODO Also add a red outline to them or something
let warning = if disconnected_cells == 0 { let warning = if disconnected_cells == 0 {
@ -77,6 +83,16 @@ impl State<App> for Viewer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) { match self.panel.event(ctx) {
Outcome::Clicked(x) => { Outcome::Clicked(x) => {
if x == "Automatically stop rat-runs" {
ctx.loading_screen("automatically filter a neighborhood", |ctx, timer| {
crate::ltn::auto::greedy_heuristic(ctx, app, &self.neighborhood, timer);
});
self.neighborhood =
Neighborhood::new(ctx, app, self.neighborhood.orig_perimeter.clone());
self.neighborhood_changed(ctx, app);
return Transition::Keep;
}
return Tab::Connectivity return Tab::Connectivity
.handle_action::<Viewer>(ctx, app, x.as_ref()) .handle_action::<Viewer>(ctx, app, x.as_ref())
.unwrap(); .unwrap();

View File

@ -13,6 +13,7 @@ use crate::app::App;
pub use browse::BrowseNeighborhoods; pub use browse::BrowseNeighborhoods;
pub use partition::Partitioning; pub use partition::Partitioning;
mod auto;
mod browse; mod browse;
mod connectivity; mod connectivity;
mod draw_cells; mod draw_cells;
@ -94,6 +95,14 @@ pub struct Cell {
pub car_free: bool, pub car_free: bool,
} }
impl Cell {
/// A cell is disconnected if it's not connected to a perimeter road. (The exception is cells
/// containing roads that by their OSM classification already ban cars.)
pub fn is_disconnected(&self) -> bool {
self.borders.is_empty() && !self.car_free
}
}
/// An interval along a road's length, with start < end. /// An interval along a road's length, with start < end.
pub struct DistanceInterval { pub struct DistanceInterval {
pub start: Distance, pub start: Distance,