mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-23 17:07:12 +03:00
Try a brute-force search for the best single filter placement. Also not
great.
This commit is contained in:
parent
65899f3a79
commit
afc9846c1c
@ -1,32 +1,64 @@
|
||||
//! Experiments to make a neighborhood be low-traffic by automatically placing filters to prevent all rat runs.
|
||||
|
||||
use abstutil::Timer;
|
||||
use widgetry::EventCtx;
|
||||
use map_model::RoadID;
|
||||
use widgetry::{Choice, 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;
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Heuristic {
|
||||
/// 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.
|
||||
Greedy,
|
||||
/// Try adding one filter to every possible road, counting the rat-runs after. Choose the next
|
||||
/// step by the least resulting rat runs.
|
||||
BruteForce,
|
||||
}
|
||||
|
||||
impl Heuristic {
|
||||
pub fn choices() -> Vec<Choice<Heuristic>> {
|
||||
vec![
|
||||
Choice::new("greedy", Heuristic::Greedy),
|
||||
Choice::new("brute-force", Heuristic::BruteForce),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn apply(
|
||||
self,
|
||||
ctx: &EventCtx,
|
||||
app: &mut App,
|
||||
neighborhood: &Neighborhood,
|
||||
timer: &mut Timer,
|
||||
) {
|
||||
if neighborhood
|
||||
.cells
|
||||
.iter()
|
||||
.filter(|c| c.is_disconnected())
|
||||
.count()
|
||||
!= 0
|
||||
{
|
||||
warn!(
|
||||
"Not automatically changing this neighborhood; it already has a disconnected cell"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO If we already have no rat-runs, stop
|
||||
|
||||
match self {
|
||||
Heuristic::Greedy => greedy(ctx, app, neighborhood, timer),
|
||||
Heuristic::BruteForce => brute_force(ctx, app, neighborhood, timer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn greedy(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer: &mut Timer) {
|
||||
let rat_runs = find_rat_runs(
|
||||
&app.primary.map,
|
||||
&neighborhood,
|
||||
@ -42,25 +74,68 @@ pub fn greedy_heuristic(
|
||||
.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();
|
||||
if try_to_filter_road(ctx, app, neighborhood, *r).is_none() {
|
||||
warn!("Filtering {} disconnects a cell, never mind", r);
|
||||
// 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
|
||||
fn brute_force(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer: &mut Timer) {
|
||||
// Which road leads to the fewest rat-runs?
|
||||
let mut best: Option<(RoadID, usize)> = None;
|
||||
|
||||
let orig_filters = app.session.modal_filters.roads.len();
|
||||
timer.start_iter(
|
||||
"evaluate candidate filters",
|
||||
neighborhood.orig_perimeter.interior.len(),
|
||||
);
|
||||
for r in &neighborhood.orig_perimeter.interior {
|
||||
timer.next();
|
||||
if app.session.modal_filters.roads.contains_key(r) {
|
||||
continue;
|
||||
}
|
||||
if let Some(new) = try_to_filter_road(ctx, app, neighborhood, *r) {
|
||||
let num_rat_runs =
|
||||
// This spams too many logs, and can't be used within a start_iter anyway
|
||||
find_rat_runs(&app.primary.map, &new, &app.session.modal_filters, &mut Timer::throwaway())
|
||||
.paths
|
||||
.len();
|
||||
// TODO Again, break ties. Just the number of paths is kind of a weak metric.
|
||||
if best.map(|(_, score)| num_rat_runs < score).unwrap_or(true) {
|
||||
best = Some((*r, num_rat_runs));
|
||||
}
|
||||
// Always undo the new filter between each test
|
||||
app.session.modal_filters.roads.remove(r).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(orig_filters, app.session.modal_filters.roads.len());
|
||||
}
|
||||
|
||||
if let Some((r, _)) = best {
|
||||
try_to_filter_road(ctx, app, neighborhood, r).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// If successful, returns a Neighborhood and leaves the new filter in place. If it disconncts a
|
||||
// cell, reverts the change and returns None
|
||||
fn try_to_filter_road(
|
||||
ctx: &EventCtx,
|
||||
app: &mut App,
|
||||
neighborhood: &Neighborhood,
|
||||
r: RoadID,
|
||||
) -> Option<Neighborhood> {
|
||||
let road = app.primary.map.get_r(r);
|
||||
app.session
|
||||
.modal_filters
|
||||
.roads
|
||||
.insert(r, road.length() / 2.0);
|
||||
// TODO This is expensive; can we just do the connectivity work and not drawing?
|
||||
let new_neighborhood = Neighborhood::new(ctx, app, neighborhood.orig_perimeter.clone());
|
||||
if new_neighborhood.cells.iter().any(|c| c.is_disconnected()) {
|
||||
app.session.modal_filters.roads.remove(&r).unwrap();
|
||||
None
|
||||
} else {
|
||||
Some(new_neighborhood)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use widgetry::{
|
||||
EventCtx, GeomBatch, GfxCtx, Key, Outcome, Panel, State, Text, TextExt, Toggle, Widget,
|
||||
};
|
||||
|
||||
use super::auto::Heuristic;
|
||||
use super::per_neighborhood::{FilterableObj, Tab, TakeNeighborhood};
|
||||
use super::Neighborhood;
|
||||
use crate::app::{App, Transition};
|
||||
@ -36,12 +37,20 @@ impl Viewer {
|
||||
Toggle::choice(ctx, "draw cells", "areas", "streets", Key::D, true),
|
||||
]),
|
||||
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),
|
||||
Widget::row(vec![
|
||||
Widget::dropdown(
|
||||
ctx,
|
||||
"heuristic",
|
||||
// TODO Session state
|
||||
Heuristic::Greedy,
|
||||
Heuristic::choices(),
|
||||
),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("Automatically stop rat-runs")
|
||||
.hotkey(Key::A)
|
||||
.build_def(ctx),
|
||||
]),
|
||||
]),
|
||||
)
|
||||
.build(ctx);
|
||||
@ -85,7 +94,8 @@ impl State<App> for Viewer {
|
||||
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);
|
||||
let heuristic: Heuristic = self.panel.dropdown_value("heuristic");
|
||||
heuristic.apply(ctx, app, &self.neighborhood, timer);
|
||||
});
|
||||
self.neighborhood =
|
||||
Neighborhood::new(ctx, app, self.neighborhood.orig_perimeter.clone());
|
||||
@ -97,13 +107,15 @@ impl State<App> for Viewer {
|
||||
.handle_action::<Viewer>(ctx, app, x.as_ref())
|
||||
.unwrap();
|
||||
}
|
||||
Outcome::Changed(_) => {
|
||||
self.world = make_world(
|
||||
ctx,
|
||||
app,
|
||||
&self.neighborhood,
|
||||
self.panel.is_checked("draw cells"),
|
||||
);
|
||||
Outcome::Changed(x) => {
|
||||
if x == "draw cells" {
|
||||
self.world = make_world(
|
||||
ctx,
|
||||
app,
|
||||
&self.neighborhood,
|
||||
self.panel.is_checked("draw cells"),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user