Mistake: hour rank 3 means only three hours survive
“Tucker with hour rank 3 keeps three of the 24 hours and discards the rest.”
T = rng.random((24, 40))
U, s, Vt = np.linalg.svd(T, full_matrices=False)
U3 = U[:, :3]
assert U3.shape == (24, 3)
recon = U3 @ np.diag(s[:3]) @ Vt[:3]
assert recon.shape == (24, 40)
assert len(np.unique(recon.round(6), axis=0)) == 24
The hour basis has one row per real hour and one column per learned direction: U3 is (24, 3), and all 24 reconstructed hour profiles are still distinct.
Rank 3 buys three directions to describe variation across all 24 hours. Rank is a budget, not a subset.