Вопрос или проблема
Вопрос:
Вот простая функция, которая работает с numpy, но не с numba:
# @numba.jit(nopython=True, fastmath=False, parallel=False)
def testgetvalue(tgvarray, tgvindex):
tgvalue = tgvarray[tuple(tgvindex)]
return tgvalue
Как мне сделать версию этой функции, которая будет работать в numba?
Я пробовал:
@numba.jit(nopython=True, fastmath=False, parallel=False)
def testgetvalue2(tgvarray, tgvindex):
tgvalue = tgvarray[tuple(tgvindex)]
currentdex = tgvindex[0]
tgvtemp = tgvarray[currentdex]
for idx in range(1, len(tgvindex)):
currentdex = tgvindex[idx]
tgvtemp = tgvtemp[currentdex]
return tgvalue
но это также не работает в numba
Я нашел этот вопрос, в котором ответ говорит, что это возможно:
В более общем плане, вы не можете создать N-арный кортеж, где N переменное, в функции Numba. Однако вы можете вместо этого сгенерировать и скомпилировать функцию для конкретного N. Если N очень мал, например, <15
Это решило бы мою проблему, но ответ не объясняет, как сгенерировать и затем скомпилировать функцию для конкретного N… если только предложение не заключается в том, чтобы мне написать скрипт, который создаст .py файл, определяющий функцию с декоратором jit, которую я затем смогу вызвать из .py файла. Думаю, это может сработать, учитывая нечастые изменения в измерениях. Я не уверен, можно ли это считать лучшей практикой, но я начну работать над скриптом для генерации .py файлов, пока кто-то не даст иного ответа.
Пожалуйста, продолжайте читать, прежде чем предполагать, что я дублирую что-то вроде этого вопроса, потому что моя настоящая проблема не обязательно связана с кортежами, т.е. версия вопроса в скобках.
Почему я задаю этот вопрос:
У меня есть код, в котором количество измерений объекта массива иногда может варьироваться от 1 до 15 измерений. Но как только происходит изменение, будет проведено десятки тысяч повторяющихся операций над этим многомерным массивом. Для многих из этих операций мне было бы желательно использовать массив индексов для изменения значения в месте многомерного массива.
Это ведет к альтернативному вопросу в скобках:
В предыдущей версии моего кода я преобразовал многомерные индексы в одномерные, взяв массив размеров в каждом измерении и сделав:
multipliers = np.cumprod(array_of_sizes_in_each_dimension)
multipliers = np.roll(multipliers, 1)
multipliers[0] = 1
Затем я могу умножить каждое значение в многомерном индексе на соответствующее значение в multipliers
, чтобы получить мой одномерный индекс. Это работает хорошо, когда я перехожу от многомерного индекса к одномерному индексу. Однако мне не приходит в голову быстрая функция для случаев, когда мне нужно перейти от одномерного индекса к многомерному индексу. В настоящее время моя самая быстрая версия – построить таблицу поиска, т.е. two_dimensional_array
размером np.prod(array_of_sizes_in_each_dimension)
X len(array_of_sizes_in_each_dimension)
, которая имеет списки всех многомерных индексов, так что two_dimensional_array[one_dimensional_index]
возвращает соответствующий многомерный индекс. Это работает хорошо, когда мой многомерный массив оказывается только с несколькими измерениями и коротким в каждом из этих измерений. Однако, по мере увеличения количества измерений, размер two_dimensional_array
становится таким большим, что я сталкиваюсь с проблемой памяти, и код замедляется на несколько порядков (например, 8 минут для 3-мерного массива и 8 дней для 11-мерного массива). Поэтому, если кто-то имеет идею для быстрой функции, чтобы заменить таблицу поиска two_dimensional_array
, это также решит мою проблему.
На самом деле возможно создать кортеж внутри numba, если его размер известен на этапе компиляции, используя numba.np.unsafe.ndarray
. Эта функция помечена как небезопасная, и я не нашел никаких следов ее в документации, поэтому не знаю, насколько она надежна в долгосрочной перспективе, но она хорошо работала для numba 0.58
до 0.60
. Например, вы можете использовать ее в качестве формы для инициализации nd-мерного массива.
import numpy as np
import numba
@numba.njit
def numba_make_tuple(old_tuple):
CONST = len(old_tuple)
a = np.empty((CONST,), dtype=np.int64)
for i in range(CONST):
a[i] = i
new_tuple = numba.np.unsafe.ndarray.to_fixed_tuple(a, CONST)
return new_tuple
t1 = (1,)
nt1 = numba_make_tuple(t1)
print(nt1, type(t1))
t2 = (2,3,1)
nt2 = numba_make_tuple(t2)
print(nt2, type(t2))
Обратите внимание, что я использую кортеж в качестве входных данных, чтобы обеспечить знание CONST
на этапе компиляции. Я не нашел ничего в официальной документации, но это обсуждается, например, на https://github.com/numba/numba/issues/8812
Попробуйте это:
import numba
import numpy as np
@numba.njit
def testgetvalue(tgvarray, tgvindex):
tgvtemp = tgvarray
for idx in range(len(tgvindex)):
tgvtemp = tgvtemp[tgvindex[idx]]
return tgvtemp
Ответ или решение
Для решения проблемы, описанной в вашем вопросе, мы можем воспользоваться возможностями библиотеки Numba для работы с кортежами фиксированной длины и обхода многомерных индексов. Ниже представлен полный ответ, который охватывает оба аспекта: создание кортежа фиксированной длины и эффективное вычисление индексов в многомерном массиве.
1. Создание кортежа фиксированной длины в Numba
Как вы уже отметили, в Numba можно создать кортеж фиксированной длины, знание длины которого доступно на этапе компиляции. Мы можем использовать numba.np.unsafe.ndarray
для создания фиксированных кортежей. Вот пример функции для создания кортежа с заранее известной длиной:
import numpy as np
import numba
@numba.njit
def make_fixed_tuple(data):
length = len(data)
array = np.empty(length, dtype=np.int64)
for i in range(length):
array[i] = data[i]
return numba.np.unsafe.ndarray.to_fixed_tuple(array, length)
# Пример использования
t1 = (1,)
nt1 = make_fixed_tuple(t1)
print(nt1, type(nt1)) # Вывод: (1,) <class 'tuple'>
t2 = (2, 3, 1)
nt2 = make_fixed_tuple(t2)
print(nt2, type(nt2)) # Вывод: (2, 3, 1) <class 'tuple'>
2. Получение значения по многомерным индексам
Теперь, чтобы эффективно работать с многомерными массивами и индексами, можем использовать следующий подход. Вместо использования tuple
, мы можем просто использовать стандартные для Numba циклы для доступа к элементам массива. Это позволяет избежать сложностей с кортежами. Пример кода:
@numba.njit
def testgetvalue(tgvarray, tgvindex):
tgvtemp = tgvarray
for idx in range(len(tgvindex)):
tgvtemp = tgvtemp[tgvindex[idx]]
return tgvtemp
# Пример использования
array = np.array([[1, 2], [3, 4]])
index = [0, 1] # соответствует array[0][1] = 2
value = testgetvalue(array, index)
print(value) # Вывод: 2
3. Преобразование индексов из одномерного в многомерный
Чтобы эффективно преобразовать одномерные индексы в многомерные, мы можем использовать следующий алгоритм:
@numba.njit
def one_to_multi(one_dim_index, array_shape):
multi_index = np.zeros(len(array_shape), dtype=np.int64)
for i in range(len(array_shape) - 1, -1, -1):
multi_index[i] = one_dim_index % array_shape[i]
one_dim_index //= array_shape[i]
return multi_index
# Пример использования
shape = (2, 3) # Двумерный массив с размерностью 2x3
one_index = 5 # Одномерный индекс
multi_index = one_to_multi(one_index, shape)
print(multi_index) # Вывод: [1 2], что соответствует элементу в массиве
Этот метод позволяет избежать создания больших таблиц соответствий и тем самым значительно ускоряет выполнение.
Заключение
С применением вышеуказанных решений, вы сможете эффективно работать с многомерными массивами в Numba, используя фиксированные кортежи и простые циклы для доступа к элементам. Это не только увеличит производительность, но и сократит использование памяти при работе с большими данными.