def rebalance(self):
        """The genetic rebalancing algorithm runs for a fixed number of
        generations. Each generation has two phases: exploration and pruning.
        In exploration, a large set of possible states are found by randomly
        applying assignment changes to the existing states. In pruning, each
        state is given a score based on the balance of the cluster and the
        states with the highest scores are chosen as the starting states for
        the next generation.
        """
        if self.args.num_gens < self.args.max_partition_movements:
            self.log.warning(
                "num-gens ({num_gens}) is less than max-partition-movements"
                " ({max_partition_movements}). max-partition-movements will"
                " never be reached.".format(
                    num_gens=self.args.num_gens,
                    max_partition_movements=self.args.max_partition_movements,
                )
            )

        if self.args.replication_groups:
            self.log.info("Rebalancing replicas across replication groups...")
            rg_movement_count, rg_movement_size = self.rebalance_replicas(
                max_movement_count=self.args.max_partition_movements,
                max_movement_size=self.args.max_movement_size,
            )
            self.log.info(
                "Done rebalancing replicas. %d partitions moved.",
                rg_movement_count,
            )
        else:
            rg_movement_size = 0
            rg_movement_count = 0

        # Use a fixed random seed to make results reproducible.
        random.seed(RANDOM_SEED)

        # NOTE: only active brokers are considered when rebalancing
        state = _State(
            self.cluster_topology,
            brokers=self.cluster_topology.active_brokers
        )
        state.movement_size = rg_movement_size
        pop = {state}

        do_rebalance = self.args.brokers or self.args.leaders

        # Cannot rebalance when all partitions have zero weight because the
        # score function is undefined.
        if do_rebalance and not state.total_weight:
            self.log.error(
                "Rebalance impossible. All partitions have zero weight.",
            )
            do_rebalance = False

        if do_rebalance:
            self.log.info("Rebalancing with genetic algorithm.")
            # Run the genetic algorithm for a fixed number of generations.
            for i in range(self.args.num_gens):
                start = time.time()
                pop_candidates = self._explore(pop)
                pop = self._prune(pop_candidates)
                end = time.time()
                self.log.debug(
                    "Generation %d: keeping %d of %d assignment(s) in %f seconds",
                    i,
                    len(pop),
                    len(pop_candidates),
                    end - start,
                )

        # Choose the state with the greatest score.
        state = sorted(pop, key=self._score, reverse=True)[0]
        self.log.info(
            "Done rebalancing. %d partitions moved.",
            state.movement_count,
        )
        self.log.info("Total movement size: %f", state.movement_size)
        assignment = state.assignment

        # Since only active brokers are considered when rebalancing, inactive
        # brokers need to be added back to the new assignment.
        all_brokers = set(self.cluster_topology.brokers.values())
        inactive_brokers = all_brokers - set(state.brokers)
        for partition_name, replicas in assignment:
            for broker in inactive_brokers:
                if broker in self.cluster_topology.partitions[partition_name].replicas:
                    replicas.append(broker.id)

        self.cluster_topology.update_cluster_topology(assignment)