Covariance matrix computation - algorithm

Input : random vector X=xi, i=1..n.
vector of means for X=meanxi, i=1..n
Output : covariance matrix Sigma (n*n).
Computation : 1) find all cov(xi,xj)= 1/n * (xi-meanxi) * (xj-meanxj), i,j=1..n
2) Sigma(i,j)=cov(xi,xj), symmetric matrix.
Is this algorithm correct and has no side-effects?

Each xi should be a vector (random variable) with it's own variance and mean.
Covariance matrix is symmetric, so you just need to compute one half of it (and copy the rest) and has variance of xi at main diagonal.
S = ...// your symmetric matrix n*n
for(int i=0; i<n;i++)
S(i,i) = var(xi);
for(j = i+1; j<n; j++)
S(i,j) = cov(xi, xj);
S(j,i) = S(i,j);
end
end
where variance (var) of xi:
v = 0;
for(int i = 0; i<xi.Count; i++)
v += (xi(i) - mean(xi))^2;
end
v = v / xi.Count;
and covariance (cov)
cov(xi, xj) = r(xi,xj) * sqrt(var(xi)) * sqrt(var(xj))
where r(xi, xj) is Pearson product-moment correlation coefficient
EDIT
or, since cov(X, Y) = E(X*Y) - E(X)*E(Y)
cov(xi, xj) = mean(xi.*xj) - mean(xi)*mean(xj);
where .* is Matlab-like element-wise multiplication.
So if x = [x1, x2], y = [y1, y2] then z = x.*y = [x1*y1, x2*y2];

Related

Is there an approximation to get meanvalue and standard deviation in one loop

I have a collection of n floating point values: x[n]. When I want to calculate the meanvalue and standard deviation, I need to iterate with two loops over all values:
First loop to sum all values and calculate the meanvalue:
sum = 0
for(i=0; i<n; i++)
sum += x[i]
mean = sum/n
In a second loop I calculate the standard deviation:
sum = 0
for(i=0; i<n; i++)
sum += pow2(x[i] - mean)
sder = sqrt(sum/n)
I am aware that you cannot reduce this complexity if you want to the exact values for meanvalue and standard deviation. But is there a way to calculate them in less time if you just approximate? Favoured in one loop.
Have a look at this section of the wiki on standard deviation, in particular the last formula leads to the following algorithm:
sum = 0;
sumsqrd = 0;
for(i = 0; i < n; i++)
sum += x[i]
sumsqrd += x[i] * x[i]
mean = sum / n
stddev = sqrt(sumsqrd / n - mean * mean)
Here's a version which does the calculations in one pass, and is computationally more stable:
mean = 0.0
sum_sqrs = 0.0
n = 0
loop do
x = get_x()
break if x == nil
delta = x - mean
n += 1
mean += delta / n
sum_sqrs += delta * (x - mean)
end
sample_var = sum_sqrs / (n - 1)
This is based on the formulas found in the bottom half of the Rapid calculation methods section of the Wikipedia page for Standard deviation.

Finding the point on a simplex closest to the origin using GJK's distance subalgorithm

I'm trying to implement the Gilbert–Johnson–Keerthi distance algorithm (GJK), but I'm having problems with the "distance subalgorithm", also known as "Johnson's Algorithm", which is used to determine the point on a simplex that is closest to the origin. I'm getting incorrect results but I can't find any bugs in my code, so the problem must be in my interpretation of the algorithm.
In Johnson’s Algorithm (as described in Gino van den Bergen's book Collision Detection in Interactive 3D Environments), the point on the affine hull of a simplex X = {yi : i ∈ Ix} closest to the origin is given by:
Where the Δi^X values are determined recursively in order of increasing cardinality of X:
... and Δ^X is given by:
For two dimensions, I find the closest point to the origin using:
Point ClosestPointToOrigin(Simplex simplex)
{
float dx = 0;
for (int i = 0; i < simplex.size(); ++i)
dx += dj(simplex, i);
Point closest_point(0,0);
for (int i = 0; i < simplex.size(); ++i)
closest_point += dj(simplex, i) / dx * simplex[i];
return closest_point;
}
In which the Δi values are determined by:
float dj(Simplex simplex, int j)
{
if (j == 0)
{
return 1;
}
else
{
float d = 0;
for (int i = 0; i < j; ++i)
d += dj(simplex, i) * (simplex[0] - simplex[j]).dotProduct(simplex[i]);
return d;
}
}
For a simplex X = {y1, y2} where y1 = (1,1), y2 = (1,-1), the above code returns (1.0, -0.333333), when the closest point is, in fact, (1, 0).
I must be doing something wrong, but I can't figure out what that is.
Your error is the dj function, maybe you have misunderstood the dxi equation or you did not write what you want.
I will try to explain myself, do not hesitate to comment if you do not understand something (I am writing pseudo python code but it should be easily understandable).
Assume I have the following Simplex:
S = Simplex({
1: Point (1, 1) # y1
2: Point (1,-1) # y2
})
I can immediately compute 2 deltas values:
Then, I can compute 2 others deltas values:
Hopefully by now you'll start to see your mistake: The Δ values are index based, so for each Simplex X of dimension n, you have n Δ values. One of your mistake was to assume that you can compute ΔX0 and ΔXi regardless of the content of X, which is false.
Now the last Δ:
Notice that:
Once you are here:
Here is a code written in Python, if you do not understand it, I'll try to write one in a language you understand:
import numpy
class Point(numpy.ndarray):
def __new__(cls, x, y):
return numpy.asarray([x, y]).astype(float).view(cls)
def __str__(self):
return repr(self)
def __repr__(self):
return "Point ({}, {})".format(self.x, self.y)
x = property(fget=lambda s: s[0])
y = property(fget=lambda s: s[1])
class Simplex(dict):
def __init__(self, points):
super(Simplex, self).__init__(enumerate(points))
def __str__(self):
return repr(self)
def __repr__(self):
return "Simplex <" + dict.__repr__(self) + ">"
def closest_point(s):
dx = sum(dj(s, i) for i in range(len(s)))
return sum(dj(s, i) / dx * v for i, v in s.items())
def dj(s, j):
if len(s) == 0 or (len(s) == 1 and j not in s):
print(s, j)
raise ValueError()
if len(s) == 1:
return 1
ts = s.copy()
yj = s[j]
del ts[j]
return sum(
dj(ts, i) * (ts[list(ts.keys())[0]] - yj).dot(v)
for i, v in ts.items()
)
S = Simplex([Point(1, 1), Point(1, -1)])
print(closest_point(S))

Is there any fast method of matrix exponentiation?

Is there any faster method of matrix exponentiation to calculate Mn (where M is a matrix and n is an integer) than the simple divide and conquer algorithm?
You could factor the matrix into eigenvalues and eigenvectors. Then you get
M = V * D * V^-1
Where V is the eigenvector matrix and D is a diagonal matrix. To raise this to the Nth power, you get something like:
M^n = (V * D * V^-1) * (V * D * V^-1) * ... * (V * D * V^-1)
= V * D^n * V^-1
Because all the V and V^-1 terms cancel.
Since D is diagonal, you just have to raise a bunch of (real) numbers to the nth power, rather than full matrices. You can do that in logarithmic time in n.
Calculating eigenvalues and eigenvectors is r^3 (where r is the number of rows/columns of M). Depending on the relative sizes of r and n, this might be faster or not.
It's quite simple to use Euler fast power algorith. Use next algorith.
#define SIZE 10
//It's simple E matrix
// 1 0 ... 0
// 0 1 ... 0
// ....
// 0 0 ... 1
void one(long a[SIZE][SIZE])
{
for (int i = 0; i < SIZE; i++)
for (int j = 0; j < SIZE; j++)
a[i][j] = (i == j);
}
//Multiply matrix a to matrix b and print result into a
void mul(long a[SIZE][SIZE], long b[SIZE][SIZE])
{
long res[SIZE][SIZE] = {{0}};
for (int i = 0; i < SIZE; i++)
for (int j = 0; j < SIZE; j++)
for (int k = 0; k < SIZE; k++)
{
res[i][j] += a[i][k] * b[k][j];
}
for (int i = 0; i < SIZE; i++)
for (int j = 0; j < SIZE; j++)
a[i][j] = res[i][j];
}
//Caluclate a^n and print result into matrix res
void pow(long a[SIZE][SIZE], long n, long res[SIZE][SIZE])
{
one(res);
while (n > 0) {
if (n % 2 == 0)
{
mul(a, a);
n /= 2;
}
else {
mul(res, a);
n--;
}
}
}
Below please find equivalent for numbers:
long power(long num, long pow)
{
if (pow == 0) return 1;
if (pow % 2 == 0)
return power(num*num, pow / 2);
else
return power(num, pow - 1) * num;
}
Exponentiation by squaring is frequently used to get high powers of matrices.
I would recommend approach used to calculate Fibbonacci sequence in matrix form. AFAIK, its efficiency is O(log(n)).

Fastest way to generate binomial coefficients

I need to calculate combinations for a number.
What is the fastest way to calculate nCp where n>>p?
I need a fast way to generate binomial coefficients for an polynomial equation and I need to get the coefficient of all the terms and store it in an array.
(a+b)^n = a^n + nC1 a^(n-1) * b + nC2 a^(n-2) * ............
+nC(n-1) a * b^(n-1) + b^n
What is the most efficient way to calculate nCp ??
You cau use dynamic programming in order to generate binomial coefficients
You can create an array and than use O(N^2) loop to fill it
C[n, k] = C[n-1, k-1] + C[n-1, k];
where
C[1, 1] = C[n, n] = 1
After that in your program you can get the C(n, k) value just looking at your 2D array at [n, k] indices
UPDATE smth like that
for (int k = 1; k <= K; k++) C[0][k] = 0;
for (int n = 0; n <= N; n++) C[n][0] = 1;
for (int n = 1; n <= N; n++)
for (int k = 1; k <= K; k++)
C[n][k] = C[n-1][k-1] + C[n-1][k];
where the N, K - maximum values of your n, k
If you need to compute them for all n, Ribtoks's answer is probably the best.
For a single n, you're better off doing like this:
C[0] = 1
for (int k = 0; k < n; ++ k)
C[k+1] = (C[k] * (n-k)) / (k+1)
The division is exact, if done after the multiplication.
And beware of overflowing with C[k] * (n-k) : use large enough integers.
If you want complete expansions for large values of n, FFT convolution might be the fastest way. In the case of a binomial expansion with equal coefficients (e.g. a series of fair coin tosses) and an even order (e.g. number of tosses) you can exploit symmetries thus:
Theory
Represent the results of two coin tosses (e.g. half the difference between the total number of heads and tails) with the expression A + A*cos(Pi*n/N). N is the number of samples in your buffer - a binomial expansion of even order O will have O+1 coefficients and require a buffer of N >= O/2 + 1 samples - n is the sample number being generated, and A is a scale factor that will usually be either 2 (for generating binomial coefficients) or 0.5 (for generating a binomial probability distribution).
Notice that, in frequency, this expression resembles the binomial distribution of those two coin tosses - there are three symmetrical spikes at positions corresponding to the number (heads-tails)/2. Since modelling the overall probability distribution of independent events requires convolving their distributions, we want to convolve our expression in the frequency domain, which is equivalent to multiplication in the time domain.
In other words, by raising our cosine expression for the result of two tosses to a power (e.g. to simulate 500 tosses, raise it to the power of 250 since it already represents a pair), we can arrange for the binomial distribution for a large number to appear in the frequency domain. Since this is all real and even, we can substitute the DCT-I for the DFT to improve efficiency.
Algorithm
decide on a buffer size, N, that is at least O/2 + 1 and can be conveniently DCTed
initialise it with the expression pow(A + A*cos(Pi*n/N),O/2)
apply the forward DCT-I
read out the coefficients from the buffer - the first number is the central peak where heads=tails, and subsequent entries correspond to symmetrical pairs successively further from the centre
Accuracy
There's a limit to how high O can be before accumulated floating-point rounding errors rob you of accurate integer values for the coefficients, but I'd guess the number is pretty high. Double-precision floating-point can represent 53-bit integers with complete accuracy, and I'm going to ignore the rounding loss involved in the use of pow() because the generating expression will take place in FP registers, giving us an extra 11 bits of mantissa to absorb the rounding error on Intel platforms. So assuming we use a 1024-point DCT-I implemented via the FFT, that means losing 10 bits' accuracy to rounding error during the transform and not much else, leaving us with ~43 bits of clean representation. I don't know what order of binomial expansion generates coefficients of that size, but I dare say it's big enough for your needs.
Asymmetrical expansions
If you want the asymmetrical expansions for unequal coefficients of a and b, you'll need to use a two-sided (complex) DFT and a complex pow() function. Generate the expression A*A*e^(-Pi*i*n/N) + A*B + B*B*e^(+Pi*i*n/N) [using the complex pow() function to raise it to the power of half the expansion order] and DFT it. What you have in the buffer is, again, the central point (but not the maximum if A and B are very different) at offset zero, and it is followed by the upper half of the distribution. The upper half of the buffer will contain the lower half of the distribution, corresponding to heads-minus-tails values that are negative.
Notice that the source data is Hermitian symmetrical (the second half of the input buffer is the complex conjugate of the first), so this algorithm is not optimal and can be performed using a complex-to-complex FFT of half the required size for optimum efficiency.
Needless to say, all the complex exponentiation will chew more CPU time and hurt accuracy compared to the purely real algorithm for symmetrical distributions above.
This is my version:
def binomial(n, k):
if k == 0:
return 1
elif 2*k > n:
return binomial(n,n-k)
else:
e = n-k+1
for i in range(2,k+1):
e *= (n-k+i)
e /= i
return e
I recently wrote a piece of code that needed to call for a binary coefficient about 10 million times. So I did a combination lookup-table/calculation approach that's still not too wasteful of memory. You might find it useful (and my code is in the public domain). The code is at
http://www.etceterology.com/fast-binomial-coefficients
It's been suggested that I inline the code here. A big honking lookup table seems like a waste, so here's the final function, and a Python script that generates the table:
extern long long bctable[]; /* See below */
long long binomial(int n, int k) {
int i;
long long b;
assert(n >= 0 && k >= 0);
if (0 == k || n == k) return 1LL;
if (k > n) return 0LL;
if (k > (n - k)) k = n - k;
if (1 == k) return (long long)n;
if (n <= 54 && k <= 54) {
return bctable[(((n - 3) * (n - 3)) >> 2) + (k - 2)];
}
/* Last resort: actually calculate */
b = 1LL;
for (i = 1; i <= k; ++i) {
b *= (n - (k - i));
if (b < 0) return -1LL; /* Overflow */
b /= i;
}
return b;
}
#!/usr/bin/env python3
import sys
class App(object):
def __init__(self, max):
self.table = [[0 for k in range(max + 1)] for n in range(max + 1)]
self.max = max
def build(self):
for n in range(self.max + 1):
for k in range(self.max + 1):
if k == 0: b = 1
elif k > n: b = 0
elif k == n: b = 1
elif k == 1: b = n
elif k > n-k: b = self.table[n][n-k]
else:
b = self.table[n-1][k] + self.table[n-1][k-1]
self.table[n][k] = b
def output(self, val):
if val > 2**63: val = -1
text = " {0}LL,".format(val)
if self.column + len(text) > 76:
print("\n ", end = "")
self.column = 3
print(text, end = "")
self.column += len(text)
def dump(self):
count = 0
print("long long bctable[] = {", end="");
self.column = 999
for n in range(self.max + 1):
for k in range(self.max + 1):
if n < 4 or k < 2 or k > n-k:
continue
self.output(self.table[n][k])
count += 1
print("\n}}; /* {0} Entries */".format(count));
def run(self):
self.build()
self.dump()
return 0
def main(args):
return App(54).run()
if __name__ == "__main__":
sys.exit(main(sys.argv))
If you really only need the case where n is much larger than p, one way to go would be to use the Stirling's formula for the factorials. (if n>>1 and p is order one, Stirling approximate n! and (n-p)!, keep p! as it is etc.)
The fastest reasonable approximation in my own benchmarking is the approximation used by the Apache Commons Maths library: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/special/Gamma.html#logGamma(double)
My colleagues and I tried to see if we could beat it, while using exact calculations rather than approximates. All approaches failed miserably (many orders slower) except one, which was 2-3 times slower. The best performing approach uses https://math.stackexchange.com/a/202559/123948, here is the code (in Scala):
var i: Int = 0
var binCoeff: Double = 1
while (i < k) {
binCoeff *= (n - i) / (k - i).toDouble
i += 1
}
binCoeff
The really bad approaches where various attempts at implementing Pascal's Triangle using tail recursion.
nCp = n! / ( p! (n-p)! ) =
( n * (n-1) * (n-2) * ... * (n - p) * (n - p - 1) * ... * 1 ) /
( p * (p-1) * ... * 1 * (n - p) * (n - p - 1) * ... * 1 )
If we prune the same terms of the numerator and the denominator, we are left with minimal multiplication required. We can write a function in C to perform 2p multiplications and 1 division to get nCp:
int binom ( int p, int n ) {
if ( p == 0 ) return 1;
int num = n;
int den = p;
while ( p > 1 ) {
p--;
num *= n - p;
den *= p;
}
return num / den;
}
I was looking for the same thing and couldn't find it, so wrote one myself that seems optimal for any Binomial Coeffcient for which the endresult fits into a Long.
// Calculate Binomial Coefficient
// Jeroen B.P. Vuurens
public static long binomialCoefficient(int n, int k) {
// take the lowest possible k to reduce computing using: n over k = n over (n-k)
k = java.lang.Math.min( k, n - k );
// holds the high number: fi. (1000 over 990) holds 991..1000
long highnumber[] = new long[k];
for (int i = 0; i < k; i++)
highnumber[i] = n - i; // the high number first order is important
// holds the dividers: fi. (1000 over 990) holds 2..10
int dividers[] = new int[k - 1];
for (int i = 0; i < k - 1; i++)
dividers[i] = k - i;
// for every dividers there is always exists a highnumber that can be divided by
// this, the number of highnumbers being a sequence that equals the number of
// dividers. Thus, the only trick needed is to divide in reverse order, so
// divide the highest divider first trying it on the highest highnumber first.
// That way you do not need to do any tricks with primes.
for (int divider: dividers) {
boolean eliminated = false;
for (int i = 0; i < k; i++) {
if (highnumber[i] % divider == 0) {
highnumber[i] /= divider;
eliminated = true;
break;
}
}
if(!eliminated) throw new Error(n+","+k+" divider="+divider);
}
// multiply remainder of highnumbers
long result = 1;
for (long high : highnumber)
result *= high;
return result;
}
If I understand the notation in the question, you don't just want nCp, you actually want all of nC1, nC2, ... nC(n-1). If this is correct, we can leverage the following relationship to make this fairly trivial:
for all k>0: nCk = prod_{from i=1..k}( (n-i+1)/i )
i.e. for all k>0: nCk = nC(k-1) * (n-k+1) / k
Here's a python snippet implementing this approach:
def binomial_coef_seq(n, k):
"""Returns a list of all binomial terms from choose(n,0) up to choose(n,k)"""
b = [1]
for i in range(1,k+1):
b.append(b[-1] * (n-i+1)/i)
return b
If you need all coefficients up to some k > ceiling(n/2), you can use symmetry to reduce the number of operations you need to perform by stopping at the coefficient for ceiling(n/2) and then just backfilling as far as you need.
import numpy as np
def binomial_coef_seq2(n, k):
"""Returns a list of all binomial terms from choose(n,0) up to choose(n,k)"""
k2 = int(np.ceiling(n/2))
use_symmetry = k > k2
if use_symmetry:
k = k2
b = [1]
for i in range(1, k+1):
b.append(b[-1] * (n-i+1)/i)
if use_symmetry:
v = k2 - (n-k)
b2 = b[-v:]
b.extend(b2)
return b
Time Complexity : O(denominator)
Space Complexity : O(1)
public class binomialCoeff {
static double binomialcoeff(int numerator, int denominator)
{
double res = 1;
//invalid numbers
if (denominator>numerator || denominator<0 || numerator<0) {
res = -1;
return res;}
//default values
if(denominator==numerator || denominator==0 || numerator==0)
return res;
// Since C(n, k) = C(n, n-k)
if ( denominator > (numerator - denominator) )
denominator = numerator - denominator;
// Calculate value of [n * (n-1) *---* (n-k+1)] / [k * (k-1) *----* 1]
while (denominator>=1)
{
res *= numerator;
res = res / denominator;
denominator--;
numerator--;
}
return res;
}
/* Driver program to test above function*/
public static void main(String[] args)
{
int numerator = 120;
int denominator = 20;
System.out.println("Value of C("+ numerator + ", " + denominator+ ") "
+ "is" + " "+ binomialcoeff(numerator, denominator));
}
}

How to generate a random convex polygon?

I'm trying to devise a method for generating random 2D convex polygons. It has to have the following properties:
coordinates should be integers;
the polygon should lie inside a square with corners (0, 0) and (C, C), where C is given;
the polygon should have number of vertices close to a given number N.
For example, generate random polygons that have 10 vertices and lie inside square [0..100]x[0..100].
What makes this task hard, is the fact that the coordinates should be integers.
The approach I tried was to generate random set of points in the given square and compute the convex hull of these points. But the resultant convex hull is very little vertices compared to N.
Any ideas?
Here is the fastest algorithm I know that generates each convex polygon with equal probability. The output has exactly N vertices, and the running time is O(N log N), so it can generate even large polygons very quickly.
Generate two lists, X and Y, of N random integers between 0 and C. Make sure there are no duplicates.
Sort X and Y and store their maximum and minimum elements.
Randomly divide the other (not max or min) elements into two groups: X1 and X2, and Y1 and Y2.
Re-insert the minimum and maximum elements at the start and end of these lists (minX at the start of X1 and X2, maxX at the end, etc.).
Find the consecutive differences (X1[i + 1] - X1[i]), reversing the order for the second group (X2[i] - X2[i + 1]). Store these in lists XVec and YVec.
Randomize (shuffle) YVec and treat each pair XVec[i] and YVec[i] as a 2D vector.
Sort these vectors by angle and then lay them end-to-end to form a polygon.
Move the polygon to the original min and max coordinates.
An animation and Java implementation is available here: Generating Random Convex Polygons.
This algorithm is based on a paper by Pavel Valtr: “Probability that n random points are in convex position.” Discrete & Computational Geometry 13.1 (1995): 637-643.
Following #Mangara answer there is JAVA implementation, if someone is interested in Python port of it
import random
from math import atan2
def to_convex_contour(vertices_count,
x_generator=random.random,
y_generator=random.random):
"""
Port of Valtr algorithm by Sander Verdonschot.
Reference:
http://cglab.ca/~sander/misc/ConvexGeneration/ValtrAlgorithm.java
>>> contour = to_convex_contour(20)
>>> len(contour) == 20
True
"""
xs = [x_generator() for _ in range(vertices_count)]
ys = [y_generator() for _ in range(vertices_count)]
xs = sorted(xs)
ys = sorted(ys)
min_x, *xs, max_x = xs
min_y, *ys, max_y = ys
vectors_xs = _to_vectors_coordinates(xs, min_x, max_x)
vectors_ys = _to_vectors_coordinates(ys, min_y, max_y)
random.shuffle(vectors_ys)
def to_vector_angle(vector):
x, y = vector
return atan2(y, x)
vectors = sorted(zip(vectors_xs, vectors_ys),
key=to_vector_angle)
point_x = point_y = 0
min_polygon_x = min_polygon_y = 0
points = []
for vector_x, vector_y in vectors:
points.append((point_x, point_y))
point_x += vector_x
point_y += vector_y
min_polygon_x = min(min_polygon_x, point_x)
min_polygon_y = min(min_polygon_y, point_y)
shift_x, shift_y = min_x - min_polygon_x, min_y - min_polygon_y
return [(point_x + shift_x, point_y + shift_y)
for point_x, point_y in points]
def _to_vectors_coordinates(coordinates, min_coordinate, max_coordinate):
last_min = last_max = min_coordinate
result = []
for coordinate in coordinates:
if _to_random_boolean():
result.append(coordinate - last_min)
last_min = coordinate
else:
result.append(last_max - coordinate)
last_max = coordinate
result.extend((max_coordinate - last_min,
last_max - max_coordinate))
return result
def _to_random_boolean():
return random.getrandbits(1)
This isn't quite complete, but it may give you some ideas.
Bail out if N < 3. Generate a unit circle with N vertices, and rotate it random [0..90] degrees.
Randomly extrude each vertex outward from the origin, and use the sign of the cross product between each pair of adjacent vertices and the origin to determine convexity. This is the step where there are tradeoffs between speed and quality.
After getting your vertices set up, find the vertex with the largest magnitude from the origin. Divide every vertex by that magnitude to normalize the polygon, and then scale it back up by (C/2). Translate to (C/2, C/2) and cast back to integer.
A simple algorithm would be:
Start with random line (a two vertices and two edges polygon)
Take random edge E of the polygon
Make new random point P on this edge
Take a line L perpendicular to E going through point P. By calculating intersection between line T and lines defined by the two edges adjacent to E, calculate the maximum offset of P when the convexity is not broken.
Offset the point P randomly in that range.
If not enough points, repeat from 2.
I've made the ruby port as well thanks to both #Mangara's answer and #Azat's answer:
#!/usr/bin/env ruby
# frozen_string_literal: true
module ValtrAlgorithm
module_function def random_polygon(length)
raise ArgumentError, "length should be > 2" unless length > 2
min_x, *xs, max_x = Array.new(length) { rand }.sort
min_y, *ys, max_y = Array.new(length) { rand }.sort
# Divide the interior points into two chains and
# extract the vector components.
vec_xs = to_random_vectors(xs, min_x, max_x)
vec_ys = to_random_vectors(ys, min_y, max_y).
# Randomly pair up the X- and Y-components
shuffle
# Combine the paired up components into vectors
vecs = vec_xs.zip(vec_ys).
# Sort the vectors by angle, in a counter clockwise fashion. Remove the
# `-` to make it clockwise.
sort_by { |x, y| - Math.atan2(y, x) }
# Lay them end-to-end
point_x = point_y = 0
min_polygon_x = min_polygon_y = 0
points = []
vecs.each do |vec_x, vec_y|
points.append([vec_x, vec_y])
point_x += vec_x
point_y += vec_y
min_polygon_x = [min_polygon_x, point_x].min
min_polygon_y = [min_polygon_y, point_y].min
end
shift_x = min_x - min_polygon_x
shift_y = min_y - min_polygon_y
result = points.map { |point_x, point_y| [point_x + shift_x, point_y + shift_y] }
# Append first point to make it a valid linear ring
result << result.first
end
private def to_random_vectors(coordinates, min, max)
last_min = last_max = min
ary = []
coordinates.each do |coordinate|
if rand > 0.5
ary << coordinate - last_min
last_min = coordinate
else
ary << last_max - coordinate
last_max = coordinate
end
end
ary << max - last_min << last_max - max
end
end
Here's another version of Valtr's algorithm using numpy. :)
import numpy as np
import numpy.typing and npt
def generateConvex(n: int) -> npt.NDArray[np.float64]:
'''
Generate convex shappes according to Pavel Valtr's 1995 alogrithm. Ported from
Sander Verdonschot's Java version, found here:
https://cglab.ca/~sander/misc/ConvexGeneration/ValtrAlgorithm.java
'''
# initialise random coordinates
X_rand, Y_rand = np.sort(np.random.random(n)), np.sort(np.random.random(n))
X_new, Y_new = np.zeros(n), np.zeros(n)
# divide the interior points into two chains
last_true = last_false = 0
for i in range(1, n):
if i != n - 1:
if random.getrandbits(1):
X_new[i] = X_rand[i] - X_rand[last_true]
Y_new[i] = Y_rand[i] - Y_rand[last_true]
last_true = i
else:
X_new[i] = X_rand[last_false] - X_rand[i]
Y_new[i] = Y_rand[last_false] - Y_rand[i]
last_false = i
else:
X_new[0] = X_rand[i] - X_rand[last_true]
Y_new[0] = Y_rand[i] - Y_rand[last_true]
X_new[i] = X_rand[last_false] - X_rand[i]
Y_new[i] = Y_rand[last_false] - Y_rand[i]
# randomly combine x and y and sort by polar angle
np.random.shuffle(Y_new)
vertices = np.stack((X_new, Y_new), axis=-1)
vertices = vertices[np.argsort(np.arctan2(vertices[:, 1], vertices[:, 0]))]
# arrange points end to end to form a polygon
vertices = np.cumsum(vertices, axis=0)
# center around the origin
x_max, y_max = np.max(vertices[:, 0]), np.max(vertices[:, 1])
vertices[:, 0] += ((x_max - np.min(vertices[:, 0])) / 2) - x_max
vertices[:, 1] += ((y_max - np.min(vertices[:, 1])) / 2) - y_max
return vertices
Here is an C++11 realization of the Pavel Valtr algorithm introduced in Mangara's Answer with some tricks similar to lewiswolf's Answer and more randomness realised by separated division process of X and Y coordinate.
#include <algorithm>
#include <iostream>
#include <random>
struct randPoly {
int RND_MAX = 655369;
std::random_device dev;
std::mt19937 rng;
std::uniform_int_distribution<std::mt19937::result_type> random_numer;
std::uniform_int_distribution<std::mt19937::result_type> random_logic;
randPoly() : rng(dev()), random_numer(0, RND_MAX), random_logic(0, 1) {}
virtual ~randPoly() {}
int generate(const int n, const double r0, std::vector<double>& poly_x,
std::vector<double>& poly_y) {
auto gen = [&]() { return random_numer(rng); };
// initialize random samples and sort them
int m = n / 2;
std::vector<int> x(n), y(n), vx(n), vy(n), idx(n);
std::vector<double> a(n);
std::generate(x.begin(), x.end(), gen);
std::generate(y.begin(), y.end(), gen);
std::iota(idx.begin(), idx.end(), 0);
std::sort(x.begin(), x.end());
std::sort(y.begin(), y.end());
// divide samples and get vector component
int x0 = x[0], x1 = x0;
for (int k = 1; k < n - 1; ++k) {
if (random_logic(rng)) {
vx[k - 1] = x[k] - x0;
x0 = x[k];
} else {
vx[k - 1] = x1 - x[k];
x1 = x[k];
}
}
vx[n - 2] = x[n - 1] - x0;
vx[n - 1] = x1 - x[n - 1];
int y0 = y[0], y1 = y0;
for (int k = 1; k < n - 1; ++k) {
if (random_logic(rng)) {
vy[k - 1] = y[k] - y0;
y0 = y[k];
} else {
vy[k - 1] = y1 - y[k];
y1 = y[k];
}
}
vy[n - 2] = y[n - 1] - y0;
vy[n - 1] = y1 - y[n - 1];
// random pair up vector components and sort by angle
std::shuffle(vy.begin(), vy.end(), rng);
for (int k = 0; k < n; ++k) {
a[k] = std::atan2(vy[k], vx[k]);
}
std::sort(idx.begin(), idx.end(),
[&a](int& lhs, int& rhs) { return a[lhs] < a[rhs]; });
// form the polygon by connencting vectors
double x_max = 0, y_max = 0, x_min = 0, y_min = 0;
x[0] = y[0] = 0;
for (int k = 1; k < n; ++k) {
x[k] = x[k - 1] + vx[idx[k - 1]];
y[k] = y[k - 1] + vy[idx[k - 1]];
if (x[k] > x_max) {
x_max = x[k];
} else if (x[k] < x_min) {
x_min = x[k];
}
if (y[k] > y_max) {
y_max = y[k];
} else if (y[k] < y_min) {
y_min = y[k];
}
}
// center and resize the polygon
poly_x.resize(n);
poly_y.resize(n);
double x_offset = -(x_max + x_min) / 2.0;
double y_offset = -(y_max + y_min) / 2.0;
double scale = r0 / std::max(x_max - x_min, y_max - y_min);
for (int k = 0; k < n; ++k) {
poly_x[k] = scale * (x[k] + x_offset);
poly_y[k] = scale * (y[k] + y_offset);
}
return 0;
}
};
int main(int, char**) {
randPoly rp;
std::vector<double> poly_x, poly_y;
rp.generate(8, 2.0, poly_x, poly_y);
for (int k = 0; k < poly_x.size(); ++k) {
std::cout << poly_x[k] << " " << poly_y[k] << std::endl;
}
}
Example shown in Rviz
Your initial approach is correct - calculating the convex hull is the only way you will satisfy randomness, convexity and integerness.
The only way I can think of optimizing your algorithm to get "more points" out is by organizing them around a circle instead of completely randomly. Your points should more likely be near the "edges" of your square than near the center. At the center, the probability should be ~0, since the polygon must be convex.
One simple option would be setting a minimum radius for your points to appear - maybe C/2 or C*0.75. Calculate the center of the C square, and if a point is too close, move it away from the center until a minimum distance is reached.

Resources