// -----------------------------------------------------------------------
//
// 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
}
}