haskell-igraph-0.8.5: src/IGraph/Algorithms/Structure.chs
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE DataKinds #-}
module IGraph.Algorithms.Structure
( -- * Shortest Path Related Functions
shortestPath
, averagePathLength
, diameter
, eccentricity
, radius
-- * Graph Components
, inducedSubgraph
, isConnected
, isStronglyConnected
, decompose
, articulationPoints
, bridges
-- * Topological Sorting, Directed Acyclic Graphs
, isDag
, topSort
, topSortUnsafe
-- * Other Operations
, density
, reciprocity
-- * Auxiliary types
, Neimode(IgraphOut,IgraphIn,IgraphAll) -- not IgraphTotal
) where
import Control.Monad
import Data.Serialize (Serialize)
import System.IO.Unsafe (unsafePerformIO)
import Data.Singletons (SingI)
import Foreign
import Foreign.C.Types
import IGraph
import IGraph.Internal.C2HS
{#import IGraph.Internal #}
{#import IGraph.Internal.Constants #}
#include "haskell_igraph.h"
-- Calculates and returns a single unweighted shortest path from a given vertex
-- to another one. If there are more than one shortest paths between the two
-- vertices, then an arbitrary one is returned.
shortestPath :: Serialize e
=> Graph d v e
-> Node -- ^ The id of the source vertex.
-> Node -- ^ The id of the target vertex.
-> Maybe (e -> Double) -- ^ A function to retrieve edge weights. If provied,
-- the Dijkstra's algorithm will be used.
-> [Node]
shortestPath gr s t getEdgeW = unsafePerformIO $ allocaVector $ \path -> do
case getEdgeW of
Nothing -> igraphGetShortestPath (_graph gr) path nullPtr s t IgraphOut
Just f -> withList (map (f . snd) $ labEdges gr) $ \ws ->
igraphGetShortestPathDijkstra (_graph gr) path nullPtr s t ws IgraphOut
map truncate <$> toList path
{-# INLINE shortestPath #-}
{#fun igraph_get_shortest_path as ^
{ `IGraph'
, castPtr `Ptr Vector'
, castPtr `Ptr Vector'
, `Int'
, `Int'
, `Neimode'
} -> `CInt' void- #}
{#fun igraph_get_shortest_path_dijkstra as ^
{ `IGraph'
, castPtr `Ptr Vector'
, castPtr `Ptr Vector'
, `Int'
, `Int'
, castPtr `Ptr Vector'
, `Neimode'
} -> `CInt' void- #}
-- | Calculates the average shortest path length between all vertex pairs.
averagePathLength :: SingI d
=> Graph d v e
-> Bool -- ^ if unconnected,
-- include only connected pairs (True)
-- or return number if vertices (False)
-> Double
averagePathLength graph unconn =
cFloatConv $ igraphAveragePathLength (_graph graph) (isDirected graph) unconn
{-# INLINE igraphAveragePathLength #-}
{#fun pure igraph_average_path_length as ^
{ `IGraph'
, alloca- `CDouble' peek*
, `Bool'
, `Bool'
} -> `CInt' void- #}
-- | Calculates the diameter of a graph (longest geodesic).
diameter :: SingI d
=> Graph d v e
-> Bool -- ^ if unconnected,
-- return largest component diameter (True)
-- or number of vertices (False)
-> (Int, [Node])
diameter graph unconn = unsafePerformIO $
alloca $ \pres ->
allocaVector $ \path -> do
igraphDiameter (_graph graph) pres nullPtr nullPtr path (isDirected graph) unconn
liftM2 (,) (peekIntConv pres) (toNodes path)
{-# INLINE igraphDiameter #-}
{#fun igraph_diameter as ^
{ `IGraph'
, castPtr `Ptr CInt'
, castPtr `Ptr CInt'
, castPtr `Ptr CInt'
, castPtr `Ptr Vector'
, `Bool'
, `Bool'
} -> `CInt' void- #}
-- | Eccentricity of some vertices.
eccentricity :: Graph d v e
-> Neimode -- ^ 'IgraphOut' to follow edges' direction,
-- 'IgraphIn' to reverse it, 'IgraphAll' to ignore
-> [Node] -- ^ vertices for which to calculate eccentricity
-> [Double]
eccentricity graph mode vids = unsafePerformIO $
allocaVector $ \res ->
withVerticesList vids $ \vs -> do
igraphEccentricity (_graph graph) res vs mode
toList res
{-# INLINE igraphEccentricity #-}
{#fun igraph_eccentricity as ^
{ `IGraph'
, castPtr `Ptr Vector'
, castPtr %`Ptr VertexSelector'
, `Neimode'
} -> `CInt' void- #}
-- | Radius of a graph.
radius :: Graph d v e
-> Neimode -- ^ 'IgraphOut' to follow edges' direction,
-- 'IgraphIn' to reverse it, 'IgraphAll' to ignore
-> Double
radius graph mode = cFloatConv $ igraphRadius (_graph graph) mode
{-# INLINE igraphRadius #-}
{#fun pure igraph_radius as ^
{ `IGraph'
, alloca- `CDouble' peek*
, `Neimode'
} -> `CInt' void- #}
-- | Creates a subgraph induced by the specified vertices. This function collects
-- the specified vertices and all edges between them to a new graph.
inducedSubgraph :: (Ord v, Serialize v)
=> Graph d v e
-> [Node]
-> Graph d v e
inducedSubgraph gr nds = unsafePerformIO $ withVerticesList nds $ \vs ->
igraphInducedSubgraph (_graph gr) vs IgraphSubgraphCreateFromScratch >>=
(\g -> return $ Graph g $ mkLabelToId g)
{-# INLINE inducedSubgraph #-}
{#fun igraph_induced_subgraph as ^
{ `IGraph'
, allocaIGraph- `IGraph' addIGraphFinalizer*
, castPtr %`Ptr VertexSelector'
, `SubgraphImplementation'
} -> `CInt' void- #}
-- | Decides whether the graph is weakly connected.
isConnected :: Graph d v e -> Bool
isConnected gr = igraphIsConnected (_graph gr) IgraphWeak
{-# INLINE isConnected #-}
isStronglyConnected :: Graph 'D v e -> Bool
isStronglyConnected gr = igraphIsConnected (_graph gr) IgraphStrong
{-# INLINE isStronglyConnected #-}
{#fun pure igraph_is_connected as ^
{ `IGraph'
, alloca- `Bool' peekBool*
, `Connectedness'
} -> `CInt' void- #}
-- | Decompose a graph into connected components.
decompose :: (Ord v, Serialize v)
=> Graph d v e -> [Graph d v e]
decompose gr = unsafePerformIO $ allocaVectorPtr $ \ptr -> do
igraphDecompose (_graph gr) ptr IgraphWeak (-1) 1
n <- igraphVectorPtrSize ptr
forM [0..n-1] $ \i -> do
p <- igraphVectorPtrE ptr i
addIGraphFinalizer (castPtr p) >>= (\g -> return $ Graph g $ mkLabelToId g)
{-# INLINE decompose #-}
{#fun igraph_decompose as ^
{ `IGraph'
, castPtr `Ptr VectorPtr'
, `Connectedness'
, `Int'
, `Int'
} -> `CInt' void- #}
-- | Find the articulation points in a graph.
articulationPoints :: Graph d v e -> [Node]
articulationPoints gr = unsafePerformIO $ allocaVector $ \res -> do
igraphArticulationPoints (_graph gr) res
toNodes res
{-#INLINE igraphArticulationPoints #-}
{#fun igraph_articulation_points as ^
{ `IGraph'
, castPtr `Ptr Vector'
} -> `CInt' void- #}
-- ^ Find all bridges in a graph.
bridges :: Graph d v e -> [Edge]
bridges gr = unsafePerformIO $ allocaVector $ \res -> do
igraphBridges (_graph gr) res
map (getEdgeByEid gr) <$> toNodes res
{-# INLINE igraphBridges #-}
{#fun igraph_bridges as ^
{ `IGraph'
, castPtr `Ptr Vector'
} -> `CInt' void- #}
-- | Checks whether a graph is a directed acyclic graph (DAG) or not.
isDag :: Graph d v e -> Bool
isDag = igraphIsDag . _graph
{#fun pure igraph_is_dag as ^
{ `IGraph'
, alloca- `Bool' peekBool*
} -> `CInt' void- #}
{-# INLINE isDag #-}
-- | Calculate a possible topological sorting of the graph. Raise error if the
-- graph is not acyclic.
topSort :: Graph d v e -> [Node]
topSort gr | isDag gr = topSortUnsafe gr
| otherwise = error "the graph is not acyclic"
{-# INLINE topSort #-}
-- | Calculate a possible topological sorting of the graph. If the graph is not
-- acyclic (it has at least one cycle), a partial topological sort is returned.
topSortUnsafe :: Graph d v e -> [Node]
topSortUnsafe gr = unsafePerformIO $ allocaVectorN n $ \res -> do
igraphTopologicalSorting (_graph gr) res IgraphOut
map truncate <$> toList res
where
n = nNodes gr
{-# INLINE topSortUnsafe #-}
{#fun igraph_topological_sorting as ^
{ `IGraph'
, castPtr `Ptr Vector'
, `Neimode'
} -> `CInt' void- #}
-- | Calculate the density of a graph.
density :: Graph d v e
-> Bool -- ^ whether to include loops
-> Double -- ^ the ratio of edges to possible edges
density gr loops = unsafePerformIO $ alloca $ \res -> do
igraphDensity (_graph gr) res loops
peek res
{-# INLINE igraphDensity #-}
{#fun igraph_density as ^
{ `IGraph'
, castPtr `Ptr Double'
, `Bool'
} -> `CInt' void -#}
-- | Calculates the reciprocity of a directed graph.
reciprocity :: Graph d v e
-> Bool -- ^ whether to ignore loop edges
-> Double -- ^ the proportion of mutual connections
reciprocity gr ignore_loops = unsafePerformIO $ alloca $ \res -> do
igraphReciprocity (_graph gr) res ignore_loops IgraphReciprocityDefault
peek res
{#fun igraph_reciprocity as ^
{ `IGraph'
, castPtr `Ptr Double'
, `Bool'
, `Reciprocity'
} -> `CInt' void -#}