# # Copyright 2007 Petra Schilhard. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # #****f* graph_partitioning.tcl/writePartitions # NAME # writePartitions -- write partitions # SYNOPSIS # writePartitions node_weight # FUNCTION # Procedure that writes for each node its partition. # INPUTS # * node_weight -- array of node weights #**** proc writePartitions {node_weight} { global nparts; global node_list; global link_list; global split_list; global finalpartition; upvar $node_weight nweight; #counts how many nodes are in each partition array set nr_nodes_partition {}; #sum up the weight of each partition array set weight_partition {}; for {set i 0} {$i<$nparts} {incr i} { set nr_nodes_partition($i) 0; set weight_partition($i) 0; } set i 0; foreach node $node_list { #write to node its partition setPartition $node $finalpartition($i); incr nr_nodes_partition($finalpartition($i)) 1; incr weight_partition($finalpartition($i)) $nweight($i); incr i; } #disconnect for algorithm connected nodes foreach split $split_list { set node1 [lindex $split 0]; set node2 [lindex $split 1]; set linkToSplit [linkByPeers $node1 $node2]; splitGUILink $linkToSplit ; } set outstr ""; for {set i 0} {$i < $nparts} {incr i} { set outstr "p$i: $nr_nodes_partition($i) vertices with weight $weight_partition($i)"; #puts [format %s $outstr]; } redrawAll; updateUndoLog; tk_dialog .dialog1 "Graph partitioning output" "Done.\n" info 0 Dismiss; } #****f* graph_partitioning.tcl/setPartition # NAME # setPartition -- set partition # SYNOPSIS # setPartition $node $partition # FUNCTION # Procedure searches the node for the information about # its partition, if found it replace the info, if not found # it adds the information to the node. # INPUTS # * node -- node id # * partition -- partition of the node #**** proc setPartition { node partition } { global $node; set p [lsearch [set $node] "partition *"]; if { $p >= 0 } { set $node [lreplace [set $node] $p $p "partition p$partition"]; } else { set $node [linsert [set $node] end "partition p$partition"]; } } #****f* graph_partitioning.tcl/getNodePartition # NAME # getNodePartition -- get node partition # SYNOPSIS # getNodePartition $node # FUNCTION # Function searches for node's partition, and returns it # (or empty string if not found) # INPUTS # * node -- node id # RESULT # * part -- the node's partition #**** proc getNodePartition { node } { global $node; set part [lindex [lsearch -inline [set $node] "partition *"] 1]; return $part; } #****f* graph_partitioning.tcl/debug # NAME # debug # SYNOPSIS # debug $message # FUNCTION # Prints the message to the stderr if enabled. # INPUTS # * message -- a messege to be printed #**** proc debug { message } { global debug; if ![info exists debug(enabled)] { #do nothing return; } puts stderr $message; } #****f* graph_partitioning.tcl/graphPartition # NAME # graphPartition -- graph partition # SYNOPSIS # graphPartition $partNum # FUNCTION # Procedure which prepares arrays for partitioning # algorithm, and starts the algorithm. # INPUTS # * partNum -- number of partitions #**** proc graphPartition {partNum} { global node_list link_list finalpartition global nparts tpwgts max_nweight array set node_weight {}; array set edge_weight {}; array set node_neighbour {}; array set edge_array {}; array set tpwgts {}; array set node_map {}; puts ""; #puts " Starting graph partitioning..."; #puts "+------------------------------------------------+"; #puts ""; set start [clock clicks -milliseconds]; #initialise the arrays for the algorithm set nparts $partNum; initNodes node_weight; set nvertices [array size node_weight]; initNeighbours node_neighbour edge_array edge_weight; if {$nparts > $nvertices} then { debug "Number of vertices should be greater then number of partitions."; displayErrorMessage "Number of vertices should be greater then number of partitions."; return; } elseif {$nparts < 2} { debug "Number of partition should be greater then 1."; displayErrorMessage "Number of partition should be greater then 1."; return; } #calculate tpwgts array for {set i 0} {$i < $nparts} {incr i} { set tpwgts($i) [expr {1.0 / (1.0 * $nparts)}]; } set t [time { recursiveBisection $nvertices node_weight node_neighbour edge_array edge_weight tpwgts $nparts 0 node_map; } 1]; set microsec [lindex $t 0]; puts "total time: [expr {$microsec * 0.000001}] sec"; #compute cut set cut 0; for {set i 0} {$i < $nvertices} {incr i} { set id($i) 0; set ed($i) 0; set curr_partition $finalpartition($i); # calculates the sum of the edge weights of the adjacent vertices of i if {[info exists node_neighbour($i)]} then { foreach ngb $node_neighbour($i) { if {$curr_partition == $finalpartition($ngb)} then { # vertice in the same partition incr id($i) 1; #$edge_weight([getEdgeBetween $i $ngb edge_array]); } else { # vertice in a different partition incr ed($i) 1; #$edge_weight([getEdgeBetween $i $ngb edge_array]); };#if-else };#foreach if {$ed($i) > 0 || [llength $node_neighbour($i)] == 0} then { #vanjski node, ili nema susjeda incr cut $ed($i); } } } set cut [expr {$cut / 2}]; puts "end cut: $cut"; #save the partitions writePartitions node_weight; set end [clock clicks -milliseconds]; puts [format "total elapsed time: %.6f s" [expr {($end - $start) * 0.001}]]; puts ""; puts " Done graph partitioning."; puts "+------------------------------------------------+"; puts ""; } #****f* graph_partitioning.tcl/initNodes # NAME # initNodes # SYNOPSIS # initNodes node_weight # FUNCTION # Initialise the node_weight array. # INPUTS # * node_weight -- empty array of node weights #**** proc initNodes {node_weight} { global node_list; upvar $node_weight nweight; set i 0; foreach node $node_list { #if the node is pseudo, remove it if {[nodeType $node] == "pseudo"} then { mergePseudoLink $node } else { #seve node's weight into array set nweight($i) [getNodeWeight $node]; incr i; } } } #****f* graph_partitioning.tcl/mergePseudoLink # NAME # mergePseudoLink -- merge pseudo link # SYNOPSIS # mergePseudoLink $pnode # FUNCTION # Removes pseudo connections. # INPUTS # * pnode -- pseudo node id #**** proc mergePseudoLink { pnode } { global node_list; global split_list; foreach n $node_list { #get the links connecting the both pseudo node's set l1 [linkByPeers $pnode $n]; if {$l1 != ""} then { set l2 [getLinkMirror $l1]; #set peers1 [linkPeers $l1]; set peers2 [linkPeers $l2]; #get it's not-pseudo peers #set n1 [lindex $peers1 0]; set n2 [lindex $peers2 0]; mergeLink $l1; if {[lsearch $split_list "$n $n2"] < 0 && [lsearch $split_list "$n2 $n"] < 0} then { lappend split_list "$n $n2"; break; } } } } #****f* graph_partitioning.tcl/initNeighbours # NAME # initNeighbours # SYNOPSIS # initNeighbours node_neighbour edge_array edge_weight # FUNCTION # Initialise node_neighbour, edge_array and edge_weight array. # INPUTS # * node_neighbour -- empty array # * edge_array -- empty array # * edge_weight -- empty array #**** proc initNeighbours {node_neighbour edge_array edge_weight} { global node_list link_list upvar $edge_array earray; upvar $node_neighbour nneighbour; upvar $edge_weight eweight; for {set i 0} {$i < [llength $link_list]} {incr i} { #take the edge one after the other set edge [lindex $link_list $i]; #read nodes incident to edge set peers [linkPeers $edge]; set node1 [lindex $peers 0]; set node2 [lindex $peers 1]; #read node's index in the node_list set idx_n1 [lsearch $node_list $node1]; set idx_n2 [lsearch $node_list $node2]; #node1 is adjacent to node2 lappend nneighbour($idx_n1) $idx_n2; lappend nneighbour($idx_n2) $idx_n1; set earray($i) "$idx_n1 $idx_n2"; #calculate the link weight set eweight($i) [expr {[getLinkWeight $edge] / 100000}]; #!!!!!!s } } #****f* graph_partitioning.tcl/recursiveBisection # NAME # recursiveBisection # SYNOPSIS # recursiveBisection $nvertices node_weight node_neighbour # edge_array edge_weight tpart_wgts $new_parts $part_nr up_map # FUNCTION # Recursive starts coarsening, initial partitioning and uncoarsening. # In each recursion it bisect the graph and recalculates arrays for each # part. # INPUTS # * nvertices -- number of vertices # * node_weight -- array of node weights # * node_neighbour -- array of node neighbours # * edge_array -- array of edges # * edge_weight -- array of edge weights # * tpart_wgts -- array of each partition ratio of the graph # * new_parts -- number to divide the graph # * part_nr -- counts how deep the recursion is # * up_map -- #**** proc recursiveBisection {nvertices node_weight node_neighbour edge_array edge_weight tpart_wgts new_parts part_nr up_map} { global part_mincut; global finalpartition; global part_partition; upvar $node_weight nweight; upvar $node_neighbour nneighbour; upvar $edge_array earray; upvar $edge_weight eweight; upvar $tpart_wgts tpwgts; upvar $up_map upmap; array set tpwgts2 {}; set nparts $new_parts; set cut 0; debug "recursiveBisection!!!!"; debug "RB: nparts=$nparts"; #calculate for each partition its wished weight set tvwgt [sum_array $nvertices nweight]; set sum_tpwgt [sum_array [expr {$nparts / 2}] tpwgts]; set tpwgts2(0) [expr {int ( ceil($tvwgt * $sum_tpwgt))}]; set tp2 [expr {ceil($sum_tpwgt * $tvwgt)}]; set tpwgts2(1) [expr {int($tvwgt - $tpwgts2(0))}]; debug "RB: tvwgt=$tvwgt, tpwgts2(0)=$tpwgts2(0), tpwgts2(1)=$tpwgts2(1), sum_tpwgt=$sum_tpwgt"; #start partitioning coarseGraph $nvertices nweight nneighbour earray eweight tpwgts2; #minimal cut set cut $part_mincut; #calculate for each vertex its right partition by adding to "0" and "1" partition a number for {set i 0} {$i < $nvertices} {incr i} { if {[array size upmap] > 0} { set finalpartition($upmap($i)) [expr {$part_partition($i) + $part_nr}]; } else { set finalpartition($i) [expr {$part_partition($i) + $part_nr}]; } } #when partition in more then 2 parts, divide the graph in 2 halfs (subgraph "0" and subgraph "1") if {$nparts > 2} then { array set snode_neighbour {}; array set snode_weight {}; array set sedge_array {}; array set sedge_weight {}; array set snode_map {}; array set snode_map_help {}; #auxiliary variable array set sn_vtxs {}; set sn_vtxs(0) 0; set sn_vtxs(1) 0; array set sn_edges {}; set sn_edges(0) 0; set sn_edges(1) 0; splitGraph $nvertices nneighbour nweight earray eweight snode_neighbour snode_weight sedge_array sedge_weight snode_map sn_vtxs sn_edges snode_map_help; array set snode_neighbour0 {}; array set snode_weight0 {}; array set sedge_array0 {}; array set sedge_weight0 {}; array set snode_map0 {}; array set snode_neighbour1 {}; array set snode_weight1 {}; array set sedge_array1 {}; array set sedge_weight1 {}; array set snode_map1 {}; #save the node characteristics from both subgraphs in two different arrays for {set i 0} {$i < $sn_vtxs(0)} {incr i} { if {[info exists snode_neighbour(0,$i)]} then { set snode_neighbour0($i) $snode_neighbour(0,$i); } if {[array size upmap] > 0} then { set snode_map0($i) $upmap($snode_map_help(0,$i)); } else { set snode_map0($i) $snode_map_help(0,$i); } debug "snode_map0($i)=$snode_map0($i)"; set snode_weight0($i) $snode_weight(0,$i); } for {set i 0} {$i < $sn_vtxs(1)} {incr i} { if {[info exists snode_neighbour(1,$i)]} then { set snode_neighbour1($i) $snode_neighbour(1,$i); } if {[array size upmap] > 0} then { set snode_map1($i) $upmap($snode_map_help(1,$i)); } else { set snode_map1($i) $snode_map_help(1,$i); } debug "snode_map1($i)=$snode_map1($i)"; set snode_weight1($i) $snode_weight(1,$i); } #save the link characteristics from both subgraphs in two different arrays for {set i 0} {$i < $sn_edges(0)} {incr i} { set sedge_array0($i) $sedge_array(0,$i); set sedge_weight0($i) $sedge_weight(0,$i); } for {set i 0} {$i < $sn_edges(1)} {incr i} { set sedge_array1($i) $sedge_array(1,$i); set sedge_weight1($i) $sedge_weight(1,$i); } } #update the tpwgts (partition's ratio of the graph) mult_array 0 [expr {int($nparts / 2)}] tpwgts [expr {1 / $sum_tpwgt}]; mult_array [expr {int($nparts / 2)}] $nparts tpwgts [expr {1.0 / (1.0 - $sum_tpwgt)}]; set max [expr {int($nparts - $nparts / 2)}]; for {set i 0} {$i < $max} {incr i} { set new_tpwgts($i) $tpwgts([expr {$i + int($nparts / 2)}]); debug " new_tpwgts($i)=$new_tpwgts($i)"; } #call recursive itself if {$nparts > 3} then { #partition the first subgraph recursiveBisection $sn_vtxs(0) snode_weight0 snode_neighbour0 sedge_array0 sedge_weight0 tpwgts [expr {int($nparts / 2)}] $part_nr snode_map0; #partition the second subgraph recursiveBisection $sn_vtxs(1) snode_weight1 snode_neighbour1 sedge_array1 sedge_weight1 new_tpwgts [expr {int($nparts - $nparts / 2)}] [expr {int($part_nr + $nparts / 2)} ] snode_map1; } elseif {$nparts == 3} then { #partition the second subgraph recursiveBisection $sn_vtxs(1) snode_weight1 snode_neighbour1 sedge_array1 sedge_weight1 new_tpwgts [expr {int($nparts - $nparts / 2)}] [expr {int($part_nr + $nparts / 2)}] snode_map1; } } #****f* graph_partitioning.tcl/coarseGraph # NAME # coarseGraph -- coarsening and uncoarsening # SYNOPSIS # coarseGraph $nvertices node_weight node_neighbour edge_array edge_weight tpwgts2 # FUNCTION # Coarsening and uncoarsening phase. Procedure first recursivly coarse the graph. # The coarsest graph is partitionend. In "backrolling" of recurson, the coarse # graph is uncoarsen and refined. # INPUTS # * nvertices -- number of vertices # * node_weight -- array of node weights # * node_neighbour -- array of node neighbours # * edge_array -- array of edges # * edge_weight -- array of edge weights # * tpwgts2 -- array of each partition size of the graph #**** proc coarseGraph {nvertices node_weight node_neighbour edge_array edge_weight tpwgts2} { global nparts; global max_nweight; global COARSEN_TO; upvar $node_weight nweight; upvar $node_neighbour nneighbour; upvar $edge_array earray; upvar $edge_weight eweight; upvar $tpwgts2 tpwgts; debug "MatchRm... $nvertices"; array set cnweight {}; array set nmap {}; array set nmatch {}; set matched ""; set cnvertices 0; #permute the nodes set permList [makePermList $nvertices ]; #array with random permuted nodes array set permArray $permList; set sum_nweight [sum_array $nvertices nweight]; set max_nweight [expr {1.5 * $sum_nweight / 20}]; #match the vertices for {set i 0} {[llength $matched] < $nvertices} {incr i} { set unmatched_node $permArray($i); if {[lsearch $matched $unmatched_node] == -1} then { lappend matched $unmatched_node; set matched_ngb 0; set max_eweight 0; debug "matched=$matched"; #node has an unmatched, passend neighbor, and is matched with it if {$nvertices > $COARSEN_TO && [info exists nneighbour($unmatched_node)]} then { foreach ngb $nneighbour($unmatched_node) { if {[lsearch $matched $ngb] == -1 && [expr {$nweight($i) + $nweight($ngb)}] < $max_nweight && $max_eweight < $eweight([getEdgeBetween $unmatched_node $ngb earray])} then { set matched_ngb 1; lappend matched $ngb; set max_eweight $eweight([getEdgeBetween $unmatched_node $ngb earray]); set nmatch($unmatched_node) $ngb; set nmatch($ngb) $unmatched_node; set nmap($unmatched_node) $cnvertices; #potrebno za uncoarse set nmap($ngb) $cnvertices; #potrebno za uncoarse set cnweight($cnvertices) [expr {$nweight($unmatched_node) + $nweight($ngb)}]; } } } #node is matched with itself if {$matched_ngb == 0} then { set nmatch($unmatched_node) $unmatched_node; set nmap($unmatched_node) $cnvertices; set cnweight($cnvertices) $nweight($unmatched_node); } debug "nmap($unmatched_node)=$nmap($unmatched_node),$unmatched_node,$nmatch($unmatched_node) "; incr cnvertices; } } array set cnneighbour {}; set used_nodes ""; set cngb 0; #coarse graph for {set i 0} {[llength $used_nodes] < $cnvertices} {incr i} { set parent1 $i; set parent2 $nmatch($i); set cnode $nmap($parent1); if {[lsearch $used_nodes $cnode] > -1} { continue; } lappend used_nodes $cnode; #save all neighbours from the 2 parent nodes to their coarse node set temp_ngb_list ""; if {[info exists nneighbour($parent1)] && [info exists nneighbour($parent2)]} then { # take all neighbours from "parent"-nodes set all_neighbours [concat $nneighbour($parent1) $nneighbour($parent2)]; foreach ngb $all_neighbours { set ngb_map $nmap($ngb); #don't save duplicates if {$ngb_map == $cnode} then { continue; } if {[lsearch $temp_ngb_list $ngb_map] == -1} then { lappend temp_ngb_list $ngb_map; } } set cnneighbour($cnode) $temp_ngb_list; } } ############## EDGES ############### array set cearray {}; set cnum_edges 0; #coarse edges for {set i 0} {$i < [array size earray]} {incr i} { set twin 0; set node1 [lindex $earray($i) 0]; set node2 [lindex $earray($i) 1]; set cnode1 $nmap($node1); set cnode2 $nmap($node2); if {$cnode1 == $cnode2} then { #edge between two coarsed nodes disappears } else { #check if the link already exists in coarsed graph for {set j 0} {$j < [array size cearray]} {incr j} { if {$cearray($j) == "$cnode1 $cnode2" || $cearray($j) == "$cnode2 $cnode1"} then { set twin 1; #add the edge weight to the weight of coarsed edge incr ceweight($j) $eweight($i); break; } } #if its no double edge, make a new edge in coarsed graph if {$twin == 0} then { set cearray($cnum_edges) "$cnode1 $cnode2"; set ceweight($cnum_edges) $eweight($i); incr cnum_edges; } } } #repeat coarsening if {$cnvertices > $COARSEN_TO && $nvertices > $cnvertices} { coarseGraph $cnvertices cnweight cnneighbour cearray ceweight tpwgts; } else { #enough coarsed, partition the coarsest graph makePartitions $cnvertices cnweight cnneighbour cearray ceweight tpwgts } debug "match Over !!!"; #balance, refine and uncoarse the graph balance $cnvertices cnneighbour cnweight cearray ceweight tpwgts 4; FMRefinement $cnvertices cnneighbour cnweight cearray ceweight tpwgts 4; project2waypartition $nvertices earray eweight nneighbour nmap nweight $cnvertices cnweight; } #****f* graph_partitioning.tcl/makePartitions # NAME # makePartitions -- initial partitioning # SYNOPSIS # makePartitions $nvertices node_weight node_neighbour edge_array edge_weight $tpwgts2 # FUNCTION # Initial partitioning of the coarsest graph. # INPUTS # * nvertices -- number of vertices # * node_weight -- array of node weights # * node_neighbour -- array of node neighbours # * edge_array -- array of edges # * edge_weight -- array of edge weights # * tpwgts2 -- array of each partition size of the graph #**** proc makePartitions {nvertices node_weight node_neighbour edge_array edge_weight tpwgts2} { global COARSEN_TO; global part_pwgts; global part_partition; global part_boundary; global part_id; global part_ed; global part_mincut; upvar $node_weight nweight; upvar $node_neighbour nneighbour; upvar $edge_array earray; upvar $edge_weight eweight; upvar $tpwgts2 tpwgts; #the sum of weight of all neighbours array set wsum_ngbs {}; array set bestpartition {}; array set part_partition {}; array set visited {}; array set part_ed {}; array set part_id {}; set part_mincut 0; #calculate the sum of all edge-weights in graph set wsum 1; for {set i 0} {$i < $nvertices} {incr i} { if {[info exists nneighbour($i)]} then { foreach ngb $nneighbour($i) { set e [getEdgeBetween $i $ngb earray]; incr wsum $eweight($e); } } set bestpartitions($i) -1; } set bestcut $wsum; if {$nvertices <= $COARSEN_TO} then { set nbfs 4; } else { set nbfs 9; } while {$nbfs > 1} { incr nbfs -1; set part_boundary ""; # set all vertices to partition 1, and for all vertices to not visited for {set i 0} {$i < $nvertices} {incr i} { set part_partition($i) 1; set visited($i) 0; } set part_pwgts(0) 0; set part_pwgts(1) [expr {$tpwgts(0) + $tpwgts(1)}]; # Breadth - first algorithm set queue {}; set start_node [expr {int(rand() * $nvertices)}]; set queue $start_node; set visited($start_node) 1; while {1} { #graph is disconnected if {[llength $queue] == 0} { set more_left 0; for {set n 0} {$n < $nvertices} {incr n} { if {$visited($n) == 0} then { set queue $n; set visited($n) 1; set more_left 1; break; } } if {$more_left == 0} then { debug "no more left!"; break; } } # take the first node from queue set i [lindex $queue 0]; set queue [lreplace $queue 0 0]; if {$part_pwgts(0) > 0 && [expr {$part_pwgts(1) - $nweight($i)}] < $tpwgts(1)} then { debug "preveliko, dalje..."; continue; } #change partition of i from 1 to 0 set part_partition($i) 0; #update the partitions weight set part_pwgts(0) [expr {$part_pwgts(0) + $nweight($i)}]; set part_pwgts(1) [expr {$part_pwgts(1) - $nweight($i)}]; #partition is bigger than it should be if {$part_pwgts(1) <= $tpwgts(1)} then { debug "tpwgts(1)=$tpwgts(1)"; break; } #search for the not visited neighbors, and attach them to the queue if {[info exists nneighbour($i)]} then { foreach ngb $nneighbour($i) { if {$visited($ngb) == 0} { set visited($ngb) 1; lappend queue $ngb; };#if };#foreach } };#while array set pwgts2 {}; set pwgts2(0) 0; set pwgts2(1) 0; #calculate ID and ED for each vertex for {set i 0} {$i < $nvertices} {incr i} { set part_id($i) 0; set part_ed($i) 0; set curr_partition $part_partition($i); incr pwgts2($curr_partition) $nweight($i); # calculates the sum of the edge weights of the adjacent vertices of i if {[info exists nneighbour($i)]} then { foreach ngb $nneighbour($i) { if {$curr_partition == $part_partition($ngb)} then { # vertice in the same partition incr part_id($i) $eweight([getEdgeBetween $i $ngb earray]); } else { # vertice in a different partition incr part_ed($i) $eweight([getEdgeBetween $i $ngb earray]); };#if-else };#foreach if {$part_ed($i) > 0 || [llength $nneighbour($i)] == 0} then { #vanjski node, ili nema susjeda incr part_mincut $part_ed($i); lappend part_boundary $i; } } } set part_mincut [expr {$part_mincut / 2}]; set sum 0; debug "init part: part_mincut=$part_mincut"; for {set k 0} {$k < $nvertices} {incr k} { incr sum $nweight($k); } if {$pwgts2(0) + $pwgts2(1) != $sum} { error "refine: partition weigth wrong!"; } #balance the graph balance $nvertices nneighbour nweight earray eweight tpwgts 4; # edge - based FM refinement FMRefinement $nvertices nneighbour nweight earray eweight tpwgts 4; #save the partitions if better then current saved if {$bestcut > $part_mincut} then { set bestcut $part_mincut; set bestboundary $part_boundary; set bestpwgts(0) $part_pwgts(0); set bestpwgts(1) $part_pwgts(1); for {set i 0} {$i < $nvertices} {incr i} { set bestpartitions($i) $part_partition($i); #save the best partitions set bestid($i) $part_id($i); set bested($i) $part_ed($i); } if {$part_mincut == 0} then { break; } } } #save to globals the best found partitions set part_mincut $bestcut; set part_boundary $bestboundary; set part_pwgts(0) $bestpwgts(0); set part_pwgts(1) $bestpwgts(1); for {set i 0} {$i < $nvertices} {incr i} { set part_partition($i) $bestpartitions($i); set part_id($i) $bestid($i); set part_ed($i) $bested($i); } } #****f* graph_partitioning.tcl/balance # NAME # balance # SYNOPSIS # balance $nvertices node_neighbour node_weight edge_array edge_weight tpart_wgts $npasses # FUNCTION # Procedure swaps vertices between two partitions, to make the partitions balanced. # The vertices from the bigger partition # are swapped to the smaller partition. After swapping, the ed and id arrays are # for all neighbor vertices updated. # INPUTS # * nvertices -- number of vertices # * node_weight -- array of node weights # * node_neighbour -- array of node neighbours # * edge_array -- array of edges # * edge_weight -- array of edge weights # * tpart_wgts -- array of each partition size of the graph # * npasses -- number of swap tries #**** proc balance {nvertices node_neighbour node_weight edge_array edge_weight tpart_wgts npasses} { global part_pwgts; global part_partition; global part_boundary; global part_id; global part_ed; global part_mincut; upvar $node_neighbour nneighbour; upvar $node_weight nweight; upvar $edge_array earray; upvar $edge_weight eweight; upvar $tpart_wgts tpwgts; set move_from -1; set move_to -1; #there is no boundary nodes if {[llength $part_boundary] == 0} then { return; } # chose the the bigger partition to move from if {($tpwgts(0) - $part_pwgts(0)) < ($tpwgts(1) - $part_pwgts(1))} then { set move_from 0; set move_to 1; } else { set move_from 1; set move_to 0; } #prority queue array set queue {}; #put all boundary nodes from move_from partition into queue for {set i 0} {$i < [llength $part_boundary]} {incr i} { set b [lindex $part_boundary $i]; if {$part_partition($b) == $move_from} then { set b_gain [expr {$part_ed($b) - $part_id($b)}]; push queue($move_from) "$b $b_gain"; } } # set all vertices free to move for {set i 0} {$i < $nvertices} {incr i} { set moved($i) -1; } for {set pass 0} {$pass < $npasses} {incr pass} { # doesn't exists, if nodes are not connected if {![info exists queue($move_from)]} then { break; } # chose the node with the highest gain set hi_gain [pop queue($move_from)]; if {$hi_gain == ""} { debug "queue($move_from) empty."; break; } #if the size of the partition, in which the node should be moved #is to small, dont move it if {$part_pwgts($move_to) + $nweight($hi_gain) > $tpwgts($move_to)} then { break; } #update partitions weight incr part_pwgts($move_from) [expr {-$nweight($hi_gain)}]; incr part_pwgts($move_to) $nweight($hi_gain); set part_partition($hi_gain) $move_to; #all the "extern" links are now "intern", and umgekehrt set tmp $part_ed($hi_gain); set part_ed($hi_gain) $part_id($hi_gain); set part_id($hi_gain) $tmp; #if it's no more boundary node if {$part_ed($hi_gain) == 0} then { #remove it from the bndy list set bndy [lreplace $part_boundary [lsearch $part_boundary $hi_gain] [lsearch $part_boundary $hi_gain]]; } #update part_id, part_ed values # go throught all neighbours of node "hi_gain" if {[info exists nneighbour($hi_gain)]} then { foreach ngb $nneighbour($hi_gain) { set is_bnd_node $part_ed($ngb); #if the value is > 0, it is a boundary node set edgeBetween [getEdgeBetween $hi_gain $ngb earray]; if {$part_partition($ngb) == $move_to} then { incr part_id($ngb) $eweight($edgeBetween); incr part_ed($ngb) -$eweight($edgeBetween); } else { incr part_ed($ngb) $eweight($edgeBetween); incr part_id($ngb) -$eweight($edgeBetween); } if {$is_bnd_node > 0} then { #node "ngb" is no longer an boundary node if {$part_ed($ngb) == 0} then { #remove it from the boundary list set part_boundary [lreplace $part_boundary [lsearch $part_boundary $ngb] [lsearch $part_boundary $ngb]]; if {$moved($ngb) == -1 && ($part_partition($ngb)==$move_from)} then { #if not moved -> remove it from the queue removeFromQueue queue($part_partition($ngb)) $ngb; } } else { #if it wasn't been moved, update it in queue if {$moved($ngb) == -1 && ($part_partition($ngb) == $move_from)} then { removeFromQueue queue($part_partition($ngb)) $ngb; set new_gain [expr {$part_ed($ngb) - $part_id($ngb)}]; push queue($part_partition($ngb)) "$ngb $new_gain"; } } } else { #puts "not boundary node: $ngb"; if {$part_ed($ngb) > 0} then { #new boundary node lappend part_boundary $ngb; #add it to the queue if {$moved($ngb) == -1} then { push queue($part_partition($ngb)) "$ngb [expr {$part_ed($ngb) - $part_id($ngb)}]"; } } } } } } } #****f* graph_partitioning.tcl/FMRefinement # NAME # FMRefinement # SYNOPSIS # FMRefinement $nvertices node_neighbour node_weight edge_array edge_weight tpart_wgt $npasses # FUNCTION # Procedure swaps the vertices between two partition, to reduce the edge-cut. # INPUTS # * nvertices -- number of vertices # * node_weight -- array of node weights # * node_neighbour -- array of node neighbours # * edge_array -- array of edges # * edge_weight -- array of edge weights # * tpart_wgts -- array of each partition size of the graph # * npasses -- number of swap tries #**** proc FMRefinement {nvertices node_neighbour node_weight edge_array edge_weight tpart_wgt npasses} { global part_pwgts; global part_partition; global part_boundary; global part_id; global part_ed; global part_mincut; upvar $node_weight nweight; upvar $node_neighbour nneighbour; upvar $edge_array earray; upvar $edge_weight eweight; upvar $tpart_wgt tpwgts; array set queue {}; array set bak_id {}; array set bak_ed {}; array set bak_part {}; array set bak_pwgts {}; set bak_bndy -1; set orig_diff [expr {abs ($tpwgts(0) - $part_pwgts(0))}]; set avg1 [expr {($part_pwgts(0) + $part_pwgts(1)) / 20}]; set avg2 [expr {2 * ($part_pwgts(0) + $part_pwgts(1)) / $nvertices}]; if {$avg1 < $avg2} then { set avg_pwgt $avg1; } else { set avg_pwgt $avg2; } set swap_limit [expr {int(0.01 * $nvertices)}]; if {$swap_limit < 15} then { set swap_limit 15; } #pamti najbolju kombinaciju set bak_bndy $part_boundary; for {set i 0} {$i < $nvertices} {incr i} { set bak_part($i) $part_partition($i); set bak_id($i) $part_id($i); set bak_ed($i) $part_ed($i); } set bak_pwgts(0) $part_pwgts(0); set bak_pwgts(1) $part_pwgts(1); # set all vertices free to move for {set i 0} {$i < $nvertices} {incr i} { set moved($i) -1; } for {set pass 0} {$pass < $npasses} {incr pass} { #set all variables to their's initial values set bndy $part_boundary; set newcut $part_mincut; set mincut $part_mincut; set min_diff [expr {abs ($tpwgts(0) - $part_pwgts(0))}]; for {set i 0} {$i < 2} {incr i} { set pwgts($i) $part_pwgts($i); set queue($i) ""; } for {set i 0} {$i < $nvertices} {incr i} { set part($i) $part_partition($i); set id($i) $part_id($i); set ed($i) $part_ed($i); } # insert boundary nodes in the priority queue set permList [makePermArray bndy]; array set permArray $permList; set mincutorder -1; for {set i 0} {$i < [array size permArray]} {incr i} { set node $permArray($i); #calculate the node's gain set node_gain [expr {$ed($node) - $id($node)}]; # push in the queue 0 or 1 (depends in which partition node is) the node and its gain push queue($part($node)) "$node $node_gain"; };#foreach # chose the best-gain move for {set nswaps 0} {$nswaps < $nvertices} {incr nswaps} { debug "nswaps=$nswaps"; # chose the node from the bigger partition to move to the smaller if {($tpwgts(0) - $pwgts(0)) < ($tpwgts(1) - $pwgts(1))} then { set move_from 0; set move_to 1; } else { set move_from 1; set move_to 0; } # chose the node with the highest gain set hi_gain [pop queue($move_from)]; if {$hi_gain == ""} { break; } # update the cut and partitions weight set newcut [expr {$newcut - $ed($hi_gain) + $id($hi_gain)}]; incr pwgts($move_from) [expr {-$nweight($hi_gain)}]; incr pwgts($move_to) $nweight($hi_gain); #check if the new cut better is than the old one set new_diff [expr {abs ($tpwgts(0) - $pwgts(0))}]; if {($newcut < $mincut) && ($new_diff <= $orig_diff + $avg_pwgt) || ($newcut == $mincut) && ($new_diff < $min_diff)} then { set mincutorder $nswaps; set mincut $newcut; set min_diff $new_diff; } elseif {$nswaps - $mincutorder > $swap_limit} { incr newcut [expr {$ed($hi_gain) - $id($hi_gain)}]; incr pwgts($move_to) [expr {-$nweight($hi_gain)}]; incr pwgts($move_from) $nweight($hi_gain); break; } #move node to the other partion set part($hi_gain) $move_to; set moved($hi_gain) $nswaps; set swaps($nswaps) $hi_gain; #all the "extern" links are now "intern", and reverse set tmp $ed($hi_gain); set ed($hi_gain) $id($hi_gain); set id($hi_gain) $tmp; #if it's no more boundary node if {$ed($hi_gain) == 0} then { #remove it from the bndy list set bndy [lreplace $bndy [lsearch $bndy $hi_gain] [lsearch $bndy $hi_gain]]; } #update ID, ED values # go throught all neighbours of node "hi_gain" if {[info exists nneighbour($hi_gain)]} then { foreach ngb $nneighbour($hi_gain) { set is_bnd_node $ed($ngb); #if the value is > 0, it is a boundary node set edgeBetween [getEdgeBetween $hi_gain $ngb earray]; if {$part($ngb) == $move_to} then { incr id($ngb) $eweight($edgeBetween); incr ed($ngb) -$eweight($edgeBetween); } else { incr ed($ngb) $eweight($edgeBetween); incr id($ngb) -$eweight($edgeBetween); } if {$is_bnd_node > 0} then { #node "ngb" is no longer an boundary node if {$ed($ngb) == 0} then { #remove it from the boundary list set bndy [lreplace $bndy [lsearch $bndy $ngb] [lsearch $bndy $ngb]]; if {$moved($ngb) == -1} then { #if not moved -> remove it from the queue removeFromQueue queue($part($ngb)) $ngb; } } else { #if it wasn't been moved, update it in queue if {$moved($ngb) == -1} then { removeFromQueue queue($part($ngb)) $ngb; set new_gain [expr {$ed($ngb) - $id($ngb)}]; push queue($part($ngb)) "$ngb $new_gain"; } } } else { #puts "not boundary node: $ngb"; if {$ed($ngb) > 0} then { ;#new boundary node lappend bndy $ngb; #add it to the queue if {$moved($ngb) == -1} then { push queue($part($ngb)) "$ngb [expr {$ed($ngb) - $id($ngb)}]"; } } } } } if {$mincutorder > -1} then { set mincutorder -1; set mincut $newcut; set bak_bndy $bndy; for {set j 0} {$j < $nvertices} {incr j} { set bak_id($j) $id($j); set bak_ed($j) $ed($j); set bak_part($j) $part($j); } set bak_pwgts(0) $pwgts(0); set bak_pwgts(1) $pwgts(1); } };#inner loop };#outer loop #save the best partitions set part_mincut $mincut; set part_boundary $bak_bndy; for {set i 0} {$i < $nvertices} {incr i} { set part_partition($i) $bak_part($i); set part_id($i) $bak_id($i); set part_ed($i) $bak_ed($i); } set part_pwgts(0) $bak_pwgts(0); set part_pwgts(1) $bak_pwgts(1); } #****f* graph_partitioning.tcl/project2waypartition # NAME # project2waypartition # SYNOPSIS # project2waypartition $nvertices edge_array edge_weight node_neighbour node_map node_weight cnv $cnvw # FUNCTION # The partitions from the coarserer graph propagates one level up. # INPUTS # * nvertices -- number of vertices # * node_weight -- array of node weights # * node_neighbour -- array of node neighbours # * edge_array -- array of edges # * edge_weight -- array of edge weights # * node_map -- array with mappings of nodes from parent to child (coarse) graph # * cnv -- number of vertices in coarse graph # * cnvw -- array of node weights in coarse graph #**** proc project2waypartition {nvertices edge_array edge_weight node_neighbour node_map node_weight cnv cnvw} { global part_pwgts; global part_mincut; global part_partition; global part_id; global part_ed; global part_boundary; upvar $cnvw cnw; upvar $node_weight nweight; upvar $edge_array earray; upvar $edge_weight eweight; upvar $node_neighbour nneighbour; upvar $node_map nmap; array set part_ed {}; array set part_id {}; set part_boundary ""; array set pwgts2 {}; #sum the weight of nodes in finer graph set n 0; set p 0; for {set i 0} {$i < $nvertices} {incr i} { incr n $nweight($i); } #sum the weight of nodes in coarsed graph for {set i 0} {$i < $cnv} {incr i} { incr p $cnw($i); } #get partition for each vertex in finer graph for {set i 0} {$i < $nvertices} {incr i} { set cnode $nmap($i); #get the node in coarsed graph which corresponse to the node in finer graph set partition($i) $part_partition($cnode); #get it's partition too set part_ed($i) 0; set part_id($i) 0; set pwgts2(0) 0; set pwgts2(1) 0; } #calculate ID and ED for {set i 0} {$i < $nvertices} {incr i} { if {![info exists nneighbour($i)] || [llength $nneighbour($i)] == 0} then { lappend part_boundary $i; } else { foreach ngb $nneighbour($i) { if {$partition($ngb) == $partition($i)} then { incr part_id($i) $eweight([getEdgeBetween $i $ngb earray]); #mogu uzeti stare tezine - posto iste #incr id($ngb) $eweight([...]); } else { incr part_ed($i) $eweight([getEdgeBetween $i $ngb earray]); #incr ed($ngb) $eweight([...]); } };#foreach if {[expr $part_ed($i) > 0 || [llength $nneighbour($i)] == 0]} then { lappend part_boundary $i; } } set part_partition($i) $partition($i); incr pwgts2($partition($i)) $nweight($i); };#for } #****f* graph_partitioning.tcl/splitGraph # NAME # splitGraph # SYNOPSIS # splitGraph $nvertices node_neighbour node_weight edge_array edge_weight snode_neighbour snode_weight sedge_array sedge_weight snode_map sn_vtxs sn_edges snode_map_help # FUNCTION # Divides the graph into two parts, one with nodes in partition 0 and the other with # the nodes in the partition 1. # INPUTS # * nvertices -- number of vertices # * node_weight -- array of node weights # * node_neighbour -- array of node neighbours # * edge_array -- array of edges # * edge_weight -- array of edge weights # * snode_weight -- array of node weights of the split graph # * snode_neighbour -- array of node neighbours of the split graph # * sedge_array -- array of edges of the split graph # * sedge_weight -- array of edge weights of the split graph # * snode_map -- array with mappings of nodes from parent to child (coarse) graph # * sn_vtxs -- number of vertices of the split graph # * sn_edges -- number of edges of the split graph # * snode_map_help -- help variable, needed for later connecting, in this procedure disconnected graphs #**** proc splitGraph {nvertices node_neighbour node_weight edge_array edge_weight snode_neighbour snode_weight sedge_array sedge_weight snode_map sn_vtxs sn_edges snode_map_help} { global part_partition; upvar $node_neighbour nneighbour; upvar $node_weight nweight; upvar $edge_array earray; upvar $edge_weight eweight; upvar $snode_neighbour snneighbour; upvar $snode_weight snweight; upvar $sedge_array searray; upvar $sedge_weight seweight; upvar $snode_map snmap; upvar $snode_map_help snmap_h; upvar $sn_vtxs snvtxs; upvar $sn_edges snedges; array set sum_np {}; set sum_np(0) 0; set sum_np(1) 0; array set auxn {}; array set auxw {}; #sets variables needed later for connecting the splited graph for {set i 0} {$i < $nvertices} {incr i} { set p $part_partition($i); set snmap($p,$i) $snvtxs($p); set snmap_h($p,$snvtxs($p)) $i; incr snvtxs($p); } #split the graph for {set i 0} {$i < $nvertices} {incr i} { set p_i $part_partition($i); set s_i $snmap($p_i,$i); set sum 0; if {[info exists nneighbour($i)]} then { foreach ngb $nneighbour($i) { set p $part_partition($ngb); if {$p == $p_i} then { set twin 0; set sngb $snmap($p_i,$ngb); lappend snneighbour($p_i,$s_i) $sngb; for {set a 0} {$a < $snedges($p_i)} {incr a} { if {$searray($p_i,$a) == "$s_i $sngb" || $searray($p_i,$a) == "$sngb $s_i"} then { set twin 1; break; } } if {$twin == 0} then { set searray($p_i,$snedges($p_i)) "$s_i $sngb"; set seweight($p_i,$snedges($p_i)) $eweight([getEdgeBetween $i $ngb earray]); incr snedges($p_i); } incr sum $nweight($ngb); } else { incr sum [expr {-$nweight($ngb)}] } };#foreach } set snweight($p_i,$s_i) $nweight($i); set sadjwgtsum($p_i,$s_i)) $sum; } } #****f* graph_partitioning.tcl/sum_array # NAME # sum_array # SYNOPSIS # sum_array $end arr # FUNCTION # Function sum the elements from the array. # INPUTS # * end -- until what index to sum # * arr -- array of numbers # RESULT # * sum -- the sum of first "end" numbers #**** proc sum_array {end arr} { upvar 1 $arr a; set sum 0.0; for {set i 0} {$i < $end} {incr i} { set sum [expr {$sum + $a($i)}]; } return $sum; } #****f* graph_partitioning.tcl/mult_array # NAME # mult_array # SYNOPSIS # mult_array $start $end arr $prod # FUNCTION # Procedure multiplyes the elements between start and end position in the array # with the number prod # INPUTS # * start -- the position in array from where to start multipling # * end -- the position in array until which to multiply # * arr -- array # * prod -- # RESULT # * #**** proc mult_array {start end arr prod} { upvar 1 $arr a; for {set i $start} {$i < $end} {incr i} { set a($i) [expr {$a($i) * $prod }]; } } #****f* graph_partitioning.tcl/makePermList # NAME # makePermList -- make permuted list # SYNOPSIS # makePermList $num # FUNCTION # Function makes a new list with num elements, and randomizes the list. # INPUTS # * num -- number of elements in list # RESULT # * list -- permuted list #**** proc makePermList {num} { array set permList ""; for {set i 0} {$i < $num} {incr i} { set permList($i) $i; } if {$num > 4} { for {set i 0} {$i < $num} {incr i 16} { set rand1 [expr {int(rand() * ($num - 4))}] set rand2 [expr {int(rand() * ($num - 4))}] swap permList $rand1 $rand2; swap permList [expr $rand1+1] [expr $rand2+1]; swap permList [expr $rand1+2] [expr $rand2+2]; swap permList [expr $rand1+3] [expr $rand2+3]; } } return [array get permList]; } #****f* graph_partitioning.tcl/makePermArray # NAME # makePermArray -- make permuted array # SYNOPSIS # makePermArray arr # FUNCTION # Function randomizes the elements in the array. # INPUTS # * arr -- array to randomize # RESULT # * list -- permuted list #**** proc makePermArray {arr} { upvar $arr a; array set permList ""; set a_size [llength $a]; for {set i 0} {$i < $a_size} {incr i} { set permList($i) [lindex $a $i]; } if {$a_size > 4} { for {set i 0} {$i < $a_size} {incr i 16} { set rand1 [expr {int(rand() * ($a_size - 4))}] set rand2 [expr {int(rand() * ($a_size - 4))}] swap permList $rand1 $rand2; swap permList [expr $rand1+1] [expr $rand2+1]; swap permList [expr $rand1+2] [expr $rand2+2]; swap permList [expr $rand1+3] [expr $rand2+3]; } } return [array get permList]; } #****f* graph_partitioning.tcl/swap # NAME # swap # SYNOPSIS # swap permArray $idx1 $idx2 # FUNCTION # Procedure swapps two elements in the array. # INPUTS # * permList -- array # * idx1 -- index of the first element # * idx2 -- index of the second element #**** proc swap {permArray idx1 idx2} { upvar $permArray rarray; set temp $rarray($idx1); set rarray($idx1) $rarray($idx2); set rarray($idx2) $temp; } #****f* graph_partitioning.tcl/getEdgeBetween # NAME # getEdgeBetween -- get edge between # SYNOPSIS # getEdgeBetween $node1 $node2 edge_array # FUNCTION # Function searches for an edge connecting the two nodes. # INPUTS # * node1 -- first node id # * node2 -- second node id # * edge_array -- array of edges # RESULT # * i -- index of the edge connecting the nodes, or null #**** proc getEdgeBetween {node1 node2 edge_array} { upvar $edge_array earray; for {set i 0} {$i < [array size earray]} {incr i} { if {$earray($i) == "$node1 $node2" || $earray($i) == "$node2 $node1"} then { return $i; } } } #****f* graph_partitioning.tcl/push # NAME # push # SYNOPSIS # push # FUNCTION # Alias for the command lappend. #**** interp alias {} push {} lappend #****f* graph_partitioning.tcl/pop # NAME # pop # SYNOPSIS # pop queue # FUNCTION # Returns the element from the queue with the highest priority. # INPUTS # * queue_name -- array # RESULT # * hi_elem -- element from the array #**** proc pop {queue_name} { upvar 1 $queue_name queue; #sort items after priority set queue [lsort -integer -decreasing -index 1 $queue]; set hi_ [lindex $queue 0]; set hi_elem [lindex $hi_ 0]; set queue [lrange $queue 1 end]; return $hi_elem; } #****f* graph_partitioning.tcl/removeFromQueue # NAME # removeFromQueue -- remove from the queue # SYNOPSIS # removeFromQueue queue_name $node # FUNCTION # Removes the node from the queue. # INPUTS # * queue_name -- array # * node -- node id #**** proc removeFromQueue {queue_name node } { upvar 1 $queue_name queue; foreach q $queue { set n [lindex $q 0]; if {$n == $node} then { set node_idx [lsearch $queue $q]; set queue [lreplace $queue $node_idx $node_idx]; } } } ############ GLOBALS set debug 0; array set tpwgts {}; set nparts 0; set finalcut 0; set COARSEN_TO 20; array set pwgts {}; set minPartWeight 0; set split_list ""; array set finalpartition {}; set part_boundary ""; array set part_partition {}; array set part_id {}; array set part_ed {}; array set part_pwgts {}; set part_mincut 0;