| 1 | {-# LANGUAGE BangPatterns, PatternGuards, RankNTypes #-} |
|---|
| 2 | {-# OPTIONS -fwarn-unused-imports #-} |
|---|
| 3 | module QuickHull |
|---|
| 4 | (quickHull) |
|---|
| 5 | where |
|---|
| 6 | import Control.Monad |
|---|
| 7 | import Control.Exception |
|---|
| 8 | import Control.Concurrent |
|---|
| 9 | import Control.Monad.ST |
|---|
| 10 | import GHC.Conc |
|---|
| 11 | import Data.IORef |
|---|
| 12 | import Data.List |
|---|
| 13 | import Data.Ord |
|---|
| 14 | import Data.Vector.Unboxed (Vector) |
|---|
| 15 | import qualified Data.Vector.Unboxed as V |
|---|
| 16 | import qualified Data.Vector.Unboxed.Mutable as MV |
|---|
| 17 | import qualified Data.Vector.Generic as G |
|---|
| 18 | |
|---|
| 19 | type Point = (Double, Double) |
|---|
| 20 | type Line = (Point, Point) |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | -- | Compute the convex hull of a vector of points. |
|---|
| 24 | quickHull :: Vector Point -> IO (Vector Point) |
|---|
| 25 | quickHull !points |
|---|
| 26 | | V.length points == 0 |
|---|
| 27 | = return points |
|---|
| 28 | |
|---|
| 29 | | otherwise |
|---|
| 30 | = do -- Find the left and right-most points. |
|---|
| 31 | let (minx, maxx) = minmax points |
|---|
| 32 | |
|---|
| 33 | -- Hull points get written to the vector in this IORef. |
|---|
| 34 | hullRef <- newIORef V.empty |
|---|
| 35 | |
|---|
| 36 | -- Fork off computations to handle half of the points each. |
|---|
| 37 | -- For uniformly distributed points this first iteration takes most of the time. |
|---|
| 38 | parIO [ hsplit hullRef points minx maxx |
|---|
| 39 | , hsplit hullRef points maxx minx] |
|---|
| 40 | |
|---|
| 41 | -- Grab the finished hull points. |
|---|
| 42 | hull <- readIORef hullRef |
|---|
| 43 | |
|---|
| 44 | -- We've got the hull points, but they can appear in arbitrary order. |
|---|
| 45 | -- Do a rubbish via-lists merge phase so that they appear clockwise around the edge. |
|---|
| 46 | -- This isn't too expensive if there aren't many points on the hull. |
|---|
| 47 | let (above, below) |
|---|
| 48 | = V.unstablePartition |
|---|
| 49 | (\p -> distance minx maxx p > 0) |
|---|
| 50 | hull |
|---|
| 51 | |
|---|
| 52 | let aboveSorted = V.fromList $ sortBy (comparing fst) $ V.toList above |
|---|
| 53 | let belowSorted = V.fromList $ sortBy (comparing fst) $ V.toList below |
|---|
| 54 | let hull' = aboveSorted V.++ V.reverse belowSorted |
|---|
| 55 | |
|---|
| 56 | return hull' |
|---|
| 57 | |
|---|
| 58 | |
|---|
| 59 | hsplit :: IORef (Vector Point) -> Vector Point -> Point -> Point -> IO () |
|---|
| 60 | {-# INLINE hsplit #-} |
|---|
| 61 | hsplit hullRef !points !p1@(!p1X, !p1Y) !p2@(!p2X, !p2Y) |
|---|
| 62 | -- we've found one. |
|---|
| 63 | | V.length packed == 0 |
|---|
| 64 | = addHullPoint hullRef p1 |
|---|
| 65 | |
|---|
| 66 | -- do the two new segments in parallel. |
|---|
| 67 | | V.length packed > 1000 |
|---|
| 68 | = parIO |
|---|
| 69 | [ hsplit hullRef packed p1 pm |
|---|
| 70 | , hsplit hullRef packed pm p2 ] |
|---|
| 71 | |
|---|
| 72 | | otherwise |
|---|
| 73 | = do hsplit hullRef packed p1 pm |
|---|
| 74 | hsplit hullRef packed pm p2 |
|---|
| 75 | |
|---|
| 76 | where (packed, pm) = parPackPoints points p1X p1Y p2X p2Y |
|---|
| 77 | |
|---|
| 78 | |
|---|
| 79 | -- | Copy points from the input vector that are on the left of the line into a |
|---|
| 80 | -- new buffer. While we're doing this, determine the point that is furthest |
|---|
| 81 | -- from the line. |
|---|
| 82 | -- |
|---|
| 83 | -- If we have a big enough vector then split it in two and do both halves |
|---|
| 84 | -- in parallel. Doing this requires a copy afterwards to join the two |
|---|
| 85 | -- results back together. |
|---|
| 86 | -- |
|---|
| 87 | parPackPoints |
|---|
| 88 | :: Vector Point |
|---|
| 89 | -> Double -> Double |
|---|
| 90 | -> Double -> Double |
|---|
| 91 | -> ( Vector Point |
|---|
| 92 | , Point) |
|---|
| 93 | |
|---|
| 94 | {-# INLINE parPackPoints #-} |
|---|
| 95 | parPackPoints !points !p1X !p1Y !p2X !p2Y |
|---|
| 96 | | numCapabilities == 1 |
|---|
| 97 | || V.length points < 1000 |
|---|
| 98 | = packPoints p1X p1Y p2X p2Y points |
|---|
| 99 | |
|---|
| 100 | | otherwise |
|---|
| 101 | = let |
|---|
| 102 | numSegments = numCapabilities |
|---|
| 103 | |
|---|
| 104 | -- Total number of points to process. |
|---|
| 105 | lenPoints = V.length points |
|---|
| 106 | |
|---|
| 107 | -- How many points to process in each segment. |
|---|
| 108 | lenSeg = lenPoints `div` numSegments |
|---|
| 109 | |
|---|
| 110 | -- If the total number of points doesn't divide evenly into segments |
|---|
| 111 | -- then there may be an odd number. Make sure to get the rest into the last segment. |
|---|
| 112 | splitPacked count ixStart |
|---|
| 113 | | count == 0 = [] |
|---|
| 114 | |
|---|
| 115 | | count == 1 |
|---|
| 116 | = let points' = V.slice ixStart (lenPoints - ixStart) points |
|---|
| 117 | result@(packed', _) = packPoints p1X p1Y p2X p2Y points' |
|---|
| 118 | in packed' `pseq` (result : []) |
|---|
| 119 | |
|---|
| 120 | | otherwise |
|---|
| 121 | = let points' = V.slice ixStart lenSeg points |
|---|
| 122 | result@(packed', _) = packPoints p1X p1Y p2X p2Y points' |
|---|
| 123 | rest = splitPacked (count - 1) (ixStart + lenSeg) |
|---|
| 124 | in packed' `par` rest `par` (result : rest) |
|---|
| 125 | |
|---|
| 126 | results = splitPacked numSegments 0 |
|---|
| 127 | vResult = concatVectors $ map fst results |
|---|
| 128 | pMax = selectFurthest p1X p1Y p2X p2Y results |
|---|
| 129 | |
|---|
| 130 | in (vResult, pMax) |
|---|
| 131 | |
|---|
| 132 | |
|---|
| 133 | selectFurthest |
|---|
| 134 | :: Double -> Double |
|---|
| 135 | -> Double -> Double |
|---|
| 136 | -> [(Vector Point, Point)] |
|---|
| 137 | -> Point |
|---|
| 138 | |
|---|
| 139 | selectFurthest !p1X !p1Y !p2X !p2Y ps |
|---|
| 140 | = go (0, 0) 0 ps |
|---|
| 141 | |
|---|
| 142 | where go pMax !distMax [] |
|---|
| 143 | = pMax |
|---|
| 144 | |
|---|
| 145 | go pMax !distMax ((packed, pm):rest) |
|---|
| 146 | | V.length packed == 0 |
|---|
| 147 | = go pMax distMax rest |
|---|
| 148 | |
|---|
| 149 | | otherwise |
|---|
| 150 | , dist <- distance (p1X, p1Y) (p2X, p2Y) pm |
|---|
| 151 | = if dist > distMax |
|---|
| 152 | then go pm dist rest |
|---|
| 153 | else go pMax distMax rest |
|---|
| 154 | |
|---|
| 155 | |
|---|
| 156 | packPoints |
|---|
| 157 | :: Double -> Double -- First point on dividing line. |
|---|
| 158 | -> Double -> Double -- Second point on dividing line. |
|---|
| 159 | -> Vector Point -- Source points. |
|---|
| 160 | -> ( Vector Point -- Packed vector containing only points on the left of the line. |
|---|
| 161 | , Point) -- The point on the left that was furthest from the line. |
|---|
| 162 | |
|---|
| 163 | {-# INLINE packPoints #-} |
|---|
| 164 | packPoints !p1X !p1Y !p2X !p2Y !points |
|---|
| 165 | = let |
|---|
| 166 | result |
|---|
| 167 | = G.create |
|---|
| 168 | $ do packed <- MV.new (V.length points + 1) |
|---|
| 169 | (pMax, ixPacked) <- fill points packed p1X p1Y p2X p2Y 0 0 |
|---|
| 170 | |
|---|
| 171 | -- We stash the maximum point on the end of the vector to get |
|---|
| 172 | -- it through the create call. |
|---|
| 173 | MV.write packed ixPacked pMax |
|---|
| 174 | return $ MV.slice 0 (ixPacked + 1) packed |
|---|
| 175 | |
|---|
| 176 | in ( V.slice 0 (V.length result - 1) result |
|---|
| 177 | , result V.! (V.length result - 1)) |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | fill :: forall s |
|---|
| 181 | . Vector Point -- Source points. |
|---|
| 182 | -> MV.MVector s Point -- Vector to write packed points into. |
|---|
| 183 | -> Double -> Double -- First point on dividing line. |
|---|
| 184 | -> Double -> Double -- Second poitn on dividing line. |
|---|
| 185 | -> Int -- Index into source points to start reading from. |
|---|
| 186 | -> Int -- Index into packed points to start writing to. |
|---|
| 187 | -> ST s |
|---|
| 188 | ( Point -- Furthest point from the line that was found. |
|---|
| 189 | , Int) -- The number of packed points written. |
|---|
| 190 | |
|---|
| 191 | {-# INLINE fill #-} |
|---|
| 192 | fill !points !packed !p1X !p1Y !p2X !p2Y !ixPoints' !ixPacked' |
|---|
| 193 | = go (0, 0) 0 ixPoints' ixPacked' |
|---|
| 194 | where go pMax !distMax !ixPoints !ixPacked |
|---|
| 195 | | ixPoints >= V.length points |
|---|
| 196 | = do return (pMax, ixPacked) |
|---|
| 197 | |
|---|
| 198 | | p <- points V.! ixPoints |
|---|
| 199 | , d <- distance (p1X, p1Y) (p2X, p2Y) p |
|---|
| 200 | , d > 0 |
|---|
| 201 | = do MV.write packed ixPacked p |
|---|
| 202 | if d > distMax |
|---|
| 203 | then go p d (ixPoints + 1) (ixPacked + 1) |
|---|
| 204 | else go pMax distMax (ixPoints + 1) (ixPacked + 1) |
|---|
| 205 | |
|---|
| 206 | | otherwise |
|---|
| 207 | = go pMax distMax (ixPoints + 1) ixPacked |
|---|
| 208 | |
|---|
| 209 | |
|---|
| 210 | minmax :: Vector Point -> (Point, Point) |
|---|
| 211 | {-# INLINE minmax #-} |
|---|
| 212 | minmax !vec |
|---|
| 213 | = go first first 0 |
|---|
| 214 | where first = vec V.! 0 |
|---|
| 215 | |
|---|
| 216 | go pMin@(!minX, !minY) pMax@(!maxX, !maxY) !ix |
|---|
| 217 | | ix >= V.length vec = (pMin, pMax) |
|---|
| 218 | |
|---|
| 219 | | (x, y) <- vec V.! ix |
|---|
| 220 | = if x < minX then go (x, y) pMax (ix + 1) |
|---|
| 221 | else if x > maxX then go pMin (x, y) (ix + 1) |
|---|
| 222 | else go pMin pMax (ix + 1) |
|---|
| 223 | |
|---|
| 224 | |
|---|
| 225 | distance :: Point -> Point -> Point -> Double |
|---|
| 226 | {-# INLINE distance #-} |
|---|
| 227 | distance (x1, y1) (x2, y2) (xo, yo) |
|---|
| 228 | = (x1-xo) * (y2 - yo) - (y1 - yo) * (x2 - xo) |
|---|
| 229 | |
|---|
| 230 | |
|---|
| 231 | addHullPoint :: IORef (Vector Point) -> Point -> IO () |
|---|
| 232 | addHullPoint hullRef p |
|---|
| 233 | = atomicModifyIORef hullRef |
|---|
| 234 | $ \hull -> (V.singleton p V.++ hull, ()) |
|---|
| 235 | |
|---|
| 236 | |
|---|
| 237 | |
|---|
| 238 | -- Can't find an equivalent for this in Control.Concurrent. |
|---|
| 239 | parIO :: [IO ()] -> IO () |
|---|
| 240 | parIO stuff |
|---|
| 241 | = do mVars <- replicateM (length stuff) newEmptyMVar |
|---|
| 242 | zipWithM_ (\c v -> forkIO $ c `finally` putMVar v ()) stuff mVars |
|---|
| 243 | mapM_ readMVar mVars |
|---|
| 244 | |
|---|
| 245 | |
|---|
| 246 | -- We really want a function in the vector library for this. |
|---|
| 247 | concatVectors :: [Vector Point] -> Vector Point |
|---|
| 248 | {-# NOINLINE concatVectors #-} |
|---|
| 249 | concatVectors vectors |
|---|
| 250 | = G.create |
|---|
| 251 | $ do let len = sum $ map V.length vectors |
|---|
| 252 | vOut <- MV.new len |
|---|
| 253 | go vectors vOut 0 |
|---|
| 254 | return vOut |
|---|
| 255 | |
|---|
| 256 | where {-# INLINE go #-} |
|---|
| 257 | go [] _ _ |
|---|
| 258 | = return () |
|---|
| 259 | |
|---|
| 260 | go (vSrc:vsSrc) vDest !ixStart |
|---|
| 261 | = do let lenSrc = V.length vSrc |
|---|
| 262 | let vDestSlice = MV.slice ixStart lenSrc vDest |
|---|
| 263 | V.copy vDestSlice vSrc |
|---|
| 264 | go vsSrc vDest (ixStart + lenSrc) |
|---|