Во-первых, вам действительно не нужен p
в качестве параметра вашей функции: len(qs)
нужно вызывать только один раз, и это очень дешево.Если ваши входные данные всегда являются пустым массивом (и в данных обстоятельствах нет причин, по которым это не должно быть), qs.size
также подойдет.
Давайте начнем с переписывания f_map
.В цикле есть только кумулятивная сумма qs
(но начинающаяся с нуля), которую вы можете предварительно вычислить один раз (или хотя бы один раз за вызов внешней функции).
def f_map(i, j, cumsum_q):
return j + cumsum_q[i]
Где cumsum_q
будет определен в build_prolongation_operator
как
cumsum_q = np.roll(np.cumsum(qs), 1)
cumsum_q[0] = 0
Я уверен, что вы могли бы оценить полезность наличия в f_map
того же набора имен переменных, что и в build_prolongation_operator
.Чтобы сделать это еще проще, мы можем просто полностью удалить f_map
и использовать вместо этого выражение, которое оно представляет, в ваших условиях:
if i == k + cumsum_q[j]:
I[i, j] = 1
Цикл над k
тогда означает «если i
равно * 1024»* для any k
", установите элемент в 1. Если мы переписываем условие как i - cumsum_q[j] == k
, вы можете видеть, что нам вообще не нужен цикл над k
.i - cumsum_q[j]
будет равно некоторым k
в диапазоне [0, qs[j])
, если оно неотрицательно и строго меньше qs[j]
.Вы можете просто проверить
if i >= cumsum_q[j] and i - cumsum_q[j] < qs[j]:
I[i, j] = 1
Это сокращает ваш цикл до одной итерации на элемент матрицы.Вы не можете сделать лучше, чем это:
def build_prolongation_operator_optimized(qs):
'''
The prolongation operator describes the relationship between
the coarse and fine bases:
V_coarse = np.dot(V_fine, I)
'''
qs = np.asanyarray(qs)
p = qs.size
cumsum_q = np.roll(np.cumsum(qs), 1)
q = cumsum_q[0]
cumsum_q[0] = 0
I = np.zeros([q, p])
for i in range(0, q):
for j in range(0, p):
# if BV i is a child of j, we set I[i, j] = 1
if 0 <= i - cumsum_q[j] < qs[j]:
I[i, j] = 1
return I
Теперь, когда вы теперь знаете формулу для каждой ячейки, вы можете просто вычислить целую матрицу для вас по существу в одной строке с использованием широковещания:
def build_prolongation_operator_numpy(qs):
qs = np.asanyarray(qs)
cumsum_q = np.roll(np.cumsum(qs), 1)
q = cumsum_q[0]
cumsum_q[0] = 0
i_ = np.arange(q).reshape(-1, 1) # Make this a column vector
return (i_ >= cumsum_q) & (i_ - cumsum_q < qs)
Я запустил небольшую демонстрацию, чтобы (A) предлагаемые решения получили тот же результат, что и ваш оригинал, и (B) работали быстрее:
In [1]: p = 10
In [2]: q = np.random.randint(3, size=p)
In [3]: ops = (
... build_prolongation_operator(p, qs),
... build_prolongation_operator_optimized(qs),
... build_prolongation_operator_numpy(qs),
... build_prolongation_operator_RaunaqJain(p, qs),
... build_prolongation_operator_gboffi(p, qs),
... )
In [4]: np.array([[(op1 == op2).all() for op1 in ops] for op2 in ops])
Out[4]:
array([[ True, True, True, True, True],
[ True, True, True, True, True],
[ True, True, True, True, True],
[ True, True, True, True, True],
[ True, True, True, True, True]])
In [5]: %timeit build_prolongation_operator(p, qs)
321 µs ± 890 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [6]: %timeit build_prolongation_operator_optimized(qs)
75.1 µs ± 1.85 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [7]: %timeit build_prolongation_operator_numpy(qs)
24.8 µs ± 77.7 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [8]: %timeit build_prolongation_operator_RaunaqJain(p, qs)
28.5 µs ± 1.55 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [9]: %timeit build_prolongation_operator_gboffi(p, qs)
31.8 µs ± 772 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [10]: %timeit build_prolongation_operator_gboffi2(p, qs)
26.6 µs ± 768 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Как вы можете видеть,самый быстрый вариант - полностью векторизованный, но параметры @ RaunaqJain и @ gboffi * идут очень близко за секунду.
Примечание
Мое векторизованное решение создает логический массив.Если вы не хотите этого, либо используйте I.astype(...)
для преобразования в нужный тип dtype, либо просмотрите его как массив np.uint8
: I.view(dtype=np.uint8)
.