def find_best_region_idx(self, area, partition, candidate_regions_idx, attr): """ Parameters ---------- area : The area to be moved to one of the regions specified by `candidate_regions_idx`. partition : `list` Each element (of type `set`) represents a region. candidate_regions_idx : iterable Each element is the index of a region in the `partition` list. attr : :class:`numpy.ndarray` See the corresponding argument in :meth:`fit_from_scipy_sparse_matrix`. Returns ------- best_idx : int The index of a region (w.r.t. `partition`) which has the smallest sum of dissimilarities after area `area` is moved to the region. """ dissim_per_idx = { region_idx: sum( self.metric(attr[area].reshape(1, -1), attr[area2].reshape( 1, -1)) for area2 in partition[region_idx]) for region_idx in candidate_regions_idx } minimum_dissim = min(dissim_per_idx.values()) best_idxs = [ idx for idx in dissim_per_idx if dissim_per_idx[idx] == minimum_dissim ] return random_element_from(best_idxs)
def find_best_area(self, region, candidates, attr): """ Parameters ---------- region : iterable Each element represents an area. candidates : iterable Each element represents an area bordering on region. attr : :class:`numpy.ndarray` See the corresponding argument in :meth:`fit_from_scipy_sparse_matrix`. Returns ------- best_area : An element of `candidates` with minimal dissimilarity when being moved to the region `region`. """ candidates = { area: sum( self.metric(attr[area].reshape(1, -1), attr[area2].reshape( 1, -1)) for area2 in region) for area in candidates } best_candidates = [ area for area in candidates if candidates[area] == min(candidates.values()) ] return random_element_from(best_candidates)
def test_random_element_from(): lst = list(range(1000)) n_pops = 5 popped = [] for _ in range(n_pops): lst_copy = list(lst) popped.append(util.random_element_from(lst_copy)) assert len(lst_copy) == len(lst) assert not_all_elements_equal(popped)
def _azp_connected_component(self, adj, initial_clustering, attr): """ Implementation of the basic tabu version of the AZP algorithm (refer to [OR1995]_) for a spatially connected set of areas (i.e. for every area there is a path to every other area). Parameters ---------- adj : :class:`scipy.sparse.csr_matrix` Refer to the corresponding argument in :meth:`AZP._azp_connected_component`. initial_clustering : :class:`numpy.ndarray` Refer to the corresponding argument in :meth:`AZP._azp_connected_component`. attr : :class:`numpy.ndarray` Refer to the corresponding argument in :meth:`AZP._azp_connected_component`. Returns ------- labels : :class:`numpy.ndarray` Refer to the return value in :meth:`AZP._azp_connected_component`. """ self.reset_tabu() # if there is only one region in the initial solution, just return it. distinct_regions = list(np.unique(initial_clustering)) if len(distinct_regions) == 1: return initial_clustering # step 2: make a list of the M regions labels = initial_clustering visited = [] stop = False while True: # added termination condition (not in Openshaw & Rao (1995)) label_tup = tuple(labels) if visited.count(label_tup) >= self.reps_before_termination: stop = True visited.append(label_tup) # step 1 Find the global best move that is not prohibited or tabu. # find possible moves (globally) best_move = None best_objval_diff = float("inf") for area in range(labels.shape[0]): old_region = labels[area] sub_adj = sub_adj_matrix( adj, np.where(labels == old_region)[0], wo_nodes=area) # moving the area must not destroy spatial contiguity in donor # region and if area is alone in its region, it must stay: if is_connected(sub_adj) and count(labels, old_region) > 1: for neigh in neighbors(adj, area): new_region = labels[neigh] if new_region != old_region: possible_move = Move(area, old_region, new_region) if possible_move not in self.tabu: objval_diff = self.objective_func.update( possible_move.area, possible_move.new_region, labels, attr) if objval_diff < best_objval_diff: best_move = possible_move best_objval_diff = objval_diff # step 2: Make this move if it is an improvement or equivalet in # value. if best_move is not None and best_objval_diff <= 0: self._make_move(best_move.area, best_move.new_region, labels) else: # step 3: if no improving move can be made, then see if a tabu # move can be made which improves on the current local best # (termed an aspiration move) improving_tabus = [ move for move in self.tabu if labels[move.area] == move.old_region and self.objective_func.update(move.area, move.new_region, labels, attr) < 0 ] if improving_tabus: aspiration_move = random_element_from(improving_tabus) self._make_move(aspiration_move.area, aspiration_move.new_region, labels) else: # step 4: If there is no improving move and no aspirational # move, then make the best move even if it is nonimproving # (that is, results in a worse value of the objective # function). if stop: break if best_move is not None: self._make_move(best_move.area, best_move.new_region, labels) return labels