TLDR; I would opt for @Parfait's use of test_gen_sum based on benchmarking across various values of n, p, and y. Keeping old answer here for continuity's sake.
Assess how n, p, y affect choice of algorithm
This analysis is done using @Parfait's functions as a means of determining if there is really one best solution or if there are a family of solutions based on values of n, p, and y.
import numpy as np
import pytest # This code also requires the pytest-benchmark plugin
def test_for_sum(n, p, y):
random_state = np.random.RandomState(1)
X = random_state.rand(p, n)
X_sum = np.zeros((n, n))
# The length of weights are not related to X's dims,
# but will always be smaller
weights = random_state.rand(y)
for k in range(y):
X_sum += np.dot(X.T[:, k + 1:],
X[:p - (k + 1), :]) * weights[k]
return X_sum
def test_list_sum(n, p, y):
random_state = np.random.RandomState(1)
X = random_state.rand(p, n)
X_sum = np.zeros((n, n))
# The length of weights are not related to X's dims,
# but will always be smaller
weights = random_state.rand(y)
matrix_list = [np.dot(X.T[:, k + 1:],
X[:p - (k + 1), :]) * weights[k] for k in range(y)]
X_sum = np.sum(matrix_list, axis=0)
return X_sum
def test_reduce_sum(n, p, y):
random_state = np.random.RandomState(1)
X = random_state.rand(p, n)
X_sum = np.zeros((n, n))
# The length of weights are not related to X's dims,
# but will always be smaller
weights = random_state.rand(y)
matrix_list = [(X.T[:, k + 1:] @
X[:p - (k + 1), :]) * weights[k] for k in range(y)]
X_sum = reduce(lambda x,y: x + y, matrix_list)
return X_sum
def test_concat_sum(n, p, y):
random_state = np.random.RandomState(1)
X = random_state.rand(p, n)
X_sum = np.zeros((n, n))
# The length of weights are not related to X's dims,
# but will always be smaller
weights = random_state.rand(y)
x_mat = np.concatenate([np.matmul(X.T[:, k + 1:],
X[:p - (k + 1), :]) for k in range(y)])
wgt_mat = np.concatenate([np.full((n,1), weights[k]) for k in range(y)])
mul_res = x_mat * wgt_mat
X_sum = mul_res.reshape(-1, n, n).sum(axis=0)
return X_sum
def test_matmul_sum(n, p, y):
random_state = np.random.RandomState(1)
X = random_state.rand(p, n)
X_sum = np.zeros((n, n))
# The length of weights are not related to X's dims,
# but will always be smaller
weights = random_state.rand(y)
# Use list comprehension and np.matmul
matrices_list = [np.matmul(X.T[:, k + 1:],
X[:p - (k + 1), :]) * weights[k] for k in range(y)]
# Sum matrices in list of matrices to get the final result
X_sum = np.sum(matrices_list, axis=0)
return X_sum
def test_gen_sum(n, p, y):
random_state = np.random.RandomState(1)
X = random_state.rand(p, n)
X_sum = np.zeros((n, n))
# The length of weights are not related to X's dims,
# but will always be smaller
weights = random_state.rand(y)
matrix_gen = (np.dot(X.T[:, k + 1:],
X[:p - (k + 1), :]) * weights[k] for k in range(y))
X_sum = sum(matrix_gen)
return X_sum
parameters = [
pytest.param(400, 800, 3)
,pytest.param(400, 2000, 3)
,pytest.param(400, 800, 750)
,pytest.param(400, 2000, 750)
]
@pytest.mark.parametrize('n,p,y', parameters)
def test_test_for_sum(benchmark, n, p, y):
benchmark(test_for_sum, n=n, p=p, y=y)
@pytest.mark.parametrize('n,p,y', parameters)
def test_test_list_sum(benchmark, n, p, y):
benchmark(test_list_sum, n=n, p=p, y=y)
@pytest.mark.parametrize('n,p,y', parameters)
def test_test_reduce_sum(benchmark, n, p, y):
benchmark(test_reduce_sum, n=n, p=p, y=y)
@pytest.mark.parametrize('n,p,y', parameters)
def test_test_concat_sum(benchmark, n, p, y):
benchmark(test_concat_sum, n=n, p=p, y=y)
@pytest.mark.parametrize('n,p,y', parameters)
def test_test_matmul_sum(benchmark, n, p, y):
benchmark(test_matmul_sum, n=n, p=p, y=y)
@pytest.mark.parametrize('n,p,y', parameters)
def test_test_gen_sum(benchmark, n, p, y):
benchmark(test_gen_sum, n=n, p=p, y=y)
n=400, p=800, y=3 (100 iterations)
- winner:
test_gen_sum

n=400, p=2000, y=3 (100 iterations)
- winner:
test_gen_sum

n=400, p=800, y=750 (10 iterations)
- winner:
test_gen_sum

n=400, p=2000, y=750 (10 iterations)
- winner:
test_gen_sum

OLD ANSWER
Smaller y values
I would definitely use np.matmul instead of np.dot this will get you the biggest boost in performance and in fact the documentation for np.dot will direct you to np.matmul for 2D array multiplication in lieu of np.dot.
I tested both np.dot and np.matmul with and without list comprehension and the pytest-benchmark results are here:

Btw pytest-benchmark is pretty slick and I highly recommend it at times like this to validate if an approach is truly performant.
Just using list comprehension has an almost negligible effect on np.matmul results and a negative effect on np.dot (albeit that is better form) in the scheme of things, but the combination of both changes yielded the best results in terms. I would warn that using list comprehensions tend to drive up the std. dev. of the runtime so you may see larger ranges in runtime performance than if you were to just use np.matmul.
Here is the code:
import numpy as np
def test_np_matmul_list_comprehension():
random_state = np.random.RandomState(1)
n = p = 1000
X = np.arange(n * n).reshape(p, n)
# The length of weights are not related to X's dims,
# but will always be smaller
y = 3
weights = [1, 1, 1]
# Use list comprehension and np.matmul
matrices_list = [np.matmul(X.T[:, k + 1:],
X[:p - (k + 1), :]) * weights[k] for k in range(y)]
# Sum matrices in list of matrices to get the final result
X_sum = np.sum(matrices_list, axis=0)
Larger y values
For larger values of y you are better off not using list comprehension. The mean/median runtime tends to be larger for both np.dot and np.matmul in both of those cases. Here are the pytest-benchmark results for (n=500,p=5000,y=750):

This is probably overkill, but I would rather err on the side of being too helpful :).