From c81ea0478a09b9ae803739d6fb45dddb0e2bceb3 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 16 Mar 2022 11:53:24 +0000 Subject: [PATCH] Try a min-cut inspired heuristic for filter placement. The results are surprising (I didn't spot the bottleneck), but quite good! --- apps/ltn/src/filters/auto.rs | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/apps/ltn/src/filters/auto.rs b/apps/ltn/src/filters/auto.rs index 74ba2442b4..396be0b7a8 100644 --- a/apps/ltn/src/filters/auto.rs +++ b/apps/ltn/src/filters/auto.rs @@ -18,6 +18,8 @@ pub enum Heuristic { /// Try adding one filter to every possible road, counting the rat-runs after. Choose the next /// step by the least resulting rat runs. BruteForce, + /// Find one filter that splits a cell, maximizing the number of streets in each new cell. + SplitCells, /// Per cell, close all borders except for one. This doesn't affect connectivity, but prevents /// all rat-runs. OnlyOneBorder, @@ -28,6 +30,7 @@ impl Heuristic { vec![ Choice::new("greedy", Heuristic::Greedy), Choice::new("brute-force", Heuristic::BruteForce), + Choice::new("split cells", Heuristic::SplitCells), Choice::new("only one border", Heuristic::OnlyOneBorder), ] } @@ -59,6 +62,7 @@ impl Heuristic { match self { Heuristic::Greedy => greedy(ctx, app, neighborhood, timer), Heuristic::BruteForce => brute_force(ctx, app, neighborhood, timer), + Heuristic::SplitCells => split_cells(ctx, app, neighborhood, timer), Heuristic::OnlyOneBorder => only_one_border(app, neighborhood), } @@ -121,6 +125,52 @@ fn brute_force(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer } } +fn split_cells(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer: &mut Timer) { + // Filtering which road leads to new cells with the MOST streets in the smaller cell? + 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) { + // Did we split the cell? + if new.cells.len() > neighborhood.cells.len() { + // Find the two new cells + let split_cells: Vec<_> = new + .cells + .iter() + .filter(|cell| cell.roads.contains_key(r)) + .collect(); + assert_eq!(2, split_cells.len()); + // We want cells to be roughly evenly-sized. Just count the number of road segments + // as a proxy for that. + let new_score = split_cells[0].roads.len().min(split_cells[1].roads.len()); + if best + .map(|(_, old_score)| new_score > old_score) + .unwrap_or(true) + { + best = Some((*r, new_score)); + } + } + // 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(); + } +} + fn only_one_border(app: &mut App, neighborhood: &Neighborhood) { for cell in &neighborhood.cells { if cell.borders.len() > 1 {