Source code for backend.backend

"""
Backend for AGE-ABM Visual Interface
@Author Max Hall
@Co-Authors Meghan Ireland and Matthew Fleischman

"""

# Imports
import os
from jsonpickle import dumps, decode
import backend.backendutils as utils
from backend.backend_classes import ImportDetails, Attribute, Component, Agent, System, Generator
from backend.backend_classes import AgentOfInterest, ModelDetails
from utils.helper_methods import adding_name_checker, getting_name_checker, get_data_types


[docs] class ModelEnv: """ This is a base class which will store all the data relating to an instance of creating a new model using the GUI this includes storing dictionaries containing imports, components, agents, systems and data collection systems classes as well as a model details class """ def __init__(self, name : str = "Custom", exist_proj : str = None): if exist_proj is not None: # pragma: no cover with open(exist_proj, 'r', encoding='UTF-8') as file: all_data = decode(file.read()) # Stores the imports classes self.imports = all_data["imports"] # Stores components classes self.components = all_data["components"] # Stores agents classes self.agents = all_data["agents"] # Stores systems classes self.systems = all_data["systems"] # Stores the model class self.model_details = all_data["model_details"] # Stores the agents_of_interest classes self.agents_of_interest = all_data["agents_of_interest"] else: formatted_name = adding_name_checker(name, []) if formatted_name in [1,2]: formatted_name = "Default_Name" # Stores import details classes self.imports = {"ECAgent.Core" : ImportDetails("Core", "as") , "ECAgent.Tags" : ImportDetails("Tags", "as") , "ECAgent.Collectors" : ImportDetails("Collectors", "as") , "numpy" : ImportDetails("np", "as")} # Stores components classes self.components = {"Counter" : Component("This keeps count of number of agents", True , {"counter" : Attribute("int", "comp_lvl", 0)})} # Stores agents classes self.agents = {} # Stores systems classes self.systems = {} # Stores the model class self.model_details = ModelDetails(formatted_name, f"The {formatted_name} Model") # Stores data collection system classes self.agents_of_interest = {}
[docs] def get_list_imports(self): """Gets a list of all imports added to the model. Returns ------- Attribute : list[str] | Returns a list of imports names. """ if self.imports: return list(self.imports) return []
[docs] def get_list_components(self): """Gets a list of all components added to the model. Returns ------- Attribute : list[str] | Returns a list of components names. """ if self.components: arr1 = list(self.components) arr1.remove("Counter") return arr1 return []
[docs] def get_list_attributes(self, relevent_component : str): """Gets a list of attributes which a relevent_component has. Parameters ---------- relevent_component : str The relevent_component having the attributes names. Returns ------- Attribute : list[str] | Returns a list of that components attributes names. """ # Tries to format the name to correct format formatted_name = getting_name_checker(relevent_component, list(self.components)) # Checks for error messages if formatted_name == 1: return [] # Name already in use return list(self.components[formatted_name].get_attributes())
[docs] def get_list_agents(self): """Gets a list of all agents added to the model. Returns ------- Attribute : list[str] | Returns a list of agents names """ if self.agents: return list(self.agents) return []
[docs] def get_list_systems(self): """Gets a list of all systems added to the model. Returns ------- Attribute : list[str] | Returns a list of systems names """ if self.systems: return list(self.systems) return []
[docs] def get_list_gen_attributes(self, relevent_system : str, relevent_generator : str): """Gets a list of attributes which a relevent_generator has. Parameters ---------- relevent_system : str The relevent_system having the generators names. relevent_generator : str The relevent_generator having the attributes names. Returns ------- Attributes : list[str] | Returns a list of that generators attributes names """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name == 1: return [] # Name not already in use # Getting the formatted name formatted_gen_name = getting_name_checker(relevent_generator , list(self.get_list_generators(formatted_system_name))) # Checks for error messages if formatted_gen_name == 1: return [] # Name not already in use if self.get_generator(formatted_system_name, formatted_gen_name).get_attributes(): return list(self.get_generator(formatted_system_name , formatted_gen_name).get_attributes()) return []
[docs] def get_list_generators(self, relevent_system : str): """Gets a list of generators which a relevent_system has. Parameters ---------- relevent_system : str The relevent_system having the generators names. Returns ------- Generators : list[str] | Returns a list of that components attributes """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name == 1: return [] # Name already in use if self.get_system(formatted_system_name).get_generators(): return list(self.get_system(formatted_system_name).get_generators()) return []
[docs] def get_list_agents_of_interest(self): """Gets a list of all agents_of_interest added to the model. Returns ------- Attribute : list[str] | Returns a list of agents_of_interest names """ if self.agents_of_interest: return list(self.agents_of_interest) return []
[docs] def add_import(self, library : str, nickname : str, import_type : str = "*" , list_of_methods : list[str] = None): """Stores the newly created import into the models imports dictionary. Parameters ---------- library : str The library library: str The nickname for the library being imported import_type : str "as" implies it has an alias, "from" implies it is a list of things being imported from the library, "*" imports everything from the library and is the default list_of_methods : list[str], Optional If import_type is "from", then this is the list of things to be imported from the library Returns ------- error term : int | Returns 0 on success | Returns 1 if library import already exist | Returns 2 if invalid import_type used | Returns 3 if type is from and no methods selected | Returns 4 if nickname already in use """ if library in self.imports: return 1 if import_type not in ["as", "from", "*"]: return 2 if import_type == "from" and (list_of_methods is None or list_of_methods == []): return 3 for import_lib in self.get_list_imports(): if nickname == self.get_import(import_lib).get_nickname(): return 4 self.imports[library] = ImportDetails(nickname, import_type, list_of_methods) return 0
[docs] def edit_import(self, library : str, nickname : str = None, import_type : str = None , list_of_methods : list[str] = None): """Edits the provided named import into the models imports dictionary. Must specify what import you would like to edit. Parameters ---------- library : str The library nickname: str, Optional The nickname for the library being imported import_type : str, Optional "as" implies it has an alias, "from" implies it is a list of things being imported from the library, "*" imports everything from the library and is the default list_of_methods : list[str], Optional If import_type is "from", then this is the list of things to be imported from the library Returns ------- error term : int | Returns 0 on success | Returns 1 if library import doesn't already exist | Returns 2 if nickname already in use | Returns 3 if invalid import_type used | Returns 4 if type is from and no methods selected """ if library not in self.imports: return 1 if nickname is not None: for import_lib in self.get_list_imports(): if nickname == self.get_import(import_lib).get_nickname(): return 2 self.get_import(library).edit_nickname(nickname) if import_type is not None: if import_type not in ["as", "from", "*"]: return 3 if import_type != "from": self.get_import(library).edit_list_of_methods(None) if import_type == "from" and (list_of_methods is None or len(list_of_methods) == 0): return 4 if import_type == "from": self.get_import(library).edit_list_of_methods(list_of_methods) self.get_import(library).edit_import_type(import_type) if list_of_methods is not None: if import_type is None and self.get_import(library).get_import_type() != "from": return 3 if len(list_of_methods) == 0: return 4 self.get_import(library).edit_list_of_methods(list_of_methods) return 0
[docs] def get_import(self, library : str): """Gets the requested import from the models imports dictionary. Parameters ---------- library : str The import's library. Returns ------- library : ImportDetails | Returns an ImportDetails object containing the details from the requested import. | Returns None if library import doesn't already exist. """ if library in self.imports: return self.imports[library] return None
[docs] def remove_import(self, library : str): """Removes a set import from the models imports dictionary. Parameters ---------- library : str The library of the import to be removed. Returns ------- error term : int | Returns 0 on success | Returns 1 if library import doesn't already exist """ if library in self.imports: if library == "ECAgent.Environments": self.edit_model_details(gridworld=False) else: del self.imports[library] return 0 return 1
[docs] def add_attribute(self, relevent_component : str, name : str, type_attr : str , define_level : str, default_value : int = None): """Stores the newly created attribute into the models attributes dictionary. Parameters ---------- relevent_component : str The relevent_component having the attributes name. name : str The attribute's name. type_attr: str ["int", "float", "bool"] The primitive type expected. default_value : int, Optional If there is a default value for the attribute, else it defaults to 'None' define_level : str, Optional ['comp_lvl', 'agent_lvl', 'model_lvl'] If there is a user defined level, else it defaults to 'None' Returns ------- error term : int | Returns 0 on success | Returns 1 if attribute name already exists | Returns 2 if invalid characters used in attribute name | Returns 3 if type attribute not valid | Returns 4 if no default value provided | Returns 5 if invalid define level used """ # Tries to format the name to correct format formatted_comp_name = getting_name_checker(relevent_component, self.get_list_components()) # Checks for error messages if formatted_comp_name in [1,2, "Counter"]: return 1 # Name not already in use or invalid chars # Tries to format the name to correct format formatted_name = adding_name_checker(name , list(self.components[formatted_comp_name].get_attributes()), True) # Checks for error messages if formatted_name in [1,2]: return formatted_name # Name already in use or invalid chars if type_attr not in ["int", "float", "bool"]: return 3 # Handles different creation based on define_level if define_level == "comp_lvl": if default_value is None: return 4 self.components[formatted_comp_name].get_attributes()[formatted_name] = Attribute(type_attr, "comp_lvl", default_value) elif define_level == "agent_lvl": self.components[formatted_comp_name].get_attributes()[formatted_name] = Attribute(type_attr, "agent_lvl", {}) elif define_level == "model_lvl": self.components[formatted_comp_name].get_attributes()[formatted_name] = Attribute(type_attr, "model_lvl") if self.get_component(formatted_comp_name).get_is_class_component() is False: # Only add it at the agent_lvl self.model_details.add_initial_parameter(f"{formatted_comp_name.upper()}_{formatted_name.upper()}", type_attr) else: return 5 # If the define level isn't a valid one return 0
[docs] def add_agent_lvl_default_value(self, agent_name : str, component_name : str , attribute_name : str, default_value : str): """Edits the provided default value for the named attribute into the models attribute's default value dictionary. Parameters ---------- agent_name : str The agent_name having the attribute_name. component_name : str The component_name having the attribute_name. attribute_name : str The attribute_name having the default_value. default_value : int The default value code for the attribute. Returns ------- error term : int | Returns 0 on success | Returns 1 if name doesn't already exists | Returns 2 if invalid characters used in name | Returns 3 if agent level define level not used """ # Getting the formatted name formatted_agent_name = getting_name_checker(agent_name, self.get_list_agents()) # Checks for error messages if formatted_agent_name in [1,2]: return formatted_agent_name # Name not already in use or invalid chars formatted_comp_name = getting_name_checker(component_name , self.get_agent(formatted_agent_name).get_given_components()) # Checks for error messages if formatted_comp_name in [1,2]: return formatted_comp_name # Name not already in use or invalid chars formatted_attr_name = getting_name_checker(attribute_name , self.get_list_attributes(formatted_comp_name), True) # Checks for error messages if formatted_attr_name in [1,2]: return formatted_attr_name # Name already in use or invalid chars attribute = self.get_attribute(formatted_comp_name, formatted_attr_name) if attribute.get_define_level() != "agent_lvl": return 3 # If isn't an agent level attribute attribute.get_default_value()[formatted_agent_name] = default_value return 0
[docs] def edit_attribute(self, relevent_component : str, name : str, type_attr : str = None , define_level : str = None, default_value : int = None): """Edits the provided named attribute into the models attributes dictionary. Must specify what attribute you would like to edit. Parameters ---------- relevent_component : str The relevent_component having the attributes name. name : str The attribute's name. type_attr: str, Optional The primitive type expected. default_value : int, Optional If there is a default value for the attribute. define_level : str, Optional ['comp_lvl', 'agent_lvl', 'model_lvl'] If there is a user defined level, else it defaults to 'None'. Returns ------- error term : int | Returns 0 on success | Returns 1 if attribute name doesn't already exists | Returns 2 if invalid characters used in attribute name | Returns 3 if invalid attribute type | Returns 4 if invalid define level | Returns 5 if define level is component, but no default value given """ # Getting the formatted name formatted_comp_name = getting_name_checker(relevent_component, self.get_list_components()) # Checks for error messages if formatted_comp_name in [1,2, "Counter"]: return 1 # Name not already in use or invalid chars # Getting the formatted name formatted_name = getting_name_checker(name , list(self.components[formatted_comp_name].get_attributes()), True) # Checks for error messages if formatted_name in [1,2]: return formatted_name # Name not already in use or invalid chars chosen_attribute = self.components[formatted_comp_name].get_attributes()[formatted_name] if type_attr is not None: if type_attr not in ["bool", "int", "float"]: return 3 chosen_attribute.edit_type_attr(type_attr) if define_level is not None: if define_level not in ["comp_lvl", "agent_lvl", "model_lvl"]: return 4 # If is now "model_lvl" old_lvl = self.get_attribute(formatted_comp_name, formatted_name).get_define_level() if define_level == "model_lvl" and define_level != old_lvl: if self.get_component(formatted_comp_name).get_is_class_component() is False: self.model_details.add_initial_parameter(f"{formatted_comp_name.upper()}_{formatted_name.upper()}", chosen_attribute.get_type_attr()) self.get_attribute(formatted_comp_name, formatted_name).edit_default_value(None) # If was previously "model_lvl" elif old_lvl == "model_lvl" and define_level != old_lvl: self.model_details.remove_initial_parameter(f"{formatted_comp_name.upper()}_{formatted_name.upper()}") # If was comp_lvl and now isn't if define_level != "comp_lvl": chosen_attribute.edit_default_value(None) if define_level == "comp_lvl" and default_value is None: return 5 if define_level == "agent_lvl": chosen_attribute.edit_default_value({}) chosen_attribute.edit_define_level(define_level) if default_value is not None: if define_level == "agent_lvl" and str(type(default_value)) != "<class 'dict'>": chosen_attribute.edit_default_value({}) chosen_attribute.edit_default_value(default_value) return 0
[docs] def get_attribute(self, relevent_component : str, name : str): """Gets the requested attribute from the models attributes dictionary. Parameters ---------- relevent_component : str The relevent_component having the attributes name. name : str The attribute's name. Returns ------- Attribute : dict | Returns a dictionary containing the details from the requested attribute. | Returns None if attribute doesn't already exist. """ # Getting the formatted name formatted_comp_name = getting_name_checker(relevent_component, list(self.components)) # Checks for error messages if formatted_comp_name in [1,2]: return None # Name not already in use or invalid chars # Getting the formatted name formatted_name = getting_name_checker(name , list(self.components[formatted_comp_name].get_attributes()), True) # Checks for error messages if formatted_name in [1,2]: return None # Name already in use or invalid chars return self.get_component(formatted_comp_name).get_attributes()[formatted_name]
[docs] def remove_attribute(self, relevent_component : str, name : str): """Removes a set attribute from the models attributes dictionary. Parameters ---------- relevent_component : str The relevent_component having the attributes name. name : str The name of the attribute to be removed. Returns ------- error term : int | Returns 0 on success | Returns 1 if attribute name doesn't already exists | Returns 2 if invalid characters used in attribute name """ # Getting the formatted name formatted_comp_name = getting_name_checker(relevent_component, self.get_list_components()) # Checks for error messages if formatted_comp_name in [1,2, "Counter"]: return 1 # Name not already in use or invalid chars # Getting the formatted name formatted_name = getting_name_checker(name , list(self.components[formatted_comp_name].get_attributes()), True) # Checks for error messages if formatted_name in [1,2]: return formatted_name # Name already in use or invalid chars if self.get_attribute(relevent_component, formatted_name).get_define_level() == "model_lvl": self.model_details.remove_initial_parameter(f"{relevent_component.upper()}_{formatted_name.upper()}") del self.components[relevent_component].get_attributes()[formatted_name] return 0
[docs] def add_component(self, name : str, description : str, is_class_component : bool = False , attributes : dict = None): """Stores the newly created component into the models components dictionary. Parameters ---------- name : str The component's name. description : str A description of the component. is_class_component : bool, Optional User can specify if this is a class component or not. attributes : dict User provided attributes of the components. Returns ------- error term : int | Returns 0 on success | Returns 1 if component name already exists | Returns 2 if invalid characters used in component name """ # Tries to format the name to correct format formatted_name = adding_name_checker(name, list(self.components)) # Checks for error messages if formatted_name in [1, "Counter"]: return 1 # Name already in use if formatted_name == 2: return 2 # Name uses invalid chars if attributes is None: attributes = {} self.components[formatted_name] = Component(description, is_class_component, attributes) return 0
[docs] def edit_component(self, name : str, description : str = None , is_class_component : bool = None): """Edits the provided named component into the models components dictionary. Parameters ---------- name : str The component's name. description : str, Optional A description of the component. attributes : dict, Optional User provided attributes for the component. is_class_component : bool, Optional Tells the viewer if the component is a class component. Returns ------- error term : int | Returns 0 on success | Returns 1 if component name doesn't already exists | Returns 2 if invalid characters used in component name """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.components)) # Checks for error messages if formatted_name in [1, "Counter"]: return 1 # Name not already in use if description is not None: self.get_component(formatted_name).edit_description(description) if is_class_component is not None: self.get_component(formatted_name).edit_is_class_component(is_class_component) return 0
[docs] def get_component(self, name : str): """Gets the requested component from the models components dictionary. Parameters ---------- name : str The component's name. Returns ------- component : Component | Returns the component object correlating to the given name. | Returns None if name doesn't correspond to existing component """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.components)) # Checks for error messages if formatted_name == 1: return None # Name not already in use return self.components[formatted_name]
[docs] def remove_component(self, name : str): """Removes a set component from the models component dictionary. Parameters ---------- name : str The name of the component to be removed. Returns ------- error term : int | Returns 0 on success | Returns 1 if component name doesn't already exists | Returns 2 if invalid characters used in component name """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.components)) # Checks for error messages if formatted_name in [1, "Counter"]: return 1 # Name not already in use given_attributes = self.get_list_attributes(formatted_name) for attr in given_attributes: if self.get_attribute(formatted_name, attr).get_define_level() == "model_lvl": self.model_details.remove_initial_parameter(f"{formatted_name.upper()}_{attr.upper()}") del self.components[formatted_name] return 0
[docs] def add_agent(self, name : str, description : str, given_components : list[str] , init_coordinates : list[str] = None): """Stores the newly created agent into the models agents dictionary. Gets parameters from components Parameters ---------- name : str The agent's name. description : str A description of the agent. given_components : str[] User provided attributes of the agents. Returns ------- error term : int | Returns 0 on success | Returns 1 if agent name already exists | Returns 2 if invalid characters used in agent name | Returns 3 if invalid component name supplied | Returns 4 if gridworld and no initial co-ordinates supplied """ given_components = given_components.copy() formatted_comp_names = [getting_name_checker(comp, list(self.get_list_components())) for comp in given_components] if 1 in formatted_comp_names: return 3 # A name is not currently a component # Making sure the agent has a counter component in order to keep its ID's unique if "Counter" not in formatted_comp_names: formatted_comp_names.append("Counter") # Tries to format the name to correct format formatted_name = adding_name_checker(name, list(self.agents)) # Checks for error messages if formatted_name == 1: return 1 # Name already in use if formatted_name == 2: return 2 # Name uses invalid chars # Adding the agent if self.model_details.get_gridworld(): if init_coordinates is None: return 4 self.agents[formatted_name] = Agent(description, formatted_comp_names, init_coordinates) else: self.agents[formatted_name] = Agent(description, formatted_comp_names) self.model_details.add_num_agent(formatted_name.upper()) for comp in formatted_comp_names: if self.get_component(comp).get_is_class_component() and comp != "Counter": for attr in self.get_list_attributes(comp): attribute = self.get_attribute(comp, attr) self.model_details.add_initial_parameter(f"{formatted_name.upper()}_{comp.upper()}_{attr.upper()}",attribute.get_type_attr()) return 0
[docs] def edit_agent(self, name : str, description : str = None, given_components : list[str] = None , init_coordinates : list[str] = None): """Edits the provided named agent into the models agents dictionary. Parameters ---------- name : str The agent's name. description : str, Optional A description of the agent. given_components : list[str], Optional User provided components for the agent. Returns ------- error term : int | Returns 0 on success | Returns 1 if agent name doesn't already exists | Returns 2 if invalid characters used in agent name | Returns 3 if invalid component name given | Returns 4 if gridworld and no initial co-ordinates supplied """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.agents)) # Checks for error messages if formatted_name == 1: return 1 # Name not already in use if description is not None: self.get_agent(formatted_name).edit_description(description) if given_components is not None: given_components = given_components.copy() formatted_comp_names = [getting_name_checker(comp_name, list(self.get_list_components())) for comp_name in given_components] if 1 in formatted_comp_names: return 3 # A name is not currently a component # Making sure the agent has a counter component in order to keep its ID's unique if "Counter" not in formatted_comp_names: formatted_comp_names.append("Counter") # Getting the non-overlapping components uncommon_components = [comp for comp in self.get_agent(formatted_name).get_given_components() if comp not in formatted_comp_names] new_components = [comp for comp in formatted_comp_names if comp not in self.get_agent(formatted_name).get_given_components()] # Removes initial value requirement for discarded class components for comp in uncommon_components: if self.get_component(comp).get_is_class_component(): for attr in self.get_list_attributes(comp): self.model_details.remove_initial_parameter(f"{formatted_name.upper()}_{comp.upper()}_{attr.upper()}") # Adds initial value requirement for new class components for comp in new_components: if self.get_component(comp).get_is_class_component() and comp != "Counter": for attr in self.get_list_attributes(comp): attribute = self.get_attribute(comp, attr) self.model_details.add_initial_parameter(f"{formatted_name.upper()}_{comp.upper()}_{attr.upper()}", attribute.get_type_attr()) self.get_agent(formatted_name).edit_given_components(formatted_comp_names) if self.model_details.get_gridworld(): if init_coordinates is None and self.get_agent(formatted_name).get_init_coordinates() is None: return 4 if init_coordinates is not None: init_coordinates = init_coordinates.copy() self.get_agent(formatted_name).edit_init_coordinates(init_coordinates) return 0
[docs] def get_agent(self, name : str): """Gets the requested agent from the models agents dictionary. Parameters ---------- name : str The agent's name. Returns ------- agent : Agent | Returns an Agent object corresponding to the given name. | Returns None if no match. """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.agents)) # Checks for error messages if formatted_name == 1: return None # Name already in use return self.agents[formatted_name]
[docs] def remove_agent(self, name : str): """Removes a set agent from the models agents dictionary. Parameters ---------- name : str The name of the agent to be removed. Returns ------- error term : int | Returns 0 on success | Returns 1 if agent name doesn't already exists | Returns 2 if invalid characters used in agent name """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.agents)) # Checks for error messages if formatted_name == 1: return 1 # Name already in use # Removes initial value requirement for discarded agents' class components given_components = self.get_agent(formatted_name).get_given_components() for comp in given_components: if self.get_component(comp).get_is_class_component() and comp != "Counter": for attr in self.get_list_attributes(comp): self.model_details.remove_initial_parameter(f"{formatted_name.upper()}_{comp.upper()}_{attr.upper()}") del self.agents[formatted_name] self.model_details.remove_num_agent(formatted_name.upper()) return 0
[docs] def add_system(self, name : str, description : str, start : int = None, end : int = None , frequency : int = None, generators : dict = None): """Stores the newly created system into the models systems dictionary. Parameters ---------- name : str The system's name. description : str A description of the system. start : int, Optional The iteration that the system must start on end : int, Optional The iteration that the end must start on frequency : int, Optional Per how many iterations must the system execute generators : dict[Generator], Optional Rather use add, edit, get, remove generator Returns ------- error term : int | Returns 0 on success | Returns 1 if system name already exists | Returns 2 if invalid characters used in system name """ # Tries to format the name to correct format formatted_name = adding_name_checker(name, list(self.systems)) # Checks for error messages if formatted_name == 1: return 1 # Name already in use if formatted_name == 2: return 2 # Name uses invalid chars # Adds system and the required extra parameters if needed self.systems[formatted_name] = System(description) if start is not None: self.edit_system(formatted_name, start=start) if end is not None: self.edit_system(formatted_name, end=end) if frequency is not None: self.edit_system(formatted_name, frequency=frequency) if generators is not None: self.edit_system(formatted_name, generators=generators) return 0
[docs] def edit_system(self, name : str, description : str = None, execute_function_body : str = None , start : int = None, end : int = None, frequency : int = None , generators : dict = None): """Edits the provided named system into the models system dictionary. Parameters ---------- name : str The system's name. description : str, Optional A description of the system. execute_function_body : str, Optional User provided execute function for the system. start : int, Optional The iteration that the system must start on. end : int, Optional The iteration that the end must start on. frequency : int, Optional Per how many iterations must the system execute. Returns ------- error term : int | Returns 0 on success | Returns 1 if system name doesn't already exists | Returns 2 if invalid characters used in system name """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.systems)) # Checks for error messages if formatted_name == 1: return 1 # Name already in use if description is not None: self.get_system(formatted_name).edit_description(description) if execute_function_body is not None: self.get_system(formatted_name).edit_execute_function_body(execute_function_body) if start is not None: self.get_system(formatted_name).edit_start(start) if end is not None: self.get_system(formatted_name).edit_end(end) if frequency is not None: self.get_system(formatted_name).edit_frequency(frequency) if generators is not None: self.get_system(formatted_name).edit_generators(generators) return 0
[docs] def get_system(self, name : str): """Gets the requested system from the models systems dictionary. Parameters ---------- name : str The system's name. Returns ------- system : System | Returns a System object which matches the provided name. | Returns None if no match. """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.systems)) # Checks for error messages if formatted_name == 1: return None # Name already in use return self.systems[formatted_name]
[docs] def remove_system(self, name : str): """Removes a set system from the models system dictionary. Parameters ---------- name : str The name of the system to be removed. Returns ------- error term : int | Returns 0 on success | Returns 1 if system name doesn't already exists | Returns 2 if invalid characters used in system name """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.systems)) # Checks for error messages if formatted_name == 1: return 1 # Name not already in use del self.systems[formatted_name] return 0
[docs] def add_gen_attribute(self, relevent_system : str, relevent_generator : str, name : str , type_attr : str): """Stores the newly created attribute into the system generators attributes dictionary. Parameters ---------- relevent_system : str The relevent_system having the relevent_generator. relevent_component : str The relevent_generator having the attribute. name : str The attribute's name. type_attr : str ["int", "bool", "float"] The type of the attribute Returns ------- error term : int | Returns 0 on success | Returns 1 if names already exists | Returns 2 if invalid characters used in the names | Returns 3 if invalid type_attr """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return formatted_system_name # Name not already in use or invalid chars formatted_gen_name = getting_name_checker(relevent_generator , self.get_list_generators(formatted_system_name)) # Checks for error messages if formatted_gen_name in [1,2]: return formatted_gen_name # Name not already in use or invalid chars formatted_attr_name = adding_name_checker(name , list(self.get_list_gen_attributes(formatted_system_name , formatted_gen_name)), True) # Checks for error messages if formatted_attr_name in [1,2]: return formatted_attr_name # Name already in use or invalid chars # Invalid type of attribute if type_attr not in ["int", "float", "bool"]: return 3 # Adds system generator attribute self.get_generator(formatted_system_name, formatted_gen_name).get_attributes()[formatted_attr_name] = Attribute(type_attr, "model_lvl") self.model_details.add_initial_parameter(f"{formatted_system_name.upper()}_{formatted_attr_name.upper()}", type_attr) return 0
[docs] def get_gen_attribute(self, relevent_system : str, relevent_generator : str, name : str): """Gets the requested attribute from the system generators attributes dictionary. Parameters ---------- relevent_system : str The relevent_system having the relevent_generator. relevent_component : str The relevent_generator having the attribute. name : str The attribute's name. Returns ------- gen_attribute : Attribute | Returns the Attribute object corresponding the the provided names. | Returns None if no match. """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return None # Name not already in use or invalid chars # Getting the formatted name formatted_gen_name = getting_name_checker(relevent_generator , self.get_list_generators(formatted_system_name)) # Checks for error messages if formatted_gen_name in [1,2]: return None # Name not already in use or invalid chars # Getting the formatted name formatted_attr_name = getting_name_checker(name , list(self.get_list_gen_attributes(formatted_system_name , formatted_gen_name)) , True) # Checks for error messages if formatted_attr_name in [1,2]: return None # Name already in use or invalid chars return self.get_generator(formatted_system_name, formatted_gen_name).get_attributes()[formatted_attr_name]
[docs] def remove_gen_attribute(self, relevent_system : str, relevent_generator : str, name : str): """Removes a set attribute from the generators attribute dictionary of a specific system. Parameters ---------- name : str The name of the system to be removed. Returns ------- error term : int | Returns 0 on success | Returns 1 if names don't already exists | Returns 2 if invalid characters used in names """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return formatted_system_name # Name not already in use or invalid chars # Getting the formatted name formatted_gen_name = getting_name_checker(relevent_generator, self.get_list_generators(formatted_system_name)) # Checks for error messages if formatted_gen_name in [1,2]: return formatted_gen_name # Name not already in use or invalid chars # Getting the formatted name formatted_attr_name = getting_name_checker(name , list(self.get_list_gen_attributes(formatted_system_name , formatted_gen_name)) , True) # Checks for error messages if formatted_attr_name in [1,2]: return formatted_attr_name # Name already in use or invalid chars self.model_details.remove_initial_parameter(f"{formatted_system_name.upper()}_{formatted_attr_name.upper()}") del self.get_generator(formatted_system_name, formatted_gen_name).get_attributes()[formatted_attr_name] return 0
[docs] def add_generator(self, relevent_system : str, name : str, description : str , nickname : str = None, return_value : str = None , attributes : dict = None): """Stores the newly created generator into the systems generators dictionary. Parameters ---------- relevent_system : str The relevent_system having the generator name. name : str The generator's name. description: str A description of the generator. nickname : str, Optional The nickname for cells using this type. return_value : str, Optional The code used to generate the return value. attributes : dict, Optional User provided attributes of the generator. Returns ------- error term : int | Returns 0 on success | Returns 1 if names already exists | Returns 2 if invalid characters used in the names | Returns 3 if nickname already in use in this system """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return formatted_system_name # Name not already in use or invalid chars # Getting the formatted name formatted_gen_name = adding_name_checker(name , list(self.get_list_generators(formatted_system_name))) # Checks for error messages if formatted_gen_name in [1,2]: return formatted_gen_name # Name not already in use or invalid chars if attributes is None: attributes = {} # Set nickname if it isn't provided if nickname is None: nickname = formatted_gen_name.lower() formatted_nickname = adding_name_checker(nickname, [], True) if formatted_nickname in [self.get_generator(formatted_system_name, gen_name).get_nickname() for gen_name in self.get_list_generators(formatted_system_name)]: return 3 # Add generator self.get_system(formatted_system_name).get_generators()[formatted_gen_name] = Generator(description, formatted_nickname, return_value, attributes) return 0
[docs] def edit_generator(self, relevent_system : str, name : str, description : str = None , nickname :str = None, return_value : str = None): """Stores the newly created generator into the systems generators dictionary. Parameters ---------- relevent_system : str The relevent_system having the generator name. name : str The generator's name. description: str, Optional A description of the generator. nickname : str, Optional The nickname for cells using this type. return_value : str, Optional The code used to generate the return value. attributes : dict, Optional User provided attributes of the generator. Returns ------- error term : int | Returns 0 on success | Returns 1 if names don't already exists | Returns 2 if invalid characters used in the names | Returns 3 if nickname already in use in this system """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return formatted_system_name # Name not already in use or invalid chars # Getting the formatted name formatted_gen_name = getting_name_checker(name , list(self.get_list_generators(formatted_system_name))) # Checks for error messages if formatted_gen_name in [1,2]: return formatted_gen_name # Name not already in use or invalid chars if description is not None: self.get_system(formatted_system_name).get_generators()[formatted_gen_name].edit_description(description) if nickname is not None: formatted_nickname = adding_name_checker(nickname, [], True) if formatted_nickname in [self.get_generator(formatted_system_name, gen_name).get_nickname() for gen_name in self.get_list_generators(formatted_system_name)]: return 3 self.get_generator(formatted_system_name, formatted_gen_name).edit_nickname(nickname) if return_value is not None: self.get_system(formatted_system_name).get_generators()[formatted_gen_name].edit_return_value(return_value) return 0
[docs] def get_generator(self, relevent_system : str, name : str): """Gets the requested generator from the systems generators dictionary. Parameters ---------- relevent_system : str Name of system the generator belongs to. name : str Name of generator. Returns ------- generator : Generator | Returns the Generator object matching the provided names | Returns None if no Match """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return None # Name not already in use or invalid chars # Getting the formatted name formatted_gen_name = getting_name_checker(name , list(self.get_list_generators(formatted_system_name))) # Checks for error messages if formatted_gen_name in [1,2]: return None # Name not already in use or invalid chars return self.get_system(formatted_system_name).get_generators()[formatted_gen_name]
[docs] def remove_generator(self, relevent_system : str, name : str): """Removes a set generator from the systems generators dictionary. Parameters ---------- relevent_system : str Name of system the generator belongs to. name : str Name of generator. Returns ------- error term : int | Returns 0 on success | Returns 1 if names don't already exists | Returns 2 if invalid characters used in the names """ # Getting the formatted name formatted_system_name = getting_name_checker(relevent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return formatted_system_name # Name not already in use or invalid chars # Getting the formatted name formatted_gen_name = getting_name_checker(name , list(self.get_list_generators(formatted_system_name))) # Checks for error messages if formatted_gen_name in [1,2]: return formatted_gen_name # Name not already in use or invalid chars del self.get_system(formatted_system_name).get_generators()[formatted_gen_name] return 0
[docs] def edit_model_details(self, name : str = None, description : str = None , gridworld : bool = None, sim_length : int = None , sim_seed : int = None): """Stores the provided data about the model. Parameters ---------- name : str, Optional Changes the model's name. description : str, Optional Changes the description of the model. initial_parameters : dict, Optional Store the initial parameters the user wants for the model. sim_length : int, Optional Changes the number of iterations of the model will be executed. sim_seed : int, Optional Changes the seed for the random generator for the model. Returns ------- error term : int | Returns 0 on success | Returns 1 if same name as before | Returns 2 on invalid name """ if name is not None: formatted_name = adding_name_checker(name, [self.get_model_details().get_name()]) if formatted_name in [1,2]: return formatted_name self.model_details.edit_name(formatted_name) if description is not None: self.model_details.edit_description(description) if gridworld is True and not self.model_details.get_gridworld(): # Ensures isn't already True self.model_details.edit_gridworld(gridworld) self.model_details.add_initial_parameter("WIDTH", "int") self.model_details.add_initial_parameter("HEIGHT", "int") self.add_import("ECAgent.Environments", "Gridworld" , "from", ["GridWorld", "PositionComponent", "discrete_grid_pos_to_id"]) if gridworld is False and self.model_details.get_gridworld(): # Ensures isn't already False self.model_details.edit_gridworld(gridworld) self.model_details.remove_initial_parameter("WIDTH") self.model_details.remove_initial_parameter("HEIGHT") del self.imports["ECAgent.Environments"] if sim_length is not None: self.model_details.edit_sim_length(sim_length) if sim_seed is not None: self.model_details.edit_sim_seed(sim_seed) return 0
[docs] def get_model_details(self): """Gets the requested system from the models systems dictionary. Returns ------- model_details : ModelDetails | Returns an Model Details Class. """ return self.model_details
[docs] def add_execute(self, parent_system : str, execute_function_body : str): """Stores an execute function into the respective parent system models system dictionary. Parameters ---------- parent_system : str The parent system's name. execute_function_body : str User provided execute function for the system. Returns ------- error term : int | Returns 0 on success | Returns 1 if name doesn't already exists """ # Getting the formatted name formatted_system_name = getting_name_checker(parent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return formatted_system_name # Name not already in use or invalid chars self.edit_system(formatted_system_name, execute_function_body = execute_function_body) return 0
[docs] def get_execute(self, parent_system : str): """Gets the execute function from the respective parent system models system dictionary. Parameters ---------- parent_system : str The parent system's name. execute_function_body : str User provided execute function for the system. Returns ------- execute : str | Returns execute function body. | Returns None if system name is invalid or doesn't match. """ # Getting the formatted name formatted_system_name = getting_name_checker(parent_system, self.get_list_systems()) # Checks for error messages if formatted_system_name in [1,2]: return None # Name not already in use or invalid chars return self.get_system(parent_system).get_execute_function_body()
[docs] def add_agent_of_interest(self, name : str, comp_of_interest : list[str] = None , count : bool = False, position : bool = False , individual : bool = False, mean_val : bool = False , median_val : bool = False, total_val : bool = False , max_val : bool = False, min_val : bool = False , variance : bool = False, std_dev : bool = False): """Adds the agent of interest to the agents of interest dictionary. Parameters ---------- name : str The name of the agent of interest to be added. comp_of_interest : list[str], Optional The list of desired components for the agent of interest. count : bool, Optional Specifies if the number of agents must be collected. position : bool, Optional Specifies is the user wants to keep the postions of the agent ids individual : bool, Optional Specifies if component values must be collected per individual agent id. mean_val : bool, Optional Specifies if the mean of the component values for the agent type must be collected. median_val : bool, Optional Specifies if the median of the component values for the agent type must be collected. total_val : bool, Optional Specifies if the total of the component values for the agent type must be collected. max_val : bool, Optional Specifies if the maximum of the component values for the agent type must be collected. min_val : bool, Optional Specifies if the minimum of the component values for the agent type must be collected. variance : bool, Optional Specifies if the variance of the component values for the agent type must be collected. std_dev : bool, Optional Specifies if the standard deviation of the component values for the agent type must be collected. Returns ------- error term : int | Returns 0 on success | Returns 1 if name error | Returns 2 if invalid characters used in the name | Returns 3 if no components of interest provided """ # Tries to format the name to correct format formatted_name = adding_name_checker(name, list(self.agents_of_interest)) # Checks for error messages if formatted_name == 1: return 1 # Name already in use if formatted_name == 2: return 2 # Name uses invalid chars # Checks that agent name exists if formatted_name not in self.agents: return 1 # Name not already in use if comp_of_interest is None: return 3 formatted_comp_names = [getting_name_checker(comp_name, self.get_agent(formatted_name).get_given_components()) for comp_name in comp_of_interest] if 1 in formatted_comp_names: return 1 # Name not already in use self.agents_of_interest[formatted_name] = AgentOfInterest(formatted_comp_names, count , position, individual, mean_val, median_val , total_val, max_val, min_val, variance , std_dev) return 0
[docs] def get_list_valid_agents(self, agent_of_focus : str, comp_of_focus : str = None , count : bool = False): """Gets a list of all valid compatible agents_of_interest in the model, needed for visualisation. Parameters ---------- agent_of_focus : str The main agent which is being visualised comp_of_focus : str, Optional The main component which is being visualised, Defaults to None for if count True count : bool, Optional Defaults to False, relates to if just checking for count as no comp_of_focus needed Returns ------- Agents_of_Interest : list[str] | Returns a list of compatible agents of interest | Returns an empty list if invalid names provided or no valid agents of interest """ # Getting the formatted name formatted_agent_of_focus = getting_name_checker(agent_of_focus, list(self.agents_of_interest)) # Checks for error messages if formatted_agent_of_focus in [1,2]: return [] # Name not already in use or invalid chars if count: valid_agents_of_interest = [] for agent_name in self.get_list_agents_of_interest(): if self.get_agent_of_interest(agent_name).get_count() and agent_name != formatted_agent_of_focus: valid_agents_of_interest.append(agent_name) return valid_agents_of_interest # Getting the formatted name formatted_comp_of_focus = getting_name_checker(comp_of_focus , list(self.get_agent_of_interest(formatted_agent_of_focus).get_comp_of_interest())) # Checks for error messages if formatted_comp_of_focus in [1,2]: return [] # Name not already in use or invalid chars valid_agents_of_interest = [] for agent_name in self.get_list_agents_of_interest(): agent_given_components = self.get_agent_of_interest(agent_name).get_comp_of_interest() if formatted_comp_of_focus in agent_given_components and agent_name != formatted_agent_of_focus: valid_agents_of_interest.append(agent_name) return valid_agents_of_interest
[docs] def get_list_valid_plot_data_types(self, agents_of_focus : list[str]): """Gets a list of all valid plot_data_types needed for visualisation. Parameters ---------- agents_of_focus : str The agents being evaluated Returns ------- plot_data_types : list[str] | Returns a list of compatible plot_data_types. | Returns an empty list if invalid agent names or no possible plot_data_types. """ formatted_agent_names = [getting_name_checker(agent, list(self.agents_of_interest)) for agent in agents_of_focus] if 1 in formatted_agent_names: return [] # A name is not currently an agent if len(formatted_agent_names) == 0: return [] # No agents parsed possible_data_types = ["count", "individual", "mean_val", "median_val", "total_val" , "min_val", "max_val", "variance_val", "std_dev_val"] available_data_types = possible_data_types.copy() for agent_of_interest in formatted_agent_names: temp_data_types = get_data_types(self.get_agent_of_interest(agent_of_interest)) for data_type in possible_data_types: if data_type not in temp_data_types and data_type in available_data_types: available_data_types.remove(data_type) # If len() = 0, implies it's an invalid combo # or the attribute is in common, but the wrong data was collected return available_data_types
[docs] def get_agent_of_interest(self, name : str): """Gets the requested agent of interest object from the agents of interest dictionary. Parameters ---------- name : str The agent of interest's name. Returns ------- agent_of_interest[name] : AgentOfInterest | Returns the agent of interest object to return. """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.agents_of_interest)) # Checks for error messages if formatted_name == 1: return None # Name not already in use return self.agents_of_interest[formatted_name]
[docs] def remove_agent_of_interest(self, name : str): """Removes the provided data collection system from the data collection dictionary. Parameters ---------- name : str The name of the agent of interest to be removed. Returns ------- error term : int | Returns 0 on success | Returns 1 if name doesn't already exists | Returns 2 if invalid characters used in the name """ # Tries to format the name to correct format formatted_name = getting_name_checker(name, list(self.agents_of_interest)) # Checks for error messages if formatted_name in [1,2]: return formatted_name # Name not already in use del self.agents_of_interest[formatted_name] return 0
[docs] def create_model(self, file_name : str, visualise_model = False): """Generates a python file(s) necessary for the model to run. Parameters ---------- file_name : str The file_name of the model to be written to. visualise_model : bool The specifies if the main file will be used for visualisation or not. Returns ------- error term : int | Returns 0 on success | Returns 1 if file generation fails """ if ".py" in file_name: # Makes sure there is no .py at the end of the filename file_name = file_name.replace(".py", "") try: file_store = utils.WriteModelFile(file_name) file_store.create_imports_section(self) file_store.create_components_section(self) file_store.create_agents_section(self) file_store.create_systems_section(self) file_store.create_data_collection_section(self) file_store.create_model_section(self) file_store.create_model_file() file_store.create_main_file_section(self, visualise_model) file_store.create_main_file() return 0 except Exception as exception: # pragma: no cover print(exception) return 1
[docs] def store_model(self): """ Stores the current model as a json file which allows us to reload in an existing project. Returns ------- error term : int | Returns 0 on success | Returns 1 if model storing fails """ try: if not os.path.isdir("ModelAsJSONFiles"): os.makedirs("ModelAsJSONFiles") file_name = "ModelAsJSONFiles/" + self.get_model_details().get_name() + ".json" with open(file_name, 'w', encoding='UTF-8') as file: all_data = {"imports" : self.imports, "components" : self.components , "agents" : self.agents, "systems" : self.systems , "model_details" : self.model_details , "agents_of_interest" : self.agents_of_interest} encoded_data = dumps(all_data) file.write(encoded_data) return 0 except EOFError as exception: # pragma: no cover print(exception) return 1