1 /*!
2 \file
3 \brief Functions for the edge-based FM refinement
4 
5 \date Started 7/23/97
6 \author George
7 \author Copyright 1997-2011, Regents of the University of Minnesota
8 \version\verbatim $Id: fm.c 10187 2011-06-13 13:46:57Z karypis $ \endverbatim
9 */
10 
11 #include "metislib.h"
12 
13 
14 /*************************************************************************
15 * This function performs an edge-based FM refinement
16 **************************************************************************/
FM_2WayRefine(ctrl_t * ctrl,graph_t * graph,real_t * ntpwgts,idx_t niter)17 void FM_2WayRefine(ctrl_t *ctrl, graph_t *graph, real_t *ntpwgts, idx_t niter)
18 {
19   if (graph->ncon == 1)
20     FM_2WayCutRefine(ctrl, graph, ntpwgts, niter);
21   else
22     FM_Mc2WayCutRefine(ctrl, graph, ntpwgts, niter);
23 }
24 
25 
26 /*************************************************************************/
27 /*! This function performs a cut-focused FM refinement */
28 /*************************************************************************/
FM_2WayCutRefine(ctrl_t * ctrl,graph_t * graph,real_t * ntpwgts,idx_t niter)29 void FM_2WayCutRefine(ctrl_t *ctrl, graph_t *graph, real_t *ntpwgts, idx_t niter)
30 {
31   idx_t i, ii, j, k, kwgt, nvtxs, nbnd, nswaps, from, to, pass, me, limit, tmp;
32   idx_t *xadj, *vwgt, *adjncy, *adjwgt, *where, *id, *ed, *bndptr, *bndind, *pwgts;
33   idx_t *moved, *swaps, *perm;
34   rpq_t *queues[2];
35   idx_t higain, mincut, mindiff, origdiff, initcut, newcut, mincutorder, avgvwgt;
36   idx_t tpwgts[2];
37 
38   WCOREPUSH;
39 
40   nvtxs  = graph->nvtxs;
41   xadj   = graph->xadj;
42   vwgt   = graph->vwgt;
43   adjncy = graph->adjncy;
44   adjwgt = graph->adjwgt;
45   where  = graph->where;
46   id     = graph->id;
47   ed     = graph->ed;
48   pwgts  = graph->pwgts;
49   bndptr = graph->bndptr;
50   bndind = graph->bndind;
51 
52   moved = iwspacemalloc(ctrl, nvtxs);
53   swaps = iwspacemalloc(ctrl, nvtxs);
54   perm  = iwspacemalloc(ctrl, nvtxs);
55 
56   tpwgts[0] = graph->tvwgt[0]*ntpwgts[0];
57   tpwgts[1] = graph->tvwgt[0]-tpwgts[0];
58 
59   limit   = gk_min(gk_max(0.01*nvtxs, 15), 100);
60   avgvwgt = gk_min((pwgts[0]+pwgts[1])/20, 2*(pwgts[0]+pwgts[1])/nvtxs);
61 
62   queues[0] = rpqCreate(nvtxs);
63   queues[1] = rpqCreate(nvtxs);
64 
65   IFSET(ctrl->dbglvl, METIS_DBG_REFINE,
66       Print2WayRefineStats(ctrl, graph, ntpwgts, 0, -2));
67 
68   origdiff = iabs(tpwgts[0]-pwgts[0]);
69   iset(nvtxs, -1, moved);
70   for (pass=0; pass<niter; pass++) { /* Do a number of passes */
71     rpqReset(queues[0]);
72     rpqReset(queues[1]);
73 
74     mincutorder = -1;
75     newcut = mincut = initcut = graph->mincut;
76     mindiff = iabs(tpwgts[0]-pwgts[0]);
77 
78     ASSERT(ComputeCut(graph, where) == graph->mincut);
79     ASSERT(CheckBnd(graph));
80 
81     /* Insert boundary nodes in the priority queues */
82     nbnd = graph->nbnd;
83     irandArrayPermute(nbnd, perm, nbnd, 1);
84     for (ii=0; ii<nbnd; ii++) {
85       i = perm[ii];
86       ASSERT(ed[bndind[i]] > 0 || id[bndind[i]] == 0);
87       ASSERT(bndptr[bndind[i]] != -1);
88       rpqInsert(queues[where[bndind[i]]], bndind[i], ed[bndind[i]]-id[bndind[i]]);
89     }
90 
91     for (nswaps=0; nswaps<nvtxs; nswaps++) {
92       from = (tpwgts[0]-pwgts[0] < tpwgts[1]-pwgts[1] ? 0 : 1);
93       to = (from+1)%2;
94 
95       if ((higain = rpqGetTop(queues[from])) == -1)
96         break;
97       ASSERT(bndptr[higain] != -1);
98 
99       newcut -= (ed[higain]-id[higain]);
100       INC_DEC(pwgts[to], pwgts[from], vwgt[higain]);
101 
102       if ((newcut < mincut && iabs(tpwgts[0]-pwgts[0]) <= origdiff+avgvwgt) ||
103           (newcut == mincut && iabs(tpwgts[0]-pwgts[0]) < mindiff)) {
104         mincut  = newcut;
105         mindiff = iabs(tpwgts[0]-pwgts[0]);
106         mincutorder = nswaps;
107       }
108       else if (nswaps-mincutorder > limit) { /* We hit the limit, undo last move */
109         newcut += (ed[higain]-id[higain]);
110         INC_DEC(pwgts[from], pwgts[to], vwgt[higain]);
111         break;
112       }
113 
114       where[higain] = to;
115       moved[higain] = nswaps;
116       swaps[nswaps] = higain;
117 
118       IFSET(ctrl->dbglvl, METIS_DBG_MOVEINFO,
119         printf("Moved %6"PRIDX" from %"PRIDX". [%3"PRIDX" %3"PRIDX"] %5"PRIDX" [%4"PRIDX" %4"PRIDX"]\n", higain, from, ed[higain]-id[higain], vwgt[higain], newcut, pwgts[0], pwgts[1]));
120 
121       /**************************************************************
122       * Update the id[i]/ed[i] values of the affected nodes
123       ***************************************************************/
124       SWAP(id[higain], ed[higain], tmp);
125       if (ed[higain] == 0 && xadj[higain] < xadj[higain+1])
126         BNDDelete(nbnd, bndind,  bndptr, higain);
127 
128       for (j=xadj[higain]; j<xadj[higain+1]; j++) {
129         k = adjncy[j];
130 
131         kwgt = (to == where[k] ? adjwgt[j] : -adjwgt[j]);
132         INC_DEC(id[k], ed[k], kwgt);
133 
134         /* Update its boundary information and queue position */
135         if (bndptr[k] != -1) { /* If k was a boundary vertex */
136           if (ed[k] == 0) { /* Not a boundary vertex any more */
137             BNDDelete(nbnd, bndind, bndptr, k);
138             if (moved[k] == -1)  /* Remove it if in the queues */
139               rpqDelete(queues[where[k]], k);
140           }
141           else { /* If it has not been moved, update its position in the queue */
142             if (moved[k] == -1)
143               rpqUpdate(queues[where[k]], k, ed[k]-id[k]);
144           }
145         }
146         else {
147           if (ed[k] > 0) {  /* It will now become a boundary vertex */
148             BNDInsert(nbnd, bndind, bndptr, k);
149             if (moved[k] == -1)
150               rpqInsert(queues[where[k]], k, ed[k]-id[k]);
151           }
152         }
153       }
154 
155     }
156 
157 
158     /****************************************************************
159     * Roll back computations
160     *****************************************************************/
161     for (i=0; i<nswaps; i++)
162       moved[swaps[i]] = -1;  /* reset moved array */
163     for (nswaps--; nswaps>mincutorder; nswaps--) {
164       higain = swaps[nswaps];
165 
166       to = where[higain] = (where[higain]+1)%2;
167       SWAP(id[higain], ed[higain], tmp);
168       if (ed[higain] == 0 && bndptr[higain] != -1 && xadj[higain] < xadj[higain+1])
169         BNDDelete(nbnd, bndind,  bndptr, higain);
170       else if (ed[higain] > 0 && bndptr[higain] == -1)
171         BNDInsert(nbnd, bndind,  bndptr, higain);
172 
173       INC_DEC(pwgts[to], pwgts[(to+1)%2], vwgt[higain]);
174       for (j=xadj[higain]; j<xadj[higain+1]; j++) {
175         k = adjncy[j];
176 
177         kwgt = (to == where[k] ? adjwgt[j] : -adjwgt[j]);
178         INC_DEC(id[k], ed[k], kwgt);
179 
180         if (bndptr[k] != -1 && ed[k] == 0)
181           BNDDelete(nbnd, bndind, bndptr, k);
182         if (bndptr[k] == -1 && ed[k] > 0)
183           BNDInsert(nbnd, bndind, bndptr, k);
184       }
185     }
186 
187     graph->mincut = mincut;
188     graph->nbnd   = nbnd;
189 
190     IFSET(ctrl->dbglvl, METIS_DBG_REFINE,
191         Print2WayRefineStats(ctrl, graph, ntpwgts, 0, mincutorder));
192 
193     if (mincutorder <= 0 || mincut == initcut)
194       break;
195   }
196 
197   rpqDestroy(queues[0]);
198   rpqDestroy(queues[1]);
199 
200   WCOREPOP;
201 }
202 
203 
204 /*************************************************************************/
205 /*! This function performs a cut-focused multi-constraint FM refinement */
206 /*************************************************************************/
FM_Mc2WayCutRefine(ctrl_t * ctrl,graph_t * graph,real_t * ntpwgts,idx_t niter)207 void FM_Mc2WayCutRefine(ctrl_t *ctrl, graph_t *graph, real_t *ntpwgts, idx_t niter)
208 {
209   idx_t i, ii, j, k, l, kwgt, nvtxs, ncon, nbnd, nswaps, from, to, pass,
210         me, limit, tmp, cnum;
211   idx_t *xadj, *adjncy, *vwgt, *adjwgt, *pwgts, *where, *id, *ed,
212         *bndptr, *bndind;
213   idx_t *moved, *swaps, *perm, *qnum;
214   idx_t higain, mincut, initcut, newcut, mincutorder;
215   real_t *invtvwgt, *ubfactors, *minbalv, *newbalv;
216   real_t origbal, minbal, newbal, rgain, ffactor;
217   rpq_t **queues;
218 
219   WCOREPUSH;
220 
221   nvtxs    = graph->nvtxs;
222   ncon     = graph->ncon;
223   xadj     = graph->xadj;
224   vwgt     = graph->vwgt;
225   adjncy   = graph->adjncy;
226   adjwgt   = graph->adjwgt;
227   invtvwgt = graph->invtvwgt;
228   where    = graph->where;
229   id       = graph->id;
230   ed       = graph->ed;
231   pwgts    = graph->pwgts;
232   bndptr   = graph->bndptr;
233   bndind   = graph->bndind;
234 
235   moved     = iwspacemalloc(ctrl, nvtxs);
236   swaps     = iwspacemalloc(ctrl, nvtxs);
237   perm      = iwspacemalloc(ctrl, nvtxs);
238   qnum      = iwspacemalloc(ctrl, nvtxs);
239   ubfactors = rwspacemalloc(ctrl, ncon);
240   newbalv   = rwspacemalloc(ctrl, ncon);
241   minbalv   = rwspacemalloc(ctrl, ncon);
242 
243   limit = gk_min(gk_max(0.01*nvtxs, 25), 150);
244 
245 
246   /* Determine a fudge factor to allow the refinement routines to get out
247      of tight balancing constraints. */
248   ffactor = .5/gk_max(20, nvtxs);
249 
250   /* Initialize the queues */
251   queues = (rpq_t **)wspacemalloc(ctrl, 2*ncon*sizeof(rpq_t *));
252   for (i=0; i<2*ncon; i++)
253     queues[i] = rpqCreate(nvtxs);
254   for (i=0; i<nvtxs; i++)
255     qnum[i] = iargmax_nrm(ncon, vwgt+i*ncon, invtvwgt);
256 
257   /* Determine the unbalance tolerance for each constraint. The tolerance is
258      equal to the maximum of the original load imbalance and the user-supplied
259      allowed tolerance. The rationale behind this approach is to allow the
260      refinement routine to improve the cut, without having to worry about fixing
261      load imbalance problems. The load imbalance is addressed by the balancing
262      routines. */
263   origbal = ComputeLoadImbalanceDiffVec(graph, 2, ctrl->pijbm, ctrl->ubfactors, ubfactors);
264   for (i=0; i<ncon; i++)
265     ubfactors[i] = (ubfactors[i] > 0 ? ctrl->ubfactors[i]+ubfactors[i] : ctrl->ubfactors[i]);
266 
267 
268   IFSET(ctrl->dbglvl, METIS_DBG_REFINE,
269       Print2WayRefineStats(ctrl, graph, ntpwgts, origbal, -2));
270 
271   iset(nvtxs, -1, moved);
272   for (pass=0; pass<niter; pass++) { /* Do a number of passes */
273     for (i=0; i<2*ncon; i++)
274       rpqReset(queues[i]);
275 
276     mincutorder = -1;
277     newcut = mincut = initcut = graph->mincut;
278 
279     minbal = ComputeLoadImbalanceDiffVec(graph, 2, ctrl->pijbm, ubfactors, minbalv);
280 
281     ASSERT(ComputeCut(graph, where) == graph->mincut);
282     ASSERT(CheckBnd(graph));
283 
284     /* Insert boundary nodes in the priority queues */
285     nbnd = graph->nbnd;
286     irandArrayPermute(nbnd, perm, nbnd/5, 1);
287     for (ii=0; ii<nbnd; ii++) {
288       i = bndind[perm[ii]];
289       ASSERT(ed[i] > 0 || id[i] == 0);
290       ASSERT(bndptr[i] != -1);
291       //rgain = 1.0*(ed[i]-id[i])/sqrt(vwgt[i*ncon+qnum[i]]+1);
292       //rgain = (ed[i]-id[i] > 0 ? 1.0*(ed[i]-id[i])/sqrt(vwgt[i*ncon+qnum[i]]+1) : ed[i]-id[i]);
293       rgain = ed[i]-id[i];
294       rpqInsert(queues[2*qnum[i]+where[i]], i, rgain);
295     }
296 
297     for (nswaps=0; nswaps<nvtxs; nswaps++) {
298       SelectQueue(graph, ctrl->pijbm, ubfactors, queues, &from, &cnum);
299 
300       to = (from+1)%2;
301 
302       if (from == -1 || (higain = rpqGetTop(queues[2*cnum+from])) == -1)
303         break;
304       ASSERT(bndptr[higain] != -1);
305 
306       newcut -= (ed[higain]-id[higain]);
307 
308       iaxpy(ncon,  1, vwgt+higain*ncon, 1, pwgts+to*ncon,   1);
309       iaxpy(ncon, -1, vwgt+higain*ncon, 1, pwgts+from*ncon, 1);
310       newbal = ComputeLoadImbalanceDiffVec(graph, 2, ctrl->pijbm, ubfactors, newbalv);
311 
312       if ((newcut < mincut && newbal <= ffactor) ||
313           (newcut == mincut && (newbal < minbal ||
314            (newbal == minbal && BetterBalance2Way(ncon, minbalv, newbalv))))) {
315         mincut      = newcut;
316         minbal      = newbal;
317         mincutorder = nswaps;
318         rcopy(ncon, newbalv, minbalv);
319       }
320       else if (nswaps-mincutorder > limit) { /* We hit the limit, undo last move */
321         newcut += (ed[higain]-id[higain]);
322         iaxpy(ncon,  1, vwgt+higain*ncon, 1, pwgts+from*ncon, 1);
323         iaxpy(ncon, -1, vwgt+higain*ncon, 1, pwgts+to*ncon,   1);
324         break;
325       }
326 
327       where[higain] = to;
328       moved[higain] = nswaps;
329       swaps[nswaps] = higain;
330 
331       if (ctrl->dbglvl&METIS_DBG_MOVEINFO) {
332         printf("Moved%6"PRIDX" from %"PRIDX"(%"PRIDX") Gain:%5"PRIDX", "
333             "Cut:%5"PRIDX", NPwgts:", higain, from, cnum, ed[higain]-id[higain], newcut);
334         for (l=0; l<ncon; l++)
335           printf("(%.3"PRREAL" %.3"PRREAL")", pwgts[l]*invtvwgt[l], pwgts[ncon+l]*invtvwgt[l]);
336         printf(" %+.3"PRREAL" LB: %.3"PRREAL"(%+.3"PRREAL")\n",
337             minbal, ComputeLoadImbalance(graph, 2, ctrl->pijbm), newbal);
338       }
339 
340 
341       /**************************************************************
342       * Update the id[i]/ed[i] values of the affected nodes
343       ***************************************************************/
344       SWAP(id[higain], ed[higain], tmp);
345       if (ed[higain] == 0 && xadj[higain] < xadj[higain+1])
346         BNDDelete(nbnd, bndind,  bndptr, higain);
347 
348       for (j=xadj[higain]; j<xadj[higain+1]; j++) {
349         k = adjncy[j];
350 
351         kwgt = (to == where[k] ? adjwgt[j] : -adjwgt[j]);
352         INC_DEC(id[k], ed[k], kwgt);
353 
354         /* Update its boundary information and queue position */
355         if (bndptr[k] != -1) { /* If k was a boundary vertex */
356           if (ed[k] == 0) { /* Not a boundary vertex any more */
357             BNDDelete(nbnd, bndind, bndptr, k);
358             if (moved[k] == -1)  /* Remove it if in the queues */
359               rpqDelete(queues[2*qnum[k]+where[k]], k);
360           }
361           else { /* If it has not been moved, update its position in the queue */
362             if (moved[k] == -1) {
363               //rgain = 1.0*(ed[k]-id[k])/sqrt(vwgt[k*ncon+qnum[k]]+1);
364               //rgain = (ed[k]-id[k] > 0 ?
365               //              1.0*(ed[k]-id[k])/sqrt(vwgt[k*ncon+qnum[k]]+1) : ed[k]-id[k]);
366               rgain = ed[k]-id[k];
367               rpqUpdate(queues[2*qnum[k]+where[k]], k, rgain);
368             }
369           }
370         }
371         else {
372           if (ed[k] > 0) {  /* It will now become a boundary vertex */
373             BNDInsert(nbnd, bndind, bndptr, k);
374             if (moved[k] == -1) {
375               //rgain = 1.0*(ed[k]-id[k])/sqrt(vwgt[k*ncon+qnum[k]]+1);
376               //rgain = (ed[k]-id[k] > 0 ?
377               //              1.0*(ed[k]-id[k])/sqrt(vwgt[k*ncon+qnum[k]]+1) : ed[k]-id[k]);
378               rgain = ed[k]-id[k];
379               rpqInsert(queues[2*qnum[k]+where[k]], k, rgain);
380             }
381           }
382         }
383       }
384 
385     }
386 
387 
388     /****************************************************************
389     * Roll back computations
390     *****************************************************************/
391     for (i=0; i<nswaps; i++)
392       moved[swaps[i]] = -1;  /* reset moved array */
393     for (nswaps--; nswaps>mincutorder; nswaps--) {
394       higain = swaps[nswaps];
395 
396       to = where[higain] = (where[higain]+1)%2;
397       SWAP(id[higain], ed[higain], tmp);
398       if (ed[higain] == 0 && bndptr[higain] != -1 && xadj[higain] < xadj[higain+1])
399         BNDDelete(nbnd, bndind,  bndptr, higain);
400       else if (ed[higain] > 0 && bndptr[higain] == -1)
401         BNDInsert(nbnd, bndind,  bndptr, higain);
402 
403       iaxpy(ncon,  1, vwgt+higain*ncon, 1, pwgts+to*ncon,         1);
404       iaxpy(ncon, -1, vwgt+higain*ncon, 1, pwgts+((to+1)%2)*ncon, 1);
405       for (j=xadj[higain]; j<xadj[higain+1]; j++) {
406         k = adjncy[j];
407 
408         kwgt = (to == where[k] ? adjwgt[j] : -adjwgt[j]);
409         INC_DEC(id[k], ed[k], kwgt);
410 
411         if (bndptr[k] != -1 && ed[k] == 0)
412           BNDDelete(nbnd, bndind, bndptr, k);
413         if (bndptr[k] == -1 && ed[k] > 0)
414           BNDInsert(nbnd, bndind, bndptr, k);
415       }
416     }
417 
418     graph->mincut = mincut;
419     graph->nbnd   = nbnd;
420 
421     IFSET(ctrl->dbglvl, METIS_DBG_REFINE,
422         Print2WayRefineStats(ctrl, graph, ntpwgts, minbal, mincutorder));
423 
424     if (mincutorder <= 0 || mincut == initcut)
425       break;
426   }
427 
428   for (i=0; i<2*ncon; i++)
429     rpqDestroy(queues[i]);
430 
431   WCOREPOP;
432 }
433 
434 
435 /*************************************************************************/
436 /*! This function selects the partition number and the queue from which
437     we will move vertices out. */
438 /*************************************************************************/
SelectQueue(graph_t * graph,real_t * pijbm,real_t * ubfactors,rpq_t ** queues,idx_t * from,idx_t * cnum)439 void SelectQueue(graph_t *graph, real_t *pijbm, real_t *ubfactors,
440          rpq_t **queues, idx_t *from, idx_t *cnum)
441 {
442   idx_t ncon, i, part;
443   real_t max, tmp;
444 
445   ncon = graph->ncon;
446 
447   *from = -1;
448   *cnum = -1;
449 
450   /* First determine the side and the queue, irrespective of the presence of nodes.
451      The side & queue is determined based on the most violated balancing constraint. */
452   for (max=0.0, part=0; part<2; part++) {
453     for (i=0; i<ncon; i++) {
454       tmp = graph->pwgts[part*ncon+i]*pijbm[part*ncon+i] - ubfactors[i];
455       /* the '=' in the test bellow is to ensure that under tight constraints
456          the partition that is at the max is selected */
457       if (tmp >= max) {
458         max   = tmp;
459         *from = part;
460         *cnum = i;
461       }
462     }
463   }
464 
465 
466   if (*from != -1) {
467     /* in case the desired queue is empty, select a queue from the same side */
468     if (rpqLength(queues[2*(*cnum)+(*from)]) == 0) {
469       for (i=0; i<ncon; i++) {
470         if (rpqLength(queues[2*i+(*from)]) > 0) {
471           max   = graph->pwgts[(*from)*ncon+i]*pijbm[(*from)*ncon+i] - ubfactors[i];
472           *cnum = i;
473           break;
474         }
475       }
476 
477       for (i++; i<ncon; i++) {
478         tmp = graph->pwgts[(*from)*ncon+i]*pijbm[(*from)*ncon+i] - ubfactors[i];
479         if (tmp > max && rpqLength(queues[2*i+(*from)]) > 0) {
480           max   = tmp;
481           *cnum = i;
482         }
483       }
484     }
485 
486     /*
487     printf("Selected1 %"PRIDX"(%"PRIDX") -> %"PRIDX" [%5"PRREAL"]\n",
488         *from, *cnum, rpqLength(queues[2*(*cnum)+(*from)]), max);
489     */
490   }
491   else {
492     /* the partitioning does not violate balancing constraints, in which case select
493        a queue based on cut criteria */
494     for (part=0; part<2; part++) {
495       for (i=0; i<ncon; i++) {
496         if (rpqLength(queues[2*i+part]) > 0 &&
497             (*from == -1 || rpqSeeTopKey(queues[2*i+part]) > max)) {
498           max   = rpqSeeTopKey(queues[2*i+part]);
499           *from = part;
500           *cnum = i;
501         }
502       }
503     }
504     /*
505     printf("Selected2 %"PRIDX"(%"PRIDX") -> %"PRIDX"\n",
506         *from, *cnum, rpqLength(queues[2*(*cnum)+(*from)]), max);
507     */
508   }
509 }
510 
511 
512 /*************************************************************************/
513 /*! Prints statistics about the refinement */
514 /*************************************************************************/
Print2WayRefineStats(ctrl_t * ctrl,graph_t * graph,real_t * ntpwgts,real_t deltabal,idx_t mincutorder)515 void Print2WayRefineStats(ctrl_t *ctrl, graph_t *graph, real_t *ntpwgts,
516          real_t deltabal, idx_t mincutorder)
517 {
518   int i;
519 
520   if (mincutorder == -2) {
521     printf("Parts: ");
522     printf("Nv-Nb[%5"PRIDX" %5"PRIDX"] ICut: %6"PRIDX,
523         graph->nvtxs, graph->nbnd, graph->mincut);
524     printf(" [");
525     for (i=0; i<graph->ncon; i++)
526       printf("(%.3"PRREAL" %.3"PRREAL" T:%.3"PRREAL" %.3"PRREAL")",
527           graph->pwgts[i]*graph->invtvwgt[i],
528           graph->pwgts[graph->ncon+i]*graph->invtvwgt[i],
529           ntpwgts[i], ntpwgts[graph->ncon+i]);
530     printf("] LB: %.3"PRREAL"(%+.3"PRREAL")\n",
531         ComputeLoadImbalance(graph, 2, ctrl->pijbm), deltabal);
532   }
533   else {
534     printf("\tMincut: %6"PRIDX" at %5"PRIDX" NBND %6"PRIDX" NPwgts: [",
535         graph->mincut, mincutorder, graph->nbnd);
536     for (i=0; i<graph->ncon; i++)
537       printf("(%.3"PRREAL" %.3"PRREAL")",
538           graph->pwgts[i]*graph->invtvwgt[i], graph->pwgts[graph->ncon+i]*graph->invtvwgt[i]);
539     printf("] LB: %.3"PRREAL"(%+.3"PRREAL")\n",
540         ComputeLoadImbalance(graph, 2, ctrl->pijbm), deltabal);
541   }
542 }
543 
544