1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
//Import external/standard modules
use rand::Rng;
use rand::distributions::Distribution;
use statrs::distribution::{Poisson, Binomial};

//Import source modules
use crate::person::Person;
use crate::people::People;
use crate::floor::Floor;
use crate::floors::Floors;
use crate::elevator::Elevator;
use crate::elevators::Elevators;

//Constant representing the probability a person leaves the building during a time step
const P_OUT: f64 = 0.05_f64;

//Constant representing the probability a person leaves a tip
const P_TIP: f64 = 0.5_f64;

//Constants defining the Bernoulli distribution parameters to sample from to generate tips
const DST_TIP_TRIALS: u64 = 100_u64;
const DST_TIP_SUCCESS: f64 = 0.5_f64;

/// # `Building` struct
///
/// A `Building` aggregates `Elevator`s and `Floor`s.  It also tracks the everage
/// energy usage by the elevators, and the average wait time among the people on
/// the building's floors and elevators.  It randomly generates arrivals.
#[derive(Clone)]
pub struct Building {
    pub elevators: Vec<Elevator>,
    pub floors: Vec<Floor>,
    pub avg_energy: f64,
    pub avg_wait_time: f64,
    pub tot_tips: f64,
    wait_time_denom: usize,
    p_in: f64,
    dst_in: Poisson,
    dst_tip: Binomial
}

/// # `Building` type implementation
///
/// The following functions are used to control the behavior of the people and elevators
/// owned by the `Building` type.
impl Building {
    /// Initialize a new building given the number of floors, the number of elevators
    /// the expected number of arrivals per time step, the base energy spent while moving
    /// an elevator up and down, and the additional energy spent per person moved.
    ///
    /// ## Example
    ///
    /// ```
    /// let num_floors: usize = 4_usize;
    /// let num_elevators: usize = 2_usize;
    /// let p_in: f64 = 0.5_f64;
    /// let floor_capacity: usize = 100_usize;
    /// let elevator_capacity: usize = 10_usize;
    /// let energy_up: f64 = 5.0_f64;
    /// let energy_down: f64 = 2.5_f64;
    /// let energy_coef: f64 = 0.5_f64;
    /// let my_building: Building = Building::from(
    ///     num_floors,
    ///     num_elevators,
    ///     p_in,
    ///     energy_up,
    ///     energy_down,
    ///     energy_coef
    /// );
    /// ```
    pub fn from(num_floors: usize, num_elevators: usize, p_in: f64,
                floor_capacity: usize, elevator_capacity: usize,
                energy_up: f64, energy_down: f64, energy_coef: f64) -> Building {
        //Initialize the Floors
        let floors: Vec<Floor> = {
            let mut tmp_floors: Vec<Floor> = Vec::new();
            for _ in 0_usize..num_floors {
                let tmp_floor: Floor = Floor::new(floor_capacity);
                tmp_floors.push(tmp_floor);
            }
            tmp_floors
        };
    
        //Initialize the Elevators
        let elevators: Vec<Elevator> = {
            let mut tmp_elevators: Vec<Elevator> = Vec::new();
            for _ in 0_usize..num_elevators {
                let tmp_elevator: Elevator = Elevator::from(
                    elevator_capacity, energy_up, energy_down, energy_coef
                );
                tmp_elevators.push(tmp_elevator);
            }
            tmp_elevators
        };
    
        //Initialize the arrival probability distribution
        let dst_in = Poisson::new(p_in).unwrap();
    
        //Initialize and return the Building
        Building {
            floors: floors,
            elevators: elevators,
            avg_energy: 0_f64,
            avg_wait_time: 0_f64,
            wait_time_denom: 0_usize,
            tot_tips: 0_f64,
            p_in: p_in,
            dst_in: dst_in,
            dst_tip: Binomial::new(DST_TIP_SUCCESS, DST_TIP_TRIALS).unwrap()
        }
    }

    /// Calculate the probability that each floor becomes a destination floor for an elevator
    /// during the next time step.  If the floor currently is a destination floor, then return
    /// `1_f64`.
    pub fn update_dest_probabilities(&mut self) {
        //Get the number of floors in the building
        let num_floors: usize = self.floors.len() as usize;

        //Get the destination floors across each elevator
        let dest_floors: Vec<usize> = self.elevators.get_dest_floors();

        //Loop through the floors
        for (i, floor) in self.floors.iter_mut().enumerate() {
            //Initialize an f64 for this floor's probability
            let dest_probability: f64 = if i == 0 {
                //If this is the first floor, then calculate the prob
                //based on arrival probability only
                let people_waiting: f64 = {
                    let waiting: f64 = if floor.are_people_waiting() { 1_f64 } else { 0_f64 };
                    let going: f64 = if dest_floors.contains(&i) { 1_f64 } else { 0_f64 };
                    if waiting > going { waiting } else { going }
                };
                let p_in: f64 = self.p_in * ((num_floors as f64 - 1_f64)/(num_floors as f64));
                if people_waiting > p_in { people_waiting } else { p_in }
            } else {
                //If this is not the first floor, then calculate the
                //prob based on the elevator's people and the floor's
                //people and append it to the list
                let people_waiting: f64 = {
                    let waiting: f64 = if floor.are_people_waiting() { 1_f64 } else { 0_f64 };
                    let going: f64 = if dest_floors.contains(&i) { 1_f64 } else { 0_f64 };
                    if waiting > going { waiting } else { going }
                };
                let p_out: f64 = floor.get_p_out();
                if people_waiting > p_out { people_waiting } else { p_out }
            };
            floor.dest_prob = dest_probability;
        }
    }

    /// Generate the people arriving by sampling the Poisson distribution to receive a number
    /// of arrivals, and then instantiate that many people and append them to the first floor.
    pub fn gen_people_arriving(&mut self, mut rng: &mut impl Rng) {
        //Initialize a vector of Persons
        let mut arrivals: Vec<Person> = Vec::new();

        //Loop until no new arrivals occur, for each arrival append a new person
        for _ in 0_i32..self.dst_in.sample(&mut rng) as i32 {
            let new_person: Person = Person::from(P_OUT, P_TIP, self.floors.len(), &mut rng);
            arrivals.push(new_person);
        }

        //Extend the first floor with the new arrivals
        self.floors[0].extend(arrivals);
    }

    /// Given the number of people who decided to tip, generate the total value of their tips
    pub fn gen_tip_value(&self, num_tips: usize, rng: &mut impl Rng) -> f64 {
        //Initialize a float to store the tip value
        let mut tip_value: f64 = 0.0_f64;

        //Sample the tip distribution for each tip, randomizing the value of the tip
        //according to the tip distribution
        for _ in 0..num_tips {
            tip_value += self.dst_tip.sample(rng) / 100_f64;
        }

        //Return the total tip value
        tip_value
    }

    /// For each of the building's elevators, exchange people between the elevator and its
    /// current floor if anyone on the elevator is going to the current floor, or if anyone on
    /// the floor is waiting for the elevator.
    pub fn exchange_people_on_elevator(&mut self) {
        for elevator in self.elevators.iter_mut() {
            //If the elevator is not stopped then continue
            if !elevator.stopped {
                continue;
            }

            //Get the elevator's floor index
            let floor_index: usize = elevator.floor_on;

            //Get the floor's free capacity and the floor's waiting capacity
            let floor_free_capacity: usize = self.floors[floor_index].get_free_capacity();
            let floor_wait_capacity: usize = self.floors[floor_index].get_num_people_waiting();
            let floor_exchange_capacity: usize = floor_free_capacity + floor_wait_capacity;

            //Get the elevator's free capacity and the elevator's leaving capacity
            let elevator_free_capacity: usize = elevator.get_free_capacity();
            let elevator_leav_capacity: usize = elevator.get_num_people_going_to_floor(floor_index);
            let elevator_exchange_capacity: usize = elevator_free_capacity + elevator_leav_capacity;

            //Calculate the exchange capacity using the floor and elevator capacities
            let exchange_capacity: usize = if floor_exchange_capacity > elevator_exchange_capacity {
                elevator_exchange_capacity
            } else {
                floor_exchange_capacity
            };

            //Move people off the floor and off the elevator
            let people_leaving_floor: Vec<Person> = self.floors[floor_index].flush_people_entering_elevator(exchange_capacity);
            let mut people_leaving_elevator: Vec<Person> = elevator.flush_people_leaving_elevator(exchange_capacity);

            //Aggregate the wait times of the people leaving the elevator into the average and reset
            let wait_times: usize = people_leaving_elevator.get_aggregate_wait_time();
            let num_people: usize = people_leaving_elevator.get_num_people();
            self.avg_wait_time = {
                let tmp_num: f64 = wait_times as f64 + (self.avg_wait_time * self.wait_time_denom as f64);
                let tmp_denom: f64 = num_people as f64 + self.wait_time_denom as f64;
                if tmp_denom == 0_f64 {
                    0_f64 //If the denominator is 0, return 0 to avoid NaNs
                } else {
                    tmp_num / tmp_denom
                }
            };
            self.wait_time_denom += num_people;
            people_leaving_elevator.reset_wait_times();

            //Extend the current floor and elevator with the people getting on and off
            elevator.extend(people_leaving_floor);
            self.floors[floor_index].extend(people_leaving_elevator);
        }
    }

    /// Removes anyone who is leaving the first floor, and generates tips
    pub fn flush_and_update_tips(&mut self, rng: &mut impl Rng) {
        let people_leaving_floor: Vec<Person> = self.floors.flush_first_floor();
        let num_tips: usize = people_leaving_floor.gen_num_tips(rng);
        let tip_value: f64 = self.gen_tip_value(num_tips, rng);
        self.tot_tips += tip_value;
    }

    /// Returns all tips collected by the building, and resets the total tips to 0
    pub fn collect_tips(&mut self) -> f64 {
        let tips: f64 = self.tot_tips;
        self.tot_tips = 0.0_f64;
        tips
    }

    /// Update the average energy spent by the building's elevators given the time
    /// step and the energy spent during the time step.
    pub fn update_average_energy(&mut self, time_step: i32, energy_spent: f64) {
        self.avg_energy = {
            let tmp_num: f64 = (self.avg_energy * time_step as f64) + energy_spent;
            let tmp_denom: f64 = (time_step + 1_i32) as f64;
            tmp_num / tmp_denom
        };
    }

    /// Append a new elevator to the building
    pub fn append_elevator(&mut self, capacity: usize, energy_up: f64, energy_down: f64, energy_coef: f64) {
        self.elevators.push(Elevator::from(capacity, energy_up, energy_down, energy_coef));
    }
}

//Display trait implementation for a building
impl std::fmt::Display for Building {
    /// Format a `Building` as a string.
    ///
    /// ### Example
    ///
    /// ```
    /// println!("{}", my_building);
    /// ```
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut building_status: String = String::new();
        let elevator_space: String = String::from("   \t ");
        for (i, floor) in self.floors.iter().enumerate() {
            //Initialize strings representing this floor
            let mut floor_roof: String = String::from("----\t||---\t||");
            let mut floor_body: String = format!("{:.2}\t||{}/{}\t||", floor.dest_prob, floor.get_num_people(), floor.capacity);

            //Loop through the elevators to check if any are on this floor
            let mut last_elevator_on_floor: usize = 0_usize;
            for (j, elevator) in self.elevators.iter().enumerate() {
                if elevator.floor_on != i as usize {
                    continue;
                }

                //If the elevator is on this floor, then display it i spaces away from the building
                let elevator_roof: String = format!("{}{}", str::repeat(&elevator_space, j - last_elevator_on_floor as usize), String::from("|-\t|"));
                let elevator_body: String = format!("{}|{}/{}\t|", str::repeat(&elevator_space, j - last_elevator_on_floor as usize), elevator.get_num_people(), elevator.capacity);

                //Append the elevator to the floor strings
                floor_roof.push_str(&elevator_roof);
                floor_body.push_str(&elevator_body);

                //Increment the counter for num elevators on this floor
                last_elevator_on_floor = j + 1_usize;
            }

            //Add the floor to the building status
            building_status = [floor_roof, floor_body, building_status].join("\n");
        }
        //Add the average energy and wait times throughout the building
        let wait_time_str: String = format!("Average wait time:\t{:.2}", self.avg_wait_time);
        let energy_str: String = format!("Average energy spent:\t{:.2}", self.avg_energy);
        let tip_str: String = format!("Total tips collected:\t${:.2}", self.tot_tips);
        building_status = [building_status, wait_time_str, energy_str, tip_str].join("\n");

        //Format the string and return
        f.write_str(&building_status)
    }
}

//Floors trait implementation for a building
impl Floors for Building {
    /// Determines whether there are any people waiting on a given floor.  Returns a bool
    /// which is true if so, and false if not.
    fn are_people_waiting_on_floor(&self, floor_index: usize) -> bool {
        self.floors.are_people_waiting_on_floor(floor_index)
    }

    /// Determines the nearest floor at which people are waiting with respect to the given
    /// floor.  Returns a tuple of usizes representing the floor index and the distance to
    /// the floor.
    fn get_nearest_wait_floor(&self, floor_on: usize) -> (usize, usize) {
        self.floors.get_nearest_wait_floor(floor_on)
    }

    /// Gets the probability that each floor becomes a destination floor in the next
    /// time step.
    fn get_dest_probabilities(&self) -> Vec<f64> {
        self.floors.get_dest_probabilities()
    }

    /// Randomly generates the people leaving each floor using each `Floor`'s
    /// `gen_people_leaving` function, which itself uses each `Person`'s `gen_is_leaving`
    /// function.
    fn gen_people_leaving(&mut self, rng: &mut impl Rng) {
        self.floors.gen_people_leaving(rng)
    }

    /// Removes anyone who is leaving the first floor, and generates tips
    fn flush_first_floor(&mut self) -> Vec<Person> {
        self.floors.flush_first_floor()
    }

    /// Increments the waiting times among people who are waiting/not at their destination
    /// floor throughout the building's floors and elevators.
    fn increment_wait_times(&mut self) {
        self.elevators.increment_wait_times();
        self.floors.increment_wait_times();
    }

    /// Appends a new floor to the building.
    fn append_floor(&mut self, capacity: usize) {
        self.floors.append_floor(capacity);
    }

    /// Updates the capacity across each of the floors
    fn update_capacities(&mut self, capacity: usize) {
        self.floors.update_capacities(capacity);
    }
}