{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE TemplateHaskell #-} -------------------------------------------------------------------------------- -- | -- Module : Algorithms.Geometry.SmallestEnclosingBall.RandomizedIncrementalConstruction -- Copyright : (C) Frank Staals -- License : see the LICENSE file -- Maintainer : Frank Staals -- -- An randomized algorithm to compute the smallest enclosing disk of a set of -- \(n\) points in \(\mathbb{R}^2\). The expected running time is \(O(n)\). -- -------------------------------------------------------------------------------- module Algorithms.Geometry.SmallestEnclosingBall.RandomizedIncrementalConstruction where import Algorithms.Geometry.SmallestEnclosingBall.Types import Control.Lens import Control.Monad.Random.Class import Data.Ext import qualified Data.Foldable as F import Data.Geometry import Data.Geometry.Ball import qualified Data.List as L import Data.List.NonEmpty import Data.Maybe (fromMaybe) import System.Random.Shuffle (shuffle) -------------------------------------------------------------------------------- -- | O(n) expected time algorithm to compute the smallest enclosing disk of a -- set of points. we need at least two points. -- implemented using randomized incremental construction smallestEnclosingDisk :: (Ord r, Fractional r, MonadRandom m) => [Point 2 r :+ p] -> m (DiskResult p r) smallestEnclosingDisk pts@(_:_:_) = ((\(p:q:pts') -> smallestEnclosingDisk' p q pts') . F.toList) <$> shuffle pts smallestEnclosingDisk _ = error "smallestEnclosingDisk: Too few points" -- | Smallest enclosing disk. smallestEnclosingDisk' :: (Ord r, Fractional r) => Point 2 r :+ p -> Point 2 r :+ p -> [Point 2 r :+ p] -> DiskResult p r smallestEnclosingDisk' a b = foldr addPoint (initial a b) . L.tails where -- The epty case occurs only initially addPoint [] br = br addPoint (p:pts) br@(DiskResult d _) | (p^.core) `inClosedBall` d = br | otherwise = smallestEnclosingDiskWithPoint p (a :| (b : pts)) -- | Smallest enclosing disk, given that p should be on it. smallestEnclosingDiskWithPoint :: (Ord r, Fractional r) => Point 2 r :+ p -> NonEmpty (Point 2 r :+ p) -> DiskResult p r smallestEnclosingDiskWithPoint p (a :| pts) = foldr addPoint (initial p a) $ L.tails pts where addPoint [] br = br addPoint (q:pts') br@(DiskResult d _) | (q^.core) `inClosedBall` d = br | otherwise = smallestEnclosingDiskWithPoints p q (a:pts') -- | Smallest enclosing disk, given that p and q should be on it smallestEnclosingDiskWithPoints :: (Ord r, Fractional r) => Point 2 r :+ p -> Point 2 r :+ p -> [Point 2 r :+ p] -> DiskResult p r smallestEnclosingDiskWithPoints p q = foldr addPoint (initial p q) where addPoint r br@(DiskResult d _) | (r^.core) `inClosedBall` d = br | otherwise = DiskResult (circle' r) (Three p q r) circle' r = fromMaybe degen $ disk (p^.core) (q^.core) (r^.core) degen = error "smallestEnclosingDisk: Unhandled degeneracy, three points on a line" -- TODO: handle degenerate case -- | Constructs the initial 'DiskResult' from two points initial :: Fractional r => Point 2 r :+ p -> Point 2 r :+ p -> DiskResult p r initial p q = DiskResult (fromDiameter (p^.core) (q^.core)) (Two p q)