packages feed

hgeometry-0.7.0.0: examples/Demo/ExpectedPairwiseDistance.hs

{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Demo.ExpectedPairwiseDistance where

import           Algorithms.Geometry.Diameter
import           Algorithms.Geometry.WellSeparatedPairDecomposition.Types
import           Algorithms.Geometry.WellSeparatedPairDecomposition.WSPD
import           Control.Lens
import           Control.Monad ((<=<))
import           Data.BinaryTree
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
import           Data.Char (isSpace)
import           Data.Ext
import qualified Data.Foldable as F
import           Data.Geometry
import qualified Data.List.NonEmpty as NonEmpty
import           Data.Maybe (mapMaybe)
import           Data.Proxy
import           Data.Data
import           Data.Semigroup
import qualified Data.Set as Set
import           GHC.TypeLits
import           Options.Applicative hiding ((<>))


import           Debug.Trace
--------------------------------------------------------------------------------


data Options = Options { _inPath    :: FilePath }
               deriving Data

options :: ParserInfo Options
options = info (helper <*> parser)
               (  progDesc "Compute expected pairwise distance of the points in the input file."
               <> header   "Expected Pairwise Distance"
               )
  where
    parser = Options
          <$> strOption (help "Input file"
                         <> short 'i'
                        )

--------------------------------------------------------------------------------

-- | Evaluates the formula: $\sum_{p,q \in pts} \|pq\|*Prb[sample has size k
-- and contains p and q]$ which solves to $\frac{{n-2 \choose k-2}}{{n \choose
-- k}} \sum_{p,q} \|pq\|$
--
-- running time: $O(n^2)$, where $n$ is the number of points
expectedPairwiseDistance       :: (Floating r, Arity d) => Int -> [Point d r :+ p] -> r
expectedPairwiseDistance k pts = makeExpected k pts pairwiseDist

-- | A $(1+\varepsilon)$-approximation of expectedPairwiseDistance
--
-- running time: $O(n(1/eps)^d + n\log n)$, where $n$ is the number of points
approxExpectedPairwiseDistance          :: (Floating r, Ord r
                                           , Arity d, Arity (d+1), 1 <= d
                                         , Show r, Show p)
                                         => r -> Int -> [Point d r :+ p] -> r
approxExpectedPairwiseDistance eps k pts =
  makeExpected k pts (approxPairwiseDistance eps)

--------------------------------------------------------------------------------
-- * Computing Distances

-- | Sum of the pairwise distances
pairwiseDist     :: (Floating r, Arity d) => [Point d r :+ p] -> r
pairwiseDist pts = sum [ euclideanDist (p^.core) (q^.core) | p <- pts, q <- pts] / 2


-- | $(1+\eps)$-approximation of the sum of the pairwise distances.
--
-- running time: $O(n(1/eps)^d + n\log n)$, where $n$ is the number of points
approxPairwiseDistance         :: (Floating r, Ord r, Arity d, Arity (d+1), 1 <= d
                                  , Show r, Show p)
                               => r -> [Point d r :+ p] -> r
approxPairwiseDistance _   []  = 0
approxPairwiseDistance eps pts =
    sum [ (size as)*(size bs)*euclideanDist (repr as) (repr bs) | (as,bs) <- pairs ]
  where
    t     = withSizes . fairSplitTree . NonEmpty.fromList $ pts
    pairs = wellSeparatedPairs (4 / eps) t

    size (access' -> (Sized (Size i) _))  = fromIntegral i
    repr (access' -> (Sized _ (First p))) = p^.core


-- wspPairs = fairSplitTree . NonEmpty.fromList

--------------------------------------------------------------------------------
-- * Helper stuff

-- | Helper to turn the result of 'f k' into 'the expected 'f k', assuming that
-- we select a set of k points.
makeExpected         :: (Fractional r, Foldable t) => Int -> t a -> (t a -> r) -> r
makeExpected k pts f = prb * f pts
  where
    n   = length pts
    prb = ((n - 2) `choose` (k - 2)) / (n `choose` k)


choose       :: (Integral a, Num b) => a -> a -> b
n `choose` k = fromIntegral $ fac n' `div` (fac (n'-k') * fac k')
  where
    n' :: Integer
    n' = fromIntegral n
    k' :: Integer
    k' = fromIntegral k

    fac z = product [1..z]

-- newtype WSPDMeasured a = WSPDMeasured a

-- instance Measured (Sized (First a)) (WSPDMeasured a) where
--   measure (WSPDMeasured p) = Sized 1 (First p)

-- instance Measured v (WSPDMeasured (Point d r :+ p))
--            => Measured v (SplitTree d p r v) where
--   measure (Leaf p)      = measure $ WSPDMeasured p
--   measure (Node _ nd _) = nd^.nodeData

-- | Annotate the split tree with sizes
withSizes :: SplitTree d p r a -> SplitTree d p r (Sized (First (Point d r :+ p)))
withSizes = foldUp f Leaf
  where
    f l (NodeData j b _) r = let nd = (access' l) <> (access' r)
                             in Node l (NodeData j b nd) r

-- | Get the measurement for a given splittree
access'               :: BinLeafTree (NodeData d r (Sized (First a))) a -> Sized (First a)
access' (Leaf x)      = Sized 1 (First x)
access' (Node _ nd _) = nd^.nodeData


--
-- | CVS file, in which every line consists of a name, followed by exactly d coordinates
parseInput :: forall d r. (Arity d, KnownNat d, Read r)
           => B.ByteString -> [Point d r :+ B.ByteString]
parseInput = mapMaybe toPoint . drop 1 . C.lines
  where
    trim      = fst . C.spanEnd isSpace . C.dropWhile isSpace
    fromList' = vectorFromList . take (fromInteger . natVal $ (Proxy :: Proxy d))

    toPoint bs = let (n:rs) = map trim . C.split ',' $ bs
                     p      = fmap Point . fromList' . map (read . C.unpack) $ rs
                 in (:+ n) <$> p


readInput :: (Arity d, KnownNat d, Read r) => FilePath -> IO [Point d r :+ C.ByteString]
readInput = fmap parseInput . B.readFile

test :: FilePath -> IO [Point 2 Double :+ C.ByteString]
test = readInput

testTree = fmap f .test
  where
    f pts = uncovered pts (4 / 0.05) (fairSplitTree $ NonEmpty.fromList pts)

-- compareBoth     :: r -> FilePath -> IO (r, r, Bool)
compareBoth eps = fmap f . test
  where
    f pts = let exact  = pairwiseDist pts
                approx = approxPairwiseDistance eps pts
            in (exact, approx, (1-eps)*exact <= approx && approx <= (1+eps)*exact)


compareBoth1 eps pts = let exact  = pairwiseDist pts
                           approx = approxPairwiseDistance eps pts
                       in (exact, approx, (1-eps)*exact <= approx && approx <= (1+eps)*exact)


mainWith (Options f) = compareBoth 0.05 f >>= print


--------------------------------------------------------------------------------
-- testing stuff


-- | Computes all pairs of points that are uncovered by the WSPD with separation s
uncovered         :: (Floating r, Ord r, Arity d, Arity (d+1), Ord p)
                  => [Point d r :+ p] -> r -> SplitTree d p r a -> [(Point d r :+ p, Point d r :+ p)]
uncovered pts s t = Set.toList $ allPairs `Set.difference` covered
  where
    allPairs = Set.fromList [ (p,q) | p <- pts, q <- pts, p < q ]
    covered  = Set.unions [ mkSet as bs | (as,bs) <- wellSeparatedPairs s t]

mkSet as bs = Set.fromList [ (min a b,max a b) | a <- F.toList as, b <- F.toList bs]

-- | Naively check if a WSP pair is actually well separated with respect to
-- separation s. i.e. computes the maximum diameter of as and bs, and then
-- tests by brute force if all pairs (a,b) from different sets are at distance
-- at least s times the maximum diameter.
isWellSeparated           :: (Floating r, Ord r, Arity d) => r -> WSP d p r a -> Bool
isWellSeparated s (as,bs) =
    and [ euclideanDist (a^.core) (b^.core) >= s*d | a <- F.toList as, b <- F.toList bs ]
  where
    d = (/2) . maximum . map (diameterNaive . F.toList) $ [as,bs]


nonWellSeparated s = map (\(a,b,c) -> (a,b))
                   . filter (\(a,b,c) -> not c)
                   . map (\p@(a,b) -> (a,b,isWellSeparated s p))
                   . wellSeparatedPairs s . fairSplitTree . NonEmpty.fromList


points1 :: [Point 2 Double :+ ()]
points1 = ext <$> [point2 0 0, point2 1 1, point2 2 10, point2 3 11, point2 5 5, point2 10 0]