elevate_lib/
building.rs

1//Import external/standard modules
2use rand::Rng;
3use rand::distributions::Distribution;
4use statrs::distribution::{Poisson, Binomial};
5
6//Import source modules
7use crate::person::Person;
8use crate::people::People;
9use crate::floor::Floor;
10use crate::floors::Floors;
11use crate::elevator::Elevator;
12use crate::elevators::Elevators;
13
14//Constant representing the probability a person leaves the building during a time step
15const P_OUT: f64 = 0.05_f64;
16
17//Constant representing the probability a person leaves a tip
18const P_TIP: f64 = 0.5_f64;
19
20//Constants defining the Bernoulli distribution parameters to sample from to generate tips
21const DST_TIP_TRIALS: u64 = 100_u64;
22const DST_TIP_SUCCESS: f64 = 0.5_f64;
23
24/// # `Building` struct
25///
26/// A `Building` aggregates `Elevator`s and `Floor`s.  It also tracks the everage
27/// energy usage by the elevators, and the average wait time among the people on
28/// the building's floors and elevators.  It randomly generates arrivals.
29#[derive(Clone)]
30pub struct Building {
31    pub elevators: Vec<Elevator>,
32    pub floors: Vec<Floor>,
33    pub avg_energy: f64,
34    pub avg_wait_time: f64,
35    pub tot_tips: f64,
36    wait_time_denom: usize,
37    p_in: f64,
38    dst_in: Poisson,
39    dst_tip: Binomial
40}
41
42/// # `Building` type implementation
43///
44/// The following functions are used to control the behavior of the people and elevators
45/// owned by the `Building` type.
46impl Building {
47    /// Initialize a new building given the number of floors, the number of elevators
48    /// the expected number of arrivals per time step, the base energy spent while moving
49    /// an elevator up and down, and the additional energy spent per person moved.
50    ///
51    /// ## Example
52    ///
53    /// ```
54    /// let num_floors: usize = 4_usize;
55    /// let num_elevators: usize = 2_usize;
56    /// let p_in: f64 = 0.5_f64;
57    /// let energy_up: f64 = 5.0_f64;
58    /// let energy_down: f64 = 2.5_f64;
59    /// let energy_coef: f64 = 0.5_f64;
60    /// let my_building: Building = Building::from(
61    ///     num_floors,
62    ///     num_elevators,
63    ///     p_in,
64    ///     energy_up,
65    ///     energy_down,
66    ///     energy_coef
67    /// );
68    /// ```
69    pub fn from(num_floors: usize, num_elevators: usize, p_in: f64, energy_up: f64,
70                energy_down: f64, energy_coef: f64) -> Building {
71        //Initialize the Floors
72        let floors: Vec<Floor> = {
73            let mut tmp_floors: Vec<Floor> = Vec::new();
74            for _ in 0_usize..num_floors {
75                let tmp_floor: Floor = Floor::new();
76                tmp_floors.push(tmp_floor);
77            }
78            tmp_floors
79        };
80    
81        //Initialize the Elevators
82        let elevators: Vec<Elevator> = {
83            let mut tmp_elevators: Vec<Elevator> = Vec::new();
84            for _ in 0_usize..num_elevators {
85                let tmp_elevator: Elevator = Elevator::from(
86                    energy_up, energy_down, energy_coef
87                );
88                tmp_elevators.push(tmp_elevator);
89            }
90            tmp_elevators
91        };
92    
93        //Initialize the arrival probability distribution
94        let dst_in = Poisson::new(p_in).unwrap();
95    
96        //Initialize and return the Building
97        Building {
98            floors: floors,
99            elevators: elevators,
100            avg_energy: 0_f64,
101            avg_wait_time: 0_f64,
102            wait_time_denom: 0_usize,
103            tot_tips: 0_f64,
104            p_in: p_in,
105            dst_in: dst_in,
106            dst_tip: Binomial::new(DST_TIP_SUCCESS, DST_TIP_TRIALS).unwrap()
107        }
108    }
109
110    /// Calculate the probability that each floor becomes a destination floor for an elevator
111    /// during the next time step.  If the floor currently is a destination floor, then return
112    /// `1_f64`.
113    pub fn update_dest_probabilities(&mut self) {
114        //Get the number of floors in the building
115        let num_floors: usize = self.floors.len() as usize;
116
117        //Get the destination floors across each elevator
118        let dest_floors: Vec<usize> = self.elevators.get_dest_floors();
119
120        //Loop through the floors
121        for (i, floor) in self.floors.iter_mut().enumerate() {
122            //Initialize an f64 for this floor's probability
123            let dest_probability: f64 = if i == 0 {
124                //If this is the first floor, then calculate the prob
125                //based on arrival probability only
126                let people_waiting: f64 = {
127                    let waiting: f64 = if floor.are_people_waiting() { 1_f64 } else { 0_f64 };
128                    let going: f64 = if dest_floors.contains(&i) { 1_f64 } else { 0_f64 };
129                    if waiting > going { waiting } else { going }
130                };
131                let p_in: f64 = self.p_in * ((num_floors as f64 - 1_f64)/(num_floors as f64));
132                if people_waiting > p_in { people_waiting } else { p_in }
133            } else {
134                //If this is not the first floor, then calculate the
135                //prob based on the elevator's people and the floor's
136                //people and append it to the list
137                let people_waiting: f64 = {
138                    let waiting: f64 = if floor.are_people_waiting() { 1_f64 } else { 0_f64 };
139                    let going: f64 = if dest_floors.contains(&i) { 1_f64 } else { 0_f64 };
140                    if waiting > going { waiting } else { going }
141                };
142                let p_out: f64 = floor.get_p_out();
143                if people_waiting > p_out { people_waiting } else { p_out }
144            };
145            floor.dest_prob = dest_probability;
146        }
147    }
148
149    /// Generate the people arriving by sampling the Poisson distribution to receive a number
150    /// of arrivals, and then instantiate that many people and append them to the first floor.
151    pub fn gen_people_arriving(&mut self, mut rng: &mut impl Rng) {
152        //Initialize a vector of Persons
153        let mut arrivals: Vec<Person> = Vec::new();
154
155        //Loop until no new arrivals occur, for each arrival append a new person
156        for _ in 0_i32..self.dst_in.sample(&mut rng) as i32 {
157            let new_person: Person = Person::from(P_OUT, P_TIP, self.floors.len(), &mut rng);
158            arrivals.push(new_person);
159        }
160
161        //Extend the first floor with the new arrivals
162        self.floors[0].extend(arrivals);
163    }
164
165    /// Given the number of people who decided to tip, generate the total value of their tips
166    pub fn gen_tip_value(&self, num_tips: usize, rng: &mut impl Rng) -> f64 {
167        //Initialize a float to store the tip value
168        let mut tip_value: f64 = 0.0_f64;
169
170        //Sample the tip distribution for each tip, randomizing the value of the tip
171        //according to the tip distribution
172        for _ in 0..num_tips {
173            tip_value += self.dst_tip.sample(rng) / 100_f64;
174        }
175
176        //Return the total tip value
177        tip_value
178    }
179
180    /// For each of the building's elevators, exchange people between the elevator and its
181    /// current floor if anyone on the elevator is going to the current floor, or if anyone on
182    /// the floor is waiting for the elevator.
183    pub fn exchange_people_on_elevator(&mut self) {
184        for elevator in self.elevators.iter_mut() {
185            //If the elevator is not stopped then continue
186            if !elevator.stopped {
187                continue;
188            }
189
190            //Get the elevator's floor index
191            let floor_index: usize = elevator.floor_on;
192
193            //Move people off the floor and off the elevator
194            let people_leaving_floor: Vec<Person> = self.floors[floor_index].flush_people_entering_elevator();
195            let mut people_leaving_elevator: Vec<Person> = elevator.flush_people_leaving_elevator();
196
197            //Aggregate the wait times of the people leaving the elevator into the average and reset
198            let wait_times: usize = people_leaving_elevator.get_aggregate_wait_time();
199            let num_people: usize = people_leaving_elevator.get_num_people();
200            self.avg_wait_time = {
201                let tmp_num: f64 = wait_times as f64 + (self.avg_wait_time * self.wait_time_denom as f64);
202                let tmp_denom: f64 = num_people as f64 + self.wait_time_denom as f64;
203                if tmp_denom == 0_f64 {
204                    0_f64 //If the denominator is 0, return 0 to avoid NaNs
205                } else {
206                    tmp_num / tmp_denom
207                }
208            };
209            self.wait_time_denom += num_people;
210            people_leaving_elevator.reset_wait_times();
211
212            //Extend the current floor and elevator with the people getting on and off
213            elevator.extend(people_leaving_floor);
214            self.floors[floor_index].extend(people_leaving_elevator);
215        }
216    }
217
218    /// Removes anyone who is leaving the first floor, and generates tips
219    pub fn flush_and_update_tips(&mut self, rng: &mut impl Rng) {
220        let people_leaving_floor: Vec<Person> = self.floors.flush_first_floor();
221        let num_tips: usize = people_leaving_floor.gen_num_tips(rng);
222        let tip_value: f64 = self.gen_tip_value(num_tips, rng);
223        self.tot_tips += tip_value;
224    }
225
226    /// Returns all tips collected by the building, and resets the total tips to 0
227    pub fn collect_tips(&mut self) -> f64 {
228        let tips: f64 = self.tot_tips;
229        self.tot_tips = 0.0_f64;
230        tips
231    }
232
233    /// Update the average energy spent by the building's elevators given the time
234    /// step and the energy spent during the time step.
235    pub fn update_average_energy(&mut self, time_step: i32, energy_spent: f64) {
236        self.avg_energy = {
237            let tmp_num: f64 = (self.avg_energy * time_step as f64) + energy_spent;
238            let tmp_denom: f64 = (time_step + 1_i32) as f64;
239            tmp_num / tmp_denom
240        };
241    }
242
243    /// Append a new elevator to the building
244    pub fn append_elevator(&mut self, energy_up: f64, energy_down: f64, energy_coef: f64) {
245        self.elevators.push(Elevator::from(energy_up, energy_down, energy_coef));
246    }
247}
248
249//Display trait implementation for a building
250impl std::fmt::Display for Building {
251    /// Format a `Building` as a string.
252    ///
253    /// ### Example
254    ///
255    /// ```
256    /// println!("{}", my_building);
257    /// ```
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        let mut building_status: String = String::new();
260        let elevator_space: String = String::from("   \t ");
261        for (i, floor) in self.floors.iter().enumerate() {
262            //Initialize strings representing this floor
263            let mut floor_roof: String = String::from("----\t||---\t||");
264            let mut floor_body: String = format!("{:.2}\t||{}\t||", floor.dest_prob, floor.get_num_people());
265
266            //Loop through the elevators to check if any are on this floor
267            let mut last_elevator_on_floor: usize = 0_usize;
268            for (j, elevator) in self.elevators.iter().enumerate() {
269                if elevator.floor_on != i as usize {
270                    continue;
271                }
272
273                //If the elevator is on this floor, then display it i spaces away from the building
274                let elevator_roof: String = format!("{}{}", str::repeat(&elevator_space, j - last_elevator_on_floor as usize), String::from("|-\t|"));
275                let elevator_body: String = format!("{}|{}\t|", str::repeat(&elevator_space, j - last_elevator_on_floor as usize), elevator.get_num_people());
276
277                //Append the elevator to the floor strings
278                floor_roof.push_str(&elevator_roof);
279                floor_body.push_str(&elevator_body);
280
281                //Increment the counter for num elevators on this floor
282                last_elevator_on_floor = j + 1_usize;
283            }
284
285            //Add the floor to the building status
286            building_status = [floor_roof, floor_body, building_status].join("\n");
287        }
288        //Add the average energy and wait times throughout the building
289        let wait_time_str: String = format!("Average wait time:\t{:.2}", self.avg_wait_time);
290        let energy_str: String = format!("Average energy spent:\t{:.2}", self.avg_energy);
291        let tip_str: String = format!("Total tips collected:\t${:.2}", self.tot_tips);
292        building_status = [building_status, wait_time_str, energy_str, tip_str].join("\n");
293
294        //Format the string and return
295        f.write_str(&building_status)
296    }
297}
298
299//Floors trait implementation for a building
300impl Floors for Building {
301    /// Determines whether there are any people waiting on a given floor.  Returns a bool
302    /// which is true if so, and false if not.
303    fn are_people_waiting_on_floor(&self, floor_index: usize) -> bool {
304        self.floors.are_people_waiting_on_floor(floor_index)
305    }
306
307    /// Determines the nearest floor at which people are waiting with respect to the given
308    /// floor.  Returns a tuple of usizes representing the floor index and the distance to
309    /// the floor.
310    fn get_nearest_wait_floor(&self, floor_on: usize) -> (usize, usize) {
311        self.floors.get_nearest_wait_floor(floor_on)
312    }
313
314    /// Gets the probability that each floor becomes a destination floor in the next
315    /// time step.
316    fn get_dest_probabilities(&self) -> Vec<f64> {
317        self.floors.get_dest_probabilities()
318    }
319
320    /// Randomly generates the people leaving each floor using each `Floor`'s
321    /// `gen_people_leaving` function, which itself uses each `Person`'s `gen_is_leaving`
322    /// function.
323    fn gen_people_leaving(&mut self, rng: &mut impl Rng) {
324        self.floors.gen_people_leaving(rng)
325    }
326
327    /// Removes anyone who is leaving the first floor, and generates tips
328    fn flush_first_floor(&mut self) -> Vec<Person> {
329        self.floors.flush_first_floor()
330    }
331
332    /// Increments the waiting times among people who are waiting/not at their destination
333    /// floor throughout the building's floors and elevators.
334    fn increment_wait_times(&mut self) {
335        self.elevators.increment_wait_times();
336        self.floors.increment_wait_times();
337    }
338
339    /// Appends a new floor to the building.
340    fn append_floor(&mut self) {
341        self.floors.append_floor();
342    }
343}