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);
    }
Example #14
0
        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);
        }