def __init__(self, name: str = "controller", oef_addr: str = "127.0.0.1", oef_port: int = 10000, version: int = 1, monitor: Optional[Monitor] = None, **kwargs): """ Initialize a Controller Agent for TAC. :param name: The name of the OEF Agent. :param oef_addr: the OEF address. :param oef_port: the OEF listening port. :param version: the version of the TAC controller. :param monitor: the GUI monitor. If None, defaults to a null (dummy) monitor. """ self.name = name self.crypto = Crypto() super().__init__(self.crypto.public_key, oef_addr, oef_port, loop=asyncio.new_event_loop()) logger.debug("[{}]: Initialized myself as Controller Agent :\n{}".format(self.name, pprint.pformat(vars()))) self.dispatcher = ControllerDispatcher(self) self.monitor = NullMonitor() if monitor is None else monitor # type: Monitor self.version = version self.game_handler = None # type: Optional[GameHandler] self.last_activity = datetime.datetime.now() self._message_processing_task = None self._timeout_checker_task = None self._is_running = False self._terminated = False
def setup_class(cls): """Test that if the controller agent does not receive enough registrations, it stops.""" tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters, NullMonitor()) cls.controller_agent.mailbox.connect() job = Thread(target=cls.controller_agent.start) job.start() cls.crypto = Crypto() cls.agent1 = TOEFAgent(cls.crypto.public_key, oef_addr='127.0.0.1', oef_port=10000) cls.agent1.connect() tac_msg = TACMessage(tac_type=TACMessage.Type.REGISTER, agent_name='agent_name') tac_bytes = TACSerializer().encode(tac_msg) cls.agent1.outbox.put_message( to=cls.controller_agent.crypto.public_key, sender=cls.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) job.join()
def setup_class(cls): """Test that if the controller agent does not receive enough registrations, it stops.""" tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters, NullMonitor())
def setup_class(cls): """Class setup.""" cls.tac_version_id = "1" cls.agent_version = "v1" cls.baseline_agents = _init_baseline_agents(5, cls.agent_version, "127.0.0.1", 10000, cls.tac_version_id) cls.tac_parameters = TACParameters( min_nb_agents=5, money_endowment=200, nb_goods=5, tx_fee=1.0, base_good_endowment=2, lower_bound_factor=0, upper_bound_factor=0, start_time=datetime.datetime.now() + datetime.timedelta(0, 2), registration_timeout=8, competition_timeout=20, inactivity_timeout=15, version_id=cls.tac_version_id, ) cls.tac_controller = ControllerAgent("controller", "127.0.0.1", 10000, cls.tac_parameters, NullMonitor()) # run the simulation try: controller_thread = Thread(target=cls.tac_controller.start) baseline_threads = [ Thread(target=_run_baseline_agent, args=[baseline_agent, "v1"]) for baseline_agent in cls.baseline_agents ] # launch all thread. all_threads = [controller_thread] + baseline_threads for thread in all_threads: thread.start() # wait for every thread. This part is blocking. for thread in all_threads: thread.join() except Exception as e: pytest.fail("Got exception: {}".format(e))
class ControllerAgent(OEFAgent): """Class for a controller agent.""" CONTROLLER_DATAMODEL = DataModel("tac", [ AttributeSchema("version", int, True, "Version number of the TAC Controller Agent."), ]) def __init__(self, name: str = "controller", oef_addr: str = "127.0.0.1", oef_port: int = 10000, version: int = 1, monitor: Optional[Monitor] = None, **kwargs): """ Initialize a Controller Agent for TAC. :param name: The name of the OEF Agent. :param oef_addr: the OEF address. :param oef_port: the OEF listening port. :param version: the version of the TAC controller. :param monitor: the dashboard monitor. If None, defaults to a null (dummy) monitor. """ self.name = name self.crypto = Crypto() super().__init__(self.crypto.public_key, oef_addr, oef_port, loop=asyncio.new_event_loop()) logger.debug( "[{}]: Initialized myself as Controller Agent :\n{}".format( self.name, pprint.pformat(vars()))) self.dispatcher = ControllerDispatcher(self) self.monitor = NullMonitor( ) if monitor is None else monitor # type: Monitor self.version = version self.game_handler = None # type: Optional[GameHandler] self.last_activity = datetime.datetime.now() self._message_processing_task = None self._timeout_checker_task = None self._is_running = False self._terminated = False def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: """ Handle a simple message. The TAC Controller expects that 'content' is a Protobuf serialization of a tac.messages.Request object. The request is dispatched to the right request handler (using the ControllerDispatcher). The handler returns an optional response, that is sent back to the sender. Notice: the message sent back has the same message id, such that the client knows to which request the response is associated to. :param msg_id: the message id :param dialogue_id: the dialogue id :param origin: the public key of the sender. :param content: the content of the message. :return: None """ logger.debug( "[{}] on_message: msg_id={}, dialogue_id={}, origin={}".format( self.name, msg_id, dialogue_id, origin)) self.update_last_activity() response = self.dispatcher.process_request( content, origin) # type: Optional[Response] if response is not None: self.send_message(msg_id, dialogue_id, origin, response.serialize()) def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: """ Handle an oef error. :param answer_id: the answer id :param operation: the oef operation :return: None """ logger.error( "[{}]: Received OEF error: answer_id={}, operation={}".format( self.name, answer_id, operation)) def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: """ Handle a dialogue error. :param answer_id: the answer id :param dialogue_id: the dialogue id :param origin: the public key of the sending agent :return: None """ logger.error( "[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" .format(self.name, answer_id, dialogue_id, origin)) def register(self): """ Register on the OEF as a TAC controller agent. :return: None. """ desc = Description({"version": 1}, data_model=self.CONTROLLER_DATAMODEL) logger.debug("[{}]: Registering with {} data model".format( self.name, desc.data_model.name)) self.register_service(0, desc) def dump(self, directory: str, experiment_name: str) -> None: """ Dump the details of the simulation. :param directory: the directory where experiments details are listed. :param experiment_name: the name of the folder where the data about experiment will be saved. :return: None. """ experiment_dir = directory + "/" + experiment_name if self.game_handler is None or not self.game_handler.is_game_running( ): logger.warning( "[{}]: Game not present. Using empty dictionary.".format( self.name)) game_dict = {} # type: Dict[str, Any] else: game_dict = self.game_handler.current_game.to_dict() os.makedirs(experiment_dir, exist_ok=True) with open(os.path.join(experiment_dir, "game.json"), "w") as f: json.dump(game_dict, f) def terminate(self) -> None: """ Terminate the controller agent. :return: None """ if self._is_running: logger.debug("[{}]: Terminating the controller...".format( self.name)) self._is_running = False self.game_handler.notify_tac_cancelled() self._loop.call_soon_threadsafe(self.stop) self._message_processing_task.join() self._message_processing_task = None if self.monitor.is_running: self.monitor.stop() def check_inactivity_timeout(self, rate: Optional[float] = 2.0) -> None: """ Check periodically if the timeout for inactivity or competition expired. :param: rate: at which rate (in seconds) the frequency of the check. :return: None """ logger.debug( "[{}]: Started job to check for inactivity of {} seconds. Checking rate: {}" .format( self.name, self.game_handler.tac_parameters.inactivity_timedelta. total_seconds(), rate)) while True: if self._is_running is False: return time.sleep(rate) current_time = datetime.datetime.now() inactivity_duration = current_time - self.last_activity if inactivity_duration > self.game_handler.tac_parameters.inactivity_timedelta: logger.debug( "[{}]: Inactivity timeout expired. Terminating...".format( self.name)) self.terminate() return elif current_time > self.game_handler.tac_parameters.end_time: logger.debug( "[{}]: Competition timeout expired. Terminating...".format( self.name)) self.terminate() return def update_last_activity(self): """Update the last activity tracker.""" self.last_activity = datetime.datetime.now() def handle_competition(self, tac_parameters: TACParameters): """ Start a Trading Agent Competition. :param tac_parameters: the parameter of the competition. :return: """ logger.debug("[{}]: Starting competition with parameters: {}".format( self.name, pprint.pformat(tac_parameters.__dict__))) self._is_running = True self._message_processing_task = Thread(target=self.run) self.monitor.start(None) self.monitor.update() self.game_handler = GameHandler(self, tac_parameters) self._message_processing_task.start() if self.game_handler.handle_registration_phase(): # start the inactivity timeout. self._timeout_checker_task = Thread( target=self.check_inactivity_timeout) self._timeout_checker_task.run() else: self.terminate() def wait_and_handle_competition(self, tac_parameters: TACParameters) -> None: """ Wait until the current time is greater than the start time, then, start the TAC. :param tac_parameters: the parameters for TAC. :return: None """ now = datetime.datetime.now() logger.debug( "[{}]: waiting for starting the competition: start_time={}, current_time={}, timedelta ={}s" .format(self.name, str(tac_parameters.start_time), str(now), (tac_parameters.start_time - now).total_seconds())) seconds_to_wait = (tac_parameters.start_time - now).total_seconds() time.sleep(0.5 if seconds_to_wait < 0 else seconds_to_wait) self.handle_competition(tac_parameters)
def main(name: str = "controller", nb_agents: int = 5, nb_goods: int = 5, money_endowment: int = 200, base_good_endowment: int = 2, lower_bound_factor: int = 0, upper_bound_factor: int = 0, tx_fee: float = 1.0, oef_addr: str = "127.0.0.1", oef_port: int = 10000, start_time: Union[str, datetime.datetime] = str(datetime.datetime.now() + datetime.timedelta(0, 10)), registration_timeout: int = 10, inactivity_timeout: int = 60, competition_timeout: int = 240, whitelist_file: Optional[str] = None, verbose: bool = False, dashboard: bool = False, visdom_addr: str = "localhost", visdom_port: int = 8097, data_output_dir: str = "data", experiment_id: Optional[str] = None, seed: int = 42, version: int = 1, **kwargs): """Run the controller script.""" agent = None # type: Optional[ControllerAgent] random.seed(seed) if verbose: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) monitor = VisdomMonitor( visdom_addr=visdom_addr, visdom_port=visdom_port) if dashboard else NullMonitor() try: agent = ControllerAgent(name=name, oef_addr=oef_addr, oef_port=oef_port, monitor=monitor, version=version) whitelist = set( open(whitelist_file).read().splitlines( keepends=False)) if whitelist_file else None tac_parameters = TACParameters( min_nb_agents=nb_agents, money_endowment=money_endowment, nb_goods=nb_goods, tx_fee=tx_fee, base_good_endowment=base_good_endowment, lower_bound_factor=lower_bound_factor, upper_bound_factor=upper_bound_factor, start_time=dateutil.parser.parse(start_time) if type(start_time) == str else start_time, registration_timeout=registration_timeout, competition_timeout=competition_timeout, inactivity_timeout=inactivity_timeout, whitelist=whitelist) agent.connect() agent.register() agent.wait_and_handle_competition(tac_parameters) except Exception as e: logger.exception(e) except KeyboardInterrupt: logger.debug("Controller interrupted...") finally: if agent is not None: agent.terminate() experiment_name = experiment_id if experiment_id is not None else str( datetime.datetime.now()).replace(" ", "_") agent.dump(data_output_dir, experiment_name) if agent.game_handler is not None and agent.game_handler.is_game_running( ): game_stats = GameStats(agent.game_handler.current_game) game_stats.dump(data_output_dir, experiment_name)
def main(name: str = "controller", nb_agents: int = 5, nb_goods: int = 5, money_endowment: int = 200, base_good_endowment: int = 2, lower_bound_factor: int = 0, upper_bound_factor: int = 0, tx_fee: float = 1.0, oef_addr: str = "127.0.0.1", oef_port: int = 10000, start_time: str = str(datetime.datetime.now() + datetime.timedelta(0, 10)), registration_timeout: int = 10, inactivity_timeout: int = 60, competition_timeout: int = 240, whitelist_file: Optional[str] = None, verbose: bool = False, dashboard: bool = False, visdom_addr: str = "localhost", visdom_port: int = 8097, data_output_dir: str = "data", version_id: str = str(random.randint(0, 10000)), seed: int = 42, **kwargs): """Run the controller script.""" agent = None # type: Optional[ControllerAgent] random.seed(seed) if verbose: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) try: monitor = (VisdomMonitor(visdom_addr=visdom_addr, visdom_port=visdom_port) if dashboard else NullMonitor()) whitelist = (set( open(whitelist_file).read().splitlines( keepends=False)) if whitelist_file else None) tac_parameters = TACParameters( min_nb_agents=nb_agents, money_endowment=money_endowment, nb_goods=nb_goods, tx_fee=tx_fee, base_good_endowment=base_good_endowment, lower_bound_factor=lower_bound_factor, upper_bound_factor=upper_bound_factor, start_time=dateutil.parser.parse(str(start_time)), registration_timeout=registration_timeout, competition_timeout=competition_timeout, inactivity_timeout=inactivity_timeout, whitelist=whitelist, data_output_dir=data_output_dir, version_id=version_id, ) agent = ControllerAgent( name=name, oef_addr=oef_addr, oef_port=oef_port, tac_parameters=tac_parameters, monitor=monitor, ) agent.start() except Exception as e: logger.exception(e) except KeyboardInterrupt: logger.debug("Controller interrupted...") finally: if agent is not None: agent.stop() agent.teardown()