hgeometry-0.14: src/Algorithms/Geometry/SSSP.hs
{-# LANGUAGE RecordWildCards #-}
--------------------------------------------------------------------------------
-- |
-- Module : Algorithms.Geometry.SSSP
-- Copyright : (C) David Himmelstrup
-- License : see the LICENSE file
-- Maintainer : David Himmelstrup
--------------------------------------------------------------------------------
module Algorithms.Geometry.SSSP
( SSSP
, triangulate
, sssp
, visibilityDual
, visibilityFinger
, visibilitySensitive
) where
import Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate')
import Algorithms.Geometry.PolygonTriangulation.Types (PolygonEdgeType)
import Algorithms.Graph.DFS (adjacencyLists, dfs', dfsSensitive)
import Control.Lens ((^.))
import Data.Bitraversable
import Data.Either
import Data.Ext (ext, extra, type (:+) (..))
import qualified Data.FingerTree as F
import Data.Geometry.Line (lineThrough)
import Data.Geometry.LineSegment (LineSegment (ClosedLineSegment, LineSegment))
import Data.Geometry.PlanarSubdivision (PolygonFaceData (..))
import Data.Geometry.Point (Point, ccw, pattern CCW, pattern CW)
import Data.Geometry.Polygon
import Data.Intersection
import Data.List (sortOn, (\\))
import Data.Maybe (fromMaybe)
import Data.PlanarGraph (PlanarGraph)
import qualified Data.PlanarGraph as Graph
import Data.PlaneGraph (FaceId (..), PlaneGraph, VertexData (..),
VertexId, VertexId', dual, graph, incidentEdges,
leftFace, vertices)
import qualified Data.PlaneGraph as PlaneGraph
import Data.Tree (Tree (Node))
import qualified Data.Vector as V
import qualified Data.Vector.Circular as CV
import qualified Data.Vector.Circular.Util as CV
import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as VU
import Data.Vinyl
import Data.Vinyl.CoRec
{-
type AbsOffset = Int
data TriangulatedPolygon t p r = TriangulatedPolygon
{ triangulatedMap :: Map AbsOffset (VertexId () Primal)
, triangulatedGraph :: PlaneGraph () AbsOffset PolygonEdgeType PolygonFaceData r
, triangulatedPolygon :: Polygon t p r
}
-}
-- | Single-source shortest paths tree. Both keys and values are vertex offset ints.
--
-- @parentOf(i) = sssp[i]@
type SSSP = Vector Int
-- FIXME: The code for generating the dual cannot deal with offsets so
-- we're running 'unsafeFromPoints . toPoints' to reset the polygon.
-- Super silly. Please fix.
-- | \( O(n \log n) \)
triangulate :: forall s p r. (Ord r, Fractional r)
=> SimplePolygon p r -> PlaneGraph s Int PolygonEdgeType PolygonFaceData r
triangulate p =
let poly' = snd $ bimapAccumL (\a _ -> (a+1,a)) (,) 0 $ unsafeFromPoints $ toPoints p
in triangulate' @s poly'
-- | \( O(n) \) Single-Source shortest path.
sssp :: (Ord r, Fractional r)
=> PlaneGraph s Int PolygonEdgeType PolygonFaceData r
-> SSSP
sssp trig =
ssspFinger d
where
Just v0 = fst <$> V.find (\(_vid, VertexData _ idx) -> idx == 0) (vertices trig)
v0i = incidentEdges v0 trig
Just (FaceId firstFace) = V.find (/= FaceId outer) $ V.map (`leftFace` trig) v0i
FaceId outer = PlaneGraph.outerFaceId trig
dualGraph = trig^.graph.dual
dualTree' = dfs' (V.map (filter (/= outer)) $ adjacencyLists dualGraph) firstFace
dualVS = fmap (\v -> toCCW $ PlaneGraph.boundaryVertices (FaceId v) trig) dualTree'
trigTree = toTrigTree trig dualVS
d = mkDual trigTree
toCCW v =
let cv = CV.reverse $ CV.unsafeFromVector v
in CV.toVector $ fromMaybe cv $ CV.findRotateTo (== v0) cv
{-
1. Find the starting face.
-}
visibilitySensitive :: forall s r. (Ord r, Fractional r, Show r)
=> PlaneGraph s Int PolygonEdgeType PolygonFaceData r
-> SimplePolygon () r
visibilitySensitive = fromPoints . map ext . rights . visibilityFinger . visibilityDual
visibilityDual :: forall s r. (Ord r, Fractional r)
=> PlaneGraph s Int PolygonEdgeType PolygonFaceData r
-> Dual r
visibilityDual trig = d
where
Just v0 = fst <$> V.find (\(_vid, VertexData _ idx) -> idx == 0) (vertices trig)
v0i = incidentEdges v0 trig
outer :: VertexId s Graph.Dual
FaceId outer = PlaneGraph.outerFaceId trig
firstFace :: VertexId s Graph.Dual
Just (FaceId firstFace) = V.find (/= FaceId outer) $ V.map (`leftFace` trig) v0i
dualGraph :: PlanarGraph s Graph.Dual PolygonFaceData PolygonEdgeType (VertexData r Int)
dualGraph = trig^.graph.dual
dualTree' :: Tree (VertexId s Graph.Dual)
dualTree' = dfsSensitive neigh firstFace
neigh :: VertexId s Graph.Dual -> [VertexId s Graph.Dual]
neigh v = V.toList $ V.filter (/=outer) $ Graph.neighboursOf v dualGraph
dualVS :: Tree (V.Vector (VertexId' s))
dualVS = fmap (\v -> toCCW $ PlaneGraph.boundaryVertices (FaceId v) trig) dualTree'
trigTree :: Tree (Index r, Index r, Index r)
trigTree = toTrigTree trig dualVS
d :: Dual r
d = mkDual trigTree
toCCW v =
let cv = CV.reverse $ CV.unsafeFromVector v
in CV.toVector $ fromMaybe cv $ CV.findRotateTo (== v0) cv
visibilityFinger :: forall r. (Fractional r, Ord r, Show r) => Dual r -> [Either (Int, Int, Int) (Point 2 r)]
visibilityFinger d =
case d of
Dual (a,b,c) ab bc ca ->
Left (indexExtra a, indexExtra b, indexExtra c) :
worker (Funnel (F.singleton b) a F.empty) ab ++
worker (Funnel (F.singleton c) a (F.singleton b)) bc ++
worker (Funnel F.empty a (F.singleton c)) ca
where
-- Final edge is the leftmost of each funnel.
-- The most visible are the rightmost of each funnel.
-- Cut line segment.
worker f EmptyDual =
let edgeA = ringAccess $ funnelRightTop f
edgeB = ringAccess $ funnelLeftTop f
edge = ClosedLineSegment (ext edgeA) (ext edgeB)
coneA = ringAccess $ funnelRightBottom f
coneB = ringAccess $ funnelLeftBottom f
lineA = lineThrough (ringAccess $ funnelCusp f) coneA
lineB = lineThrough (ringAccess $ funnelCusp f) coneB
-- findIntersection :: Line 2 r -> Point 2 r
findIntersection line =
match (edge `intersect` line) $
H (\NoIntersection -> error "no intersection")
:& H (\pt -> Right pt)
:& H (\LineSegment{} -> error "line intersection")
:& RNil
in [if edgeA == coneA then Right coneA else findIntersection lineA] ++
if edgeB == coneB then [] else [findIntersection lineB]
worker f (NodeDual x l r) =
Left (indexExtra $ fromMaybe (funnelCusp f) $ chainTop (funnelRight f)
,indexExtra x
,indexExtra $ fromMaybe (funnelCusp f) $ chainTop (funnelLeft f)) :
case splitFunnel x f of
(_v, fL, fR, dir) -> case dir of
-- 'x' is to the left of the visibility cone. Everything further to the left cannot
-- be visible to just go right.
SplitLeft -> worker fR r -- assert cusp of fR == cusp of f
-- 'x' is visible from our cusp. Add it to the output and go both to the left and right.
NoSplit -> worker fR r ++ [Right (ringAccess x)] ++ worker fL l
-- 'x' is to the right of the visibility cone. Everything further to the right cannot
-- be visible to just go left.
SplitRight -> worker fL l -- assert cusp of fL == cusp of f
--------------------------------------------------------------------------------
-- SSSP (with fingertree) implementation
data MinMax r = MinMax (Index r) (Index r) | MinMaxEmpty deriving (Show)
instance Semigroup (MinMax r) where
MinMaxEmpty <> b = b
a <> MinMaxEmpty = a
MinMax a _b <> MinMax _c d
= MinMax a d
instance Monoid (MinMax r) where
mempty = MinMaxEmpty
-- Including the 'Point 2 r' here means we don't have to look it up.
-- This mattered since lookups used to be O(log n) rather than O(1).
newtype Index r = Index (Point 2 r :+ Int) -- deriving (Show)
instance Show (Index r) where
show = show . indexExtra
indexExtra :: Index r -> Int
indexExtra (Index p) = p^.extra
instance Eq (Index r) where
Index (_ :+ a) == Index (_ :+ b) = a == b
type Chain r = F.FingerTree (MinMax r) (Index r)
data Funnel r = Funnel
{ funnelLeft :: Chain r -- Left-most element is furthest away from cusp.
, funnelCusp :: Index r
, funnelRight :: Chain r -- Left-most element is furthest away from cusp.
} deriving (Show)
-- Left side of the funnel, furthest away from the cusp.
funnelLeftTop :: Funnel r -> Index r
funnelLeftTop f = fromMaybe (funnelCusp f) $ chainTop (funnelLeft f)
-- Left side of the funnel, closest to the cusp.
funnelLeftBottom :: Funnel r -> Index r
funnelLeftBottom f = fromMaybe (funnelCusp f) $ chainBottom (funnelLeft f)
-- Right side of the funnel, furthest away from the cusp.
funnelRightTop :: Funnel r -> Index r
funnelRightTop f = fromMaybe (funnelCusp f) $ chainTop (funnelRight f)
-- Right side of the funnel, closest to the cusp.
funnelRightBottom :: Funnel r -> Index r
funnelRightBottom f = fromMaybe (funnelCusp f) $ chainBottom (funnelRight f)
-- Element closest to the cusp.
chainBottom :: Chain r -> Maybe (Index r)
chainBottom chain = case F.viewl chain of
F.EmptyL -> Nothing
elt F.:< _ -> Just elt
-- Element furthest away from the cusp.
chainTop :: Chain r -> Maybe (Index r)
chainTop chain = case F.viewr chain of
F.EmptyR -> Nothing
_ F.:> elt -> Just elt
instance F.Measured (MinMax r) (Index r) where
measure i = MinMax i i
data SplitDirection = SplitLeft | NoSplit | SplitRight
deriving (Show)
-- Split a funnel w.r.t. a point 'x'. There are three cases:
-- 1. 'x' is visible from the cusp.
-- 2. the path to 'x' hits the left side of the funnel.
-- 3. the path to 'x' hits the right side of the funnel.
--
-- ********************************************************
-- Drawing guide:
-- \ /
-- left side of funnel -> \ / <- right side of funnel
-- \ /
-- * <- cusp
-- ********************************************************
--
-- Case 1:
-- x
-- \ /
-- \ /
-- \ /
-- *
--
-- Case 2:
--
-- x
-- \ /
-- \ /
-- \ /
-- *
--
-- Case 3:
--
-- x
-- \ /
-- \ /
-- \ /
-- *
--
-- If 'x' is visible from the cusp, then the shortest path is a straight line and we're done.
-- If 'x' is not visible from the cusp, then we find the first point up the funnel where
-- 'x' becomes visible. We'll use a fingertree to find the point in O(log(min(n,m))). Because
-- of math, this adds up to O(n) for the entire SSSP tree.
--
-- Once we've found the first point that can see 'x', we split the funnel in two: One funnel
-- that will be used for points to the left of 'x' and one funnel for points to the right of
-- 'x'. Oh, "left" and "right" here are used to indicate branches in the dual tree.
splitFunnel :: (Fractional r, Ord r) => Index r -> Funnel r -> (Index r, Funnel r, Funnel r, SplitDirection)
splitFunnel x Funnel{..}
| isOnLeftChain =
case doSearch isRightTurn funnelLeft of
(lower, t, upper) ->
( t
, Funnel upper t (F.singleton x)
, Funnel (lower F.|> t F.|> x) funnelCusp funnelRight
, SplitLeft)
| isOnRightChain =
case doSearch isLeftTurn funnelRight of
(lower, t, upper) ->
( t
, Funnel funnelLeft funnelCusp (lower F.|> t F.|> x)
, Funnel (F.singleton x) t upper
, SplitRight)
| otherwise =
( funnelCusp
, Funnel funnelLeft funnelCusp (F.singleton x)
, Funnel (F.singleton x) funnelCusp funnelRight
, NoSplit)
where
isOnLeftChain = fromMaybe False $
isLeftTurnOrLinear cuspElt <$> leftElt <*> pure targetElt
isOnRightChain = fromMaybe False $
isRightTurnOrLinear cuspElt <$> rightElt <*> pure targetElt
doSearch fn chain =
case F.search (searchChain fn) chain of
F.Position lower t upper -> (lower, t, upper)
F.OnLeft -> error "cannot happen"
F.OnRight -> error "cannot happen"
F.Nowhere -> error "cannot happen"
searchChain _ MinMaxEmpty _ = False
searchChain _ _ MinMaxEmpty = True
searchChain check (MinMax _ l) (MinMax r _) =
check (ringAccess l) (ringAccess r) targetElt
cuspElt = ringAccess funnelCusp
targetElt = ringAccess x
leftElt = ringAccess <$> chainBottom funnelLeft
rightElt = ringAccess <$> chainBottom funnelRight
-- FIXME: Turning a list of pairs into a vector is incredibly inefficient.
-- Would be much faster to write directly into a mutable vector and
-- then freeze it at the end.
-- \( O(n) \)
ssspFinger :: (Fractional r, Ord r) => Dual r -> SSSP
ssspFinger d = toSSSP $
case d of
Dual (a,b,c) ab bc ca ->
(a, a) :
(b, a) :
(c, a) :
loopLeft a c ca ++
worker (Funnel (F.singleton c) a (F.singleton b)) bc ++
loopRight a b ab
where
toSSSP :: [(Index r,Index r)] -> SSSP
toSSSP lst =
VU.fromList . map snd . sortOn fst $
[ (a,b) | (Index (_ :+ a), Index (_ :+ b)) <- lst ]
loopLeft a outer l =
case l of
EmptyDual -> []
NodeDual x l' r' ->
(x,a) :
worker (Funnel (F.singleton x) a (F.singleton outer)) r' ++
loopLeft a x l'
loopRight a outer r =
case r of
EmptyDual -> []
NodeDual x l' r' ->
(x, a) :
worker (Funnel (F.singleton outer) a (F.singleton x)) l' ++
loopRight a x r'
worker _ EmptyDual = []
worker f (NodeDual x l r) =
case splitFunnel x f of
(v, fL, fR, _) ->
(x, v) :
worker fL l ++
worker fR r
--------------------------------------------------------------------------------
-- Duals
data Dual r = Dual (Index r, Index r, Index r) -- (a,b,c)
(DualTree r) -- borders ab
(DualTree r) -- borders bc
(DualTree r) -- borders ca
deriving (Show)
data DualTree r
= EmptyDual
| NodeDual (Index r) -- axb triangle, a and b are from parent.
(DualTree r) -- borders xb
(DualTree r) -- borders ax
deriving (Show)
toTrigTree :: PlaneGraph s Int PolygonEdgeType PolygonFaceData r
-> Tree (V.Vector (VertexId' s))
-> Tree (Index r,Index r,Index r)
toTrigTree trig = fmap toTrig . fmap (fmap toDat)
where
toTrig v = case V.toList v of
[a,b,c] -> (a,b,c)
_ -> error "Algorithms.Geometry.SSSP: Invalid triangulation."
toDat v = Index $ PlaneGraph.vtxDataToExt (trig ^. PlaneGraph.vertexDataOf v)
-- pp :: Show a => Tree a -> IO ()
-- pp = putStrLn . drawTree . fmap show
mkDual :: Tree (Index r,Index r,Index r) -> Dual r
mkDual (Node (a,b,c) forest) =
Dual (a, b, c)
(dualTree a b forest)
(dualTree b c forest)
(dualTree c a forest)
dualTree :: Index r -> Index r -> [Tree (Index r,Index r,Index r)] -> DualTree r
dualTree p1 p2 (Node (a,b,c) sub:xs) =
case [a,b,c] \\ [p1,p2] of
[x] -> NodeDual x (dualTree x p2 sub) (dualTree p1 x sub)
_ -> dualTree p1 p2 xs
dualTree _p1 _p2 [] = EmptyDual
--------------------------------------------------------------------------------
-- Helpers
ringAccess :: Index r -> Point 2 r
ringAccess (Index (pt :+ _idx)) = pt
isRightTurnOrLinear :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool
isRightTurnOrLinear p1 p2 p3 = not $ isLeftTurn p1 p2 p3
isLeftTurnOrLinear :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool
isLeftTurnOrLinear p1 p2 p3 = not $ isRightTurn p1 p2 p3
isLeftTurn :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool
isLeftTurn p1 p2 p3 =
ccw p1 p2 p3 == CCW
isRightTurn :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool
isRightTurn p1 p2 p3 =
ccw p1 p2 p3 == CW