diff --git a/DokumentationAufgabe1.tex b/DokumentationAufgabe1.tex index 22cde15..4a91b3a 100644 --- a/DokumentationAufgabe1.tex +++ b/DokumentationAufgabe1.tex @@ -221,7 +221,7 @@ Außerdem wird Lisa auf einer optimalen Route vor dem letzten Abschnitt immer vo \end{figure} \section{Bedienung des Programms} -Das Programm liest die Problemstellung von der Standardeingabe ein. Die grafische Ausgabe wird in die Standardausgabe geschrieben. Wird $-t$ übergeben, kann die Ausgabe in einem \LaTeX-Dokument verwendet werden. Standardmäßig wird ein SVG-Dokument erzeugt. Mit $-s$ werden auch Zwischenlösungen im aktuellen Ordner als SVG-Dateien gespeichert. Falls man die Geschwindigkeit von Lisa oder dem Bus verändern will, kann man dies mit $-l$ bzw. $-b$ tun. Für zusätzliche Debug-Ausgaben kann man $-d$ verwenden. Die Standardoptionen $-h$ und $-V$ zeigen die Hilfe und die Version des Programms an. +Das Programm liest die Problemstellung von der Standardeingabe ein. Die grafische Ausgabe wird in die Standardausgabe geschrieben. Wird $-t$ übergeben, kann die Ausgabe in einem \LaTeX-Dokument verwendet werden. Standardmäßig wird ein SVG-Dokument erzeugt. Falls man die Geschwindigkeit von Lisa oder dem Bus verändern will, kann man dies mit $-l$ bzw. $-b$ tun. Für zusätzliche Debug-Ausgaben kann man $-d$ verwenden. Die Standardoptionen $-h$ und $-V$ zeigen die Hilfe und die Version des Programms an. \lstinputlisting[caption=Hilfetext des Programmes,frame=single,breaklines=true]{help.txt} Das vorgegebene Eingabeformat ist mühsam zu schreiben. Daher kann das Programm auch SVG-Dateien einlesen. Punkte werden als Startpunkte interpretiert, sonstige Pfade sind Hindernisse. @@ -276,6 +276,7 @@ Alle Beispiele sind im Maßstab 1:70m. Für die folgenden fünf Beispiele benöt \label{fig:bsp5} \end{figure} \subsection{Eigene Beispiele} +Für diese benötigt das Programm höchstens 250 ms Rechenzeit. \lstinputlisting[caption=Lösung für Abb. \ref{fig:ebsp2},frame=single,breaklines=true]{runwelt3.txt} \begin{figure}[H] \centering diff --git a/src/display.rs b/src/display.rs index 44d4a9b..7919ef1 100644 --- a/src/display.rs +++ b/src/display.rs @@ -4,8 +4,6 @@ use svg::node::element::{AnimateMotion, Circle, MotionPath}; use svg::Document; use svg::Node; -use std::fs; - use super::*; pub(crate) fn dump_latex_code(route: &[RunState], polys: &[Polygon]) { @@ -90,21 +88,6 @@ pub(crate) fn dump_route(opt: Opt, house: Point, polys: &[Polygon], route: &[Run ); } -pub(crate) fn save_svg( - opt: Opt, - filename: &str, - house: Point, - polys: &[Polygon], - route: &[RunState], -) { - let (points, lines, route1, route2_start) = gen_params(opt, house, route); - fs::write( - filename, - generate_svg(&points, &lines, polys, &route1, route2_start), - ) - .unwrap(); -} - #[allow(clippy::type_complexity)] fn gen_params( opt: Opt, diff --git a/src/main.rs b/src/main.rs index 6f7a616..654c3d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,18 @@ +// Zeitrechnung use chrono::{Duration, NaiveTime}; +// Geometrie-Datenstrukturen und -Algorithmen use geo::prelude::*; +// schnellere Hash-Tabelle use hashbrown::HashMap; +// globale Variablen use lazy_static::lazy_static; +// effizienteres Readers-Writer-Lock use parking_lot::RwLock; +// Implementierung des Dijkstra-Algorithmus use pathfinding::directed::dijkstra::dijkstra; +// R*-Baum-Implementierung use rstar::{RTree, RTreeObject}; +// Kommandozeilen-Argumente einfach einlesen use structopt::StructOpt; use std::cmp; @@ -28,9 +36,6 @@ struct Opt { /// Debug-Modus aktivieren #[structopt(short = "d", long = "debug")] debug: bool, - /// Zwischenlösungen speichern - #[structopt(short = "s", long = "save")] - _save_intermediates: bool, /// Beste Lösung als tkz-Code ausgeben #[structopt(short = "t", long = "tkz")] tkz: bool, @@ -52,9 +57,10 @@ struct RunState { // Bisher zurückgelegte Strecke total_distance: f32, } -/// Trait-Implementierung, damit die Struktur in BinaryHeaps passt +/// Trait-Implementierung, damit die Struktur im Dijkstra-Algorithmus verwendet werden kann impl PartialEq for RunState { fn eq(&self, other: &Self) -> bool { + // nur die Position ist für die Wegfindung relevant self.pos.x() == other.pos.x() && self.pos.y() == other.pos.y() } } @@ -89,7 +95,7 @@ fn main() { // Geschwindigkeiten in m/s umrechnen let bus_speed = opt.bus / 3.6; let lisa_speed = opt.lisa / 3.6; - // Startzeitpunkt berechnen + // Startzeitpunkt berechnen (für eine gegebene Position und Laufstrecke) let calc_time = |total_distance, y| { NaiveTime::from_hms(7, 30, 0) - Duration::seconds(((total_distance * (opt.bus / opt.lisa) - y) / bus_speed) as i64) @@ -103,9 +109,10 @@ fn main() { let data = input::read_input(); let house = data.start; let polys = data.polys; - // Polygone in RTree speichern + // Polygone in R*-Baum speichern let mut rtree = RTREE.write(); *rtree = RTree::bulk_load(polys.clone()); + // Write-Lock aufheben drop(rtree); // alle Ecken der Hindernisse bestimmen let points = polys @@ -136,9 +143,11 @@ fn main() { let mut iterations = 0; let mut time = time::Duration::from_secs(0); - let best = dijkstra( + // Dijkstra-Algorithmus anwenden + let route = dijkstra( &start, |last| { + // ausgehend von einem Zustand die nächsten Zustände/Positionen bestimmen let start = Instant::now(); // neue Zustände let mut all = vec![]; @@ -146,8 +155,6 @@ fn main() { // versuche, zu jeder anderen Ecke zu gehen for &next in points .iter() - // Ecke sollte nicht schon in Route sein - //.filter(|&&next| !state.iter().any(|p| p.pos == next)) // Weg zu dieser Ecke sollte kein Polygon schneiden .filter(|&&next| none_intersect(Line::new(last.pos, next))) { @@ -159,7 +166,7 @@ fn main() { bus, total_distance, }; - //eprintln!("{:?} -> {:?} {:?} {:?}", last.pos, new.pos, last.bus, bus); + // Weg kann nicht besser sein als direkter Weg debug_assert!((last.bus - bus) > -0.01); all.push((new, ((last.bus - bus) * 1000.0) as u32)); } @@ -190,15 +197,14 @@ fn main() { |node| node.pos.x() == 0.0, ) .unwrap().0; - //best.reverse(); + // Performanz-Einschätzung ausgeben if opt.debug { eprintln!("{}us/iteration ({} iterations)", time.as_micros() / iterations, iterations) } // beste Lösung ausgeben eprintln!("Finale Lösung:"); - let route = best; let last = route.last().unwrap(); eprintln!( "Startzeit: {:?}", @@ -238,7 +244,7 @@ fn main() { display::dump_route(opt, house, &polys, &route); } // Prozess direkt beenden, ohne Variablen zu löschen - // (ca. 20% der Laufzeit..) + // (spart ca. 20% der Laufzeit..) std::process::exit(0); } @@ -247,11 +253,11 @@ lazy_static! { // R*-Baum zur Beschleunigung der Routenabschnitt-Hindernis-Überprüfungen static ref RTREE: RwLock> = RwLock::new(RTree::new()); // Cache für die Funktion none_intersect - static ref COLLISIONS: RwLock> = RwLock::new(HashMap::new()); + static ref COLLISIONS: RwLock> = RwLock::new(HashMap::new()); } /// Schneidet die Linie keines der Polygone? -/// (Berührungen erlaubt) +/// (Berührungen sind erlaubt) fn none_intersect(mut line: Line) -> bool { // Linie normalisieren if line.start.y > line.end.y || (line.start.y == line.end.y && line.start.x > line.end.x) { @@ -261,10 +267,10 @@ fn none_intersect(mut line: Line) -> bool { let mut collisions = COLLISIONS.write(); // Liniendaten hashbar machen let id = [ - line.start.x as i16, - line.start.y as i16, - line.end.x as i16, - line.end.y as i16, + line.start.x as i32, + line.start.y as i32, + line.end.x as i32, + line.end.y as i32, ]; // evtl. gefundenes Ergebnis sofort zurückgeben if let Some(&r) = collisions.get(&id) {