[docs]defclusterize(A:nx.Graph,capacity:int)->tuple[list[set[int]],list[int]]:"""Partition the terminals of A into one cluster per root. For the moment, it enforces the minimum number of feeders, i.e. ceil(T/capacity). The algorithm guarantees that the number of feeders for the entire location is not increased by the clustering. This means only one partition may have a subtree with capacity slack. It does not attempt to make uniform-sized clusters, terminals tend to be allocated to the closest root (distance measured in `P_paths` - see `make_planar_embedding()`). """R,T=(A.graph[k]forkin'RT')d2roots=A.graph['d2roots']mainheap=[]idx_=[tuple(range(-R,i))+tuple(range(i+1,0))foriinrange(-R,0)]expel_=[[]for_inrange(R)]cluster_=[set()for_inrange(R)]# initialize mainheapclosest_root=-R+d2roots[:T].argmin(axis=1)forn,rinenumerate(closest_root):d=d2roots[n,r]heappush(mainheap,(d,n,r))total_feeders=math.ceil(T/capacity)num_slack=total_feeders*capacity-T# expeller functiondefexpel_from(r,blocked=[]):expel=expel_[r]cluster=cluster_[r]saved_=[]whileexpel:tradeoff_exp,n_exp,r_exp=heappop(expel)ifr_expinblocked:# blocked prevents a node being expelled back and forthsaved_.append((tradeoff_exp,n_exp,r_exp))continueifn_expincluster:breakforsavedinsaved_:heappush(expel,saved)was_cluster_exp_round=(len(cluster_[r_exp])%capacity)==0cluster.remove(n_exp)# print(f'{F[n_exp]}: {F[r]} -> {F[r_exp]}', end='|')cluster_[r_exp].add(n_exp)d_exp=d2roots[n_exp,r_exp]foriinidx_[r_exp]:heappush(expel_[r_exp],(d2roots[n_exp,i]-d_exp,n_exp,i))# print(f'({d2roots[n_exp, i]:.0f}, {d_exp:.0f})', end='|')ifwas_cluster_exp_round:expel_from(r_exp,blocked+[r])whilemainheap:d,n,r=heappop(mainheap)cluster=cluster_[r]expel=expel_[r]was_cluster_round=(len(cluster)%capacity)==0# the (- 1) is because we just popped a node but have not assigned itthreshold=(sum((capacity-len(clu)%capacity)forcluincluster_)-num_slack-1)# add first and expel later if necessarycluster.add(n)foriinidx_[r]:heappush(expel,(d2roots[n,i]-d,n,i))if(len(mainheap)<=thresholdandwas_cluster_roundandnotall((len(cluster_[i])%capacity==0)foriinidx_[r])):# cluster is overfull: expelexpel_from(r)# this only works because the slack is allocated in a single clusternum_slack_=[num_slackif(len(cluster)%capacity)else0forclusterincluster_]returncluster_,num_slack_