How to generate approximately equally spaced points efficiently

I don't very content with current method.So a better solution is expected still.I hope it meet two conditions in following.

1. That space is approximately equivalence.
2. We can control how many points to produce.

v11.1 provides a new function SpherePoints. As the Details

SpherePoints[n] gives exactly equally spaced points in certain cases for small n. In other cases, it places points so they are approximately equally spaced.

Can we achieve the same goal i.e. approximately equally spaced points in an arbitrary 2D Region?

The following is my attempt based on Union:

SeedRandom[1]
region = ConvexHullMesh[RandomReal[1, {100, 2}]];
UniformPts =
Union[RandomPoint[region, 50000],
SameTest -> (EuclideanDistance[#1, #2] < .1 &)];
Show[region, Graphics[Point[UniformPts]]]


Nevertheless, this approach has two weakness:

1. It is slow with a large number of pre-generated points i.e. the 2nd argument of RandomPoint, while the space won't be uniform enough if I don't pre-generate enough points, here's an example:

2. The number of resulting points isn't controllable.

• Is the region always 2D? – Kagaratsch Mar 28 '17 at 13:23
• @Kagaratsch Yes.. – yode Mar 28 '17 at 13:28
• See the edit of my answer. – Kagaratsch Mar 28 '17 at 16:33
• – C. E. Mar 28 '17 at 21:39
• – anderstood Mar 28 '17 at 21:42

Annealing

Found this to be an interesting question and immediately I thought it to be a good application for simulated annealing.

Here's a little unoptimized annealing function I wrote. The idea is that your points move around like atoms in random directions but they "cool down" over time and move less and settle into a minimum energy configuration state.

My rules are:

1. plan a move in a random direction and random distance of maximum length step
2. move only if the distance to the nearest point increases
3. move only if the new location is inside the region

Assumes rm is a globally defined RegionMember function.

anneal[pts_, step_] :=
Module[{np, nn, test1, test2, pl, potentialMoves},
pl = Length@pts;
np = Nearest@pts;
nn = np[#, 2][[2]] & /@ pts;
potentialMoves = RandomReal[step, pl]*RandomPoint[Circle[], pl];
test1 =
MapThread[EuclideanDistance[#1, #2] &, {pts, nn}] <
EuclideanDistance[#1, #2] &, {pts + potentialMoves, nn}]];
test2 = Boole[rm /@ (pts + potentialMoves)];
pts + potentialMoves*test1*test2]


Here is an example with 200 pts, 1000 steps and an anneal rate of .995. Initial step should be on the order of the region size:

Clear[x,y];reg=ImplicitRegion[x^2-y^2<=1,{{x,-3,3},{y,-3,3}}];
rm=RegionMember[reg];
pts=RandomPoint[reg,200];
step=1;
Do[pts=anneal[pts,step=.995*step],1000];
Show[RegionPlot[reg],Graphics[{Black,Point/@pts}]]


Here is an animation of the process:

• It is not a high efficient solution,but it is the answer which I content with indeed.Thanks very much. – yode Apr 11 '17 at 19:27
• Now that I think about it, one might even consider a hybrid of kirma's and Kelly's approaches: start with annealing for a few steps, and accelerate convergence near the end with Lloyd. – J. M. will be back soon Apr 13 '17 at 1:23
• I think we should make the move have a rough but not a random direction maybe will be better. – yode Apr 13 '17 at 13:01

Many solutions similar to how to get $n$ equidistributed points on the unit sphere are possible, especially if one can accept that points are not on the edges of a region. For instance, one can use analytical Lloyd's method:

With[{reg = RegularPolygon[5]},
Nest[RegionNearest[reg][
RegionCentroid@RegionIntersection[reg, #] & /@
MeshPrimitives[VoronoiMesh@#, 2]] &, RandomPoint[reg, 20], 20] //
Graphics[{LightBlue, reg, Black, Point@#}] &]


This is awfully slow, though.

A much faster variant is to use a Monte Carlo method to estimate Voronoi cell centroids:

With[{reg = RegularPolygon[5]},
With[{points = 500, samples = 40000, iterations = 20},
Nest[With[{randoms = Join[#, RandomPoint[reg, samples]]},
RegionNearest[reg][
Mean@randoms[[#]] & /@
Values@PositionIndex@Nearest[#, randoms]]] &,
RandomPoint[reg, points], iterations]] //
Graphics[{LightBlue, reg, Black, Point@#}] &]


A more complex concave example (which benefits from higher iteration count) as suggested by @J.M.:

With[{reg =
Cases[GraphicsPolygonUtilsPolygonData[
"HexaSpiral"], _Polygon, \[Infinity]][[1]]},
With[{points = 500, samples = 40000, iterations = 200},
Nest[With[{randoms = Join[#, RandomPoint[reg, samples]]},
RegionNearest[reg][
Mean@randoms[[#]] & /@
Values@PositionIndex@Nearest[#, randoms]]] &,
RandomPoint[reg, points], iterations]] //
Graphics[{LightBlue, reg, Black, Point@#}] &]


• Good work,and have you seen this blog? – yode Mar 29 '17 at 2:36
• This seems to do well with concave regions, too. Try, for instance, reg = Cases[GraphicsPolygonUtilsPolygonData["HexaSpiral"], _Polygon, ∞][[1]] – J. M. will be back soon Mar 29 '17 at 3:32
• @yode Yes, I've seen it. Lloyd's method is a nice trick people tend to find when thinking of these things. I'm not certain how I found it out, but it's also featured in Mma documentation of VoronoiMesh. – kirma Mar 29 '17 at 4:09
• @yode @ kirma Regarding the performance and generalization to arbitrary region, I had some significant improvements. The key-points are vectorization and compilation. While getting time and appropriate chance, I'd definitely like to share them. – Silvia Apr 13 '17 at 8:59
• @yode I'm sincerely sorry to say that I might not be able to find some time until at least 1 month later... Also I think, in principle, my method would have no huge differences from kirma's. It's just the old blog with some numerical optimizations. – Silvia Apr 13 '17 at 15:38

This does not give you real control over the number of points, but its one way to get somewhat equally spaced distribution of points:

region = ConvexHullMesh[RandomReal[1, {100, 2}]];
<< NDSolveFEM
elementMesh = ToElementMesh[region, MeshQualityGoal -> 0];
coordinates = elementMesh[[1]];
triangles = elementMesh[[2, 1, 1]];
centers =
Table[
Sum[{coordinates[[triangles[[i, j]], 1]],coordinates[[triangles[[i, j]], 2]]}, {j, 1, 3}]/3
, {i, 1, Length[triangles]}];
Show[region, Graphics[Point[centers]]]


Update

Trivial solution, but seems to be relatively quick:

numberOfPoints = 500;
region = BoundaryDiscretizeGraphics[Text["K"], _Text];
<< NDSolveFEM
elementMesh = ToElementMesh[region, MeshQualityGoal -> 0];
coordinates = Transpose[elementMesh[[1]]];
Xmin = Min[coordinates[[1]]];
Xmax = Max[coordinates[[1]]];
Ymin = Min[coordinates[[2]]];
Ymax = Max[coordinates[[2]]];
scale = (Xmax - Xmin + Ymax - Ymin)/2;
spacing = scale/40;
test[x_] := RegionMember[region, x];
points := DeleteCases[
Flatten[Table[ If[test[{i, j}], {i, j}, -1], {i, Xmin, Xmax, spacing}, {j, Ymin, Ymax, spacing}], 1]
, -1];
(
data = Table[{1/(spacing = ii scale/300), points // Length} // Reverse, {ii, 10, 20}];
fun[x_] = NonlinearModelFit[data, a + b x^(1/2), {a, b}, x] // Normal;
spacing = 1/fun[numberOfPoints];
(pts = points) // Length
) // AbsoluteTiming
Show[region, Graphics[Point[pts]]]


{0.187837, 505}

We can see that within 0.188 seconds we have generated a distribution of 505 points where we tried to generate a 500 points distribution. The error in number of points is always just a few percent at the most.

• Thanks for the new update. :)And it's seem cannot control the number of points still? – yode Mar 28 '17 at 16:37
• For given region, you could get desired number of points by fine tuning the spacing variable. Requires some trial and error, or you could write a short minimization routine... Would make it slower though. – Kagaratsch Mar 28 '17 at 16:41
• @yode Honestly, density of points might be a more objective measure than number of points, since you never know what total area the region has before you generate it. So being able to adjust the spacing is probably more useful than number of points. Don't you agree? – Kagaratsch Mar 28 '17 at 16:45
• Actually don't very agree. :) – yode Mar 28 '17 at 16:47
• @yode see the update of the trivial solution above. This allows to generate a distribution of approximately the desired amount of points. The error is always within a few percent, as far as I have tested. – Kagaratsch Apr 11 '17 at 22:37

Rebuilt and little refine for this answer

equPoint[region_, pointNum_] :=
Module[{pts, stepDist, rm, regionRange, beFar, InRegion, moveDir},
pts = RandomPoint[region, pointNum];
rm = RegionMember[region];
regionRange =
EuclideanDistance @@ Transpose[RegionBounds[region]]/100;
stepDist = Subdivide[regionRange, 0, 500];
moveDir =
RandomReal[#, pointNum]*RandomPoint[Circle[], pointNum] & /@
stepDist;
Fold[(beFar =
EuclideanDistance @@@ Nearest[#, #, 2] <
EuclideanDistance @@@ Nearest[#1 + #2, #1 + #2, 2]];
InRegion = Boole[rm /@ (#1 + #2)];
# + #2*beFar*InRegion) &, N[pts], moveDir]]


Explanation

I have not iterated 1000 times,but make the difference of the last two times less the pre,whose default value is 0.001,after 500 times.And you don't need to consider the size of region.

Example

region = ImplicitRegion[x^2 - y^2 <= 1, {{x, -3, 3}, {y, -3, 3}}];
BoundaryDiscretizeRegion[region,
Epilog -> {Point[equPoint[region, 200]]}]


Here is an animation of the process: