Calculate an image histogram with repa - haskell

I'm loading an RGB image from disk with JuicyPixels-repa. Unfortunately the Array representation of the image is Array F DIM3 Word8 where the inner dimension is the RGB pixels. That's a bit incompatibe with existing repa imageprocessing algorithms where an RGB image is Array U DIM2 (Word8, Word8, Word8).
I want to calculate the RGB histograms of the image, I'm searching a function with the Signature:
type Hist = Array U DIM1 Int
histogram:: Array F DIM3 Word8 -> (Hist, Hist, Hist)
how can I fold my 3d array to get a 1d array for each colorchannel?
Edit:
The main problem is not that I'm not able to convert from DIM3 to DIM2 for each channel (easy done with slicing). The problem is that I have to iterate the source image DIM2 or DIM3 and have to accumulate to an DIM1 array of a different Shape (Z:.256) and extent.
So I can't use repa's foldS as it reduces the dimension by one, but with the same extent.
I also experimented with traverse but it iterates over the extent of the destination image, providing a function to get pixels from the source image, that would lead to very inefficient code, counting the same pixels for each colorvalue.
A good way would be a simple folding over a Vector with the histogram type as accumulator, but unfortunately I have no U (unboxed) or V (vector) based array, from which I can efficiently get a Vector. I have an Array F (foreign pointer).

Ok, I found a few minutes. Below, I cover four solutions and have made the worst solutions (the middle two, involving O(n) data conversion) really easy for you.
Lets Acknowledge the Dumb Solution
It's reasonable to start with the obvious. You could use Data.List.foldl to traverse the rows and columns, building up your histograms from initial zero arrays (untested/partial code follows):
foldl (\(histR, histG, histB) (row,col) ->
let r = arr ! (Z:.row:.col:.0)
g = arr ! (Z:.row:.col:.1)
b = arr ! (Z:.row:.col:.2)
in (incElem r histR, incElem g histG, incElem b histB)
(zero,zero,zero)
[ (row,col) | row <- [0..nrRow-1], col <- [0..nrCol-1] ]
...
where (Z:.nrRow:.nrCol:._) = extent arr
I'm not sure how efficient this will be, but suspect that it will do too much bounds checking. Switching to unsafeIndex should do reasonably, assuming the delayed arrays, hist*, do well due to however you'd pick to implement incElem.
You Can Build the Array You Want
Using traverse you can actually convert JP-Repa style arrays into DIM2 arrays with tuples for elements:
main = do
let arr = R.fromFunction (Z:.a:.b:.c) (\(Z:.i:.j:.k) -> i+j-k)
a =4 :: Int
b = 4 :: Int
c= 4 :: Int
new = R.traverse arr
(\(Z:.r:.c:._) -> Z:.r:.c) -- the extent
(\l idx -> (l (idx:.0)
,l (idx:.1)
,l (idx :. 2)))
print (R.computeS new :: R.Array R.U DIM2 (Int,Int,Int))
Could you point me to the body of code you talked about that uses this format? It would be simple to patch JP-Repa to include a function of this type.
You can build the Unboxed Vector You Mentioned
You mentioned an easy solution is to fold over unboxed vectors, but lamented that JP-repa doesn't provide an unboxed array. Luckily, conversion is simple:
toUnboxed :: Img a -> VU.Vector Word8
toUnboxed = R.toUnboxed . R.computeUnboxedS . R.delay . imgData
We Could Patch Repa
This is really only a problem because Repa doesn't have what I consider a normal traverse function. Repa's traverse is more of an array construction that happens to provide an indexing function into another array. We want traverse in the form:
newTraverse :: Array r sh e -> a -> (a -> sh -> e -> a) -> a
but of coarse this is actually just a malformed fold. So lets rename it and reorder the arguments:
foldAllIdxS :: (sh -> a - > e -> a) -> a -> Array r sh e -> a
which contrasts nicely with the (preexisting) foldAllS operation:
foldAllS :: (a -> a -> a) -> a -> Array r sh a -> a
Notice how our new fold has two critical characteristics. The result type is not required to match the element type, so we could start with a tuple of Histograms. Second, our version of fold passes the index, which allows you to select which histogram in the tuple to update (if any).
You can lazily use the latest JuicyPixels-Repa
To acquire your preferred Repa array format, or to acquire an unboxed vector, you can just use the newly uploaded JuicyPixels-Repa-0.6.
someImg <- readImage path :: IO (Either String (Img RGBA))
let img = either (error "Blah") id someImg
uvec = toUnboxed img
tupleArr = collapseColorChannel img
Now you can fold over the vector or use the tuple array directly, as you originally desired.
I also took an ugly stab at fleshing out the first, horribly naive, solution:
histograms :: Img a -> (Histogram, Histogram, Histogram, Histogram)
histograms (Img arr) =
let (Z:.nrRow:.nrCol:._) = R.extent arr
zero = R.fromFunction (Z:.256) (\_ -> 0 :: Word8)
incElem idx x = RU.unsafeTraverse x id (\l i -> l i + if i==(Z:.fromIntegral idx) then 1 else 0)
in Prelude.foldl (\(hR, hG, hB, hA) (row,col) ->
let r = R.unsafeIndex arr (Z:.row:.col:.0)
g = R.unsafeIndex arr (Z:.row:.col:.1)
b = R.unsafeIndex arr (Z:.row:.col:.2)
a = R.unsafeIndex arr (Z:.row:.col:.3)
in (incElem r hR, incElem g hG, incElem b hB, incElem a hA))
(zero,zero,zero,zero)
[ (row,col) | row <- [0..nrRow-1], col <- [0..nrCol-1] ]
I'm too wary of the performance of this code (3 traversals per index... I must be tired) to throw it into JP-Repa, but if you find it works well then let me know.

Related

Haskell :: Recursion in Recursion for Loop in Loop

How it goes: Based on the set of tuple (id, x, y), find the min max for x and y , then two dots (red points) created. Each element in tuple are grouped to two groups based on the distance towards the red dots.
Each group cant exceed 5 dots. If exceed, new group should be computed. I've managed to do recursion for the first phase. But I have no idea how to do it for second phase. The second phase should look like this:
Based on these two groups, again it need to find the min max for x and y (for each group), then four dots (red points) created. Each element in tuple are grouped to two groups based on the distance towards the red dots.
getDistance :: (Int, Double, Double) -> (Int, Double, Double) -> Double
getDistance (_,x1,y1) (_,x2,y2) = sqrt $ (x1-x2)^2 + (y1-y2)^2
getTheClusterID :: (Int, Double, Double) -> Int
getTheClusterID (id, _, _) = id
idxy = [(id, x, y)]
createCluster id cs = [(id, minX, minY),(id+1, maxX, minY), (id+2, minX, maxY), (id+3, maxX, maxY)]
where minX = minimum $ map (\(_,x,_,_) -> x) cs
maxX = maximum $ map (\(_,x,_,_) -> x) cs
minY = minimum $ map (\(_,_,y,_) -> y) cs
maxY = maximum $ map (\(_,_,y,_) -> y) cs
idCluster = [1]
cluster = createCluster (last idCluster) idxy
clusterThis (id,a,b) = case (a,b) of
j | getDistance (a,b) (cluster!!0) < getDistance (a,b) (cluster!!1) &&
-> (getTheClusterID (cluster!!0), a, b)
j | getDistance (a,b) (cluster!!1) < getDistance (a,b) (cluster!!0) &&
-> (getTheClusterID (cluster!!1), a, b)
_ -> (getTheClusterID (cluster!!0), a, b)
groupAll = map clusterThis idxy
I am moving from imperative to functional. Sorry if my way of thinking is still in imperative way. Still learning.
EDIT:
To clarify, this is the original data looks like.
The basic principle to follow in writing such an algorithm is to write small, compositional programs; each program is then easy to reason about and test in isolation, and the final program can be written in terms of the smaller ones.
The algorithm can be summarized as follows:
Compute the points which bound the set of points.
Split the rest of the points into two clusters, one containing points closer to the minimum point, the other containing all other points (equivalently, points closer to the maximum point).
If any cluster contains more than 5 points, repeat the process on that cluster.
The presence of a 'repeat the process' step indicates this to be a divide and conquer problem.
I see no need for an ID for each point, so I've dispensed with this.
To begin, define datatypes for each type of data you will be working with:
import Data.List (partition)
data Point = Point { ptX :: Double, ptY :: Double }
data Cluster = Cluster { clusterPts :: [Point] }
This may seem silly for such simple data, but it can potentially save you quite a bit of confusion during debugging. Also note the import of a function we will be using later.
The 1st step:
minMaxPoints :: [Point] -> (Point, Point)
minMaxPoints ps =
(Point minX minY
,Point maxX maxY)
where minX = minimum $ map ptX ps
maxX = maximum $ map ptX ps
minY = minimum $ map ptY ps
maxY = maximum $ map ptY ps
This is essentially the same as your createCluster function.
The 2nd step:
pointDistance :: Point -> Point -> Double
pointDistance (Point x1 y1) (Point x2 y2) = sqrt $ (x1-x2)^2 + (y1-y2)^2
cluster1 :: [Point] -> [Cluster]
cluster1 ps =
let (mn, mx) = minMaxPoints ps
(psmn, psmx) = partition (\p -> pointDistance mn p < pointDistance mx p) ps
in [ Cluster psmn, Cluster psmx ]
This function should clear - it is a direct translation of the above statement of this step into code. The partition function takes a predicate and a list and produces two lists, the first containing all elements for which the predicate is true, and the second all elements for which it is false. pointDistance is essentially the same as your getDistance function.
The 3rd step:
cluster :: [Point] -> [Cluster]
cluster ps =
cluster1 ps >>= \cl#(Cluster c) ->
if length c > 5
then cluster c
else [cl]
This also implements the statement above very directly. Perhaps the only confusing part is the use of >>=, which (here) has type [a] -> (a -> [b]) -> [b]; it simply applies the given function to each element of the given list, and concatenates the result (equivalently, it is written flip concatMap).
Finally your test case (which I hope I've translated correctly from pictures to Haskell data):
testPts :: [Point]
testPts = map (uncurry Point)
[ (0,0), (1,0), (2,1), (0,2)
, (5,2), (5,4), (4,3), (4,4)
, (8,2), (9,3), (10,2)
, (11,4), (12,3), (13,3), (13,5) ]
main = mapM_ (print . map (\p -> (ptX p, ptY p)) . clusterPts) $ cluster testPts
Running this program produces
[(0.0,0.0),(0.0,2.0),(2.0,1.0),(1.0,0.0)]
[(4.0,4.0),(5.0,2.0),(5.0,4.0),(4.0,3.0)]
[(10.0,2.0),(9.0,3.0),(8.0,2.0)]
[(13.0,3.0),(12.0,3.0),(11.0,4.0),(13.0,5.0)]
Functional programmers love recursion, yet they go to great lengths to avoid writing it. Jeez, people, make up your minds!
I like to structure my code, to the extent possible, using common, well-understood combinators. I want to demonstrate a style of Haskell programming which leans heavily on standard tools to implement the boring parts of a program (mapping, zipping, looping) as tersely and generically as possible, freeing you up to focus on the problem at hand.
So don't worry if you don't understand everything here. I just want to show you what's possible! (And please ask if you have questions!)
Vectors
First things first: we're working with two-dimensional space, so we'll need two-dimensional vectors and some secondary school vector algebra to work with them.
I'm going to parameterise my vector by the scalar on which our vector space is built. This'll allow me to work with standard type classes like Functor, so I can delegate a lot of the work of building a vector algebra to the machine. I've turned on DeriveFunctor and DeriveFoldable, which allow me to utter the magic words deriving (Functor, Foldable).
data Pair a = Pair {
px :: a,
py :: a
} deriving (Show, Functor, Foldable)
Hereafter I'm going to avoid working explicitly with Pair, and program to an interface, not an implementation. This'll allow me to build a simple linear algebra library in a manner that's independent of the dimensionality of the vector space. I'll give example type signatures in terms of V2:
type V2 = Pair Double
Scalar multiplication: functors
A vector space is required to have two operations: scalar multiplication and vector addition. Scalar multiplication means multiplying each component of a vector by a constant scalar. If you view a vector as a container of components, it should be clear that this means "do the same thing to every element in a container" - that is, it's a mapping operation. That's what Functor is for.
-- mul :: Double -> V2 -> V2
mul :: (Functor f, Num n) => n -> f n -> f n
mul k f = fmap (k *) f
Vector addition: zippy applicatives
Vector addition involves adding up the components of a vector point-wise. Thinking of a vector as a container of components, addition is a zipping operation - match up each element of the two vectors and add them up.
Applicative functors are functors with an additional "apply" operation. Thinking of a functor f as a container, Applicative's <*> :: f (a -> b) -> f a -> f b gives you a way to take a container of functions and apply it to a container of values to get a new container of values. It should be clear that one way to make Pair into an Applicative is to use zipping to apply functions to values.
instance Applicative Pair where
pure x = Pair x x
Pair f g <*> Pair x y = Pair (f x) (g y)
(For another example of a zippy applicative, see this answer of mine.)
Now that we have a way to zip two pairs, we can leverage a bit of standard Applicative machinery to implement vector addition.
-- add :: V2 -> V2 -> V2
add :: (Applicative f, Num n) => f n -> f n -> f n
add = liftA2 (+)
Vector subtraction, which gives you a way to find the distance between two points, is defined in terms of multiplication and addition.
-- minus :: V2 -> V2 -> V2
minus :: (Applicative f, Num n) => f n -> f n -> f n
v `minus` u = v `add` mul (-1) u
Dot products: foldable containers
2D Euclidean space is actually a Hilbert space - a vector space equipped with a way to measure lengths and angles in the form of a dot product. To take the dot product of two vectors, you multiply the components together and then add up the results. Once more, we'll be using Applicative to multiply the components, but that just gives us another vector: how do we implement "adding up the results"?
Foldable is the class of containers which admit an "aggregation" operation foldr :: (a -> b -> b) -> b -> f a -> b. The standard prelude's sum is defined in terms of foldr, so:
-- dot :: V2 -> V2 -> Double
dot :: (Applicative f, Foldable f, Num n) => f n -> f n -> n
v `dot` u = sum $ liftA2 (*) v u
This gives us a way to find the absolute length of a vector: dot it with itself and take the square root.
-- modulus :: V2 -> Double
modulus :: (Applicative f, Foldable f, Floating n) => f n -> n
modulus v = sqrt $ v `dot` v
So the distance between two points is the modulus of the difference of the vectors.
dist :: (Applicative f, Foldable f, Floating n) => f n -> f n -> n
dist v u = modulus (v `minus` u)
N-ary zipping: traversable containers
An axis-aligned (hyper-)rectangle can be defined by just two points. We'll represent the bounding box of a set of points as a Pair of vectors pointing to opposite corners of the bounding box.
Given a collection of vectors of components, we can find the opposite corners of the bounding box by finding the maximum and minimum of each component across the collection. This requires us to zip up, or transpose, a collection of vectors of components into a vector of collections of components. For this I'll use Traversable's sequenceA.
-- boundingBox :: [V2] -> Pair V2
boundingBox :: (Traversable t, Applicative f, Ord n) => t (f n) -> Pair (f n)
boundingBox vs =
let components = sequenceA vs
in Pair (minimum <$> components) (maximum <$> components)
Clustering
Now that we have a library for working with vectors, we can get down to the meaty part of the algorithm: dividing sets of points into clusters.
Partitioning
Let me rephrase the specification of the inner loop of your algorithm. You want to partition a set of points based on whether they're closer to the bottom-left corner of the set's bounding box or to the top-right corner. That's what partition does.
We can write a function, whichCluster which uses minus and modulus to decide this for a single point, and then use partition to apply it to the whole set.
type Cluster = []
-- cluster :: Cluster V2 -> [Cluster V2]
cluster :: (Applicative f, Foldable f, Ord n, Floating n) => Cluster (f n) -> [Cluster (f n)]
cluster vs =
let Pair bottomLeft topRight = boundingBox vs
whichCluster v = dist v bottomLeft <= dist v topRight
(g1, g2) = partition whichCluster vs
in [g1, g2]
Repetition, repetition, repetition
Now we want to repeatedly cluster until we don't have any groups larger than 5. Here's the plan. We'll keep track of two sets of clusters, those which are small enough, and those which require further sub-clustering. I'll use partition to sort a list of clusters into those which are small enough and those which need subclustering. I'll use the list monad's >>= :: [a] -> (a -> [b]) -> [b] (here [Cluster V2] -> ([V2] -> [Cluster V2]) -> [Cluster V2]), which maps a function over a list and flattens the result, to implement the notion of subclustering. And I'll use until to repeatedly subcluster until the set of remaining too-large clusters is empty.
-- smallClusters :: Int -> Cluster V2 -> [Cluster V2]
smallClusters :: (Applicative f, Foldable f, Ord n, Floating n) => Int -> Cluster (f n) -> [Cluster (f n)]
smallClusters maxSize vs = fst $ until (null . snd) splitLarge ([], [vs])
where
smallEnough xs = length xs <= maxSize
splitLarge (small, remaining) =
let (newSmall, large) = partition smallEnough remaining
in (small ++ newSmall, large >>= cluster)
A quick test, cribbed from #user2407038's answer:
testPts :: [V2]
testPts = map (uncurry Pair)
[ (0,0), (1,0), (2,1), (0,2)
, (5,2), (5,4), (4,3), (4,4)
, (8,2), (9,3), (10,2)
, (11,4), (12,3), (13,3), (13,5) ]
ghci> smallClusters 5 testPts
[
[Pair {px = 0.0, py = 0.0},Pair {px = 1.0, py = 0.0},Pair {px = 2.0, py = 1.0},Pair {px = 0.0, py = 2.0}],
[Pair {px = 5.0, py = 2.0},Pair {px = 5.0, py = 4.0},Pair {px = 4.0, py = 3.0},Pair {px = 4.0, py = 4.0}],
[Pair {px = 8.0, py = 2.0},Pair {px = 9.0, py = 3.0},Pair {px = 10.0, py = 2.0}]
[Pair {px = 11.0, py = 4.0},Pair {px = 12.0, py = 3.0},Pair {px = 13.0, py = 3.0},Pair {px = 13.0, py = 5.0}]
]
There you go. Small clusters in n-dimensional space, all without a single recursive function.
Labelling
Part of the point of working with the Applicative and Foldable interfaces, rather than working with V2 directly, was so I could demonstrate the following little magic trick.
Your original code represented points as 3-tuples consisting of two Doubles for the location and an Int for the point's label, but my V2 has no label. Can we recover this? Well, since the code doesn't at any point mention any concrete types - just standard type classes - we can just build a new type for labelled vectors. As long as said type is a Foldable Applicative all of the above code will continue to work without modification!
data Labelled m f a = Labelled m (f a) deriving (Show, Functor, Foldable)
instance (Monoid m, Applicative f) => Applicative (Labelled m f) where
pure = Labelled mempty . pure
Labelled m ff <*> Labelled n fx = Labelled (m <> n) (ff <*> fx)
The Monoid constraint is there because when combining actions you also need a way to combine their labels. I'm just going to use First - left-biased choice - because I'm not expecting the points' labels to be relevant to the zipping operations like modulus and boundingBox.
type LabelledV2 = Labelled (First Int) Pair Double
testPts :: [LabelledV2]
testPts = zipWith (Labelled . First . Just) [0..] $ map (uncurry Pair)
[ (0,0), (1,0), (2,1), (0,2)
, (5,2), (5,4), (4,3), (4,4)
, (8,2), (9,3), (10,2)
, (11,4), (12,3), (13,3), (13,5) ]
ghci> traverse (traverse (getFirst . lbl)) $ smallClusters 5 testPts
Just [[0,1,2,3],[4,5,6,7],[8,9,10],[11,12,13,14]] -- try reordering testPts

How to map a function over a specific range of a Vector in Haskell

I have a very large Vector a and I want to map a function over a small range. For example, if I have a Vector of size 1000, I want to map function f over values at indices 100-200 and then return the entire updated Vector. What is the best way to do this. I'm open to other data structures, but I would prefer not to use mutable vectors.
Edit:
Here's an equivalent in imperative code.
for (int i = startIdx; i < endIdx ; i++){
bigVector[i] = myFunction(bigVector[i]);
}
The immutable vector implementation of the "vector" package is basically an array. This means that any modifying operation on it requires the traversal of the whole array. That limitation doesn't play well with the requirements that you've mentioned.
Since you're saying that you're open to a choice of a different data-structure, the first thing you need to consider is a data-structure driven by the Array-Mapped Trie algorithm. That algorithm allows you to project on the slices of the data-structure, concatenate, drop/take, append/prepend - all in either a constant or logarithmic time. It's efficiency is proven in production with it driving the ubiquitous immutable vector data-structures of such languages as Clojure and Scala.
Fortunately, Haskell has an implementation as well. It's presented by the "persistent-vector" package, and here's how you can efficiently solve your problem using it:
mapOverSlice :: Int -> Int -> (a -> a) -> Vector a -> Vector a
mapOverSlice startIndex length mapper input =
let
(split1Heading, split1Trail) =
splitAt startIndex input
(split2Heading, split2Trail) =
splitAt (pred length) split1Trail
in
split1Heading <>
map mapper split2Heading <>
split2Trail
How about something like
maprange :: (a -> a) -> [a] -> Int -> Int -> [a]
maprange f vector skip nb = (take skip vector) ++ (map f (take nb (drop skip vector))) ++ (drop (skip + nb) vector)
and then use it like
*Main> maprange (\x -> 2*x) [1 .. 10] 3 4
[1,2,3,8,10,12,14,8,9,10]
to double the 4 elements after the first 3 elements.
One way to to this is
zip your original vector with [1..] to get a vector with indices
map over the zipped vector; if the index is within (skip, skip+nb] then apply f, else return the original value
The implementation is pretty straightforward:
maprange :: (a -> a) -> [a] -> Int -> Int -> [a]
maprange f vector skip nb = map (\(idx,v) -> if idx > skip && idx <= skip+nb then f v else v) zipped_vector
where zipped_vector = zip [1..] vector
The complexity should be O(n) where n is the size of the original vector (I'm not 100% sure about the complexity of zip, though); if you need something more efficient, you should investigate the data structures mentioned in the answer by Nikita Volkov.

Haskell create list from newtype data

This is a homework assignment first off. We are given a newtype Matrix which is the professor's implementation of an abstract Matrix. My main issue is how do you create a list of type Matrix. The first function fillWith which I need to implement takes a tuple which is (number of rows, number of columns) of the matrix to create and the data to place at each index.
module Matrix (Matrix, fillWith, fromRule, numRows, numColumns,
at, mtranspose, mmap, add, mult)
where
-- newtype is like "data", but has some efficiency advantages
newtype Matrix a = Mat ((Int,Int),(Int,Int) -> a)
--fillWith :: (Int,Int) -> a -> (Matrix a)
--fillWith ix val = Mat ((,
--trying to create a row of type Matrix
row_matrix :: [a] -> Matrix a
row_matrix ls = Mat ((1, length ls), (\x y -> if x > 1 then undefined else ls !! (y-1)))
You are just missing some parens and a comma:
row_matrix ls = Mat ((1, length ls), (\(x,y) -> if x > 1 then undefined else ls !! (y-1)))
^-^-^--- add these
Mat is a tuple where:
the first element is itself a tuple, and
the second element is a function taking a tuple to a value of type a
You may always use a where or let clause to simplify the construction of values, e.g.:
row_matrix as = Mat (bounds, accessor)
where bounds = (1, length as)
accessor (i,j) = if i > 1 then undefined else as !! (j-1)
Using a where clause makes the code a lot more readable.
To implement fillWith I would follow the same recipe:
fillWith bounds val = Mat (bounds, accessor)
where accessor (i,j) = ???
I think it's obvious now what the ??? should be.

Haskell: How To Fix Error With "add An Instance Declaration For (Unbox A)" When Using Unboxed Vectors?

I have written some code in which a small part of the code takes a large one dimensional Unboxed.Vector and returns them as a Vector (Vector a).
A part of the code is giving an error. Here is a sample piece of code that is similar to the actual code and gives the same error.
import Data.Vector.Unboxed as D
xs = [0,1,2,3,4,5,6,7,8,9,10,11]
rows = 3
cols = 4
sb = D.fromList xs
takeRows::Int -> Int -> Vector Int -> Vector (Vector Int)
takeRows rows cols x0 = D.map (\x -> D.slice x (fromIntegral cols) x0) starts
where
starts = D.enumFromStepN 0 cols rows
-- takeRowsList::Int -> Int -> Vector Int -> [Vector Int]
-- takeRowsList rows cols x0 = Prelude.map (\x -> D.slice x (fromIntegral cols) x0) starts
-- where
-- starts = D.toList . D.enumFromStepN 0 cols $ rows
the error is
No instance for (Unbox (Vector Int))
arising from a use of `D.map'
Possible fix: add an instance declaration for (Unbox (Vector Int))
In the expression:
D.map (\ x -> slice x (fromIntegral cols) x0) starts
In an equation for `takeRows':
takeRows rows cols x0
= D.map (\ x -> slice x (fromIntegral cols) x0) starts
where
starts = enumFromStepN 0 cols rows
I have written a similar function takeRowsList, which makes the outer Vector as a List and this doesn't suffer from the same problem. I've also included it above but commented it out, to demonstrate my problem.
I understand that some functions need type definitions, when, I use them with Unboxed Vectors. But in this case I am stumped as to where to put a type definition. I've tried pretty much type defining everything and I keep getting the above error.
Thanks in advance for your help.
Unboxed vectors need to know the size of their elements, and that size must be constant. Vectors can have different sizes, so they can't be elements of unboxed vectors. They could be elements of boxed vectors, though, so if lists aren't good for what you do, you can make it a boxed vector (import qualified Data.Vector as B and qualify the relevant functions with B instead of D).
You cannot have an unboxed vector contain another vector. Only certain primitive datatypes can be unboxed, namely the ones for which Unbox instances are defined. Vectors are not primitive datatypes.
What you can do is have your function return a normal (boxed) vector of unboxed vectors.

Repa nested array definitions resulting in "Performing nested parallel computation sequentially..."

As part of a larger problem, I am trying to define an array inside an array like so:
import Data.Array.Repa
type Arr = Array DIM2 Int
arr = force $ fromList (Z :. 5 :. 5) [1..25] :: Arr
combined :: Arr
combined = arr `deepSeqArray`
traverse arr (\_ -> Z :. 4 :. 4 :: DIM2) (\f (Z :. x :. y) ->
let reg = force $ extract f (x,y) (2,2)
in reg `deepSeqArray` sumAll reg)
extract :: (DIM2 -> Int) -> (Int,Int) -> (Int,Int) -> Arr
extract lookup (x0,y0) (width,height) = fromFunction bounds
$ \sh -> offset lookup sh
where
bounds = Z :. width :. height
offset :: (DIM2 -> Int) -> DIM2 -> Int
offset f (Z :. x :. y) = f (Z :. x + x0 :. y + y0)
main = print combined
The extract function is using fromFunction and the lookup function provided to it, but it could also use traverse and arr ! ... for the same effect. Despite using force and deepSeqArray everywhere as early as possible, the console is filled with the message here, followed by the correct result:
Data.Array.Repa: Performing nested parallel computation sequentially.
You've probably called the 'force' function while another instance was
already running. This can happen if the second version was suspended due
to lazy evaluation. Use 'deepSeqArray' to ensure each array is fully
evaluated before you 'force' the next one.
While I haven't constructed a version with lists to compare the speeds, in the larger version performance is suffering.
Is this simply a consequence of nested array definitions and thus I should restructure my program for either the inner or outer definition to be lists? Is my extract function horrible and the cause of the problems?
The tips from this question were useful to get this far but I haven't yet gone crawling through the compiled code.
It's because 'print' implicitly forces the array as well. The inner 'force' and 'sumAll' functions invoke parallel computation, but does 'print', so you have nested parallelism. That fact that this is so non-obvious is a great sadness in the Repa 2 API.
Repa 3 addresses these sort of issues by exporting both sequential and parallel versions of 'force' and 'sumAll' etc. It also adds a tag to the array type to indicate whether the array is delayed or manifest. Repa 3 isn't finished yet, but you could use the head version on http://code.ouroborus.net/repa. It should be out shorty after GHC 7.4 later this year.
Here is a Repa 3 version of your example that runs without giving the warning about nested parallelism. Note that 'force' is now 'compute'.
import Data.Array.Repa
arr :: Array U DIM2 Int
arr = fromListUnboxed (Z :. 5 :. 5) [1..25]
combined :: Array U DIM2 Int
combined
= computeP $ traverse arr (\_ -> Z :. 4 :. 4 :: DIM2)
$ \f (Z :. x :. y) -> sumAllS $ extract f (x,y) (2,2)
extract :: (DIM2 -> Int) -> (Int,Int) -> (Int,Int) -> Array D DIM2 Int
extract lookup (x0,y0) (width,height)
= fromFunction bounds
$ \sh -> offset lookup sh
where
bounds = Z :. width :. height
offset :: (DIM2 -> Int) -> DIM2 -> Int
offset f (Z :. x :. y) = f (Z :. x + x0 :. y + y0)
main = print combined

Resources