// ----------------------------------------------------------------------- // // Original Matlab code by John Burkardt, Florida State University // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // // ----------------------------------------------------------------------- namespace UnityEngine.U2D.Animation.TriangleNet .Tools { using System; /// /// Applies the Cuthill and McKee renumbering algorithm to reduce the bandwidth of /// the adjacency matrix associated with the mesh. /// internal class CuthillMcKee { // The adjacency matrix of the mesh. AdjacencyMatrix matrix; /// /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. /// /// The mesh. /// Permutation vector. public int[] Renumber(Mesh mesh) { // Algorithm needs linear numbering of the nodes. mesh.Renumber(NodeNumbering.Linear); return Renumber(new AdjacencyMatrix(mesh)); } /// /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. /// /// The mesh. /// Permutation vector. public int[] Renumber(AdjacencyMatrix matrix) { this.matrix = matrix; int bandwidth1 = matrix.Bandwidth(); var pcol = matrix.ColumnPointers; // Adjust column pointers (1-based indexing). Shift(pcol, true); // TODO: Make RCM work with 0-based matrix. // Compute the RCM permutation. int[] perm = GenerateRcm(); int[] perm_inv = PermInverse(perm); int bandwidth2 = PermBandwidth(perm, perm_inv); if (Log.Verbose) { Log.Instance.Info(String.Format("Reverse Cuthill-McKee (Bandwidth: {0} > {1})", bandwidth1, bandwidth2)); } // Adjust column pointers (0-based indexing). Shift(pcol, false); return perm_inv; } #region RCM /// /// Finds the reverse Cuthill-Mckee ordering for a general graph. /// /// The RCM ordering. /// /// For each connected component in the graph, the routine obtains /// an ordering by calling RCM. /// int[] GenerateRcm() { // Number of nodes in the mesh. int n = matrix.N; int[] perm = new int[n]; int i, num, root; int iccsze = 0; int level_num = 0; /// Index vector for a level structure. The level structure is stored in the /// currently unused spaces in the permutation vector PERM. int[] level_row = new int[n + 1]; /// Marks variables that have been numbered. int[] mask = new int[n]; for (i = 0; i < n; i++) { mask[i] = 1; } num = 1; for (i = 0; i < n; i++) { // For each masked connected component... if (mask[i] != 0) { root = i; // Find a pseudo-peripheral node ROOT. The level structure found by // ROOT_FIND is stored starting at PERM(NUM). FindRoot(ref root, mask, ref level_num, level_row, perm, num - 1); // RCM orders the component using ROOT as the starting node. Rcm(root, mask, perm, num - 1, ref iccsze); num += iccsze; // We can stop once every node is in one of the connected components. if (n < num) { return perm; } } } return perm; } /// /// RCM renumbers a connected component by the reverse Cuthill McKee algorithm. /// /// the node that defines the connected component. It is used as the starting /// point for the RCM ordering. /// Input/output, int MASK(NODE_NUM), a mask for the nodes. Only those nodes with /// nonzero input mask values are considered by the routine. The nodes numbered by RCM will have /// their mask values set to zero. /// Output, int PERM(NODE_NUM), the RCM ordering. /// Output, int ICCSZE, the size of the connected component that has been numbered. /// the number of nodes. /// /// The connected component is specified by a node ROOT and a mask. /// The numbering starts at the root node. /// /// An outline of the algorithm is as follows: /// /// X(1) = ROOT. /// /// for ( I = 1 to N-1) /// Find all unlabeled neighbors of X(I), /// assign them the next available labels, in order of increasing degree. /// /// When done, reverse the ordering. /// void Rcm(int root, int[] mask, int[] perm, int offset, ref int iccsze) { int[] pcol = matrix.ColumnPointers; int[] irow = matrix.RowIndices; int fnbr; int i, j, k, l; int jstop, jstrt; int lbegin, lnbr, lperm, lvlend; int nbr, node; // Number of nodes in the mesh. int n = matrix.N; /// Workspace, int DEG[NODE_NUM], a temporary vector used to hold /// the degree of the nodes in the section graph specified by mask and root. int[] deg = new int[n]; // Find the degrees of the nodes in the component specified by MASK and ROOT. Degree(root, mask, deg, ref iccsze, perm, offset); mask[root] = 0; if (iccsze <= 1) { return; } lvlend = 0; lnbr = 1; // LBEGIN and LVLEND point to the beginning and // the end of the current level respectively. while (lvlend < lnbr) { lbegin = lvlend + 1; lvlend = lnbr; for (i = lbegin; i <= lvlend; i++) { // For each node in the current level... node = perm[offset + i - 1]; jstrt = pcol[node]; jstop = pcol[node + 1] - 1; // Find the unnumbered neighbors of NODE. // FNBR and LNBR point to the first and last neighbors // of the current node in PERM. fnbr = lnbr + 1; for (j = jstrt; j <= jstop; j++) { nbr = irow[j - 1]; if (mask[nbr] != 0) { lnbr += 1; mask[nbr] = 0; perm[offset + lnbr - 1] = nbr; } } // Node has neighbors if (lnbr > fnbr) { // Sort the neighbors of NODE in increasing order by degree. // Linear insertion is used. k = fnbr; while (k < lnbr) { l = k; k = k + 1; nbr = perm[offset + k - 1]; while (fnbr < l) { lperm = perm[offset + l - 1]; if (deg[lperm - 1] <= deg[nbr - 1]) { break; } perm[offset + l] = lperm; l = l - 1; } perm[offset + l] = nbr; } } } } // We now have the Cuthill-McKee ordering. Reverse it. ReverseVector(perm, offset, iccsze); } /// /// Finds a pseudo-peripheral node. /// /// On input, ROOT is a node in the the component of the graph for /// which a pseudo-peripheral node is sought. On output, ROOT is the pseudo-peripheral /// node obtained. /// MASK[NODE_NUM], specifies a section subgraph. Nodes for which MASK /// is zero are ignored by FNROOT. /// Output, int LEVEL_NUM, is the number of levels in the level /// structure rooted at the node ROOT. /// Output, int LEVEL_ROW(NODE_NUM+1), the level structure array pair /// containing the level structure found. /// Output, int LEVEL(NODE_NUM), the level structure array pair /// containing the level structure found. /// the number of nodes. /// /// The diameter of a graph is the maximum distance (number of edges) /// between any two nodes of the graph. /// /// The eccentricity of a node is the maximum distance between that /// node and any other node of the graph. /// /// A peripheral node is a node whose eccentricity equals the /// diameter of the graph. /// /// A pseudo-peripheral node is an approximation to a peripheral node; /// it may be a peripheral node, but all we know is that we tried our /// best. /// /// The routine is given a graph, and seeks pseudo-peripheral nodes, /// using a modified version of the scheme of Gibbs, Poole and /// Stockmeyer. It determines such a node for the section subgraph /// specified by MASK and ROOT. /// /// The routine also determines the level structure associated with /// the given pseudo-peripheral node; that is, how far each node /// is from the pseudo-peripheral node. The level structure is /// returned as a list of nodes LS, and pointers to the beginning /// of the list of nodes that are at a distance of 0, 1, 2, ..., /// NODE_NUM-1 from the pseudo-peripheral node. /// /// Reference: /// Alan George, Joseph Liu, /// Computer Solution of Large Sparse Positive Definite Systems, /// Prentice Hall, 1981. /// /// Norman Gibbs, William Poole, Paul Stockmeyer, /// An Algorithm for Reducing the Bandwidth and Profile of a Sparse Matrix, /// SIAM Journal on Numerical Analysis, /// Volume 13, pages 236-250, 1976. /// /// Norman Gibbs, /// Algorithm 509: A Hybrid Profile Reduction Algorithm, /// ACM Transactions on Mathematical Software, /// Volume 2, pages 378-387, 1976. /// void FindRoot(ref int root, int[] mask, ref int level_num, int[] level_row, int[] level, int offset) { int[] pcol = matrix.ColumnPointers; int[] irow = matrix.RowIndices; int iccsze; int j, jstrt; int k, kstop, kstrt; int mindeg; int nghbor, ndeg; int node; int level_num2 = 0; // Determine the level structure rooted at ROOT. GetLevelSet(ref root, mask, ref level_num, level_row, level, offset); // Count the number of nodes in this level structure. iccsze = level_row[level_num] - 1; // Extreme cases: // A complete graph has a level set of only a single level. // Every node is equally good (or bad). // or // A "line graph" 0--0--0--0--0 has every node in its only level. // By chance, we've stumbled on the ideal root. if (level_num == 1 || level_num == iccsze) { return; } // Pick any node from the last level that has minimum degree // as the starting point to generate a new level set. for (;;) { mindeg = iccsze; jstrt = level_row[level_num - 1]; root = level[offset + jstrt - 1]; if (jstrt < iccsze) { for (j = jstrt; j <= iccsze; j++) { node = level[offset + j - 1]; ndeg = 0; kstrt = pcol[node - 1]; kstop = pcol[node] - 1; for (k = kstrt; k <= kstop; k++) { nghbor = irow[k - 1]; if (mask[nghbor] > 0) { ndeg += 1; } } if (ndeg < mindeg) { root = node; mindeg = ndeg; } } } // Generate the rooted level structure associated with this node. GetLevelSet(ref root, mask, ref level_num2, level_row, level, offset); // If the number of levels did not increase, accept the new ROOT. if (level_num2 <= level_num) { break; } level_num = level_num2; // In the unlikely case that ROOT is one endpoint of a line graph, // we can exit now. if (iccsze <= level_num) { break; } } } /// /// Generates the connected level structure rooted at a given node. /// /// the node at which the level structure is to be rooted. /// MASK[NODE_NUM]. On input, only nodes with nonzero MASK are to be processed. /// On output, those nodes which were included in the level set have MASK set to 1. /// Output, int LEVEL_NUM, the number of levels in the level structure. ROOT is /// in level 1. The neighbors of ROOT are in level 2, and so on. /// Output, int LEVEL_ROW[NODE_NUM+1], the rooted level structure. /// Output, int LEVEL[NODE_NUM], the rooted level structure. /// the number of nodes. /// /// Only nodes for which MASK is nonzero will be considered. /// /// The root node chosen by the user is assigned level 1, and masked. /// All (unmasked) nodes reachable from a node in level 1 are /// assigned level 2 and masked. The process continues until there /// are no unmasked nodes adjacent to any node in the current level. /// The number of levels may vary between 2 and NODE_NUM. /// /// Reference: /// Alan George, Joseph Liu, /// Computer Solution of Large Sparse Positive Definite Systems, /// Prentice Hall, 1981. /// void GetLevelSet(ref int root, int[] mask, ref int level_num, int[] level_row, int[] level, int offset) { int[] pcol = matrix.ColumnPointers; int[] irow = matrix.RowIndices; int i, iccsze; int j, jstop, jstrt; int lbegin, lvlend, lvsize; int nbr; int node; mask[root] = 0; level[offset] = root; level_num = 0; lvlend = 0; iccsze = 1; // LBEGIN is the pointer to the beginning of the current level, and // LVLEND points to the end of this level. for (;;) { lbegin = lvlend + 1; lvlend = iccsze; level_num += 1; level_row[level_num - 1] = lbegin; // Generate the next level by finding all the masked neighbors of nodes // in the current level. for (i = lbegin; i <= lvlend; i++) { node = level[offset + i - 1]; jstrt = pcol[node]; jstop = pcol[node + 1] - 1; for (j = jstrt; j <= jstop; j++) { nbr = irow[j - 1]; if (mask[nbr] != 0) { iccsze += 1; level[offset + iccsze - 1] = nbr; mask[nbr] = 0; } } } // Compute the current level width (the number of nodes encountered.) // If it is positive, generate the next level. lvsize = iccsze - lvlend; if (lvsize <= 0) { break; } } level_row[level_num] = lvlend + 1; // Reset MASK to 1 for the nodes in the level structure. for (i = 0; i < iccsze; i++) { mask[level[offset + i]] = 1; } } /// /// Computes the degrees of the nodes in the connected component. /// /// the node that defines the connected component. /// MASK[NODE_NUM], is nonzero for those nodes which are to be considered. /// Output, int DEG[NODE_NUM], contains, for each node in the connected component, its degree. /// Output, int ICCSIZE, the number of nodes in the connected component. /// Output, int LS[NODE_NUM], stores in entries 1 through ICCSIZE the nodes in the /// connected component, starting with ROOT, and proceeding by levels. /// the number of nodes. /// /// The connected component is specified by MASK and ROOT. /// Nodes for which MASK is zero are ignored. /// /// Reference: /// Alan George, Joseph Liu, /// Computer Solution of Large Sparse Positive Definite Systems, /// Prentice Hall, 1981. /// void Degree(int root, int[] mask, int[] deg, ref int iccsze, int[] ls, int offset) { int[] pcol = matrix.ColumnPointers; int[] irow = matrix.RowIndices; int i, ideg; int j, jstop, jstrt; int lbegin, lvlend; int lvsize = 1; int nbr, node; // The sign of ADJ_ROW(I) is used to indicate if node I has been considered. ls[offset] = root; pcol[root] = -pcol[root]; lvlend = 0; iccsze = 1; // If the current level width is nonzero, generate another level. while (lvsize > 0) { // LBEGIN is the pointer to the beginning of the current level, and // LVLEND points to the end of this level. lbegin = lvlend + 1; lvlend = iccsze; // Find the degrees of nodes in the current level, // and at the same time, generate the next level. for (i = lbegin; i <= lvlend; i++) { node = ls[offset + i - 1]; jstrt = -pcol[node]; jstop = Math.Abs(pcol[node + 1]) - 1; ideg = 0; for (j = jstrt; j <= jstop; j++) { nbr = irow[j - 1]; if (mask[nbr] != 0) // EDIT: [nbr - 1] { ideg = ideg + 1; if (0 <= pcol[nbr]) // EDIT: [nbr - 1] { pcol[nbr] = -pcol[nbr]; // EDIT: [nbr - 1] iccsze = iccsze + 1; ls[offset + iccsze - 1] = nbr; } } } deg[node] = ideg; } // Compute the current level width. lvsize = iccsze - lvlend; } // Reset ADJ_ROW to its correct sign and return. for (i = 0; i < iccsze; i++) { node = ls[offset + i]; pcol[node] = -pcol[node]; } } #endregion #region Tools /// /// Computes the bandwidth of a permuted adjacency matrix. /// /// The permutation. /// The inverse permutation. /// Bandwidth of the permuted adjacency matrix. /// /// The matrix is defined by the adjacency information and a permutation. /// The routine also computes the bandwidth and the size of the envelope. /// int PermBandwidth(int[] perm, int[] perm_inv) { int[] pcol = matrix.ColumnPointers; int[] irow = matrix.RowIndices; int col, i, j; int band_lo = 0; int band_hi = 0; int n = matrix.N; for (i = 0; i < n; i++) { for (j = pcol[perm[i]]; j < pcol[perm[i] + 1]; j++) { col = perm_inv[irow[j - 1]]; band_lo = Math.Max(band_lo, i - col); band_hi = Math.Max(band_hi, col - i); } } return band_lo + 1 + band_hi; } /// /// Produces the inverse of a given permutation. /// /// Number of items permuted. /// PERM[N], a permutation. /// The inverse permutation. int[] PermInverse(int[] perm) { int n = matrix.N; int[] perm_inv = new int[n]; for (int i = 0; i < n; i++) { perm_inv[perm[i]] = i; } return perm_inv; } /// /// Reverses the elements of an integer vector. /// /// number of entries in the array. /// the array to be reversed. /// /// Input: /// N = 5, /// A = ( 11, 12, 13, 14, 15 ). /// /// Output: /// A = ( 15, 14, 13, 12, 11 ). /// void ReverseVector(int[] a, int offset, int size) { int i; int j; for (i = 0; i < size / 2; i++) { j = a[offset + i]; a[offset + i] = a[offset + size - 1 - i]; a[offset + size - 1 - i] = j; } } void Shift(int[] a, bool up) { int length = a.Length; if (up) { for (int i = 0; i < length; a[i]++, i++) ; } else { for (int i = 0; i < length; a[i]--, i++) ; } } #endregion } }