项目作者: stla

项目描述 :
Convex hull in arbitrary dimension
高级语言: C
项目地址: git://github.com/stla/cxhull.git
创建时间: 2018-07-04T15:32:33Z
项目社区:https://github.com/stla/cxhull

开源协议:

下载


cxhull

2022-10-25

R-CMD-check

The purpose of the cxhull package is to compute the convex hull of a
set of points in arbitrary dimension. Its main function is named
cxhull.

The output of the cxhull function is a list with the following fields.

  • vertices: The vertices of the convex hull. Each vertex is given
    with its neighbour vertices, its neighbour ridges and its neighbour
    facets.

  • edges: The edges of the convex hull, given as pairs of vertices
    identifiers.

  • ridges: The ridges of the convex hull, i.e. the elements of the
    convex hull of dimension dim-2. Thus the ridges are just the
    vertices in dimension 2, and they are the edges in dimension 3.

  • facets: The facets of the convex hull, i.e. the elements of the
    convex hull of dimension dim-1. Thus the facets are the edges in
    dimension 2, and they are the faces of the convex polyhedron in
    dimension 3.

  • volume: The volume of the convex hull (area in dimension 2, volume
    in dimension 3, hypervolume in higher dimension).

Let’s look at an example. The points we take are the vertices of a cube
and the center of this cube (in the first row):

  1. library(cxhull)
  2. points <- rbind(
  3. c(0.5, 0.5, 0.5),
  4. c(0, 0, 0),
  5. c(0, 0, 1),
  6. c(0, 1, 0),
  7. c(0, 1, 1),
  8. c(1, 0, 0),
  9. c(1, 0, 1),
  10. c(1, 1, 0),
  11. c(1, 1, 1)
  12. )
  13. hull <- cxhull(points)

Obviously, the convex hull of these points is the cube. We can quickly
see that the convex hull has 8 vertices, 12 edges, 12 ridges, 6 facets,
and its volume is 1:

  1. str(hull, max = 1)
  2. ## List of 5
  3. ## $ vertices:List of 8
  4. ## $ edges : int [1:12, 1:2] 2 2 2 3 3 4 4 5 6 6 ...
  5. ## $ ridges :List of 12
  6. ## $ facets :List of 6
  7. ## $ volume : num 1
  8. ## - attr(*, "3d")= logi TRUE

Each vertex, each ridge, and each facet has an identifier. A vertex
identifier is the index of the row corresponding to this vertex in the
set of points passed to the cxhull function. It is given in the field
id of the vertex:

  1. hull[["vertices"]][[1]]
  2. ## $id
  3. ## [1] 2
  4. ##
  5. ## $point
  6. ## [1] 0 0 0
  7. ##
  8. ## $neighvertices
  9. ## [1] 3 4 6
  10. ##
  11. ## $neighridges
  12. ## [1] 1 2 5
  13. ##
  14. ## $neighfacets
  15. ## [1] 1 2 4

Also, the list of vertices is named with the identifiers:

  1. names(hull[["vertices"]])
  2. ## [1] "2" "3" "4" "5" "6" "7" "8" "9"

Edges are given as a matrix, each row representing an edge given as a
pair of vertices identifiers:

  1. hull[["edges"]]
  2. ## [,1] [,2]
  3. ## [1,] 2 3
  4. ## [2,] 2 4
  5. ## [3,] 2 6
  6. ## [4,] 3 5
  7. ## [5,] 3 7
  8. ## [6,] 4 5
  9. ## [7,] 4 8
  10. ## [8,] 5 9
  11. ## [9,] 6 7
  12. ## [10,] 6 8
  13. ## [11,] 7 9
  14. ## [12,] 8 9

The ridges are given as a list:

  1. hull[["ridges"]][[1]]
  2. ## $id
  3. ## [1] 1
  4. ##
  5. ## $ridgeOf
  6. ## [1] 1 4
  7. ##
  8. ## $vertices
  9. ## [1] 2 4

The vertices field provides the vertices identifiers of the ridge. A
ridge is between two facets; the identifiers of these facets are given
in the field ridgeOf.

Facets are given as a list:

  1. hull[["facets"]][[1]]
  2. ## $vertices
  3. ## [1] 8 4 6 2
  4. ##
  5. ## $edges
  6. ## [,1] [,2]
  7. ## [1,] 2 4
  8. ## [2,] 2 6
  9. ## [3,] 4 8
  10. ## [4,] 6 8
  11. ##
  12. ## $ridges
  13. ## [1] 1 2 3 4
  14. ##
  15. ## $neighbors
  16. ## [1] 2 3 4 5
  17. ##
  18. ## $volume
  19. ## [1] 1
  20. ##
  21. ## $center
  22. ## [1] 0.5 0.5 0.0
  23. ##
  24. ## $normal
  25. ## [1] 0 0 -1
  26. ##
  27. ## $offset
  28. ## [1] 0
  29. ##
  30. ## $family
  31. ## [1] NA
  32. ##
  33. ## $orientation
  34. ## [1] 1

There is no id field for the facets: the integer i is the identifier
of the i-th facet of the list.

The orientation field has two possible values, 1 or -1, it
indicates the orientation of the facet. See the plotting example below.

Here, the family field is NA for every facet:

  1. sapply(hull[["facets"]], `[[`, "family")
  2. ## [1] NA NA NA NA NA NA

This field has a possibly non-missing value only when one requires the
triangulation of the convex hull:

  1. thull <- cxhull(points, triangulate = TRUE)
  2. sapply(thull[["facets"]], `[[`, "family")
  3. ## [1] 0 0 2 2 4 4 6 6 8 8 10 10

The hull is triangulated into 12 triangles: each face of the cube is
triangulated into two triangles. Therefore one gets six different
families, each one consisting of two triangles: two triangles belong to
the same family mean that they are parts of the same facet of the
non-triangulated hull.

Ordering the vertices

Observe the vertices of the first face of the cube:

  1. hull[["facets"]][[1]][["vertices"]]
  2. ## [1] 8 4 6 2

They are given as 8-4-6-2. They are not ordered, in the sense that
4-6 and 2-8 are not edges of this face:

  1. ( face_edges <- hull[["facets"]][[1]][["edges"]] )
  2. ## [,1] [,2]
  3. ## [1,] 2 4
  4. ## [2,] 2 6
  5. ## [3,] 4 8
  6. ## [4,] 6 8

One can order the vertices as follows:

  1. polygonize <- function(edges){
  2. nedges <- nrow(edges)
  3. vs <- edges[1, ]
  4. v <- vs[2]
  5. edges <- edges[-1, ]
  6. for(. in 1:(nedges-2)){
  7. j <- which(apply(edges, 1, function(e) v %in% e))
  8. v <- edges[j, ][which(edges[j, ] != v)]
  9. vs <- c(vs, v)
  10. edges <- edges[-j, ]
  11. }
  12. vs
  13. }
  14. polygonize(face_edges)
  15. ## [1] 2 4 8 6

Instead of using this function, use the hullMesh function. It returns
the vertices of the convex hull and its faces with ordered indices.

The cxhullEdges function

The cxhull function returns a lot of information about the convex
hull. If you only want to find the edges of the convex hull, use the
cxhullEdges function instead, for a speed gain and less memory
consumption. For example, the cxhull function fails on my laptop for
the E8 root
polytope
,
while the cxhullEdges function works (but it takes a while).

Plotting a 3-dimensional hull

The package provides the function plotConvexHull3d to plot a
triangulated 3-dimensional hull with rgl. Let’s take an
icosidodecahedron as example:

  1. library(cxhull)
  2. library(rgl)
  3. # icosidodecahedron
  4. phi <- (1+sqrt(5))/2
  5. vs1 <- rbind(
  6. c(0, 0, 2*phi),
  7. c(0, 2*phi, 0),
  8. c(2*phi, 0, 0)
  9. )
  10. vs1 <- rbind(vs1, -vs1)
  11. vs2 <- rbind(
  12. c( 1, phi, phi^2),
  13. c( 1, phi, -phi^2),
  14. c( 1, -phi, phi^2),
  15. c(-1, phi, phi^2),
  16. c( 1, -phi, -phi^2),
  17. c(-1, phi, -phi^2),
  18. c(-1, -phi, phi^2),
  19. c(-1, -phi, -phi^2)
  20. )
  21. vs2 <- rbind(vs2, vs2[, c(2, 3, 1)], vs2[, c(3, 1, 2)])
  22. points <- rbind(vs1, vs2)
  23. # computes the triangulated convex hull:
  24. hull <- cxhull(points, triangulate = TRUE)
  1. # plot:
  2. open3d(windowRect = c(50, 50, 562, 562))
  3. view3d(10, 80, zoom = 0.7)
  4. plotConvexHull3d(
  5. hull, facesColor = "orangered", edgesColor = "yellow",
  6. tubesRadius = 0.06, spheresRadius = 0.08
  7. )

Facets orientation

The plotConvexHull3d function calls the TrianglesXYZ function, which
takes care of the orientation of the facets. Indeed, with the code
below, we see the whole convex hull while we hide the back side of the
triangles:

  1. triangles <- TrianglesXYZ(hull)
  2. open3d(windowRect = c(50, 50, 562, 562))
  3. view3d(10, 80, zoom = 0.7)
  4. triangles3d(triangles, color = "green", back = "culled")

The orientation field of a facet indicates its orientation (1 or
-1).

Plotting with multiple colors

There are three possiblities for the facesColor argument of the
plotConvexHull3d function. We have already seen the first one: a
single color. The second possibiity is to assign a color to each
triangle of the hull. There are 56 triangles:

  1. length(hull[["facets"]])
  2. ## [1] 56

So we specify 56 colors:

  1. library(randomcoloR)
  2. colors <- distinctColorPalette(56)
  3. open3d(windowRect = c(50, 50, 562, 562))
  4. view3d(10, 80, zoom = 0.7)
  5. plotConvexHull3d(
  6. hull, facesColor = colors, edgesColor = "yellow",
  7. tubesRadius = 0.06, spheresRadius = 0.08
  8. )

The third possibility is to assign a color to each face of the convex
hull. There are 32 faces:

  1. summary <- hullSummary(hull)
  2. attr(summary, "facets")
  3. ## [1] "20 triangular facets, 12 other facets"
  1. library(randomcoloR)
  2. colors <- distinctColorPalette(32)
  3. open3d(windowRect = c(50, 50, 562, 562))
  4. view3d(10, 80, zoom = 0.7)
  5. plotConvexHull3d(
  6. hull, facesColor = colors, edgesColor = "yellow",
  7. tubesRadius = 0.06, spheresRadius = 0.08
  8. )

Finally, instead of using the facesColor argument, you can use the
palette argument, which allows to decorate the faces with a color
gradient.

  1. open3d(windowRect = c(50, 50, 562, 562))
  2. view3d(10, 80, zoom = 0.7)
  3. plotConvexHull3d(
  4. hull, palette = hcl.colors(256, "BuPu"), bias = 0.25,
  5. edgesColor = "yellow", tubesRadius = 0.06, spheresRadius = 0.08
  6. )

A four-dimensional example

Now, to illustrate the cxhull package, we deal with a four-dimensional
polytope: the truncated tesseract.

It is a convex polytope whose vertices are given by all permutations of
(±1, ±(√2+1), ±(√2+1), ±(√2+1)).

Let’s enter these 64 vertices in a matrix points:

  1. sqr2p1 <- sqrt(2) + 1
  2. points <- rbind(
  3. c(-1, -sqr2p1, -sqr2p1, -sqr2p1),
  4. c(-1, -sqr2p1, -sqr2p1, sqr2p1),
  5. c(-1, -sqr2p1, sqr2p1, -sqr2p1),
  6. c(-1, -sqr2p1, sqr2p1, sqr2p1),
  7. c(-1, sqr2p1, -sqr2p1, -sqr2p1),
  8. c(-1, sqr2p1, -sqr2p1, sqr2p1),
  9. c(-1, sqr2p1, sqr2p1, -sqr2p1),
  10. c(-1, sqr2p1, sqr2p1, sqr2p1),
  11. c(1, -sqr2p1, -sqr2p1, -sqr2p1),
  12. c(1, -sqr2p1, -sqr2p1, sqr2p1),
  13. c(1, -sqr2p1, sqr2p1, -sqr2p1),
  14. c(1, -sqr2p1, sqr2p1, sqr2p1),
  15. c(1, sqr2p1, -sqr2p1, -sqr2p1),
  16. c(1, sqr2p1, -sqr2p1, sqr2p1),
  17. c(1, sqr2p1, sqr2p1, -sqr2p1),
  18. c(1, sqr2p1, sqr2p1, sqr2p1),
  19. c(-sqr2p1, -1, -sqr2p1, -sqr2p1),
  20. c(-sqr2p1, -1, -sqr2p1, sqr2p1),
  21. c(-sqr2p1, -1, sqr2p1, -sqr2p1),
  22. c(-sqr2p1, -1, sqr2p1, sqr2p1),
  23. c(-sqr2p1, 1, -sqr2p1, -sqr2p1),
  24. c(-sqr2p1, 1, -sqr2p1, sqr2p1),
  25. c(-sqr2p1, 1, sqr2p1, -sqr2p1),
  26. c(-sqr2p1, 1, sqr2p1, sqr2p1),
  27. c(sqr2p1, -1, -sqr2p1, -sqr2p1),
  28. c(sqr2p1, -1, -sqr2p1, sqr2p1),
  29. c(sqr2p1, -1, sqr2p1, -sqr2p1),
  30. c(sqr2p1, -1, sqr2p1, sqr2p1),
  31. c(sqr2p1, 1, -sqr2p1, -sqr2p1),
  32. c(sqr2p1, 1, -sqr2p1, sqr2p1),
  33. c(sqr2p1, 1, sqr2p1, -sqr2p1),
  34. c(sqr2p1, 1, sqr2p1, sqr2p1),
  35. c(-sqr2p1, -sqr2p1, -1, -sqr2p1),
  36. c(-sqr2p1, -sqr2p1, -1, sqr2p1),
  37. c(-sqr2p1, -sqr2p1, 1, -sqr2p1),
  38. c(-sqr2p1, -sqr2p1, 1, sqr2p1),
  39. c(-sqr2p1, sqr2p1, -1, -sqr2p1),
  40. c(-sqr2p1, sqr2p1, -1, sqr2p1),
  41. c(-sqr2p1, sqr2p1, 1, -sqr2p1),
  42. c(-sqr2p1, sqr2p1, 1, sqr2p1),
  43. c(sqr2p1, -sqr2p1, -1, -sqr2p1),
  44. c(sqr2p1, -sqr2p1, -1, sqr2p1),
  45. c(sqr2p1, -sqr2p1, 1, -sqr2p1),
  46. c(sqr2p1, -sqr2p1, 1, sqr2p1),
  47. c(sqr2p1, sqr2p1, -1, -sqr2p1),
  48. c(sqr2p1, sqr2p1, -1, sqr2p1),
  49. c(sqr2p1, sqr2p1, 1, -sqr2p1),
  50. c(sqr2p1, sqr2p1, 1, sqr2p1),
  51. c(-sqr2p1, -sqr2p1, -sqr2p1, -1),
  52. c(-sqr2p1, -sqr2p1, -sqr2p1, 1),
  53. c(-sqr2p1, -sqr2p1, sqr2p1, -1),
  54. c(-sqr2p1, -sqr2p1, sqr2p1, 1),
  55. c(-sqr2p1, sqr2p1, -sqr2p1, -1),
  56. c(-sqr2p1, sqr2p1, -sqr2p1, 1),
  57. c(-sqr2p1, sqr2p1, sqr2p1, -1),
  58. c(-sqr2p1, sqr2p1, sqr2p1, 1),
  59. c(sqr2p1, -sqr2p1, -sqr2p1, -1),
  60. c(sqr2p1, -sqr2p1, -sqr2p1, 1),
  61. c(sqr2p1, -sqr2p1, sqr2p1, -1),
  62. c(sqr2p1, -sqr2p1, sqr2p1, 1),
  63. c(sqr2p1, sqr2p1, -sqr2p1, -1),
  64. c(sqr2p1, sqr2p1, -sqr2p1, 1),
  65. c(sqr2p1, sqr2p1, sqr2p1, -1),
  66. c(sqr2p1, sqr2p1, sqr2p1, 1)
  67. )

As said before, the truncated tesseract is convex, therefore its convex
hull is itself. Let’s run the cxhull function on its vertices:

  1. library(cxhull)
  2. hull <- cxhull(points)
  3. str(hull, max = 1)
  4. ## List of 5
  5. ## $ vertices:List of 64
  6. ## $ edges : int [1:128, 1:2] 1 1 1 1 2 2 2 2 3 3 ...
  7. ## $ ridges :List of 88
  8. ## $ facets :List of 24
  9. ## $ volume : num 541

We can observe that cxhull has not changed the order of the points:

  1. all(names(hull[["vertices"]]) == 1:64)
  2. ## [1] TRUE

Let’s look at the cells of the truncated tesseract:

  1. table(sapply(hull[["facets"]], function(cell) length(cell[["ridges"]])))
  2. ##
  3. ## 4 14
  4. ## 16 8

We see that 16 cells are made of 4 ridges; these cells are tetrahedra.
We will draw them later, after projecting the truncated tesseract in the
3D-space.

For now, let’s draw the projected vertices and the edges.

The vertices in the 4D-space lie on the centered sphere with radius
√(1+3(√2+1)2).

Therefore, a stereographic projection is appropriate to project the
truncated tesseract in the 3D-space.

  1. sproj <- function(p, r){
  2. c(p[1], p[2], p[3])/(r - p[4])
  3. }
  4. ppoints <- t(apply(points, 1,
  5. function(point) sproj(point, sqrt(1+3*sqr2p1^2))))

Now we are ready to draw the projected vertices and the edges.

  1. edges <- hull[["edges"]]
  2. library(rgl)
  3. open3d(windowRect = c(100, 100, 600, 600))
  4. view3d(45, 45)
  5. spheres3d(ppoints, radius = 0.07, color = "orange")
  6. for(i in 1:nrow(edges)){
  7. shade3d(cylinder3d(rbind(ppoints[edges[i, 1], ], ppoints[edges[i, 2], ]),
  8. radius = 0.05, sides = 30), col = "gold")
  9. }

Pretty nice.

Now let’s show the 16 tetrahedra. Their faces correspond to triangular
ridges. So we get the 64 triangles as follows:

  1. ridgeSizes <-
  2. sapply(hull[["ridges"]], function(ridge) length(ridge[["vertices"]]))
  3. triangles <- t(sapply(hull[["ridges"]][which(ridgeSizes == 3)],
  4. function(ridge) ridge[["vertices"]]))
  5. head(triangles)
  6. ## [,1] [,2] [,3]
  7. ## [1,] 1 17 33
  8. ## [2,] 1 17 49
  9. ## [3,] 1 33 49
  10. ## [4,] 17 33 49
  11. ## [5,] 12 44 60
  12. ## [6,] 12 28 44

We finally add the triangles:

  1. for(i in 1:nrow(triangles)){
  2. triangles3d(rbind(
  3. ppoints[triangles[i, 1], ],
  4. ppoints[triangles[i, 2], ],
  5. ppoints[triangles[i, 3], ]),
  6. color = "red", alpha = 0.4)
  7. }

We could also use different colors for the tetrahedra:

  1. open3d(windowRect = c(100, 100, 600, 600))
  2. view3d(45, 45)
  3. spheres3d(ppoints, radius= 0.07, color = "orange")
  4. for(i in 1:nrow(edges)){
  5. shade3d(cylinder3d(rbind(ppoints[edges[i, 1], ], ppoints[edges[i, 2], ]),
  6. radius = 0.05, sides = 30), col = "gold")
  7. }
  8. cellSizes <- sapply(hull[["facets"]], function(cell) length(cell[["ridges"]]))
  9. tetrahedra <- hull[["facets"]][which(cellSizes == 4)]
  10. colors <- rainbow(16)
  11. for(i in seq_along(tetrahedra)){
  12. triangles <- tetrahedra[[i]][["ridges"]]
  13. for(j in 1:4){
  14. triangle <- hull[["ridges"]][[triangles[j]]][["vertices"]]
  15. triangles3d(rbind(
  16. ppoints[triangle[1], ],
  17. ppoints[triangle[2], ],
  18. ppoints[triangle[3], ]),
  19. color = colors[i], alpha = 0.4)
  20. }
  21. }