Alternating least squares
Fitting CP means choosing A, B, C to minimise ‖T − Σᵣ aᵣ ∘ bᵣ ∘ cᵣ‖², and that is not convex in all three at once: the model multiplies unknowns together. There is no SVD recipe, and unlike Tucker you cannot go one mode at a time.
Fix B and C, and the model is linear in A. Unfolding along mode 0 gives T₍₀₎ ≈ A (B ⊙ C)ᵀ, an ordinary least-squares problem that section 07’s pseudoinverse solves:
A = unfold(T, 0) @ khatri_rao(B, C) @ np.linalg.pinv((B.T @ B) * (C.T @ C))
ALS does that for A, then B, then C, sweep after sweep. Each step is an exact convex solve, so the error never goes up. It is not guaranteed to reach the global minimum: the non-convexity moved into the choice of starting point, and at higher ranks different seeds stop at different errors.