packages feed

cabal-matrix-1.0.0.0: src/Cabal/Matrix/Rectangle.hs

-- | Designed for qualified import. Operations are strict.
module Cabal.Matrix.Rectangle
  ( Rectangle(..)
  , rows
  , indexRow
  , toRowMajor
  , fromRowMajor
  , mapRows
  , columns
  , indexColumn
  , toColumnMajor
  , mapColumns
  , indexCell

  , unitRow
  , empty
  , vertical

  , productRows
  , blockDiagonal
  , appendRowsUnioningColumns
  , subtractRowsBySubsetColumns
  , unCartesianProduct
  ) where

import Control.Monad.ST
import Data.Foldable
import Data.IntSet qualified as IntSet
import Data.Map.Strict qualified as Map
import Data.Primitive
import Data.Set qualified as Set


-- | A rectangle is a collection of rows of type @a@, columns of type @b@, and
-- for each row and column pair a cell of type @c@.
data Rectangle a b c = Rectangle
  {-# UNPACK #-} !(Array a)
  {-# UNPACK #-} !(Array b)
  {-# UNPACK #-} !(Array c)
  -- ^ INVARIANT: length of the third array is the product of the lengths of the
  -- first two
  deriving stock (Show, Functor)

rows :: Rectangle a b c -> Array a
rows (Rectangle rs _ _) = rs

indexRow :: Rectangle a b c -> Int -> (a, [(b, c)])
indexRow (Rectangle rs cs cells) y =
  ( indexArray rs y
  , [ (indexArray cs x, indexArray cells (y * width + x))
    | x <- [0 .. width - 1]
    ]
  )
  where
    !width = sizeofArray cs

toRowMajor :: Rectangle a b c -> [(a, [(b, c)])]
toRowMajor rect@(Rectangle rs _ _)
  = [indexRow rect y | y <- [0 .. height - 1]]
  where
    !height = sizeofArray rs

fromRowMajor :: [b] -> [(a, [c])] -> Rectangle a b c
fromRowMajor cols pairs = Rectangle rs cs cells
  where
    !cs = arrayFromList cols
    !width = sizeofArray cs
    !height = length pairs
    !rs = arrayFromListN height $ fst <$> pairs
    !cells = arrayFromListN (width * height)
      [ x
      | (_, row) <- pairs
      , x <- if length row == width
        then row
        else error "fromRowMajor: incorrect row width"
      ]

mapRows :: (a -> a') -> Rectangle a b c -> Rectangle a' b c
mapRows f (Rectangle rs cs cells) = Rectangle (mapArray' f rs) cs cells

columns :: Rectangle a b c -> Array b
columns (Rectangle _ cs _) = cs

indexColumn :: Rectangle a b c -> Int -> (b, [(a, c)])
indexColumn (Rectangle rs cs cells) x =
  ( indexArray cs x
  , [ (indexArray rs y, indexArray cells (y * width + x))
    | y <- [0 .. height - 1]
    ]
  )
  where
    !width = sizeofArray cs
    !height = sizeofArray rs

toColumnMajor :: Rectangle a b c -> [(b, [(a, c)])]
toColumnMajor rect@(Rectangle _ cs _)
  = [indexColumn rect x | x <- [0 .. width - 1]]
  where
    !width = sizeofArray cs

mapColumns :: (b -> b') -> Rectangle a b c -> Rectangle a b' c
mapColumns f (Rectangle rs cs cells) = Rectangle rs (mapArray' f cs) cells

indexCell :: Rectangle a b c -> Int -> Int -> c
indexCell (Rectangle _ cs cells) x y = indexArray cells (y * width + x)
  where
    !width = sizeofArray cs

unitRow :: a -> Rectangle a b c
unitRow !x = Rectangle (arrayFromListN 1 [x]) emptyArray emptyArray

empty :: Rectangle a b c
empty = Rectangle emptyArray emptyArray emptyArray

vertical :: b -> [(a, c)] -> Rectangle a b c
vertical c pairs = Rectangle
  (arrayFromList $ fst <$> pairs)
  (arrayFromListN 1 [c])
  (arrayFromList $ snd <$> pairs)

-- | Applies the function strictly
productRows
  :: (a -> a' -> a'')
  -> Rectangle a b c
  -> Rectangle a' b c
  -> Rectangle a'' b c
productRows combine (Rectangle rs cs cells) (Rectangle rs' cs' cells') =
  Rectangle
    (createArray height'' (error "urk") \m -> do
      for_ [0 .. height - 1] \y -> do
        let !r = (indexArray rs y)
        for_ [0 .. height' - 1] \y' -> do
          writeArray m (y * height' + y')
            $! combine r (indexArray rs' y'))
    (cs <> cs')
    (createArray (width'' * height'') (error "urk") \m -> do
      for_ [0 .. height - 1] \y -> do
        for_ [0 .. height' - 1] \y' -> do
          copyArray m ((y * height' + y') * width'')
            cells (y * width) width
          copyArray m ((y * height' + y') * width'' + width)
            cells' (y' * width') width')
  where
    !width = sizeofArray cs
    !width' = sizeofArray cs'
    !width'' = width + width'
    !height = sizeofArray rs
    !height' = sizeofArray rs'
    !height'' = height * height'

blockDiagonal :: Rectangle a b c -> c -> c -> Rectangle a b c -> Rectangle a b c
blockDiagonal (Rectangle rs cs cells) !tl !br (Rectangle rs' cs' cells') =
  Rectangle
    (rs <> rs')
    (cs <> cs')
    (createArray (width'' * height'') (error "urk") \m -> do
      for_ [0 .. height - 1] \y -> do
        copyArray m (y * width'')
          cells (y * width) width
        for_ [0 .. width' - 1] \x' -> do
          writeArray m (y * width'' + width + x') tl
      for_ [0 .. height' - 1] \y' -> do
        for_ [0 .. width - 1] \x -> do
          writeArray m ((height + y') * width'' + x) br
        copyArray m ((height + y') * width'' + width)
          cells' (y' * width') width'
        )
  where
    !width = sizeofArray cs
    !width' = sizeofArray cs'
    !width'' = width + width'
    !height = sizeofArray rs
    !height' = sizeofArray rs'
    !height'' = height + height'

data Source b c
  = FromNeither !b !c
  | FromLeft {-# UNPACK #-} !Int !c
  | FromRight {-# UNPACK #-} !Int !c
  | FromBoth {-# UNPACK #-} !Int {-# UNPACK #-} !Int (b -> b -> b)

appendRowsWithPairing
  :: Array (Source b c) -> Rectangle a b c -> Rectangle a b c -> Rectangle a b c
appendRowsWithPairing pairing (Rectangle rs cs cells) (Rectangle rs' cs' cells')
  = Rectangle
    (rs <> rs')
    (flip mapArray' pairing \case
      FromNeither c _ -> c
      FromLeft x _ -> indexArray cs x
      FromRight x' _ -> indexArray cs' x'
      FromBoth x x' combine -> combine (indexArray cs x) (indexArray cs' x'))
    (createArray (width'' * height'') (error "urk") \m -> do
      for_ [0 .. height - 1] \y -> do
        for_ [0 .. width'' - 1] \x'' -> do
          writeArray m (y * width'' + x'')
            $! case indexArray pairing x'' of
              FromNeither _ cell -> cell
              FromLeft x _ -> indexArray cells (y * width + x)
              FromRight _ cell -> cell
              FromBoth x _ _ -> indexArray cells (y * width + x)
      for_ [0 .. height' - 1] \y' -> do
        for_ [0 .. width'' - 1] \x'' -> do
          writeArray m ((height + y') * width'' + x'')
            $! case indexArray pairing x'' of
              FromNeither _ cell -> cell
              FromLeft _ cell -> cell
              FromRight x' _ -> indexArray cells' (y' * width' + x')
              FromBoth _ x' _ -> indexArray cells' (y' * width' + x'))
  where
    !width = sizeofArray cs
    !width' = sizeofArray cs'
    !width'' = sizeofArray pairing
    !height = sizeofArray rs
    !height' = sizeofArray rs'
    !height'' = height + height'

makePairing :: Ord b => c -> Array b -> Array b -> Array (Source b c)
makePairing dflt left right = arrayFromList $ go initMap initSet 0
  where
    !leftSize = sizeofArray left
    !rightSize = sizeofArray right
    !initMap = Map.fromListWith IntSet.union
      [ (indexArray right i, IntSet.singleton i)
      | i <- [0 .. rightSize - 1]
      ]
    !initSet = IntSet.fromDistinctAscList [0 .. rightSize - 1]

    go !m !s !i
      | i >= leftSize = (`FromRight` dflt) <$> IntSet.toAscList s
      | !l <- indexArray left i
      , Just js <- Map.lookup l m
      , (j, js') <- IntSet.deleteFindMin js
      = FromBoth i j const : go
        (if IntSet.null js' then Map.delete l m else Map.insert l js' m)
        (IntSet.delete j s) (i + 1)
      | otherwise = FromLeft i dflt : go m s (i + 1)

appendRowsUnioningColumns
  :: Ord b => c -> Rectangle a b c -> Rectangle a b c -> Rectangle a b c
appendRowsUnioningColumns dflt left right = appendRowsWithPairing
  (makePairing dflt (columns left) (columns right)) left right

filterRowsBySelector
  :: Array Int -> (Array c -> Bool) -> Rectangle a b c -> Rectangle a b c
filterRowsBySelector selector predicate (Rectangle rs cs cells) = runST do
  mrs' <- newArray height (error "urk")
  mcells' <- newArray (width * height) (error "urk")
  let
    go !y !y'
      | y >= height
      = do
        rs' <- freezeArray mrs' 0 y'
        cells' <- freezeArray mcells' 0 (y' * width)
        pure $ Rectangle rs' cs cells'
      | predicate $ mapArray' (\x -> indexArray cells (y * width + x)) selector
      = do
        writeArray mrs' y' (indexArray rs y)
        copyArray mcells' (y' * width) cells (y * width) width
        go (y + 1) (y' + 1)
      | otherwise
      = go (y + 1) y'
  go 0 0
  where
    !width = sizeofArray cs
    !height = sizeofArray rs

makeSelector :: Ord b => Array b -> Array b -> Maybe (Array Int)
makeSelector left right = go initMap 0 []
  where
    !leftSize = sizeofArray left
    !rightSize = sizeofArray right
    !initMap = Map.fromListWith IntSet.union
      [ (indexArray left i, IntSet.singleton i)
      | i <- [0 .. leftSize - 1]
      ]

    go !m !i out
      | i >= rightSize = Just $ arrayFromListN rightSize $ reverse out
      | !r <- indexArray right i
      , Just js <- Map.lookup r m
      , (!j, !js') <- IntSet.deleteFindMin js
      = go
        (if IntSet.null js' then Map.delete r m else Map.insert r js' m)
        (i + 1) (j:out)
      | otherwise = Nothing

cellsRowMajorArr :: Rectangle a b c -> [Array c]
cellsRowMajorArr (Rectangle rs cs cells) =
  [ cloneArray cells (y * width) width
  | y <- [0 .. height - 1]
  ]
  where
    !width = sizeofArray cs
    !height = sizeofArray rs

subtractRowsBySubsetColumns
  :: (Ord b, Ord c) => Rectangle a b c -> Rectangle a' b c -> Rectangle a b c
subtractRowsBySubsetColumns !rect !rect'
  = case makeSelector (columns rect) (columns rect') of
    Nothing -> rect
    Just selector
      | !rowSet <- Set.fromList $ cellsRowMajorArr rect'
      -> filterRowsBySelector selector (`Set.notMember` rowSet) rect

partitionByBitmaskAtOffset :: Array Bool -> Array a -> Int -> (Array a, Array a)
partitionByBitmaskAtOffset !bitmask !input !offset = runST do
  mfalses <- newArray len (error "urk")
  mtrues <- newArray len (error "urk")
  let
    go !f !t !i
      | i >= len = (,)
        <$> freezeArray mfalses 0 f
        <*> freezeArray mtrues 0 t
      | indexArray bitmask i = do
        writeArray mtrues t $ indexArray input (offset + i)
        go f (t + 1) (i + 1)
      | otherwise = do
        writeArray mfalses f $ indexArray input (offset + i)
        go (f + 1) t (i + 1)
  go 0 0 0
  where
    !len = sizeofArray bitmask

unCartesianProduct
  :: Ord c
  => Array Bool
  -> Rectangle a b c
  -> (Rectangle () b c, Rectangle () b c, Rectangle () () (Maybe Int))
unCartesianProduct bitmask (Rectangle rs cs cells) = runST do
  mfalses <- newArray (fwidth * height) (error "urk")
  mtrues <- newArray (twidth * height) (error "urk")
  mfis <- newPrimArray height
  mtis <- newPrimArray height
  let
    go !y !fMap !tMap
      | y >= height = do
        fcells <- freezeArray mfalses 0 (fwidth * fSize)
        tcells <- freezeArray mtrues 0 (twidth * tSize)
        let !frs = createArray fSize () \_ -> pure ()
        let !trs = createArray tSize () \_ -> pure ()
        icells <- do
          m <- newArray (fSize * tSize) Nothing
          for_ [0 .. height - 1] \i -> do
            fi <- readPrimArray mfis i
            ti <- readPrimArray mtis i
            writeArray m (ti * fSize + fi) (Just i)
          unsafeFreezeArray m
        pure
          ( Rectangle
            frs
            fcs
            fcells
          , Rectangle
            trs
            tcs
            tcells
          , Rectangle
            trs
            frs
            icells
          )
      | (!fc, !tc) <- partitionByBitmaskAtOffset bitmask cells (y * width)
      = do
        !fMap' <- case Map.lookup fc fMap of
          Nothing -> do
            copyArray mfalses (fSize * fwidth) fc 0 fwidth
            writePrimArray mfis y fSize
            pure $ Map.insert fc fSize fMap
          Just i -> do
            writePrimArray mfis y i
            pure fMap
        !tMap' <- case Map.lookup tc tMap of
          Nothing -> do
            copyArray mtrues (tSize * twidth) tc 0 twidth
            writePrimArray mtis y tSize
            pure $ Map.insert tc tSize tMap
          Just i -> do
            writePrimArray mtis y i
            pure tMap
        go (y + 1) fMap' tMap'
      where
        !fSize = Map.size fMap
        !tSize = Map.size tMap
  go 0 Map.empty Map.empty
  where
    !width = sizeofArray cs
    !height = sizeofArray rs
    (!fcs, !tcs) = partitionByBitmaskAtOffset bitmask cs 0
    !fwidth = sizeofArray fcs
    !twidth = sizeofArray tcs