toysolver-0.6.0: src/ToySolver/Graph/ShortestPath.hs
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
--------------------------------------------------------------------------
-- |
-- Module : ToySolver.Graph.ShortestPath
-- Copyright : (c) Masahiro Sakai 2016-2017
-- License : BSD-style
--
-- Maintainer : masahiro.sakai@gmail.com
-- Stability : provisional
-- Portability : non-portable
--
-- This module provides functions for shotest path computation.
--
-- Reference:
--
-- * Friedrich Eisenbrand. “Linear and Discrete Optimization”.
-- <https://www.coursera.org/course/linearopt>
--
--------------------------------------------------------------------------
module ToySolver.Graph.ShortestPath
(
-- * Graph data types
Graph
, Edge
, OutEdge
, InEdge
-- * Fold data type
, Fold (..)
, monoid'
, monoid
, unit
, pair
, path
, firstOutEdge
, lastInEdge
, cost
-- * Path data types
, Path (..)
, pathFrom
, pathTo
, pathCost
, pathEmpty
, pathAppend
, pathEdges
, pathEdgesBackward
, pathEdgesSeq
, pathVertexes
, pathVertexesBackward
, pathVertexesSeq
, pathFold
, pathMin
-- * Shortest-path algorithms
, bellmanFord
, dijkstra
, floydWarshall
-- * Utility functions
, bellmanFordDetectNegativeCycle
) where
import Control.Monad
import Control.Monad.ST
import Control.Monad.Trans
import Control.Monad.Trans.Except
import Data.Hashable
import qualified Data.HashTable.Class as H
import qualified Data.HashTable.ST.Cuckoo as C
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import qualified Data.Heap as Heap -- http://hackage.haskell.org/package/heaps
import Data.List (foldl')
import Data.Monoid
import Data.Ord
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Data.STRef
-- ------------------------------------------------------------------------
-- | Graph represented as a map from vertexes to their outgoing edges
type Graph vertex cost label = HashMap vertex [OutEdge vertex cost label]
-- | Edge data type
type Edge vertex cost label = (vertex, vertex, cost, label)
-- | Outgoing edge data type (source vertex is implicit)
type OutEdge vertex cost label = (vertex, cost, label)
-- | Incoming edge data type (target vertex is implicit)
type InEdge vertex cost label = (vertex, cost, label)
-- | path data type.
data Path vertex cost label
= Empty vertex
-- ^ empty path
| Singleton (Edge vertex cost label)
-- ^ path with single edge
| Append (Path vertex cost label) (Path vertex cost label) !cost
-- ^ concatenation of two paths
deriving (Eq, Show)
-- | Source vertex
pathFrom :: Path vertex cost label -> vertex
pathFrom (Empty v) = v
pathFrom (Singleton (from,_,_,_)) = from
pathFrom (Append p1 _ _) = pathFrom p1
-- | Target vertex
pathTo :: Path vertex cost label -> vertex
pathTo (Empty v) = v
pathTo (Singleton (_,to,_,_)) = to
pathTo (Append _ p2 _) = pathTo p2
-- | Cost of a path
pathCost :: Num cost => Path vertex cost label -> cost
pathCost (Empty _) = 0
pathCost (Singleton (_,_,c,_)) = c
pathCost (Append _ _ c) = c
-- | Empty path
pathEmpty :: vertex -> Path vertex cost label
pathEmpty = Empty
-- | Concatenation of two paths
pathAppend :: (Eq vertex, Num cost) => Path vertex cost label -> Path vertex cost label -> Path vertex cost label
pathAppend p1 p2
| pathTo p1 /= pathFrom p2 = error "ToySolver.Graph.ShortestPath.pathAppend: pathTo/pathFrom mismatch"
| otherwise =
case (p1, p2) of
(Empty _, _) -> p2
(_, Empty _) -> p1
_ -> Append p1 p2 (pathCost p1 + pathCost p2)
-- | Edges of a path
pathEdges :: Path vertex cost label -> [Edge vertex cost label]
pathEdges p = f p []
where
f (Empty _) xs = xs
f (Singleton e) xs = e : xs
f (Append p1 p2 _) xs = f p1 (f p2 xs)
-- | Edges of a path, but in the reverse order
pathEdgesBackward :: Path vertex cost label -> [Edge vertex cost label]
pathEdgesBackward p = f p []
where
f (Empty _) xs = xs
f (Singleton e) xs = e : xs
f (Append p1 p2 _) xs = f p2 (f p1 xs)
-- | Edges of a path, but as `Seq`
pathEdgesSeq :: Path vertex cost label -> Seq (Edge vertex cost label)
pathEdgesSeq (Empty _) = Seq.empty
pathEdgesSeq (Singleton e) = Seq.singleton e
pathEdgesSeq (Append p1 p2 _) = pathEdgesSeq p1 <> pathEdgesSeq p2
-- | Vertexes of a path
pathVertexes :: Path vertex cost label -> [vertex]
pathVertexes p = pathFrom p : f p []
where
f (Empty _) xs = xs
f (Singleton (_,v2,_,_)) xs = v2 : xs
f (Append p1 p2 _) xs = f p1 (f p2 xs)
-- | Vertexes of a path, but in the reverse order
pathVertexesBackward :: Path vertex cost label -> [vertex]
pathVertexesBackward p = pathTo p : f p []
where
f (Empty _) xs = xs
f (Singleton (v1,_,_,_)) xs = v1 : xs
f (Append p1 p2 _) xs = f p2 (f p1 xs)
-- | Vertex of a path, but as `Seq`
pathVertexesSeq :: Path vertex cost label -> Seq vertex
pathVertexesSeq p = f True p
where
f True (Empty v) = Seq.singleton v
f False (Empty _) = mempty
f True (Singleton (v1,v2,_,_)) = Seq.fromList [v1, v2]
f False (Singleton (v1,_,_,_)) = Seq.singleton v1
f b (Append p1 p2 _) = f False p1 <> f b p2
pathMin :: (Num cost, Ord cost) => Path vertex cost label -> Path vertex cost label -> Path vertex cost label
pathMin p1 p2
| pathCost p1 <= pathCost p2 = p1
| otherwise = p2
-- | Fold a path using a given 'Fold` operation.
pathFold :: Fold vertex cost label a -> Path vertex cost label -> a
pathFold (Fold fV fE fC fD) p = fD (h p)
where
h (Empty v) = fV v
h (Singleton e) = fE e
h (Append p1 p2 _) = fC (h p1) (h p2)
-- ------------------------------------------------------------------------
-- | Strict pair type
data Pair a b = Pair !a !b
-- ------------------------------------------------------------------------
-- | Operations for folding edge information along with a path into a @r@ value.
--
-- @Fold vertex cost label r@ consists of three operations
--
-- * @vertex -> a@ corresponds to an empty path,
--
-- * @Edge vertex cost label -> a@ corresponds to a single edge,
--
-- * @a -> a -> a@ corresponds to path concatenation,
--
-- and @a -> r@ to finish the computation.
data Fold vertex cost label r
= forall a. Fold (vertex -> a) (Edge vertex cost label -> a) (a -> a -> a) (a -> r)
instance Functor (Fold vertex cost label) where
{-# INLINE fmap #-}
fmap f (Fold fV fE fC fD) = Fold fV fE fC (f . fD)
instance Applicative (Fold vertex cost label) where
{-# INLINE pure #-}
pure a = Fold (\_ -> ()) (\_ -> ()) (\_ _ -> ()) (const a)
{-# INLINE (<*>) #-}
Fold fV1 fE1 fC1 fD1 <*> Fold fV2 fE2 fC2 fD2 =
Fold (\v -> Pair (fV1 v) (fV2 v))
(\e -> Pair (fE1 e) (fE2 e))
(\(Pair a1 b1) (Pair a2 b2) -> Pair (fC1 a1 a2) (fC2 b1 b2))
(\(Pair a b) -> fD1 a (fD2 b))
-- | Project `Edge` into a monoid value and fold using monoidal operation.
monoid' :: Monoid m => (Edge vertex cost label -> m) -> Fold vertex cost label m
monoid' f = Fold (\_ -> mempty) f mappend id
-- | Similar to 'monoid'' but a /label/ is directly used as a monoid value.
monoid :: Monoid m => Fold vertex cost m m
monoid = monoid' (\(_,_,_,m) -> m)
-- | Ignore contents and return a unit value.
unit :: Fold vertex cost label ()
unit = monoid' (\_ -> ())
-- | Pairs two `Fold` into one that produce a tuple.
pair :: Fold vertex cost label a -> Fold vertex cost label b -> Fold vertex cost label (a,b)
pair (Fold fV1 fE1 fC1 fD1) (Fold fV2 fE2 fC2 fD2) =
Fold (\v -> Pair (fV1 v) (fV2 v))
(\e -> Pair (fE1 e) (fE2 e))
(\(Pair a1 b1) (Pair a2 b2) -> Pair (fC1 a1 a2) (fC2 b1 b2))
(\(Pair a b) -> (fD1 a, fD2 b))
-- | Construct a `Path` value.
path :: (Eq vertex, Num cost) => Fold vertex cost label (Path vertex cost label)
path = Fold pathEmpty Singleton pathAppend id
-- | Compute cost of a path.
cost :: Num cost => Fold vertex cost label cost
cost = Fold (\_ -> 0) (\(_,_,c,_) -> c) (+) id
-- | Get the first `OutEdge` of a path.
firstOutEdge :: Fold vertex cost label (First (OutEdge vertex cost label))
firstOutEdge = monoid' (\(_,v,c,l) -> First (Just (v,c,l)))
-- | Get the last `InEdge` of a path.
-- This is useful for constructing a /parent/ map of a spanning tree.
lastInEdge :: Fold vertex cost label (Last (InEdge vertex cost label))
lastInEdge = monoid' (\(v,_,c,l) -> Last (Just (v,c,l)))
-- ------------------------------------------------------------------------
-- | Bellman-Ford algorithm for finding shortest paths from source vertexes
-- to all of the other vertices in a weighted graph with negative weight
-- edges allowed.
--
-- It compute shortest-paths from given source vertexes, and folds edge
-- information along shortest paths using a given 'Fold' operation.
bellmanFord
:: (Eq vertex, Hashable vertex, Real cost)
=> Fold vertex cost label a
-- ^ Operation used to fold shotest paths
-> Graph vertex cost label
-> [vertex]
-- ^ List of source vertexes @vs@
-> HashMap vertex (cost, a)
bellmanFord (Fold fV fE fC fD) g ss = runST $ do
let n = HashMap.size g
d <- C.newSized n
forM_ ss $ \s -> H.insert d s (Pair 0 (fV s))
updatedRef <- newSTRef (HashSet.fromList ss)
_ <- runExceptT $ replicateM_ n $ do
us <- lift $ readSTRef updatedRef
when (HashSet.null us) $ throwE ()
lift $ do
writeSTRef updatedRef HashSet.empty
forM_ (HashSet.toList us) $ \u -> do
-- modifySTRef' updatedRef (HashSet.delete u) -- possible optimization
Just (Pair du a) <- H.lookup d u
forM_ (HashMap.lookupDefault [] u g) $ \(v, c, l) -> do
m <- H.lookup d v
case m of
Just (Pair c0 _) | c0 <= du + c -> return ()
_ -> do
H.insert d v (Pair (du + c) (a `fC` fE (u,v,c,l)))
modifySTRef' updatedRef (HashSet.insert v)
liftM (fmap (\(Pair c x) -> (c, fD x))) $ freezeHashTable d
freezeHashTable :: (H.HashTable h, Hashable k, Eq k) => h s k v -> ST s (HashMap k v)
freezeHashTable h = H.foldM (\m (k,v) -> return $! HashMap.insert k v m) HashMap.empty h
-- | Utility function for detecting a negative cycle.
bellmanFordDetectNegativeCycle
:: forall vertex cost label a. (Eq vertex, Hashable vertex, Real cost)
=> Fold vertex cost label a
-- ^ Operation used to fold a cycle
-> Graph vertex cost label
-> HashMap vertex (cost, Last (InEdge vertex cost label))
-- ^ Result of @'bellmanFord' 'lastInEdge' vs@
-> Maybe a
bellmanFordDetectNegativeCycle (Fold fV fE fC fD) g d = either (Just . fD) (const Nothing) $ do
forM_ (HashMap.toList d) $ \(u,(du,_)) ->
forM_ (HashMap.lookupDefault [] u g) $ \(v, c, l) -> do
let (dv,_) = d HashMap.! v
when (du + c < dv) $ do
-- a negative cycle is detected
let d' = HashMap.insert v (du + c, Last (Just (u, c, l))) d
parent u = do
case HashMap.lookup u d' of
Just (_, Last (Just (v,_,_))) -> v
_ -> undefined
u0 = go (parent (parent v)) (parent v)
where
go hare tortoise
| hare == tortoise = hare
| otherwise = go (parent (parent hare)) (parent tortoise)
let go u result = do
let Just (_, Last (Just (v,c,l))) = HashMap.lookup u d'
if v == u0 then
fC (fE (v,u,c,l)) result
else
go v (fC (fE (v,u,c,l)) result)
Left $ go u0 (fV u0)
-- ------------------------------------------------------------------------
-- | Dijkstra's algorithm for finding shortest paths from source vertexes
-- to all of the other vertices in a weighted graph with non-negative edge
-- weight.
--
-- It compute shortest-paths from given source vertexes, and folds edge
-- information along shortest paths using a given 'Fold' operation.
dijkstra
:: forall vertex cost label a. (Eq vertex, Hashable vertex, Real cost)
=> Fold vertex cost label a
-- ^ Operation used to fold shotest paths
-> Graph vertex cost label
-> [vertex]
-- ^ List of source vertexes
-> HashMap vertex (cost, a)
dijkstra (Fold fV fE fC (fD :: x -> a)) g ss =
fmap (\(Pair c x) -> (c, fD x)) $
loop (Heap.fromList [Heap.Entry 0 (Pair s (fV s)) | s <- ss]) HashMap.empty
where
loop
:: Heap.Heap (Heap.Entry cost (Pair vertex x))
-> HashMap vertex (Pair cost x)
-> HashMap vertex (Pair cost x)
loop q visited =
case Heap.viewMin q of
Nothing -> visited
Just (Heap.Entry c (Pair v a), q')
| v `HashMap.member` visited -> loop q' visited
| otherwise ->
let q2 = Heap.fromList
[ Heap.Entry (c+c') (Pair ch (a `fC` fE (v,ch,c',l)))
| (ch,c',l) <- HashMap.lookupDefault [] v g
, not (ch `HashMap.member` visited)
]
in loop (Heap.union q' q2) (HashMap.insert v (Pair c a) visited)
-- ------------------------------------------------------------------------
-- | Floyd-Warshall algorithm for finding shortest paths in a weighted graph
-- with positive or negative edge weights (but with no negative cycles).
--
-- It compute shortest-paths between each pair of vertexes, and folds edge
-- information along shortest paths using a given 'Fold' operation.
floydWarshall
:: forall vertex cost label a. (Eq vertex, Hashable vertex, Real cost)
=> Fold vertex cost label a
-- ^ Operation used to fold shotest paths
-> Graph vertex cost label
-> HashMap vertex (HashMap vertex (cost, a))
floydWarshall (Fold fV fE fC (fD :: x -> a)) g =
fmap (fmap (\(Pair c x) -> (c, fD x))) $
HashMap.unionWith (HashMap.unionWith minP) (foldl' f tbl0 vs) paths0
where
vs = HashMap.keys g
paths0 :: HashMap vertex (HashMap vertex (Pair cost x))
paths0 = HashMap.mapWithKey (\v _ -> HashMap.singleton v (Pair 0 (fV v))) g
tbl0 :: HashMap vertex (HashMap vertex (Pair cost x))
tbl0 = HashMap.mapWithKey (\v es -> HashMap.fromListWith minP [(u, (Pair c (fE (v,u,c,l)))) | (u,c,l) <- es]) g
minP :: Pair cost x -> Pair cost x -> Pair cost x
minP = minBy (comparing (\(Pair c _) -> c))
f :: HashMap vertex (HashMap vertex (Pair cost x))
-> vertex
-> HashMap vertex (HashMap vertex (Pair cost x))
f tbl vk =
case HashMap.lookup vk tbl of
Nothing -> tbl
Just hk -> HashMap.map h tbl
where
h :: HashMap vertex (Pair cost x) -> HashMap vertex (Pair cost x)
h m =
case HashMap.lookup vk m of
Nothing -> m
Just (Pair c1 x1) -> HashMap.unionWith minP m (HashMap.map (\(Pair c2 x2) -> (Pair (c1+c2) (fC x1 x2))) hk)
minBy :: (a -> a -> Ordering) -> a -> a -> a
minBy f a b =
case f a b of
LT -> a
EQ -> a
GT -> b
-- ------------------------------------------------------------------------