Solving tips
- Cosine similarity is the dot product of unit-normalized vectors, so normalize the rows once and a single X_norm @ X_norm.T gives every pair.
- A zero vector has no direction and no unit form; guard the division so you get 0 similarity instead of a nan from dividing by zero.
- Keep the norm as a column vector with keepdims=True so it broadcasts cleanly across each row.
Compute the full matrix of cosine similarities between the rows of a data matrix. Given X of shape (n, d), produce the (n, n) matrix S where S[i, j] is the cosine similarity between rows X[i] and X[j]. Cosine similarity is the workhorse of text and embedding retrieval, and interviewers use it to check whether you can turn a per-pair formula into one clean normalize-then-multiply.
Definition
Cosine similarity measures the angle between two vectors, ignoring their magnitudes:
S[i, j] = (X[i] . X[j]) / (||X[i]|| * ||X[j]||)
If you first scale each row to unit length, u = x / ||x||, then the formula collapses to a plain dot product:
S[i, j] = u_i . u_j
So normalizing every row once and taking X_norm @ X_norm.T produces the whole matrix in a single matrix product.
Task
Complete cosine_similarity_matrix(X) so it returns the (n, n) similarity matrix, fully vectorized with NO Python loops. Compute each row’s L2 norm, divide each row by its norm to get unit vectors, then multiply the unit matrix by its transpose. A zero-norm row (all zeros) has no direction: treat its unit vector as all zeros so its similarity with everything, including itself, is 0 rather than nan. Do not call sklearn, scipy, or any built-in cosine helper.
Example
X = np.array([[1.0, 0.0],
[0.0, 2.0],
[1.0, 1.0],
[0.0, 0.0]])
cosine_similarity_matrix(X)
# -> array([[1. , 0. , 0.70710678, 0. ],
# [0. , 1. , 0.70710678, 0. ],
# [0.70710678, 0.70710678, 1. , 0. ],
# [0. , 0. , 0. , 0. ]])
Row 0 is orthogonal to row 1 (similarity 0) and at 45 degrees to row 2 (similarity 1/sqrt(2) ≈ 0.7071). Row 3 is the zero vector, so every entry in its row and column is 0.
Constraints
1 <= n <= 10^4,1 <= d <= 10^3; use vectorized NumPy, no Python loop over pairs.- Values fit in float64. The output is symmetric and each non-zero row has
S[i, i] = 1. - Handle zero-norm rows without dividing by zero: their similarities are
0, notnan.