From 03d17f617419930e7759c679862c7ec830a1d712 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 5 Apr 2018 15:38:32 -0600 Subject: [PATCH] finish physics simulation extraction --- Chapter03/Cargo.toml | 8 + Chapter03/src/lib.rs | 284 +++++++++++++++++++++++++++ Chapter03/src/main.rs | 6 + Chapter03/src/physics.rs | 105 ++++++++++ Chapter03/test1.txt | 9 + Chapter03/tests/integration_tests.rs | 10 + 6 files changed, 422 insertions(+) create mode 100644 Chapter03/Cargo.toml create mode 100644 Chapter03/src/lib.rs create mode 100644 Chapter03/src/main.rs create mode 100644 Chapter03/src/physics.rs create mode 100644 Chapter03/test1.txt create mode 100644 Chapter03/tests/integration_tests.rs diff --git a/Chapter03/Cargo.toml b/Chapter03/Cargo.toml new file mode 100644 index 0000000..a79cb18 --- /dev/null +++ b/Chapter03/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "elevator" +version = "1.0.0" + +[dependencies] +floating-duration = "0.1.2" +termion = "1.0" +timebomb = "0.1" diff --git a/Chapter03/src/lib.rs b/Chapter03/src/lib.rs new file mode 100644 index 0000000..4ec5f97 --- /dev/null +++ b/Chapter03/src/lib.rs @@ -0,0 +1,284 @@ +mod physics; + +extern crate floating_duration; +use std::time::Instant; +use floating_duration::{TimeAsFloat, TimeFormat}; +use std::time::SystemTime; +use std::{thread, time}; +use std::env; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::io::prelude::*; +use std::process; +extern crate termion; +use termion::{clear, cursor, style}; +use termion::raw; +use termion::raw::IntoRawMode; +use termion::input::TermRead; +use termion::event::Key; +use std::cmp; + +fn variable_summary(stdout: &mut raw::RawTerminal, vname: String, data: Vec) { + let (avg, dev) = variable_summary_stats(data); + variable_summary_print(stdout, vname, avg, dev); +} + +fn variable_summary_stats(data: Vec) -> (f64, f64) +{ + //calculate statistics + let N = data.len(); + let sum = data.clone().into_iter() + .fold(0.0, |a, b| a+b); + let avg = sum / (N as f64); + let dev = ( + data.clone().into_iter() + .map(|v| (v - avg).powi(2)) + .fold(0.0, |a, b| a+b) + / (N as f64) + ).sqrt(); + (avg, dev) +} + +fn variable_summary_print(stdout: &mut raw::RawTerminal, vname: String, avg: f64, dev: f64) +{ + //print formatted output + write!(stdout, "Average of {:25}{:.6}\r\n", vname, avg); + write!(stdout, "Standard deviation of {:14}{:.6}\r\n", vname, dev); + write!(stdout, "\r\n"); +} + +pub fn run_simulation() +{ + + //1. Store location, velocity, and acceleration state + let mut location: f64 = 0.0; // meters + let mut velocity: f64 = 0.0; // meters per second + let mut acceleration: f64 = 0.0; // meters per second squared + + //2. Store motor input voltage + let mut up_input_voltage: f64 = 0.0; + let mut down_input_voltage: f64 = 0.0; + + //3. Store input building description and floor requests + let mut floor_count: u64 = 0; + let mut floor_height: f64 = 0.0; // meters + let mut floor_requests: Vec = Vec::new(); + + //4. Parse input and store as building description and floor requests + match env::args().nth(1) { + Some(ref fp) if *fp == "-".to_string() => { + let mut buffer = String::new(); + io::stdin().read_to_string(&mut buffer) + .expect("read_to_string failed"); + + for (li,l) in buffer.lines().enumerate() { + if li==0 { + floor_count = l.parse::().unwrap(); + } else if li==1 { + floor_height = l.parse::().unwrap(); + } else { + floor_requests.push(l.parse::().unwrap()); + } + } + }, + None => { + let fp = "test1.txt"; + let mut buffer = String::new(); + File::open(fp) + .expect("File::open failed") + .read_to_string(&mut buffer) + .expect("read_to_string failed"); + + for (li,l) in buffer.lines().enumerate() { + if li==0 { + floor_count = l.parse::().unwrap(); + } else if li==1 { + floor_height = l.parse::().unwrap(); + } else { + floor_requests.push(l.parse::().unwrap()); + } + } + }, + Some(fp) => { + let mut buffer = String::new(); + File::open(fp) + .expect("File::open failed") + .read_to_string(&mut buffer) + .expect("read_to_string failed"); + + for (li,l) in buffer.lines().enumerate() { + if li==0 { + floor_count = l.parse::().unwrap(); + } else if li==1 { + floor_height = l.parse::().unwrap(); + } else { + floor_requests.push(l.parse::().unwrap()); + } + } + } + } + + //5. Loop while there are remaining floor requests + let mut prev_loop_time = Instant::now(); + let termsize = termion::terminal_size().ok(); + let termwidth = termsize.map(|(w,_)| w-2).expect("termwidth") as u64; + let termheight = termsize.map(|(_,h)| h-2).expect("termheight") as u64; + let mut stdout = io::stdout().into_raw_mode().unwrap(); + let mut record_location = Vec::new(); + let mut record_velocity = Vec::new(); + let mut record_acceleration = Vec::new(); + let mut record_voltage = Vec::new(); + + while floor_requests.len() > 0 + { + //5.1. Update location, velocity, and acceleration + let now = Instant::now(); + let dt = now.duration_since(prev_loop_time) + .as_fractional_secs(); + prev_loop_time = now; + + record_location.push(location); + record_velocity.push(velocity); + record_acceleration.push(acceleration); + record_voltage.push(up_input_voltage-down_input_voltage); + + location = location + velocity * dt; + velocity = velocity + acceleration * dt; + acceleration = { + let F = (up_input_voltage - down_input_voltage) * 8.0; + let m = 1200000.0; + -9.8 + F/m + }; + + //5.2. If next floor request in queue is satisfied, then remove from queue + let next_floor = floor_requests[0]; + if (location - (next_floor as f64)*floor_height).abs() < 0.01 && + velocity.abs() < 0.01 + { + velocity = 0.0; + floor_requests.remove(0); + } + + //5.3. Adjust motor control to process next floor request + + //it will take t seconds to decelerate from velocity v at -1 m/s^2 + let t = velocity.abs() / 1.0; + + //during which time, the carriage will travel d=t * v/2 meters + //at an average velocity of v/2 before stopping + let d = t * (velocity/2.0); + + //l = distance to next floor + let l = (location - (next_floor as f64)*floor_height).abs(); + + let target_acceleration = { + //are we going up? + let going_up = location < (next_floor as f64)*floor_height; + + //Do not exceed maximum velocity + if velocity.abs() >= 5.0 { + if going_up==(velocity>0.0) { + 0.0 + //decelerate if going in wrong direction + } else if going_up { + 1.0 + } else { + -1.0 + } + + //if within comfortable deceleration range and moving in right direction, decelerate + } else if l < d && going_up==(velocity>0.0) { + if going_up { + -1.0 + } else { + 1.0 + } + + //else if not at peak velocity, accelerate + } else { + if going_up { + 1.0 + } else { + -1.0 + } + } + }; + + let gravity_adjusted_acceleration = target_acceleration + 9.8; + let target_force = gravity_adjusted_acceleration * 1200000.0; + let target_voltage = target_force / 8.0; + if target_voltage > 0.0 { + up_input_voltage = target_voltage; + down_input_voltage = 0.0; + } else { + up_input_voltage = 0.0; + down_input_voltage = target_voltage.abs(); + }; + + //5.4. Print realtime statistics + print!("{}{}{}", clear::All, cursor::Goto(1, 1), cursor::Hide); + let carriage_floor = (location / floor_height).floor(); + let carriage_floor = if carriage_floor < 1.0 { 0 } else { carriage_floor as u64 }; + let carriage_floor = cmp::min(carriage_floor, floor_count-1); + let mut terminal_buffer = vec![' ' as u8; (termwidth*termheight) as usize]; + for ty in 0..floor_count + { + terminal_buffer[ (ty*termwidth + 0) as usize ] = '[' as u8; + terminal_buffer[ (ty*termwidth + 1) as usize ] = + if (ty as u64)==((floor_count-1)-carriage_floor) { 'X' as u8 } + else { ' ' as u8 }; + terminal_buffer[ (ty*termwidth + 2) as usize ] = ']' as u8; + terminal_buffer[ (ty*termwidth + termwidth-2) as usize ] = '\r' as u8; + terminal_buffer[ (ty*termwidth + termwidth-1) as usize ] = '\n' as u8; + } + let stats = vec![ + format!("Carriage at floor {}", carriage_floor+1), + format!("Location {:.06}", location), + format!("Velocity {:.06}", velocity), + format!("Acceleration {:.06}", acceleration), + format!("Voltage [up-down] {:.06}", up_input_voltage-down_input_voltage), + ]; + for sy in 0..stats.len() + { + for (sx,sc) in stats[sy].chars().enumerate() + { + terminal_buffer[ sy*(termwidth as usize) + 6 + sx ] = sc as u8; + } + } + write!(stdout, "{}", String::from_utf8(terminal_buffer).ok().unwrap()); + stdout.flush().unwrap(); + + thread::sleep(time::Duration::from_millis(10)); + } + + //6 Calculate and print summary statistics + write!(stdout, "{}{}{}", clear::All, cursor::Goto(1, 1), cursor::Show).unwrap(); + variable_summary(&mut stdout, "location".to_string(), record_location); + variable_summary(&mut stdout, "velocity".to_string(), record_velocity); + variable_summary(&mut stdout, "acceleration".to_string(), record_acceleration); + variable_summary(&mut stdout, "voltage".to_string(), record_voltage); + stdout.flush().unwrap(); + +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn variable_stats() { + let test_data = vec![ + (vec![1.0, 2.0, 3.0, 4.0, 5.0], 3.0, 1.41), + (vec![1.0, 3.0, 5.0, 7.0, 9.0], 5.0, 2.83), + (vec![1.0, 9.0, 1.0, 9.0, 1.0], 4.2, 3.92), + (vec![1.0, 0.5, 0.7, 0.9, 0.6], 0.74, 0.19), + (vec![200.0, 3.0, 24.0, 92.0, 111.0], 86.0, 69.84), + ]; + for (data, avg, dev) in test_data + { + let (ravg, rdev) = variable_summary_stats(data); + assert!( (avg-ravg).abs() < 0.1 ); + assert!( (dev-rdev).abs() < 0.1 ); + } + } +} diff --git a/Chapter03/src/main.rs b/Chapter03/src/main.rs new file mode 100644 index 0000000..b346f2b --- /dev/null +++ b/Chapter03/src/main.rs @@ -0,0 +1,6 @@ +extern crate elevator; + +fn main() +{ + elevator::run_simulation(); +} diff --git a/Chapter03/src/physics.rs b/Chapter03/src/physics.rs new file mode 100644 index 0000000..53ca04b --- /dev/null +++ b/Chapter03/src/physics.rs @@ -0,0 +1,105 @@ +extern crate floating_duration; +use std::time::Instant; +use floating_duration::{TimeAsFloat, TimeFormat}; +use std::{thread, time}; + +#[derive(Clone)] +pub enum MotorInput +{ + Up { voltage: f64 }, + Down { voltage: f64 } +} + +#[derive(Clone)] +pub struct ElevatorSpecification +{ + floor_count: u64, + floor_height: f64, + carriage_weight: f64 +} + +#[derive(Clone)] +pub struct ElevatorState +{ + timestamp: Instant, + location: f64, + velocity: f64, + acceleration: f64, + motor_input: MotorInput +} + +pub type FloorRequests = Vec; + +pub trait MotorController +{ + fn init(&mut self, esp: ElevatorSpecification, est: ElevatorState); + fn poll(&mut self, est: ElevatorState, dst: u64) -> MotorInput; +} + +pub trait DataRecorder +{ + fn init(&mut self, esp: ElevatorSpecification, est: ElevatorState); + fn poll(&mut self, est: ElevatorState, dst: u64); +} + +pub trait MotorForce { + fn calculate_force(&self) -> f64; +} +impl MotorForce for MotorInput { + fn calculate_force(&self) -> f64 + { + match *self { + MotorInput::Up { voltage: v } => { v * 8.0 } + MotorInput::Down { voltage: v } => { v * -8.0 } + } + } +} + +pub fn simulate_elevator(esp: ElevatorSpecification, est: ElevatorState, req: FloorRequests, + mc: &mut MC, dr: &mut DR) { + + //immutable input becomes mutable local state + let mut esp = esp.clone(); + let mut est = est.clone(); + let mut req = req.clone(); + + //initialize MotorController and DataController + mc.init(esp.clone(), est.clone()); + dr.init(esp.clone(), est.clone()); + + //5. Loop while there are remaining floor requests + while req.len() > 0 + { + //5.1. Update location, velocity, and acceleration + let now = Instant::now(); + let dt = now.duration_since(est.timestamp) + .as_fractional_secs(); + est.timestamp = now; + + + est.location = est.location + est.velocity * dt; + est.velocity = est.velocity + est.acceleration * dt; + est.acceleration = { + let F = est.motor_input.calculate_force(); + let m = 1200000.0; + -9.8 + F/m + }; + + //5.2. If next floor request in queue is satisfied, then remove from queue + let next_floor = req[0]; + if (est.location - (next_floor as f64)*esp.floor_height).abs() < 0.01 && + est.velocity.abs() < 0.01 + { + est.velocity = 0.0; + req.remove(0); + } + + //5.3. Adjust motor control to process next floor request + mc.poll(est.clone(), next_floor); + + //5.4. Print realtime statistics + dr.poll(est.clone(), next_floor); + + thread::sleep(time::Duration::from_millis(10)); + } +} diff --git a/Chapter03/test1.txt b/Chapter03/test1.txt new file mode 100644 index 0000000..cd1734c --- /dev/null +++ b/Chapter03/test1.txt @@ -0,0 +1,9 @@ +5 +5.67 +2 +1 +4 +0 +3 +1 +0 diff --git a/Chapter03/tests/integration_tests.rs b/Chapter03/tests/integration_tests.rs new file mode 100644 index 0000000..9dd2128 --- /dev/null +++ b/Chapter03/tests/integration_tests.rs @@ -0,0 +1,10 @@ +extern crate elevator; +extern crate timebomb; +use timebomb::timeout_ms; + +#[test] +fn test_main() { + timeout_ms(|| { + elevator::run_simulation(); + }, 300000); +}