Accelerating a Fortran code with OpenACC using the PGI compiler, I got problems with a matmul call in an accelerated loop.
In the simplified example, I apply the identity matrix on two vectors, so the input and the output values should be the same:
program test
implicit none
integer :: a(3, 3)
integer :: v1(3, 2), v2(3, 2)
integer :: i
a = reshape([1, 0, 0, 0, 1, 0, 0, 0, 1], [3, 3])
v1 = reshape([1, 2, 3, 4, 5, 6], [3, 2])
print *, v1
!$acc kernels copyin(a, v1) copyout(v2)
!$acc loop independent
do i = 1, 2
v2(:, i) = matmul(a, v1(:, i))
enddo
!$acc end kernels
print *, v2
endprogram
When compiling with the PGI compiler version 20.9, I got these information:
test:
12, Generating copyin(a(:,:),v1(:,:)) [if not already present]
Generating implicit copyout(z_a_0(:)) [if not already present]
Generating copyout(v2(:,:)) [if not already present]
14, Loop is parallelizable
Generating Tesla code
14, !$acc loop gang ! blockidx%x
15, !$acc loop vector(32) ! threadidx%x
15, Loop is parallelizable
Running the code gives the following values:
1 2 3 4 5 6
4 5 6 4 5 6
the second line should be like the first one, which is the case on sequential execution. What is wrong in the code?
Looks to be a compiler issue. The problem line being:
Generating implicit copyout(z_a_0(:))
"z_a_0" is compiler temp array being created to hold the intermediary result from the call to matmul. It's declaration is being hoisted out of the loop and then copied back in as shared array. Since it's shared, it then causes a race condition.
I've submitted a problem report (TPR #29482) and sent it to our engineers for further investigation.
#Mat Colgrove explained the reason of the incorrect behavior. The workaround I found was to write the matrix vector multiplication explicitly:
program test
implicit none
integer :: a(3, 3)
integer :: v1(3, 2), v2(3, 2)
integer :: i, j, k
a = reshape([1, 0, 0, 0, 1, 0, 0, 0, 1], [3, 3])
v1 = reshape([1, 2, 3, 4, 5, 6], [3, 2])
print *, v1
!$acc kernels copyin(a, v1) copyout(v2)
!$acc loop independent
do i = 1, 2
!$acc loop seq
do k = 1, 3
v2(k, i) = 0
!$acc loop seq
do j = 1, 3
v2(k, i) = v2(k, i) + a(j, k) * v1(j, i)
enddo
enddo
enddo
!$acc end kernels
print *, v2
endprogram
I am trying to learn about arrays. I know that python has lists, not arrays, but the idea is the same. I have a list of lists setup like an array, and I am trying to modify them for random art fun, but I can only get one resulting random number out of this piece of code.
##prior list creation code making a large list of zeros called "array"###
while 3 not in array:
i= random.randint(1,9)
j= random.randint(1,28)
if (i%3)!=0 and (j%7)!=0:
if 5 in array:
array[i][j]=3
return array
elif 3 in array:
array[i][j]=4
return array
else:
array[i][j]=5
amy=(i, j)
return array
continue
##the resulting list called "array" does not chnange any zero to any number except one to "5"##
I have cut the code that made an array filled with zeros. The only number that will show up is the 5... ideally, I would have each number only show up once with each run, but in different spots
What am I doing wrong? I don't fully understand arrays, so that might be it, but I'm having trouble searching what I think the problem might be. Any help you can provide would be great!
Edit:
Sorry about forgetting, the array is the proper size to hold the data (9 rows by 28columns), and it isn't throwing any errors or exceptions... that should have been in there before I posted.
It's hard to tell exactly what you are asking, but I think you just wanted to randomly assign the numbers 3, 4, and 5 somewhere inside your matrix with the condition that i is not divisible by 3 and j is not divisible by 7. If that's what you want, then this should do it:
import random
array = [ [ 0 for j in range(28) ] for i in range(9) ]
for n in [3, 4, 5]:
while True:
i = random.randint(1, 9)
if i % 3 != 0:
break
while True:
j = random.randint(1, 28)
if j % 7 != 0:
break
array[i][j] = n
print('\n'.join(str(a) for a in array))
So let's talk about how to get to this answer. First off, we want a way to generate random numbers until a condition happens. In most languages, this would involve a do-while loop, but Python doesn't have those. However, we can make something that is equivalent to a do-while loop using just a while loop:
# This says to run this loop *forever*
while True:
# do something here
pass # This means "do nothing" in Python
if condition:
# This says if the previously mentioned condition
# is True then we will stop executing the
# currently containing loop.
break
So this construct, that I just showed is a building block which you can use to make a loop that runs until a condition is met.
Let's see how that fits in your original example. We want a random number in the range [1, 9) that is not divisible by 3. The random.randint function will provide a random number in that range, but it doesn't guarantee that it is not divisible by 3. So we need to enforce that constraint ourselves. One way to accomplish that is to simply generate a new number if the constraint is not met.
So now we can use the previously discussed loop construct to build a loop that runs until we have a number that is not divisible by 3:
while True:
i = random.randint(1, 9)
if i % 3 != 0:
break
I'm sure you can do the logical replacement in the previous loop to see how it fits the other example.
So now we can talk a little bit more about where you went wrong in your original code, and how you can prevent from making those same mistakes in the future.
Let's talk about this line first:
while 3 not in array:
First of all, when developing code, it's a very good idea to try things out at the Python interactive interpreter prompt. This is especially true when you are trying out a feature of the language for the first time. As I showed in my comment, the condition in your while loop is always False. The reason for that is clear if you try it out in the interpreter:
>>> 3 in [[3],[3],[3]]
False
The in operator only looks one level deep into a list. Also, it's always a good idea to start small when testing things out interactively. Notice I'm using a list of only 3 elements with nested lists containing only 1 element each instead of your original example of a 9 element list with nested lists containing 28 elements.
Now, another approach that we could have taken to make your loop condition change over time would be to make a "recursive" version of the in operator. Alternatively, we could have just hard coded it to expect a list that contains lists. I'm going to take this second approach because it is simpler, and I don't know if you are already familiar with recursion, and this is already a long explanation.
def contains2d(outer, element):
"""Expects a 2D-array-like list. Returns True if any of the inner
sub-lists within the outer list contain element.
"""
return any([e in inner for inner in outer])
If we try this function out, we'll see that it behaves as you originally expected the in operator to behave:
>>> contains2d([[3],[3],[3]], 3)
True
>>> contains2d([[0],[0],[3]], 3)
True
>>> contains2d([[0],[0],[0]], 3)
False
So now let's talk about your misunderstanding of the continue keyword. A while loop repeats automatically. You don't need to use continue inside of a while loop for it to repeat. The continue keyword is only used to skip the rest of the body of a loop. This is generally used if you have some special case for which you don't want to do the normal loop processing. Here's a reasonable example of how you might use continue:
>>> for i in range(10):
... if i % 3 == 0:
... continue
... print(i)
...
1
2
4
5
7
8
Notice that it really doesn't make any sense to put continue at the end of a loop:
while True:
print("This loop runs forever!")
# The following continue is useless
continue
Alright, so now that we have contains2d, we can start to think about what we want. In your example, you say you want the array variable to contain 3,4, and 5 at the end of your loop. Again, let's start small and see if the condition is True under the desired circumstances. We know from earlier that contains2d([[0],[0],[3]], 3) == True, so that is an insufficient loop termination criteria. Remember, we want the loop to only be true when all conditions have been met. So that means we need to use the and operator
>>> contains2d([[0],[0],[3]], 3) and contains2d([[0],[0],[3]], 4) and contains2d([[0],[0],[3]], 5)
False
>>> contains2d([[4],[0],[3]], 3) and contains2d([[4],[0],[3]], 4) and contains2d([[4],[0],[3]], 5)
False
>>> contains2d([[4],[5],[3]], 3) and contains2d([[4],[5],[3]], 4) and contains2d([[4],[5],[3]], 5)
True
Note that this condition is very ugly and hard to write. Ideally, we'd probably like to refactor it. One way to do that is to use the built-in all function. I'll let you experiment with it on your own, but here's the end result:
>>> all([contains2d([[4],[5],[3]], e) for e in [3,4,5]])
True
That's much shorter and much more clear. So, moving on, we want to run this loop as long as that condition is not True:
while not all([contains2d(array, e) for e in [3,4,5]]):
# ...
The next two lines are actually just fine, but I would format them according to PEP-8:
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
# ...
If we replace the in conditions with our contains2d function, then we're almost getting to a working solution:
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
if contains2d(array, 5):
# ...
elif contains2d(array, 3):
# ...
else:
# ...
The assignments within the if conditions are perfectly fine as well. However, when you have a return inside of a loop, that will exit the loop and exit the entire containing function. In this code you don't actually have a containing function, so that will just end your program. That's not what you want to do here, so let's just drop all of those return statements:
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
if contains2d(array, 5):
array[i][j] = 3
elif contains2d(array, 3):
array[i][j] = 4
else:
array[i][j] = 5
This program is very close to being right. However, the innermost if block is using some odd logic. If you run this progam using my initialization and output, you'll see something like this:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 5, 4]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
[0, 4, 4, 4, 4, 4, 4, 0, 3, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4]
It actually took me a minute to figure out what was going wrong, but the key obsevation is that the first time through the loop for which you get a successful pair of i and j, you'll take the else branch randomly placing a 5 in the nested list. The next successful time you get inside the if, you'll take the first branch which randomly places a 3 inside the nested list. However, here's where things go sideways. On the next succssful iteration, you'll still take the first branch, so you'll randomly place another 3 inside the nested list. You will keep doing this for several more iterations. Eventually, you will overwrite the previously written 5. At that point, the first condition will no longer hold and instead you will start taking the second branch which randomly places a 4 in the nested list. This branch will continue to be taken since 5 is no longer present in the nested list. Eventually, you will overwrite all of the previously written 3s with 4s in the nested list. At that point, the second branch condition will no longer hold, and you will land on the else branch again, writing a 5 to the nested list. Finally, the first condition will hold again, so on the next successful iteration, you'll write a 3 again, and as long as you don't get unlucky, that will miss the 5 and you'll now have met the termination criteria for the outer while loop leaving you with a bunch of 4s and just a single 3 and a single 5.
Maybe this is what you wanted to do, but it didn't seem that way from your question, so let's assume you really just wanted one of each of the numbers present in the nested list. If that's the case, we can easily correct the previous program. We just need to fix the conditions for each of the if cases. We only want to take the first branch when a 5 is present, but a 3 is not present. Thus that gives us if contains2d(array, 5) and not contains2d(array, 3):. Furthermore, we only want to take the second branch if 5 and 3 are both already present. Thus that gives us elif contains2d(array, 5) and contains2d(array, 3):. Finally, we only want to take the last branch if a 5 is not present. Thus we must change the else to another elif giving us elif not contains2d(array, 5):. Putting this all together gives us:
array = [ [ 0 for j in range(28) ] for i in range(9) ]
while not all([contains2d(array, e) for e in [3,4,5]]):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
if contains2d(array, 5) and not contains2d(array, 3):
array[i][j] = 3
elif contains2d(array, 5) and contains2d(array, 3):
array[i][j] = 4
elif not contains2d(array, 5):
array[i][j] = 5
print('\n'.join(str(a) for a in array))
This actually works like my original answer. However, it's not very satisfactory because the logic inside the if block is quite complicated. There's actually very rigid sequence of events that must happen. Whenever you think of a sequence of things, you should think of a list. In this case, the sequence goes like this, we assign 5, then we assign 3, then finally we assign 4. That can be represented as this list: [5, 3, 4]. If we reorder things a bit, we can get the following program:
array = [ [ 0 for j in range(28) ] for i in range(9) ]
for n in [5, 3, 4]:
while not contains2d(array, n):
i = random.randint(1, 9)
j = random.randint(1, 28)
if i % 3 != 0 and j % 7 != 0:
array[i][j] = n
print('\n'.join(str(a) for a in array))
This particular program does have a flaw in that it's possible for one of the later values to overwrite one of the earlier values, and thus the postcondition of all the numbers being present could possibly not hold if your random numbers happen to collide. In fact, my initial answer has this same issue. I'll leave fixing that as an exercise for you to figure out.