49

What is the simplest way to test if a point P is inside a convex hull formed by a set of points X?

I'd like an algorithm that works in a high-dimensional space (say, up to 40 dimensions) that doesn't explicitly compute the convex hull itself. Any ideas?

gsamaras
  • 71,951
  • 46
  • 188
  • 305
dimi
  • 601
  • 1
  • 6
  • 5
  • 2
    Is there a particular reason you want to do this? Computing the convex hull is not very costly (O(n lg n)) and greatly simplifies the problem. – templatetypedef Feb 04 '11 at 19:25
  • 4
    @templatetypedef: Computing the convex hull is not very costly in 2 dimensions. But it gets exponentially more expensive as you increase the number of dimensions. You don't want to do that for a 40 dimensional problem. – btilly Feb 04 '11 at 21:24
  • 1
    Perhaps this question would be more suited to [mathoverflow](http://mathoverflow.com)? – wich Feb 04 '11 at 22:11
  • @btilly- Ah, my mistake - I misread the Wikipedia page. Thanks for pointing that out! – templatetypedef Feb 04 '11 at 22:27
  • I see that you have not yet accepted an answer. The answer of user1071136 of using linear programming is *the* answer (you will not find a more efficient approach). – equaeghe Apr 26 '14 at 13:39
  • Just wanted to say that there are also a few answers for this on a [separate question](https://stackoverflow.com/questions/16750618) (some of solutions there there do not require computing the hull itself). – Berk U. Dec 30 '17 at 06:51

7 Answers7

27

The problem can be solved by finding a feasible point of a Linear Program. If you're interested in the full details, as opposed to just plugging an LP into an existing solver, I'd recommend reading Chapter 11.4 in Boyd and Vandenberghe's excellent book on convex optimization.

Set A = (X[1] X[2] ... X[n]), that is, the first column is v1, the second v2, etc.

Solve the following LP problem,

minimize (over x): 1
s.t.     Ax = P
         x^T * [1] = 1
         x[i] >= 0  \forall i

where

  1. x^T is the transpose of x
  2. [1] is the all-1 vector.

The problem has a solution iff the point is in the convex hull.

user1071136
  • 15,636
  • 4
  • 42
  • 61
  • 1
    Are there any implementations of this around? I am having a very hard time constructing this in code. – Brendan Annable May 05 '14 at 00:22
  • 1
    Which LP solver do you use? http://lpsolve.sourceforge.net/5.5/ is an open source LP solver which is pretty straight-forward to use. EDIT: didn't realize you're looking for the whole shebang; I'm unaware of any such package, unfortunately. – user1071136 May 05 '14 at 06:55
  • I'm actually programming this in the browser so I am currently using http://numericjs.com/documentation.html - I'm just having trouble doing the transformations into the inequalities. There is any example here http://www.joyofdata.de/blog/testing-linear-separability-linear-programming-r-glpk/ but I am not familiar with R either so I'm still having trouble! – Brendan Annable May 05 '14 at 15:21
  • This solution was the absolute bomb once I figured out the right package in R, and right modifications to the function. What was once taking forever, and even failing due to lack of memory, is now instantaneous because of not computing the convex hull. THANK YOU! – Balter Feb 15 '17 at 04:34
  • Any implementations around (especially in Python would be amazing). I am not a programmer and I have a hard time coding this. –  Dec 07 '17 at 09:05
  • @user37143 There is an implementation that uses SciPy on another answer: https://stackoverflow.com/a/43564754/568249 – Berk U. Dec 30 '17 at 06:49
  • Can anybody help if instead of minimizing the above function (minimize (over x): 1), we can solve the constraints directly? – Nile May 27 '18 at 13:31
24

The point lies outside of the convex hull of the other points if and only if the direction of all the vectors from it to those other points are on less than one half of a circle/sphere/hypersphere around it.

Here is a sketch for the situation of two points, a blue one inside the convex hull (green) and the red one outside:

Sketch of the points

For the red one, there exist bisections of the circle, such that the vectors from the point to the points on the convex hull intersect only one half of the circle. For the blue point, it is not possible to find such a bisection.

allo
  • 3,955
  • 8
  • 40
  • 71
Svante
  • 50,694
  • 11
  • 78
  • 122
  • 1
    That seems like a nice `O(m*n^2)` solution! +1 – Nikita Rybak Feb 04 '11 at 23:02
  • On the second though, this property is gonna be tricky to check for the `m` dimensions. Not easier than solving `m` linear equations. – Nikita Rybak Feb 04 '11 at 23:12
  • this is actually a very good algorithm. To check for "on less than 1/2 of a hypersphere" you check the dot product between the two directions and see if any 2 are negative (i.e. in opposite direction). or did I miss anything? – Evan Pu Sep 25 '15 at 21:55
  • 2
    It is not sufficient to do that, Evans. But you could do this: sum all vectors from the point to the other points. Then, use that summed vector as axis to project again all the vectors from the point in question. If all projections lay on the same side of the axis, the point is outside the hull. I think this is O(m*n) – yombo Nov 10 '15 at 13:19
  • How do you choose the radius of the hypersphere? – allo Nov 12 '18 at 09:27
  • 1
    @allo: it doesn't matter, only the relative angles matter. – Svante Nov 12 '18 at 10:08
  • Ah, I think you're meaning with "one half of" a half sphere. I was thinking about half the radius. Now I understand and it is fully clear why it works. – allo Nov 13 '18 at 12:42
  • 1
    I added a sketch of the situation, it would be nice if you can check it for correctness. – allo Nov 13 '18 at 12:53
11

You don't have to compute convex hull itself, as it seems quite troublesome in multidimensional spaces. There's a well-known property of convex hulls:

Any vector (point) v inside convex hull of points [v1, v2, .., vn] can be presented as sum(ki*vi), where 0 <= ki <= 1 and sum(ki) = 1. Correspondingly, no point outside of convex hull will have such representation.

In m-dimensional space, this will give us the set of m linear equations with n unknowns.

edit
I'm not sure about complexity of this new problem in general case, but for m = 2 it seems linear. Perhaps, somebody with more experience in this area will correct me.

Nikita Rybak
  • 67,365
  • 22
  • 157
  • 181
  • 4
    Actually in m-dimensional space you only need m+1 points because of Carathéodory's theorem: http://en.wikipedia.org/wiki/Carathéodory's_theorem_(convex_hull) The difficulty is findind which m+1 points work. – lhf Feb 04 '11 at 21:11
  • 1
    @lhf That's a good note, although it doesn't affect correctness of the answer (and it's not clear how to apply it to solving those equations). – Nikita Rybak Feb 04 '11 at 21:28
  • 3
    The problem is that linear algebra will easily give you an n-m dimensional space of solutions to your equation, but doesn't give you any easy way to also satisfy the inequalities. Therefore the theorem you cite is a good way to show that a point is within the convex hull of m+1 points, but for a larger set of points you need to find the right set of m+1 points to make use of said theorem. There are n choose m+1 such sets to try. In 40 dimensions that will be a problem. – btilly Feb 04 '11 at 22:06
  • 1
    This in fact does not solve the problem. As @btilly mentioned, the solution space to the system of linear equations contains a lot of points whose `ki` is negative or greater than 1. – user1071136 Jul 31 '12 at 00:16
3

I had the same problem with 16 dimensions. Since even qhull didn't work properly as too much faces had to be generated, I developed my own approach by testing, whether a separating hyperplane can be found between the new point and the reference data (I call this "HyperHull" ;) ).

The problem of finding a separating hyperplane can be transformed to a convex quadratic programming problem (see: SVM). I did this in python using cvxopt with less then 170 lines of code (including I/O). The algorithm works without modification in any dimension even if there exists the problem, that as higher the dimension as higher the number of points on the hull (see: On the convex hull of random points in a polytope). Since the hull isn't explicitely constructed but only checked, whether a point is inside or not, the algorithm has very big advantages in higher dimensions compared to e.g. quick hull.

This algorithm can 'naturally' be parallelized and speed up should be equal to number of processors.

Community
  • 1
  • 1
Michael Hecht
  • 2,093
  • 6
  • 25
  • 37
  • 1
    If you could put up your implementation or part of it on github it would be highly appreciated, by many people I believe ( http://www.mathworks.com/matlabcentral/answers/77363-very-high-dimensional-convex-hulls ). Perhaps for you it seems very simple. – j13r Mar 11 '14 at 22:05
3

Though the original post was three years ago, perhaps this answer will still be of help. The Gilbert-Johnson-Keerthi (GJK) algorithm finds the shortest distance between two convex polytopes, each of which is defined as the convex hull of a set of generators---notably, the convex hull itself does not have to be calculated. In a special case, which is the case being asked about, one of the polytopes is just a point. Why not try using the GJK algorithm to calculate the distance between P and the convex hull of the points X? If that distance is 0, then P is inside X (or at least on its boundary). A GJK implementation in Octave/Matlab, called ClosestPointInConvexPolytopeGJK.m, along with supporting code, is available at http://www.99main.com/~centore/MunsellAndKubelkaMunkToolbox/MunsellAndKubelkaMunkToolbox.html . A simple description of the GJK algorithm is available in Sect. 2 of a paper, at http://www.99main.com/~centore/ColourSciencePapers/GJKinConstrainedLeastSquares.pdf . I've used the GJK algorithm for some very small sets X in 31-dimensional space, and had good results. How the performance of GJK compares to the linear programming methods that others are recommending is uncertain (although any comparisons would be interesting). The GJK method does avoid computing the convex hull, or expressing the hull in terms of linear inequalities, both of which might be time-consuming. Hope this answer helps.

2

Are you willing to accept a heuristic answer that should usually work but is not guaranteed to? If you are then you could try this random idea.

Let f(x) be the cube of the distance to P times the number of things in X, minus the sum of the cubes of the distance to all of the points in X. Start somewhere random, and use a hill climbing algorithm to maximize f(x) for x in a sphere that is very far away from P. Excepting degenerate cases, if P is not in the convex hull this should have a very good probability of finding the normal to a hyperplane which P is on one side of, and everything in X is on the other side of.

btilly
  • 43,296
  • 3
  • 59
  • 88
  • Could you elaborate a bit more on this approach. In particular, x is not present in your objective function, so I don't understand what you mean. – tommsch May 31 '18 at 06:21
  • @tommsch The idea is to find a point x' on a hypersphere that is far from, P and close to things in X. If P is outside of the convex hull, then the dot product the vector from P to x' with the vector from P to every x_i in X should be positive. – btilly May 31 '18 at 15:47
1

A write-up to test if a point is in a hull space, using scipy.optimize.minimize.

Based on user1071136's answer.

It does go a lot faster if you compute the convex hull, so I added a couple of lines for people who want to do that. I switched from graham scan (2D only) to the scipy qhull algorithm.

scipy.optimize.minimize documentation:
https://docs.scipy.org/doc/scipy/reference/optimize.nonlin.html

import numpy as np
import scipy.optimize
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull


def hull_test(P, X, use_hull=True, verbose=True, hull_tolerance=1e-5, return_hull=True):
    if use_hull:
        hull = ConvexHull(X)
        X = X[hull.vertices]

    n_points = len(X)

    def F(x, X, P):
        return np.linalg.norm( np.dot( x.T, X ) - P )

    bnds = [[0, None]]*n_points # coefficients for each point must be > 0
    cons = ( {'type': 'eq', 'fun': lambda x: np.sum(x)-1} ) # Sum of coefficients must equal 1
    x0 = np.ones((n_points,1))/n_points # starting coefficients

    result = scipy.optimize.minimize(F, x0, args=(X, P), bounds=bnds, constraints=cons)

    if result.fun < hull_tolerance:
        hull_result = True
    else:
        hull_result = False

    if verbose:
        print( '# boundary points:', n_points)
        print( 'x.T * X - P:', F(result.x,X,P) )
        if hull_result: 
            print( 'Point P is in the hull space of X')
        else: 
            print( 'Point P is NOT in the hull space of X')

    if return_hull:
        return hull_result, X
    else:
        return hull_result

Test on some sample data:

n_dim = 3
n_points = 20
np.random.seed(0)

P = np.random.random(size=(1,n_dim))
X = np.random.random(size=(n_points,n_dim))

_, X_hull = hull_test(P, X, use_hull=True, hull_tolerance=1e-5, return_hull=True)

Output:

# boundary points: 14
x.T * X - P: 2.13984259782e-06
Point P is in the hull space of X

Visualize it:

rows = max(1,n_dim-1)
cols = rows
plt.figure(figsize=(rows*3,cols*3))
for row in range(rows):
    for col in range(row, cols):
        col += 1
        plt.subplot(cols,rows,row*rows+col)
        plt.scatter(P[:,row],P[:,col],label='P',s=300)
        plt.scatter(X[:,row],X[:,col],label='X',alpha=0.5)
        plt.scatter(X_hull[:,row],X_hull[:,col],label='X_hull')
        plt.xlabel('x{}'.format(row))
        plt.ylabel('x{}'.format(col))
plt.tight_layout()

Visualization of hull test

cnash
  • 564
  • 1
  • 7
  • 14