diff --git a/.gitignore b/.gitignore index 1764b6f..7d8ef7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk -tmp*.svg \ No newline at end of file +tmp*.svg +*.pdf diff --git a/Cargo.lock b/Cargo.lock index 78f49fe..e51e184 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,7 @@ name = "Aufgabe1" version = "0.1.0" dependencies = [ + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "decorum 0.1.2", "geo 0.11.0", "svg 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -44,6 +45,16 @@ name = "cfg-if" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "chrono" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "decorum" version = "0.1.2" @@ -96,6 +107,14 @@ name = "libc" version = "0.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-integer" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.2.6" @@ -122,6 +141,11 @@ dependencies = [ "proc-macro2 0.4.25 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "redox_syscall" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rstar" version = "0.1.1" @@ -177,6 +201,16 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-xid" version = "0.1.0" @@ -207,13 +241,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749" "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" +"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)" = "48450664a984b25d5b479554c29cc04e3150c97aa4c01da5604a2d4ed9151476" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum pdqselect 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ec91767ecc0a0bbe558ce8c9da33c068066c57ecc8bb8477ef8c1ad3ef77c27" "checksum proc-macro2 0.4.25 (registry+https://github.com/rust-lang/crates.io-index)" = "d3797b7142c9aa74954e351fc089bbee7958cebbff6bf2815e7ffff0b19f547d" "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" +"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" "checksum rstar 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64ffe043929ee67d46694af1a4851f6bbe571b52a55677ba1686222dc35fd449" "checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" "checksum serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "0e732ed5a5592c17d961555e3b552985baf98d50ce418b7b655f31f6ba7eb1b7" @@ -221,6 +258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum svg 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)" = "a863ec1f8e7cfd4ea449f77445cca06aac240b9a677ccf12b0f65ef020db52c7" "checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index 2f6c353..7a6aebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,11 @@ version = "0.1.0" authors = ["arne"] edition = "2018" +[profile.release] +codegen-units = 1 + [dependencies] geo = { path = "/home/arne/Documents/Code/Github/geo/geo" } svg = "0.5.11" decorum = { path = "/home/arne/Documents/Code/Github/decorum" } +chrono = "0.4.6" diff --git a/DokumentationAufgabe1.tex b/DokumentationAufgabe1.tex new file mode 100644 index 0000000..06af360 --- /dev/null +++ b/DokumentationAufgabe1.tex @@ -0,0 +1,157 @@ +\documentclass[a4paper,10pt,ngerman]{scrartcl} +\usepackage{babel} +\usepackage[T1]{fontenc} +\usepackage[a4paper,margin=2.5cm]{geometry} + +% automatische Quotes +\usepackage{csquotes} +\MakeOuterQuote{"} + +% Die nächsten drei Felder bitte anpassen: +\newcommand{\Name}{Arne Keller} % Teamname oder eigenen Namen angeben +\newcommand{\TeilnahmeId}{50966} +\newcommand{\Aufgabe}{Aufgabe 1: Lisa rennt} + +% Kopf- und Fußzeilen +\usepackage{scrlayer-scrpage, lastpage} +\setkomafont{pageheadfoot}{\textrm} +\lohead{\Aufgabe} +\chead{\thepage{}/\pageref{LastPage}} +\rohead{\Name/Teilnahme-Id: \TeilnahmeId} +\cfoot{} + +% Für mathematische Befehle und Symbole +\usepackage{amsmath,amsthm,amssymb} + +% Für Bilder +\usepackage{graphicx,subcaption,float} +\usepackage{tkz-euclide} +\usetkzobj{all} + +% Für Algorithmen +%\usepackage{algpseudocode} + +% Für Quelltext +\usepackage{listings} +\usepackage{color} +\definecolor{mygreen}{rgb}{0,0.6,0} +\definecolor{mygray}{rgb}{0.5,0.5,0.5} +\definecolor{mymauve}{rgb}{0.58,0,0.82} +\lstset{ + keywordstyle=\color{blue},commentstyle=\color{mygreen}, + stringstyle=\color{mymauve},rulecolor=\color{black}, + basicstyle=\footnotesize\ttfamily,numberstyle=\tiny\color{mygray}, + captionpos=b, % sets the caption-position to bottom + keepspaces=true, % keeps spaces in text + numbers=left, numbersep=5pt, showspaces=false,showstringspaces=false, + showtabs=false, stepnumber=2, tabsize=2, title=\lstname +} +\lstdefinelanguage{Rust}{ + keywords={break, match, continue, else, for, fn, if, return, self, let, mut, loop, while, extern, crate, use, static, enum, struct, impl, as, type, derive, mod}, + morecomment=[l]{//}, + morecomment=[s]{/*}{*/}, + morestring=[b]", + sensitive=true +} + +% Diese beiden Pakete müssen als letztes geladen werden +%\usepackage{hyperref} % Anklickbare Links im Dokument +%\usepackage{cleveref} + +% Daten für die Titelseite +\title{\Aufgabe} +\author{\Name\\Teilnahme-ID: \TeilnahmeId} +\date{29. April 2019} + + +\begin{document} +\maketitle +\tableofcontents + +\section{Lösungsidee} +Zunächst kann man feststellen, dass der letzte Abschnitt jeder Route zum Bus immer exakt gerade ist. In einer Kurve würde Lisa weiter laufen, ohne am Ende auf der y-Achse weiter zu sein. Es gibt keinen, einen oder zwei Treffpunkte, bei denen Lisa (ohne zu warten) den Bus erwischt: + +\begin{figure}[H] +\centering +\begin{subfigure}{.33\textwidth} + \centering + \begin{tikzpicture} + \tkzInit[xmax=3.5,ymax=7.5] + \tkzAxeXY + \tkzGrid + \tkzDefPoint(0,0){B} + \tkzDefPoint(1.25,3.5){L} + \tkzDefPoint(0,2.83333){M1} + \tkzDefPoint(0,6.5){M2} + + \tkzDrawSegment(L,M1) + \tkzDrawSegment(L,M2) + \tkzDrawPoints(B,L,M1,M2) + + \tkzLabelPoints(B,L,M1,M2) + \end{tikzpicture} + \caption{Zwei Treffpunkte} + \label{abb:fall1} +\end{subfigure}% +\begin{subfigure}{.33\textwidth} + \centering + \begin{tikzpicture} + \tkzInit[xmax=3.5,ymax=7.5] + \tkzAxeXY + \tkzGrid + \tkzDefPoint(0,0){B} + \tkzDefPoint(2.020725942,3.5){L} + \tkzDefPoint(0,4.614){M} + + \tkzDrawSegment(L,M) + \tkzDrawPoints(B,L,M) + + \tkzLabelPoints[yshift=14pt](M) + \tkzLabelPoints(B,L) + \end{tikzpicture} + \caption{Ein Treffpunkt} + \label{abb:fall1} +\end{subfigure}% +\begin{subfigure}{.33\textwidth} + \centering + \begin{tikzpicture} + \tkzInit[xmax=3.5,ymax=7.5] + \tkzAxeXY + \tkzGrid + \tkzDefPoint(0,0){B} + \tkzDefPoint(3,3.5){L} + + \tkzDrawPoints(B,L) + + \tkzLabelPoints(B,L) + \end{tikzpicture} + \caption{Kein Treffpunkt} + \label{abb:fall1} +\end{subfigure} +\caption{Mögliche Treffpunktanzahlen} +\label{fig:treffpunkte} +\end{figure} + +\begin{proof} + Sei Lisa bei $L(x_{L},y_{L})$ und der Bus bei $B(x_{B},y_{B})$. $M(x_{M},y_{M})$ sei der Treffpunkt von Lisa mit dem Bus. $d$ sei der Weg, den Lisa zu diesem Punkt geht. + \begin{align*} + d &= \sqrt{(x_{L}-x_{M})^2 + (y_{L}-y_{M})^2} &&\text{(Satz des Pythagoras)} \\ + &= \sqrt{x_{L}^2 + (y_{L}-y_{M})^2} &&\text{($x_{B} = x_{M} = 0$)} \\ + d^2 &= x_{L}^2 + (y_{L}-y_{M})^2 \\ + &= x_{L}^2 + (y_{L}-y_{B}-2d)^2 &&\text{($y_{M} = y_{B} + 2d$)} \\ + &= x_{L}^2 + y_{L}^2 + y_{B}^2 + 4d^2 - 2y_{B}y_{M} - 4dy_{L} + 4dy_{B} \\ + -3d^2 + 4\cdot(y_{B}-y_{L})\cdot{}d &= x_{L}^2 + y_{L}^2 + y_{B}^2 - 2y_{B}y_{M} \\ + d &= \frac{a\pm\sqrt{a^2 + 12\cdot{}(-x_{L}^2 - y_{L}^2 - y_{B}^2 + 2y_{B}y_{M})}}{-6} &&\text{($a = 4\cdot(y_{B}-y_{L})$)}\qedhere + \end{align*} +\end{proof} + +Für diese Gleichung gibt es maximal zwei nicht-negative Lösungen. + +\section{Umsetzung} + +\section{Beispiele} + +\section{Quellcode} +\lstinputlisting[frame=single,language=Rust,breaklines=true]{src/main.rs} + +\end{document} diff --git a/src/display.rs b/src/display.rs index 56e004b..ed40ae9 100644 --- a/src/display.rs +++ b/src/display.rs @@ -4,17 +4,49 @@ use svg::node::element::{AnimateMotion, Circle, MotionPath}; use svg::Document; use svg::Node; -use std::io; +use std::fs; use super::*; -pub(crate) fn dump_svg( +pub(crate) fn dump_route(house: Point, polys: &[Polygon], route: &Vec) { + let (points, lines, route1, route2_start) = gen_params(house, route); + print!("{}", generate_svg(&points, &lines, polys, &route1, route2_start)); +} + +pub(crate) fn save_svg(filename: &str, house: Point, polys: &[Polygon], route: &Vec) { + let (points, lines, route1, route2_start) = gen_params(house, route); + fs::write(filename, generate_svg(&points, &lines, polys, &route1, route2_start)).unwrap(); +} + +fn gen_params(house: Point, route: &Vec) -> (Vec<(Point, &'static str)>, Vec<(Line, &'static str)>, Vec, Point) { + let first = route.first().unwrap(); + let last = route.last().unwrap(); + let best_delay = last.delay; + let points = vec![ + (house, "red"), + (first.bus.translate(0.0.into(), best_delay), "yellow"), + (last.bus, "orange"), + ]; + let lines = route + .iter() + .map(|x| x.pos) + .collect::() + .lines() + .map(|x| (x, "gray")) + .collect::>(); + + let route1 = route.iter().map(|x| x.pos).collect::>(); + let route2_start = first.bus.translate(0.0.into(), best_delay); + (points, lines, route1, route2_start) +} + +pub(crate) fn generate_svg( points: &[(Point, &'static str)], lines: &[(Line, &'static str)], polys: &[Polygon], route1: &[Point], route2_start: Point, -) { +) -> String { let mut document = Document::new() // view box at (0, -200), w*h (200, 200) .set("viewBox", (0, -1100, 2100, 2100)) @@ -125,5 +157,5 @@ pub(crate) fn dump_svg( dot1.append(motion1); document.append(dot1); - svg::write(io::stdout(), &document).unwrap(); + document.to_string() } diff --git a/src/main.rs b/src/main.rs index 0f9bc62..27889ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,25 @@ use geo::prelude::*; +use chrono::{Duration, NaiveTime}; + use std::cmp; use std::collections::BinaryHeap; mod display; mod input; -type Point = geo::Point; -type Line = geo::Line; -type LineString = geo::LineString; -type Polygon = geo::Polygon; +type Point = geo::Point; +type Line = geo::Line; +type LineString = geo::LineString; +type Polygon = geo::Polygon; -// 30 km/h = 1 units/unit -// 15 km/h = 0.5 units/unit +// 30 km/h = 8+1/3 meters/second +// 15 km/h = 4+1/6 meters/second #[derive(Debug, Clone, PartialEq)] struct RunState { /// Theoretically possible maximum delay - delay: f32, + delay: f64, /// Our current location pos: Point, /// Current location of the bus, not including any delays @@ -36,7 +38,11 @@ impl cmp::PartialOrd for RunState { } fn main() { - let bus = Point::new(0.0, -2000.0); + // starting at 07:00, bus is 15 km away + let start_time = NaiveTime::from_hms(7, 0, 0); + let bus = Point::new(0.0, -15000.0); + let bus_speed = 30.0 / 3.6; + let delay_to_time = |delay| start_time + Duration::seconds((delay / bus_speed) as i64); let data = input::read_input(); let house = data.start; @@ -58,25 +64,27 @@ fn main() { delay: max_possible_delay(bus, house), }; - eprintln!("# Max. delay possible: {:?}", start.delay); + eprintln!("# Max. delay possible: {:?}", delay_to_time(start.delay)); let mut states = BinaryHeap::new(); states.push(vec![start]); let mut best_delay = 0.0; let mut best = vec![]; + let save_prefix = "tmp3_"; + let mut save_counter = 0; while states.peek().map(|x| x[0].delay > best_delay) == Some(true) { - eprintln!(". {:?} states left:", states.len()); + //eprintln!(". {:?} states left:", states.len()); let s = states.pop().unwrap(); let last = &s[0]; //s.last().unwrap(); - eprintln!("{},{}", last.pos.x(), last.pos.y()); + //eprintln!("{},{}", last.pos.x(), last.pos.y()); // new states let mut all = vec![]; // attempt to go to any other vertex/point - for next in &points { + for next in points.iter().filter(|next| !s.iter().any(|x| x.pos == (**next).into())) { let next = Point::from(*next); if next != last.pos && none_intersect(&polys, &Line::new(last.pos, next)) { // could run to that point @@ -94,82 +102,58 @@ fn main() { all.push(route); } } - // attempt to go to the bus, with varying delays - let mut bus_reached = false; - for delay in float_range(last.delay.into(), 0.0) { - let bus = last.bus.translate(0.0.into(), delay); - let range = to_bus(bus, last.pos); - if range.len() == 2 { - bus_reached = true; // TODO: what if effectively unreachable? - let range = Line::new(range[0], range[1]); - for percent in float_range(0.0, 1.0) { - let mut next = range.start; - next.x += range.dx() * percent; - next.y += range.dy() * percent; - let next = Point::from(next); - if none_intersect(&polys, &Line::new(last.pos, next)) { - if delay > best_delay { - // new high score! - let mut route = s.clone(); - route.insert( - 0, - RunState { - pos: next, - bus: next, - delay, - }, - ); - eprintln!("# New best delay {:?}", delay); - best = route; - best.reverse(); - best_delay = delay; - } else { - // not worth it - } + // attempt to go to the bus + let bus = last.bus; + let range = to_bus(bus, last.pos); + if range.len() == 2 { + let range = Line::new(range[0], range[1]); + for percent in float_range(0.0, 1.0) { + let mut next = range.start; + next.x += range.dx() * percent; + next.y += range.dy() * percent; + let next = Point::from(next); + let line = Line::new(last.pos, next); + if none_intersect(&polys, &line) { + let delay = line.end.y - bus.y() - line.end_point().euclidean_distance(&line.start_point()) * 2.0; + if delay > best_delay { + // new high score! + let mut route = s.clone(); + route.insert( + 0, + RunState { + pos: next, + bus: next, + delay, + }, + ); + eprintln!("# New best delay {:?} ({:?} states left)", delay_to_time(delay), states.len()); + best = route; + best.reverse(); + best_delay = delay; + display::save_svg(&format!("{}{}.svg", save_prefix, save_counter), house, &polys, &best); + save_counter += 1; + } else { + // not worth it } } } - } - if bus_reached { - //eprintln!("-> adding all to queue"); states.extend(all); } } - eprintln!("d = {:?}", best_delay); + eprintln!("d = {:?}", delay_to_time(best_delay)); let route = best; eprintln!("Route:"); for s in &route { eprintln!("{:.02},{:.02}", s.pos.x(), s.pos.y()); } - let first = route.first().unwrap(); - let last = route.last().unwrap(); - let points = vec![ - (house, "red"), - (first.bus.translate(0.0.into(), best_delay), "yellow"), - (last.bus, "orange"), - ]; - let lines = route - .iter() - .map(|x| x.pos) - .collect::() - .lines() - .map(|x| (x, "gray")) - .collect::>(); - - display::dump_svg( - &points, - &lines, - &polys, - &route.iter().map(|x| x.pos).collect::>(), - first.bus.translate(0.0.into(), best_delay), - ); + display::dump_route(house, &polys, &route); } /// [a; b] -fn float_range(a: f32, b: f32) -> impl Iterator { - const STEPS: usize = 25; +fn float_range(a: f64, b: f64) -> impl Iterator { + const STEPS: usize = 1000; let d = b - a; - (0..=STEPS).map(move |s| a + ((s as f32) * d) / STEPS as f32) + (0..=STEPS).map(move |s| a + ((s as f64) * d) / STEPS as f64) } fn none_intersect(polys: &[Polygon], line: &Line) -> bool { @@ -236,11 +220,11 @@ fn none_intersect(polys: &[Polygon], line: &Line) -> bool { true } -fn distance(a: Point, b: Point) -> f32 { +fn distance(a: Point, b: Point) -> f64 { ((a.x() - b.x()).powi(2) + (a.y() - b.y()).powi(2)).sqrt() } -fn max_possible_delay(bus: Point, start: Point) -> f32 { +fn max_possible_delay(bus: Point, start: Point) -> f64 { let a = start.x(); let b = start.y(); let c = bus.x();