Fancy output; initial documentation

This commit is contained in:
Arne Keller 2019-01-30 15:38:04 +01:00
parent 460e0749ad
commit d97e51c3bc
6 changed files with 296 additions and 80 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
**/*.rs.bk
tmp*.svg
tmp*.svg
*.pdf

38
Cargo.lock generated
View File

@ -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"

View File

@ -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"

157
DokumentationAufgabe1.tex Normal file
View File

@ -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}

View File

@ -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<RunState>) {
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<RunState>) {
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<RunState>) -> (Vec<(Point, &'static str)>, Vec<(Line, &'static str)>, Vec<Point>, 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::<LineString>()
.lines()
.map(|x| (x, "gray"))
.collect::<Vec<_>>();
let route1 = route.iter().map(|x| x.pos).collect::<Vec<_>>();
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()
}

View File

@ -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<f32>;
type Line = geo::Line<f32>;
type LineString = geo::LineString<f32>;
type Polygon = geo::Polygon<f32>;
type Point = geo::Point<f64>;
type Line = geo::Line<f64>;
type LineString = geo::LineString<f64>;
type Polygon = geo::Polygon<f64>;
// 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::<LineString>()
.lines()
.map(|x| (x, "gray"))
.collect::<Vec<_>>();
display::dump_svg(
&points,
&lines,
&polys,
&route.iter().map(|x| x.pos).collect::<Vec<_>>(),
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<Item = f32> {
const STEPS: usize = 25;
fn float_range(a: f64, b: f64) -> impl Iterator<Item = f64> {
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();