Exemplo n.º 1
0
def test_pop_randomly_from():
    lst = list(range(1000))
    n_pops = 5
    popped = []
    for _ in range(n_pops):
        lst_copy = list(lst)
        popped.append(util.pop_randomly_from(lst_copy))
        assert len(lst_copy) == len(lst) - 1
    assert not_all_elements_equal(popped)
Exemplo n.º 2
0
def test_pop_randomly_from():
    lst = list(range(1000))
    n_pops = 5
    popped = []
    for _ in range(n_pops):
        lst_copy = list(lst)
        popped.append(util.pop_randomly_from(lst_copy))
        assert len(lst_copy) == len(lst) - 1
    assert not_all_elements_equal(popped)
Exemplo n.º 3
0
    def assign_enclaves(self, partition, enclave_areas, neigh_dict, attr):
        """
        Start with a partial partition (not all areas are assigned to a region)
        and a list of enclave areas (i.e. areas not present in the partial
        partition). Then assign all enclave areas to regions in the partial
        partition and return the resulting partition.

        Parameters
        ----------
        partition : `list`
            Each element (of type `set`) represents a region.
        enclave_areas : `list`
            Each element represents an area.
        neigh_dict : `dict`
            Each key represents an area. Each value is an iterable of the
            corresponding neighbors.
        attr : :class:`numpy.ndarray`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.

        Returns
        -------
        partition : `list`
            Each element (of type `set`) represents a region.
        """
        # print("partition:", partition, "- enclaves:", enclave_areas)
        while enclave_areas:
            neighbors_of_assigned = [
                area for area in enclave_areas if any(
                    neigh not in enclave_areas for neigh in neigh_dict[area])
            ]
            area = pop_randomly_from(neighbors_of_assigned)
            neigh_regions_idx = []
            for neigh in neigh_dict[area]:
                try:
                    neigh_regions_idx.append(
                        find_sublist_containing(neigh, partition, index=True))
                except LookupError:
                    pass
            region_idx = self.find_best_region_idx(area, partition,
                                                   neigh_regions_idx, attr)
            partition[region_idx].add(area)
            enclave_areas.remove(area)
        return partition
Exemplo n.º 4
0
    def grow_regions(self, adj, attr, spatially_extensive_attr, threshold):
        """
        Parameters
        ----------
        adj : :class:`scipy.sparse.csr_matrix`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.
        attr : :class:`numpy.ndarray`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.
        spatially_extensive_attr : :class:`numpy.ndarray`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.
        threshold : numbers.Real or :class:`numpy.ndarray`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.

        Returns
        -------
        result : `tuple`
            `result[0]` is a `list`. Each list element is a `set` of a region's
            areas. Note that not every area is assigned to a region after this
            function has terminated, so they won't be in any of the `set`s in
            `result[0]`.
            `result[1]` is a `list` of areas not assigned to any region.
        """
        # print("grow_regions called with spatially_extensive_attr", spatially_extensive_attr)
        partition = []
        enclave_areas = []
        unassigned_areas = list(range(adj.shape[0]))
        assigned_areas = []

        # todo: rm prints
        while unassigned_areas:
            # print("partition", partition)
            area = pop_randomly_from(unassigned_areas)
            # print("seed in area", area)
            assigned_areas.append(area)
            if (spatially_extensive_attr[area] >= threshold).all():
                # print("  seed --> region :)")
                # print("because", spatially_extensive_attr[area], ">=", threshold)
                partition.append({area})
            else:
                region = {area}
                # print("  all neighbors:", neigh_dict[area])
                # print("  already assigned:", assigned_areas)
                unassigned_neighs = set(
                    adj[area].nonzero()[1]).difference(assigned_areas)
                feasible = True
                spat_ext_attr = spatially_extensive_attr[area].copy()
                while (spat_ext_attr < threshold).any():
                    # print(" ", spat_ext_attr, "<", threshold, "Need neighs!")
                    # print("  potential neighbors:", unassigned_neighs)
                    if unassigned_neighs:
                        neigh = self.find_best_area(region, unassigned_neighs,
                                                    attr)
                        # print(" we choose neighbor", neigh)
                        region.add(neigh)
                        unassigned_neighs.remove(neigh)
                        unassigned_neighs.update(set(adj[neigh].nonzero()[1]))
                        unassigned_neighs.difference_update(assigned_areas)
                        spat_ext_attr += spatially_extensive_attr[neigh]
                        unassigned_areas.remove(neigh)
                        assigned_areas.append(neigh)
                    else:
                        # print("  Oh no! No neighbors left :(")
                        enclave_areas.extend(region)
                        feasible = False
                        # the following line (present in the algorithm in
                        # [DAR2012]) is commented out because it leads to an
                        # infinite loop:
                        # unassigned_areas.extend(region)
                        for area in region:
                            assigned_areas.remove(area)
                        break
                if feasible:
                    partition.append(region)
                # print("  unassigned:", unassigned_areas)
                # print("  assigned:", assigned_areas)
                # print()
        # print("grow_regions partit.:", partition, "enclaves:", enclave_areas)
        return partition, enclave_areas
Exemplo n.º 5
0
    def _azp_connected_component(self, adj, initial_clustering, attr):
        """
        Implementation of the AZP algorithm 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`
            Adjacency matrix representing the contiguity relation. The matrix'
            shape is (N, N) where N denotes the number of areas in the
            currently considered connected component.
        initial_clustering : :class:`numpy.ndarray`
            Array of labels. Shape: (N,) where N denotes the number of areas in
            the currently considered connected component.
        attr : :class:`numpy.ndarray`
            Array of labels. Shape: (N, M) where N denotes the number of areas
            in the currently considered connected component and M denotes the
            number of attributes per area.

        Returns
        -------
        labels : :class:`numpy.ndarray`
            One-dimensional array of region labels after the AZP algorithm has
            been performed. Only region labels of the currently considered
            connected component are returned.
        """
        # 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
        distinct_regions_copy = distinct_regions.copy()

        #  step 2: make a list of the M regions
        labels = initial_clustering

        obj_val_start = float("inf")
        obj_val_end = self.allow_move_strategy.objective_val

        region_neighbors = {}
        for region in distinct_regions:
            region_areas = set(np.where(labels == region)[0])
            neighs = set()
            for area in region_areas:
                neighs.update(neighbors(adj, area))
            region_neighbors[region] = neighs.difference(region_areas)
        del neighs

        # step 7: Repeat until no further improving moves are made
        while obj_val_end < obj_val_start:  # improvement
            obj_val_start = float(obj_val_end)
            distinct_regions = distinct_regions_copy.copy()
            # step 6: when the list for region K is exhausted return to step 3
            # and select another region and repeat steps 4-6

            while distinct_regions:
                # step 3: select & remove any region K at random from this list
                recipient = pop_randomly_from(distinct_regions)
                while True:
                    # step 4: identify a set of zones bordering on members of
                    # region K that could be moved into region K without
                    # destroying the internal contiguity of the donor region(s)

                    candidates = []
                    for neigh in region_neighbors[recipient]:
                        neigh_region = labels[neigh]
                        sub_adj = sub_adj_matrix(
                            adj,
                            np.where(labels == neigh_region)[0],
                            wo_nodes=neigh)
                        if is_connected(sub_adj):
                            # if area is alone in its region, it must stay
                            if count(labels, neigh_region) > 1:
                                candidates.append(neigh)
                    # step 5: randomly select zones from this list until either
                    # there is a local improvement in the current value of the
                    # objective function or a move that is equivalently as good
                    # as the current best. Then make the move, update the list
                    # of candidate zones, and return to step 4 or else repeat
                    # step 5 until the list is exhausted.
                    while candidates:
                        cand = pop_randomly_from(candidates)
                        if self.allow_move_strategy(cand, recipient, labels):
                            donor = labels[cand]

                            make_move(cand, recipient, labels)

                            region_neighbors[donor].add(cand)
                            region_neighbors[recipient].discard(cand)

                            neighs_of_cand = neighbors(adj, cand)

                            recipient_region_areas = set(
                                np.where(labels == recipient)[0])
                            region_neighbors[recipient].update(neighs_of_cand)
                            region_neighbors[recipient].difference_update(
                                recipient_region_areas)

                            donor_region_areas = set(
                                np.where(labels == donor)[0])
                            not_donor_neighs_anymore = set(
                                area for area in neighs_of_cand if not any(
                                    a in donor_region_areas
                                    for a in neighbors(adj, area)))
                            region_neighbors[donor].difference_update(
                                not_donor_neighs_anymore)
                            break
                    else:
                        break

            obj_val_end = float(self.allow_move_strategy.objective_val)
        return labels
Exemplo n.º 6
0
    def grow_regions(self, adj, attr, spatially_extensive_attr, threshold):
        """
        Parameters
        ----------
        adj : :class:`scipy.sparse.csr_matrix`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.
        attr : :class:`numpy.ndarray`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.
        spatially_extensive_attr : :class:`numpy.ndarray`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.
        threshold : numbers.Real or :class:`numpy.ndarray`
            See the corresponding argument in
            :meth:`fit_from_scipy_sparse_matrix`.

        Returns
        -------
        result : `tuple`
            `result[0]` is a `list`. Each list element is a `set` of a region's
            areas. Note that not every area is assigned to a region after this
            function has terminated, so they won't be in any of the `set`s in
            `result[0]`.
            `result[1]` is a `list` of areas not assigned to any region.
        """
        partition = []
        enclave_areas = []
        unassigned_areas = list(range(adj.shape[0]))
        assigned_areas = []

        while unassigned_areas:
            area = pop_randomly_from(unassigned_areas)
            assigned_areas.append(area)
            if (spatially_extensive_attr[area] >= threshold).all():
                partition.append({area})
            else:
                region = {area}
                unassigned_neighs = set(
                    adj[area].nonzero()[1]).difference(assigned_areas)
                feasible = True
                spat_ext_attr = spatially_extensive_attr[area].copy()
                while (spat_ext_attr < threshold).any():
                    if unassigned_neighs:
                        neigh = self.find_best_area(region, unassigned_neighs,
                                                    attr)
                        region.add(neigh)
                        unassigned_neighs.remove(neigh)
                        unassigned_neighs.update(set(adj[neigh].nonzero()[1]))
                        unassigned_neighs.difference_update(assigned_areas)
                        spat_ext_attr += spatially_extensive_attr[neigh]
                        unassigned_areas.remove(neigh)
                        assigned_areas.append(neigh)
                    else:
                        enclave_areas.extend(region)
                        feasible = False
                        # the following line (present in the algorithm in
                        # [DAR2012]) is commented out because it leads to an
                        # infinite loop:
                        # unassigned_areas.extend(region)
                        for area in region:
                            assigned_areas.remove(area)
                        break
                if feasible:
                    partition.append(region)
        return partition, enclave_areas