I am all new to Rhino / Grasshopper and would like to know how to improve the speed of a Python script.
I am particularly interested in 2 options:
- using Numba and its
@jitoperator for parallelization
- replacing all the
forloops by matrix operations with Numpy
I haven’t tested the first option yet since I can’t seem to install “GH_CPython” on Rhino 6. I don’t know whether it is a compatibility issue or not, and if by chance @Mahmoud_Mohamed_Ouf reads this message, I would really appreciate to have some information regarding that matter.
I should also mention that the “GH_PythonRemote” component that I am currently testing instead doesn’t work with jit operators.
Now, the problem that I have with Numpy is when I need to convert numpy arrays of vectors to Rhino Geometry objects like
Point3d. It takes forever.
In the example below I have a simple line growth algorithm where positions, distances and forces computation is entirely vectorized with numpy. Without converting the vectors array to Point3d the script runs faster than the regular
for loop version and computes the position of about 2000 points under 100 ms per iteration (without RTrees or KDTrees). However, when I iterate over the numpy array to convert the vectors to Point3D the script slows down drastically (23 times slower in this example) and becomes much slower than the
for loop version.
definition: Line Growth (numpy).gh (8.7 KB)
import Rhino.Geometry as rg import ghpythonremote import scriptcontext np = scriptcontext.sticky['numpy'] sp = scriptcontext.sticky['scipy.spatial'] distance = sp.distance def setup(): global arr #Take every consecutive pair of points on a curve and insert a new one in between pts = np.array([[p.X, p.Y, p.Z] for p in iPoints]) avg = (pts[:-1] + pts[1:]) * .5 arr = np.insert(avg, range(pts.shape), pts, 0) print "loaded" if iDraw: push() subdivide() #Convert numpy vectors array to Point3d oPoints = [rg.Point3d(p, p, p) for p in arr] else: setup() def subdivide(): global arr #Check distances between each consecutive pair of points #insert a new point if distance is below the specified threshold d = np.sqrt(np.sum(np.diff(arr, axis=0)**2,1)) idc = np.nonzero(d>iDiameter) means = (arr[idc] + arr[idc+1]) * .5 arr = np.insert(arr, idc+1, means, 0) def push(): global arr #Computes distances matrix and set self-comparisons to NaN d = distance.cdist(arr, arr) np.fill_diagonal(d, None) #Find pairs of vectors whose separation distance is < iDiameter id1, id2 = np.nonzero(d<iDiameter) #Count (number of "too close" vectors for each vector) count = np.vstack(np.bincount(id1)) #Difference between pairs of selected vectors dif = arr[id2] - arr[id1] #Distances between pairs of selected vectors dis = np.vstack(d[id1, id2]) #Normalized differences dif /= dis #Scaled differences dif *= .5 * (iDiameter - dis) #Average of differences (0 at start) avg = np.zeros([arr.shape, 3]) #Indicates where to sum (indices) idx = np.flatnonzero(np.concatenate((, np.diff(id1)))) #Average sums of differences avg[np.unique(id1)] = np.add.reduceat(dif, idx, axis = 0) / count #Push arr -= avg
- Is there a faster way to convert the numpy array to Point3d ?
- If not, how would you use numpy to speed-up this script ?
- Do you know another / better way to improve the speed of this script ?
Any help, guidelines or hints, would be greatly appreciated.
If something isn’t clear about my question, please let me know.