如何在一个范围内使用Repa进行数组切片

时间:2011-05-29 19:44:22

标签: arrays haskell repa

我正在尝试使用Repa实现累积和函数以计算积分图像。我目前的实现如下:

cumsum :: (Elt a, Num a) => Array DIM2 a -> Array DIM2 a
cumsum array = traverse array id cumsum' 
    where
        elementSlice inner outer = slice array (inner :. (0 :: Int))
        cumsum' f (inner :. outer) = Repa.sumAll $ elementSlice inner outer

问题出在elementSlice函数中。在matlab中或说numpy,这可以指定为array [inner,0:outer]。所以我正在寻找的是:

slice array (inner :. (Range 0 outer))

但是,似乎不允许在当前的Repa范围内指定切片。我考虑过在Haskell中使用高效并行模板卷积中讨论的分区,但如果每次迭代都改变它,这似乎是一种相当重要的方法。我还考虑过屏蔽切片(乘以二进制矢量) - 但这似乎再次对大型矩阵表现不佳,因为我会为矩阵中的每个点分配一个掩码矢量...

我的问题 - 有没有人知道是否有计划在一个范围内添加切片支持到Repa?或者是否有一种高效的方式我可以解决这个问题,可能采用不同的方法?

2 个答案:

答案 0 :(得分:5)

提取子范围是一个索引空间操作,很容易用fromFunction表达,尽管我们应该为它添加一个更好的包装器。

let arr = fromList (Z :. (5 :: Int)) [1, 2, 3, 4, 5 :: Int] 
in  fromFunction (Z :. 3) (\(Z :. ix) -> arr ! (Z :. ix + 1))

> [2,3,4]

通过偏移提供的索引并从源查找该索引来检索结果中的元素。这种技术自然地扩展到更高级别的阵列。

关于实现并行折叠和扫描,我们可以通过向库中添加原语来实现。我们无法在地图方面定义并行缩减,但我们仍然可以使用延迟数组的整体方法。这将是一个合理的正交扩展。

答案 1 :(得分:3)

实际上,我认为主要问题是Repa没有扫描原语。 (但是,库Accelerate非常相似。)扫描有两种变体,前缀扫描和后缀扫描。给定一维数组

[a_1, ..., a_n]

前缀扫描返回

[0, a_0, a_0 + a_1, ..., a_0 + ... + a_{n-1} ]

,后缀扫描生成

[a_0, a_0 + a_1, ..., a_0 + a_1 + ... + a_n ]

我认为这是你的累积总和(cumsum)函数的目的。

前缀和后缀扫描非常自然地概括为多维数组,并且具有基于树减少的有效实现。关于该主题的相对陈旧的论文是"Scan Primitives for Vector Computers"。此外,Conal Elliott最近撰写了several blog posts,以便在Haskell中获得高效的并行扫描。

可以通过两次扫描计算积分图像(在2D阵列上),一次扫描,一次垂直扫描。在没有扫描原语的情况下,我实现了一个,非常低效。

horizScan :: Array DIM2 Int -> Array DIM2 Int
horizScan arr = foldl addIt arr [0 .. n - 1]
  where 
    addIt :: Array DIM2 Int -> Int -> Array DIM2 Int
    addIt accum i = accum +^ vs
       where 
         vs = toAdd (i+1) n (slice arr (Z:.All:.i))
    (Z:.m:.n) = arrayExtent arr

--
-- Given an @i@ and a length @len@ and a 1D array @arr@ 
-- (of length n) produces a 2D array of dimensions n X len.
-- The columns are filled with zeroes up to row @i@.
-- Subsequently they are filled with columns equal to the 
-- values in @arr.
--
toAdd :: Int -> Int -> Array DIM1 Int -> Array DIM2 Int
toAdd i len arr = traverse arr (\sh -> sh:.(len::Int)) 
               (\_ (Z:.n:.m) -> if m >= i then arr ! (Z:.n) else 0) 

然后可以将计算积分图像的函数定义为

vertScan :: Array DIM2 Int -> Array DIM2 Int
vertScan = transpose . horizScan . transpose

integralImage = horizScan . vertScan

鉴于已经为Accelerate实施了扫描,将它添加到Repa中应该不会太难。我不确定是否可以使用现有的Repa原语进行有效的实现。