def test_pathos_pp_callable () : """Test parallel processnig with pathos: ParallelPool """ logger = getLogger("ostap.test_pathos_pp_callable") if not pathos : logger.error ( "pathos is not available" ) return logger.info ('Test job submission with %s' % pathos ) if DILL_PY3_issue : logger.warning ("test is disabled (DILL/ROOT/PY3 issue)" ) return ## logger.warning ("test is disabled for UNKNOWN REASON") ## return from pathos.helpers import cpu_count ncpus = cpu_count () from pathos.pools import ParallelPool as Pool pool = Pool ( ncpus ) logger.info ( "Pool is %s" % ( type ( pool ).__name__ ) ) pool.restart ( True ) mh = MakeHisto() jobs = pool.uimap ( mh.process , [ ( i , n ) for ( i , n ) in enumerate ( inputs ) ] ) result = None for h in progress_bar ( jobs , max_value = len ( inputs ) ) : if not result : result = h else : result.Add ( h ) pool.close () pool.join () pool.clear () logger.info ( "Histogram is %s" % result.dump ( 80 , 10 ) ) logger.info ( "Entries %s/%s" % ( result.GetEntries() , sum ( inputs ) ) ) with wait ( 1 ) , use_canvas ( 'test_pathos_pp_callable' ) : result.draw ( ) return result
def gap_stat(self, err_init, B=10): """ Calcule les statistiques utiles dans le choix du nombre optimal de cluster. Paramètres d'entrée : err_init : matrice de la forme [nb_cluster; erreur de classification; erreur relative de classification; variance intra-classe] B : Nombre d'itération de k-means avec échantillons aléatoires. Paramètres de sortie : stat : matrice de la forme [nb_cluster; erreur de classification; erreur relative de classification; variance intra-classe; logarithme de l'erreur de classification; moyenne des log des erreurs avec echantillons aléatoires; différence entre les logs des erreurs obtenues et la moyenne des log des erreurs avec echantillons aléatoires; gap statistical | (différence des échantillons n) - (différence des échantillons n+1 * variance des log des erreurs avec echantillons aléatoires)] """ pool = Pool(self.cpu) pool.close() pool.join() mini, maxi = np.min(self.data.data, axis=0), np.max(self.data.data, axis=0) shape = self.data.data.shape log = np.log10(err_init[1]) mean_alea, var_alea = [], [] for i in range(1, self.nb_cluster + 1): err = [] f = partial(self.__stat_i, mini=mini, maxi=maxi, shape=shape, i=i) pool.restart() err = list(pool.map(f, range(B))) pool.close() pool.join() err = np.log10(np.array(err)) mean_alea.append(np.mean(err)) var_alea.append(np.std(err)) mean_alea = np.array(mean_alea) var_alea = np.array(var_alea) * np.sqrt(1 + (1 / float(B))) gap = mean_alea - log diff_gap = gap[0:-1] - (gap[1:] - var_alea[1:]) diff_gap = np.hstack((diff_gap, 0)) stat = np.vstack((err_init, log, mean_alea, gap, diff_gap)) del pool return stat
def parmap(f, X, nprocs=multiprocessing.cpu_count(), chunk_size=1, use_tqdm=False, **tqdm_kwargs): if len(X) == 0: return [] # like map # nprocs = min(nprocs, cn.max_procs) if nprocs != multiprocessing.cpu_count() and len(X) < nprocs * chunk_size: chunk_size = 1 # use chunk_size = 1 if there is enough procs for a batch size of 1 nprocs = int(max(1, min(nprocs, len(X) / chunk_size))) # at least 1 if len(X) < nprocs: if nprocs != multiprocessing.cpu_count(): print("parmap too much procs") nprocs = len(X) # too much procs if force_serial or nprocs == 1: # we want it serial (maybe for profiling) return list(map(f, tqdm(X, smoothing=0, **tqdm_kwargs))) def _spawn_fun(input, func, c): import random, numpy random.seed(1554 + i + c) numpy.random.seed(42 + i + c) # set random seeds try: res = func(input) res_dict = dict() res_dict["res"] = res # res_dict["functions_dict"] = function_cache2.caches_dicts # res_dict["experiment_purpose"] = cn2.experiment_purpose # res_dict["curr_params_list"] = cn2.curr_experiment_params_list return res_dict except: import traceback traceback.print_exc() raise # re-raise exception # if chunk_size == 1: # chunk_size = math.ceil(float(len(X)) / nprocs) # all procs work on an equal chunk try: # try-catch hides bugs global proc_count old_proc_count = proc_count proc_count = nprocs p = Pool(nprocs) p.restart(force=True) # can throw if current proc is daemon if use_tqdm: retval_par = tqdm(p.imap(_spawn_fun, X, [f] * len(X), range(len(X)), chunk_size=chunk_size), total=len(X), smoothing=0, **tqdm_kwargs) else: retval_par = p.map(_spawn_fun, X, [f] * len(X), range(len(X)), chunk_size=chunk_size) retval = list(map(lambda res_dict: res_dict["res"], retval_par)) # make it like the original map p.terminate() # for res_dict in retval_par: # add all experiments params we missed # curr_params_list = res_dict["curr_params_list"] # for param in curr_params_list: # cn.add_experiment_param(param) # cn.experiment_purpose = retval_par[0]["experiment_purpose"] # use the "experiment_purpose" from the fork # function_cache.merge_cache_dicts_from_parallel_runs(map(lambda a: a["functions_dict"], retval_par)) # merge all proc_count = old_proc_count global i i += 1 except AssertionError as e: if str(e) == "daemonic processes are not allowed to have children": retval = map(f, X) # can't have pool inside pool else: print("error message is: " + str(e)) raise # re-raise orig exception return retval
def parmap(f: Callable, X: List[object], nprocs=multiprocessing.cpu_count(), force_parallel=False, chunk_size=1, use_tqdm=False, keep_child_tqdm=True, **tqdm_kwargs) -> list: """ Utility function for doing parallel calculations with multiprocessing. Splits the parameters into chunks (if wanted) and calls. Equivalent to list(map(func, params_iter)) Args: f: The function we want to calculate for each element X: The parameters for the function (each element ins a list) chunk_size: Optional, the chunk size for the workers to work on nprocs: The number of procs to use (defaults for all cores) use_tqdm: Whether to use tqdm (default to False) tqdm_kwargs: kwargs passed to tqdm Returns: The list of results after applying func to each element Has problems with using self.___ as variables in f (causes self to be pickled) """ if len(X) == 0: return [] # like map if nprocs != multiprocessing.cpu_count() and len(X) < nprocs * chunk_size: chunk_size = 1 # use chunk_size = 1 if there is enough procs for a batch size of 1 nprocs = int(max(1, min(nprocs, len(X) / chunk_size))) # at least 1 if len(X) < nprocs: if nprocs != multiprocessing.cpu_count(): print("parmap too much procs") nprocs = len(X) # too much procs args = zip(X, [f] * len(X), range(len(X)), [keep_child_tqdm] * len(X)) if chunk_size > 1: args = list(chunk_iterator(args, chunk_size)) s_fun = _chunk_spawn_fun # spawn fun else: s_fun = _spawn_fun # spawn fun if (nprocs == 1 and not force_parallel ) or force_serial: # we want it serial (maybe for profiling) return list(map(f, tqdm(X, disable=not use_tqdm, **tqdm_kwargs))) try: # try-catch hides bugs global proc_count old_proc_count = proc_count proc_count = nprocs p = Pool(nprocs) p.restart(force=True) # can throw if current proc is daemon if use_tqdm: retval_par = tqdm(p.imap(lambda arg: s_fun(arg), args), total=int(len(X) / chunk_size), **tqdm_kwargs) else: # import pdb # pdb.set_trace() retval_par = p.map(lambda arg: s_fun(arg), args) retval = list(retval_par) # make it like the original map if chunk_size > 1: retval = flatten(retval) p.terminate() proc_count = old_proc_count global i i += 1 except AssertionError as e: # if e == "daemonic processes are not allowed to have children": retval = list(map(f, tqdm(X, disable=not use_tqdm, **tqdm_kwargs))) # can't have pool inside pool return retval
class MainLoop(metaclass=ABCMeta): """ Defines the logical loop of a game, running MAX_FPS times per second, sending the inputs to the HumanControllerWrapper, and the game updates to the BotControllerWrappers. """ def __init__(self, api: API): """ Instantiates the logical loop Args: api: The game to run in this loop """ self.api = api self.game = api.game self.game.addCustomMoveFunc = self._addCustomMove self._currentTurnTaken = False self._screen = None self._state = CONTINUE # The game must go on at start self._eventsToSend = {} # type: Dict[ControllerWrapper, List[Event]] self.wrappersConnection = { } # type: Dict[ControllerWrapper, PipeConnection] self.wrappersInfoConnection = { } # type: Dict[ControllerWrapper, PipeConnection] self.wrappers = {} # type: Dict[ControllerWrapper, Unit] self._unitsMoves = {} # type: Dict[Unit, Tuple[Path, Queue]] self._moveDescriptors = {} # type: Dict[Path, MoveDescriptor] self._otherMoves = {} # type: Dict[Unit, Path] self._killSent = { } # Used to maintain the fact that the kill event has been sent self.executor = None self._prepared = False self._frames = 0 # -------------------- PUBLIC METHODS -------------------- # def run(self, max_fps: int = MAX_FPS) -> Union[None, Tuple[Unit, ...]]: """ Launch the game and its logical loop Args: max_fps: The maximum frame per seconds of the game Returns: a tuple containing all the winning players, or an empty tuple in case of draw, or None if the game was closed by the user """ pygame.init() clock = pygame.time.Clock() assert self.game.board.graphics is not None try: self._screen = pygame.display.set_mode( self.game.board.graphics.size, DOUBLEBUF) except pygame.error: # No video device pass if not self._prepared: self._prepareLoop() for player_number in self.api.getPlayerNumbers(): self._sendEventsToController(player_number) while self._state != END: clock.tick(max_fps) self._frames += 1 self._handleInputs() if self._state == FINISH: self.executor.terminate() self._prepared = False return None elif self._state != PAUSE: self._state = self._checkGameState() if self._state == CONTINUE: self._getNextMoveFromControllerWrapperIfAvailable() self._handlePendingMoves() self._refreshScreen() self.executor.terminate() self._prepared = False return self.game.winningPlayers def addUnit(self, unit: Unit, wrapper: ControllerWrapper, tile_id: TileIdentifier, initial_action: MoveDescriptor = None, team: int = -1) -> None: """ Adds a unit to the game, located on the tile corresponding to the the given tile id and controlled by the given controller Args: unit: The unit to add to the game wrapper: The linker of that unit tile_id: The identifier of the tile it will be placed on initial_action: The initial action of the unit team: The number of the team this player is in (-1 = no team) """ is_controlled = wrapper is not None self.game.addUnit(unit, team, tile_id, is_avatar=is_controlled) if is_controlled: self._addControllerWrapper(wrapper, unit) if self._mustSendInitialWakeEvent(initial_action, unit): self._eventsToSend[wrapper].append(WakeEvent()) self._unitsMoves[unit] = (None, Queue()) tile = self.game.board.getTileById(tile_id) resize_unit(unit, self.game.board) unit.moveTo(tile.center) if initial_action is not None: unit.setLastAction(initial_action) self._handleEvent(unit, initial_action, wrapper.controller.playerNumber, force=True) def getWrapperFromPlayerNumber(self, player_number: int): """ Retrieves the wrapper from the given player number Args: player_number: The number representing the player for which we want the wrapper Returns: The wrapper that wraps the controller of the given player """ found = None for wrapper in self.wrappersConnection: if wrapper.controller.playerNumber == player_number: found = wrapper break return found def pause(self) -> None: """ Change the state of the game to "PAUSE" """ self._state = PAUSE print(self._frames, "frames") def resume(self) -> None: """ Resume the game """ self._state = CONTINUE # -------------------- PROTECTED METHODS -------------------- # def _refreshScreen(self) -> None: """ Update the visual state of the game """ try: if self._screen is None: raise pygame.error("No Video device") self.game.board.draw(self._screen) drawn_units = [] for unit in self.wrappers.values(): if unit.isAlive(): unit.draw(self._screen) drawn_units.append(unit) for unit in self.game.unitsLocation: if unit.isAlive() and unit not in drawn_units: unit.draw(self._screen) pygame.display.flip() except pygame.error: # No video device pass def _handleInputs(self) -> None: """ Handles all the user input (mouse and keyboard) """ try: events_got = pygame.event.get() except pygame.error: # No video device events_got = [] for event in events_got: if event.type == QUIT: self._state = FINISH elif event.type == KEYDOWN: if event.key == K_ESCAPE: if self._state == CONTINUE: self.pause() elif self._state == PAUSE: self.resume() else: self._dispatchInputToHumanControllers(event.key) elif event.type == MOUSEBUTTONDOWN: self._dispatchMouseEventToHumanControllers(event.pos) elif event.type == MOUSEBUTTONUP: self._dispatchMouseEventToHumanControllers(None, click_up=True) def _addMove(self, unit: Unit, move: Path) -> None: """ Adds a move (cancelling the pending moves) Args: unit: The unit for which add a move move: The move to add for the given controller """ if self._unitsMoves[unit][0] is not None: self._cancelCurrentMoves(unit) fifo = self._unitsMoves[unit][1] # type: Queue fifo.put(move) def _addCustomMove(self, unit: Unit, move: Path, event: MoveDescriptor) -> None: """ Adds a move that is NOT PERFORMED BY A CONTROLLER Args: unit: The unit that will be moved move: The move that will be performed """ if unit not in self._otherMoves or self._otherMoves[unit] is None: self._otherMoves[unit] = move self._moveDescriptors[move] = event def _cancelCurrentMoves(self, unit: Unit) -> None: """ Cancel the current movement if there is one and remove all the other pending movements. Args: unit: The unit for which cancel the movements """ if unit in self._unitsMoves: move_tuple = self._unitsMoves[unit] fifo = move_tuple[1] # type: Queue last_move = move_tuple[0] # type: Path new_fifo = Queue() if last_move is not None: last_move.stop() while True: try: move = fifo.get_nowait() del self._moveDescriptors[move] except Empty: break self._unitsMoves[unit] = (last_move, new_fifo) def _dispatchInputToHumanControllers(self, input_key) -> None: """ Handles keyboard events and send them to Human Controllers to trigger actions if needed Args: input_key: The key pressed on the keyboard """ for linker in self.wrappers: # type: HumanControllerWrapper if issubclass(type(linker), HumanControllerWrapper): self._getPipeConnection(linker).send( self.game.createKeyboardEvent( self._getUnitFromControllerWrapper(linker), input_key)) def _dispatchMouseEventToHumanControllers(self, pixel: Optional[Coordinates], click_up=False) -> None: """ Handles mouse events and send them to Human Controllers to trigger actions if needed Args: pixel: The pixel clicked click_up: True if the button was released, False if the button was pressed """ tile = None if pixel is not None: tile = self.game.board.getTileByPixel(pixel) self._previouslyClickedTile = tile mouse_state = pygame.mouse.get_pressed() for linker in self.wrappers: # type: ControllerWrapper if issubclass(type(linker), HumanControllerWrapper): tile_id = None if tile is not None: tile_id = tile.identifier self._getPipeConnection(linker).send( self.game.createMouseEvent( self._getUnitFromControllerWrapper(linker), pixel, mouse_state, click_up, tile_id)) def _getNextMoveFromControllerWrapperIfAvailable(self) -> None: """ Gets event from the controllers and dispatch them to the right method """ for current_wrapper in self.wrappersConnection: # type: ControllerWrapper pipe_conn = self._getPipeConnection(current_wrapper) if pipe_conn.poll(): move = pipe_conn.recv() if self._mustRetrieveNextMove(current_wrapper): self._handleEvent(self.wrappers[current_wrapper], move, current_wrapper.controller.playerNumber) def _handlePendingMoves(self) -> None: """ Get the next move to be performed and perform its next step """ moved_units = [] completed_moves = { } # type: Dict[Unit, Tuple[TileIdentifier, MoveDescriptor]] just_started = {} # type: Dict[int, MoveDescriptor] illegal_moves = [] # type: List[Unit] impossible_moves = {} # type: List[Unit] self._handleOtherMoves(completed_moves, illegal_moves, impossible_moves, just_started, moved_units) self._handleMoves(completed_moves, illegal_moves, impossible_moves, just_started, moved_units) self._updateFromMoves(completed_moves, illegal_moves, impossible_moves, just_started) def _updateFromMoves(self, completed_moves, illegal_moves, impossible_moves, just_started): players_to_be_sent_messages = [] for unit, (tile_id, move_descriptor) in completed_moves.items(): self.game.updateGameState(unit, tile_id, move_descriptor) for player_number, move_descriptor in just_started.items(): controller_unit = self.game.getControllerUnitForNumber( player_number) if controller_unit is not None: controller_unit.setCurrentAction(move_descriptor) self._addMessageToSendToAll(player_number, move_descriptor) players_to_be_sent_messages = self._getPlayerNumbersToWhichSendEvents( ) for player_number in players_to_be_sent_messages: self._sendEventsToController(player_number) for unit in illegal_moves: self.game.unitsLocation[ unit] = self.game.board.OUT_OF_BOARD_TILE.identifier self._killUnit(unit, self.getWrapperFromPlayerNumber(unit.playerNumber)) # self.game.checkIfFinished() self._cancelCurrentMoves(unit) for unit in impossible_moves: self._cancelCurrentMoves(unit) self.game.checkIfFinished() def _handleMoves(self, completed_moves, illegal_moves, impossible_moves, just_started, moved_units): for wrapper in self.wrappers: # type: ControllerWrapper unit = self._getUnitFromControllerWrapper(wrapper) if unit not in moved_units: # Two moves on the same unit cannot be performed at the same time... if not unit.isAlive() and (unit not in self._killSent or not self._killSent[unit]): self.wrappersInfoConnection[wrapper].send( SpecialEvent(flag=SpecialEvent.UNIT_KILLED)) self._killSent[unit] = True current_move = self._getNextMoveForUnitIfAvailable(unit) if current_move is not None: move_state = self._performNextStepOfMove( current_move.unit, current_move) self._fillMoveStructures(completed_moves, just_started, illegal_moves, impossible_moves, current_move, move_state) def _handleOtherMoves(self, completed_moves, illegal_moves, impossible_moves, just_started, moved_units): for unit in self._otherMoves: # type: Unit move = self._otherMoves[unit] if move is not None: move_state = self._performNextStepOfMove(move.unit, move) if move_state != MOVE_FAILED: moved_units.append(move.unit) if move.finished(): self._otherMoves[unit] = None self._fillMoveStructures(completed_moves, just_started, illegal_moves, impossible_moves, move, move_state) def _fillMoveStructures(self, completed_moves: Dict[Unit, Tuple[TileIdentifier, MoveDescriptor]], just_started: Dict[int, MoveDescriptor], illegal_moves: List[Unit], impossible_moves: List[Unit], move: Path, move_state: int): """ Takes a move's state and the data structures of the performed moves in this iteration an fill them following the state's value Args: completed_moves: The dict containing the units that completed a move along with their new tile_id just_started: The dict containing the number of the units that started a move, along with the descriptor of the started move illegal_moves: The list containing all the units that performed an illegal move this iteration impossible_moves: The list containing all the units that performed an impossible move this iteration move: The performed move move_state: The state of the performed move """ if move_state == MOVE_COMPLETED_AND_JUST_STARTED: completed_moves[move.unit] = (move.reachedTileIdentifier, self._moveDescriptors[move]) just_started[move.unit.playerNumber] = self._moveDescriptors[move] elif move_state == MOVE_COMPLETED: completed_moves[move.unit] = (move.reachedTileIdentifier, self._moveDescriptors[move]) elif move_state == MOVE_JUST_STARTED: just_started[move.unit.playerNumber] = self._moveDescriptors[move] elif move_state == MOVE_ILLEGAL: illegal_moves.append(move.unit) elif move_state == MOVE_IMPOSSIBLE: impossible_moves.append(move.unit) def _addMessageToSendToAll(self, moved_unit_number: int, move_descriptor: MoveDescriptor): """ Adds a message to the message queue of each ControllerWrapper Args: moved_unit_number: The number representing the unit that moved move_descriptor: The descriptor of the performed move """ controlled_unit = self.game.getControllerUnitForNumber( moved_unit_number) if controlled_unit is None: controlled_unit_number = moved_unit_number else: controlled_unit_number = controlled_unit.playerNumber for wrapper in self._eventsToSend: self._eventsToSend[wrapper].append( BotEvent(controlled_unit_number, move_descriptor)) @staticmethod def _performNextStepOfMove(unit: Unit, current_move: Path) -> int: """ Perform the next step of the given move on the given unit Args: unit: The unit that performs the move current_move: The current move to perform Returns: A couple of booleans. The first indicating that the move has been completed and the second indicating that the move has just started """ if unit.isAlive(): if current_move is not None: try: just_started, move_completed, tile_id = current_move.performNextMove( ) if just_started and move_completed: return MOVE_COMPLETED_AND_JUST_STARTED elif move_completed: # A new tile has been reached by the movement return MOVE_COMPLETED elif just_started: return MOVE_JUST_STARTED return MOVE_IN_PROGRESS except IllegalMove: return MOVE_ILLEGAL except ImpossibleMove: return MOVE_IMPOSSIBLE else: if current_move is not None: current_move.stop(cancel_post_action=True) return MOVE_FAILED def _getNextMoveForUnitIfAvailable(self, unit: Unit) -> Union[Path, None]: """ Checks if a move is available for the given controller, and if so, returns it Args: unit: The given Returns: The next move if it is available, and None otherwise """ moves = self._unitsMoves[unit] current_move = moves[0] # type: Path if current_move is None or current_move.finished(): if current_move is not None: if isinstance(current_move, Path): self._reactToFinishedMove() del self._moveDescriptors[current_move] try: move = moves[1].get_nowait() # type: Path self._unitsMoves[unit] = (move, moves[1]) current_move = move except Empty: self._unitsMoves[unit] = (None, moves[1]) current_move = None return current_move def _checkGameState(self) -> int: """ Checks if the game is finished Returns: 0 = CONTINUE; 2 = END """ if self.game.isFinished(): self.winningPlayers = self.game.winningPlayers return END return CONTINUE def _handleEvent(self, unit: Unit, event: MoveDescriptor, player_number: int, force: bool = False) -> None: """ The goal of this method is to handle the given event for the given unit Args: unit: The unit that sent the event through its linker event: The event sent by the controller """ try: move = self.api.createMoveForDescriptor( unit, event, force=force) # may raise: UnfeasibleMoveException self._currentTurnTaken = True self._moveDescriptors[move] = event self._addMove(unit, move) except UnfeasibleMoveException: self._sendEventsToController(player_number, event=WakeEvent()) def _getPipeConnection(self, linker: ControllerWrapper) -> PipeConnection: """ Args: linker: The linker for which we want the pipe connection Returns: The pipe connection to send and receive game updates """ return self.wrappersConnection[linker] def _getUnitFromControllerWrapper(self, linker: ControllerWrapper) -> Unit: """ Args: linker: The linker for which we want the unit Returns: The unit for the given linker """ return self.wrappers[linker] def _sendEventsToController(self, player_number: int, event: Event = None): player_wrapper = self.getWrapperFromPlayerNumber(player_number) pipe_conn = self._getPipeConnection(player_wrapper) if event is None: events = self._eventsToSend[player_wrapper] if len(events) > 0: event = MultipleEvents(events) if event is not None: pipe_conn.send(event) self._eventsToSend[player_wrapper] = [] def _informBotOnPerformedMove(self, moved_unit_number: int, move_descriptor: MoveDescriptor) -> None: """ Update the game state of the bot controllers Args: moved_unit_number: The number representing the unit that moved and caused the update move_descriptor: The move that caused the update """ for wrapper in self.wrappers: if issubclass(type(wrapper), BotControllerWrapper): pipe_conn = self._getPipeConnection(wrapper) pipe_conn.send(BotEvent(moved_unit_number, move_descriptor)) def _killUnit(self, unit: Unit, linker: ControllerWrapper) -> None: """ Kills the given unit and tells its linker Args: unit: The unit to kill linker: The linker, to which tell that the unit is dead """ unit.kill() if not unit.isAlive(): self.wrappersInfoConnection[linker].send( SpecialEvent(flag=SpecialEvent.UNIT_KILLED)) def _addCollaborationPipes(self, linker: BotControllerWrapper) -> None: """ Adds the collaboration pipes between the given linker and its teammate's Args: linker: The linker to connect with its teammate """ for teammate in self.game.teams[self.game.unitsTeam[ self.wrappers[linker]]]: if teammate is not self.wrappers[linker]: teammate_linker = None # type: BotControllerWrapper for other_linker in self.wrappers: if self.wrappers[other_linker] is teammate: teammate_linker = other_linker break pipe1, pipe2 = Pipe() linker.addCollaborationPipe( teammate_linker.controller.playerNumber, pipe1) teammate_linker.addCollaborationPipe( linker.controller.playerNumber, pipe2) def _prepareLoop(self) -> None: """ Launches the processes of the AIs """ self.executor = Pool(len(self.wrappers)) try: self.executor.apipe(lambda: None) except ValueError: self.executor.restart() for wrapper in self.wrappers: if isinstance(wrapper, BotControllerWrapper): wrapper.controller.gameState = self.game.copy() self.executor.apipe(wrapper.run) for wrapper in self.wrappers: pipe = self._getPipeConnection(wrapper) event = pipe.recv( ) # Waiting for the processes to launch correctly assert (isinstance(event, ReadyEvent)) self._prepared = True def _addControllerWrapper(self, wrapper: ControllerWrapper, unit: Unit) -> None: """ Adds the linker to the loop, creating the pipe connections Args: wrapper: The linker to add unit: The unit, linked by this linker """ self.wrappers[wrapper] = unit parent_conn, child_conn = Pipe() parent_info_conn, child_info_conn = Pipe() self.wrappersConnection[wrapper] = parent_conn self.wrappersInfoConnection[wrapper] = parent_info_conn self._eventsToSend[wrapper] = [] wrapper.setMainPipe(child_conn) wrapper.setGameInfoPipe(child_info_conn) if isinstance(wrapper, BotControllerWrapper): self._addCollaborationPipes(wrapper) @abstractmethod def _mustSendInitialWakeEvent(self, initial_action: MoveDescriptor, unit: Unit) -> bool: pass @abstractmethod def _mustRetrieveNextMove(self, current_wrapper: ControllerWrapper) -> bool: pass @abstractmethod def _getPlayerNumbersToWhichSendEvents(self) -> List[int]: pass @abstractmethod def _reactToFinishedMove(self): pass
def run_global_automated(self, grphq=False, duration_gif=0.5, pas=1, B=10, loop=100): """ Implémentation modifiée de run_global où le choix du nombre de cluster est déterminé par des statistiques calculés au fur et à mesure. Les paramètres sont : grphq, duration_gif, pas, B, loop et correspondent aux définitions évoqués dans run_global. Paramètre de sortie : instance idéal de Kmeans. """ pool = Pool(self.cpu) pool.close() pool.join() mini, maxi = np.min(self.data.data, axis=0), np.max(self.data.data, axis=0) shape = self.data.data.shape i = 1 self.set_nb_cluster(i) self.choose_means_initiate() self.calc_grp() self.choose_means() self.calc_grp() means = self.means if grphq: self.grphq.plot_graph(self.data.data, self.grp, self.means.reshape((1, -1)), 1) self.print_meta_data() gap, var = self.gap_stat_mono(self.error, i, mini, maxi, shape, pool, B) cond = True km_cpy = self.copy(erase_dir=False) print("Fin de l'étape {}".format(i)) while cond: i += 1 self.set_nb_cluster(i) pool.restart() f = partial(self.__multi_j, loop=loop, means=means) s = pool.uimap(f, range(0, self.L, pas)) pool.close() pool.join() s = np.array(list(s)) arg = np.argmin(s[:, 1]) j = int(s[arg, 0]) means_cpy = np.vstack((means, self.data.data[j])) self.means = means_cpy k = 0 backup = (None, None, -1, -1) self.calc_grp() while (self.cond_conv(backup)) and (k < loop): k += 1 backup = self.backup_metadata() self.choose_means() if ((self.choose_means != self.choose_means_moy_true) and (self.choose_means != self.choose_means_med_true)): self.calc_grp() self.migration = np.count_nonzero( (self.grp[:, 1] - backup[1][:, 1])) self.same_means = np.array_equal(self.means, backup[0]) means = self.means gap_f, var_f = self.gap_stat_mono(self.error, i, mini, maxi, shape, pool, B) diff = gap - (gap_f - var_f) print("Gap statistical (étape {}) : {}".format(i - 1, diff)) if grphq: self.grphq.plot_graph(self.data.data, self.grp, self.means, i) self.print_meta_data() print("Fin de l'étape {}".format(i)) if diff >= 0: break else: gap = gap_f km_cpy = self.copy(erase_dir=False) if grphq: self.grphq.create_gif(duration=duration_gif) self = km_cpy.copy(erase_dir=False) self.calc_grp() print("Le nombre optimal de classes est : {}".format(self.nb_cluster)) del pool return self
def run_global(self, loop=100, grphq=False, duration_gif=0.5, pas=1, choose_nb_graph=False, B=10): """ Implémente l'algorithme des global k-means qui calcule incrémentalement la configuration optimale des groupes pour un nombre de clusters donnée. L'algotrithme procède comme suit : 0) On définit le nombre cluster à 1 et on calcule le centre de la matrice de données. 1) On incrément le nombre de cluster. On définit comme centre les centres de l'étape précédente. On définit successivement chaque individu de la matrice de données comme dernier centre, on exécute l'algorithme du k-means avec chaque lot de centres et on garde le lot de centre qui minimise l'erreur. i+1) On réitère l'étape précédente jusqu'à obtenir le bon nombre de groupe. !!! Très gourmand en ressources. Paramètres d'entrée : loop : entier définissant le nombre d'itérations au sein des calcule de k-means avant arrêt du calcul, défaut = 100 grphq : boolean indiquant si les graphes doivent être affichés et enregistrés. duration_gif : réel qui caractérise la durée de chaque image dans la production du gif final, inutile si grphq = False pas : entier qui détermine l'écart entre chaque individu à tester pour le choix des individus comme centre. choose_nb_graph : boolean, affiche un lot de statistiques qui permettent de déterminer le nombre idéal de clusters. B : entier qui qui entre en jeu dans le calcul des statistiques évoquées précédemment. Paramètre de sortie : err : erreur de classification pour le nombre de cluster choisi. """ pool = Pool(self.cpu) pool.close() pool.join() err = [] n = self.nb_cluster self.set_nb_cluster(1) self.choose_means_initiate() self.calc_grp() self.choose_means() self.calc_grp() means = self.means err.append([1, self.error, self.clustering_error_rel(), self.var]) if grphq: self.grphq.plot_graph(self.data.data, self.grp, self.means.reshape((1, -1)), 1) self.print_meta_data() print("Fin de l'étape {}".format(1)) for i in range(2, n + 1): self.set_nb_cluster(i) pool.restart() f = partial(self.__multi_j, loop=loop, means=means) s = pool.uimap(f, range(0, self.L, pas)) pool.close() pool.join() s = np.array(list(s)) arg = np.argmin(s[:, 1]) j = int(s[arg, 0]) means_cpy = np.vstack((means, self.data.data[j])) self.means = means_cpy k = 0 backup = (None, None, -1, -1) self.calc_grp() while (self.cond_conv(backup)) and (k < loop): k += 1 backup = self.backup_metadata() self.choose_means() if ((self.choose_means != self.choose_means_moy_true) and (self.choose_means != self.choose_means_med_true)): self.calc_grp() self.migration = np.count_nonzero( (self.grp[:, 1] - backup[1][:, 1])) self.same_means = np.array_equal(self.means, backup[0]) means = self.means if grphq: self.grphq.plot_graph(self.data.data, self.grp, self.means, i) self.print_meta_data() err.append([i, self.error, self.clustering_error_rel(), self.var]) print("Fin de l'étape {}".format(i)) err = np.array(err) err = err[np.argsort(err[:, 0]), :].T if grphq: self.grphq.create_gif(duration=duration_gif) if choose_nb_graph: self.grphq.plot_crb_err_cluster(self.gap_stat(err, B)) del pool return err