packages feed

haskell-igraph-0.8.5: src/IGraph/Algorithms/Community.chs

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DataKinds #-}
module IGraph.Algorithms.Community
    ( findCommunity
    , CommunityMethod(..)
    , leadingEigenvector
    , spinglass
    , leiden
    , modularity
    ) where

import           Data.Function             (on)
import           Data.List (sortBy, groupBy)
import Data.List.Ordered (nubSortBy)
import           Data.Ord (comparing)
import           System.IO.Unsafe          (unsafePerformIO)
import           Data.Serialize            (Serialize)

import           Foreign
import           Foreign.C.Types

import           IGraph
import           IGraph.Random
import IGraph.Internal.C2HS
{#import IGraph.Internal #}
{#import IGraph.Internal.Constants #}

#include "haskell_igraph.h"

-- | Detecting community structure.
findCommunity :: (Serialize v, Serialize e)
              => Graph 'U v e
              -> Maybe (Node -> v -> Double)  -- ^ Function to assign node weights
              -> Maybe (e -> Double)  -- ^ Function to assign edge weights
              -> CommunityMethod  -- ^ Community finding algorithms
              -> Gen
              -> IO [[Int]]
findCommunity gr getNodeW getEdgeW method _ = allocaVector $ \result ->
    withListMaybe ew $ \ew' -> do
        case method of
            LeadingEigenvector n -> allocaArpackOpt $ \arpack ->
                igraphCommunityLeadingEigenvector (_graph gr) ew' nullPtr result
                                                  n arpack nullPtr False
                                                  nullPtr nullPtr nullPtr
                                                  nullFunPtr nullPtr
            Spinglass{..} -> igraphCommunitySpinglass (_graph gr) ew' nullPtr nullPtr result
                                     nullPtr _nSpins False _startTemp
                                     _stopTemp _coolFact
                                     IgraphSpincommUpdateConfig _gamma
                                     IgraphSpincommImpOrig 1.0
            Leiden{..} -> do
                _ <- withListMaybe nw $ \nw' -> igraphCommunityLeiden
                    (_graph gr) ew' nw' _resolution _beta False result nullPtr
                return ()
        fmap ( map (fst . unzip) . groupBy ((==) `on` snd)
              . sortBy (comparing snd) . zip [0..] ) $ toList result
  where
    ew = case getEdgeW of
        Nothing -> Nothing
        Just f -> Just $ map (f . snd) $ labEdges gr
    nw = case getNodeW of
        Nothing -> Nothing
        Just f -> Just $ map (uncurry f) $ labNodes gr

data CommunityMethod =
      LeadingEigenvector
        { _nIter     :: Int  -- ^ number of iterations, default is 10000
        }
    | Spinglass
        { _nSpins    :: Int  -- ^ number of spins, default is 25
        , _startTemp :: Double  -- ^ the temperature at the start
        , _stopTemp  :: Double  -- ^ the algorithm stops at this temperature
        , _coolFact  :: Double  -- ^ the cooling factor for the simulated annealing
        , _gamma     :: Double  -- ^ the gamma parameter of the algorithm.
        }
    | Leiden
        { _resolution :: Double
        , _beta :: Double
        }

-- | Default parameters for the leading eigenvector algorithm.
leadingEigenvector :: CommunityMethod
leadingEigenvector = LeadingEigenvector 10000

-- | Default parameters for the spin-glass algorithm.
spinglass :: CommunityMethod
spinglass = Spinglass
    { _nSpins = 25
    , _startTemp = 1.0
    , _stopTemp = 0.01
    , _coolFact = 0.99
    , _gamma = 1.0 }

-- | Default parameters for the leiden algorithm.
-- 1 / 2m sum_ij (A_ij - gamma n_i n_j)d(s_i, s_j), where
-- m is the total edge weight,
-- A_ij is the weight of edge (i, j),
-- gamma is the so-called resolution parameter,
-- n_i is the node weight of node i,
-- s_i is the cluster of node i and
-- d(x, y) = 1 if and only if x = y and 0 otherwise.
-- By setting n_i = k_i, the degree of node i, and dividing gamma by 2m,
-- you effectively obtain an expression for modularity.
-- Hence, the standard modularity will be optimized when you supply the degrees
-- as node_weights and by supplying as a resolution parameter 1.0/(2*m), with m the number of edges.
--
-- RBConfigurationVertexPartition: supplying the degrees as node weights, and
-- a resolution parameter 1.0/(2*m), with m the number of edges.
-- CPM: 
leiden :: CommunityMethod
leiden = Leiden
    { _resolution = 1
    , _beta = 0.01 }

{#fun igraph_community_spinglass as ^
    { `IGraph'
    , castPtr `Ptr Vector'
    , id `Ptr CDouble'
    , id `Ptr CDouble'
    , castPtr `Ptr Vector'
    , castPtr `Ptr Vector'
    , `Int'
    , `Bool'
    , `Double'
    , `Double'
    , `Double'
    , `SpincommUpdate'
    , `Double'
    , `SpinglassImplementation'
    , `Double'
    } -> `CInt' void- #}

{#fun igraph_community_leading_eigenvector as ^
    { `IGraph'
    , castPtr `Ptr Vector'
    , castPtr `Ptr Matrix'
    , castPtr `Ptr Vector'
    , `Int'
    , castPtr `Ptr ArpackOpt'
    , id `Ptr CDouble'
    , `Bool'
    , castPtr `Ptr Vector'
    , castPtr `Ptr VectorPtr'
    , castPtr `Ptr Vector'
    , id `T'
    , id `Ptr ()'
    } -> `CInt' void- #}

{#fun igraph_community_leiden as ^
    { `IGraph'
    , castPtr `Ptr Vector'
    , castPtr `Ptr Vector'
    , `Double'
    , `Double'
    , `Bool'
    , castPtr `Ptr Vector'
    , alloca- `Int' peekIntConv*
    , id `Ptr CDouble'
    } -> `CInt' void- #}

type T = FunPtr ( Ptr ()
                -> CLong
                -> CDouble
                -> Ptr ()
                -> FunPtr (Ptr CDouble -> Ptr CDouble -> CInt -> Ptr () -> IO CInt)
                -> Ptr ()
                -> Ptr ()
                -> IO CInt)

-- | Calculate the modularity of a graph with respect to some vertex types.
modularity :: Serialize e
           => Graph d v e
           -> Maybe (e -> Double)  -- ^ Function to assign edge weights
           -> [[Int]]   -- ^ Communities.
           -> Double
modularity gr getEdgeW clusters
    | length nds /= length (concat clusters) = error "Duplicated nodes"
    | nds /= nodes gr = error "Some nodes were not given community assignments"
    | otherwise = unsafePerformIO $ withList membership $ \membership' ->
        withListMaybe ws (igraphModularity (_graph gr) membership')
  where
    (membership, nds) = unzip $ nubSortBy (comparing snd) $ concat $
        zipWith f [0 :: Int ..] clusters
      where
        f i xs = zip (repeat i) xs
    ws = case getEdgeW of
        Nothing -> Nothing
        Just f -> Just $ map (f . snd) $ labEdges gr
{#fun igraph_modularity as ^
    { `IGraph'
    , castPtr `Ptr Vector'
	, alloca- `Double' peekFloatConv*
	, castPtr `Ptr Vector'
    } -> `CInt' void- #}