GetSnapGraphMetricCalculatorPath ( SnapGraphMetrics eSnapGraphMetrics ) { AssertValid(); String sSnapGraphMetricCalculatorPath = m_sSnapGraphMetricCalculatorPath; if (sSnapGraphMetricCalculatorPath == null) { // SetSnapGraphMetricCalculatorPath() hasn't been called. Use a // default path. sSnapGraphMetricCalculatorPath = Path.Combine( Path.GetDirectoryName( System.Reflection.Assembly.GetExecutingAssembly().Location), DefaultSnapGraphMetricCalculatorFileName ); } if (eSnapGraphMetrics == SnapGraphMetrics.Cliques) { // This is an ugly, temporary kludge. // // An executable based on the Snap-09-12-28.zip release is used for // all graph metrics except cliques. It is stable and reliable and // has been used by NodeXL for years. // // That release did not calculate cliques, however. A new // executable is based on the Snap-11-12-31.zip release, which does // calculate cliques. However, the newer executable crashes in a // simple repro case when ClausetNewmanMooreClusters are // calculated. // // While this problem is worked out, two SNAP executables ship with // NodeXL. sSnapGraphMetricCalculatorPath = sSnapGraphMetricCalculatorPath.Replace( DefaultSnapGraphMetricCalculatorFileName, "SnapGraphMetricCalculatorForCliquesOnly.exe" ); } if (!File.Exists(sSnapGraphMetricCalculatorPath)) { throw new IOException( "The executable that calculates SNAP graph metrics can't be" + " found. See the comments for the GraphMetricCalculatorBase" + ".SetSnapGraphMetricCalculatorPath() method." ); } return(sSnapGraphMetricCalculatorPath); }
CalculateSnapGraphMetrics ( IGraph oGraph, SnapGraphMetrics eSnapGraphMetrics ) { AssertValid(); return(CalculateSnapGraphMetrics(oGraph, eSnapGraphMetrics, null)); }
//************************************************************************* // Constructor: OneSnapGraphMetricCalculatorBase() // /// <summary> /// Initializes a new instance of the <see /// cref="OneSnapGraphMetricCalculatorBase" /> class. /// </summary> /// /// <param name="snapGraphMetric"> /// The graph metric to calculate. Must be just one value in the <see /// cref="GraphMetricCalculatorBase.SnapGraphMetrics" /> enumeration. /// </param> /// /// <param name="graphMetricDescription"> /// A description suitable for use within the sentence "Calculating /// [GraphMetricDescription]." /// </param> /// /// <param name="expectedHeaderLineInOutputFile"> /// The expected header line in the output file created by SNAP. This is /// used for diagnostic purposes. /// </param> //************************************************************************* protected OneSnapGraphMetricCalculatorBase( SnapGraphMetrics snapGraphMetric, String graphMetricDescription, String expectedHeaderLineInOutputFile ) { m_eSnapGraphMetric = snapGraphMetric; m_sGraphMetricDescription = graphMetricDescription; m_sExpectedHeaderLineInOutputFile = expectedHeaderLineInOutputFile; AssertValid(); }
//************************************************************************* // Constructor: OneSnapGraphMetricCalculatorBase() // /// <summary> /// Initializes a new instance of the <see /// cref="OneSnapGraphMetricCalculatorBase" /> class. /// </summary> /// /// <param name="snapGraphMetric"> /// The graph metric to calculate. Must be just one value in the <see /// cref="GraphMetricCalculatorBase.SnapGraphMetrics" /> enumeration. /// </param> /// /// <param name="graphMetricDescription"> /// A description suitable for use within the sentence "Calculating /// [GraphMetricDescription]." /// </param> /// /// <param name="expectedHeaderLineInOutputFile"> /// The expected header line in the output file created by SNAP. This is /// used for diagnostic purposes. /// </param> //************************************************************************* protected OneSnapGraphMetricCalculatorBase ( SnapGraphMetrics snapGraphMetric, String graphMetricDescription, String expectedHeaderLineInOutputFile ) { m_eSnapGraphMetric = snapGraphMetric; m_sGraphMetricDescription = graphMetricDescription; m_sExpectedHeaderLineInOutputFile = expectedHeaderLineInOutputFile; AssertValid(); }
TryCallSnapGraphMetricCalculator ( SnapGraphMetrics eSnapGraphMetrics, String sArguments, out String sStandardError ) { ProcessStartInfo oProcessStartInfo = new ProcessStartInfo( GetSnapGraphMetricCalculatorPath(eSnapGraphMetrics), sArguments); oProcessStartInfo.UseShellExecute = false; oProcessStartInfo.RedirectStandardError = true; oProcessStartInfo.CreateNoWindow = true; Process oProcess = new Process(); oProcess.StartInfo = oProcessStartInfo; oProcess.Start(); sStandardError = oProcess.StandardError.ReadToEnd(); oProcess.WaitForExit(); return(sStandardError.Length == 0); }
CalculateSnapOverallMetrics ( IGraph oGraph, out Nullable <Int32> iMaximumGeodesicDistance, out Nullable <Double> dAverageGeodesicDistance, out Nullable <Double> dModularity ) { Debug.Assert(oGraph != null); AssertValid(); iMaximumGeodesicDistance = new Nullable <Int32>(); dAverageGeodesicDistance = new Nullable <Double>(); dModularity = new Nullable <Double>(); if (oGraph.Vertices.Count == 0 || oGraph.Edges.Count == 0) { return; } // Always calculate the geodesic distances. SnapGraphMetrics eSnapGraphMetrics = SnapGraphMetrics.GeodesicDistances; // Also calculate modularity if the graph has at least one group. Boolean bCalculateModularity = false; List <GroupInfo> oGroups = null; if (GroupUtil.GraphHasGroups(oGraph)) { oGroups = GroupUtil.GetGroupsWithAllVertices(oGraph, false); // SNAP can't handle isolates when calculating modularity, so // remove them. GroupUtil.RemoveIsolatesFromGroups(oGroups); if (oGroups.Count > 0) { eSnapGraphMetrics |= SnapGraphMetrics.Modularity; bCalculateModularity = true; } else { oGroups = null; } } String sOutputFilePath = CalculateSnapGraphMetrics( oGraph, eSnapGraphMetrics, oGroups); using (StreamReader oStreamReader = new StreamReader( sOutputFilePath)) { // The first line is a header. String sLine = oStreamReader.ReadLine(); #if DEBUG const String GeodesicDistancesHeader = "Maximum Geodesic Distance\tAverage Geodesic Distance"; if (bCalculateModularity) { Debug.Assert(sLine == GeodesicDistancesHeader + "\tModularity"); } else { Debug.Assert(sLine == GeodesicDistancesHeader); } #endif // The second line contains the metric values. Debug.Assert(oStreamReader.Peek() >= 0); sLine = oStreamReader.ReadLine(); String [] asFields = sLine.Split('\t'); Debug.Assert(asFields.Length == (bCalculateModularity ? 3: 2)); iMaximumGeodesicDistance = ParseSnapInt32GraphMetricValue(asFields, 0); dAverageGeodesicDistance = ParseSnapDoubleGraphMetricValue(asFields, 1); if (bCalculateModularity) { dModularity = ParseSnapDoubleGraphMetricValue(asFields, 2); } } File.Delete(sOutputFilePath); }
CalculateSnapGraphMetrics ( IGraph oGraph, SnapGraphMetrics eSnapGraphMetrics ) { AssertValid(); String sInputFilePath = null; String sOutputFilePath = null; try { // The SNAP executable expects an input text file that contains one // line per edge. The line contains a tab-separated pair of // integer vertex IDs. Create the file. sInputFilePath = Path.GetTempFileName(); using (StreamWriter oStreamWriter = new StreamWriter( sInputFilePath)) { foreach (IEdge oEdge in oGraph.Edges) { IVertex [] oVertices = oEdge.Vertices; oStreamWriter.WriteLine( "{0}\t{1}" , oVertices[0].ID, oVertices[1].ID ); } } // SNAP will overwrite this output file, which initially has zero // length: sOutputFilePath = Path.GetTempFileName(); // The arguments should look like this: // // InputFilePath IsDirected GraphMetricsToCalculate OutputFilePath String sArguments = String.Format( "\"{0}\" {1} {2} \"{3}\"" , sInputFilePath, oGraph.Directedness == GraphDirectedness.Directed ? "true" : "false", (Int32)eSnapGraphMetrics, sOutputFilePath ); String sStandardError; if (!TryCallSnapGraphMetricCalculator(sArguments, out sStandardError)) { throw new IOException( "A problem occurred while calling the executable that" + " calculates SNAP graph metrics. Details: " + sStandardError ); } Debug.Assert(File.Exists(sOutputFilePath)); } catch { // Delete the output file on error. if (sOutputFilePath != null && File.Exists(sOutputFilePath)) { File.Delete(sOutputFilePath); } throw; } finally { // Always delete the input file. if (sInputFilePath != null && File.Exists(sInputFilePath)) { File.Delete(sInputFilePath); } } return(sOutputFilePath); }
CalculateSnapGraphMetrics ( IGraph oGraph, SnapGraphMetrics eSnapGraphMetrics, List <GroupInfo> oGroups ) { AssertValid(); String sInputFilePath = null; String sGroupInputFilePath = null; String sOutputFilePath = null; try { // Create the input file(s) that SNAP expects. // // Do not use Path.GetTempFileName() here. That was used at one // time, but it threw random "[UnauthorizedAccessException]: Access // to the path is denied" exceptions when graph metrics were // calculated in simultaneous instances of the NodeXLNetworkServer // program. This might have been related to the known Windows bug // "The GetTempFileName function fails together with an access // denied error in Windows 7 or in Windows Server 2008 R2" at // http://support.microsoft.com/kb/982613. sInputFilePath = FileUtil.GetPathToUseForTemporaryFile(); CreateSnapInputFile(oGraph, sInputFilePath); if (oGroups != null) { // The SNAP executable expects a group input text file that has // the same name as the input file but with "groups" appended // to the name. sGroupInputFilePath = sInputFilePath + "groups"; CreateSnapGroupInputFile(oGroups, sGroupInputFilePath); } // SNAP will overwrite this output file, which initially has zero // length: sOutputFilePath = FileUtil.GetPathToUseForTemporaryFile(); // The arguments should look like this: // // InputFilePath IsDirected GraphMetricsToCalculate OutputFilePath String sArguments = String.Format( "\"{0}\" {1} {2} \"{3}\"" , sInputFilePath, oGraph.Directedness == GraphDirectedness.Directed ? "true" : "false", (Int32)eSnapGraphMetrics, sOutputFilePath ); String sStandardError; if (!TryCallSnapGraphMetricCalculator(eSnapGraphMetrics, sArguments, out sStandardError)) { throw new IOException( "A problem occurred while calling the executable that" + " calculates SNAP graph metrics. Details: " + sStandardError ); } Debug.Assert(File.Exists(sOutputFilePath)); } catch { // Delete the output file on error. DeleteSnapFile(sOutputFilePath); throw; } finally { // Always delete the input file(s). DeleteSnapFile(sInputFilePath); DeleteSnapFile(sGroupInputFilePath); } return(sOutputFilePath); }
GetSnapGraphMetricCalculatorPath ( SnapGraphMetrics eSnapGraphMetrics ) { AssertValid(); String sSnapGraphMetricCalculatorPath = m_sSnapGraphMetricCalculatorPath; if (sSnapGraphMetricCalculatorPath == null) { // SetSnapGraphMetricCalculatorPath() hasn't been called. Use a // default path. sSnapGraphMetricCalculatorPath = Path.Combine( Path.GetDirectoryName( System.Reflection.Assembly.GetExecutingAssembly().Location), DefaultSnapGraphMetricCalculatorFileName ); } if (eSnapGraphMetrics == SnapGraphMetrics.Cliques) { // This is an ugly, temporary kludge. // // An executable based on the Snap-09-12-28.zip release is used for // all graph metrics except cliques. It is stable and reliable and // has been used by NodeXL for years. // // That release did not calculate cliques, however. A new // executable is based on the Snap-11-12-31.zip release, which does // calculate cliques. However, the newer executable crashes in a // simple repro case when ClausetNewmanMooreClusters are // calculated. // // While this problem is worked out, two SNAP executables ship with // NodeXL. sSnapGraphMetricCalculatorPath = sSnapGraphMetricCalculatorPath.Replace( DefaultSnapGraphMetricCalculatorFileName, "SnapGraphMetricCalculatorForCliquesOnly.exe" ); } if ( !File.Exists(sSnapGraphMetricCalculatorPath) ) { throw new IOException( "The executable that calculates SNAP graph metrics can't be" + " found. See the comments for the GraphMetricCalculatorBase" + ".SetSnapGraphMetricCalculatorPath() method." ); } return (sSnapGraphMetricCalculatorPath); }
TryCallSnapGraphMetricCalculator ( SnapGraphMetrics eSnapGraphMetrics, String sArguments, out String sStandardError ) { ProcessStartInfo oProcessStartInfo = new ProcessStartInfo( GetSnapGraphMetricCalculatorPath(eSnapGraphMetrics), sArguments); oProcessStartInfo.UseShellExecute = false; oProcessStartInfo.RedirectStandardError = true; oProcessStartInfo.CreateNoWindow = true; Process oProcess = new Process(); oProcess.StartInfo = oProcessStartInfo; oProcess.Start(); sStandardError = oProcess.StandardError.ReadToEnd(); oProcess.WaitForExit(); return (sStandardError.Length == 0); }
CalculateSnapGraphMetrics ( IGraph oGraph, SnapGraphMetrics eSnapGraphMetrics, List<GroupInfo> oGroups ) { AssertValid(); String sInputFilePath = null; String sGroupInputFilePath = null; String sOutputFilePath = null; try { // Create the input file(s) that SNAP expects. // // Do not use Path.GetTempFileName() here. That was used at one // time, but it threw random "[UnauthorizedAccessException]: Access // to the path is denied" exceptions when graph metrics were // calculated in simultaneous instances of the NodeXLNetworkServer // program. This might have been related to the known Windows bug // "The GetTempFileName function fails together with an access // denied error in Windows 7 or in Windows Server 2008 R2" at // http://support.microsoft.com/kb/982613. sInputFilePath = FileUtil.GetPathToUseForTemporaryFile(); CreateSnapInputFile(oGraph, sInputFilePath); if (oGroups != null) { // The SNAP executable expects a group input text file that has // the same name as the input file but with "groups" appended // to the name. sGroupInputFilePath = sInputFilePath + "groups"; CreateSnapGroupInputFile(oGroups, sGroupInputFilePath); } // SNAP will overwrite this output file, which initially has zero // length: sOutputFilePath = FileUtil.GetPathToUseForTemporaryFile(); // The arguments should look like this: // // InputFilePath IsDirected GraphMetricsToCalculate OutputFilePath String sArguments = String.Format( "\"{0}\" {1} {2} \"{3}\"" , sInputFilePath, oGraph.Directedness == GraphDirectedness.Directed ? "true" : "false", (Int32)eSnapGraphMetrics, sOutputFilePath ); String sStandardError; if ( !TryCallSnapGraphMetricCalculator(eSnapGraphMetrics, sArguments, out sStandardError) ) { throw new IOException( "A problem occurred while calling the executable that" + " calculates SNAP graph metrics. Details: " + sStandardError ); } Debug.Assert( File.Exists(sOutputFilePath) ); } catch { // Delete the output file on error. DeleteSnapFile(sOutputFilePath); throw; } finally { // Always delete the input file(s). DeleteSnapFile(sInputFilePath); DeleteSnapFile(sGroupInputFilePath); } return (sOutputFilePath); }
CalculateSnapGraphMetrics ( IGraph oGraph, SnapGraphMetrics eSnapGraphMetrics ) { AssertValid(); return ( CalculateSnapGraphMetrics(oGraph, eSnapGraphMetrics, null) ); }
TryCalculateClustersSnap ( IGraph oGraph, SnapGraphMetrics eSnapGraphMetric, BackgroundWorker oBackgroundWorker, out ICollection<Community> oGraphMetrics ) { Debug.Assert(oGraph != null); AssertValid(); LinkedList<Community> oCommunities = new LinkedList<Community>(); oGraphMetrics = oCommunities; if ( !ReportProgressAndCheckCancellationPending( 1, 3, oBackgroundWorker) ) { return (false); } // Make it easy to find the graph's vertices by vertex ID. The key is // a vertex ID and the value is the corresponding vertex object. IVertexCollection oVertices = oGraph.Vertices; Dictionary<Int32, IVertex> oVertexIDDictionary = new Dictionary<Int32, IVertex>(oVertices.Count); foreach (IVertex oVertex in oVertices) { oVertexIDDictionary.Add(oVertex.ID, oVertex); } // Tell the SNAP graph library to calculate the clusters. String sOutputFilePath = CalculateSnapGraphMetrics(oGraph, eSnapGraphMetric); if ( !ReportProgressAndCheckCancellationPending( 2, 3, oBackgroundWorker) ) { return (false); } // The output file for cluster metrics has a header line followed by // one line for each vertex that identifies which cluster the vertex is // in. The vertices are sorted by cluster. using ( StreamReader oStreamReader = new StreamReader( sOutputFilePath) ) { String sLine = oStreamReader.ReadLine(); Debug.Assert(sLine == "Cluster ID\tVertex ID"); Int32 iLastClusterID = -1; Community oCommunity = null; while (oStreamReader.Peek() >= 0) { sLine = oStreamReader.ReadLine(); String [] asFields = sLine.Split('\t'); Debug.Assert(asFields.Length == 2); Int32 iClusterID = ParseSnapInt32GraphMetricValue(asFields, 0); Int32 iVertexID = ParseSnapInt32GraphMetricValue(asFields, 1); if (iClusterID != iLastClusterID) { oCommunity = new Community(); oCommunity.ID = iClusterID; oCommunities.AddLast(oCommunity); iLastClusterID = iClusterID; } Debug.Assert(oCommunity != null); oCommunity.Vertices.Add( oVertexIDDictionary[iVertexID] ); } } File.Delete(sOutputFilePath); return (true); }
TryCalculateClustersSnap ( IGraph oGraph, SnapGraphMetrics eSnapGraphMetric, BackgroundWorker oBackgroundWorker, out ICollection <Community> oGraphMetrics ) { Debug.Assert(oGraph != null); AssertValid(); LinkedList <Community> oCommunities = new LinkedList <Community>(); oGraphMetrics = oCommunities; if (!ReportProgressAndCheckCancellationPending( 1, 3, oBackgroundWorker)) { return(false); } // Make it easy to find the graph's vertices by vertex ID. The key is // a vertex ID and the value is the corresponding vertex object. IVertexCollection oVertices = oGraph.Vertices; Dictionary <Int32, IVertex> oVertexIDDictionary = new Dictionary <Int32, IVertex>(oVertices.Count); foreach (IVertex oVertex in oVertices) { oVertexIDDictionary.Add(oVertex.ID, oVertex); } // Tell the SNAP graph library to calculate the clusters. String sOutputFilePath = CalculateSnapGraphMetrics(oGraph, eSnapGraphMetric); if (!ReportProgressAndCheckCancellationPending( 2, 3, oBackgroundWorker)) { return(false); } // The output file for cluster metrics has a header line followed by // one line for each vertex that identifies which cluster the vertex is // in. The vertices are sorted by cluster. using (StreamReader oStreamReader = new StreamReader( sOutputFilePath)) { String sLine = oStreamReader.ReadLine(); Debug.Assert(sLine == "Cluster ID\tVertex ID"); Int32 iLastClusterID = -1; Community oCommunity = null; while (oStreamReader.Peek() >= 0) { sLine = oStreamReader.ReadLine(); String [] asFields = sLine.Split('\t'); Debug.Assert(asFields.Length == 2); Int32 iClusterID = ParseSnapInt32GraphMetricValue(asFields, 0); Int32 iVertexID = ParseSnapInt32GraphMetricValue(asFields, 1); if (iClusterID != iLastClusterID) { oCommunity = new Community(); oCommunity.ID = iClusterID; oCommunities.AddLast(oCommunity); iLastClusterID = iClusterID; } Debug.Assert(oCommunity != null); oCommunity.Vertices.Add(oVertexIDDictionary[iVertexID]); } } File.Delete(sOutputFilePath); return(true); }
//************************************************************************* // Method: CalculateSnapGraphMetrics() // /// <summary> /// Calculates one or more graph metrics using the SNAP executable. /// </summary> /// /// <param name="oGraph"> /// The graph to calculate metrics for. /// </param> /// /// <param name="eSnapGraphMetrics"> /// One or more graph metrics to calculate. /// </param> /// /// <returns> /// Full path to a temporary output file containing the calculated metrics. /// The caller must delete this file after it is done with it. /// </returns> /// /// <remarks> /// If an error occurs while calling the executable, an IOException is /// thrown. /// </remarks> //************************************************************************* protected String CalculateSnapGraphMetrics( IGraph oGraph, SnapGraphMetrics eSnapGraphMetrics ) { AssertValid(); String sInputFilePath = null; String sOutputFilePath = null; try { // The SNAP executable expects an input text file that contains one // line per edge. The line contains a tab-separated pair of // integer vertex IDs. Create the file. sInputFilePath = Path.GetTempFileName(); using ( StreamWriter oStreamWriter = new StreamWriter( sInputFilePath) ) { foreach (IEdge oEdge in oGraph.Edges) { IVertex [] oVertices = oEdge.Vertices; oStreamWriter.WriteLine( "{0}\t{1}" , oVertices[0].ID, oVertices[1].ID ); } } // SNAP will overwrite this output file, which initially has zero // length: sOutputFilePath = Path.GetTempFileName(); // The arguments should look like this: // // InputFilePath IsDirected GraphMetricsToCalculate OutputFilePath String sArguments = String.Format( "\"{0}\" {1} {2} \"{3}\"" , sInputFilePath, oGraph.Directedness == GraphDirectedness.Directed ? "true" : "false", (Int32)eSnapGraphMetrics, sOutputFilePath ); String sStandardError; if ( !TryCallSnapGraphMetricCalculator(sArguments, out sStandardError) ) { throw new IOException( "A problem occurred while calling the executable that" + " calculates SNAP graph metrics. Details: " + sStandardError ); } Debug.Assert( File.Exists(sOutputFilePath) ); } catch { // Delete the output file on error. if ( sOutputFilePath != null && File.Exists(sOutputFilePath) ) { File.Delete(sOutputFilePath); } throw; } finally { // Always delete the input file. if ( sInputFilePath != null && File.Exists(sInputFilePath) ) { File.Delete(sInputFilePath); } } return (sOutputFilePath); }