I am interested in evenly distributing N points on the surface of spheres in dimensions 3 and higher.
To be more specific:
- Given a number of points N and number of dimensions D (where D > 1, N > 1)
- The distance of every point to the origin must be 1
- The minimum distance between any two points should be as large as possible
- The distance of each point to it's closest neighbour doesn't necessarily have to be the same for every point (indeed it's not possible for it to be the same unless the number of points forms the vertices of a platonic solid or if N <= D).
I am not interested in:
- Creating a uniform random distribution on a hypersphere, because I want the minimum distance between any two points to be as large as possible instead of being randomly distributed.
- Particle repulsion simulation type methods, because they are hard to implement and take an extremely long time to run for large N (Ideally the method should be deterministic and in O(n)).
One method that satisfies these criteria is called the fibonacci lattice, but I have only been able to find code implementations for that in 2d and 3d.
The method behind the fibonacci lattice (also known as the fibonacci spiral) is to generate a 1d line that spirals around the surface of the sphere such that the surface area covered by the line is roughly the same at every turn. You can then drop N points equally distributed on the spiral and they will roughly be evenly distributed on the surface of the sphere.
In this answer there is a python implementation for 3 dimensions that generates the following:
I wanted to know whether the fibonacci spiral could be extended to dimensions higher than 3 and posted a question on the maths stack exchange. To my surprise I received two amazing answers which as far as I can tell (because I don't fully understand the maths shown) shows it's indeed possible to extend this method to N dimensions.
Unfortunately I don't understand enough of the maths shown to be able to turn either answer into (pseudo)code. I am an experienced computer programmer, but my maths background only goes so far.
I will copy in what I believe to be the most important part of one of the answers below (unfortunately SO doesn't support mathjax so I had to copy as an image)
Difficulties presented by the above that I struggle with:
- How to resolve the inverse function used for ψn?
- The example given is for d = 3. How do I generate formulae for arbitrary d?
Would anyone here who understands the maths involved be able to make progress towards a pseudo code implementation of either answer to the linked fibonacci lattice question? I understand a full implementation may be quite difficult so I'd be happy with a part implementation that leads me far enough to be able to complete the rest myself.
To make it easier, I've already coded a function that takes spherical coordinates in N dimensions and turns them into cartesian coordinates, so the implementation can output either one as I can easily convert.
Additionally I see that one answer uses the next prime number for each additional dimension. I can easily code a function that outputs each successive prime, so you can assume that's already implemented.
Failing an implementation of the fibonacci lattice in N dimensions, I'd be happy to accept a different method that satisfies the above constraints.
All of the previous answer worked, but still lacked actual code. There were two real pieces missing, which this implements generally.
sin^(d-2)(x)
. This has a closed form if you do recursive integration by parts. Here I implement it in the recursive fashion, although for dimension ~> 100 I found numeric integration ofsin^d
to be fastersin^d
,d > 1
doesn't have a close form. Here I compute it using binary search, although there are likely better ways as stated in other answers.These two combined with a way to generate primes results in the full algorithm:
Which produces the following image for 200 points on a sphere:
We have n points, which are P1, ..., Pn. We have a dimension number d. Each (i = 1,n) point can be represented as:
Pi = (pi(x1), ..., pi(xd))
We know that
D(Pi, 0) = 1 <=>
sqrt((pi(x1) - pj(x1))^2 + ... + (pi(xd) - pj(xd))^2) = 1
and the minimal distance between any points, MD is
MD <= D(Pi, Pj)
A solution is acceptable if and only if MD could not be higher.
If d = 2, then we have a circle and put points on it. The circle is a polygon with the following properties:
So, a polygon of n angles, where n is a finite number and higher than 2, also, each side is of similar length is closer to a circle each time we increment n. Note that the firs polygon in d = 2 is the triangle. We have a single angle and our minimal angle unit is 360degrees / n.
Now, if we have a square and distribute the points evenly on it, then converting our square into circle via base transformation should be either the exact solution, or very close to it. If it is the exact solution, then this is a simple solution for the case when d = 2. If it is only very close, then with an approach of approximation we can determine what the solution is within a given precision of your choice.
I would use this idea for the case when d = 3. I would solve the problem for a cube, where the problem is much simpler and use base transformation to convert my cube points to my sphere points. And I would use this approach on d > 3, solving the problem for a hypercube and transform it to a hypersphere. Use the Manhattan distance when you evenly distribute your points on a hypercube of d dimensions.
Note that I do not know whether the solution for a hypercube transformed into a hypersphere is the exact solution or just close to it, but if it's not the exact solution, then we can increase precision with approximation.
So, this approach is a solution for the problem, which is not necessarily the best approach in terms of time complexity, so, if one has delved into the Fibonacci lattice area and knows how to generalize it for more dimensions, then his/her answer might be a better choice for acceptance than mine.
The invert of f(x) = x - 0.5sin2x can be determined if you defined the Taylor series of f(x). You will get a polynomial series of x which can be inverted.
Very interesting question. I wanted to implement this into mine 4D rendering engine as I was curious how would it look like but I was too lazy and incompetent to handle ND transcendent problems from the math side.
Instead I come up with different solution to this problem. Its not a Fibonaci Latice !!! Instead I expand the parametrical equation of a hypersphere or n-sphere into hyperspiral and then just fit the spiral parameters so the points are more or less equidistant.
It sounds horrible I know but its not that hard and the results look correct to me (finally :) after solving some silly typos copy/paste bugs)
The main idea is to use the n-dimensional parametrical equations for hypersphere to compute its surface points from angles and radius. Here implementation:
see the [edit2]. Now the problem boils down to 2 main problems:
compute number of screws
so if we want that our points are equidistant so they must lye on the spiral path in equidistances (see bullet #2) but also the screws itself should have the same distance between each other. For that we can exploit geometrical properties of the hypersphere. Let start with 2D:
so simply
screws = r/d
. The number of points can be also inferred aspoints = area/d^2 = PI*r^2/d^2
.so we can simply write 2D spiral as:
To be more simple we can assume
r=1.0
sod=d/r
(and just scale the points later). Then the expansions (each dimension just adds angle parameter) look like this:2D:
3D:
4D:
Now beware points for 4D are just my assumption. I empirically found out that they relate to
constant/d^3
but not exactly. The screws are different for each angle. Mine assumption is that there is no other scale thanscrews^i
but it might need some constant tweaking (did not do analysis of the resulting point-cloud as the result look ok to me)Now we can generate any point on spiral from single parameter
t=<0.0,1.0>
.Note if you reverse the equation so
d=f(points)
you can have points as input value but beware its just approximate number of points not exact !!!generate step on spirals so points are equidistant
This is the part I skip the algebraic mess and use fitting instead. I simply binary search delta
t
so the resulting point isd
distant to previous point. So simply generate pointt=0
and then binary searcht
near estimated position until isd
distant to the start point. Then repeat this untilt<=1.0
...You can use binary search or what ever. I know its not as fast as
O(1)
algebraic approach but no need to derive the stuff for each dimension... Looks 10 iterations are enough for fitting so its not that slow either.Here implementation from my 4D engine C++/GL/VCL:
Where
n=N
are set dimensionality,r
is radius andd
is desired distance between points. I am using a lot of stuff not declared here but what isimportant is just thatpnt[]
list the list of points of the object andas2(i0,i1)
adds line from points at indexesi0,i1
to the mesh.Here few screenshots...
3D perspective:
4D perspective:
4D cross-section with hyperplane
w=0.0
:and the same with more points and bigger radius:
the shape changes with rotations in which its animated ...
[Edit1] more code/info
This is how my engine mesh class look like:
I use mine dynamic list template so:
List<double> xxx;
is the same asdouble xxx[];
xxx.add(5);
adds5
to end of the listxxx[7]
access array element (safe)xxx.dat[7]
access array element (unsafe but fast direct access)xxx.num
is the actual used size of the arrayxxx.reset()
clears the array and setxxx.num=0
xxx.allocate(100)
preallocate space for100
itemsso you need to port it to any list you have at disposal (like
std:vector<>
). I also use 5x5 transform matrix whereconvert point either to global or local coordinates (by multiplying direct or inverse matrix by point). You can ignore it as its used just once in the rendering and you can copy the points instead (no rotation)... In the same header are also some constants:
I got also vector and matrix math template integrated in the transform matrix header so
vector<n>
is n dimensional vector andmatrix<n>
isn*n
square matrix but its used only for rendering so again you can ignore it. If youre interested here few links from whic all this was derived:The enums and dimension reductions are used only for rendering. The
cfg
holds how should be each dimension reduced down to 2D.AnsiString
is a self relocating string from VCL so either usechar*
or string class you got in your environment.DWORD
is just unsigned 32 bit int. Hope I did not forget something ...As a partial answer, you can use Newton's method to compute the inverse of f. Using
x
as the initial point in the Newton iteration is a good choice sincef(x)
is never more than 1 unit away fromx
. Here is a Python implementation:A nice fact about this application of Newton's method is that whenever
cos(2*x) = -1
(where you would have division by 0) you automatically havesin(2*x) = 0
so thatf(x) = x
. In this case, the while loop is never entered andf_inv
simply returns the original x.