def __init__( self, infectious_rate_distribution: Disease_Property_Distribution, immunity_distribution: Disease_Property_Distribution, disease_period_distribution: Disease_Property_Distribution, death_probability_distribution: Disease_Property_Distribution, incubation_period_distribution: Disease_Property_Distribution): """Initialize a disease properties object and create infectious and immunity dictionaries. Args: infectious_rate_distribution (Disease_Property_Distribution): The distribution explaining infectious rate. immunity_distribution (Disease_Property_Distribution): The distribution of people's immunity. disease_period_distribution (Disease_Property_Distribution): This holds the distribution of infectious period. incubation_period_distribution (Disease_Property_Distribution): The distribution of incubation period. death_probability_distribution (Disease_Property_Distribution): This determines the probability that the virus kills someone. """ self.immunity_distribution = immunity_distribution self.infectious_rate_distribution = infectious_rate_distribution self.disease_period_distribution = disease_period_distribution self.death_probability_distribution = death_probability_distribution self.incubation_period_distribution = incubation_period_distribution self.infectious_rate_dict: Dict[int, float] = {} self.immunity_dict: Dict[int, float] = {} logger.info('Disease Properties generated')
def display_population_statistics(population_generator): """Print population statistics on the terminal. Args: population_generator (Population_Generator): The population generator class consisting of population data. """ # family patterns patterns_table = Texttable() patterns = population_generator.family_pattern_probability_dict for pattern in patterns: members_count = pattern.number_of_members genders = ['Male' if gender else 'Female' for gender in pattern.genders] patterns_table.add_rows([['Family Pattern Probability', 'Number of Members', 'Genders'], [patterns[pattern], members_count, genders]]) logger.info(f'\n{patterns_table.draw()}') # community types types_table = Texttable() types = population_generator.community_types for community_type in types: name = community_type.name communities_count = community_type.number_of_communities sub_community_types = [sct.name for sct in community_type.sub_community_types] types_table.add_rows([['Community Type', 'Number of Communities', 'Sub-community Types'], [name, communities_count, sub_community_types]]) logger.info(f'\n{types_table.draw()}')
def parse_optimization_trials_result( folder_name: str = 'trials') -> List[hyperopt.base.Trials]: """Extract trial objects from pickle files. From the documentation of trials we have that: trials.trials - a list of dictionaries representing everything about the search. trials.results - a list of dictionaries returned by 'objective' during the search. trials.losses() - a list of losses (float for each 'ok' trial) trials.statuses() - a list of status strings Args: folder_name (str, optional): The name of trials folder. Defaults to trials. Returns: List[hyperopt.base.Trials]: The parsed trial obejects. """ trials_list: List[hyperopt.base.Trials] = list() logger.info(f'Entering the {folder_name} directory') for file_name in os.listdir(folder_name): with open(os.path.join(folder_name, file_name), 'rb') as trial_file: trials_list.append(pickle.load(trial_file)) return trials_list
def display_simulator_statistics(simulator): """Print the simulator data on the terminal. Args: simulator (Simulator): The simulator object which its data is printed. """ t = Texttable() t.add_rows([['Simulator', 'Data'], ['Start Time', simulator.end_time.init_date_time], ['End Time', simulator.end_time.get_utc_time()], ['Spread Period', simulator.spread_period.get_minutes()], ['Database', simulator.database_name]]) logger.info(f'\n{t.draw()}')
def generate_model(self, is_parallel: bool = False, show_progress: bool = True): """Generates a the simulation model, including the people, graph, families, and communities. Args: is_parallel (bool): If true, the function uses multiprocessing to boost the computations. Otherwise, the method runs in normal single processing mode. show_progress (bool, optional): Whether to show the progress bar or not. Defaults to True. """ self.people, self.graph, self.families, self.communities = \ self.population_generator.generate_population(is_parallel, show_progress) logger.info('Simulation model generated')
def take_action(self, simulator, end_time: Time): """Start the action that the commands is designed for. Args: simulator (Simulator): The main simulator object which is passed to this command. end_time (Time): The final time of the simulation. """ if self.condition.is_satisfied(simulator, end_time): logger.info(f'Command executed: {self.__class__.__name__}') for person in simulator.people: person.unquarantine()
def display_spread_statistics(simulator): """Print the parameters related to the spread like R0 of a completed simulation. R0: At the beginning of an epidemics the R0 value describes how many other people an infected person will infect on average. Args: simulator (Simulator): The simulator objects. """ t = Texttable() t.add_rows([['Parameter', 'Value'], ['R0', simulator.end_time.init_date_time], ['R effective', simulator.end_time.get_utc_time()], ['Spread Period', simulator.spread_period.get_minutes()], ['Database', simulator.database_name]]) logger.info(f'\n{t.draw()}')
def take_action(self, simulator, end_time: Time): """Start the action that the commands is designed for. Args: simulator (Simulator): The main simulator object which is passed to this command. end_time (Time): The final time of the simulation. """ if self.condition.is_satisfied(simulator, end_time): logger.info(f'Command executed: {self.__class__.__name__}') for family in simulator.families: if family.id_number in self.ids: logger.debug(f'Family unquarantined: {family.id_number}') family.unquarantine(simulator.people)
def take_action(self, simulator, end_time: Time): """Start the action that the commands is designed for. Args: simulator (Simulator): The main simulator object which is passed to this command. end_time (Time): The final time of the simulation. """ if self.condition.is_satisfied(simulator, end_time): logger.info(f'Command executed: {self.__class__.__name__}') for person in simulator.people: if person.infection_status is Infection_Status.CONTAGIOUS or \ person.infection_status is Infection_Status.INCUBATION: logger.debug(f'Person unquarantined: {person.id_number}') person.unquarantine()
def save_model(self, model_name: str): """Save the simulation model generated in generate model method for later use. This function saves the model in a pickle file located in the data/pickle folder. Warning: Using save model will change the reference of the objects, and leads to creation of different communities, people, families, etc. Only use it once to maintain consistency of the references. Args: model_name (str): The name of the pickle file to be saved. """ if basename(os.path.abspath(os.path.join(os.getcwd(), os.pardir))) == 'Pyfectious': path = os.path.join(os.getcwd(), os.pardir, 'data', 'pickle', model_name) elif basename(os.getcwd()) == 'Pyfectious': path = os.path.join(os.getcwd(), 'data', 'pickle', model_name) else: logger.warning('Failed to save model, change directory to src, example, or \ project main directory and try again!') return os.makedirs(path, exist_ok=True) graph_path = os.path.join(path, 'graph.pickle') people_path = os.path.join(path, 'people.pickle') families_path = os.path.join(path, 'families.pickle') communities_path = os.path.join(path, 'communities.pickle') with open(people_path, 'wb') as f: pickle.dump(self.people, f) with open(graph_path, 'wb') as f: pickle.dump(self.graph, f) with open(families_path, 'wb') as f: pickle.dump(self.families, f) with open(communities_path, 'wb') as f: pickle.dump(self.communities, f) logger.info(f'Simulator model {model_name} saved')
def take_action(self, simulator, end_time: Time): """Start the action that the commands is designed for. Args: simulator (Simulator): The main simulator object which is passed to this command. end_time (Time): The final time of the simulation. """ if self.condition.is_satisfied(simulator, end_time): logger.info( f'Command executed: {self.__class__.__name__}, {self.role_name}' ) for person in simulator.people: for community, subcommunity_type_index in person.communities: if self.role_name == community.community_type.sub_community_types[ subcommunity_type_index].name: community.community_type.sub_community_types[subcommunity_type_index] \ .community_type_role.presence_prob = 1 - self.restriction_ratio
def display_family_statistics(people: List, families: List): """Print the family statistics on the terminal. Args: people (List[Person]): The people of the simulator that are merged into families. families (List[Family]): The families of the simulation environment. """ stat_dict = Statistics.get_family_statistics(people, families) total_families = len(families) dead = stat_dict[Health_Condition.DEAD] is_infected = stat_dict[Health_Condition.IS_INFECTED] has_been_infected = stat_dict[Health_Condition.HAS_BEEN_INFECTED] t = Texttable() t.add_rows([['Families', 'Count'], ['Number of Families', total_families], ['Confirmed (Active + Close)', has_been_infected], ['Total Death Cases', dead], ['Currently Active Cases', is_infected]]) logger.info(f'\n{t.draw()}')
def display_people_statistics(simulator): """Print the people's statistics table on the terminal Args: simulator (Simulator): The simulation environment. """ stat_dict = simulator.statistics.get_people_statistics() total_people = stat_dict[Health_Condition.ALL] dead = stat_dict[Health_Condition.DEAD] is_infected = stat_dict[Health_Condition.IS_INFECTED] has_been_infected = stat_dict[Health_Condition.HAS_BEEN_INFECTED] t = Texttable() t.add_rows([['People', 'Count'], ['Population Size', total_people], ['Confirmed (Active + Close)', has_been_infected], ['Total Death Cases', dead], ['Total Recovered', has_been_infected - dead], ['Currently Active Cases', is_infected]]) logger.info(f'\n{t.draw()}')
def take_action(self, simulator, end_time: Time): """Start the action that the commands is designed for. Args: simulator (Simulator): The main simulator object which is passed to this command. end_time (Time): The final time of the simulation. """ if self.condition.is_satisfied(simulator, end_time): logger.info( f'Command executed: {self.__class__.__name__}, target: {self.community_type_name}' ) for community_type_id in simulator.communities: if simulator.communities[community_type_id][ 0].community_type.name == self.community_type_name: logger.debug( f'Community quarantined: {self.community_type_name}, {self.community_index}' ) simulator.communities[community_type_id][ self.community_index].unquarantine()
def load_model(self, model_name: str): """Load a saved simulation model from the pickle folder. This function automatically sets the simulation main entities to the data loaded from the pickle file. Args: model_name (str): The name of the model to be loaded. """ if basename(os.getcwd()) == 'Pyfectious': sys.path.insert(1, 'src') path = os.path.join(os.getcwd(), 'data', 'pickle', model_name) elif basename(os.path.abspath(os.path.join(os.getcwd(), os.pardir))) == 'Pyfectious': sys.path.insert(1, os.path.join(os.pardir, 'src')) path = os.path.join(os.getcwd(), os.pardir, 'data', 'pickle', model_name) else: raise FileNotFoundError('Failed to save model, change directory to src, example, or \ project main directory and try again!') graph_path = os.path.join(path, 'graph.pickle') people_path = os.path.join(path, 'people.pickle') families_path = os.path.join(path, 'families.pickle') communities_path = os.path.join(path, 'communities.pickle') with open(people_path, 'rb') as f: self.people = pickle.load(f) with open(graph_path, 'rb') as f: self.graph = pickle.load(f) with open(families_path, 'rb') as f: self.families = pickle.load(f) with open(communities_path, 'rb') as f: self.communities = pickle.load(f) logger.info(f'Simulator model {model_name} loaded')
def quarantine_optimization_function(params, index: int = 0): """A sample objective function to quarantine certain roles. Args: index (int): The index of the running object, if applicable on cluster. params (Dict): The set of input parameters dictionary. """ simulator = Parser(folder_name='town_optimization').parse_simulator() end_time, spread_period, initialized_infected_ids, commands, observers = \ Parser(folder_name='town_optimization').parse_simulator_data() simulator.load_model('town_optimization') commands.clear() quarantine_effective_day = 2 condition_student = Time_Point_Condition(deadline=Time(timedelta(days=quarantine_effective_day))) condition_worker = Time_Point_Condition(deadline=Time(timedelta(days=quarantine_effective_day))) condition_customer = Time_Point_Condition(deadline=Time(timedelta(days=quarantine_effective_day))) commands.append(Restrict_Certain_Roles(condition=condition_student, role_name='Student', restriction_ratio=params['student_restriction_ratio'])) commands.append(Restrict_Certain_Roles(condition=condition_worker, role_name='Worker', restriction_ratio=params['worker_restriction_ratio'])) commands.append(Restrict_Certain_Roles(condition=condition_customer, role_name='Customer', restriction_ratio=1.4 - params['worker_restriction_ratio'] - params['student_restriction_ratio'])) logger.info(f'Restriction ratios are {[(command.role_name, command.restriction_ratio) for command in commands]}') logger.info(f'Quarantine starts at day {quarantine_effective_day}') simulator.simulate(end_time=Time(delta_time=timedelta(days=20), init_date_time=datetime.now()), spread_period=spread_period, initialized_infected_ids=initialized_infected_ids, commands=commands, observers=observers, report_statistics=1) stats, times = observers[0].get_disease_statistics_during_time(Health_Condition.IS_INFECTED) loss = max(stats) optimization_index = loss logger.info(f'Peak height is {loss}, and parameters are {params}') from cluster_utils import save_lists_csv save_lists_csv(data_lists=[stats, times], list_names=['statistics', 'time'], file_name='data_node_' + str(index) + '_' + str(optimization_index), folder_name='data_optimization') return {'loss': loss, 'params': params, 'status': STATUS_OK}
def display_disease_characteristics(disease_properties): """Print the statistics related to disease in terminal. Args: disease_properties (Disease_Properties): The disease properties object containing disease information. """ infectious_rate_distribution = disease_properties.infectious_rate_distribution immunity_distribution = disease_properties.immunity_distribution disease_period_distribution = disease_properties.disease_period_distribution death_probability_distribution = disease_properties.death_probability_distribution t = Texttable() t.add_rows([['Disease Property', 'Distribution Type', 'Parameters'], ['Infectious Rate', infectious_rate_distribution.__class__.__name__, infectious_rate_distribution.parameters_dict], ['Immunity Rate', immunity_distribution.__class__.__name__, immunity_distribution.parameters_dict], ['Disease Period', disease_period_distribution.__class__.__name__, disease_period_distribution.parameters_dict], ['Death Probability', death_probability_distribution.__class__.__name__, death_probability_distribution.parameters_dict]]) logger.info(f'\n{t.draw()}')
def simulate(self, end_time: Time, spread_period: Time, initialized_infected_ids: List[int], commands: List, observers: List, report_statistics: int = 1, database_name: Tuple = None, show_progress: bool = True): """The main simulator function that starts the simulation given a certain criteria. The simulation main loop is located inside this function, in addition to collecting the observers and commands (policy). Args: end_time (Time): The simulation end time. Determines the total period of the simulation. spread_period (Time): The spread period determines the period of running the virus spread function. initialized_infected_ids (List[int]): A list of infected people at the beginning of the simulation. commands (List[Command]): A list of actions given to the simulator to contract the spread of the virus. The acceptable commands are located in commands.py module. observers (List[Observer]): The observer object to record the data from simulation into the database. report_statistics (int, optional): This parameter determines the degree of statistics shown after the simulation concluded. Starting from 0, meaning no statistics at all, and goes all the way to 2, where all the available statistics are shown in the console. Defaults to 1. database_name (Tuple, optional): The name of the database in case a change is required. Default to None. show_progress (bool, optional): Whether to show the progress bar or not. Defaults to True. """ # Initialize simulation logger.info('Initializing the simulation') self.initialized_infected_ids = initialized_infected_ids self.end_time, self.spread_period = end_time, spread_period self.commands, self.observers = commands, observers self.database_name = database_name self.initialize_simulation() # Print a log and the progress indicator logger.info('Starting the simulation') with tqdm(total=self.end_time.get_minutes(), file=sys.stdout, disable=not show_progress) \ as progress_bar: # Simulation main loop while Time.check_less(self.clock, self.end_time): # Events event = heapq.heappop(self.events) event.activate(self) # Observations self.execute_observers() # Commands self.execute_commands() # Update clock self.clock.set_minutes(event.minute) # Show progress progress_bar.update(self.clock.get_minutes() - progress_bar.n) logger.info('Simulation completed') # Show statistics self.statistics.report(self, report_statistics) # Database termination self.database.commit()
def build_experiment_results_data_dict( number_of_experiments: int = 16, experiment_name_constant_part: str = 'data_town_', folder_name: str = 'data', experiment_folder: str = 'cluster_experiment_1', add_time: bool = False) -> Dict[int, List[Dict]]: """Build a dictionary containing the experiment folders at the first level and the individual executions in the second level. Note: The function assumes that the data is either stored in the same path as the function's execution path, or in the data folder and in the same path. TODO: This function needs a huge refactor afterward. Args: add_time (bool, optional): Add the times to the dictionary separately if required. experiment_folder (str, optional): The folder under json to determine the experiment configuration sets. Defaults to 'cluster_experiment_1'. number_of_experiments (int, optional): The total number of experiment folders in the data. experiment_name_constant_part: The beginning string of experiment result folders. Defaults to 'data_town_', e.g., data_town_0. folder_name(str, optional) The folder name of the experiments. Defaults to 'data'. It's also possible to just copy the data into cluster folder. Returns: Dict[int, List[Dict]]: The major dictionary containing the results and some specifications of all the experiments. """ # Change directory to the data folder (if exists) if os.path.exists(folder_name): os.chdir(folder_name) # Build the grand dictionary of data towns_data_dict: Dict[int, List[Dict]] = dict() for file_index in range(number_of_experiments): folder_name = experiment_name_constant_part + str(file_index) logger.info(f'Entering the {folder_name} directory') single_town_data_list = list() for file_name in os.listdir(folder_name): data_dict = dict() # TODO: change the following lines later. data = read_lists_csv(list_names=['statistics', 'time'], file_name=file_name, folder_name=folder_name) statistics, times = data data_dict['statistics'] = statistics[:len(statistics) - 1] data_dict['time'] = convert_time_str_to_datetime( times[:len(times) - 1]) if add_time: data_dict['simulation_time'] = float(times[-1]) data_dict['generation_time'] = statistics[-1] single_town_data_list.append(data_dict) towns_data_dict[file_index] = single_town_data_list # Move to cluster directory if not os.path.exists('data'): os.chdir(os.path.join(os.getcwd(), os.pardir)) # Use parser to complete the dicts for folder_index in towns_data_dict: json_parser = Parser( os.path.join(experiment_folder, 'town_' + str(folder_index))) simulator = json_parser.parse_simulator() population_size = simulator.population_generator.population_size immunity_distribution_dict = simulator.disease_properties.immunity_distribution.parameters_dict infectious_rate_dict = simulator.disease_properties.infectious_rate_distribution.parameters_dict for data_dict in towns_data_dict[folder_index]: data_dict['population_size'] = population_size data_dict[ 'immunity_distribution_dict'] = immunity_distribution_dict data_dict['infectious_rate_dict'] = infectious_rate_dict return towns_data_dict