Source code for backend.backendutils

"""
Backend Utility functions for creating AGE-ABM file
@Author Max Hall
@Co-Authors Meghan Ireland and Matthew Fleischman

"""

### Imports
import os

[docs] class WriteModelFile: ''' This is a base class which will store all the data relating to an instance of writing a model to a file this includes storing strings containing the code to be written into the imports, components, agents, systems and data collection and model sections as well as the main file ''' def __init__(self, file_name : str = "Custom"): self.file_name = file_name self.imports_section = "" # Code to import required libraries self.components_section = "" # Code for the components self.agents_section = "" # Code for the agents self.systems_section = "" # Code for the systems self.data_collection_section = "" # Code for the data collection systems self.model_section = "" # Code for the model self.main_file_section = ""
[docs] def create_imports_section(self, model): """Writes the required imports in the file desired format. Parameters ---------- model : model_env This allows the function to access the necessary info to write to the file Returns ------- error term : int | Returns 0 on success | Returns 1 if creation fails, prints error message in terminal """ try: self.imports_section += "\n" + "#"*10 + "IMPORTS" + "#"*10 + "\n\n" for req_import in model.get_list_imports(): library_details = model.get_import(req_import) # If importing with an alias if library_details.get_import_type() == "as": self.imports_section += f"import {req_import} as {library_details.get_nickname()}\n" # If importing specific things from the library elif library_details.get_import_type() == "from": list_of_imports = library_details.get_list_of_methods() self.imports_section += f"from {req_import}" + " import " + (list_of_imports[0] if len(list_of_imports) >= 1 else (list_of_imports[0] + "\n")) if len(list_of_imports) >= 1: for i in range(1, len(list_of_imports)): self.imports_section += f", {list_of_imports[i]}" self.imports_section += "\n" # If importing everything from the library elif library_details.get_import_type() == "*": self.imports_section += f"from {req_import} import *" self.imports_section += "\n\n" return 0 except Exception as exception: # pragma: no cover print(exception) return 1
[docs] def create_components_section(self, model): """Writes the components into the section. Parameters ---------- model : model_env This allows the function to access the necessary info to write to the file Returns ------- error term : int | Returns 0 on success | Returns 1 if creation fails, prints error message in terminal """ try: self.components_section += "#"*10 + "COMPONENTS" + "#"*10 + "\n\n" components_list = model.get_list_components() components_list.append("Counter") for comp in components_list: comp_data = model.get_component(comp) # Class header self.components_section += f"class {comp}Component(Core.Component):\n" # Writes in Comment description = comp_data.get_description() if comp_data.get_description() != "" else (comp + " Component") self.components_section += f"\t''' {description} '''\n" # Initialisation Method #component_attributes = comp_data.get_attributes() self.components_section += "\tdef __init__(self, agent : Core.Agent, model : Core.Model" for name in model.get_list_attributes(comp): attribute = model.get_attribute(comp, name) if attribute.get_define_level() != "comp_lvl": self.components_section += f", {name} : {attribute.get_type_attr()}" self.components_section += "):\n" # Calling the parent classes constructor self.components_section += "\t\tsuper().__init__(agent, model)\n" # Writing in the attributes for name in model.get_list_attributes(comp): attribute = model.get_attribute(comp, name) self.components_section += f"\t\tself.{name}" default_value = str(attribute.get_default_value()) if attribute.get_define_level() == "comp_lvl" else name self.components_section += f" = {default_value}\n" # Seperating Components self.components_section += "\n\n" return 0 except Exception as exception: # pragma: no cover print(exception) return 1
[docs] def create_agents_section(self, model): """Writes the agents into the section. Parameters ---------- model : model_env This allows the function to access the necessary info to write to the file Returns ------- error term : int | Returns 0 on success | Returns 1 if creation fails, prints error message in terminal """ try: self.agents_section += "#"*10 + "TAGS" + "#"*10 + "\n\n" for agent in model.get_list_agents(): self.agents_section += f"# Add {agent.capitalize()} Tag\n" self.agents_section += f"Tags.add_tag(\"{agent.upper()}\")\n" self.agents_section += "\n\n" + "#"*10 + "AGENTS" + "#"*10 + "\n\n" for agent in model.get_list_agents(): agent_data = model.get_agent(agent) agent_components = agent_data.get_given_components() agent_name = agent.capitalize() agent_tag = agent.upper() # Class header self.agents_section += f"class {agent_name}Agent(Core.Agent):\n" # Writes in Comment description = agent_data.get_description() if agent_data.get_description() != "" else (agent_name + " Agent") self.agents_section += f"\t''' {description} '''\n" # Writing Initialisation Method self.agents_section += "\tdef __init__(self, model : Core.Model" # Writing model level attributes for agents into initialisation for comp in agent_components: if model.get_component(comp).get_is_class_component(): continue for attr in model.get_list_attributes(comp): attribute = model.get_attribute(comp, attr) if attribute.get_define_level() == "model_lvl": self.agents_section += f", {attr} : {attribute.get_type_attr()}" # Writing agent level attributes for agents into initialisation with default None for comp in agent_components: for attr in model.get_list_attributes(comp): attribute = model.get_attribute(comp, attr) if attribute.get_define_level() == "agent_lvl": self.agents_section += f", {attr} : {attribute.get_type_attr()} = None" self.agents_section += "):\n\n" # Writing in the id definition self.agents_section += f"\t\tagent_id = \"{agent_name}\" + str({agent_name}Agent[CounterComponent].counter)\n" # Calling the parent classes constructor self.agents_section += f"\t\tsuper().__init__(agent_id, model, tag=Tags.{agent_tag})\n\n" # Writing in the components for comp in agent_components: if model.get_component(comp).get_is_class_component() is True: continue self.agents_section += f"\t\tself.add_component({comp.capitalize()}Component(self, model" for name in model.get_list_attributes(comp): attribute = model.get_attribute(comp, name) if attribute.get_define_level() in "agent_lvl": if agent not in attribute.get_default_value(): raise Exception("No attribute default value provided for this agent") self.agents_section += f", {name} = {name} if {name} is not None else {attribute.get_default_value()[agent]}" elif attribute.get_define_level() in "model_lvl": self.agents_section += f", {name} = {name}" self.agents_section += "))\n" # Seperating agents and increasing the counter to ensure unique self.agents_section += f"\n\t\t{agent_name}Agent[CounterComponent].counter += 1" self.agents_section += "\n\n" return 0 except Exception as exception: print(exception) return 1
[docs] def create_systems_section(self, model): """Writes the agents into the section. Parameters ---------- model : model_env This allows the function to access the necessary info to write to the file Returns ------- error term : int | Returns 0 on success | Returns 1 if creation fails, prints error message in terminal """ try: self.systems_section += "#"*10 + "SYSTEMS" + "#"*10 + "\n\n" for system in model.get_list_systems(): sys_data = model.get_system(system) # Class header self.systems_section += f"class {system}System(Core.System):\n" # Writes in Comment description = sys_data.get_description() if sys_data.get_description() != "" else (system + " System") self.systems_section += f"\t''' {description} '''\n" # Initialisation Method self.systems_section += "\tdef __init__(self, id : str, model : Core.Model" # Checks there are generators for this system if len(model.get_list_generators(system)) > 0: # Making sure the user provided attributes are passed in for generator in model.get_list_generators(system): for attribute in model.get_list_gen_attributes(system, generator): official_name = f"{system.upper()}_{generator.upper()}_{attribute.upper()}" attribute = model.get_gen_attribute(system, generator, attribute) self.systems_section += f", {official_name} : {attribute.get_type_attr()}" self.systems_section += "):\n" # Calling the parent classes constructor and adding in changes to system self.systems_section += "\t\tsuper().__init__(id, model" if sys_data.get_start() is not None: self.systems_section += f", start={sys_data.get_start()}" if sys_data.get_end() is not None: self.systems_section += f", end={sys_data.get_end()}" if sys_data.get_frequency() is not None: self.systems_section += f", frequency={sys_data.get_frequency()}" self.systems_section += ")\n\n" # Checks there are generators for this system if len(model.get_list_generators(system)) > 0: # Instantiating the user provided attributes for generator in model.get_list_generators(system): for attribute in model.get_list_gen_attributes(system, generator): official_name = f"{system.upper()}_{generator.upper()}_{attribute.upper()}" self.systems_section += f"\t\tself.{official_name} = {official_name}\n" self.systems_section += "\n" # Making sure initial resources are generated for generator in model.get_list_generators(system): curr_generator = model.get_generator(system, generator) fun_name = f"{generator}_generator" self.systems_section += f"\t\tdef {fun_name}(pos, cells):\n" self.systems_section += f"\t\t\t'''{curr_generator.get_description()}'''\n" self.systems_section += f"\t\t\treturn {curr_generator.get_return_value()}\n\n" self.systems_section += f"\t\tmodel.environment.add_cell_component(\'{curr_generator.get_nickname()}\', {fun_name})\n\n" self.systems_section += "\n\n" # Writing in the Execute function section self.systems_section += f"{sys_data.get_execute_function_body()}\n" # Seperating Systems self.systems_section += "\n\n" return 0 except Exception as exception: # pragma: no cover print(exception) return 1
[docs] def create_data_collection_section(self, model): """Writes data collection into the section. Parameters ---------- model : model_env This allows the function to access the necessary info to write to the file Returns ------- error term : int | Returns 0 on success | Returns 1 if creation fails, prints error message in terminal """ try: # Create basic class details self.data_collection_section += "#"*10 + "DATA COLLECTION" + "#"*10 + "\n\n" self.data_collection_section += "class DataCollector(Collectors.Collector):\n" self.data_collection_section += "\t''' data collection system '''\n" # Initialisation Method self.data_collection_section += "\tdef __init__(self, id : str, model):\n" # Calling the parent classes constructor self.data_collection_section += "\t\tsuper().__init__(id, model)\n" # Load in the users specified agents of interest and desired components interests = model.get_list_agents_of_interest() # Create records dictionary for each agent self.data_collection_section += "\t\tself.records = {" interests_count = len(interests) for key in interests: if interests_count != len(interests): self.data_collection_section += "\n\t\t\t, " agent = model.get_agent_of_interest(key) # Load agent of interest details into variables agent_name = key comp_list = agent.get_comp_of_interest() count = agent.get_count() position = agent.get_position() individual = agent.get_individual() mean_val = agent.get_mean_val() median_val = agent.get_median_val() total_val = agent.get_total_val() max_val = agent.get_max_val() min_val = agent.get_min_val() variance = agent.get_variance() std_dev = agent.get_std_dev() # start agent records self.data_collection_section += f"'{agent_name}_records' : " + "{" # Add count if requested if count and len(comp_list) != 0 or position: self.data_collection_section += "'count': [], " elif count: self.data_collection_section += "'count': []}" # Add position if requested if position and len(comp_list) != 0: self.data_collection_section += "'position': []," elif position and len(comp_list) == 0: self.data_collection_section += "'postion': []}" # Make list of true booleans bool_list = [] if individual: bool_list.append('individual') if mean_val: bool_list.append('mean_val') if median_val: bool_list.append('median_val') if total_val: bool_list.append('total_val') if max_val: bool_list.append('max_val') if min_val: bool_list.append('min_val') if variance: bool_list.append('variance_val') if std_dev: bool_list.append('std_dev_val') # Create data interests dictionary first = True data_of_interest = "{" for data_type in bool_list: if first: data_of_interest += f"'{data_type}' : []" first = False else: data_of_interest += f", '{data_type}' : []" data_of_interest += "}" # Add attributes of desired components and give data interests dictionary comp_attr_list = [] for comp in comp_list: attr_list = model.get_list_attributes(comp) comp_attr_list += [f"{comp}_{attr}" for attr in attr_list] index = 0 for comp_attr in comp_attr_list: if index < len(comp_attr_list)-1: self.data_collection_section += f"'{comp_attr}': {data_of_interest}, " else: self.data_collection_section += f"'{comp_attr}': {data_of_interest}" + "}" index += 1 interests_count -= 1 self.data_collection_section += "\n\t\t\t}\n" # Create collector method self.data_collection_section += "\n\tdef collect(self):\n" for key in interests: agent = model.get_agent_of_interest(key) # Load agent of interest details into variables agent_name = key comp_list = agent.get_comp_of_interest() count = agent.get_count() position = agent.get_position() individual = agent.get_individual() mean_val = agent.get_mean_val() median_val = agent.get_median_val() total_val = agent.get_total_val() max_val = agent.get_max_val() min_val = agent.get_min_val() variance = agent.get_variance() std_dev = agent.get_std_dev() # Specify which agent is being collected self.data_collection_section += f"\n\t# {agent_name} Collector\n" # Collect count of agents if required and add to records if count: self.data_collection_section += "\t\t# Collect count\n" self.data_collection_section += f"\t\tself.records['{agent_name}_records']['count'].append(" self.data_collection_section += f"len(self.model.environment.get_agents(tag=Tags.{agent_name.upper()})))\n" # Get IDs of agents if there are desired components if len(comp_list) != 0 or position: if count: self.data_collection_section += "\n\t\t# Get list of agent IDs\n" else: self.data_collection_section += "\t\t# Get list of agent IDs\n" self.data_collection_section += f"\t\t{agent_name}_agent_ids = self.model.environment.get_agents(tag=Tags.{agent_name.upper()})\n\n" self.data_collection_section += "\t\t# Get attribute data and append to respecitve lists\n" # Create position and attribute lists if position: self.data_collection_section += "\t\tposition_values = {}\n" for comp in comp_list: attr_list = model.get_list_attributes(comp) for attr in attr_list: self.data_collection_section += f"\t\t{attr}_values = []\n" # Add the attribute data for each agent id to the attribute lists and collect position if position or comp_list != []: self.data_collection_section += f"\t\tfor agent_id in {agent_name}_agent_ids:\n" if position: self.data_collection_section += "\t\t\tposition_values[agent_id.id] = list(agent_id.get_component(PositionComponent).xy())\n" for comp in comp_list: attr_list = model.get_list_attributes(comp) for attr in attr_list: # If boolean, convert to 1 or 0 if model.get_attribute(comp, attr).get_type_attr() == "bool": self.data_collection_section += f"\t\t\t{attr}_val = int(agent_id.get_component({comp.capitalize()}Component).{attr})\n" self.data_collection_section += f"\t\t\t{attr}_values.append({attr}_val)\n" # Otherwise just append value else: self.data_collection_section += f"\t\t\t{attr}_values.append(agent_id.get_component({comp.capitalize()}Component).{attr})\n" # Add the position and interested data for each attribute to records if position: self.data_collection_section += "\n\t\t# Add positions to records\n" self.data_collection_section += f"\t\tself.records['{agent_name}_records']['position'].append(position_values)\n" for comp in comp_list: attr_list = model.get_list_attributes(comp) for attr in attr_list: self.data_collection_section += f"\n\t\t# Add interests of {attr} data to {agent_name} records\n" if individual: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['individual'].append({attr}_values)\n" if mean_val: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['mean_val'].append(np.mean({attr}_values).tolist())\n" if median_val: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['median_val'].append(np.median({attr}_values).tolist())\n" if total_val: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['total_val'].append(np.sum({attr}_values).tolist())\n" if max_val: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['max_val'].append(np.max({attr}_values).tolist())\n" if min_val: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['min_val'].append(np.min({attr}_values).tolist())\n" if variance: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['variance_val'].append(np.var({attr}_values).tolist())\n" if std_dev: self.data_collection_section += f"\t\tself.records['{agent_name}_records']['{comp}_{attr}']['std_dev_val'].append(np.std({attr}_values).tolist())\n" self.data_collection_section += "\n\n" return 0 except Exception as exception: # pragma: no cover print(exception) return 1
[docs] def create_model_section(self, model): """Writes the model into its section. Parameters ---------- model : model_env This allows the function to access the necessary info to write to the file Returns ------- error term : int | Returns 0 on success | Returns 1 if creation fails, prints error message in terminal """ try: self.model_section += "#"*10 + "MODEL" + "#"*10 + "\n\n" model_data = model.get_model_details() model_name = model_data.get_name() # Class header self.model_section += f"class {model_name}Model(Core.Model):\n" # Writes in Comment description = model_data.get_description() if model_data.get_description() != "" else (model_name + " Model") self.model_section += f"\t\'\'\' {description} \'\'\'\n" # Initialisation Method self.model_section += "\tdef __init__(self" parameters = model_data.get_list_initial_parameters() # Agents initial/overall counts are stored in the following: agents = model_data.get_list_num_agents() for agent in agents: self.model_section += f", INIT_{agent}_AGENT : int" # Adding in the remainding parameters for parm in parameters: parm_data = model_data.get_initial_parameter(parm) self.model_section += f", {parm} : {parm_data[0]}" # Adding in the seed and ending off the section self.model_section += ", SEED : int = None):\n" # Calling parent class constructor self.model_section += "\t\tsuper().__init__(seed=SEED)\n\n" # Adding gridworld functionality if needed if model_data.get_gridworld(): self.model_section += "\t\tself.environment = GridWorld(self, WIDTH, HEIGHT)\n\n" # Initialising Systems for system in model.get_list_systems(): self.model_section += f"\t\tself.systems.add_system({system}System(\"{system}\", self" for generator in model.get_list_generators(system): for attr in model.get_list_gen_attributes(system, generator): self.model_section += f", {system.upper()}_{attr.upper()}" self.model_section += "))\n" # Initialising Data Collector if needed if len(model.get_list_agents_of_interest()) > -1: self.model_section += "\t\tself.systems.add_system(DataCollector(\"collector\", self))\n" self.model_section += "\n" # Adding in class components to respective agents for agent in model.get_list_agents(): agent_name = agent.capitalize() # Writes in the Class Components Initialisations given_components = model.get_agent(agent).get_given_components() # Must account for the Counter Class Components given_components.append("Counter") # Adding in the class components for comp in given_components: if model.get_component(comp).get_is_class_component() is True: self.model_section += f"\t\t{agent_name}Agent.add_class_component({comp.capitalize()}Component({agent_name}Agent, self" if comp == "Counter": self.model_section += "))\n" continue for attr in model.get_list_attributes(comp): self.model_section += f", {agent_name.upper()}_{comp.upper()}_{attr.upper()}" self.model_section += "))\n" self.model_section += "\n" self.model_section += "\n" # Adding agents for agent in model.get_list_agents(): self.model_section += f"\t\tfor _ in range(INIT_{agent.upper()}_AGENT):\n" agent_name = agent.lower().capitalize() self.model_section += f"\t\t\tself.environment.add_agent({agent_name}Agent(self" # Allowing model level attributes to be passed in as parameters to the agent for comp in model.get_agent(agent).get_given_components(): if not model.get_component(comp).get_is_class_component(): for attr in model.get_list_attributes(comp): if model.get_attribute(comp, attr).get_define_level() == "model_lvl": self.model_section += f", {attr} = {comp.upper()}_{attr.upper()}" self.model_section += ")" if model_data.get_gridworld(): init_coordinates = model.get_agent(agent).get_init_coordinates() if init_coordinates is None: raise Exception("No initial co-ordinates provided for this agent") self.model_section += f", x_pos = {init_coordinates[0]}, y_pos = {init_coordinates[1]}" self.model_section += ")\n\n" # Adding the run function self.model_section += "\tdef run(self , sim_length : int):\n\t\tself.execute(sim_length)\n" return 0 except Exception as exception: print(exception) return 1
[docs] def create_main_file_section(self, model, visualise_model : bool = False): """Writes the code required for the user to execute the model. Parameters ---------- model : model_env This allows the function to access the necessary info to write to the file Returns ------- error term : int | Returns 0 on success | Returns 1 if creation fails, prints error message in terminal """ try: self.main_file_section += f"from {self.file_name} import *\n" self.main_file_section += "import argparse\n" if visualise_model: self.main_file_section += "from jsonpickle import dumps, decode\n" self.main_file_section += "import os\n" self.main_file_section += "\n\n" + "#"*10 + "MAIN FILE" + "#"*10 + "\n\n" model_data = model.get_model_details() model_name = model_data.get_name() # Agents initial/overall counts are stored in the following: agents = model_data.get_list_num_agents() parameters = model_data.get_list_initial_parameters() # Writing in initial values as provided by user for num_agents and initial_parameters final_parms = [] for agent in agents: self.main_file_section += f"INIT_{agent}_AGENT = {model_data.get_num_agent(agent)}\n" final_parms.append(f"INIT_{agent}_AGENT") # Adding in the remainding parameters for parm in parameters: parm_data = model_data.get_initial_parameter(parm) self.main_file_section += f"{parm} = {str(parm_data[1])}\n" final_parms.append(parm) self.main_file_section += f"\n\nSEED = {model_data.get_sim_seed()}\n" final_parms.append("SEED") self.main_file_section += f"ITERATIONS = {model_data.get_sim_length()}\n\n\n" # Adds in the arg parse section on the main file self.main_file_section += "# This gets the args from the user and runs them as the model\n" self.main_file_section += "parser = argparse.ArgumentParser(description='This is your main file which executes your model based on provided initial parameters or new ones you parse.')\n" for parameter in agents: self.main_file_section += f"parser.add_argument('--init_{parameter.lower()}_agent', type=int, help='Type : int - Initial agent values for the {parameter.lower()} Agent', default=INIT_{parameter}_AGENT)\n" for parameter in parameters: type_of_parm = model_data.get_initial_parameter(parameter)[0] self.main_file_section += f"parser.add_argument('--{parameter.lower()}', type={type_of_parm}, help='Type : {type_of_parm} - Initial value for the {parameter.lower()} parameter', default={parameter})\n" self.main_file_section += "parser.add_argument('--seed', type=int, help='Type : int - Seed for the model', default=SEED)\n" self.main_file_section += "parser.add_argument('--iterations', type=int, help='Type : int - Number of iterations', default=ITERATIONS)\n" self.main_file_section += "\nargs = parser.parse_args()\n\n" # Stores the line which will execute the model self.main_file_section += f"myModel = {model_name}Model(args.{final_parms[0].lower()}" for i in range(1, len(final_parms)): self.main_file_section += f", args.{final_parms[i].lower()}" # Instantiating the model and running it self.main_file_section += ")\n" self.main_file_section += "myModel.run(ITERATIONS)\n" ### PHASE 2 - Visualisation ### if visualise_model: self.main_file_section += "\n\n# This Section Stores the Data Generated in the Model\n" self.main_file_section += "\nmy_recorded_data = myModel.systems['collector'].records\n\n" # Writing to new file self.main_file_section += "if not os.path.isdir('dataFiles'):\n" self.main_file_section += "\tos.makedirs('dataFiles')\n" self.main_file_section += "with open((\"dataFiles/records.json\"), 'w') as file:\n" self.main_file_section += "\tencoded_data = dumps(my_recorded_data)\n" self.main_file_section += "\tfile.write(encoded_data)\n" return 0 except Exception as exception: # pragma: no cover print(exception) return 1
[docs] def create_model_file(self): """Writes the model into the file. Returns ------- error term : int | Returns 0 on success | Returns 1 if write fails, prints error message in terminal """ try: if not os.path.isdir("runtimeFiles"): os.makedirs("runtimeFiles") with open(f"runtimeFiles/{self.file_name}.py", 'w', encoding='UTF-8') as file: file.write(self.imports_section + self.components_section + self.agents_section + self.systems_section + self.data_collection_section + self.model_section) print(f"Done Creating runtimeFiles/{self.file_name}.py") return 0 except Exception as exception: # pragma: no cover print(exception) print(f"Failed Creating runtimeFiles/{self.file_name}.py") return 1
[docs] def create_main_file(self): """Writes the main/runtime into the file. Returns ------- error term : int | Returns 0 on success | Returns 1 if write fails, prints error message in terminal """ try: if not os.path.isdir("runtimeFiles"): os.makedirs("runtimeFiles") with open(f"runtimeFiles/{self.file_name}_main.py", 'w', encoding='UTF-8') as file: file.write(self.main_file_section) print(f"Done Creating runtimeFiles/{self.file_name}_main.py") return 0 except Exception as exception: # pragma: no cover print(exception) print(f"Failed Creating runtimeFiles/{self.file_name}_main.py") return 1