1

I have a function like this (which I cannot change):

def myFunc(x, y):
    nx = int(x)
    ny = int(y)
    Freq = 0.4

    e0 = 1 * gen.noise2d(Freq * nx, Freq * ny)
    return e0 + 500

Right now, I am trying to use an np.ndarray for other parts of my code, and pass in the x and y values in my loop one at a time:

# passing in a particles which is an array like this:
#     [[2,4], [5, 9], [2, 5]]
# this array would have more than 5000 pairs of x, y coordinates

def mapFunc(particles):
    N = len(particles)
    mask = []
    distance = []

    for i in range(N):
        x = particles[i][0]
        y = particles[i][1]
        ground_dist = mapFunc(x, y)
 
        # add in the distances to the ground
        distance.append(ground_dist)

        # add True if the ground is less than 50 feet away
        mask.append(ground_dist < 50)

    return distance, mask

Is there a better/faster/more efficient way to get the values from my np.ndarray? Can I somehow pass in the whole arrary into myFunc? The problem is int(x) and int(y), not sure how to work with that in Python in regards to an array.

Edit 1 - There was a mistake in the return of myFunc, it was supposed to be using e0 to add 500

Edit 2 - the gen.noise2d is from https://github.com/lmas/opensimplex to "Generate 2D OpenSimplex noise from X,Y coordinates."

sleo
  • 13
  • 3

1 Answers1

0

You can fully vectorize your code if the following two conditions are met:

  1. gen.noise2d is vectorizable (possibly using the technique shown below), or ignorable
  2. myFunc is a python function, as opposed to one written in C

You can monkeypatch the name int it myFunc's global namespace to refer to np.around or np.trunc. The latter is closer to what int currently does in the code:

myFunc.__globals__['int'] = np.trunc

You may need to either modify the dependencies of myFunc.__globals__['gen']['noise2d'], or swap it out entirely. Alternatively, you may want to ignore the noise2d function entirely since its result does not appear to be used in the first place.

Now you can rewrite your code as follows:

def mapFunc(particles):
    particles = np.asarray(particles)
    distance = myFunc(*particles.T)
    mask = distance < 50
    return distance, mask

The line myFunc.__globals__['int'] = np.trunc will modify the __dict__ of the module that myFunc is defined in. This may be a bad thing if you want to use the real int elsewhere in that module. Since the __globals__ attribute is read-only, you can create a copy of the function object with the original code and a different globals. This is likely overkill, so I will link you to the following post: How to create a copy of a python function.

Perhaps a simpler solution is just to bind a different object to the name myFunc, and assign it to the appropriate module?

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Thanks! This seems to be on the right track. I made an error in my question, it is using the result from noise2d result in the return line, I just corrected it in the question `return e0 + 500` – sleo Sep 15 '20 at 14:02
  • @sleo. In that case, I recommend adding that function to the question. If this answer helps you move forward, I suggest selecting it by clicking on the check mark next to it. That will remove your question from the unanswered queue. – Mad Physicist Sep 15 '20 at 14:04
  • So I dug a bit deeper and now it seems like `gen.noise2d` is causing problems. This is guy is from https://github.com/lmas/opensimplex Now its saying the line in noise2d `xsb = floor(xs)` is the culprit, giving the error message `TypeError: only size-1 arrays can be converted to Python scalars` – sleo Sep 15 '20 at 16:56
  • @sleo. That's because `math.floor` calls `__float__` on the input. Numpy arrays support that just fine, but you can't convert a multi-array element to a single float, hence the error. So monkey-patch `floor` to `numpy.floor`: `myFunc.__globals__['gen']['noise2d'].__globals__['floor'] = np.floor`. It's a lot of work for something that should already be vectorized, but I hope you're at least having fun with it. – Mad Physicist Sep 15 '20 at 17:13
  • @sleo. The semi-nice thing about monkeypatching in numpy functions like that is that you aren't likely to break anything. The scalar interface is essentially the same, and duck typing ensures that if your outputs are an array instead of a float, none of the consumers will care since it behaves the same. – Mad Physicist Sep 15 '20 at 17:16
  • Thanks for the quick response! I tried it out but no dice: `TypeError: 'type' object is not subscriptable`. Tried digging a bit, and it seems like the OpenSimplex import is not letting me change the globals in there. I am kind of stuck using these functions, which for the life of me I can't figure out why they would make it so constraint to not being able to use numpy. – sleo Sep 15 '20 at 18:11
  • @sleo. Please ask another question. I suspect that the issue is not what you think it is, just that you're accessing the wrong object. It may be that `gen` is a class with a static `noise2d` method, which just requires different access. If you ask a question, you can post all the info I would need to answer, which you can't really do in a comment. – Mad Physicist Sep 15 '20 at 18:17
  • Also, why are you tied to this library? The concept is nice, but the implementation is pretty terrible. – Mad Physicist Sep 15 '20 at 18:18
  • @sleo And if you do post another question, be sure to ping me here, as well as to show exactly how you get the `gen` object. I did not realize that it is something that you are making in your own namespace. I thought `gen` was a module... – Mad Physicist Sep 15 '20 at 18:21