Dihedral angles of a $k$-simplex Given a $k$-simplex $(p_0, ..., p_k)$, where $p_i$ are $n$-dimensional points.
Define the dihedral angle $\theta_j$ as the angle between the (hyperplanes of the) two $(k-1)$-facets incident to the $(k-2)$-facet $e_j$, where $j \in 0, ..., \frac{n(n + 1)}{2} - 1$.
For a tetrahedron ($k = 3$), it means that the dihedral angle $\theta_j$ is the angle between the (planes of the) two faces incident to edge $e_j$, where $j \in 0, ..., 5$.
Now, what is the computationally simplest way to compute any of $\theta_j$?
 A: First, since there are only $k+1$ points, you should shrink your points from $n$ dimensions down to $k$ dimensions without changing the dihedral angles. You can do this by (1) 
translate all points $p_i$ to $p_i - p_0$ so that $p_0$ is now the origin, (2) apply Gram-Schmidt to compute an orthonormal basis for $\{p_1, p_2, \dots, p_k\}$, then (3) express $p_1, \dots, p_k$ with respect to the orthonormal basis.
Ok, now you have $(k+1)$ points in $\mathbb{R}^k$. Let $P_i$ be the plane containing all points except $p_i$. If you want to compute the dihedral angle between $P_i$ and $P_j$, you can compute a normal vector $v_i$ and $v_j$ for each plane, then compute the angle between the normal vectors.
The normal vector $v_i$ can be computed by finding the null space of the matrix $M_i$ with rows as follows: pick any one of the points defining the plane $p_t$, and use rows $p_0 - p_t, p_1 -p_t, \dots, p_{i-1} - p_t, p_{i+1} - p_t, \dots, p_k - p_t$.
Given two vectors $v_i, v_j$, the angle between them is $\cos^{-1}(\frac{\langle v_i, v_j\rangle}{\|v_i\|\cdot\|v_j\|})$.
A: The following Python function calculates all dihedral angles of a k-simplex inspired by the ideas in the great book Matrices and Graphs in Geometry. The mathematical explanation can be found in the book, the following are the steps translated into code:


*

*Subtract one of the points from all other points. This creates a square matrix $M$ with edge vectors in the rows.

*Calculate the outward pointing normals by inverting the matrix. $M^{-1} M = I$ tells us, that the column vectors in the inverse matrix $Minv$ are orthogonal to all edges except for one and thus represent normals for all but one facet. They are inward facing, so we need to invert them.

*The remaining normal $nn$ can be calculated as the negative sum of all other normals. All $normals$ are stacked and normalized to unit length.

*The gram matrix calculates dot products of all facet normal combinations. (this is just convenient, you could also do it manually with half the calculations)

*Finally to retrieve the dihedral angles, we need to subtract $\cos^{-1}(n_i^T n_j)$ from $\pi$.




import numpy as np

def dihedral_angles(points):
    # mxn numpy array of m points in n dimensions
    assert points.shape[0] == points.shape[1] + 1

    #subtract origin
    points[1:] -= points[0]
    M = points[1:]

    #calc outer normals
    Minv = -np.linalg.inv(M)
    nn = -np.sum(Minv, axis=1)   
    normals = np.hstack((Minv, nn[:,None]))
    normals /= np.linalg.norm(normals, axis = 0)

    #calc gram matrix
    ng = np.tril(normals.T.dot(normals), -1)
    ang_rad = np.pi - np.arccos(ng)
    ang_deg = ang_rad * 180 / np.pi

    #extract angles to array
    angles = np.tril(ang_deg, -1)

    return angles[angles!=0]


# 4-simplex
s4d = np.random.rand(5, 4)
# Tetrahedron
s3d = np.array([[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, 0, 1]])
# Triangle
s2d = np.array([[0, 1], [2, 1], [1, 2]])
dihedral_angles(s4d)

