Skip to content

tonyday567/numhask-array

 
 

Repository files navigation

numhask-array

https://img.shields.io/hackage/v/numhask-array.svg https://github.com/tonyday567/numhask-array/workflows/haskell-ci/badge.svg

Deprecated in favour of harpie

Usage

This package provides an interface into the numhask API, and both type and value level shape manipulation routines.

:set -XRebindableSyntax
import NumHask.Prelude
import NumHask.Array

In situations where shape is only known at run-time, a clear module configuration is:

import NumHask.Array.Shape
import qualified NumHask.Array.Fixed as F
import qualified NumHask.Array.Dynamic as D

recip development

‘chol’ uses the Cholesky-Crout algorithm.

:r
:set -Wno-type-defaults
:set -Wno-unused-do-bind
:set -Wno-name-shadowing
:set -XOverloadedStrings
:set -XOverloadedLabels
:set -XRebindableSyntax
:set -XDataKinds
import Data.Functor.Rep
import NumHask.Prelude
import NumHask.Array.Fixed qualified as F
import NumHask.Array.Shape qualified as S
import NumHask.Array.Dynamic qualified as D
putStrLn "ok"

Cholesky decomposition

‘chol’ uses the Cholesky-Crout algorithm.

Example from https://rosettacode.org/wiki/Cholesky_decomposition#Haskell

a = [25, 15, -5 ,15, 18, 0 ,-5,  0, 11] :: Matrix 3 3 Double
b = [ 18, 22, 54, 42 , 22, 70, 86, 62 , 54, 86, 174, 134 , 42, 62, 134, 106] :: Matrix 4 4 Double
chol a
chol b
fmap (fixed (Just 3)) (a `mmult` recip a)
fmap (fixed (Just 3)) (b `mmult` recip b)
fmap (fixed (Just 3)) (dot sum (*) b (recip b))
[[5.0, 0.0, 0.0],
 [3.0, 3.0, 0.0],
 [-1.0, 1.0, 3.0]]
[[4.242640687119285, 0.0, 0.0, -5.102196573270443e-15],
 [5.185449728701349, 6.565905201197403, 0.0, 0.0],
 [12.727922061357857, 3.0460384954008553, 1.6497422479090704, 0.0],
 [9.899494936611667, 1.624553864213788, 1.8497110052313648, 1.3926212476456026]]
[["1.000", "0.000", "-0.000"],
 ["0.000", "1.000", "0.000"],
 ["-0.000", "0.000", "1.000"]]
[["1.000", "0.000", "-0.000", "0.000"],
 ["-0.000", "1.000", "-0.000", "0.000"],
 ["-0.000", "0.000", "1.000", "0.000"],
 ["-0.000", "0.000", "-0.000", "1.000"]]
[["1.000", "0.000", "-0.000", "0.000"],
 ["-0.000", "1.000", "-0.000", "0.000"],
 ["-0.000", "0.000", "1.000", "0.000"],
 ["-0.000", "0.000", "-0.000", "1.000"]]

dyno development

imports

:r
:set -Wno-name-shadowing
:set -Wno-type-defaults
:set -XNoImplicitPrelude
import NumHask.Prelude as P
import NumHask.Array.Dynamic qualified as D
import NumHask.Array.Shape as S
import NumHask.Array.Fixed as F
import Prettyprinter
a = fmap (1+) $ range [2,3,4] :: D.Array Int
pretty a
-- :t \d o l a -> backpermute (S.setDim d l) (S.modifyDim d (+o)) a
a = D.array [3,3] [4,12,-16,12,37,-43,-16,-43,98] :: D.Array Double
pretty (D.chol a)
D.mult (D.chol a) (D.transpose (D.chol a)) == a
pretty (D.inverse a)
D.mult (D.inverse a) a == a
a = D.array [3,3] ([1,0,1,0,1,2,0,0,1] :: [Double]) :: D.Array Double
n = D.shape a !! 0
ti = D.undiag 2 (fmap recip (D.diag a))
tl = zipWith (-) a (D.undiag 2 (D.diag a))
l = fmap negate (D.dot sum (*) ti tl)
l
-- fmap (sum (fmap (l ^) (fmap fromIntegral $ D.range [n])) *) ti

seq = D.range [n] :: D.Array Int
x' = fmap (\x -> fmap (^x) l) seq
D.shape x'
D.shape (x' ! [0,0])


foldr (zipWith (+)) (x' ! [0]) (D.tails [0] x')
D.isScalar $ heads [0] x'
x' ! [0]
D.tails [0] x'
import NumHask.Array.Fixed as F
import Data.Functor.Rep
:set -XOverloadedLists
a' = [1,0,1,0,1,2,0,0,1] :: F.Array '[3,3] Double
n = F.shape a' !! 0
ti = F.undiag (fmap recip (F.diag a'))
tl = a' - (F.undiag (F.diag a'))
l = fmap negate (F.dot sum (*) ti tl)
invtri a'
:t l
(l ^ 1)
(l ^^ 2)
a = D.array [3,3] ([1,0,1,0,1,2,0,0,1] :: [Double]) :: D.Array Double
n = D.shape a !! 0
ti = D.undiag 2 (fmap recip (D.diag a))
tl = zipWith (-) a (D.undiag 2 (D.diag a))
l = fmap negate (D.dot sum (*) ti tl)
pow a n = foldr ($) (D.ident [3,3]) (replicate n (D.mult a))
:t pow
pow l 2
add = zipWith (+)
sum' = foldl' add (D.konst [3,3] 0)
:t sum'
D.mult (sum' (fmap (pow l) (fmap fromIntegral $ D.range [n]))) ti
import Control.Monad
xs n = replicateM n . uniformRM (0,1)
:t xs
gen <- newIOGenM (mkStdGen 42)
:t xs 10 gen :: IO [Double]

backpermute

import qualified Data.List as List
x = range [2,3]
x
D.backpermute (List.drop 1 :: [Int] -> [Int]) x

range

D.range (D.toScalar 3)
D.join $ D.tabulateA (D.toScalar 3) id
D.join $ D.tabulate (D.toScalar 3) id
D.tabulate (D.toScalar 3) id
D.tabulateA (D.toScalar 3) id

zipWith

m = D.array [3,4] [0..11]
m
D.zipWith (-) m m
D.zipWith (-) m m

cons

(x:|xs) = array [4] [0..3]
x
xs
(x:|xs)

transmit

Apply a binary fnuction to sub-components of an array matching the size of the second array, and an array.

import qualified Data.List as List
a = D.array [2,3] [0..5]
b = D.array [3] [0..2]
D.extracts [1] a
D.extracts [0] b
f = D.concatenate 0
D.transmits [(1,0)] f a b

3-Dim example

let a = D.array [2,3,4] [0..23] :: D.Array Int
pretty a
pretty $ (D.shape @[Int]) <$> D.extracts [0] a

scalars

s = D.tabulate [] (const 0) :: D.Array Int
s
pretty s
s1 = D.array [1] [0]
s1
pretty s1
s2 = D.toScalar 0
s2
pretty s2

tabulate

s3 = tabulate [] (const 3) :: D.Array Int
s3
s3' = tabulate [1] (const 3) :: D.Array Int
s3'
s3'' = tabulate (Scalar 1) (const 3) :: D.Array Int
s3''

indices

D.indices []
D.indices [3]
D.indices (Scalar 3)
D.indices (D.array [] [3])
D.indices [2,3]

range

joins' $ fmap D.asArray $ D.indices []
joins' $ fmap D.asArray $ D.indices [3]
joins' $ fmap D.asArray $ D.indices (Scalar 3)
joins' $ fmap D.asArray $ D.indices (D.array [] [3])
joins' $ fmap D.asArray $ D.indices [2,3]

diag

import NumHask.Array.Shape as S
:set -Wno-x-partial
a = D.array [] [3]
D.diag a
D.diag (D.array [3] [1,2,3])
D.diag (D.ident [3,2])
-- D.tabulate [S.minimum (zero:D.shape a)] (\xs -> index a (replicate (D.rank a) (head xs)))
-- D.tabulate [] (\xs -> index a (replicate (D.rank a) (head xs)))
-- S.minimum ((D.shape a) :: [Int])
-- index a []

indexes

D.indexes [2] [1] (D.array [] [3::Int])
D.indexes [0] [2] (D.array [3] [0..2::Int])
m = D.reshape [2,3] (D.range [6])
pretty m
D.indexes [1] [0] (D.reshape [2,3] (D.range [6]))
D.indexes [1] [2] (D.reshape [2,3] (D.range [6]))
D.indexes [0,1] [1,2] (D.reshape [2,3] (D.range [6]))
:t D.index @[Int]
:t \a -> D.index @_ @[Int] a
:t D.index @_ @[Int]

folds

pretty $ D.folds [0,1] (sum . fmap (const one)) a

extracts

D.extracts [0,1] a
D.joins [0,1] (D.extracts [0,1] a)
D.joins [0,1] $ D.extracts [1,0] $ D.joins [1,0] (D.extracts [0,1] a)
pretty $ D.maps (D.reverses [0]) [0] a
pretty $ D.insert 2 2 a (D.array [2,3] [100..105])

dimension

S.index (D.shape a) 2

reverses

S.reverseIndex [0] [] []
S.reverseIndex [0] [5] [0]
S.reverseIndex [0] [5] [4]
S.reverseIndex [2] [2,3,4] [0,1,2]
:t (\ds ns xs -> fmap (\(i,x,n) -> bool x (n-1-x) (i `elem` ds)) (zip3 [0..] xs ns))
pretty $ D.reverses [0] $ D.array [3,2] [1..6]
import Data.Proxy
import NumHask.Array.Fixed as F
:t with (D.array @[Int] [2,3,4] [1..24]) (NumHask.Array.Fixed.indexes (Proxy :: Proxy '[0,1]) [1,1] :: F.Array '[2,3,4] Int -> F.Array '[4] Int)

pretty $ with (D.array @[Int] [2,3,4] [1..24]) (NumHask.Array.Fixed.indexes (Proxy :: Proxy '[0,1]) [1,1] :: F.Array '[2,3,4] Int -> F.Array '[4] Int)

cons

let a = D.array [2,3,4] [0..23] :: D.Array Int
a
m = D.array [3,2] [0..5]
(x:|xs) = D.array [3,2] [0..5]
pretty x
pretty xs

D.indexes' [(0,0)] m
D.drops [1,0] m

scalar applications

S.shapen [] 20
S.flatten [] []
S.deleteDim [] 2
S.setDim 0 1 []
S.modifyDim 0 (+1) []
S.setDim 1 3 []
S.reverseIndex [0] [] []
S.rotateIndex [(0,1)] [] [1]
D.stretch 0 (D.toScalar 1)
x = D.toScalar 1 :: D.Array Int
y = D.toScalar 2 :: D.Array Int
D.length x
fmap (+1) x
-- > toScalar x == D.tabulate [] (const x)
D.index x []
D.indices []
D.indexes [] x
D.indexes [(0,0)] x
D.indexes [(2,2)] x
D.takes [] x
D.takes [(0,1)] x
D.takes [(1,1)] x

-- D.drops [(0,0)] x
-- D.row 0 (D.toScalar 2)
-- D.concatenate 0 (D.toScalar 2) (D.toScalar 3)

A.divide (D.toScalar 1) (D.toScalar 2)

fixed

:set -XPolyKinds
:t Array @[2,3]

fromList [1..24] :: Array [2,3,4] Int

reference

APLicative Programming with Naperian Functors

fixed development

imports

:r
:set -Wno-name-shadowing
:set -Wno-type-defaults
:set -XNoImplicitPrelude
import NumHask.Prelude as P
import NumHask.Array.Dynamic qualified as D
import NumHask.Array.Shape as S
import NumHask.Array.Fixed as F
import Prettyprinter
import GHC.TypeNats
import Data.Proxy
import Test.QuickCheck
import Data.Vector qualified as V
a = range @[2,3,4]
pretty a
:t a

SNat

SNat or KnownNat interface?

a = aVector [2,3,4]
:t a
show a

a' = aVector' [2,3,4]
:t a'
show a'
show a'
(\(SomeVector' (SNat :: SNat n) a) -> toDynamic @'[n] a) a'
(\(SomeVector' sn _) -> fromSNat sn) a'
(\(SomeVector' _ v) -> asVector v) a'

example_append

example_append a
example_append' a'

example_insert

example_insert a
example_insert' a'

delete

Cannot satisfy 1 <= n

example_delete' :: (Show a) => SomeVector' a -> String
example_delete' (SomeVector' (SNat :: SNat n) a) = show (delete (SNat @0) 0 a)
:t (\(SomeVector' (SNat :: SNat n) v) -> delete (SNat @0) 0 v) a'

basics

import GHC.TypeNats
:i SNat

x = SNat @1
:t x
x
y = natSing @2
:t y
y
fromSNat x

withSomeSNat

:{
import Data.Proxy

f :: forall n. SNat n -> Nat
f SNat = natVal (Proxy :: Proxy n)
:}
f (SNat @4)
withSomeSNat 5 f

withSomeNat

For when you have both an SNat input and a KnownNat constraint (which is always in this API).

Uses both withKnownNat and withSomeNat. Very similar to reifyNat …

reifyNat :: forall r. Integer -> (forall (n :: Nat). KnownNat n => Proxy n -> r) -> r
reifyNat n k = TN.withSomeSNat (fromInteger n :: Numeric.Natural) $
               \(sn :: (SNat n)) -> TN.withKnownNat sn $ k (Proxy :: Proxy n)
:t \n -> vector' n [2,3,4::Int]
vector' (SNat @3) [2,3,4::Int]
withSomeNat 3 (\n -> pretty $ vector' n [2,3,4])
withSomeNat 2 (\n -> pretty $ vector' n [2,3,4])
withSomeNat 3 (\n -> vector' n [2,3,4])

withSomeSNat works where there are no external upstream KnownNat constraints (which is almost never).

withSomeSNat 3 (\n -> show $ vector' n [2,3,4])
withSomeSNat 3 (\n -> pretty $ vector' n [2,3,4])

withKnownNat needs to be nailed down to work:

:t withSomeNat
:t vector [2,3,4]
:t \n -> withKnownNat n (vector [2,3,4])
f = (\n -> withKnownNat n (vector [2,3,4])) :: (forall n. KnownNat n => SNat n -> Array '[n] Int)
:t f
withSomeNat 3 (pretty . f)
:t vector [2,3,4]
:t \n -> withKnownNat n (pretty (vector [2,3,4]))
:t withKnownNat

SomeVector

Simple example:

https://snakamura.github.io/log/2021/9/length_indexed_vector.html

:t withSomeNat
s = someVector' (SNat @3) [2,3,4]
show s
-- :t withKnownNat (SNat @3) (show $ someVector [2,3,4])
v = aVector' [2,3,4]
show v
v = aVector [2,3,4]
show v

Existentials on a leash:

https://github.com/cdfa/existentials-on-a-leash

HMatrix uses an SNat:

https://github.com/bmsherman/LinAlg/blob/7fc0f38db2b0bd101beeaa762e01909bdcf33f56/Numeric/LinAlg/HMatrix.hs#L10

A SomeVec that uses SNat rather than KnownNat:

https://github.com/clash-lang/clash-compiler/blob/5927123c1df848190e87cd0830b6a533139ebb39/clash-prelude-hedgehog/src/Clash/Hedgehog/Sized/Vector.hs#L49

https://github.com/mstksg/inCode/blob/fdc967c38bf0384794bada6d29a95a11b9af0827/code-samples/type-levels/Level5.hs#L106

https://github.com/snakamura/haskell/blob/28318f3c1b558d9cedb86945bc35f01458ab373f/vec_len/vec_len.hs#L38

Sigma usage:

https://github.com/snakamura/haskell/blob/28318f3c1b558d9cedb86945bc35f01458ab373f/vec_len/vec_len_singleton.hs#L38

arbitrary

rvs <- sample' arbitrary :: IO [SomeVector' Int]
show rvs

convert a KnownNat constraint into an SNat input

withKnownNat

:t \xs -> vector xs
:t \xs -> (\(SNat :: SNat n) -> vector @n xs)
:t \xs sn -> withKnownNat sn (vector xs)
g = \xs -> (\(SNat :: SNat n) -> vector @n xs)
:t g

complexity

More complicated functions look too hard.

a = F.range @[2,3,4]
F.take (SNat @1) (SNat @2) a
withSomeNat 2 (\sd -> show (F.take (SNat @2) sd a))

SomeSorted

https://github.com/mstksg/inCode/blob/7ab61f481bbaf76b867f5586b301c5acad6d7841/code-samples/type-levels/Level6.hs

Arbitrary instance

from https://github.com/RyanGlScott/text-show/blob/d72caae6fb759f1bf4351019dcc426eb6431feca/tests/Instances/GHC/TypeLits.hs#L49

instance GArbitrary SNat where
  garbitrary = do
    n <- arbitrary
    TN.withSomeSNat n (pure . Some)

See also https://hackage.haskell.org/package/typelits-witnesses-0.4.1.0/docs/GHC-TypeLits-Witnesses.html#t:SomeNat

instance Arbitrary SomeSNat where
  arbitrary :: Gen SomeSNat
  arbitrary = do
    n <- fromInteger . getNonNegative <$> arbitrary
    return $ withSomeSNat n SomeSNat
:t someNatVal 5

withKnownNat

uvm-plaid/SensCheck#12

:t withKnownNat
:t iota
withKnownNat (SNat @4) iota
:{
withKnownNat' :: Nat -> (forall n. KnownNat n => r) -> r
withKnownNat' n f = withSomeSNat n $ \(SNat :: SNat n) -> f @n
:}
withKnownNat' 4 iota

AllowAmbiguousTypes injectivity

https://www.youtube.com/watch?v=1vd9mvH8Bos&list=PLD8gywOEY4HaG5VSrKVnHxCptlJv2GAn7&index=4

:{
withKnownNat2 :: (forall n1 n2 . (KnownNat n1, KnownNat n2) => r) -> Natural -> Natural -> r
withKnownNat2 f n1 n2 = withSomeSNat n1 $ \(SNat :: SNat n1) ->
    withSomeSNat n2 $ \(SNat :: SNat n2) ->
      f @n1 @n2
:}
:t \d x -> withKnownNat2 (F.take d x a) 1 2

withSomeSnat complicated example

https://github.com/DCPagan/advent-of-code-2023-haskell/blob/c07bd4a87375d2e0059a52330268f1ba7b068b79/src/Days/Day10.hs#L245

fin styles

https://hackage.haskell.org/package/fin-0.3.1/docs/Data-Type-Nat.html#t:SNatI

plugin

https://hackage.haskell.org/package/ghc-typelits-knownnat-0.7.12/docs/GHC-TypeLits-KnownNat-Solver.html

https://github.com/Mikolaj/horde-ad/blob/master/src/HordeAd/Core/Types.hs

:{
withSNat :: Int -> (forall n. KnownNat n => (SNat n -> r)) -> r
withSNat i f = withSomeSNat (fromIntegral i) $ \msnat -> case msnat of
  snat@SNat -> f snat
:}
:t withSNat 5
a = F.range @[2,3,4]
pretty $ F.take (SNat @1) (SNat @2) a
:t withSomeSNat 5
a = F.range @[2,3,4]
pretty $ F.take (SNat @1) (SNat @2) a
:t withSomeSNat 5 iota

https://github.com/DCPagan/advent-of-code-2023-haskell/blob/c07bd4a87375d2e0059a52330268f1ba7b068b79/src/Days/Day10.hs#L245

:t \d x -> withSomeSNat d $ \(SNat @d) -> (withSomeSNat x $ \(SNat @x) -> F.take (SNat @d) (SNat @x) a)
:t \n -> withSomeSNat n (\(SNat @n) -> iota @n)

arbitrary instance of Vec n

https://discourse.haskell.org/t/how-to-create-arbitrary-instance-for-dependent-types/6990/37

https://github.com/hellwolf/haskell-examples/blob/d33b4e2613b3bf720c013401b42e8de573cdf185/2023-07-16-genvectn/genvectn.hs#L59

reifyNat

-- Convert regular Int to type
-- Can't simply do: exists n. Int -> Proxy n
-- Existential trick: Use CPS
reifyNat :: Int -> (forall n. KnownNat n => Proxy n -> r) -> r
reifyNat 0 k = k (Proxy :: Proxy Z)
reifyNat n k = reifyNat (n - 1) $ \(Proxy :: Proxy n_1) -> k (Proxy :: Proxy (S n_1))
-- As found in the `reflection` library
reifyNat :: Integer -> (forall (n :: Nat). KnownNat n => Proxy n -> r) -> r
reifyNat n k = withSomeSNat n $ \(mbSn :: Maybe (SNat n)) ->
               case mbSn of
                 Just sn -> withKnownNat sn $ k @n Proxy
                 Nothing -> throw Underflow

plugin

import GHC.TypeLits.KnownNat.Solver

Does the plugin work with ghci?

:{
-- | proof that plugin exists and is working
pluginf' :: forall n . KnownNat n => Proxy n -> Nat
pluginf' _ = natVal (Proxy :: Proxy n) + natVal (Proxy :: Proxy (n+2))
:}

SomeArray

https://discourse.haskell.org/t/how-to-create-arbitrary-instance-for-dependent-types/6990/5

singletons

https://github.com/goldfirere/singletons/blob/master/README.md

dependent type examples

:{
example_inserta :: (Show a, FromInteger a) => SomeArray a -> String
example_inserta (SomeArray (SNats :: SNats ns) a) = show (insert (SNat @0) (SNat @0) a (toScalar 0))
:}
-- (If (s Data.Type.Equality.== '[]) '[1] s)’
--    arising from a use of ‘insert’

segfaults as SNats is somehow SNat @’[]

:{
import Fcf (Eval)
someTakeDim :: forall d t s. (KnownNats s, KnownNat d, KnownNat t) => SNats (Eval (TakeDim d t s))
someTakeDim = withSomeSNats (fromIntegral <$> takeDim (int (SNat :: SNat d)) (int (SNat :: SNat t)) (ints (SNats :: SNats s))) unsafeCoerce

example_take :: forall a s. (KnownNats s, Show a) => Nat -> Nat -> Array s a -> String
example_take d t a =
  withSomeSNat d
  (\(SNat :: SNat d) ->
    withSomeSNat t
    (\(SNat :: SNat t) ->
      case someTakeDim @d @t @s of
        SNats -> show $ F.take (SNat @d) (SNat @t) a))
:}

but this is ok

:{
someTakeDim2 :: forall d t s. (KnownNats s, KnownNat d, KnownNat t) => SNats (Eval (TakeDim d t s))
someTakeDim2 = UnsafeSNats (fromIntegral <$> takeDim (int @d) (int @t) (ints @s))

example_take' :: forall a s. (KnownNats s, Show a) => Nat -> Nat -> Array s a -> String
example_take' d t a =
  withSomeSNat d
  (\(SNat :: SNat d') ->
    withSomeSNat t
    (\(SNat :: SNat t) ->
      case someTakeDim2 @d' @t @s of
        x -> show x <> " " <> show (someTakeDim2 @d' @t @s) <> " : take " <> show (SNat @d') <> " " <> show (SNat @t) <> " " <> show a))

:}

About

higher-kinded numbers

Resources

License

Stars

Watchers

Forks

Packages

No packages published