Idea
I will adjust your notation a bit to distinguish between vectors and scalars. Use $\vec{a}$ as the fixed vector of unit length in $\mathbb{R}^{n+1}$ and $0 \leq b < 1$ as the constant.
The intersection is an $n-1$ dimensional sphere of radius $\sqrt{1 - b^2}$.
So you could:
- Use Gram-Schmidt to find an orthonormal basis $\vec{u}_1, \vec{u}_2, \dots \vec{u}_n$ of the hyperplane $\langle \vec{a}, \vec{x} \rangle = 0$.
- Draw random samples $\vec{c}$ from an $n$ dimensional multivariate normal distribution.
- Divide those by their length, and then multiply by $\sqrt{1 - b^2}$. Let $$\hat{c} = \frac{\sqrt{1-b^2}}{|\vec{c}|}\vec{c}$$ These are uniformly distributed on the sphere of radius $\sqrt{1-b^2}$ in $\mathbb{R}^n$.
- Use these as the coefficients for the $\vec{u}_i$: the vector $\sum_i^n \hat{c}_i \vec{u}_i$ is now a random point on the unit sphere in the hyperplane $\langle \vec{a}, \vec{x} \rangle = 0$.
- Translate by $b \vec{a}$ to be on the hyperplane $\langle \vec{a}, \vec{x} \rangle = b$.
Not sure if this is the most efficient algorithm, but it should get the job done. I will hopefully update with some working python code later, using the numpy library to do the random draws.
Python code with explanation
Note that this may not be the most efficient code, but it should get the job done.
n = 5
num_samples = 500
b = 0.7
Colab link to uncommented code. What follows is a line by line explanation of the code:
I will now generate a vector $\vec{a} \in \mathbb{R}^n$ of norm 1:
a = np.random.multivariate_normal(np.zeros(n), np.eye(n), size=1, check_valid='warn', tol=1e-8).reshape(n)
a = a/np.linalg.norm(a)
To project $\vec{x}$ onto the hyperplane $\vec{a}^\perp$, we map $\vec{x} \mapsto \vec{x} - (\vec{x} \cdot \vec{a})\vec{a}$. The matrix of this map is $I - \vec{a} \vec{a}^\top$. This has eigenvalues of $0$ and $1$. $\vec{a}$ is the eigenvector with eigenvalue $0$ and every vector in $\vec{a}^\perp$ is an eigenvector with eigenvalue $1$. The numpy function np.linalg.eigh takes a matrix and returns a tuple. The index 1 element of this tuple is a matrix whose columns are orthonormal eigenvectors, corresponding to increasing eigenvalues. So we want the last $n-1$ of these columns.
U = np.linalg.eigh(np.eye(n) - np.dot(a.reshape(-1,1), a.reshape(1,-1)))[1][:,1:]
I now generate num_sample samples uniformly at random from the unit sphere in $\mathbb{R}^{n-1}$:
c = np.random.multivariate_normal(np.zeros(n-1), np.eye(n-1), size=num_samples, check_valid='warn', tol=1e-8)
c = c/np.linalg.norm(c, axis = 1, keepdims = True)
I use these as the coordinates with the basis $U$ of the subspace $\vec{a}^\perp$:
s = np.dot(U,np.transpose(c))
Finally I scale and translate as indicated in my sketch above:
s = s*np.sqrt(1 - b**2)
s = s + np.repeat(b*a.reshape(-1,1), num_samples, axis = 1)
You can check that these are all on the unit sphere:
np.linalg.norm(s, axis = 0, keepdims = True)
The above should be an array of ones.
You can also check that the dot product of all of these with $\vec{a}$ are equal to $b$:
np.dot(a,s)
The above should be an array whose entries are all $b$.
Visualization
Setting $n = 3$ in the code above gives us the case of intersection a plane with the unit sphere in $3D$ space, which is something we can visualize:
