packages feed

huihua-0.1.0.1: src/Huihua/Array.hs

-- | uiua API for harry
module Huihua.Array
  ( dyadicPervasive,
    reduceU,
    reduce1U,

    -- * Mirror of uiua API
    not,
    sign,
    negate',
    absolute,
    sqrt,
    sin,
    floor,
    ceiling,
    round,
    sig,
    add,
    subtract,
    multiply,
    divide,
    equals,
    notequals,
    lessThan,
    lessOrEqual,
    greaterThan,
    greaterOrEqual,
    modulus,
    modD,
    power,
    logarithm,
    minimum,
    maximum,
    atangent,
    length,
    shape,
    range,
    first,
    reverse,
    deshape,
    fix,
    bits',
    bits,
    transpose,
    rise,
    fall,
    where',
    classifyScan,
    classify,
    deduplicateScan,
    deduplicate,
    uniqueScan,
    unique,
    member,
    indexOf,
    couple,
    match,
    pick,
    rotate,
    join,
    select,
    take,
    drop,
    reshape,
    rerank,
    windows,
    keep,
    find,
    mask,
    equalsR,
    notEqualsR,
    lessThanR,
    lessOrEqualR,
    greaterThanR,
    greaterOrEqualR,
    addR,
    subtractR,
    multiplyR,
    divideR,
    minimumR,
    maximumR,
    modulusR,
    powerR,
    logarithmR,
    (¬),
  )
where

import Data.Bits hiding (rotate)
import Data.Bool hiding (not)
import Data.Foldable hiding (find, length, maximum, minimum)
import Data.Function hiding (fix)
import Data.List qualified as List
import Data.Map.Strict qualified as Map
import Data.Maybe
import Data.Ord
import Data.Set qualified as Set
import Data.Vector qualified as V
import Harpie.Array (Array (..))
import Harpie.Array qualified as D
import Harpie.Shape qualified as S
import Huihua.Warning
import Prelude hiding (ceiling, drop, floor, length, maximum, minimum, not, reverse, round, sin, sqrt, subtract, take)
import Prelude qualified as P

-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import Huihua.Array as A
-- >>> import Harpie.Array as D
-- >>> import Prettyprinter

-- | Dyadic pervasive
dyadicPervasive :: (b -> a -> c) -> Array a -> Array b -> Either HuihuaWarning (Array c)
dyadicPervasive op a b
  | D.shape a `List.isPrefixOf` D.shape b = Right (D.transmit (D.zipWith (flip op)) a b)
  | D.shape b `List.isPrefixOf` D.shape a = Right (D.transmit (D.zipWith op) b a)
  | otherwise = Left SizeMismatch

-- | Apply a binary boolean function, right-to-left
dyadicPervasiveBool :: (b -> a -> Bool) -> Array a -> Array b -> Either HuihuaWarning (Array Int)
dyadicPervasiveBool f = dyadicPervasive (\x x' -> sig (f x x'))

-- | https://www.uiua.org/docs/reduce
reduceU :: (a -> b -> b) -> b -> Array a -> Array b
reduceU f a0 a = D.reduces (S.exceptDims [0] (D.shape a)) (foldl' (flip f) a0) a

-- | Version for no identity functions
reduce1U :: (a -> a -> a) -> Array a -> Either HuihuaWarning (Array a)
reduce1U f a
  | null a = Left NoIdentity
  | D.length a == 1 = Right (D.select 0 0 a)
  | otherwise =
      let (x D.:> xs) = a
       in Right (D.zipWith (foldl' (flip f)) x (D.extracts (S.exceptDims [0] (D.shape xs)) xs))

-- * uiua api

not :: (Num a) => Array a -> Array a
not = fmap (1 -)

(¬) :: (Num a) => Array a -> Array a
(¬) = not

sign :: (Num a) => Array a -> Array a
sign = fmap signum

negate' :: (Num a) => Array a -> Array a
negate' = fmap negate

absolute :: (Num a) => Array a -> Array a
absolute = fmap abs

sqrt :: (Floating a) => Array a -> Array a
sqrt = fmap P.sqrt

sin :: (Floating a) => Array a -> Array a
sin = fmap P.sin

floor :: (RealFrac a) => Array a -> Array Int
floor = fmap P.floor

ceiling :: (RealFrac a) => Array a -> Array Int
ceiling = fmap P.ceiling

round :: (RealFrac a) => Array a -> Array Int
round = fmap P.round

sig :: Bool -> Int
sig = bool 0 1

add :: (Num a) => Array a -> Array a -> Either HuihuaWarning (Array a)
add = dyadicPervasive (+)

subtract :: (Num a) => Array a -> Array a -> Either HuihuaWarning (Array a)
subtract = dyadicPervasive (-)

multiply :: (Num a) => Array a -> Array a -> Either HuihuaWarning (Array a)
multiply = dyadicPervasive (*)

divide :: (Fractional a) => Array a -> Array a -> Either HuihuaWarning (Array a)
divide = dyadicPervasive (/)

equals :: (Eq a) => Array a -> Array a -> Either HuihuaWarning (Array Int)
equals = dyadicPervasiveBool (==)

notequals :: (Eq a) => Array a -> Array a -> Either HuihuaWarning (Array Int)
notequals = dyadicPervasiveBool (/=)

lessThan :: (Ord a) => Array a -> Array a -> Either HuihuaWarning (Array Int)
lessThan = dyadicPervasiveBool (<)

lessOrEqual :: (Ord a) => Array a -> Array a -> Either HuihuaWarning (Array Int)
lessOrEqual = dyadicPervasiveBool (<=)

greaterThan :: (Ord a) => Array a -> Array a -> Either HuihuaWarning (Array Int)
greaterThan = dyadicPervasiveBool (>)

greaterOrEqual :: (Ord a) => Array a -> Array a -> Either HuihuaWarning (Array Int)
greaterOrEqual = dyadicPervasiveBool (>=)

modulus :: Array Double -> Array Double -> Either HuihuaWarning (Array Double)
modulus = dyadicPervasive modD

-- | `mod` for doubles.
-- >>> modD 7.5 5
-- 2.5
modD :: Double -> Double -> Double
modD n d
  | d == (1 / 0) = n
  | d == 0 = 0 / 0
  | otherwise = n - d * fromIntegral (P.floor (n / d) :: Int)

power :: (Floating a) => Array a -> Array a -> Either HuihuaWarning (Array a)
power = dyadicPervasive (**)

logarithm :: (Floating a) => Array a -> Array a -> Either HuihuaWarning (Array a)
logarithm = dyadicPervasive (\x x' -> logBase x' x)

minimum :: (Ord a) => Array a -> Array a -> Either HuihuaWarning (Array a)
minimum = dyadicPervasive P.min

maximum :: (Ord a) => Array a -> Array a -> Either HuihuaWarning (Array a)
maximum = dyadicPervasive P.max

atangent :: (RealFloat a) => Array a -> Array a -> Either HuihuaWarning (Array a)
atangent = dyadicPervasive (flip atan2)

length :: Array a -> Array Int
length = D.toScalar . D.length

range :: Array Int -> Array Int
range a
  | D.rank a == 0 = bool (D.range [D.fromScalar a]) (fmap (negate 1 -) $ D.range [abs $ D.fromScalar a]) (D.fromScalar a < 0)
  | otherwise = D.join $ D.tabulate (D.arrayAs a') (\s -> D.asArray $ D.zipWith (\ab si -> bool ab (negate 1 - ab) (si < 0)) (D.asArray s) s')
  where
    a' = fmap abs a
    s' = fmap signum a

shape :: Array a -> Array Int
shape = D.asArray . D.shape

first :: Array a -> Array a
first = D.select 0 0

reverse :: Array a -> Array a
reverse a = D.reverses [0] a

deshape :: Array a -> Array a
deshape a = D.reshape [product (D.shape a)] a

fix :: Array a -> Array a
fix a = D.elongate 0 a

bits' :: Int -> Array Int
bits' x =
  bool id (fmap negate) (x < 0) $
    [0 ..] & P.take (finiteBitSize x' - countLeadingZeros x') & fmap (sig . testBit x') & D.asArray
  where
    x' = abs x

bits :: Array Int -> Array Int
bits a = D.join bs'
  where
    bs = fmap bits' a
    m = P.maximum (fmap D.length bs)
    bs' = fmap (D.pad 0 [m]) bs

-- | Rotate the axes by 1
transpose :: Array a -> Array a
transpose a = D.reorder (S.rotate 1 [0 .. D.rank a - 1]) a

rise :: (Ord a) => Array a -> Array Int
rise a = D.orders [0] $ D.extracts [0] a

fall :: (Ord a) => Array a -> Array Int
fall a = D.ordersBy [0] (fmap Down) $ D.extracts [0] a

where' :: Array Int -> Array Int
where' a = D.join $ D.asArray $ fmap D.asArray $ fold $ D.zipWith replicate a (D.indices (D.shape a))

classifyScan :: (Ord a) => [a] -> [Int]
classifyScan [] = []
classifyScan (x : xs) =
  (\(f, _, _) -> f)
    <$> scanl
      ( \(s, c, m) k ->
          maybe
            (c, c + 1, Map.insert k (1 + s) m)
            (,c,m)
            (Map.lookup k m)
      )
      (0, 1, Map.singleton x (0 :: Int))
      xs

classify :: (Ord a) => Array a -> Array Int
classify a = (D.asArray . classifyScan . D.arrayAs) $ D.extracts [0] a

deduplicateScan :: (Ord a) => [a] -> [(Maybe a, Set.Set a)]
deduplicateScan [] = []
deduplicateScan (x0 : xs) =
  scanl
    ( \(_, set) k ->
        bool
          (Just k, Set.insert k set)
          (Nothing, set)
          (Set.member k set)
    )
    (Just x0, Set.singleton x0)
    xs

deduplicate :: (Ord a) => Array a -> Array a
deduplicate a = D.joins [0] $ (D.asArray . mapMaybe fst . deduplicateScan . D.arrayAs) $ D.extracts [0] a

uniqueScan :: (Ord a) => [a] -> [(Int, Set.Set a)]
uniqueScan [] = []
uniqueScan (x0 : xs) =
  scanl
    ( \(_, set) k ->
        bool
          (1, Set.insert k set)
          (0, set)
          (Set.member k set)
    )
    (1, Set.singleton x0)
    xs

unique :: (Ord a) => Array a -> Array Int
unique a = (D.asArray . fmap fst . uniqueScan . D.arrayAs) (D.extracts [0] a)

member :: (Ord a) => Array a -> Array a -> Array Int
member i a
  | D.isScalar i = fmap (sig . Set.member (D.fromScalar i) . Set.fromList . toList) (D.extracts (S.exceptDims [D.rank a - 1] (D.shape a)) a)
  | otherwise = D.asArray (fmap sig ks)
  where
    spliti
      | D.rank i == 0 = D.singleton i
      | D.rank a - D.rank i == 1 = D.singleton i
      | otherwise = D.extracts (List.take (D.rank i - D.rank a + 1) [0 ..]) i
    aset = Set.fromList (toList (D.extracts [0] a))
    ks = (\x -> Set.member x aset) <$> spliti

indexOf :: (Eq a) => Array a -> Array a -> Array Int
indexOf i a
  | D.isScalar i = fmap (\x -> findI x (D.fromScalar i)) (D.extracts (S.exceptDims [D.rank a - 1] (D.shape a)) a)
  | D.rank a == 1 = fmap (findI a) i
  | otherwise = fmap (findI (D.extracts (S.exceptDims [D.rank a - 1] (D.shape a)) a)) (D.extracts (S.exceptDims [D.rank i - 1] (D.shape i)) i)
  where
    findI xs i' = fromMaybe (List.length xs) . List.elemIndex i' . toList $ xs

couple :: Array a -> Array a -> Array a
couple x y = D.couple 0 x y

match :: (Eq a) => Array a -> Array a -> Array Int
match a a' = D.toScalar (bool 0 1 (a == a'))

pick :: Array Int -> Array a -> Either HuihuaWarning (Array a)
pick i a
  | D.length (first i) > D.rank a = Left BadPick
  | otherwise = Right $ D.join $ fmap (\s -> D.rowWise D.indexes (D.arrayAs s) a) (D.extracts [0 .. D.rank i - 2] i)

rotate :: Array Int -> Array a -> Array a
rotate r a = D.rowWise (D.dimsWise D.rotate) (D.arrayAs r) a

-- | https://www.uiua.org/docs/join
join :: Array a -> Array a -> Either HuihuaWarning (Array a)
join a a'
  | P.drop 1 (D.shape a) == P.drop 1 (D.shape a') = Right $ D.concatenate 0 a a'
  | D.shape a == P.drop 1 (D.shape a') = Right $ D.prepend 0 a a'
  | P.drop 1 (D.shape a) == D.shape a' = Right $ D.append 0 a a'
  | D.shape a `List.isSuffixOf` D.shape a' = Right $ D.prepend 0 (D.repeat (List.drop 1 $ D.shape a') a) a'
  | D.shape a' `List.isSuffixOf` D.shape a = Right $ D.append 0 a (D.repeat (List.drop 1 $ D.shape a) a')
  | otherwise = Left SizeMismatch

-- | Select multiple rows from an array
select :: Array Int -> Array a -> Array a
select i a = D.joins [0 .. D.rank i - 1] $ (\x -> D.indexes [0] [x] a) <$> i

-- |
--
-- >>> pretty $ Huihua.Array.reshape (D.asArray [3,2]) (D.asArray [1..5::Int])
-- [[1,2],
--  [3,4],
--  [5,1]]
-- >>> pretty $ Huihua.Array.reshape (D.asArray [3,-1]) (D.asArray [1..8::Int])
-- [[1,2],
--  [3,4],
--  [5,6]]
reshape :: Array Int -> Array a -> Array a
reshape i a
  | D.rank i == 0 = D.repeat (D.fromScalar i : D.shape a) a
  | otherwise = D.array i' (V.take (product i') (V.concat (replicate (1 + product i' `div` V.length (D.asVector a)) (D.asVector a))))
  where
    iflat = D.arrayAs i
    hasNeg = any (< 0) iflat
    i' = bool iflat (fmap (\x -> bool x subDim (x < 0)) iflat) hasNeg
    subDim = D.size a `div` product (filter (>= 0) iflat)

rerank :: Array Int -> Array a -> Either HuihuaWarning (Array a)
rerank r a
  | P.not (D.isScalar r) = Left TypeMismatch
  | otherwise = Right (D.rerank r' a)
  where
    x = D.fromScalar r
    r' = bool (x + 1) (x + 1 + D.rank a) (x < 0)

take :: Array Int -> Array a -> Either HuihuaWarning (Array a)
take i a
  | D.rank i > 1 || D.length i > D.rank a = Left BadTake
  | otherwise = Right $ D.rowWise (D.dimsWise D.take) (D.arrayAs i) a

drop :: Array Int -> Array a -> Either HuihuaWarning (Array a)
drop i a
  | D.rank i > 1 || D.length i > D.rank a = Left BadTake
  | otherwise = Right $ D.rowWise (D.dimsWise D.drop) (D.arrayAs i) a

windows :: Array Int -> Array a -> Array a
windows ws a = D.windows ws' a
  where
    ws' = List.zipWith (\w s -> bool w (s - w + 1) (w < 0)) (D.arrayAs ws) (D.shape a)

keep :: Array Int -> Array a -> Array a
keep i a = D.join $ D.asArray $ fold $ D.zipWith replicate (D.cycle (List.take 1 $ D.shape a) i) (D.extracts [0] a)

find :: (Eq a) => Array a -> Array a -> Array Int
find i a = D.pad 0 (D.shape a :: [Int]) (fmap sig $ D.find i a)

mask :: (Eq a) => Array a -> Array a -> Array Int
mask i a = m
  where
    iexp = D.rerank (D.rank a) i
    found = fmap sig $ D.findNoOverlap iexp a
    accf = V.drop 1 . V.map snd . V.scanl' (\(acc, _) x -> bool (acc + 1, acc + 1) (acc, 0) (x == 0)) (0, 0)
    found' = D.unsafeModifyVector accf found
    found'' = D.pad 0 (D.shape a) found'
    start = (\s -> 1 - s) <$> D.shape iexp
    backchecks s = List.zip (List.zipWith (\s0 s' -> max 0 (s0 + s')) start s) (List.zipWith (\i' s' -> min i' (s' + 1)) (D.shape iexp) s)
    m = D.tabulate (D.shape a) (\s -> sum (D.rowWise (D.dimsWise (\d (o, l) -> D.slice d o l)) (backchecks s) found''))

-- * reducing operators

reduceBool :: (Num b) => (a -> a -> Bool) -> Array a -> Array b
reduceBool f a = fmap (bool 0 1 . or) (D.reduces (S.exceptDims [0] (D.shape a)) (D.diffs [0] [1] (D.zipWith f)) a)

equalsR :: (Eq a) => Array a -> Array Int
equalsR = reduceBool (==)

notEqualsR :: (Eq a) => Array a -> Array Int
notEqualsR = reduceBool (/=)

lessThanR :: (Ord a) => Array a -> Array Int
lessThanR = reduceBool (<)

lessOrEqualR :: (Ord a) => Array a -> Array Int
lessOrEqualR = reduceBool (<=)

greaterThanR :: (Ord a) => Array a -> Array Int
greaterThanR = reduceBool (>)

greaterOrEqualR :: (Ord a) => Array a -> Array Int
greaterOrEqualR = reduceBool (>=)

addR :: (Num a) => Array a -> Array a
addR = reduceU (+) 0

subtractR :: (Num a) => Array a -> Array a
subtractR = reduceU (-) 0

divideR :: (Fractional a) => Array a -> Array a
divideR = reduceU (/) 1

multiplyR :: (Num a) => Array a -> Array a
multiplyR = reduceU (*) 1

minimumR :: (Ord a, Fractional a) => Array a -> Array a
minimumR = reduceU min (1 / 0)

maximumR :: (Ord a, Fractional a) => Array a -> Array a
maximumR = reduceU max (-(1 / 0))

modulusR :: Array Double -> Array Double
modulusR = reduceU modD (1 / 0)

powerR :: (Floating a) => Array a -> Array a
powerR = reduceU (**) 1

logarithmR :: (Floating a) => Array a -> Either HuihuaWarning (Array a)
logarithmR = reduce1U (flip logBase)