packages feed

dataframe-3.7.0.0: tests/Operations/SortMask.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

-- | The radix sort and parallel mask paths agree with simple references.
module Operations.SortMask where

import Data.Bits (shiftR, xor)
import Data.Function ((&))
import Data.Int (Int32)
import qualified Data.List as L
import Data.Ord (Down (..), comparing)
import qualified Data.Vector.Unboxed as VU

import qualified DataFrame as D
import qualified DataFrame.Functions as F
import DataFrame.Internal.Column (maskIndices)
import qualified DataFrame.Internal.Column as DI
import qualified DataFrame.Internal.DataFrame as DD
import Test.HUnit

-- | Deterministic pseudo-random values in @[0, range)@.
draws :: Int -> Int -> Int -> [Int]
draws seed range n = [mix (seed * 1000003 + i) `mod` range | i <- [1 .. n]]
  where
    mix z0 =
        let z1 = (z0 `xor` (z0 `shiftR` 30)) * (-4658895280553007687)
            z2 = (z1 `xor` (z1 `shiftR` 27)) * (-7723592293110705685)
         in abs (z2 `xor` (z2 `shiftR` 31))

-- | Row order of a stable sort, the order 'D.sortBy' must reproduce.
stableOrder :: (Ord k) => (a -> k) -> [a] -> [Int]
stableOrder key xs = map fst (L.sortBy (comparing (key . snd)) (zip [0 ..] xs))

sortedV :: D.DataFrame -> [Int]
sortedV df = maybe [] (DI.toList @Int) (DD.getColumn "v" df)

-- Many ties and negative keys, so stability and sign handling both show.
intSort :: Bool -> Test
intSort desc = TestCase $ do
    let n = 300000
        ks = map (subtract 500) (draws 7 1000 n)
        df = D.fromColumns [("k", DI.fromList ks), ("v", DI.fromList [0 .. n - 1 :: Int])]
        ord = if desc then D.Desc (F.col @Int "k") else D.Asc (F.col @Int "k")
        expected = if desc then stableOrder Down ks else stableOrder id ks
    assertEqual
        ("Int sort desc=" ++ show desc)
        expected
        (sortedV (df & D.sortBy [ord]))

narrowSort :: Test
narrowSort = TestCase $ do
    let ks = map (fromIntegral . subtract 50) (draws 8 100 5000) :: [Int32]
        df = D.fromColumns [("k", DI.fromList ks), ("v", DI.fromList [0 .. 4999 :: Int])]
    assertEqual
        "Int32 sort"
        (stableOrder id ks)
        (sortedV (df & D.sortBy [D.Asc (F.col @Int32 "k")]))

-- -0.0 and 0.0 compare equal, so they keep row order.
doubleSort :: Test
doubleSort = TestCase $ do
    let ks =
            [0.0, -0.0, 1.5, -2.25, 1 / 0, -(1 / 0), 0.0, 1.5, -1.0e-300, 1.0e-300] ::
                [Double]
        df =
            D.fromColumns [("k", DI.fromList ks), ("v", DI.fromList [0 .. length ks - 1])]
    assertEqual
        "Double sort"
        (stableOrder id ks)
        (sortedV (df & D.sortBy [D.Asc (F.col @Double "k")]))
    assertEqual
        "Double sort desc"
        (stableOrder Down ks)
        (sortedV (df & D.sortBy [D.Desc (F.col @Double "k")]))

-- A NaN column keeps the comparison sort; this only checks it still sorts.
nanSort :: Test
nanSort = TestCase $ do
    let ks = [2, 0 / 0, 1, 3] :: [Double]
        df = D.fromColumns [("k", DI.fromList ks), ("v", DI.fromList [0 .. 3 :: Int])]
    assertEqual
        "NaN sort length"
        4
        (length (sortedV (df & D.sortBy [D.Asc (F.col @Double "k")])))

maskCases :: [Test]
maskCases =
    [ TestCase $
        let bs = map ((< t) . (`mod` 10)) (draws n 10 n)
         in assertEqual
                ("maskIndices n=" ++ show n ++ " t=" ++ show t)
                (VU.fromList [i | (i, True) <- zip [0 ..] bs])
                (maskIndices (VU.fromList bs))
    | n <- [0, 1, 1000, 300001]
    , t <- [0, 3, 10]
    ]

tests :: [Test]
tests =
    map
        (TestLabel "sortMask")
        ([intSort False, intSort True, narrowSort, doubleSort, nanSort] ++ maskCases)