# Optimize GHPython random 2D points component for faster population?

Hi,

I’ve ported the `Population` method from the Vector GHa lib, mentioned by @DavidRutten in this thread to GHPython. Now I’m looking for advice, on how to optimise the code in order to make it run faster, since I need it to produce about 10 000 to 20 000 points (tuples with 3 coordinates) for me.

Here are the current speeds of the Python component:

Population Time Percentage
10 6 ms 28 %
100 853 ms 85 %
1000 1.3 min 100 %
10 000 60 min >100 %

This is what the code looks like! The most interesting section to optimise might be the `populate()` method of the `Population` class.

``````import Rhino.Geometry as rg
import random
import math
import sys

class Population:
"""A population of random points within a region.

To use:
>>> ppl = Population(10, (25,25))
>>> ppl.populate()

>>> ppl.get_coordinates()
[(x0, y0, z0), (x1, y1, y2), ..., (x10, y10, z10)]

>>> ppl.get_points()
[Rhino.Geometry.Point3d, Rhino.Geometry.Point3d, ..., Rhino.Geometry.Point3d]
"""

def __init__(self, count, region):
self.count = int(count)
self.region = region
self.population = []

def distance_to(self, ptA, ptB):
"""Calculate the distance between two 3-dimensional points.

Args:
ptA: 3-dimensional point A (Rhino.Geometry.Point3d, tuple or list)
ptB: 3-dimensional point B (Rhino.Geometry.Point3d, tuple or list)
Returns:
The distance between point A and point B.
"""
dist = math.sqrt((ptA[0] -  ptB[0])**2 + (ptA[1] -  ptB[1])**2 + (ptA[2] -  ptB[2])**2)
return dist

def next_point(self, domain):
"""Constructs a random 2-dimensional point coordinate.

Args:
domain: Tuple of 2 maximum values in x and y.
Returns:
Returns a tuple of random 2D point coordinates.
"""
maxX = domain[0]
minX = -maxX
maxY = domain[1]
minY = -maxY
return (random.uniform(minX, maxX), random.uniform(minY, maxY), 0)

def populate(self):
"""Populates a 2-dimensional region with random 2-dimensional points."""
distance_threshold = sys.maxint

for i in range(self.count):
if (len(self.population) == 0): # first point
pt = self.next_point(self.region)
self.population.append(pt)

else: # other points
attempts = int(max(50, math.sqrt(i)))
max_dist = -sys.maxint
max_pt = 0

for k in range(attempts):
if (k > 100) and (max_dist > distance_threshold * 0.9):
break
elif (k > 50) and (max_dist > distance_threshold * 0.92):
break
elif (k > 25) and (max_dist > distance_threshold * 0.95):
break

pt = self.next_point(self.region)
dist = sorted(self.distance_to(pt, prev_pt) for prev_pt in self.population)[0]
if dist == sys.maxint:
break
if dist > max_dist:
max_dist = dist
max_pt = pt
if (max_dist > distance_threshold * 1.25):
break

distance_threshold = max_dist
self.population.append(max_pt)

def get_coordinates(self):
"""Returns a list of random 2-dimensional point coordinates witihn a region."""
return self.population

def get_points(self):
"""Returns a list of Rhino.Geometry.Point3d witin a redion."""
pts = [rg.Point3d(c[0], c[1], c[2]) for c in self.population]
return pts

# Variables
count = 10 # number of random points to produce
domain = (5.0, 5.0) # max. extends of the region in x and y

# Initialise class
ppl = Population(count, domain)
ppl.populate()

# Output
a = ppl.get_points()
``````

(To test the script, just copy and paste the python code into a GHPython component and run it.)

Any advice is welcome and appreciated!

``````import rhinoscriptsyntax as rs
rs.EnableRedraw(False)
...code here...
rs.EnableRedraw(True)
``````

Could you refactor the code to use generators instead of creating a massive list of points? You could keep a list of guid if you need to keep track of them

1 Like

Thanks @Dancergraham!

Could you explain a little, what you mean by generators? Would keeping a list of GUIDs be less expensive than keeping a list op tuples with 3 float values each?

Generators let you generate objects one by one rather than storing them all in memory. They are defined much like functions but use yield rather than return:

2 Likes

No saving in using the guid rather than the tuple

``````import rhinoscriptsyntax as rs
import sys
import Rhino.Geometry as rg

ptcoords = (1000.,1000.,1000.)
rhpt = rg.Point3d(*ptcoords)

print (ptcoords, sys.getsizeof(ptcoords))
print (pt, sys.getsizeof(pt))
print (rhpt, sys.getsizeof(rhpt))
``````

((1000.0, 1000.0, 1000.0), 16)
(<System.Guid object at 0x0000000000000034 [7f7452f7-9235-406d-ad57-85c8fb5a4684]>, 16)
(<Rhino.Geometry.Point3d object at 0x0000000000000035 [1000,1000,1000]>, 16)

1 Like

Thanks, great explanation!