Repository: psychopy/psychopy

Issue:
ElementArrayStim randomly fails to generate positions when nElements is too low
This issue exists for me on both 1.90.03 and 3.0.0b7 (2 & 3), not sure if it's a bug, not supported behavior, or I'm doing something wrong. I'm on Mac OS 10.11.6.

Here is a minimum example:

```
from psychopy import visual

window = visual.Window()

for i in range(1000):
    dots = visual.ElementArrayStim(window,nElements=5)
```

Trackback:

```
  File "/Users/Colin/Desktop/debug_crash.py", line 6, in <module>
    dots = visual.ElementArrayStim(window,nElements=5)
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/visual/elementarray.py", line 159, in __init__
    self._updateVertices()
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/visual/elementarray.py", line 616, in _updateVertices
    units=self.units, win=self.win)
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/tools/monitorunittools.py", line 95, in convertToPix
    return unit2pixFunc(vertices, pos, win)
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/tools/monitorunittools.py", line 58, in _norm2pix
    return (pos + vertices) * win.size / 4.0
ValueError: operands could not be broadcast together with shapes (3,2) (20,2)
```

Changing `nElements` to `20` works as expected. I poked around a bit and obviously ElementArrayStim is [expecting a larger number of objects](https://github.com/psychopy/psychopy/blob/c8f7fe70a8db6e47d38b8800456a69ab8d3725df/psychopy/visual/elementarray.py#L257), but it's not clear to me if a smaller number is a valid value. 




ElementArrayStim randomly fails to generate positions when nElements is too low
This issue exists for me on both 1.90.03 and 3.0.0b7 (2 & 3), not sure if it's a bug, not supported behavior, or I'm doing something wrong. I'm on Mac OS 10.11.6.

Here is a minimum example:

```
from psychopy import visual

window = visual.Window()

for i in range(1000):
    dots = visual.ElementArrayStim(window,nElements=5)
```

Trackback:

```
  File "/Users/Colin/Desktop/debug_crash.py", line 6, in <module>
    dots = visual.ElementArrayStim(window,nElements=5)
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/visual/elementarray.py", line 159, in __init__
    self._updateVertices()
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/visual/elementarray.py", line 616, in _updateVertices
    units=self.units, win=self.win)
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/tools/monitorunittools.py", line 95, in convertToPix
    return unit2pixFunc(vertices, pos, win)
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/tools/monitorunittools.py", line 58, in _norm2pix
    return (pos + vertices) * win.size / 4.0
ValueError: operands could not be broadcast together with shapes (3,2) (20,2)
```

Changing `nElements` to `20` works as expected. I poked around a bit and obviously ElementArrayStim is [expecting a larger number of objects](https://github.com/psychopy/psychopy/blob/c8f7fe70a8db6e47d38b8800456a69ab8d3725df/psychopy/visual/elementarray.py#L257), but it's not clear to me if a smaller number is a valid value. 






Discussion:
The issue seems to be that, with the default arguments, psychopy attempts to position the dots randomly within a circle. It does this by sampling positions for double the number of dots and then including the required number of dots from inside the circle. Particularly with low ```nElements``` values, there is a chance that there won't be enough dots that have their random positions within the circle - that is causing the error that you see.

A workaround would be to explicitly assign the positions of the dots (via ```xys```) yourself.
@djmannion Would it be better to switch to a sampling method which all values fall within a circle as seen here?

https://stackoverflow.com/questions/46996866/sampling-uniformly-within-the-unit-circle
That does seem like a good idea! The reason I didn't use that before was because I couldn't get a nice uniform distribution - not sure why I didn't think to square root! That seems obvious now!!
Great that I found this post after getting the same errors. I think modifying `fieldPos` and/or `fieldSize` for creating `ElementArrayStim` would also work?
The issue seems to be that, with the default arguments, psychopy attempts to position the dots randomly within a circle. It does this by sampling positions for double the number of dots and then including the required number of dots from inside the circle. Particularly with low ```nElements``` values, there is a chance that there won't be enough dots that have their random positions within the circle - that is causing the error that you see.

A workaround would be to explicitly assign the positions of the dots (via ```xys```) yourself.
@djmannion Would it be better to switch to a sampling method which all values fall within a circle as seen here?

https://stackoverflow.com/questions/46996866/sampling-uniformly-within-the-unit-circle
That does seem like a good idea! The reason I didn't use that before was because I couldn't get a nice uniform distribution - not sure why I didn't think to square root! That seems obvious now!!
Great that I found this post after getting the same errors. I think modifying `fieldPos` and/or `fieldSize` for creating `ElementArrayStim` would also work?

Resolving patch:
diff --git a/psychopy/visual/elementarray.py b/psychopy/visual/elementarray.py
--- a/psychopy/visual/elementarray.py
+++ b/psychopy/visual/elementarray.py
@@ -258,7 +258,11 @@ def xys(self, value):
                 # NB always circular - uses fieldSize in X only
                 normxy = old_div(xys, (old_div(fsz, 2.0)))
                 dotDist = numpy.sqrt((normxy[:, 0]**2.0 + normxy[:, 1]**2.0))
-                self.__dict__['xys'] = xys[dotDist < 1.0, :][0:self.nElements]
+                xys = numpy.vstack((
+                    xys[dotDist <= 1.0, :],  # not flipped
+                    xys[dotDist > 1.0, :] * -1  # flipped to other side
+                ))
+                self.__dict__['xys'] = xys[:, :][0:self.nElements]
         else:
             self.__dict__['xys'] = self._makeNx2(value, ['Nx2'])
         # to keep a record if we are to alter things later.


Test patch: