Source code for ECAgent.Collectors

from sys import maxsize
from ECAgent.Core import System, Model


[docs]class Collector(System): """ This is the Collector base class. Collectors are, by default, Systems and behave the same way. The collector base class adds a 'records' list property. This property holds all of the data collected by the collector. When writing your own collector, override the collect() method not the execute() method. The Collector base class automatically calls the collect() method whenever the execute() method is called. If you do need to override the execute method, make sure you also call the collect() method to follow the intended behaviour of a Collector object.""" def __init__(self, id: str, model: Model, priority=-1, frequency=1, start=0, end=maxsize): super().__init__(id, model, priority, frequency, start, end) self.records = []
[docs] def execute(self): """ This overrides the Systems base execution method. It simply calls the collect method""" self.collect()
[docs] def collect(self): """The collect method. This method is overridden to define the data collection behaviour of your Custom Collector.""" pass
[docs]class AgentCollector(Collector): """This is a collector system specifically designed to iterate through every iteration whenever the system is executed. The AgentCollector defaults its systemID to 'AgentCollector'. If you are using multiple agent collectors, you must give them unique ids. An AgentCollector can be supplied with custom lambdas for both agent specific operations as well as composite data collection. The agentFunc must be a function that accepts an agent as input like so: def myCustomCollectionFunc(agent): return data The result of that function is then stored in a dict that uses the agent's id as a key. If None is returned, the collector simply skips that agent. If you want to collect composite/aggregate data about the model (like a gini-index), supplying the compositeFunc property with a lambda that returns a dict of all of the composite data will do the trick. The function might look like so: def myCustomCompositeFunction(agents) return {} The dict returned is then used to update the dict of that record. Returning None will not update the dict. To see the agent collector in action, see the Environment and Data Collection tutorial.""" def __init__(self, model: Model, agentFunc, compositeFunc=None, includeTimstep=False, id="AgentCollector", priority=-1, frequency=1, start=0, end=maxsize): super().__init__(id, model, priority, frequency, start, end) self.agentFunc = agentFunc self.compositeFunc = compositeFunc self.includeTimestep = includeTimstep
[docs] def collect(self): """ The AgentCollector Collect() function iterates through every agent, a, in the model.environments.agents dict calling the agentFunc lambda like so agentFunc(a). The result of that call is then stored in a temporary dict that is later added to the records list. If includeTimestep is True, the collector will also the record the value of the current timestep in the tmpDict. After calling agentFunc(a) for all agents, the compositeFunc is called and supplied with a dict of all agents. The dict returned from the compositeFunc(agents) operation is then used to update the tmpDict. A record will not be appended to the records list if the tmpDict is empty.""" # Create Empty record tmpDict = {} # Include timestep value in record if self.includeTimestep: tmpDict['timestep'] = self.model.systems.timestep # Loop through all agents in the environment for agentKey in self.model.environment.agents: result = self.agentFunc(self.model.environment.agents[agentKey]) # If the result from the agentFunc is not None, add result to the dict if result is not None: tmpDict[agentKey] = result # Call compositeFunc if self.compositeFunc is not None: comp_result = self.compositeFunc(self.model.environment.agents) # Add comp_result to lambda if not None if comp_result is not None: tmpDict.update(comp_result) # Add record if tmpDict is not empty if len(tmpDict) > 0: self.records.append(tmpDict)
[docs]class FileCollector(Collector): """This is the base class for Collectors that want to write to files. The base implementation simply writes the records of the collector to the specified file name. When implementing your own FileCollector, you may need to override two methods: - The collect() method (As you would if you were writing your own non file-based collector). - The write_records() method which describes how your collector writes content to a file.""" def __init__(self, id: str, model: Model, filename: str, priority=-1, frequency=1, start=0, end=maxsize, filemode: str = 'a', write_count: int = 0, clear_records_on_write: bool = True): super().__init__(id, model, priority, frequency, start, end) self.filename = filename self.filemode = filemode self.write_count = write_count self.last_write = 0 self.clear_records_on_write = clear_records_on_write
[docs] def execute(self): super().execute() self.last_write += 1 # Increase counter since we collected data if self.write_count < self.last_write: self.write_records() self.last_write = 0 if self.clear_records_on_write: self.records.clear()
[docs] def write_records(self): """Loops through all of the self.records and writes their contents to a file specified by the self.filename property.""" file = open(self.filename, self.filemode) for record in self.records: file.write(record) file.close()