py5coding / py5generator

Meta-programming project that creates the py5 library code.
https://py5coding.org/
GNU General Public License v3.0
52 stars 13 forks source link

`random_choice()` on a list of `Py5Vector` returns a list of `numpy.ndarray` #200

Closed camblomquist closed 1 year ago

camblomquist commented 1 year ago

My words are failing me today

choices = [Py5Vector2D.random(), Py5Vector2D.random()]
selection = py5.random_choice(choices)
# Expect: type(selection[0]) == <class 'Py5Vector2D'>
# Actual: type(selection[0]) == <class 'numpy.ndarray'>

I know that Py5Vector is backed by a numpy.ndarray, and that random_choice uses numpy.random.choice internally, but I won't pretend to have any idea as to how this is happening.

villares commented 1 year ago

Cheers @camblomquist!

Check out the previous issue: https://github.com/py5coding/py5generator/issues/199

hx2A commented 1 year ago

Welcome, @camblomquist !

This bug is a result of a poor design choice on my part. It will be fixed in the next release. In the mean time, you can change your code to:

selection = py5.random_choice(choices)[0]

Which should give the desired result. After the next release (January) you will need to remove the [0].

camblomquist commented 1 year ago

I think there's a misunderstanding. I'm aware of #199. This is a different issue. Given selection = py5.random_choice(choices)[0], the type of selection will be numpy.ndarray even though the original type was Py5Vector. The Py5Vector is getting stripped away and its data is returned instead.

hx2A commented 1 year ago

Oh, I see. Sorry @camblomquist , I didn't read your first comment carefully enough.

You are correct, this is a separate but related issue. I'm glad you found this, because it is very likely my fix for #199 would not have been a proper fix.

What I believe is happening here is numpy sees that Py5Vector implements __iter__() so it unpacks Py5Vector and makes a numpy array instead. Observe the following:

In [13]: v1 = Py5Vector2D.random()

In [14]: v2 = Py5Vector2D.random()

In [15]: np.array([v1, v2])
Out[15]: 
array([[0.59092497, 0.80672652],
       [0.20564236, 0.97862721]])

In [16]: np.abs(v1)
Out[16]: array([0.59092497, 0.80672652])

Implementing __iter__() is useful if you want to write code like py5.vector(*v1) but it also means that when using numpy functions it will convert Py5Vectors to numpy arrays.

hx2A commented 1 year ago

This is now fixed. Thank you @camblomquist for this helpful bug report.

We changed the API a bit. Now py5.random_choice() will only return one item from the input sequence. An empty input will return None.

And py5.random_sample() will return a sequence of one or more items. If you input a numpy array you'll get a numpy array output, if you input a list of things you'll get a list of things back. You can give it a generator. An empty input will return [].

In [3]: choices = [Py5Vector2D.random() for _ in range(10)]

In [4]: choices
Out[4]: 
[Py5Vector2D([0.17232667, 0.98503986]),
 Py5Vector2D([-0.94899056, -0.31530448]),
 Py5Vector2D([0.96401531, 0.26584673]),
 Py5Vector2D([-0.0318977 ,  0.99949114]),
 Py5Vector2D([ 0.45152884, -0.89225652]),
 Py5Vector2D([0.74174634, 0.67068052]),
 Py5Vector2D([-0.79971296,  0.60038253]),
 Py5Vector2D([0.84921868, 0.52804132]),
 Py5Vector2D([0.49868618, 0.86678261]),
 Py5Vector2D([0.7289256 , 0.68459292])]

In [5]: py5.random_choice(choices)
Out[5]: Py5Vector2D([0.7289256 , 0.68459292])

In [6]: py5.random_sample(choices)
Out[6]: [Py5Vector2D([-0.94899056, -0.31530448])]

In [7]: py5.random_sample(choices, size=3)
Out[7]: 
[Py5Vector2D([0.17232667, 0.98503986]),
 Py5Vector2D([ 0.45152884, -0.89225652]),
 Py5Vector2D([0.74174634, 0.67068052])]