hgeometry-0.9.0.0: src/Algorithms/Geometry/SmallestEnclosingBall/RandomizedIncrementalConstruction.hs
{-# 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)