def test_simulation_speed_to_kmh(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual(units.simulation_speed_to_kmh(1), 10, "Wrong conversion from sim speed to real speed")
def test_minutes_to_steps(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual(units.minutes_to_steps(0.6), 20.0, "Wrong conversion from minutes to steps")
def test_seconds_to_hours(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual(units.seconds_to_hours(3600), 1.0, "Wrong units conversion to hours")
def test_steps_to_minutes(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual(units.steps_to_minutes(20), 0.6, "Wrong conversion from steps to minutes")
def test_steps_to_seconds(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual(units.steps_to_seconds(10), 18.0, "Wrong conversion steps to seconds")
def test_cells_to_meters(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual(units.cells_to_meters(10), 50, "Wrong conversion from cell to meters")
def test_meter_to_km(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual(units.meters_to_km(1000), 1.0, "Wrong converesion to km")
def set_simulation_units(self, speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135): self.units = Units(speed, cell_length, simulation_speed, battery, cs_power, autonomy) # Write the attributes in orde to save them in the HDF5 file. self.SPEED = speed self.CELL_LENGTH = cell_length self.SIMULATION_SPEED = simulation_speed self.BATTERY = battery self.CS_POWER = cs_power self.AUTONOMY = autonomy
def test_steps_to_recharge(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) d = 1.8 * 27000 n = 10 * (((3.6 * 10**6) * 24) / 7000) self.assertEqual(round(units.steps_to_recharge(10), 5), round(n / d, 5), "Wrong computation of the steps to recharge")
def tests_step_to_s(self): units = Units(speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135) self.assertEqual( units.step_to_s, 1.8, "Wrong constant value for converting steps to seconds")
def load_data(self): """Takes the attributes of the root folder of the HDF5 file and stores them as class attributes. It also loads the datasets from different groups and repetitions and place them together in numpy arrays creating two dictionaries per group: one containing the mean of the different repetitions and other containing the std. """ with h5py.File(self.filepath, "r") as file: # Save the simulation attributes for key, val in file.attrs.items(): self.__setattr__(key, val) # Retrieve the data from the groups for group in list(file['0'].keys()): data_mean, data_std = self.get_data_from_group(file, group) self.__setattr__(group+"_mean", data_mean) self.__setattr__(group+"_std", data_std) # Obtain the total time spent charging and insert it # in the dictionary of global attributes data_mean, data_std = self.get_data_total_group(file, "global") self.global_mean['total'] = data_mean self.global_std['total'] = data_std # Obtain the mean and std of the velocities group by columns. data_mean, data_std = self.get_data_from_group( file, 'velocities', axis=1) for key, val in data_mean.items(): self.global_mean[key] = val # Once we have loaded the datasets, we have to create a # units objects self.units = Units(self.SPEED, self.CELL_LENGTH, self.SIMULATION_SPEED, self.BATTERY, self.CS_POWER, self.AUTONOMY)
class Simulation(): def __init__(self, EV_DEN, TF_DEN, ST_LAYOUT, PATHNAME): """Creates a Simulation object receiving: :param TF_DEN: traffic density of the simulation, it is expressed as a percentage of the total drivable cells. A TF_DEN of 1 means that every possible cell in the city is occupied by a vehicle. :param EV_DEN: electric vehicle dendisty, it's the proportion of EVs relative to the total amount of vehicles. :param ST_LAYOUT: is the layout of the stations, can either be 'distributed', 'central' or 'four'. In order for the simulation to work, once the Simulation object is created, the following methods must be called: Simulation.set_simulation_units() Simulation.set_battery_distribution() Simulation.set_idle_distribution() Simulation.create_city() Simulation.stations_placement() Simulation.create_simulator() """ super().__init__() # Attributes that define a simulation self.EV_DEN = EV_DEN self.TF_DEN = TF_DEN self.ST_LAYOUT = ST_LAYOUT self.PATHNAME = PATHNAME self.sim_name = "EV_DEN: {} TF_DEN: {} LAYOUT: {}".format(EV_DEN, TF_DEN, ST_LAYOUT) self.filename = PATHNAME + "/results/{}#{}#{}".format( self.EV_DEN, self.TF_DEN, self.ST_LAYOUT) # Attributes filled in the method set_simulation_units() self.units = None # Attributes filled in the method create_city() self.city_builder = None self.SCALE = None self.AV_LENGTH = None self.RB_LENGTH = None self.INTERSEC_LENGTH = None self.city_map = None self.city_matrix = None self.avenues = None self.SIZE = None self.STR_RATE = None # Attributes filled in the method stations_placement() self.TOTAL_PLUGS = None # Total number of plugs in the city self.TOTAL_D_ST = None # Total number of distributed stations self.stations = None self.stations_map = None def set_simulation_units(self, speed=10, cell_length=5, simulation_speed=1, battery=24, cs_power=7, autonomy=135): self.units = Units(speed, cell_length, simulation_speed, battery, cs_power, autonomy) # Write the attributes in orde to save them in the HDF5 file. self.SPEED = speed self.CELL_LENGTH = cell_length self.SIMULATION_SPEED = simulation_speed self.BATTERY = battery self.CS_POWER = cs_power self.AUTONOMY = autonomy def create_city(self, Builder, RB_LENGTH=6, AV_LENGTH=4*5, SCALE=1, INTERSEC_LENGTH=3): """Builder is a CityBuilder class""" # Create the city builder self.city_builder = Builder(RB_LENGTH, AV_LENGTH, SCALE, INTERSEC_LENGTH) self.city_map = self.city_builder.city_map self.city_matrix = self.city_builder.city_matrix self.avenues = self.city_builder.avenues self.roundabouts = self.city_builder.roundabouts self.SCALE = SCALE self.AV_LENGTH = AV_LENGTH self.RB_LENGTH = RB_LENGTH self.INTERSEC_LENGTH = INTERSEC_LENGTH self.SIZE = self.city_builder.SIZE self.STR_RATE = self.city_builder.STR_RATE # Create the city districts self.districts = self.city_builder.create_districts(self.ST_LAYOUT) def stations_placement(self, min_plugs_per_station, min_num_stations): """This method computes the number total number of distributed stations that are needed in order to make the city symmetrical and compatible between different stations layour, for example the total number of distributed stations need to be a divisor of 4. Then computes the placement of the stations for this simulation and creates the simulation objects with the correct amount of chargers. :param min_plugs_per_station: is the minimum number of plugs that we want each distrbuted station to have. :param min_num_stations: is the minimum number of distributed stations that we want to have in a simulation with the ST_LAYOUT = "distributed" """ # Compute the amount of plugs and the number of distributed stations self.TOTAL_PLUGS, self.TOTAL_D_ST = self.city_builder.set_max_chargers_stations( min_plugs_per_station, min_num_stations) # Place the stations around the city based on the layout # self.stations_pos = self.city_builder.place_stations(self.ST_LAYOUT, self.districts, self.TOTAL_D_ST) self.stations_clusters, self.pos_clusters = self.city_builder.place_stations_new(self.ST_LAYOUT, self.TOTAL_D_ST) # Based on the layout, compute the number of plugs that each station will have. plugs_per_station = min_plugs_per_station if self.ST_LAYOUT == "central": plugs_per_station = self.TOTAL_PLUGS elif self.ST_LAYOUT == "four": plugs_per_station = self.TOTAL_PLUGS/4 # Create the stations and the stations map self.stations, self.stations_map = self.create_stations(self.stations_clusters, self.pos_clusters, plugs_per_station) def create_stations(self, stations_clusters, pos_clusters, plugs_per_station): """ :param stations_clusters: A list of lists. Each list is a "district" and has the positions of the stations belonging to that district. :param pos_clusters: A list of lists. Each list is the positions of the city that belongs to the stations cluster with the same index. :param plugs_per_station: is the number of plugs that a station can have. """ stations = [] stations_map = {} for stations_pos, influence_area in zip(stations_clusters,pos_clusters): # For each position in the station cluster, create a proper Station object district_stations = [Station(self.city_map[pos], plugs_per_station) for pos in stations_pos] # For each position in the influence area of this cluster, link them in the stations map for influence_pos in influence_area: stations_map[influence_pos] = district_stations # Add the stations to the list of stations. stations.extend(district_stations) return stations, stations_map def set_battery_distribution(self, lower, std): """This method sets the parameters of the normal distribution used to set the goal charge of a vehicle when it wants to recharge. :param lower: Is the minimum amount to recharge. 1 sets the lower limit to 100% of the vehicle's autonomy while 0 sets the lower limit to 0% of the vehicle's autonomy. :param std: is the deviation of the distribution with respect to the mean. A value of 1 means a deviation equal to 100% of the mean while a value of 0 means no deviation at all. Internally the battery values are expressed as autonomy in simulation cells. """ self.BATTERY_UPPER = int(self.units.autonomy) self.BATTERY_LOWER = int(lower * self.units.autonomy) self.BATTERY_MEAN = (lower + self.BATTERY_UPPER) // 2 self.BATTERY_STD = std * self.BATTERY_MEAN def set_idle_distribution(self, upper, lower, std): """This method sets the parameters of the normal distribution used to compute the amount of time a vehicle is idle at a destination. :param upper: Is the maximum time, in minutes, that a vehicle can stay idle. :param lower: Is the minimum time, in minutes, that a vehicle can stay idle. :param std: controls the deviation with respect to the mean. A value of 1 means a deviation equal to 100% while a deviation of 0 no deviation at all. Internally the idle times are expressed as simulation time steps. """ self.IDLE_UPPER = int(self.units.minutes_to_steps(upper)) self.IDLE_LOWER = int(self.units.minutes_to_steps(lower)) self.IDLE_MEAN = (self.IDLE_UPPER + self.IDLE_LOWER) // 2 self.IDLE_STD = std * self.IDLE_MEAN def create_vehicles(self): """Creates the vehicles and places them around the city. Initially all the vehicles are in the AT_DEST state waiting to be released. The realeasing follows the same distribution as the idle time spent at a destination.""" # Add constants to the simulation object self.TOTAL_VEHICLES = int(self.STR_RATE * self.SIZE * self.SIZE * self.TF_DEN) self.TOTAL_EV = int(self.EV_DEN * self.TOTAL_VEHICLES) # Create the vehicles, place them on the city. # Initially all the vehicles are in a AT_DEST # state and so they don't occupy a place. city_cells = list(self.city_map.values()) random.shuffle(city_cells) ev_vehicles = set() vehicles = [] for _ in range(self.TOTAL_EV): v = ElectricVehicle(city_cells.pop(), self.simulator.compute_idle(), self.simulator.compute_battery()) vehicles.append(v) ev_vehicles.add(v) for _ in range(self.TOTAL_VEHICLES-self.TOTAL_EV): v = Vehicle(city_cells.pop(), self.simulator.compute_idle()) vehicles.append(v) self.vehicles = vehicles self.ev_vehicles = ev_vehicles def create_simulator(self): """Creates the simulator object. Then it also creates the vehicles calling Simulation.create_vehicles() """ self.simulator = SimulatorEngine(self) self.create_vehicles() def print_summary(self): msg = """*******************************************************\ \nInitializing the simulation: {}\ \nThe size of the city is: {} \ \nThis will run for {} tsteps, taking measures every {} tsteps.\ \nThe battery distribution has a mean of {} steps and std of {} steps.\ \nThe idle distribution has a mean of {} steps and std of {} steps.\ \nIn total there is {} vehicles being electric {} """\ .format(self.filename,self.SIZE ,self.TOTAL_TSTEPS, self.DELTA_TSTEPS, self.BATTERY_MEAN, self.BATTERY_STD, self.IDLE_MEAN, self.IDLE_STD, self.TOTAL_VEHICLES, self.TOTAL_EV) print(msg) def set_progress_message(self, num_progress_msg): """Computes a list of tsteps that will prompt a message displaying the progress of the simulation. """ self.progress_tsteps = [int(((i+1)*self.TOTAL_TSTEPS)/(num_progress_msg*self.DELTA_TSTEPS))*self.DELTA_TSTEPS for i in range(num_progress_msg)] def print_progress(self, tstep): progress = int(100*(tstep/self.TOTAL_TSTEPS)) msg = """Simulation: {} Repetition: {} Progress: {}% """.format( self.filename, self.repetition, progress) print(msg) def prepare_results_file(self): """Checks if the results folder exists and truncates the destination file.""" # Check if the results folder exists. if not os.path.exists(self.PATHNAME + "/results"): os.makedirs(self.PATHNAME + "/results") else: with open(self.filename + ".hdf5", "w"): pass def write_header_attr(self): """This method writes the global attributes of the simulation as HDF5 attributes of the root group. """ # Write the attributes with h5py.File(self.filename + ".hdf5", "a") as f: for key, value in self.__dict__.items(): if key.isupper(): f.attrs[key] = value def write_results(self, repetition, metrics): """Once the simulation is over, the results are written to a HDF5 file. The name of the file is made with the attributes EV_DEN#TF_DEN#ST_LAYOUT that identify a simulation. For each repetition a group is made with the index of the repetition and the data is saved into datasets.""" with h5py.File(self.filename+".hdf5", "a") as f: metrics.write_results( f, "/"+str(repetition)+"/", self.simulator.seeking_history, self.simulator.queueing_history) def run(self, total_time, measure_period, repetitions, visual=None): """ Method to execute the simulation. The attributes are given in the SI, time (hours) and measure_period (minutes). :param time: total time to simulate in hours :param measure_period: time between two consecutive snapshots of the system. :param repetitions: number of times the simulation is run. """ # Convert the time to simulation steps total_tsteps = int(self.units.minutes_to_steps(total_time*60)) delta_tsteps = int(self.units.minutes_to_steps(measure_period)) if delta_tsteps == 0: delta_tsteps = 1 self.TOTAL_TSTEPS = total_tsteps self.DELTA_TSTEPS = delta_tsteps self.REPETITIONS = repetitions # Set the list of tsetps for displaying a message self.set_progress_message(10) # Prepare the HDF5 file self.prepare_results_file() self.print_summary() elapsed = time.time() for i in range(repetitions): self.repetition = i # Restart the cells, vehicles, stations and the simulator for cell in self.city_map.values(): cell.occupied = False for v in self.vehicles: v.restart() for st in self.stations: st.restart() self.simulator.restart() # Create a metrics object and initilize it metrics = SimulationMetric( self.city_map, self.stations, 3, total_tsteps, delta_tsteps, self.SIZE) metrics.initialize( self.vehicles, self.ev_vehicles, self.stations) # Run the simulation using the simulator object if visual == None: self.run_simulator(metrics) else: self.run_simulator_visual(metrics, visual) # Store the data into an HDF5 file. self.write_results(i, metrics) self.ELAPSED = round((time.time() - elapsed) / repetitions, 3) self.write_header_attr() def run_simulator(self, metrics): """This method controls the flow of the simulator, saving the data and displaying a progress message. """ previous_snapshot = SimulationSnapshot(self.vehicles) for current_tstep in range(1, self.TOTAL_TSTEPS+1): # Compute next step of the simulation self.simulator.next_step() # Check if we have to update the data collection if current_tstep % self.DELTA_TSTEPS == 0: current_snapshot = SimulationSnapshot(self.vehicles) metrics.update_data(self.vehicles, self.ev_vehicles, self.stations, current_snapshot, previous_snapshot, current_tstep) previous_snapshot = current_snapshot # Check if we have to display a progress message if current_tstep in self.progress_tsteps: self.print_progress(current_tstep) def run_simulator_visual(self, metrics, visual): """This method controls the flow of the simulator, saving the data and displaying a progress message. """ self.previous_snapshot = SimulationSnapshot(self.vehicles) def next_frame(): # Compute next step of the simulation self.simulator.next_step() visual.update() current_tstep = visual.current_tstep # Check if we have to update the data collection if current_tstep % self.DELTA_TSTEPS == 0: current_snapshot = SimulationSnapshot(self.vehicles) metrics.update_data(self.vehicles, self.ev_vehicles, self.stations, current_snapshot, self.previous_snapshot, current_tstep) self.previous_snapshot = current_snapshot # Check if we have to display a progress message if current_tstep in self.progress_tsteps: self.print_progress(current_tstep) # Increment the steps counter visual.current_tstep += 1 # Check for termination if visual.current_tstep == self.TOTAL_TSTEPS: "Terminar la visualizacion" pass visual.beginRepresentation(next_frame) def prepare_simulation(self, total_time, measure_period, repetitions): """First function to call when we want to run a new simulation from the application. """ # Convert the time to simulation steps total_tsteps = int(self.units.minutes_to_steps(total_time*60)) delta_tsteps = int(self.units.minutes_to_steps(measure_period)) if delta_tsteps == 0: delta_tsteps = 1 self.TOTAL_TSTEPS = total_tsteps self.DELTA_TSTEPS = delta_tsteps self.REPETITIONS = repetitions # Set the list of tsetps for displaying a message self.set_progress_message(10) # Prepare the HDF5 file self.prepare_results_file() self.print_summary() def end_simulation(self): """Last function to call when we want to run a simulation from the application. """ self.ELAPSED = 0 self.write_header_attr() def run_simulator_application(self, current_repetition, current_tstep, current_metrics, previous_snapshot): """Function that updates the simulation from the application. """ if current_tstep == 0: print("Starting") # Restart the cells, vehicles, stations and the simulator for cell in self.city_map.values(): cell.occupied = False for v in self.vehicles: v.restart() for st in self.stations: st.restart() self.simulator.restart() self.repetition = current_repetition # Create a metrics object and initilize it current_metrics = SimulationMetric(self.city_map, self.stations, 3, self.TOTAL_TSTEPS, self.DELTA_TSTEPS, self.SIZE) current_metrics.initialize(self.vehicles, self.ev_vehicles, self.stations) self.metrics = current_metrics # Create the first snapshot previous_snapshot = SimulationSnapshot(self.vehicles) current_tstep += 1 elif current_tstep > self.TOTAL_TSTEPS: print("End of repetition") # Store the data into an HDF5 file. self.write_results(current_repetition, current_metrics) # test whether there are more simulations left current_repetition += 1 if current_repetition < self.REPETITIONS: # There are more simulations left to do, set the time step to zero. print("There are more repetitions left") current_tstep = 0 else: # There are no more simulations left to, terminate. print("No more repetitions left") self.end_simulation() return None else: self.simulator.next_step() # Check if we have to update the data collection if current_tstep % self.DELTA_TSTEPS == 0: current_snapshot = SimulationSnapshot(self.vehicles) current_metrics.update_data(self.vehicles, self.ev_vehicles, self.stations, current_snapshot, previous_snapshot, current_tstep) previous_snapshot = current_snapshot # Check if we have to display a progress message if current_tstep in self.progress_tsteps: self.print_progress(current_tstep) current_tstep += 1 return current_repetition, current_tstep, current_metrics, previous_snapshot