packages feed

easytensor-2.1.1.0: test/Numeric/Arbitraries.hs

{-# LANGUAGE AllowAmbiguousTypes       #-}
{-# LANGUAGE CPP                       #-}
{-# LANGUAGE ConstraintKinds           #-}
{-# LANGUAGE DataKinds                 #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts          #-}
{-# LANGUAGE FlexibleInstances         #-}
{-# LANGUAGE FunctionalDependencies    #-}
{-# LANGUAGE GADTs                     #-}
{-# LANGUAGE PartialTypeSignatures     #-}
{-# LANGUAGE PolyKinds                 #-}
{-# LANGUAGE QuasiQuotes               #-}
{-# LANGUAGE RankNTypes                #-}
{-# LANGUAGE ScopedTypeVariables       #-}
{-# LANGUAGE TemplateHaskell           #-}
{-# LANGUAGE TypeApplications          #-}
{-# LANGUAGE TypeOperators             #-}
{-# LANGUAGE UndecidableInstances      #-}
{-# OPTIONS_GHC -fno-warn-orphans      #-}
-- | Provide instance of Arbitrary for all DataFrame types.
--   Also, this module is an example of fancy type inference and DataFrame
--   traversals with monadic actions.
module Numeric.Arbitraries where

import Test.QuickCheck

#if !(MIN_VERSION_base(4,13,0))
import Control.Monad.Fail
#endif
import           Data.Int
import           Data.Kind            (Type)
import           Data.List            (inits, tails)
import           Data.Semigroup       hiding (All)
import           Data.Word
import           Language.Haskell.TH  (ExpQ, Q, TypeQ)
import qualified Language.Haskell.TH  as TH
import           Numeric.DataFrame
import           Numeric.Dimensions
import           Numeric.Quaternion
import qualified Numeric.Tuple.Lazy   as LT
import qualified Numeric.Tuple.Strict as ST

-- See dimensions:Numeric.Dimensions.Dim
#define IS_UNSOUND_MATCHING_810_900 (MIN_VERSION_GLASGOW_HASKELL(8,10,0,0) && !MIN_VERSION_GLASGOW_HASKELL(9,1,0,0))

-- | Maximum number of elements in SomeDims lists
maxDims :: Word
maxDims = 5

-- | Some tests are rather slow when we have too many elements
maxTotalDim :: Word
maxTotalDim = 400

-- | Maximum value of @Dim (XN _)@
maxDimSize :: Word
maxDimSize = 30

-- | Odds of using `fromScalar` constructor instead of element-by-element.
--   Interpret as "one to fromScalarChanceFactor"
fromScalarChanceFactor :: Int
fromScalarChanceFactor = 5

concatDimsQ :: TypeQ -> TypeQ -> TypeQ
concatDimsQ as bs = [t| Concat $(as) $(bs) |]

aFewPropVariants :: ExpQ -> ExpQ
aFewPropVariants = TH.listE . replicate 100

someElemTypeFQ :: Q TypeQ
someElemTypeFQ = TH.runIO . generate $ elements elemTypesF

someElemTypeIQ :: Q TypeQ
someElemTypeIQ = TH.runIO . generate $ elements elemTypesI

someElemTypeQ :: Q TypeQ
someElemTypeQ = TH.runIO . generate . elements $ elemTypesI ++ elemTypesF

elemTypesF :: [TypeQ]
elemTypesF = [ [t|Float|], [t|Double|] ]

elemTypesI :: [TypeQ]
elemTypesI =
  [ [t|Int|],  [t|Int8|],  [t|Int16|],  [t|Int32|],  [t|Int64|]
  , [t|Word|], [t|Word8|], [t|Word16|], [t|Word32|], [t|Int64|]
  ]


-- | Generate type (n :: Nat) at compile time
someDimNQ :: Q TypeQ
someDimNQ = TH.litT . TH.numTyLit . toInteger
         <$> TH.runIO (generate (chooseDim 1))

-- | Generate type (n :: XNat) at compile time
someDimXQ :: Q TypeQ
someDimXQ = fmap pure $ do
  d <- TH.litT . TH.numTyLit . toInteger
     <$> TH.runIO (generate (chooseDim 1))
  TH.runIO (generate arbitrary) >>=
    \isXN -> if isXN then [t|XN $(d)|] else [t|N $(d)|]

-- | Generate type (N n) at compile time
someDimXfixedQ :: Q TypeQ
someDimXfixedQ = fmap pure $ do
  d <- TH.litT . TH.numTyLit . toInteger
     <$> TH.runIO (generate (chooseDim 1))
  [t| N $(d) |]

-- | Generate type (ns :: [k]) at compile time
someDimsQ :: Q TypeQ
someDimsQ = do
  isXNat <- TH.runIO (generate arbitrary)
  if isXNat then someDimsXQ
            else someDimsNQ

-- | Generate type (ns :: [Nat]) at compile time
someDimsNQ :: Q TypeQ
someDimsNQ = fmap pure $ TH.runIO (removeDims <$> generate arbitrary)
         >>= \(SomeDims ds) -> wordsToDimsNQ (listDims ds)

-- | Generate type (Map 'N (ns :: [Nat])) at compile time
someDimsXQfixed :: Q TypeQ
someDimsXQfixed = fmap pure $ TH.runIO (removeDims <$> generate arbitrary)
         >>= \(SomeDims ds) -> wordsToDimsXQfixed (listDims ds)

-- | Generate type (Map 'N (ns :: [Nat])) at compile time
someDimsXQ :: Q TypeQ
someDimsXQ = fmap pure $ TH.runIO (removeDims <$> generate arbitrary)
         >>= \(SomeDims ds) -> wordsToDimsXQ(listDims ds)

-- | Generate type (ns :: [k]) at compile time
someLenAsBsQ :: Int -> Q (TypeQ, TypeQ)
someLenAsBsQ len = do
  isXNat <- TH.runIO (generate arbitrary)
  if isXNat then someLenAsBsXQ len
            else someLenAsBsNQ len

-- | Generate type (ns :: [k]) at compile time
someAsBsQ :: Q (TypeQ, TypeQ)
someAsBsQ = do
  isXNat <- TH.runIO (generate arbitrary)
  if isXNat then someAsBsXQ
            else someAsBsNQ

-- | Generate type (ns :: [k]) at compile time
someAsBsQfixed :: Q (TypeQ, TypeQ)
someAsBsQfixed = do
  isXNat <- TH.runIO (generate arbitrary)
  if isXNat then someAsBsXQfixed
            else someAsBsNQ

-- | Generate type (as :: [Nat], bs :: [Nat]) at compile time,
--   given a fixed length of the first dims (as).
someLenAsBsNQ :: Int -> Q (TypeQ, TypeQ)
someLenAsBsNQ len = do
    (asw, bsw) <- TH.runIO (generate (genConcatDims len))
    as <- wordsToDimsNQ asw
    bs <- wordsToDimsNQ bsw
    return (pure as, pure bs)

-- | Generate type (as :: [XNat], Map 'N bs :: [XNat]) at compile time,
--   given a fixed length of the first dims (as).
someLenAsBsXQ :: Int -> Q (TypeQ, TypeQ)
someLenAsBsXQ len = do
    (asw, bsw) <- TH.runIO (generate (genConcatDims len))
    as <- wordsToDimsXQ asw
    bs <- wordsToDimsXQfixed bsw
    return (pure as, pure bs)

-- | Generate type (Map 'N as :: [XNat], Map 'N bs :: [XNat]) at compile time,
--   given a fixed length of the first dims (as).
someLenAsBsXQfixed :: Int -> Q (TypeQ, TypeQ)
someLenAsBsXQfixed len = do
    (asw, bsw) <- TH.runIO (generate (genConcatDims len))
    as <- wordsToDimsXQfixed asw
    bs <- wordsToDimsXQfixed bsw
    return (pure as, pure bs)

-- | Generate type (as :: [Nat], bs :: [Nat]) at compile time.
someAsBsNQ :: Q (TypeQ, TypeQ)
someAsBsNQ = TH.runIO (generate (choose (0, fromIntegral maxDims - 1)))
  >>= someLenAsBsNQ

-- | Generate type (as :: [XNat], Map 'N bs :: [XNat]) at compile time.
someAsBsXQ :: Q (TypeQ, TypeQ)
someAsBsXQ = TH.runIO (generate (choose (0, fromIntegral maxDims - 1)))
  >>= someLenAsBsXQ

-- | Generate type (Map 'N as :: [XNat], Map 'N bs :: [XNat]) at compile time.
someAsBsXQfixed:: Q (TypeQ, TypeQ)
someAsBsXQfixed = TH.runIO (generate (choose (0, fromIntegral maxDims - 1)))
  >>= someLenAsBsXQfixed


wordsToDimsNQ :: [Word] -> TypeQ
wordsToDimsNQ = (\ns -> [t| ($(ns) :: [Nat]) |]) . foldr f TH.promotedNilT
  where
    f :: Word -> TypeQ -> TypeQ
    f w l = let d = TH.litT . TH.numTyLit $ toInteger w
            in  TH.promotedConsT `TH.appT` d `TH.appT` l

wordsToDimsXQfixed :: [Word] -> TypeQ
wordsToDimsXQfixed = (\ns -> [t| ($(ns) :: [XNat]) |]) . foldr f TH.promotedNilT
  where
    f :: Word -> TypeQ -> TypeQ
    f w l = let d = TH.litT . TH.numTyLit $ toInteger w
            in  TH.promotedConsT `TH.appT` [t| N $(d) |] `TH.appT` l

wordsToDimsXQ :: [Word] -> TypeQ
wordsToDimsXQ = (\ns -> [t| ($(ns) :: [XNat]) |]) . foldr f TH.promotedNilT
  where
    f :: Word -> TypeQ -> TypeQ
    f w l = let d = TH.litT . TH.numTyLit $ toInteger w
                xd = TH.runIO (generate arbitrary) >>=
                        \isXN -> if isXN then [t|XN $(d)|] else [t|N $(d)|]
            in  TH.promotedConsT `TH.appT` xd `TH.appT` l

-- | Generate two lists of Dims as Words, such that the combined list
--   is under the maxTotalDim limit, but the first list has exactly the given
--   number of dims.
genConcatDims :: Int -> Gen ([Word], [Word])
genConcatDims len
    = arbitrary >>= (\(SomeDims xs) -> checkLen (listDims xs)) . removeDims
  where
    checkLen :: [Word] -> Gen ([Word], [Word])
    checkLen xs =
      let td = product xs
          (as, bs) = splitAt len xs
          l  = length as
      in if l == len then pure (as, bs)
                     else (,) <$> addDims (len - l) (maxTotalDim `quot` td) as
                              <*> pure bs
    addDims :: Int -> Word -> [Word] -> Gen [Word]
    addDims 0 _ xs = pure xs
    addDims n lim xs = do
      x <- choose (1, max 1 (min lim maxDimSize))
      addDims (n-1) (lim `quot` x) (x : xs)


-- | Remove dims from a dim list until its totalDim is less than maxTotalDim
removeDims :: SomeDims -> SomeDims
removeDims = removeDimsAbove maxTotalDim

-- | Remove dims from a dim list until its totalDim is less than a given value
removeDimsAbove :: Word -> SomeDims -> SomeDims
removeDimsAbove _ (SomeDims U) = SomeDims U
removeDimsAbove z (SomeDims nns@(_ :* ns))
  | totalDim nns > z = removeDimsAbove z (SomeDims ns)
  | otherwise        = SomeDims nns

-- | Reduce individual XN-dims untils its totalDim is less than maxTotalDim
reduceDims :: (All KnownDimType xns, BoundedDims xns)
           => Dims (xns :: [XNat]) -> Dims (xns :: [XNat])
reduceDims = reduceDims' 1

reduceDims' :: (All KnownDimType xns, BoundedDims xns)
            => Word -> Dims (xns :: [XNat]) -> Dims (xns :: [XNat])
reduceDims' _ U = U
reduceDims' l nns@(n :* ns)
  | totalDim nns * l <= maxTotalDim = nns
  | otherwise = case n of
      Dn d -> n :* reduceDims' (l * dimVal d) ns
      (Dx d :: Dim xn) -> case compareDim (dimBound @xn) D2 of
        SLT -> Dx D2 :* reduceDims' (l * 2) ns
        SEQ -> Dx D2 :* reduceDims' (l * 2) ns
        SGT -> n :* reduceDims' (l * dimVal d) ns

-- | Most of the time, we assume the error is proportional to the maginutude of
--   the biggest element.
maxElem :: (SubSpace t ds '[] ds, Ord t, Num t)
        => DataFrame t (ds :: [Nat]) -> Scalar t
maxElem = ewfoldr' (max . abs) 0

rotateList :: [a] -> [[a]]
rotateList xs = init (zipWith (++) (tails xs) (inits xs))

class (RealFloatExtras t, Show t) => Approx t x | x -> t where
    -- | Check if two values are approximately equal
    approxEq :: t -- ^ Extra multiplier constant
             -> x -> x -> Property

-- | Check if two values are approximately equal.
(=~=) :: Approx t x => x -> x -> Property
(=~=) = approxEq 1
infix 4 =~=

instance Approx Double Double where
    approxEq c a b = counterexample
      (unlines
        [ "  Double approxEq failed:"
        , "    values:    "   ++ show (a, b)
        , "    error:     "   ++ show err
        , "    tolerance: "   ++ show tol
        ]
      ) $ err <= tol
      where
        err = abs (a - b)
        mel = abs a `max` abs b `max` 1
        tol = M_EPS*mel*c

instance Approx Float Float where
    approxEq c a b = counterexample
      (unlines
        [ "  Double approxEq failed:"
        , "    values:    "   ++ show (a, b)
        , "    error:     "   ++ show err
        , "    tolerance: "   ++ show tol
        ]
      ) $ err <= tol
      where
        err = abs (a - b)
        mel = abs a `max` abs b `max` 1
        tol = M_EPS*mel*c

instance MonadFail Gen where fail = error

instance (Quaternion t, Arbitrary t, Num t) => Arbitrary (Quater t) where
  arbitrary = sequence
    [ Quater <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
    , Quater <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
    , Quater <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
    , Quater <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
    , Quater <$> arbitrary <*> pure 0 <*> pure 0 <*> pure 0
    , Quater <$> pure 0 <*> arbitrary <*> pure 0 <*> pure 0
    , Quater <$> pure 0 <*> pure 0 <*> arbitrary <*> pure 0
    , Quater <$> pure 0 <*> pure 0 <*> pure 0 <*> arbitrary
    , Quater <$> arbitrary <*> arbitrary <*> pure 0 <*> pure 0
    , Quater <$> arbitrary <*> pure 0 <*> arbitrary <*> pure 0
    , Quater <$> arbitrary <*> pure 0 <*> pure 0 <*> arbitrary
    , Quater <$> pure 0 <*> arbitrary <*> arbitrary <*> pure 0
    , Quater <$> pure 0 <*> arbitrary <*> pure 0 <*> arbitrary
    , Quater <$> pure 0 <*> pure 0 <*> arbitrary <*> arbitrary
    , Quater <$> arbitrary <*> arbitrary <*> arbitrary <*> pure 0
    , Quater <$> arbitrary <*> arbitrary <*> pure 0 <*> arbitrary
    , Quater <$> arbitrary <*> pure 0 <*> arbitrary <*> arbitrary
    , Quater <$> pure 0 <*> arbitrary <*> arbitrary <*> arbitrary
    ] >>= elements

  shrink (Quater x y z t)
      -- shrink either real or the whole imaginary part
    = ($) <$> zipWith3 Quater (shrink x) (shrink y) (shrink z) <*> shrink t

instance (RealFloatExtras t, Show t, Quaternion t) => Approx t (Quater t) where
    approxEq c a b = counterexample
        (unlines
          [ "  Quaternion approxEq failed:"
          , "    max elem:  "   ++ show mel
          , "    error:     "   ++ show err
          , "    tolerance: "   ++ show tol
          ]
        ) $ err <= tol
      where
        v1 = toVec4 a
        v2 = toVec4 b
        err = unScalar $ maxElem (v1 - v2)
        mel = unScalar $ 1 `max` maxElem v1 `max` maxElem v2
        tol = M_EPS*mel*c

instance (Arbitrary t, PrimBytes t, Num t, Ord t, Dimensions ds)
      => Arbitrary (DataFrame t (ds :: [Nat])) where
    arbitrary = do
        full <- (1 < ) <$> choose (1, fromScalarChanceFactor)
        zeroChance <- choose (0, 8)
        if full -- I want to check fromScalar code path sometimes
        then arbitrary >>= elementWise @_ @ds @'[] (f zeroChance) . ewgen . scalar
        else fromScalar . scalar <$> arbitrary
      where
        f :: (Arbitrary a, Num a) => Double -> Scalar a -> Gen (Scalar a)
        f zeroChance _ = do
          dice <- (zeroChance >=) <$> choose (0, 10)
          if dice
          then return 0
          else scalar <$> arbitrary
    shrink df
        | Just (Max ma) <- ewfoldMap @t @ds @'[] @ds
            ((\x -> if x == 0 then Nothing else Just (Max x)) . abs) df
           = [ ewmap (\x -> if abs x == ma then 0 else x) df
             , ewmap (scalar . withAbs . unScalar) df
             ]
        | otherwise = []
            where
              withAbs :: t -> t
              withAbs x
                | abs x <= 1 = 0
                | otherwise  = signum x * closest2 (abs x) 1
              closest2 :: t -> t -> t
              closest2 x b = if x <= b * 2 then b else closest2 x (b*2)

instance (RealFloatExtras t, Show t, Dimensions ds)
       => Approx (DataFrame t ('[] :: [Nat])) (DataFrame t (ds :: [Nat])) where
    approxEq (S t) = approxEqDF t

instance (RealFloatExtras t, Show t)
       => Approx (DataFrame t ('[] :: [XNat])) (DataFrame t (ds :: [XNat])) where
    approxEq (XFrame (S t)) (XFrame a) (XFrame b) = case sameDims da db of
        Just Dict -> approxEqDF t a b
        Nothing -> counterexample
          (unlines
            [ "  DataFrame approxEq failed [XNat] due to different dims:"
            , "    dims a:  "   ++ show da
            , "    dims b:  "   ++ show db
            ]
          ) False
      where
        da = dims `inSpaceOf` a
        db = dims `inSpaceOf` b

approxEqDF ::
       forall t (ds :: [Nat])
     . ( Dimensions ds
       , Show t, RealFloatExtras t
       , KnownBackend t ds)
    => t -> DataFrame t ds -> DataFrame t (ds :: [Nat]) -> Property
approxEqDF c a b
  | U <- dims @ds = counterexample
      (unlines
        [ "  Scalar approxEq failed:"
        , "    values:    "   ++ show (unScalar a, unScalar b)
        , "    error:     "   ++ show err
        , "    tolerance: "   ++ show tol
        ]
      ) $ err <= tol
  | otherwise = counterexample
      (unlines
        [ "  DataFrame approxEq failed:"
        , "    max elem:  "   ++ show mel
        , "    error:     "   ++ show err
        , "    tolerance: "   ++ show tol
        ]
      ) $ err <= tol
  where
    err = unScalar $ maxElem (a - b)
    mel = unScalar $ maxElem a `max` maxElem b `max` 1
    tol = M_EPS*mel*c

instance ( All Arbitrary ts, All PrimBytes ts, All Num ts, All Ord ts
         , RepresentableList ts, Dimensions ds)
      => Arbitrary (DataFrame ts (ds :: [Nat])) where
    -- We create arbitrary MultiFrame by combining several SingleFrames.
    -- SingleFrames are "variables" or "columns" of a MultiFrame that are
    -- independent byte arrays bounded by a common dimensions type signature.
    arbitrary = -- Use RepresentableList to find out how many columns are there.
                case tList @ts of
        -- Zero columns, empty MultiFrame
        U -> return Z
        -- Cons-like construction.
        -- Note, pattern matching TypeList brings RepresentableList evidence
        -- for Tail ts.
        _ :* (TypeList :: TypeList ts') -> do
          at   <- arbitrary
          ats' <- arbitrary @(DataFrame ts' ds)
          return (at :*: ats')
    -- MultiFrame is a newtype wrapper on a TypedList.
    -- Thus, we can always recover RepresentableList ts by using function @types@
    shrink (at :*: ats@(MultiFrame ats'))
      | TypeList <- types ats'
      = (:*:) <$> shrink at <*> shrink ats
    shrink _ = []

-- | Generate a random word not smaller than the given number, and not bigger
--   than maxDimSize. Also tend to generate smaller values more often to have
--   a chance to pack more dims within maxTotalDim.
chooseDim :: Word -> Gen Word
chooseDim lowLim = f <$> choose (0, maxDimSize)
  where
    l = realToFrac lowLim :: Double
    f :: Word -> Word
    f w = let x = realToFrac w :: Double
              n = realToFrac maxDimSize :: Double
          in round $ l + max 0 (n - l) * (x / n) ** 3

instance KnownDim a => Arbitrary (Dim (N a)) where
    arbitrary = return $ Dn (dim @a)
    shrink _ = []

instance KnownDim m => Arbitrary (Dim (XN m)) where
    arbitrary = do
      dimN <- chooseDim $ dimVal' @m
      case constrainDim @(XN m) (someDimVal dimN) of
        Nothing -> error "impossible argument"
        Just d  -> return d
    shrink _ = []

instance Arbitrary SomeDims where
    arbitrary = do
      dimN <- choose (0, maxDims) :: Gen Word
      wdims <- mapM (\_ -> chooseDim 1) [1..dimN]
      return $ someDimsVal wdims
    shrink (SomeDims U)         = []
    shrink (SomeDims (_ :* ds)) = [SomeDims ds]

instance Arbitrary (Dims '[]) where
    arbitrary = return U
    shrink _ = []

instance (KnownDim n, Arbitrary (Dims xs)) => Arbitrary (Dims (N n ': xs)) where
    arbitrary = (:*) <$> arbitrary <*> arbitrary
    shrink _ = []

instance (KnownDim m, Arbitrary (Dims xs)) => Arbitrary (Dims (XN m ': xs)) where
    arbitrary = (:*) <$> arbitrary <*> arbitrary
    shrink _ = []

instance (Arbitrary t, PrimBytes t, Num t, Ord t)
      => Arbitrary (SomeDataFrame t) where
    arbitrary = do
      SomeDims (Dims :: Dims ds) <- removeDims <$> arbitrary
      SomeDataFrame <$> arbitrary @(DataFrame t ds)
    shrink (SomeDataFrame df) = SomeDataFrame <$> shrink df

-- All same as above, just change constraints a bit
instance ( All Arbitrary ts, All PrimBytes ts, All Num ts, All Ord ts
         , RepresentableList ts)
      => Arbitrary (SomeDataFrame ts) where
    arbitrary = do
      SomeDims ds <- removeDims <$> arbitrary
      case ds of
        (Dims :: Dims ds) -> case inferKnownBackend @ts @ds of
          Dict -> SomeDataFrame <$> arbitrary @(DataFrame ts ds)
#if IS_UNSOUND_MATCHING_810_900
        _ -> error "Numeric.Arbitraries.Arbitrary.arbitratry/ds: impossible pattern"
#endif
    shrink (SomeDataFrame df) = SomeDataFrame <$> shrink df


instance ( Arbitrary t, PrimBytes t, Num t, Ord t
         , Arbitrary (Dims xs), All KnownDimType xs, BoundedDims xs)
      => Arbitrary (DataFrame t (xs :: [XNat])) where
    arbitrary = do
      XDims (Dims :: Dims ds) <- reduceDims <$> arbitrary @(Dims xs)
      XFrame <$> arbitrary @(DataFrame t ds)
    shrink (XFrame df) = XFrame <$> shrink df

instance ( All Arbitrary ts, All PrimBytes ts, All Num ts, All Ord ts
         , RepresentableList ts
         , Arbitrary (Dims xs), All KnownDimType xs, BoundedDims xs)
      => Arbitrary (DataFrame ts (xs :: [XNat])) where
    arbitrary = do
      ds <- reduceDims <$> arbitrary @(Dims xs)
      case ds of
        XDims (Dims :: Dims ds) -> case inferKnownBackend @ts @ds of
          Dict -> XFrame <$> arbitrary @(DataFrame ts ds)
#if IS_UNSOUND_MATCHING_810_900
        _ -> error "Numeric.Arbitraries.Arbitrary.arbitratry/ds: impossible pattern"
#endif
    shrink (XFrame df) = XFrame <$> shrink df


instance KnownDim n => Arbitrary (Idx (n :: Nat)) where
    arbitrary = Idx <$> choose (minBound, dimVal' @n - 1)

instance KnownDim n => Arbitrary (Idx (N n)) where
    arbitrary = Idx <$> choose (minBound, dimVal' @n - 1)

instance KnownDim n => Arbitrary (Idx (XN n)) where
    arbitrary = Idx <$> choose (minBound, dimVal' @n - 1)

instance Dimensions ns => Arbitrary (Idxs (ns :: [Nat])) where
    arbitrary = go (dims @ns)
      where
        go :: forall (bs :: [Nat]) . Dims bs -> Gen (Idxs bs)
        go U         = pure U
        go (D :* bs) = (:*) <$> arbitrary <*> go bs

instance (BoundedDims ns, All KnownDimType ns) => Arbitrary (Idxs (ns :: [XNat])) where
    arbitrary = go (minimalDims @ns)
      where
        go :: forall (bs :: [XNat])
            . (BoundedDims bs, All KnownDimType bs) => Dims bs -> Gen (Idxs bs)
        go U            = pure U
        go (Dn D :* bs) = (:*) <$> arbitrary <*> go bs
        go (Dx D :* bs) = (:*) <$> arbitrary <*> go bs


instance (RepresentableList xs, All Arbitrary xs) => Arbitrary (ST.Tuple xs) where
    arbitrary = go (tList @xs)
      where
        go :: forall (bs :: [Type])
            . All Arbitrary bs
           => TypeList bs -> Gen (ST.Tuple bs)
        go U         = pure U
        go (_ :* bs) = (ST.:$) <$> arbitrary <*> go bs

instance (RepresentableList xs, All Arbitrary xs) => Arbitrary (LT.Tuple xs) where
    arbitrary = go (tList @xs)
      where
        go :: forall (bs :: [Type])
            . All Arbitrary bs
           => TypeList bs -> Gen (LT.Tuple bs)
        go U         = pure U
        go (_ :* bs) = (LT.:$) <$> arbitrary <*> go bs

data AnyMatrix
data NonSingular

data SomeSquareMatrix prop t
  = forall (n :: Nat)
  . (KnownDim n, KnownBackend t '[n], KnownBackend t '[n, n])
  => SSM (DataFrame t '[n,n])

instance (Show t, PrimBytes t) => Show (SomeSquareMatrix prop t) where
  show (SSM df) = show df

instance (Arbitrary t, PrimBytes t, Num t, Ord t)
      => Arbitrary (SomeSquareMatrix AnyMatrix t) where
    arbitrary = do
      Dx (D :: Dim n) <- arbitrary @(Dim (XN 2))
      SSM <$> arbitrary @(DataFrame t '[n,n])
    shrink (SSM df)= SSM <$> shrink df

instance (Arbitrary t, PrimBytes t, Num t, Ord t)
      => Arbitrary (SomeSquareMatrix NonSingular t) where
    arbitrary = do
      SSM (someMat :: DataFrame t '[n, n]) <- arbitrary @(SomeSquareMatrix AnyMatrix t)
      -- https://en.wikipedia.org/wiki/Diagonally_dominant_matrix
      return . SSM $
        iwmap @t @'[n] @'[n] @'[n,n]
              @t @'[n] @'[n,n]
          ( \i v ->
            let s = ewfoldl (\a -> (a +) . abs) 1 v
            in update i s v
          ) someMat
    shrink _ = []