static real *get_or_assign_node_force(real *force, int i, SingleLinkedList l, int dim){ real *f = (real*) node_data_get_data(SingleLinkedList_get_data(l)); if (!f){ node_data_get_data(SingleLinkedList_get_data(l)) = &(force[i*dim]); f = (real*) node_data_get_data(SingleLinkedList_get_data(l)); } return f; }
static void QuadTree_get_nearest_internal(QuadTree qt, real *x, real *y, real *min, int *imin, int tentative, int *flag){ /* get the narest point years to {x[0], ..., x[dim]} and store in y.*/ SingleLinkedList l; real *coord, dist; int dim, i, iq = -1; real qmin; real *point = x; *flag = 0; if (!qt) return; dim = qt->dim; l = qt->l; if (l){ while (l){ coord = node_data_get_coord(SingleLinkedList_get_data(l)); dist = point_distance(point, coord, dim); if(*min < 0 || dist < *min) { *min = dist; *imin = node_data_get_id(SingleLinkedList_get_data(l)); for (i = 0; i < dim; i++) y[i] = coord[i]; } l = SingleLinkedList_get_next(l); } } if (qt->qts){ dist = point_distance(qt->center, point, dim); if (*min >= 0 && (dist - sqrt((real) dim) * qt->width > *min)){ return; } else { if (tentative){/* quick first approximation*/ qmin = -1; for (i = 0; i < 1<<dim; i++){ if (qt->qts[i]){ dist = point_distance(qt->qts[i]->average, point, dim); if (dist < qmin || qmin < 0){ qmin = dist; iq = i; } } } assert(iq >= 0); QuadTree_get_nearest_internal(qt->qts[iq], x, y, min, imin, tentative, flag); } else { for (i = 0; i < 1<<dim; i++){ QuadTree_get_nearest_internal(qt->qts[i], x, y, min, imin, tentative, flag); } } } } }
void QuadTree_get_supernodes_internal(QuadTree qt, real bh, real *point, int nodeid, int *nsuper, int *nsupermax, real **center, real **supernode_wgts, real **distances, real *counts, int *flag){ SingleLinkedList l; real *coord, dist; int dim, i; (*counts)++; if (!qt) return; dim = qt->dim; l = qt->l; if (l){ while (l){ check_or_realloc_arrays(dim, nsuper, nsupermax, center, supernode_wgts, distances); if (node_data_get_id(SingleLinkedList_get_data(l)) != nodeid){ coord = node_data_get_coord(SingleLinkedList_get_data(l)); for (i = 0; i < dim; i++){ (*center)[dim*(*nsuper)+i] = coord[i]; } (*supernode_wgts)[*nsuper] = node_data_get_weight(SingleLinkedList_get_data(l)); (*distances)[*nsuper] = point_distance(point, coord, dim); (*nsuper)++; } l = SingleLinkedList_get_next(l); } } if (qt->qts){ dist = point_distance(qt->center, point, dim); if (qt->width < bh*dist){ check_or_realloc_arrays(dim, nsuper, nsupermax, center, supernode_wgts, distances); for (i = 0; i < dim; i++){ (*center)[dim*(*nsuper)+i] = qt->average[i]; } (*supernode_wgts)[*nsuper] = qt->total_weight; (*distances)[*nsuper] = point_distance(qt->average, point, dim); (*nsuper)++; } else { for (i = 0; i < 1<<dim; i++){ QuadTree_get_supernodes_internal(qt->qts[i], bh, point, nodeid, nsuper, nsupermax, center, supernode_wgts, distances, counts, flag); } } } }
static void QuadTree_print_internal(FILE *fp, QuadTree q, int level){ /* dump a quad tree in Mathematica format. */ SingleLinkedList l, l0; real *coord; int i, dim; if (!q) return; draw_polygon(fp, q->dim, q->center, q->width); dim = q->dim; l0 = l = q->l; if (l){ printf(",(*a*) {Red,"); while (l){ if (l != l0) printf(","); coord = node_data_get_coord(SingleLinkedList_get_data(l)); fprintf(fp, "(*node %d*) Point[{", node_data_get_id(SingleLinkedList_get_data(l))); for (i = 0; i < dim; i++){ if (i != 0) printf(","); fprintf(fp, "%f",coord[i]); } fprintf(fp, "}]"); l = SingleLinkedList_get_next(l); } fprintf(fp, "}"); } if (q->qts){ for (i = 0; i < 1<<dim; i++){ fprintf(fp, ",(*b*){"); QuadTree_print_internal(fp, q->qts[i], level + 1); fprintf(fp, "}"); } } }
static void QuadTree_repulsive_force_accumulate(QuadTree qt, real *force, real *counts){ /* push down forces on cells into the node level */ real wgt, wgt2; real *f, *f2; SingleLinkedList l = qt->l; int i, k, dim; QuadTree qt2; dim = qt->dim; wgt = qt->total_weight; f = get_or_alloc_force_qt(qt, dim); assert(wgt > 0); counts[2]++; if (l){ while (l){ i = node_data_get_id(SingleLinkedList_get_data(l)); f2 = get_or_assign_node_force(force, i, l, dim); wgt2 = node_data_get_weight(SingleLinkedList_get_data(l)); wgt2 = wgt2/wgt; for (k = 0; k < dim; k++) f2[k] += wgt2*f[k]; l = SingleLinkedList_get_next(l); } return; } for (i = 0; i < 1<<dim; i++){ qt2 = qt->qts[i]; if (!qt2) continue; assert(qt2->n > 0); f2 = get_or_alloc_force_qt(qt2, dim); wgt2 = qt2->total_weight; wgt2 = wgt2/wgt; for (k = 0; k < dim; k++) f2[k] += wgt2*f[k]; QuadTree_repulsive_force_accumulate(qt2, force, counts); } }
static QuadTree QuadTree_add_internal(QuadTree q, real *coord, real weight, int id, int level){ int i, dim = q->dim, ii; node_data nd = NULL; int max_level = q->max_level; int idd; /* Make sure that coord is within bounding box */ for (i = 0; i < q->dim; i++) { if (coord[i] < q->center[i] - q->width - 1.e5*MACHINEACC*q->width || coord[i] > q->center[i] + q->width + 1.e5*MACHINEACC*q->width) { #ifdef DEBUG_PRINT fprintf(stderr,"coordinate %f is outside of the box:{%f, %f}, \n(q->center[i] - q->width) - coord[i] =%g, coord[i]-(q->center[i] + q->width) = %g\n",coord[i], (q->center[i] - q->width), (q->center[i] + q->width), (q->center[i] - q->width) - coord[i], coord[i]-(q->center[i] + q->width)); #endif //return NULL; } } if (q->n == 0){ /* if this node is empty right now */ q->n = 1; q->total_weight = weight; q->average = MALLOC(sizeof(real)*dim); for (i = 0; i < q->dim; i++) q->average[i] = coord[i]; nd = node_data_new(q->dim, weight, coord, id); assert(!(q->l)); q->l = SingleLinkedList_new(nd); } else if (level < max_level){ /* otherwise open up into 2^dim quadtrees unless the level is too high */ q->total_weight += weight; for (i = 0; i < q->dim; i++) q->average[i] = ((q->average[i])*q->n + coord[i])/(q->n + 1); if (!q->qts){ q->qts = MALLOC(sizeof(QuadTree)*(1<<dim)); for (i = 0; i < 1<<dim; i++) q->qts[i] = NULL; }/* done adding new quadtree, now add points to them */ /* insert the old node (if exist) and the current node into the appropriate child quadtree */ ii = QuadTree_get_quadrant(dim, q->center, coord); assert(ii < 1<<dim && ii >= 0); if (q->qts[ii] == NULL) q->qts[ii] = QuadTree_new_in_quadrant(q->dim, q->center, (q->width)/2, max_level, ii); q->qts[ii] = QuadTree_add_internal(q->qts[ii], coord, weight, id, level + 1); assert(q->qts[ii]); if (q->l){ idd = node_data_get_id(SingleLinkedList_get_data(q->l)); assert(q->n == 1); coord = node_data_get_coord(SingleLinkedList_get_data(q->l)); weight = node_data_get_weight(SingleLinkedList_get_data(q->l)); ii = QuadTree_get_quadrant(dim, q->center, coord); assert(ii < 1<<dim && ii >= 0); if (q->qts[ii] == NULL) q->qts[ii] = QuadTree_new_in_quadrant(q->dim, q->center, (q->width)/2, max_level, ii); q->qts[ii] = QuadTree_add_internal(q->qts[ii], coord, weight, idd, level + 1); assert(q->qts[ii]); /* delete the old node data on parent */ SingleLinkedList_delete(q->l, node_data_delete); q->l = NULL; } (q->n)++; } else { assert(!(q->qts)); /* level is too high, append data in the linked list */ (q->n)++; q->total_weight += weight; for (i = 0; i < q->dim; i++) q->average[i] = ((q->average[i])*q->n + coord[i])/(q->n + 1); nd = node_data_new(q->dim, weight, coord, id); assert(q->l); q->l = SingleLinkedList_prepend(q->l, nd); } return q; }
static void QuadTree_repulsive_force_interact(QuadTree qt1, QuadTree qt2, real *x, real *force, real bh, real p, real KP, real *counts){ /* calculate the all to all reopulsive force and accumulate on each node of the quadtree if an interaction is possible. force[i*dim+j], j=1,...,dim is teh force on node i */ SingleLinkedList l1, l2; real *x1, *x2, dist, wgt1, wgt2, f, *f1, *f2, w1, w2; int dim, i, j, i1, i2, k; QuadTree qt11, qt12; if (!qt1 || !qt2) return; assert(qt1->n > 0 && qt2->n > 0); dim = qt1->dim; l1 = qt1->l; l2 = qt2->l; /* far enough, calculate repulsive force */ dist = point_distance(qt1->average, qt2->average, dim); if (qt1->width + qt2->width < bh*dist){ counts[0]++; x1 = qt1->average; w1 = qt1->total_weight; f1 = get_or_alloc_force_qt(qt1, dim); x2 = qt2->average; w2 = qt2->total_weight; f2 = get_or_alloc_force_qt(qt2, dim); assert(dist > 0); for (k = 0; k < dim; k++){ if (p == -1){ f = w1*w2*KP*(x1[k] - x2[k])/(dist*dist); } else { f = w1*w2*KP*(x1[k] - x2[k])/pow(dist, 1.- p); } f1[k] += f; f2[k] -= f; } return; } /* both at leaves, calculate repulsive force */ if (l1 && l2){ while (l1){ x1 = node_data_get_coord(SingleLinkedList_get_data(l1)); wgt1 = node_data_get_weight(SingleLinkedList_get_data(l1)); i1 = node_data_get_id(SingleLinkedList_get_data(l1)); f1 = get_or_assign_node_force(force, i1, l1, dim); l2 = qt2->l; while (l2){ x2 = node_data_get_coord(SingleLinkedList_get_data(l2)); wgt2 = node_data_get_weight(SingleLinkedList_get_data(l2)); i2 = node_data_get_id(SingleLinkedList_get_data(l2)); f2 = get_or_assign_node_force(force, i2, l2, dim); if ((qt1 == qt2 && i2 < i1) || i1 == i2) { l2 = SingleLinkedList_get_next(l2); continue; } counts[1]++; dist = distance_cropped(x, dim, i1, i2); for (k = 0; k < dim; k++){ if (p == -1){ f = wgt1*wgt2*KP*(x1[k] - x2[k])/(dist*dist); } else { f = wgt1*wgt2*KP*(x1[k] - x2[k])/pow(dist, 1.- p); } f1[k] += f; f2[k] -= f; } l2 = SingleLinkedList_get_next(l2); } l1 = SingleLinkedList_get_next(l1); } return; } /* identical, split one */ if (qt1 == qt2){ for (i = 0; i < 1<<dim; i++){ qt11 = qt1->qts[i]; for (j = i; j < 1<<dim; j++){ qt12 = qt1->qts[j]; QuadTree_repulsive_force_interact(qt11, qt12, x, force, bh, p, KP, counts); } } } else { /* split the one with bigger box, or one not at the last level */ if (qt1->width > qt2->width && !l1){ for (i = 0; i < 1<<dim; i++){ qt11 = qt1->qts[i]; QuadTree_repulsive_force_interact(qt11, qt2, x, force, bh, p, KP, counts); } } else if (qt2->width > qt1->width && !l2){ for (i = 0; i < 1<<dim; i++){ qt11 = qt2->qts[i]; QuadTree_repulsive_force_interact(qt11, qt1, x, force, bh, p, KP, counts); } } else if (!l1){/* pick one that is not at the last level */ for (i = 0; i < 1<<dim; i++){ qt11 = qt1->qts[i]; QuadTree_repulsive_force_interact(qt11, qt2, x, force, bh, p, KP, counts); } } else if (!l2){ for (i = 0; i < 1<<dim; i++){ qt11 = qt2->qts[i]; QuadTree_repulsive_force_interact(qt11, qt1, x, force, bh, p, KP, counts); } } else { assert(0); /* can be both at the leaf level since that should be catched at the beginning of this func. */ } } }
Multilevel_MQ_Clustering Multilevel_MQ_Clustering_establish(Multilevel_MQ_Clustering grid, int maxcluster) { int *matching = grid->matching; SparseMatrix A = grid->A; int n = grid->n, level = grid->level, nc = 0, nclusters = n; real mq = 0, mq_in = 0, mq_out = 0, mq_new, mq_in_new, mq_out_new, mq_max = 0, mq_in_max = 0, mq_out_max = 0; int *ia = A->ia, *ja = A->ja; real *a, amax = 0; real *deg_intra = grid->deg_intra, *wgt = grid->wgt; real *deg_intra_new, *wgt_new = NULL; int i, j, k, jj, jc, jmax; real *deg_inter, gain = 0, *dout = grid->dout, *dout_new, deg_in_i, deg_in_j, wgt_i, wgt_j, a_ij, dout_i, dout_j, dout_max = 0, wgt_jmax = 0; int *mask; real maxgain = 0; real total_gain = 0; SingleLinkedList *neighbors = NULL, lst; neighbors = MALLOC(sizeof(SingleLinkedList)*n); for (i = 0; i < n; i++) neighbors[i] = NULL; mq = grid->mq; mq_in = grid->mq_in; mq_out = grid->mq_out; deg_intra_new = MALLOC(sizeof(real)*n); wgt_new = MALLOC(sizeof(real)*n); deg_inter = MALLOC(sizeof(real)*n); mask = MALLOC(sizeof(int)*n); dout_new = MALLOC(sizeof(real)*n); for (i = 0; i < n; i++) mask[i] = -1; assert(n == A->n); for (i = 0; i < n; i++) matching[i] = UNMATCHED; /* gain in merging node A into cluster B is mq_in_new = mq_in - |E(A,A)|/(V(A))^2 - |E(B,B)|/(V(B))^2 + (|E(A,A)|+|E(B,B)|+|E(A,B)|)/(|V(A)|+|V(B)|)^2 . = mq_in - deg_intra(A)/|A|^2 - deg_intra(B)/|B|^2 + (deg_intra(A)+deg_intra(B)+a(A,B))/(|A|+|B|)^2 mq_out_new = mq_out - |E(A,B)|/(|V(A)|*V(B)|)-\sum_{C and A connected, C!=B} |E(A,C)|/(|V(A)|*|V(C)|)-\sum_{C and B connected,C!=B} |E(B,C)|/(|V(B)|*|V(C)|) . + \sum_{C connected to A or B, C!=A, C!=B} (|E(A,C)|+|E(B,C)|)/(|V(C)|*(|V(A)|+|V(B)|) . = mq_out + a(A,B)/(|A|*|B|)-\sum_{C and A connected} a(A,C)/(|A|*|C|)-\sum_{C and B connected} a(B,C)/(|B|*|C|) . + \sum_{C connected to A or B, C!=A, C!=B} (a(A,C)+a(B,C))/(|C|*(|A|+|B|)) Denote: dout(i) = \sum_{j -- i} a(i,j)/|j| then mq_out_new = mq_out - |E(A,B)|/(|V(A)|*V(B)|)-\sum_{C and A connected, C!=B} |E(A,C)|/(|V(A)|*|V(C)|)-\sum_{C and B connected,C!=B} |E(B,C)|/(|V(B)|*|V(C)|) . + \sum_{C connected to A or B, C!=A, C!=B} (|E(A,C)|+|E(B,C)|)/(|V(C)|*(|V(A)|+|V(B)|) . = mq_out + a(A,B)/(|A|*|B|)-dout(A)/|A| - dout(B)/|B| . + (dout(A)+dout(B))/(|A|+|B|) - (a(A,B)/|A|+a(A,B)/|B|)/(|A|+|B|) . = mq_out -dout(A)/|A| - dout(B)/|B| + (dout(A)+dout(B))/(|A|+|B|) after merging A and B into cluster AB, dout(AB) = dout(A) + dout(B); dout(C) := dout(C) - a(A,C)/|A| - a(B,C)/|B| + a(A,C)/(|A|+|B|) + a(B, C)/(|A|+|B|) mq_new = mq_in_new/(k-1) - mq_out_new/((k-1)*(k-2)) gain = mq_new - mq */ a = (real*) A->a; for (i = 0; i < n; i++) { if (matching[i] != UNMATCHED) continue; /* accumulate connections between i and clusters */ for (j = ia[i]; j < ia[i+1]; j++) { jj = ja[j]; if (jj == i) continue; if ((jc=matching[jj]) != UNMATCHED) { if (mask[jc] != i) { mask[jc] = i; deg_inter[jc] = a[j]; } else { deg_inter[jc] += a[j]; } } } deg_in_i = deg_intra[i]; wgt_i = wgt[i]; dout_i = dout[i]; maxgain = 0; jmax = -1; for (j = ia[i]; j < ia[i+1]; j++) { jj = ja[j]; if (jj == i) continue; jc = matching[jj]; if (jc == UNMATCHED) { a_ij = a[j]; wgt_j = wgt[jj]; deg_in_j = deg_intra[jj]; dout_j = dout[jj]; } else if (deg_inter[jc] < 0) { continue; } else { a_ij = deg_inter[jc]; wgt_j = wgt_new[jc]; deg_inter[jc] = -1; /* so that we do not redo the calulation when we hit another neighbor in cluster jc */ deg_in_j = deg_intra_new[jc]; dout_j = dout_new[jc]; } mq_in_new = mq_in - deg_in_i/pow(wgt_i, 2) - deg_in_j/pow(wgt_j,2) + (deg_in_i + deg_in_j + a_ij)/pow(wgt_i + wgt_j,2); mq_out_new = mq_out - dout_i/wgt_i - dout_j/wgt_j + (dout_i + dout_j)/(wgt_i + wgt_j); if (nclusters > 2) { mq_new = 2*(mq_in_new/(nclusters - 1) - mq_out_new/((nclusters - 1)*(nclusters - 2))); } else { mq_new = 2*mq_in_new/(nclusters - 1); } #ifdef DEBUG { int ncluster; double mq2, mq_in2, mq_out2, *dout2; int *matching2, nc2 = nc; matching2 = MALLOC(sizeof(int)*A->m); matching2 = MEMCPY(matching2, matching, sizeof(real)*A->m); if (jc != UNMATCHED) { matching2[i] = jc; } else { matching2[i] = nc2; matching2[jj] = nc2; nc2++; } for (k = 0; k < n; k++) if (matching2[k] == UNMATCHED) matching2[k] =nc2++; mq2 = get_mq(A, matching2, &ncluster, &mq_in2, &mq_out2, &dout2); fprintf(stderr," {dout_i, dout_j}={%f,%f}, {predicted, calculated}: mq = {%f, %f}, mq_in ={%f,%f}, mq_out = {%f,%f}\n",dout_i, dout_j, mq_new, mq2, mq_in_new, mq_in2, mq_out_new, mq_out2); mq_new = mq2; } #endif gain = mq_new - mq; if (Verbose) fprintf(stderr,"gain in merging node %d with node %d = %f-%f = %f\n", i, jj, mq, mq_new, gain); if (j == ia[i] || gain > maxgain) { maxgain = gain; jmax = jj; amax = a_ij; dout_max = dout_j; wgt_jmax = wgt_j; mq_max = mq_new; mq_in_max = mq_in_new; mq_out_max = mq_out_new; } } /* now merge i and jmax */ if (maxgain > 0 || (nc >= 1 && nc > maxcluster)) { total_gain += maxgain; jc = matching[jmax]; if (jc == UNMATCHED) { fprintf(stderr, "maxgain=%f, merge %d, %d\n",maxgain, i, jmax); neighbors[nc] = SingleLinkedList_new_int(jmax); neighbors[nc] = SingleLinkedList_prepend_int(neighbors[nc], i); dout_new[nc] = dout_i + dout_max; matching[i] = matching[jmax] = nc; wgt_new[nc] = wgt[i] + wgt[jmax]; deg_intra_new[nc] = deg_intra[i] + deg_intra[jmax] + amax; nc++; } else { fprintf(stderr,"maxgain=%f, merge with existing cluster %d, %d\n",maxgain, i, jc); neighbors[jc] = SingleLinkedList_prepend_int(neighbors[jc], i); dout_new[jc] = dout_i + dout_max; wgt_new[jc] += wgt[i]; matching[i] = jc; deg_intra_new[jc] += deg_intra[i] + amax; } mq = mq_max; mq_in = mq_in_max; mq_out = mq_out_max; nclusters--; } else { fprintf(stderr,"gain: %f -- no gain, skip merging node %d\n", maxgain, i); assert(maxgain <= 0); neighbors[nc] = SingleLinkedList_new_int(i); matching[i] = nc; deg_intra_new[nc] = deg_intra[i]; wgt_new[nc] = wgt[i]; nc++; } /* update scaled outdegree of neighbors of i and its merged node/cluster jmax */ jc = matching[i]; lst = neighbors[jc]; do { mask[*((int*) SingleLinkedList_get_data(lst))] = n+i; lst = SingleLinkedList_get_next(lst); } while (lst); lst = neighbors[jc]; do { k = *((int*) SingleLinkedList_get_data(lst)); for (j = ia[k]; j < ia[k+1]; j++) { jj = ja[j]; if (mask[jj] == n+i) continue;/* link to within cluster */ if ((jc = matching[jj]) == UNMATCHED) { if (k == i) { dout[jj] += -a[j]/wgt_i + a[j]/(wgt_i + wgt_jmax); } else { dout[jj] += -a[j]/wgt_jmax + a[j]/(wgt_i + wgt_jmax); } } else { if (k == i) { dout_new[jc] += -a[j]/wgt_i + a[j]/(wgt_i + wgt_jmax); } else { dout_new[jc] += -a[j]/wgt_jmax + a[j]/(wgt_i + wgt_jmax); } } } lst = SingleLinkedList_get_next(lst); } while (lst); } fprintf(stderr,"verbose=%d\n",Verbose); if (Verbose) fprintf(stderr,"mq = %f new mq = %f level = %d, n = %d, nc = %d, gain = %g, mq_in = %f, mq_out = %f\n", mq, mq + total_gain, level, n, nc, total_gain, mq_in, mq_out); #ifdef DEBUG { int ncluster; mq = get_mq(A, matching, &ncluster, &mq_in, &mq_out, &dout); fprintf(stderr," mq = %f\n",mq); } #endif if (nc >= 1 && (total_gain > 0 || nc < n)) { /* now set up restriction and prolongation operator */ SparseMatrix P, R, R0, B, cA; real one = 1.; Multilevel_MQ_Clustering cgrid; R0 = SparseMatrix_new(nc, n, 1, MATRIX_TYPE_REAL, FORMAT_COORD); for (i = 0; i < n; i++) { jj = matching[i]; SparseMatrix_coordinate_form_add_entries(R0, 1, &jj, &i, &one); } R = SparseMatrix_from_coordinate_format(R0); SparseMatrix_delete(R0); P = SparseMatrix_transpose(R); B = SparseMatrix_multiply(R, A); if (!B) goto RETURN; cA = SparseMatrix_multiply(B, P); if (!cA) goto RETURN; SparseMatrix_delete(B); grid->P = P; grid->R = R; level++; cgrid = Multilevel_MQ_Clustering_init(cA, level); deg_intra_new = REALLOC(deg_intra_new, nc*sizeof(real)); wgt_new = REALLOC(wgt_new, nc*sizeof(real)); cgrid->deg_intra = deg_intra_new; cgrid->mq = grid->mq + total_gain; cgrid->wgt = wgt_new; dout_new = REALLOC(dout_new, nc*sizeof(real)); cgrid->dout = dout_new; cgrid = Multilevel_MQ_Clustering_establish(cgrid, maxcluster); grid->next = cgrid; cgrid->prev = grid; } else { /* no more improvement, stop and final clustering found */ for (i = 0; i < n; i++) matching[i] = i; FREE(deg_intra_new); FREE(wgt_new); FREE(dout_new); } RETURN: for (i = 0; i < n; i++) SingleLinkedList_delete(neighbors[i], free); FREE(neighbors); FREE(deg_inter); FREE(mask); return grid; }