def fit_from_scipy_sparse_matrix( self, adj, attr, spatially_extensive_attr, threshold, max_it=10, objective_func=ObjectiveFunctionPairwise()): """ Solve the max-p-regions problem in a heuristic way (see [DAR2012]_). The resulting region labels are assigned to the instance's :attr:`labels_` attribute. Parameters ---------- adj : :class:`scipy.sparse.csr_matrix` Adjacency matrix representing the areas' contiguity relation. attr : :class:`numpy.ndarray` Array (number of areas x number of attributes) of areas' attributes relevant to clustering. spatially_extensive_attr : :class:`numpy.ndarray` Array (number of areas x number of attributes) of areas' attributes relevant to ensuring the threshold condition. threshold : numbers.Real or :class:`numpy.ndarray` The lower bound for a region's sum of spatially extensive attributes. The argument's type is numbers.Real if there is only one spatially extensive attribute per area, otherwise it is a one-dimensional array with as many entries as there are spatially extensive attributes per area. max_it : int, default: 10 The maximum number of partitions produced in the algorithm's construction phase. objective_func : :class:`region.objective_function.ObjectiveFunction`, default: ObjectiveFunctionPairwise() The objective function to use. """ print("f_f_SCIPY got:\n", attr, "\n", spatially_extensive_attr, "\n", threshold, sep="") weights = ps_api.WSP(adj).to_W() areas_dict = weights.neighbors self.metric = objective_func.metric best_partition = None best_obj_value = float("inf") feasible_partitions = [] partitions_before_enclaves_assignment = [] max_p = 0 # maximum number of regions # construction phase # print("constructing") for _ in range(max_it): # print(" ", _) partition, enclaves = self.grow_regions(adj, attr, spatially_extensive_attr, threshold) n_regions = len(partition) if n_regions > max_p: partitions_before_enclaves_assignment = [(partition, enclaves)] max_p = n_regions elif n_regions == max_p: partitions_before_enclaves_assignment.append( (partition, enclaves)) # print("\n" + "assigning enclaves") for partition, enclaves in partitions_before_enclaves_assignment: # print(" cleaning up in partition", partition) feasible_partitions.append( self.assign_enclaves(partition, enclaves, areas_dict, attr)) for partition in feasible_partitions: print(partition, "\n") # local search phase if self.local_search is None: self.local_search = AZP() self.local_search.allow_move_strategy = AllowMoveAZPMaxPRegions( spatially_extensive_attr, threshold, self.local_search.allow_move_strategy) for partition in feasible_partitions: self.local_search.fit_from_scipy_sparse_matrix( adj, attr, max_p, initial_labels=array_from_region_list(partition), objective_func=objective_func) partition = self.local_search.labels_ # print("optimized partition", partition) obj_value = objective_func(partition, attr) if obj_value < best_obj_value: best_obj_value = obj_value best_partition = partition self.labels_ = best_partition
def _tree(adj, attr, n_regions, solver, metric): """ Parameters ---------- adj : class:`scipy.sparse.csr_matrix` Refer to the corresponding argument in :func:`_flow`. attr : :class:`numpy.ndarray` Refer to the corresponding argument in :func:`_flow`. n_regions : int Refer to the corresponding argument in :func:`_flow`. solver : str Refer to the corresponding argument in :func:`_flow`. metric : function Refer to the corresponding argument in :func:`_flow`. Returns ------- result : :class:`numpy.ndarray` Refer to the return value in :func:`_flow`. """ print("running TREE algorithm") # TODO: rm prob = LpProblem("Tree", LpMinimize) # Parameters of the optimization problem n_areas = attr.shape[0] I = list(range(n_areas)) # index for areas II = [(i, j) for i in I for j in I] II_upper_triangle = [(i, j) for i, j in II if i < j] d = { (i, j): metric( attr[i].reshape(attr.shape[1], 1), # reshaping to... attr[j].reshape(attr.shape[1], 1)) # ...avoid warnings for i, j in II } # Decision variables t = LpVariable.dicts("t", ((i, j) for i, j in II), lowBound=0, upBound=1, cat=LpInteger) x = LpVariable.dicts("x", ((i, j) for i, j in II), lowBound=0, upBound=1, cat=LpInteger) u = LpVariable.dicts("u", (i for i in I), lowBound=0, cat=LpInteger) # Objective function # (3) in Duque et al. (2011): "The p-Regions Problem" prob += lpSum(d[i, j] * t[i, j] for i, j in II_upper_triangle) # Constraints # (4) in Duque et al. (2011): "The p-Regions Problem" lhs = lpSum(x[i, j] for i in I for j in neighbors(adj, i)) prob += lhs == n_areas - n_regions # (5) in Duque et al. (2011): "The p-Regions Problem" for i in I: prob += lpSum(x[i, j] for j in neighbors(adj, i)) <= 1 # (6) in Duque et al. (2011): "The p-Regions Problem" for i in I: for j in I: for m in I: if i != j and i != m and j != m: prob += t[i, j] + t[i, m] - t[j, m] <= 1 # (7) in Duque et al. (2011): "The p-Regions Problem" for i, j in II: prob += t[i, j] - t[j, i] == 0 # (8) in Duque et al. (2011): "The p-Regions Problem" for i in I: for j in neighbors(adj, i): prob += x[i, j] <= t[i, j] # (9) in Duque et al. (2011): "The p-Regions Problem" for i in I: for j in neighbors(adj, i): prob += u[i] - u[j] + (n_areas - n_regions) * x[i, j] \ + (n_areas - n_regions - 2) * x[j, i] \ <= n_areas - n_regions - 1 # (10) in Duque et al. (2011): "The p-Regions Problem" for i in I: prob += u[i] <= n_areas - n_regions prob += u[i] >= 1 # (11) in Duque et al. (2011): "The p-Regions Problem" # already in LpVariable-definition # (12) in Duque et al. (2011): "The p-Regions Problem" # already in LpVariable-definition # Solve the optimization problem solver = get_solver_instance(solver) prob.solve(solver) # build a list of regions like [[0, 1, 2, 5], [3, 4, 6, 7, 8]] idx_copy = set(I) regions = [[] for _ in range(n_regions)] for i in range(n_regions): area = idx_copy.pop() regions[i].append(area) for other_area in idx_copy: if t[area, other_area].varValue == 1: regions[i].append(other_area) idx_copy.difference_update(regions[i]) result = array_from_region_list(regions) return result